diff --git a/README.md b/README.md index d70794af..d01fd78c 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,12 @@ Special thanks to: - x3m77rus + Blueberryy for making russian translations, and updating some part of the code to support more translation text. - Blad3z for making Finnish translations. -- Diviously for the new Slender Fortress 2 Modified logo # Slender Fortress Modified Versions Since Kit is busy with his real life, Kenzzer is no longer working on SF2, I used the new versions to create the new modified versions to allow much more diversity to SF2. -![SF2M-Logo](https://user-images.githubusercontent.com/42941613/106558495-1359bf00-64e1-11eb-99ac-5e028217622d.png) +![SF2M-Logo](https://user-images.githubusercontent.com/42941613/229934962-2e6ba294-ea21-4256-badc-77c9002a16fc.png) Slender Fortress ================ diff --git a/TERMSOFSERVICE.md b/TERMSOFSERVICE.md deleted file mode 100644 index 8d00752b..00000000 --- a/TERMSOFSERVICE.md +++ /dev/null @@ -1,43 +0,0 @@ -# Terms of Service - -Welcome to Slender Fortress Modified. These are the terms and service for use of the gamemode which you may use in many differnt ways, including but not limited to content creation, server hosting, and beta testing. By hosting Slender Fortress Modified, you are agreeing to be bound by these Terms. If you disagree with any part of the terms then you may not use Slender Fortress Modified. - -## 1. Server Hosting - -Slender Fortress Modified is a Sourcemod plugin offered by Mentrillum for free. If you plan to only use the gamemode for a private server that no one else can join, you can skip this section of the Terms of Service. You may host a server publicly for full or part time and have any compatible plugin you'd like to add to your server. You may not host your server publicly or promote it in any way which includes announcements and video showcases if one of these conditions are met. - -### A. Stolen Content - -Any bosses found on the Github wiki that have not been taken down are allowed to be used without permission from the original creators (Exceptions being the Yakuza 0 bosses created by Astolfo Alter where no one is allowed to use them period). Obtaining any boss after a download is terminated for public download will depend if the original creator wants that content on your server still and if no other outside variables determine that decision. Any maps found on the Github wiki follow the same rules as the bosses. If you specifically want to add a certain boss or map that is not found on any public source or the Github page, you have to talk to the creator of the content about that. Failing to do so and making recreations without reextracting the boss assets, not remaking a map from the ground up, or outright copying and pasting a map results in your server having stolen content. Removing the stolen content will no longer violate this rule. Consider this your warning. - -### B. Explicit Content - -Any content found in your server that contains Not Safe For Work (NSFW) elements or is prone to violate server rules specifically against NSFW content is prohibited to add and keep. Slender Fortress' main audience consists of users under the age of 18. Showing off the content in video form will result in you having to take that video down, not privating the video or unlisting it, but deleting it. Very few exceptions can be made where you can put age restrictions on your videos. Any other content that is prone to violate any server rules outside of your server such as explicit slurs, enforcing political and religious beliefs, fetishes, signs of sexual elements, nudity or revealing elements, discrimination, and propaganda are prohibited. We allow bosses like The Twins if any of these elements are censored out enough. - -### C. Server Handling - -Hosing a server and promoting it with the sole purpose of luring children in to show any explicit content, trying to enforce beliefs on users with specific content, or traumetizing children is prohibited. This includes failing to enforce your server rules properly, exposing the alformentioned factors publicly, creating content to specifically enforce beliefs, or not creating any server rule that stops any of the alformentioned factors from happening. We do not take this part lightly, consider fixing your server if this rule is violated. - -## 2. Content Creation - -Creating any form of content follows the same rules as hosting a server with extra considerations to take into account. If you do not plan to show the content off to anyone which includes servers even after the content is fully finished, you may skip this part of the Terms of Service. You may release any content publicly or privately. By allowing any server to take your boss that is public, you also agree to let server owners or developers edit your boss without your permission or consent, but they are not allowed to release the edited version unless they get permission from you as the original creator unless they reextract and reimport assets to make it different from your version. - -### A. Remakes - -You are allowed to remake a boss if you specifically do not copy and paste the original boss and recreate the config as perfectly as possible if the boss is considered a private boss, this includes leaking private configs. It is also required to reextract and reimport assets if you can show that you did more than the original boss creator (EG adding new animations, changing the model's texture to be more appropriate with the original source, remaking a model by hand), it is also required you do more than just one of the example tasks allowing others to see that you did more than just copy and paste assets. Currently its debatable if you are unable to add something new to the boss if the original version added everything that you can't add, its best to ask the original porter and creator if you can remake the boss if this is the case. Remaking maps is allowed as long as you do not copy and paste a lot of map elements that impact the map heavily (EG the layout), designs and decorations are not considered as long as you are free to use the assets you want (EG assets found in TF2, HL2, etc). If you specifically want to make a new unofficial version of the map, it is best you talk with the map creator to negociate how both of you want to go about doing this. If the creator is impossible to reach in both creating areas, do your best to show that your unofficial version is unofficial. Restorations of long lost maps and bosses are allowed (EG Clocktower Office, Jaraxxus, Flame Atronach). - -### B. Content Submissions - -You may skip this part of the Terms of Service if you don't plan to submit any content to be displayed on the Github page for everyone to easily find and download or to any officially supported servers. Submitting any content to Mentrillum where it can be added on the Github wiki means you agree to follow the content creating guidelines before submitting your content. Any small detail that may hint at any of these violations will result in a request for removing that small detail. Failing to do so will not get your content added on the Github. If these details are noticed months after the content was added, it will be taken down immediately after the discovery and will only be readded if you remove those details. Content will also be reviewed based on the quality of the boss, we do not want carbon copy bosses sitting on the Github page. This does not mean your boss or map has to be balanced out perfectly. The same logic will apply to submitting content to servers that are officially supported (Disc-FF and District Zen). - -## 3. Experimental Builds - -A [Patreon](https://www.patreon.com/Mentrillum) exists where it contains all experimental builds that have not yet been released to the general public. You are allowed to host these builds publicly on your server. You are not allowed to distribute these builds to other people unless they to donated to the Patreon with proof. You are free to distribute the experimental builds of a specific version when the final release of that version comes out (EG 1.7.5 experimental builds). - -## 4. Consequences - -After you are caught violating any of these rules, you will be approached by one of the higher ups at the SF2M team and will be requested to take action to fix any of these violations which may or may not include deleting parts or all of a certain piece of content, adjusting your server rules, deleting parts or a whole video, etc. Going forth with these requests and proving you put in effort to fix these violations without question will not make anything major happen depending on if these violations were intentional or unintentional. Intentionally violating these terms and fixing the violations will result in a warning meaning you can still create content and host a server. Depending on if you just create content for servers or host a server and make videos about it, the second warning will be different. For just creating and submitting content boss or map related, you will be given a second warning if these violations intentionally happened which most of the time is the case. The third warning for creating and submitting content boss or map related or the second warning for hosting a server and making videos on that server will depend on which of the following actions you do. Creating and submitting content will result in a blacklist that every server owner can see which they can quickly make the choice to either avoid your content or carelessly add it either way, the blacklist will have an associated reason as to why this blacklist happened. Getting a second warning for hosting a server or making videos about a server showing content that violates these terms will also result in a blacklist but for average players. Neglecting to fix these actions in any way which includes ignoring any team member's request, being defiant about these requests, never responding to these team members, or continuing to violate these already violated terms to a greater degree will result in a immediate blacklist. - -## 5. Appeals - -If you are already blacklisted in any way but you believe you have changed enough to where you don't violate these terms anymore, you can negociate with Mentrillum proving these terms are no longer violated and you show you will not violate these terms any longer. These appeals can only be given after a one month grace period since the blacklist addition. If these appeals are accepted, your blacklist will be removed and you'll be back to square one in terms of the warning system. Rejected appeals typically mean either you haven't changed or you aren't trusted enough to create content, host a server, or make videos about both that may violate the terms, if this is the case you'll have to wait for an undetermined long period of time. Checking back in on if an appeal can be accepted in an annoying way will lessen the likelihood of the current and following appeals to be accepted. diff --git a/addons/sourcemod/configs/sf2/changelog.cfg b/addons/sourcemod/configs/sf2/changelog.cfg new file mode 100644 index 00000000..8bdcc09f --- /dev/null +++ b/addons/sourcemod/configs/sf2/changelog.cfg @@ -0,0 +1,36 @@ +"Changelog" +{ + "plugin" + { + "name" "New/Changed Features" + + "1" "Change the boss AI to use the behavior system." + "2" "Added a customizable changelog." + "3" "Added the PvE arena and some custom bosses." + "4" "Fixed the explosive dance attack one shotting anyone." + "5" "Sentries no longer target bosses (unless they are a PvE boss)." + "6" "Changed the class sprint speeds to be a consistent 350-400 HU/s." + "7" "Removed the mechanic where class sprint speed decreases overtime." + "8" "Crouch footsteps no longer alert bosses by default." + "9" "Tweaked the FOV change when sprinting." + "10" "Tweaked peeking to no longer be in the scared state." + "11" "The blink meter now only appears when you see a statue boss." + } + + "bosses" + { + "name" "Boss Changes" + + "1" "Decreased/Fixed the increase chase duration rate for bosses." + "2" "Reworked the boss projectiles to be customizable." + "3" "Bosses can now have 2.147 billion attacks." + "4" "Reworked the auto chase behavior to be player tied now." + "5" "Added boss death states." + "6" "Added boss smell states." + "7" "Improved chase upon look for bosses." + "8" "Added attack begin and attack end animations + sounds." + "9" "Added spatial/3D sounds for bosses." + "10" "Alerted bosses can now wander around." + "11" "Bosses can now 'look around' while in the idle state." + } +} \ No newline at end of file diff --git a/addons/sourcemod/configs/sf2/class_stats.cfg b/addons/sourcemod/configs/sf2/class_stats.cfg index b036a8ec..9d1f9f5c 100644 --- a/addons/sourcemod/configs/sf2/class_stats.cfg +++ b/addons/sourcemod/configs/sf2/class_stats.cfg @@ -77,12 +77,12 @@ { "scout" { - "runspeed" "305.0" + "runspeed" "400.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" - "sprint_multiplier" "1.08" + "sprint_multiplier" "1.05" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -118,12 +118,12 @@ "soldier" { - "runspeed" "280.0" + "runspeed" "350.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "0" + "sprint_loss_while_jumping" "0.0" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -159,12 +159,12 @@ "pyro" { - "runspeed" "290.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.33" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -200,12 +200,12 @@ "demoman" { - "runspeed" "280.0" + "runspeed" "350.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" - "sprint_multiplier" "1.15" + "sprint_multiplier" "1.1" "scare_sprint_multiplier" "1.667" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -241,12 +241,12 @@ "heavy" { - "runspeed" "280.0" + "runspeed" "350.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -282,12 +282,12 @@ "engineer" { - "runspeed" "295.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -323,12 +323,12 @@ "medic" { - "runspeed" "290.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" - "sprint_multiplier" "1.05" + "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.15" "can_pickup_health" "0" @@ -364,12 +364,12 @@ "sniper" { - "runspeed" "295.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -405,12 +405,12 @@ "spy" { - "runspeed" "300.0" + "runspeed" "370.0" "walkspeed" "190.0" - "danger_speed_multiplier" "1.28" - "sprint_multiplier" "1.05" + "danger_speed_multiplier" "1.34" + "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" diff --git a/addons/sourcemod/configs/sf2/specialrounds.cfg b/addons/sourcemod/configs/sf2/specialrounds.cfg index 1e35c152..3f58a1f1 100644 --- a/addons/sourcemod/configs/sf2/specialrounds.cfg +++ b/addons/sourcemod/configs/sf2/specialrounds.cfg @@ -33,8 +33,8 @@ "6" { - "display_text_hud" "Stealth Box of Doom - Spawns 2 extra bosses in slient mode." - "display_text_chat" "Stealth Box of Doom - They really wanna go MGS on your ass." + "display_text_hud" "SILENT SLENDER - More survivors, yet more bosses on silent!" + "display_text_chat" "SILENT SLENDER - 7 years and you still play me?!" "display_icon_hud" "d_purgatory" } @@ -193,74 +193,74 @@ } "29" - { - "display_text_hud" "SILENT SLENDER - More survivors, yet more bosses on silent!" - "display_text_chat" "SILENT SLENDER - Now its Lag Simulator 2020!" - "display_icon_hud" "d_purgatory" - } - - "30" { "display_text_hud" "PAGE REWARDS - Get a reward or get punished!" "display_text_chat" "PAGE REWARDS - Slender has left some goodies for us." "display_icon_hud" "leaderboard_dominated" } - "31" + "30" { "display_text_hud" "TINY BOSSES - Bosses size is reduced by 2!" "display_text_chat" "TINY BOSSES - Tiny boss is tiny, is funny to me!" "display_icon_hud" "d_purgatory" } - "32" + "31" { "display_text_hud" "IN THE 90s - Run for your life!" "display_text_chat" "IN THE 90s - Running in the 90s!" "display_icon_hud" "ico_kart" } - "33" + "32" { "display_text_hud" "TRIPLE BOSSES - Triple the bosses, triple the death!" "display_text_chat" "TRIPLE BOSSES - Escape the bosses... 3 times?!" "display_icon_hud" "leaderboard_streak" } - "34" + "33" { "display_text_hud" "MODified Bosses - You like pain and suffering? We got you!" "display_text_chat" "MODified Bosses - Pain and suffering on a new level, good luck..." "display_icon_hud" "d_purgatory" } - "35" + "34" { "display_text_hud" "BOSS ROULETTE - An RNG rigged version of Russian Roulette." "display_text_chat" "BOSS ROULETTE - Pick a page, pick a boss!" "display_icon_hud" "hud_taunt_menu_icon" } - "36" + "35" { "display_text_hud" "THANATOPHOBIA - The fear of death." "display_text_chat" "THANATOPHOBIA - Do not die, or your teammates will suffer..." "display_icon_hud" "d_skull" } - "37" + "36" { "display_text_hud" "WALL HAX - You can see the bosses through the walls!" "display_text_chat" "WALL HAX - GG EZ GET GOOD!" "display_icon_hud" "ico_build" } - "38" + "37" { "display_text_hud" "SINGLE PLAYER - Playing by yourself...?" "display_text_chat" "SINGLE PLAYER - Playing by yourself...?" "display_icon_hud" "number_1" } + + "38" + { + "display_text_hud" "BEATBOX - 3... 2... 1... Stop!" + "display_text_chat" "BEATBOX - 3... 2... 1... Stop!" + "display_icon_hud" "ico_notify_sixty_seconds" + } "jokes" { diff --git a/addons/sourcemod/gamedata/sf2.txt b/addons/sourcemod/gamedata/sf2.txt index a7bbe654..6babec27 100644 --- a/addons/sourcemod/gamedata/sf2.txt +++ b/addons/sourcemod/gamedata/sf2.txt @@ -6,67 +6,439 @@ { "CTFPlayer::WantsLagCompensationOnEntity" { - "linux" "335" - "windows" "334" + "linux" "336" + "linux64" "336" + "windows" "335" + "windows64" "335" } + "CTFWeaponBase::GetCustomDamageType" { - "linux" "386" - "windows" "380" + "linux" "388" + "linux64" "388" + "windows" "382" + "windows64" "382" } + "CBaseEntity::ShouldTransmit" { "linux" "21" + "linux64" "21" "windows" "20" + "windows64" "20" } - "CBaseEntity::GetSmoothedVelocity" - { - "linux" "146" - "windows" "145" - } + "CBaseEntity::UpdateTransmitState" { "linux" "22" + "linux64" "22" "windows" "21" + "windows64" "21" } + "CBaseProjectile::CanCollideWithTeammates" { - "linux" "230" - "windows" "229" + "linux" "231" + "linux64" "231" + "windows" "230" + "windows64" "230" } + "CTFPlayer::EquipWearable" { - "linux" "439" - "windows" "438" + "linux" "440" + "linux64" "440" + "windows" "439" + "windows64" "439" } + "CBaseTrigger::PassesTriggerFilters" { - "linux" "208" - "windows" "207" + "linux" "209" + "linux64" "209" + "windows" "208" + "windows64" "208" + } + + "CTFWeaponBase::GetWeaponID" + { + "windows" "380" + "linux" "386" + "linux64" "386" + "windows64" "380" + } + + "CBaseEntity::IsBaseCombatWeapon" + { + "windows" "91" + "linux" "92" + "linux64" "92" + "windows64" "91" + } + + "CBaseEntity::ChangeTeam" + { + "library" "server" + "linux" "97" + "linux64" "97" + "windows" "96" + "windows64" "96" + } + + "CTeam::AddPlayer" + { + "linux" "209" + "linux64" "209" + "windows" "208" + "windows64" "208" + } + + "CTeam::RemovePlayer" + { + "linux" "210" + "linux64" "210" + "windows" "209" + "windows64" "209" + } + + "CBaseGrenade::Explode" + { + "library" "server" + "linux" "236" + "linux64" "236" + "windows" "235" + "windows64" "235" + } + + "CTFBaseRocket::Explode" + { + "library" "server" + "linux" "240" + "linux64" "240" + "windows" "239" + "windows64" "239" + } + + "CBaseEntity::VPhysicsUpdate" + { + "library" "server" + "linux" "164" + "linux64" "164" + "windows" "163" + "windows64" "163" + } + + "CTFSniperRifle::GetPenetrateType" + { + "library" "server" + "linux" "407" + "linux64" "407" + "windows" "400" + "windows64" "400" + } + + "CBaseEntity::Deflected" + { + "linux" "160" + "linux64" "160" + "windows" "159" + "windows64" "159" + } + + "CBasePlayer::Event_Killed" + { + "linux" "69" + "linux64" "69" + "windows" "68" + "windows64" "68" + } + + "CBaseObject::OnTakeDamage" + { + "linux" "65" + "linux64" "65" + "windows" "65" + "windows64" "65" } } + "Signatures" { "CTFPlayer::PlaySpecificSequence" { - "library" "server" - "linux" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" - "windows" "\x55\x8B\xEC\x53\x56\x8B\x75\x08\x57\x56\x8B\xF9\xE8\x2A\x2A\x2A\x2A\x8B\xD8" + "library" "server" + "linux" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" + "linux64" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" + "windows" "\x55\x8B\xEC\x56\xFF\x75\x08\x8B\xF1\xE8\x2A\x2A\x2A\x2A\x8B\xCE\x83\xF8\xFF" + "windows64" "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x48\x8B\xFA\x48\x8B\xD9\xE8\x2A\x2A\x2A\x2A\x48\x8B\xCB\x83\xF8\xFF\x74\x2A" } + "CBaseTrigger::PointIsWithin" { - "library" "server" - "linux" "@_ZN12CBaseTrigger13PointIsWithinERK6Vector" - "windows" "\x53\x8B\xDC\x83\xEC\x2A\x83\xE4\x2A\x83\xC4\x2A\x55\x8B\x6B\x2A\x89\x6C\x2A\x2A\x8B\xEC\x81\xEC\x2A\x2A\x2A\x2A\x8B\x43\x2A\x56\x50\x8B\xF1" + "library" "server" + "linux" "@_ZN12CBaseTrigger13PointIsWithinERK6Vector" + "linux64" "@_ZN12CBaseTrigger13PointIsWithinERK6Vector" + "windows" "\x53\x8B\xDC\x83\xEC\x08\x83\xE4\xF0\x83\xC4\x04\x55\x8B\x6B\x04\x89\x6C\x24\x04\x8B\xEC\x81\xEC\xAC\x00\x00\x00" + "windows64" "\x48\x81\xEC\xF8\x00\x00\x00\xF3\x0F\x10\x02" } // "CreateEntityByName( %s, %d ) - CreateEdict failed." "CreateEntityByName" { - "library" "server" - "linux" "@_Z18CreateEntityByNamePKci" - "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x0C\x83\xFE\xFF\x74\x24\x8B\x0D" + "library" "server" + "linux" "@_Z18CreateEntityByNamePKci" + "linux64" "@_Z18CreateEntityByNamePKci" + "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x0C\x83\xFE\xFF\x74\x24\x8B\x0D" + "windows64" "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x8B\xDA\x48\x8B\xF9\x83\xFA\xFF" + } + + "CBaseAnimating::LookupBone" // int CBaseAnimating::LookupBone( const char *szName ) + { + "library" "server" + "windows" "\x55\x8B\xEC\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\x83\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x8B\xCE\xE8\x2A\x2A\x2A\x2A\x8B\x86\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x83\x38\x00\x74\x2A\xFF\x75\x2A\x50\xE8\x2A\x2A\x2A\x2A\x83\xC4\x08\x5E" + "windows64" "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x80\xB9\x65\x04\x00\x00\x00\x48\x8B\xFA\x48\x8B\xD9\x75\x2A\x48\x83\xB9\xC0\x05\x00\x00\x00\x75\x2A\xE8\x2A\x2A\x2A\x2A\x48\x85\xC0\x74\x2A\x48\x8B\xCB\xE8\x2A\x2A\x2A\x2A\x48\x8B\x8B\xC0\x05\x00\x00\x48\x85\xC9\x74\x2A\x48\x83\x39\x00\x74\x2A\x48\x8B\xD7\x48\x8B\x5C\x24\x30" + "linux" "@_ZN14CBaseAnimating10LookupBoneEPKc" + "linux64" "@_ZN14CBaseAnimating10LookupBoneEPKc" + } + + "CBaseAnimating::GetBonePosition" // void CBaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x30\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x6C\x24\x10\x48\x89\x74\x24\x18\x57\x48\x83\xEC\x50\x80\xB9\x65\x04\x00\x00\x00" + "linux" "@_ZN14CBaseAnimating15GetBonePositionEiR6VectorR6QAngle" + "linux64" "@_ZN14CBaseAnimating15GetBonePositionEiR6VectorR6QAngle" + } + + "Studio_SeqVelocity" + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x20\x53\x56\x8B\x75\x0C\x57\x8B\x7D\x08" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x81\xEC\x80\x00\x00\x00\x0F\x29\x74\x24\x70\x49\x8B\xF9" + "linux" "@_Z18Studio_SeqVelocityPK10CStudioHdrifPKfR6Vector" + "linux64" "@_Z18Studio_SeqVelocityPK10CStudioHdrifPKfR6Vector" + } + + "CBaseEntity::PhysicsDispatchThink" + { + "library" "server" + "linux" "@_ZN11CBaseEntity20PhysicsDispatchThinkEMS_FvvE" + "linux64" "@_ZN11CBaseEntity20PhysicsDispatchThinkEMS_FvvE" + "windows" "\x55\x8B\xEC\xA1\x2A\x2A\x2A\x2A\x83\xEC\x08\x83\x78\x30\x00\x53" + "windows64" "\x48\x89\x74\x24\x18\x57\x48\x83\xEC\x60\x48\x8B\x05\x87\xD6\xA8\x00" + } + + "CBaseEntity::GetNextThink" + { + "library" "server" + "linux" "@_ZN11CBaseEntity12GetNextThinkEPKc" + "linux64" "@_ZN11CBaseEntity12GetNextThinkEPKc" + "windows" "\x55\x8B\xEC\x8B\x45\x08\x56\x8B\xF1\x85\xC0\x75\x2A\x8B\x86\xD0\x00\x00\x00\x89\x45\x08" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x83\xEC\x30\x0F\x29\x74\x24\x20\x0F\x28\xF2\x49\x8B\xF1" + } + + "GetGlobalTeam" + { + "library" "server" + "linux" "@_Z13GetGlobalTeami" + "linux64" "@_Z13GetGlobalTeami" + "windows" "\x55\x8B\xEC\x8B\x4D\x08\x85\xC9\x78\x2A\x3B\x0D\x2A\x2A\x2A\x2A\x7D\x2A\xA1\x2A\x2A\x2A\x2A\x8B\x04\x88\x5D\xC3" + "windows64" "\x85\xC9\x78\x2A\x3B\x0D\xBE\x0D\xA4\x00" + } + + "CTFTeam::AddObject" + { + "library" "server" + "linux" "@_ZN7CTFTeam9AddObjectEP11CBaseObject" + "linux64" "@_ZN7CTFTeam9AddObjectEP11CBaseObject" + "windows" "\x80\xB9\x72\x0C\x00\x00\x00\x74\x2A\x80\xB9\xD1\x03\x00\x00\x00" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x6C\x24\x18\x48\x89\x74\x24\x20\x57\x48\x83\xEC\x50" + } + + "CTFTeam::RemoveObject" + { + "library" "server" + "linux" "@_ZN7CTFTeam12RemoveObjectEP11CBaseObject" + "linux64" "@_ZN7CTFTeam12RemoveObjectEP11CBaseObject" + "windows" "\x55\x8B\xEC\x56\x8B\xF1\x83\xBE\xF0\x03\x00\x00\x00" + "windows64" "\x40\x53\x56\x48\x83\xEC\x28\x83\xB9\x20\x05\x00\x00\x00" + } + + "CWeaponMedigun::AllowedToHealTarget" + { + "library" "server" + "linux" "@_ZN14CWeaponMedigun19AllowedToHealTargetEP11CBaseEntity" + "linux64" "@_ZN14CWeaponMedigun19AllowedToHealTargetEP11CBaseEntity" + "windows" "\x55\x8B\xEC\x53\x8B\xD9\x56\x57\x8B\x93\x10\x02\x00\x00\x85\xD2\x0F\x84\x2A\x2A\x2A\x2A\xB9\xFF\x1F\x00\x00\x83\xFA\xFF\x74\x2A\x0F\xB7\xCA\xA1\x2A\x2A\x2A\x2A\xC1\xE1\x04\x8D\x78\x04\x03\xF9\x0F\x84\x2A\x2A\x2A\x2A\xC1\xEA\x10\x39\x57\x04\x0F\x85\x2A\x2A\x2A\x2A\x8B\x3F\x85\xFF\x0F\x84\x2A\x2A\x2A\x2A\x8B\x07\x8B\xCF\x8B\x80\x48\x01\x00\x00\xFF\xD0\x84\xC0\x0F\x84\x2A\x2A\x2A\x2A\x8B\x75\x08\x85\xF6\x74\x2A" + "windows64" "\x48\x89\x5C\x24\x10\x48\x89\x6C\x24\x18\x48\x89\x74\x24\x20\x57\x48\x83\xEC\x30\x48\x8B\xDA" + } + + "CTFPlayer::TeamFortress_SetSpeed" + { + "linux" "@_ZN9CTFPlayer21TeamFortress_SetSpeedEv" + "windows" "\x55\x8B\xEC\x83\xEC\x24\xA1\x2A\x2A\x2A\x2A\x56\x8B\xF1\x89\x75\xF0" + "linux64" "@_ZN9CTFPlayer21TeamFortress_SetSpeedEv" + "windows64" "\x40\x55\x48\x83\xEC\x60\x48\x8B\x05\x03\x3D\x7A\x00" + } + } + + "Functions" + { + "CTFFlameThrower::DeflectPlayer" + { + "signature" "CTFFlameThrower::DeflectPlayer" + "callconv" "thiscall" + "hooktype" "entity" + "return" "bool" + "this" "entity" + "arguments" + { + "target" + { + "type" "cbaseentity" + } + "owner" + { + "type" "cbaseentity" + } + "pos" + { + "type" "vectorptr" + } + } + } + + "CBaseEntity::PhysicsDispatchThink" + { + "signature" "CBaseEntity::PhysicsDispatchThink" + "callconv" "thiscall" + "return" "void" + "this" "entity" + "arguments" + { + "thinkFunc" + { + "type" "int" + } + } + } + + "CBaseGrenade::Explode" + { + "offset" "CBaseGrenade::Explode" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pTrace" + { + "type" "int" + } + "bitsDamageType" + { + "type" "int" + } + } + } + + "CTFBaseRocket::Explode" + { + "offset" "CTFBaseRocket::Explode" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pTrace" + { + "type" "int" + } + "pOther" + { + "type" "cbaseentity" + } + } + } + + "CWeaponMedigun::AllowedToHealTarget" + { + "signature" "CWeaponMedigun::AllowedToHealTarget" + "callconv" "thiscall" + "return" "bool" + "this" "entity" + "arguments" + { + "pTarget" + { + "type" "cbaseentity" + } + } + } + + "CBaseEntity::VPhysicsUpdate" + { + "offset" "CBaseEntity::VPhysicsUpdate" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pPhysics" + { + "type" "objectptr" + } + } + } + + "CBaseEntity::Deflected" + { + "offset" "CBaseEntity::Deflected" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pDeflectedBy" + { + "type" "cbaseentity" + } + "vecDir" + { + "type" "vectorptr" + } + } + } + + "CBasePlayer::Event_Killed" + { + "offset" "CBasePlayer::Event_Killed" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "info" + { + "type" "objectptr" + } + } + } + + "CBaseObject::OnTakeDamage" + { + "offset" "CBaseObject::OnTakeDamage" + "hooktype" "entity" + "return" "int" + "this" "entity" + "arguments" + { + "info" + { + "type" "objectptr" + } + } } } } -} +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 9442ac8a..ce4880fa 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -3,8 +3,15 @@ #endif #define _sf2_included -#define PLUGIN_VERSION "1.7.5.10 M" -#define PLUGIN_VERSION_DISPLAY "1.7.5.10 M" +#include +#include +#include +#include +#include +#include + +#define PLUGIN_VERSION "1.8.0 M RC1" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M RC1" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 @@ -12,7 +19,6 @@ #define MAX_BOSSES 64 #define MAX_NPCTELEPORTER 5 -#define MAX_NODES -1 #define MAX_TRAPS 64 #define MAX_CLASSES 9 @@ -44,7 +50,7 @@ enum SPECIALROUND_DOUBLEMAXPLAYERS, SPECIALROUND_LIGHTSOUT, SPECIALROUND_BEACON, - SPECIALROUND_DOOMBOX, + SPECIALROUND_SILENTSLENDER, SPECIALROUND_NOGRACE, SPECIALROUND_2DOUBLE, SPECIALROUND_DOUBLEROULETTE, @@ -67,7 +73,6 @@ enum SPECIALROUND_COFFEE, SPECIALROUND_PAGEDETECTOR, SPECIALROUND_CLASSSCRAMBLE, - SPECIALROUND_2DOOM, SPECIALROUND_PAGEREWARDS, SPECIALROUND_TINYBOSSES, SPECIALROUND_RUNNINGINTHE90S, @@ -77,6 +82,7 @@ enum SPECIALROUND_THANATOPHOBIA, SPECIALROUND_WALLHAX, SPECIALROUND_SINGLEPLAYER, + SPECIALROUND_BEATBOX, SPECIALROUND_MAXROUNDS }; @@ -84,12 +90,12 @@ enum enum { STATE_IDLE = 0, - STATE_WANDER, STATE_ALERT, STATE_CHASE, STATE_ATTACK, STATE_STUN, STATE_DEATHCAM, + STATE_DEATH, MAX_STATE }; @@ -107,7 +113,7 @@ enum SoundType enum { - Difficulty_Easy = 0, + Difficulty_Easy = 0, // Also called the none difficulty Difficulty_Normal, Difficulty_Hard, Difficulty_Insane, @@ -129,7 +135,6 @@ enum SF2BossType_Static = 0, SF2BossType_Statue, SF2BossType_Chaser, - SF2BossType_AdvancedChaser, SF2BossType_MaxTypes }; @@ -143,10 +148,16 @@ enum SF2BossSound_Stun = 5, SF2BossSound_Attack, SF2BossSound_AttackKilled, - SF2BossSound_RageAll, - SF2BossSound_RageTwo, - SF2BossSound_RageThree = 10, + SF2BossSound_Death, + SF2BossSound_TauntKill, + SF2BossSound_Smell = 10, + SF2BossSound_AttackBegin, + SF2BossSound_AttackEnd, SF2BossSound_SelfHeal, + SF2BossSound_RageAll, + SF2BossSound_RageTwo = 15, + SF2BossSound_RageThree, + SF2BossSound_Despawn, SF2BossSound_MaxSounds }; @@ -154,20 +165,23 @@ enum { SF2BossAnimation_Idle = 0, SF2BossAnimation_Walk, - SF2BossAnimation_WalkAlert, SF2BossAnimation_Attack, - SF2BossAnimation_Shoot, - SF2BossAnimation_Run = 5, + SF2BossAnimation_Run, SF2BossAnimation_Stun, - SF2BossAnimation_ChaseInitial, + SF2BossAnimation_ChaseInitial = 5, SF2BossAnimation_Rage, SF2BossAnimation_Spawn, - SF2BossAnimation_FleeInitial = 10, + SF2BossAnimation_FleeInitial, SF2BossAnimation_Heal, - SF2BossAnimation_DeathCam, - SF2BossAnimation_CrawlWalk, - SF2BossAnimation_CrawlRun, - SF2BossAnimation_MaxAnimations = 15 + SF2BossAnimation_DeathCam = 10, + SF2BossAnimation_Death, + SF2BossAnimation_TauntKill, + SF2BossAnimation_Smell, + SF2BossAnimation_AttackBegin, + SF2BossAnimation_AttackEnd = 15, + SF2BossAnimation_ProjectileShoot, + SF2BossAnimation_Despawn, + SF2BossAnimation_MaxAnimations } enum SF2RoundState @@ -185,7 +199,7 @@ enum { SF2Attribute_ReducedSpeedOnLook = 0, SF2Attribute_ReducedWalkSpeedOnLook, - SF2Attribute_AddStunHealhOnStun, + SF2Attribute_AddStunHealthOnStun, SF2Attribute_AlwaysLookAtTarget, SF2Attribute_AlwaysLookAtTargetWhileChasing, SF2Attribute_AlwaysLookAtTargetWhileAttacking = 5, @@ -201,15 +215,14 @@ enum SF2Attribute_SilentMarkPlayerForDeathOnScare = 15, SF2Attribute_IgnitePlayerOnDeath, SF2Attribute_AddSpeedOnStun, - SF2Attribute_AddMaxSpeedOnStun, SF2Attribute_DeathCamOnLowHealth, - SF2Attribute_AlertCopies = 20, - SF2Attribute_AlertCompanions, + SF2Attribute_AlertCopies, + SF2Attribute_AlertCompanions = 20, SF2Attribute_BlockWalkSpeedUnderDifficulty, SF2Attribute_ChaseTargetOnScare, SF2Attribute_IgnoreNonMarkedForChase, - SF2Attribute_ReducedAccelerationOnLook = 25, - SF2Attribute_AddAccelerationOnStun, + SF2Attribute_ReducedAccelerationOnLook, + SF2Attribute_AddAccelerationOnStun = 25, SF2Attribute_Max }; @@ -223,10 +236,38 @@ char g_SlenderVoiceList[SF2BossSound_MaxSounds][] = "sound_stun", "sound_attackenemy", "sound_attack_killed", + "sound_death", + "sound_taunt_kill", + "sound_smell", + "sound_attack_begin", + "sound_attack_end", + "sound_heal_self", "sound_rage", "sound_rage_2", "sound_rage_3", - "sound_heal_self" + "sound_despawn" +}; + +char g_SlenderAnimationsList[SF2BossAnimation_MaxAnimations][] = +{ + "idle", + "walk", + "attack", + "run", + "stun", + "chaseinitial", + "rage", + "spawn", + "fleestart", + "heal", + "deathcam", + "death", + "tauntkill", + "smell", + "attack_begin", + "attack_end", + "shoot", + "despawn" }; char g_AttributesList[SF2Attribute_Max][] = @@ -249,7 +290,6 @@ char g_AttributesList[SF2Attribute_Max][] = "silent mark player for death on scare", "ignite player on death", "add speed on stun", - "add max speed on stun", "death cam on low health", "alert copies", "alert companions", @@ -272,6 +312,31 @@ enum ParticleAttachment MAX_PATTACH_TYPES, }; +enum SF2NPCMoveTypes +{ + SF2NPCMoveType_Walk = 0, + SF2NPCMoveType_Run, + SF2NPCMoveType_Attack, + SF2NPCMoveType_Max +} + +enum RenevantWave +{ + RenevantWave_Normal = 0, + RenevantWave_IncreaseDifficulty, + RenevantWave_MultiEffect, + RenevantWave_BaconSpray, + RenevantWave_DoubleTrouble, + RenevantWave_DoomBox, + RenevantWave_90s, + RenevantWave_MarkForDeath, + RenevantWave_SingleBoss, + RenevantWave_AdminBoss, + RenevantWave_WallHax, + RenevantWave_Max +} + +#include #include #include @@ -289,10 +354,11 @@ enum ParticleAttachment #define SFF_HASSIGHTSOUNDS (1 << 10) #define SFF_HASSTATICLOOPLOCALSOUND (1 << 11) #define SFF_HASVIEWSHAKE (1 << 12) -#define SFF_COPIES (1 << 13) +#define SFF_NOCOPIES (1 << 13) #define SFF_ATTACKPROPS (1 << 14) #define SFF_WEAPONKILLS (1 << 15) #define SFF_WEAPONKILLSONRADIUS (1 << 16) +#define SFF_HASSTATICSHAKE (1 << 17) // Interrup conditions. #define COND_HEARDSUSPICIOUSSOUND (1 << 0) @@ -304,30 +370,26 @@ enum ParticleAttachment #define COND_CHASETARGETINVALIDATED (1 << 6) #define COND_SAWENEMY (1 << 7) #define COND_HEARDFLASHLIGHT (1 << 8) - -// Projectile flags. -#define PROJ_FIREBALL (1 << 0) -#define PROJ_ICEBALL (1 << 1) -#define PROJ_ROCKET (1 << 2) -#define PROJ_SENTRYROCKET (1 << 3) -#define PROJ_GRENADE (1 << 4) -#define PROJ_ARROW (1 << 5) -#define PROJ_MANGLER (1 << 6) -#define PROJ_BASEBALL (1 << 7) -#define PROJ_FIREBALL_ATTACK (1 << 8) -#define PROJ_ICEBALL_ATTACK (1 << 9) +#define COND_ALERT_TRIGGER (1 << 9) +#define COND_NEWENEMY (1 << 10) +#define COND_ENEMYNEAR (1 << 11) +#define COND_ENEMYVISIBLE (1 << 12) +#define COND_ENEMYRECHASE (1 << 13) +#define COND_DEBUG (1 << 14) +#define COND_ALERT_TRIGGER_POS (1 << 15) /** * Called after a boss profile is successfully loaded. * - * @param profile Profile name + * @param profile Profile name + * @param kv Key Values */ forward void SF2_OnBossProfileLoaded(const char[] profile, KeyValues kv); /** * Called before a boss profile is unloaded. * - * @param profile Profile name + * @param profile Profile name */ forward void SF2_OnBossProfileUnloaded(const char[] profile); @@ -362,12 +424,13 @@ forward void SF2_OnBossDespawn(int bossIndex); forward void SF2_OnBossChangeState(int bossIndex, int oldState, int newState); /** - * Called when the boss updates its animation. + * Called each time a boss update its animation. * - * @param bossIndex Boss index. - * @return If Plugin_Handled, animation change will be blocked. + * @param bossIndex Boss index. + * @param animation The animation name used. + * @return If Plugin_Handled will not update the boss animation. */ -forward Action SF2_OnBossAnimationUpdate(int bossIndex); +forward Action SF2_OnBossAnimationUpdate(int bossIndex, const char[] animation); /** * Called to determine the boss's run speed. @@ -416,11 +479,19 @@ forward void SF2_OnBossRemoved(int bossIndex); /** * Called when the boss gets stun by a player. * - * @param bossIndex Boss index. + * @param bossIndex Boss index. * @param client The client who stunned the boss, will return -1 if the stun was a flashlight stun. */ forward void SF2_OnBossStunned(int bossIndex, int client); +/** + * Called when the boss gets killed.. + * + * @param bossIndex Boss index. + * @param attacker The attacker who killed the boss. + */ +forward void SF2_OnBossKilled(int bossIndex, int attacker); + /** * Called when the boss cloaks. * @@ -442,6 +513,9 @@ forward void SF2_OnBossDecloaked(int bossIndex); */ forward void SF2_OnBossFinishSpawning(int bossIndex); +#pragma deprecated Use custom boss attacks. +forward void SF2_OnBossAttacked(int bossIndex, int attackIndex); + /** * Called when the boss finishes validating all attack indexes. * @@ -451,12 +525,33 @@ forward void SF2_OnBossFinishSpawning(int bossIndex); forward void SF2_OnBossPreAttack(int bossIndex, ArrayList attackArray); /** - * Called when the boss finishes an attack yet not exiting it's attack state. + * Called just before a chaser boss takes damage. * - * @param bossIndex Boss index. - * @param attackIndex Current attack index. + * @param chaser Chaser entity + * @param attacker Attacker entity + * @param inflictor Inflictor entity + * @param damage Damage about to be taken + * @return Returning Plugin_Stop will invalidate any damage taken by the boss. */ -forward void SF2_OnBossAttacked(int bossIndex, int attackIndex); +forward Action SF2_OnBossPreTakeDamage(SF2_ChaserBossEntity chaser, CBaseEntity attacker, CBaseEntity inflictor, float damage); + +/** + * Called whenever a chaser boss registers a player uses a flashlight for flashight stunning. + * + * @param chaser Chaser entity + * @param player Player using the flashlight + * @return Returning Plugin_Stop will invalidate any flashlight damage taken by the boss from the specified player. + */ +forward Action SF2_OnBossPreFlashlightDamage(SF2_ChaserBossEntity chaser, SF2_Player player); + +/** + * Called whenever a chaser boss gets a command string "suspend for action" using INextBotEventResponder.OnCommandString() + * + * @param chaser Chaser entity + * @param action Action to use + * @return Returning anything not Plugin_Continue will make the boss use the specified action and suspend for that + */ +forward Action SF2_OnChaserBossGetSuspendAction(SF2_ChaserBossEntity chaser, NextBotAction &action); /** * Called when pages have spawned into the map. @@ -578,13 +673,22 @@ forward void SF2_OnClientLooksAwayFromBoss(int client, int bossIndex); */ forward void SF2_OnClientStartDeathCam(int client, int bossIndex); +/** + * Called right before the player dies in a death cam + * + * @param client Client index. + * @param bossIndex Boss index. + */ +forward void SF2_OnClientPreKillDeathCam(int client, int bossIndex); + /** * Called when the player gets exits a boss's deathcam. * * @param client Client index. - * @param bossIndex Boss index. + * @param bossIndex Boss index. + * @param antiCamp Whether the client died due to the anti camping timer. */ -forward void SF2_OnClientEndDeathCam(int client, int bossIndex); +forward void SF2_OnClientEndDeathCam(int client, int bossIndex, bool antiCamp); /** * Called to determine a player's default walk speed. @@ -637,15 +741,46 @@ forward void SF2_OnClientDamagedByBoss(int client, int bossIndex, int inflictor, /** * Called when a player spawns as a proxy. + * + * @param client The player who spawned as a proxy */ forward void SF2_OnClientSpawnedAsProxy(int client); +/** + * Called when a player enters a PvP arena + * + * @param client The player who entered the PvP arena + */ +forward void SF2_OnClientEnterPvP(int client); + +/** + * Called when a player exits a PvP arena + * + * @param client The player who exits the PvP arena + */ +forward void SF2_OnClientExitPvP(int client); + +/** + * Called when a player enters a PvE arena + * + * @param client The player who entered the PvE arena + */ +forward void SF2_OnClientEnterPvE(int client); + +/** + * Called when a player exits a PvE arena + * + * @param client The player who exits the PvE arena + */ +forward void SF2_OnClientExitPvE(int client); + /** * Called whenever the Renevant gamemode triggers a new wave. * * @param wave The wave number that got triggered. + * @param condition The wave condition used. */ -forward void SF2_OnRenevantWaveTrigger(int wave); +forward void SF2_OnRenevantWaveTrigger(int wave, RenevantWave condition); /** * Called whenever the boss pack vote starts @@ -681,6 +816,71 @@ forward Action SF2_OnGroupEnterGame(int groupIndex); */ forward void SF2_OnEverythingLoaded(); +/** + * Called whenever the difficulty vote finishes + * + * @param list ArrayList containing all valid attacks + */ +forward void SF2_OnDifficultyVoteFinished(ArrayList list, bool isRunOff); + +/** + * Called when a Chaser boss is determining if it's allowed to use the custom attack. + * + * @param chaser Chaser entity + * @param attackName Attack name + * @param data Attack data + * @param target The target entity that the boss is trying to use this attack on + * @return Returning a value other than Plugin_Continue will block the boss from using + * the attack. + */ +forward Action SF2_OnIsBossCustomAttackPossible(SF2_ChaserBossEntity chaser, const char[] attackName, SF2ChaserBossProfileAttackData data, CBaseEntity target); + +/** + * Called when the Chaser boss has selected the custom attack and is requesting a + * NextBotAction to handle the attack logic. + * + * @param chaser Chaser entity + * @param attackName Attack name + * @param data Attack data + * @param target The target entity to use the attack on + * @param action Parameter to fill with the NextBotAction + * @return Return a value other than Plugin_Continue to use `action` + */ +forward Action SF2_OnBossGetCustomAttackAction(SF2_ChaserBossEntity chaser, const char[] attackName, SF2ChaserBossProfileAttackData data, CBaseEntity target, NextBotAction &action); + +/** + * Called when a projectile from SF2 has touched another entity successfully and has either exploded or dealt damage. + * + * @param projectile Projectile entity + * @param other Entity the projectile touched + */ +forward void SF2_OnProjectileTouch(SF2_Projectile_Base projectile, CBaseEntity other); + +/** + * Fires each time a boss starts an attack. + * + * @param chaser Chaser entity + * @param attackName Attack name + */ +forward void SF2_OnChaserBossStartAttack(SF2_ChaserBossEntity chaser, const char[] attackName); + +/** + * Fires after a boss has ended an attack. + * + * @param chaser Chaser entity + * @param attackName Attack name + */ +forward void SF2_OnChaserBossEndAttack(SF2_ChaserBossEntity chaser, const char[] attackName); + +/** + * Called whenever the gamemode picks a custom boss. Use this for creating entities. + * + * @param boss The chosen boss + * @param pos The selected position the boss will spawn on + * @param ang The selected angle the boss will spawn on + */ +forward void SF2_OnSelectedPvEBoss(const char[] boss, const float pos[3], const float ang[3]); + /** * Returns the global variable g_Config. * @@ -716,6 +916,15 @@ native bool SF2_IsRoundInGracePeriod(); */ native int SF2_GetCurrentDifficulty(); +/** + * Returns the current difficulty on the boss. This is only useful for boxing/PvE bosses. + * If the boss is not a PvE/boxing boss, then this will return the current gamemode difficulty. + * + * @param bossIndex Boss index. + * @return Integer of the difficulty. + */ +native int SF2_GetBossDifficulty(int bossIndex); + /** * Returns the current difficulty of the round. * @@ -745,6 +954,16 @@ native int SF2_GetSpecialRoundType(); */ native int SF2_IsInSpecialRound(int specialRound); +/** + * Retrieves name of the current boss pack + * + * @param buffer Buffer to store the pack name. + * @param bufferLen Max length of buffer. + * + * @return Number of bytes copied + */ +native int SF2_GetCurrentBossPack(char[] buffer, int bufferLen); + /** * Returns the current group a player is in. * @@ -769,6 +988,57 @@ native int SF2_GetClientQueuePoints(int client); */ native void SF2_SetClientQueuePoints(int client, int amount); +/** + * Returns whether or not the client is valid for SF2. + * + * @param client Client index. + * @return True if the player is valid, false if not. + */ +native bool SF2_IsValidClient(int client); + +/** + * Returns whether or not the client is crit boosted for SF2. + * + * @param client Client index. + * @return True if the player is crit boosted, false if not. + */ +native bool SF2_IsClientCritBoosted(int client); + +/** + * Returns whether or not the client is mini crit boosted for SF2. + * + * @param client Client index. + * @return True if the player is mini crit boosted, false if not. + */ +native bool SF2_IsClientMiniCritBoosted(int client); + +/** + * Returns whether or not the client is ubercharged for SF2. + * + * @param client Client index. + * @return True if the player is ubercharged, false if not. + */ +native bool SF2_IsClientUbercharged(int client); + +/** + * Returns whether or not the client is in a kart for SF2. + * + * @param client Client index. + * @return True if the player is in a kart, false if not. + */ +native bool SF2_IsClientInKart(int client); + +#if defined _tf2_included +/** + * Returns whether or not the client is in a specific condition for SF2. + * + * @param client Client index. + * @param condition Condition to check. + * @return True if the player is in a condition, false if not. + */ +native bool SF2_IsClientInCondition(int client, TFCond condition); +#endif + /** * Returns a bool about the client's elimination state. * @@ -793,6 +1063,14 @@ native bool SF2_IsClientInGhostMode(int client); */ native bool SF2_IsClientInPvP(int client); +/** + * Returns a bool if the client is in a Player vs. Environment zone or not. + * + * @param client Client index. + * @return True if the player is in a PvE zone, false if not. + */ +native bool SF2_IsClientInPvE(int client); + /** * Tells whether if the client is a Proxy or not. * @@ -990,8 +1268,9 @@ native bool SF2_IsClientInDeathCam(int client); * @param bossIndex Boss index. * @param vecLookPos The position the player should look at. * @param antiCamp Whether the player was camping or not. + * @param staticDeath Whether or not the player's death was caused by static. */ -native void SF2_ClientStartDeathCam(int client, int bossIndex, const float vecLookPos[3], bool antiCamp = false); +native void SF2_ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false); /** * Returns whether or not a proxy can be spawned, returns a teleport position or spawn point if defined and the result is true, works well with SF2_ClientForceProxy. @@ -1021,6 +1300,11 @@ native bool SF2_ClientForceProxy(int client, int bossIndex, const float teleport */ native void SF2_CollectAsPage(int pageEnt, int client); +/** + * Returns the empty page spawn points not occupied by a page in ArrayList form. + */ +native ArrayList SF2_GetEmptyPageSpawnPoints(); + /** * Returns the maximum boss count. * @@ -1068,16 +1352,6 @@ native int SF2_BossIDToBossIndex(int bossID); */ native int SF2_BossIndexToBossID(int bossIndex); -/** - * Updates the boss animation based on the defined state - * - * @param bossIndex Boss index. - * @param ent Entity index. - * @param state State. - * @param spawn Determines if the boss should use a spawn animation. -*/ -native void SF2_UpdateBossAnimation(int bossIndex, int ent, int state, bool spawn = false); - /** * Forces a boss to jump at a position. * @@ -1246,92 +1520,10 @@ native void SF2_GetBossEyePosition(int bossIndex, float eyePos[3]); */ native void SF2_GetBossEyePositionOffset(int bossIndex, float eyePosOffset[3]); -/** - * Returns the boss's field of view. - * - * @param bossIndex Boss index. - * @return Field of view. - */ -native float SF2_GetBossFOV(int bossIndex); - -/** - * If the boss is in a chase state, returns the time which the boss's persistency on the target ends. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time where persistency ends. - */ -native float SF2_GetBossTimeUntilNoPersistence(int bossIndex); - -/** - * If the boss is in a chase state, sets the time which the boss's persistency on the target ends. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time where persistency ends. - */ -native void SF2_SetBossTimeUntilNoPersistence(int bossIndex, float time); - -/** - * If the boss is in an alert state, returns the time which the boss should revert to idle state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time where chase state should revert to idle state. - */ -native float SF2_GetBossTimeUntilIdle(int bossIndex); - -/** - * If the boss is in an alert state, sets the time which the boss should revert to idle state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time where chase state should revert to idle state. - */ -native void SF2_SetBossTimeUntilIdle(int bossIndex, float time); - -/** - * If the boss is in a chase state, returns the time which the boss should revert to alert state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time where chase state should revert to alert state. - */ -native float SF2_GetBossTimeUntilAlert(int bossIndex); - -/** - * If the boss is in a chase state, sets the time which the boss should revert to alert state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time where chase state should revert to alert state. - */ -native void SF2_SetBossTimeUntilAlert(int bossIndex, float time); - -/** - * Returns whether or not the boss is stunnable. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return True if stunnable, false otherwise. - */ +#pragma deprecated Use SF2ChaserBossProfileAttackData.StunEnabled[Difficulty_Max] instead. native bool SF2_IsBossStunnable(int bossIndex); -/** - * Returns whether or not the boss is stunnable by flashlight. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return True if stunnable by flashlight, false otherwise. - */ +#pragma deprecated Use SF2ChaserBossProfileAttackData.StunFlashlight[Difficulty_Max] instead. native bool SF2_IsBossStunnableByFlashlight(int bossIndex); /** @@ -1364,37 +1556,6 @@ native float SF2_GetBossStunHealth(int bossIndex); */ native void SF2_SetBossStunHealth(int bossIndex, float health); -/** - * Returns the time where the boss is not under stun cooldown. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time when boss is not under stun cooldown. - */ -native float SF2_GetBossNextStunTime(int bossIndex); - -/** - * Sets the time where the boss is not under stun cooldown. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time when boss is not under stun cooldown. - */ -native float SF2_SetBossNextStunTime(int bossIndex, float time); - -/** - * Forces the boss to stop chasing. - * - * Only used with Chaser bosses. - * - * Note: If the boss is set to chase endlessly then this will not do anything. - * - * @param bossIndex Boss index. - */ -native void SF2_ForceBossGiveUp(int bossIndex); - /** * Gets the current goal position of the boss. * @@ -1405,30 +1566,6 @@ native void SF2_ForceBossGiveUp(int bossIndex); */ native void SF2_GetBossGoalPosition(int bossIndex, float position[3]); -/** - * Returns whether or not the boss is able to hear the player given sound type. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param client Client index. - * @param soundType The type of sound to check. - * @return True if boss can hear, false otherwise. - */ -native bool SF2_CanBossHearClient(int bossIndex, int client, SoundType soundType); - -/** - * Creates a sound hint for the boss to follow. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param soundType The type of sound hint to create. - * @param position The position of the sound. - * @param difficulty The difficulty. - */ -native void SF2_CreateBossSoundHint(int bossIndex, SoundType soundType, const float position[3], int difficulty); - /** * Returns if the given profile is loaded or not. * @@ -1437,392 +1574,1891 @@ native void SF2_CreateBossSoundHint(int bossIndex, SoundType soundType, const fl */ native bool SF2_IsBossProfileValid(const char[] profile); -/** - * Retrieves an integer keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2BossProfileData instead. native int SF2_GetBossProfileNum(const char[] profile, const char[] key, int defaultValue=0); -/** - * Retrieves a float keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2BossProfileData instead. native float SF2_GetBossProfileFloat(const char[] profile, const char[] key, float defaultValue=0.0); -/** - * Retrieves a string keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param bufferLen Max length of buffer. - * @param defaultValue Fallback value. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetBossProfileString(const char[] profile, const char[] key, char[] buffer, int bufferLen, const char[] defaultValue=""); -/** - * Retrieves a vector keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param defaultValue Fallback value. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetBossProfileVector(const char[] profile, const char[] key, float buffer[3], const float defaultValue[3]=NULL_VECTOR); -/** - * Retrieves integer difficulty keyvalues from the profile. - * - * @param kv KeyValues - * @param baseKeyName Name of the base key. - * @param buffer Values to store the key values in - * @param defaultValues Fallback values - * @return The value, or defaultValue if the key doesn't exist. - */ -native void SF2_GetBossProfileDifficultyNumValues(KeyValues kv, const char[] baseKeyName, int buffer[Difficulty_Max], const int defaultValues[Difficulty_Max] = { 0, ... }) - -/** - * Retrieves bool difficulty keyvalues from the profile. - * - * @param kv KeyValues - * @param baseKeyName Name of the base key. - * @param buffer Values to store the key values in - * @param defaultValues Fallback values - * @return The value, or defaultValue if the key doesn't exist. - */ -native void SF2_GetBossProfileDifficultyBoolValues(KeyValues kv, const char[] baseKeyName, bool buffer[Difficulty_Max], const bool defaultValues[Difficulty_Max] = { false, ... }) - -/** - * Retrieves float difficulty keyvalues from the profile. - * - * @param kv KeyValues - * @param baseKeyName Name of the base key. - * @param buffer Values to store the key values in - * @param defaultValues Fallback values - * @return The value, or defaultValue if the key doesn't exist. - */ -native void SF2_GetBossProfileDifficultyFloatValues(KeyValues kv, const char[] baseKeyName, float buffer[Difficulty_Max], const float defaultValues[Difficulty_Max] = { 0.0, ... }) - -/** - * Retrieves an integer keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native int SF2_GetBossAttackProfileNum(const char[] profile, const char[] key, int defaultValue=0, const int attackIndex); -/** - * Retrieves a float keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native float SF2_GetBossAttackProfileFloat(const char[] profile, const char[] key, float defaultValue=0.0, const int attackIndex); -/** - * Retrieves a string keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param bufferLen Max length of buffer. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native bool SF2_GetBossAttackProfileString(const char[] profile, const char[] key, char[] buffer, int bufferLen, const char[] defaultValue="", const int attackIndex); -/** - * Retrieves a vector keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native bool SF2_GetBossAttackProfileVector(const char[] profile, const char[] key, float buffer[3], const float defaultValue[3]=NULL_VECTOR, const int attackIndex); -/** - * Retrieves a random string keyvalue from a section within the profile. - * - * Example: In a profile, a valid section would look like this: - * - * "profile" - * { - * "section" - * { - * "1" "item1" - * "2" "item2" - * } - * } - * - * This function will randomly choose a string value within "section" if key = "section". - * - * @param profile Profile name. - * @param key Name of the key. Must be a section. - * @param buffer Buffer to store the value. - * @param bufferLen Max length of buffer. - * @param index Mainly used for attack indexes, selects a random sound from an array that is index. - * @return True if profile exists and key is set; false otherwise. - */ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetRandomStringFromBossProfile(const char[] profile, const char[] key, char[] buffer, int bufferLen, int index=-1); -/** - * Returns an attribute name if one exists - * @param bossIndex Boss index. - * @param attribute Name of attribute in enum. -*/ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetBossAttributeName(int bossIndex, int attribute); -/** - * Returns the value of an attribute. - * @param bossIndex Boss index. - * @param attribute Name of attribute in enum. - * @param defaultValue Default value of attribute. -*/ +#pragma deprecated Use SF2BossProfileData instead. native float SF2_GetBossAttributeValue(int bossIndex, int attribute); +#pragma deprecated Use SF2_ChaserBossEntity.AttackIndex instead. +native int SF2_GetBossCurrentAttackIndex(int bossIndex); + +#pragma deprecated Use SF2ChaserBossProfileAttackData.Type instead. +native int SF2_GetBossAttackIndexType(int bossIndex, int attackIndex); + +#pragma deprecated This no longer does anything. +native float SF2_GetBossTimeUntilNoPersistence(int bossIndex); + +#pragma deprecated This no longer does anything. +native void SF2_SetBossTimeUntilNoPersistence(int bossIndex, float time); + +#pragma deprecated Use SF2_BaseBossEntity.CurrentChaseDuration instead. +native float SF2_GetBossTimeUntilAlert(int bossIndex); + +#pragma deprecated Use SF2_BaseBossEntity.CurrentChaseDuration instead. +native void SF2_SetBossTimeUntilAlert(int bossIndex, float time); + +#pragma deprecated Use SF2_ChaserBossEntity.PerformVoice instead. +native void SF2_PerformBossVoice(int bossIndex, const int attackIndex = -1, int soundType = -1); + +#pragma deprecated Use SF2_ChaserBossEntity.CreateSoundHint instead. +native void SF2_CreateBossSoundHint(int bossIndex, SoundType soundType, const float position[3], int difficulty); + +#pragma deprecated Use SF2ChaserBossProfileAttackData.Damage[Difficulty_Max] instead. +native float SF2_GetBossAttackIndexDamage(int bossIndex, int attackIndex, int difficulty); + +#pragma deprecated Use SF2ChaserBossProfileAttackData.DamageType[Difficulty_Max] instead. +native int SF2_GetBossAttackIndexDamageType(int bossIndex, int attackIndex); + +#pragma deprecated Use SF2_BaseBossEntity.ResetProfileAnimation instead. +native void SF2_UpdateBossAnimation(int bossIndex, int ent, int state, bool spawn = false); + /** - * Returns the projectile type a boss uses. + * Returns the teleport think timer in Handle form * * Only used with Chaser bosses. * * @param bossIndex Boss index. */ -native int SF2_GetBossProjectileType(int bossIndex); +native Handle SF2_GetBossTeleportThinkTimer(int bossIndex); /** - * Returns the current attack index of the boss. - * - * Only used with Chaser bosses. + * Sets the teleport think timer and destroys the old think timer * * @param bossIndex Boss index. - * @return Attack index. + * @param timer New timer (Can also be null). */ -native int SF2_GetBossCurrentAttackIndex(int bossIndex); +native void SF2_SetBossTeleportThinkTimer(int bossIndex, Handle timer); /** - * Sets the boss' current attack index. - * - * Only used with Chaser bosses. + * Returns the boss' teleport target * * @param bossIndex Boss index. - * @param attackIndex New attack index. + * @param difficulty The difficulty index. + * @return Teleport entity index. */ -native int SF2_SetBossCurrentAttackIndex(int bossIndex, int attackIndex); +native int SF2_GetBossTeleportTarget(int bossIndex); /** - * Returns all available attack indexes of the boss - * - * Only used with Chaser bosses. + * Returns the StringMap variable containing every precached base boss profile * - * @param bossIndex Boss index. + * @return StringMap containing all base boss profile datas */ -native int SF2_GetBossMaxAttackIndexes(int bossIndex); +native StringMap SF2_GetBossProfileData(); /** - * Returns the boss' current run duration time - * - * Only used with Chaser bosses. + * Returns the StringMap variable containing every precached chaser boss profile * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @return Attack run duration time. + * @return StringMap containing all chaser boss profile datas */ -native float SF2_GetBossAttackRunDurationTime(int bossIndex, int attackIndex); +native StringMap SF2_GetChaserBossProfileData(); /** - * Sets the boss' current run duration time - * - * Only used with Chaser bosses. + * Returns the StringMap variable containing every precached statue boss profile * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param newTime New attack run duration time. + * @return StringMap containing all statue boss profile datas */ -native void SF2_SetBossAttackRunDurationTime(int bossIndex, int attackIndex, float newTime); +native StringMap SF2_GetStatueBossProfileData(); /** - * Returns the boss' current run duration time - * - * Only used with Chaser bosses. + * Spawns boss effects based on the list of effects used. + * Note this will not spawn the extra effects like the festive lights and the disco ball + * Only use this best for spawning in temporary effects. + * Note the array of effects must be an ArrayList that contains SF2BossProfileBaseEffectInfo. * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @return Attack run delay time. -*/ -native float SF2_GetBossAttackRunDelayTime(int bossIndex, int attackIndex); + * @param effects The array of effects + * @param bossIndex Boss to use + * @param overridePos Override the spawn position of the effects + * @param overrideAng Override the spawn angles of the effects + * @param output The ArrayList that will contain all of the outputted entities as entity references + * @param entityOverride If not INVALID_ENT_REFERENCE will attach all particles to the desired entity + */ +native void SF2_SpawnBossEffects(ArrayList effects, int bossIndex, float overridePos[3] = NULL_VECTOR, float overrideAng[3] = NULL_VECTOR, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE); /** - * Sets the boss' current run delay time + * Returns if a boss can be seen by any player * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param newTime New attack run delay time. -*/ -native void SF2_SetBossAttackRunDelayTime(int bossIndex, int attackIndex, float newTime); + * @param bossIndex Boss to use + * @param checkFOV Use player FOV to check if the boss can be seen + * @param checkBlink Use player blink to check if the boss can be seen + * @param checkEliminated Include/exclude eliminated players + */ +native bool SF2_CanBossBeSeen(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true); /** - * Returns the damage type of a set attack index. - * - * Only used with Chaser bosses. + * Translates an activity name string into an Activity. * - * @param bossIndex Boss index. - * @param attackIndex Attack index. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.DamageType[Difficulty_Max] instead. -native int SF2_GetBossAttackIndexDamageType(int bossIndex, int attackIndex); + * @param sActivity + * @return Activity, or ACT_INVALID if not found. + */ +native Activity SF2_TranslateProfileActivityFromName(const char[] activity); /** - * Returns the damage of a set attack index. - * - * Only used with Chaser bosses. + * Given an activity or sequence, translates into a sequence index. * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param difficulty The difficulty. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.Damage[Difficulty_Max] instead. -native float SF2_GetBossAttackIndexDamage(int bossIndex, int attackIndex, int difficulty); + * @param iEntity Entity index + * @param sAnimation Activity/sequence string + * @return Sequence index, or -1 if not found. + */ +native int SF2_LookupProfileAnimation(int entity, const char[] animation); /** - * Returns the spread of a set attack index + * Returns the profile data found on a boss index, note this is the basic profile data, I.E. what all bosses use * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param difficulty The difficulty. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.Spread[Difficulty_Max] instead. -native float SF2_GetBossAttackIndexSpread(int bossIndex, int attackIndex); + * @param bossIndex Boss index. + * @param data Data to be stored in. Note this takes in SF2BossProfileData + */ +native void SF2_GetProfileFromBossIndex(int bossIndex, any data[sizeof(SF2BossProfileData)]); /** - * Returns the range of a set attack index - * - * Only used with Chaser bosses. + * Returns the chaser profile data found on a boss index. * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param difficulty The difficulty. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.Range[Difficulty_Max] instead. -native float SF2_GetBossAttackIndexRange(int bossIndex, int attackIndex); + * @param bossIndex Boss index. + * @param data Data to be stored in. Note this takes in SF2ChaserBossProfileData + */ +native void SF2_GetChaserProfileFromBossIndex(int bossIndex, any data[sizeof(SF2ChaserBossProfileData)]); /** - * Returns the attack type of a set attack index. + * Returns the statue profile data found on a boss index. * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Attack index. -*/ -native int SF2_GetBossAttackIndexType(int bossIndex, int attackIndex); + * @param bossIndex Boss index. + * @param data Data to be stored in. Note this takes in SF2StatueBossProfileData + */ +native void SF2_GetStatueProfileFromBossIndex(int bossIndex, any data[sizeof(SF2StatueBossProfileData)]); /** - * Returns the teleport think timer in Handle form + * Returns the profile data found from a profile name, note this is the basic profile data, I.E. what all bosses use * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. -*/ -native Handle SF2_GetBossTeleportThinkTimer(int bossIndex); + * @param profile Profile name. + * @param data Data to be stored in. Note this takes in SF2BossProfileData + */ +native bool SF2_GetProfileFromName(const char[] profile, any data[sizeof(SF2BossProfileData)]); /** - * Sets the teleport think timer and destroys the old think timer + * Returns the chaser profile data found from a profile name. * - * @param bossIndex Boss index. - * @param timer New timer (Can also be null). -*/ -native void SF2_SetBossTeleportThinkTimer(int bossIndex, Handle timer); + * @param profile Profile name. + * @param data Data to be stored in. Note this takes in SF2ChaserBossProfileData + */ +native bool SF2_GetChaserProfileFromName(const char[] profile, any data[sizeof(SF2ChaserBossProfileData)]); /** - * Returns the boss' teleport target + * Returns the statue profile data found from a profile name. * - * @param bossIndex Boss index. - * @param difficulty The difficulty index. - * @return Teleport entity index. -*/ -native int SF2_GetBossTeleportTarget(int bossIndex); + * @param profile Profile name. + * @param data Data to be stored in. Note this takes in SF2StatueBossProfileData + */ +native bool SF2_GetStatueProfileFromName(const char[] profile, any data[sizeof(SF2ChaserBossProfileData)]); /** - * Returns the boss think timer in Handle form + * Forces a boss to enter in the chase state and immediately go after the target. + * If the boss is already chasing a target, then the target will be queued up for it to be auto chased. * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Handle timer. -*/ -native Handle SF2_GetBossThinkTimer(int bossIndex); + * @param bossIndex Boss index. + * @param target Entity to use. + * @param value Force chase state + */ +native void SF2_SetEntityForceChaseState(int bossIndex, CBaseEntity target, bool value); /** - * Sets the boss think timer and destroys the old think timer - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param timer New timer (Can also be null). + * Methodmap responsible for all boss entities. */ -native void SF2_SetBossThinkTimer(int bossIndex, Handle timer); +methodmap SF2_BaseBossEntity < CBaseCombatCharacter +{ + /** + * Whether or not the entity is actually a base boss + */ + property bool IsValid + { + public native get(); + } + + /** + * Gets the controller index that owns this entity. + */ + property int Controller + { + public native get(); + } + + /** + * Gets the target entity index. + */ + property CBaseEntity Target + { + public native get(); + } + + /** + * The current state of the boss. + */ + property int State + { + public native get(); + } + + /** + * Time left in its current chase. Note that this does not always tick down + * at a steady rate. + */ + property float CurrentChaseDuration + { + public native get(); + public native set(float value); + } + + /** + * If set will override the chase duration next time the boss goes into the chase state. + * This value will reset to 0 once the boss enters the chase state. + */ + property float InitialChaseDuration + { + public native get(); + public native set(float value); + } + + /** + * Determines if the boss is currently in a public death cam. + */ + property bool IsKillingSomeone + { + public native get(); + } + + /** + * Gets the boss's eye position. + * + * @param buffer Buffer to store position + */ + public native void EyePosition(float buffer[3]); + + /** + * Gets the boss's profile name + * + * @param buffer Buffer to name + * @param bufferLen Size of buffer + */ + public native void GetProfileName(char[] buffer, int bufferLen); + + /** + * Gets the boss's name + * + * @param buffer Buffer to name + * @param bufferLen Size of buffer + */ + public native void GetName(char[] buffer, int bufferLen); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_BaseBossEntity(int entity) + { + return view_as(entity); + } + + /** + * Gets the boss's profile data. + * + * @param data Data to be stored in. Note this takes in SF2BossProfileData + */ + public native void ProfileData(any data[sizeof(SF2BossProfileData)]); + + /** + * Plays an animation on the boss from a specific animation section, even custom ones. Note this is limited to the main "animations" section. + * + * @param animType Animation section to search in. + * @param preDefinedIndex Optional animation index to use. + * @param preDefinedName Optional animation name to use. + * @param duration Optional animation duration return. + */ + public native void ResetProfileAnimation(const char[] animType, int preDefinedIndex = -1, const char[] preDefinedName = "", float &duration = 0.0) +} /** - * Unhooks the boss' current pre-think hook function. Use this sparingly. (Or don't) - * - * @param bossIndex Boss index. + * The methodmap designed for statue bosses. */ -native void SF2_UnhookBossThinkHook(int bossIndex); +methodmap SF2_StatueBossEntity < SF2_BaseBossEntity +{ + /** + * Whether or not the entity is actually a statue boss + */ + property bool IsValid + { + public native get(); + } + + /** + * Returns whether or not the statue is moving. + */ + property bool IsMoving + { + public native get(); + } + + /** + * Gets the time since the statue killed someone. + */ + property float LastKillTime + { + public native get(); + } + + /** + * Gets the boss's profile data. + * + * @param data Data to be stored in. Note this takes in SF2StatueBossProfileData + */ + public native void ProfileData(any data[sizeof(SF2StatueBossProfileData)]); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_StatueBossEntity(int entity) + { + return view_as(entity); + } +} + +methodmap SF2_ChaserBossEntity < SF2_BaseBossEntity +{ + /** + * Whether or not the entity is actually a chaser boss + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_ChaserBossEntity(int entity) + { + return view_as(entity); + } + + /** + * Whether or not the boss is attempting to move somewhere else. + */ + property bool IsAttemptingToMove + { + public native get(); + } + + /** + * Whether or not the boss is attacking. + */ + property bool IsAttacking + { + public native get(); + } + + /** + * Whether or not the boss is stunned or not. + */ + property bool IsStunned + { + public native get(); + } + + /** + * The current stun health. + */ + property int StunHealth + { + public native get(); + } + + /** + * The maximum stun health the boss spawns with. + */ + property int MaxStunHealth + { + public native get(); + } + + /** + * Whether or not stun health can be subtracted from the boss. This is + * affected by several factors including: if stunning is enabled or + * stun cooldown has elapsed. + */ + property bool CanBeStunned + { + public native get(); + } + + /** + * Whether or not the boss can take damage. This is mainly useful if the + * boss is performing an attack that makes them invulnerable or are currently + * in a rage state. + */ + property bool CanTakeDamage + { + public native get(); + } + + /** + * Whether or not the boss is currently raging. + */ + property bool IsRaging + { + public native get(); + } + + /** + * Whether or not the boss is currently running away to go heal. + */ + property bool IsRunningAway + { + public native get(); + } + + /** + * Whether or not the boss is currently trying to self heal. + */ + property bool IsSelfHealing + { + public native get(); + } + + /** + * Gets the boss's profile data. + * + * @param data Data to be stored in. Note this takes in SF2ChaserBossProfileData + */ + public native void ProfileData(any data[sizeof(SF2ChaserBossProfileData)]); + + /** + * Forces the boss to perform a voice. Note custom sound sections will not work. + * + * @param soundType Sound type. + * @param attackName Optional attack name to search for. + * @return Whether or not the boss performed a voice. + */ + public native bool PerformVoice(int soundType = -1, const char[] attackName = "") + + /** + * Forces the boss to perform a voice using a custom sound section. + * + * @param soundInfo Sound section to use. + * @return Whether or not the boss performed a voice. + */ + public native bool PerformCustomVoice(any soundInfo[sizeof(SF2BossProfileSoundInfo)]) + + /** + * Gets the default posture of the boss. The default posture of a boss + * is named "default". + * + * @param buffer Buffer to store string + * @param bufferSize Size of buffer + * @return Number of characters written to buffer + */ + public native int GetDefaultPosture(char[] buffer, int bufferSize); + + /** + * Sets the default posture of the boss. + * + * @param buffer Posture to set to + * @return True if successful, false otherwise + */ + public native void SetDefaultPosture(const char[] buffer); + + /** + * Gets the current attack name of the boss. + * + * @param buffer Buffer to store string + * @param bufferSize Size of buffer + * @return Number of characters written to buffer + */ + public native int GetAttackName(char[] buffer, int bufferSize); + + /** + * Gets the current attack index of the boss. + */ + property int AttackIndex + { + public native get(); + } + + /** + * Gets the time which the attack can be used again + * + * @param attackName Attack name + * @return Timestamp + */ + public native float GetNextAttackTime(const char[] attackName); + + /** + * Sets the time which the attack can be used again + * + * @param attackName Attack name + * @param time Timestamp + */ + public native void SetNextAttackTime(const char[] attackName, float time); + + /** + * Drops a medkit/ammo pack depending on how the boss is configured + * + * @param death Whether or not to use the death state's item drop state + */ + public native void DropItem(bool death = false); + + /** + * Forces a boss to be alert at a specific position. + * + * @param position Position the boss should be alerted to. + */ + public native void CreateSoundHint(float position[3]); + + /** + * Enables/Disables the ability for bosses to utilize their ground movement as their speed. + * Only use this for situations where the boss does not constantly path towards someone. + */ + property bool GroundSpeedOverride + { + public native get(); + public native set(bool value); + } + + /** + * Checks if an attack is possible the default way + * This only includes FOV and range checks + * Difficulty checks and health checks are not included by default + * Note that cooldown checks are already handled in SF2_ChaserEntity.GetAttackAction + * And visibility checks are already handled in SF2_ChaserAttackLayerAction + * You would use this primarily in SF2_OnIsBossCustomAttackPossible + * + * @param attackName Name of the attack + * @param includeDifficulties Whether or not to include the difficulty checks + * @param includeHealth Whether or not to include target health checks + * @return Whether or not the attack is possible with the distance and FOV checks + */ + public bool IsAttackPossibleDefault(const char[] attackName, bool includeDifficulties = false, bool includeHealth = false) + { + if (!this.Target.IsValid()) + { + return false; + } + + int controller = this.Controller; + int difficulty = SF2_GetBossDifficulty(controller); + + INextBot bot = this.MyNextBotPointer(); + SF2ChaserBossProfileData data; + this.ProfileData(data); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + float eyePos[3], targetPos[3], direction[3], eyeAng[3]; + this.GetAbsAngles(eyeAng); + SF2_GetBossEyePosition(controller, eyePos); + this.Target.GetAbsOrigin(targetPos); + SubtractVectors(targetPos, eyePos, direction); + GetVectorAngles(direction, direction); + direction[2] = 180.0; + + float distance = bot.GetRangeSquaredTo(this.Target.index); + float fov = FloatAbs(AngleDiff(direction[1], eyeAng[1])); + + if (distance > Pow(attackData.BeginRange[difficulty], 2.0)) + { + return false; + } + + if (fov > attackData.BeginFOV[difficulty]) + { + return false; + } + + if (includeDifficulties) + { + if (difficulty < attackData.UseOnDifficulty) + { + return false; + } + + if (difficulty >= attackData.BlockOnDifficulty) + { + return false; + } + } + + if (includeHealth) + { + float health = float(this.Target.GetProp(Prop_Send, "m_iHealth")); + if (attackData.UseOnHealth != -1.0 && health < attackData.UseOnHealth) + { + return false; + } + if (attackData.BlockOnHealth != -1.0 && health >= attackData.BlockOnHealth) + { + return false; + } + } + return true; + } +} /** - * Forcefully makes the boss perform a voice based on pre-defined voices - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Optional index that looks mainly for attack sounds in sub sections - * @param soundType Type of sound the boss should perform -*/ -native void SF2_PerformBossVoice(int bossIndex, const int attackIndex = -1, int soundType = -1); + * An action that plays a given sequence on the actor. This action exits when the + * duration has elapsed. + */ +methodmap SF2_PlaySequenceAndWaitAction < NextBotAction +{ + /** + * @param sequence Sequence index + * @param duration How long the action should last. If 0.0, then this will be the sequence's + * duration. + * @param rate Playback rate + * @param cycle Start cycle value, from 0.0 to 1.0 + */ + public native SF2_PlaySequenceAndWaitAction(int sequence, float duration = 0.0, float rate = 1.0, float cycle = 0.0); +} /** - * Returns the StringMap variable containing every precached base boss profile - * - * @return StringMap containing all base boss profile datas -*/ -native StringMap SF2_GetBossProfileData(); + * Interface to SF2_BasePlayer + */ +methodmap SF2_Player < CBaseCombatCharacter +{ + /** + * Attempts to cast a client to be this entity. + * + * @param client Client to use + */ + public SF2_Player(int client) + { + if (client <= 0) + { + return view_as(-1); + } + return view_as(client); + } + + property int UserID + { + public native get(); + } + + /** + * Returns true if the client index is > 0 and <= MaxClients, and if the client is in game. + */ + property bool IsValid + { + public native get(); + } + + /** + * Alias of IsPlayerAlive() + */ + property bool IsAlive + { + public native get(); + } + + /** + * Alias of IsClientInGame() + * Use this over IsClientInGame() though as it has extra optimizations + */ + property bool IsInGame + { + public native get(); + } + + /** + * Alias of IsFakeClient() + */ + property bool IsBot + { + public native get(); + } + + /** + * Alias of IsClientSourceTV() + */ + property bool IsSourceTV + { + public native get(); + } + + /** + * Alias of IsClientReplay() + */ + property bool IsReplay + { + public native get(); + } + + /** + * Whether or not the client is in third person mode + */ + property bool InThirdPerson + { + public native get(); + } + + /** + * Alias of GetClientButtons() + */ + property int Buttons + { + public native get(); + } + + /** + * Whether or not the client is moving + */ + public native bool IsMoving(); + + /** + * Gets/Sets the client's health + */ + property int Health + { + public native get(); + + public native set(int value); + } + + /** + * Gets the client's max health + */ + property int MaxHealth + { + public native get(); + } + + /** + * Whether or not the client is trying to duck + */ + property bool Ducking + { + public native get(); + } + + /** + * Whether or not the client is currently ducked + */ + property bool Ducked + { + public native get(); + } + + /** + * Alias of GetEntDataEnt2() + */ + public native int GetDataEnt(int offset); + + #if defined _tf2_stocks_included + /** + * Alias of TF2_GetPlayerClass() + */ + property TFClassType Class + { + public native get(); + } + #endif + + /** + * Alias of GetClientTeam() + */ + property int Team + { + public native get(); + } + + /** + * Whether or not the client has a regenerating item like the Cozy Camper + */ + property bool HasRegenItem + { + public native get(); + + public native set(bool state); + } + + #if defined _tf2_included + /** + * Alias of TF2_IsPlayerInCondition() + * Use this over TF2_IsPlayerInCondition() though as it has extra optimizations + * + * @param condition Condition type + */ + public native bool InCondition(TFCond condition); + + /** + * Alias of both TF2_RemoveCondition() and TF2_AddCondition() + * + * @param condition Condition type + * @param remove Whether or not the condition should be removed + * @param duration How long the condition will last + * @param inflictor Inflictor of the condition + */ + public native bool ChangeCondition(TFCond condition, bool remove = false, float duration = -1.0, int inflictor = 0); + #endif + + /** + * Whether or not the client is crit boosted + */ + public native bool IsCritBoosted(); + + /** + * Whether or not the client is mini crit boosted + */ + public native bool IsMiniCritBoosted(); + + /** + * Alias of TF2_IgnitePlayer() + * + * @param self Whether or not the attacker should be the player itself + * @param attacker Attacker index, this doesn't need to be set if self is true + * @param duration How long the afterburn should last + */ + public native void Ignite(bool self = false, int attacker = 0, float duration = 10.0); + + /** + * Alias of TF2_MakeBleed() + * + * @param self Whether or not the attacker should be the player itself + * @param attacker Attacker index, this doesn't need to be set if self is true + * @param duration How long the bleed should last + */ + public native void Bleed(bool self = false, int attacker = 0, float duration = 5.0); + + /** + * Alias of TF2_StunPlayer() + * + * @param duration How long the stun should last + * @param slowdown The multiplier to the player's speed in which they'll be slowed for + * @param stunFlags The flags of the stun + * @param attacker Attacker index + */ + public native void Stun(float duration, float slowdown = 0.0, int stunflags, int attacker = 0); + + /** + * Alias of TF2_RegeneratePlayer() + */ + public native void Regenerate(); + + /** + * Alias of TF2_SetPlayerClass() + * + * @param classType New class + * @param weapons Ignored/Unused + * @param persistent Whether or not the class change should remain after death + */ + public native void SetClass(TFClassType classType, bool weapons = true, bool persistent = true); + + /** + * Alias of GetClientEyePosition() + * + * @param vector Destination to store the eye position + */ + public native void GetEyePosition(float vector[3]); + + /** + * Alias of GetClientEyeAngles() + * + * @param vector Destination to store the eye angles + */ + public native void GetEyeAngles(float vector[3]); + + /** + * Alias of GetEntDataVector() + * + * @param offset Offset to use + * @param buffer Vector to store the data in + */ + public native void GetDataVector(int offset, float buffer[3]); + + /** + * Alias of SetEntDataVector() + * + * @param offset Offset to use + * @param buffer Vector to set + * @param state Whether or not the state should be sent over the network + */ + public native void SetDataVector(int offset, const float buffer[3], bool state = false); + + /** + * Returns the player's distance from an entity + * Note this does not use the square root method + * + * @param ent Entity to check + */ + public native float GetDistanceFromEntity(int ent); + + /** + * Alias of GetPlayerWeaponSlot() + * + * @param slot Weapon slot + */ + public native int GetWeaponSlot(int slot); + + /** + * Switches the player's weapon slot to a specified index + * + * @param slot Weapon slot + */ + public native void SwitchToWeaponSlot(int slot); + + /** + * Alias of TF2_RemoveWeaponSlot() + * + * @param slot Weapon slot + */ + public native void RemoveWeaponSlot(int slot); + + /** + * Shakes the player's screen + * + * @param amp Amplitude + * @param duration Duration of the screen shake + * @param freq Frequency + */ + public native void ScreenShake(float amp, float duration, float freq); + + /** + * Flings the client's camera in a specific direction + * + * @param punchVel Direction/Strength + */ + public native void ViewPunch(const float punchVel[3]); + + /** + * Alias of SDKHooks_TakeDamage() + * + * @param self Self damage + * @param inflictor Inflictor index, don't need to set if self is true + * @param attacker Attacker index, don't need to set if self is true + * @param damage Damage to take + * @param damageType Type of damage + * @param weapon Weapon index + * @param damageForce Velocity of damage force + * @param damagePosition Origin of damage + * @param bypassHooks Whether or not to bypass SDK hooks on OnTakeDamage + */ + public native void TakeDamage(bool self = false, int inflictor = 0, int attacker = 0, float damage, int damageType = 0, int weapon = -1, + const float damageForce[3] = NULL_VECTOR, const float damagePosition[3] = NULL_VECTOR, bool bypassHooks = true); + + /** + * Alias of TF2_RespawnPlayer() + */ + public native void Respawn(); + + /** + * Updates the player's listening flags which determine if a player can hear the voice chat from a player + * + * @param reset Resets all flags + */ + public native void UpdateListeningFlags(bool reset = false); + + /** + * Determines if the player is actually is a valid class, is in RED or BLU, and if the player is not coaching + */ + property bool IsParticipating + { + public native get(); + } + + /** + * Alias of GetClientName() + * + * @param name Buffer to store the client's name. + * @param length Maximum length of string buffer + */ + public native bool GetName(char[] name, int length); + + /** + * Gets/Sets the last buttons the player pressed + */ + property int LastButtons + { + public native get(); + + public native set(int value); + } + + /** + * Makes the player's speed fade to a specific color + */ + public native void ScreenFade(int duration, int time, int flags, int r, int g, int b, int a); + + /** + * Whether or not the player is eliminated from the game + */ + property bool IsEliminated + { + public native get(); + + public native set(bool state); + } + + /** + * Whether or not the player is in ghost mode + */ + property bool IsInGhostMode + { + public native get(); + } + + /** + * Sets the player's ghost mode state + * + * @param state New ghost mode state + */ + public native void SetGhostState(bool state); + + /** + * Whether or not the player is a proxy + */ + property bool IsProxy + { + public native get(); + + public native set(bool state); + } + + /** + * Gets/Sets the player's proxy control points + */ + property int ProxyControl + { + public native get(); + + public native set(int value); + } + + /** + * Gets/Sets the player's proxy master as a boss index + */ + property int ProxyMaster + { + public native get(); + + public native set(int value); + } + + /** + * Whether or not the player is in a PvP arena + */ + property bool IsInPvP + { + public native get(); + } + + /** + * Whether or not the player is in a PvE arena + */ + property bool IsInPvE + { + public native get(); + } + + /** + * Whether or not the player is in a death cam + */ + property bool IsInDeathCam + { + public native get(); + } + + /** + * Starts a death cam for a boss + * + * @param bossIndex Boss index to use + * @param lookPos What position the player should look at + * @param antiCamp Whether or not this was an anticamping death, only useful for public death cams + * @param staticDeath Whether or not the player's death was caused by static. + */ + public native void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false); + + /** + * Whether or not the player has escaped + */ + property bool HasEscaped + { + public native get(); + } + + /** + * Makes the player escape. Note this will not teleport the player to a escape point + */ + public native void Escape(); + + /** + * Teleports the player to an escape point + */ + public native void TeleportToEscapePoint(); + + /** + * Makes the player escape and teleports them to an escape point + */ + public native void ForceEscape(); + + /** + * Whether or not the player is using a flashlight + */ + property bool UsingFlashlight + { + public native get(); + } + + /** + * Toggles the player's flashlight state + */ + public native void HandleFlashlight(); + + /** + * Gets/Sets the player's flashlight battery life + */ + property float FlashlightBatteryLife + { + public native get(); + + public native set(float value); + } + + /** + * Resets the player's flashlight + */ + public native void ResetFlashlight(); + + /** + * Returns the player's next flashlight input time + */ + public native float GetFlashlightNextInputTime(); + + /** + * Whether or not the player is sprinting. Note this only checks if the player is pressing the sprint button + */ + property bool IsSprinting + { + public native get(); + } + + /** + * Whether or not the player is really sprinting. + * This does consider if the player is on the ground and is actually moving + */ + property bool IsReallySprinting + { + public native get(); + } + + /** + * Toggles the player's sprint state + */ + public native void HandleSprint(bool sprint); + + /** + * Gets/sets the player's stamina + */ + property float Stamina + { + public native get(); + + public native set(float value); + } + + /** + * Sets the stamina's next recharge time + * + * @param time Amount of time it will take for the stamina to continue to recharge + * @param checkTime Whether or not to check if the current recharge time is greater than the + * inputted recharge time + */ + public native void SetStaminaRechargeTime(float time, bool checkTime = true); + + /** + * Whether or not the player's blink HUD element is showing + */ + property bool HasStartedBlinking + { + public native get(); + } + + /** + * Whether or not the player is currently blinking + */ + property bool IsBlinking + { + public native get(); + } + + /** + * Whether or not the player is continuing to stay blinked + */ + public native bool IsHoldingBlink(); + + /** + * Sets the player's blink state + * + * @param value Blink state + */ + public native void SetHoldingBlink(bool value); + + /** + * Gets/Sets the player's blink meter from 0.0 to 1.0 + */ + property float BlinkMeter + { + public native get(); + + public native set(float amount); + } + + /** + * Returns how many times the player blinked + */ + property int BlinkCount + { + public native get(); + } + + /** + * Makes the player blink + */ + public native void Blink(); + + /** + * Makes the player start the peeking. This puts the player in third person + */ + public native bool StartPeeking(); + + /** + * Makes the player end the peeking state. This simply puts the player back in first person + */ + public native void EndPeeking(); + + /** + * Gets/Sets the amount of pages the player has collected + */ + property int PageCount + { + public native get(); + + public native set(int amount); + } + + /** + * Shows a hint to the player if the player has it enabled + * + * @param hint Hint index + */ + public native void ShowHint(int hint); + + /** + * Gets/Sets the player's trap state + */ + property bool IsTrapped + { + public native get(); + + public native set(bool state); + } + + /** + * Gets/Sets the player's times they need to jump to not be trapped anymore + */ + property int TrapCount + { + public native get(); + + public native set(int amount); + } + + /** + * Gets/Sets the player's latched state if they're latched onto a boss's rope/tongue ability + */ + property bool IsLatched + { + public native get(); + + public native set(bool state); + } + + /** + * Gets/Sets the player's times they need to jump to not be latched by a boss anymore + */ + property int LatchCount + { + public native get(); + + public native set(int amount); + } + + /** + * Gets/Sets the player's latcher based on which boss is latching them with a boss's rope/tongue ability + */ + property int Latcher + { + public native get(); + + public native set(int value); + } + + /** + * Updates the player's music state + * + * @param initialize Unknown????? Nobody knows what this actually does. + */ + public native void UpdateMusicSystem(bool initialize = false); + + /** + * Whether or not the player has a glow active + */ + property bool HasConstantGlow + { + public native get(); + } + + /** + * Sets the player's play state based on if they're in RED + * + * @param state State to be in + * @param enablePlay Whether or not the player is actively playing. Only set this if you know what you're doing + */ + public native void SetPlayState(bool state, bool enablePlay = true); + + /** + * Whether or not the player can see a boss + * + * @param bossIndex Boss to check + * @param checkFOV Considers if the boss is in the player's FOV + * @param checkBlink Considers if the player is blinking + * @param checkEliminated Considers if the player is eliminated + */ + public native bool CanSeeSlender(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true); + + /** + * Sets the player's AFK time based on a set number of conditions + * + * @param reset Whether or not the AFK time should be reset to the default AFK time + */ + public native void SetAFKTime(bool reset = true); + + /** + * Sets the player's AFK state + */ + public native void SetAFKState(); + + /** + * Checks to make sure if the player is AFK or not, if they are the game puts the player in a AFK state + */ + public native void CheckAFKTime(); + + /** + * Returns if the player should be immediately chased by a boss + * + * @param bossIndex Boss to check + */ + public native bool ShouldBeForceChased(int bossIndex); + + /** + * Sets the player's state to be immediately chased by a boss + * + * @param bossIndex Boss to use + * @param value State + */ + public native void SetForceChaseState(int bossIndex, bool value); + + /** + * Checks if a player is looking at a boss + * + * @param bossIndex Boss to check + */ + public native bool IsLookingAtBoss(int bossIndex); +} /** - * Returns the StringMap variable containing every precached chaser boss profile + * Interface to SF2_ProjectileBase * - * @return StringMap containing all chaser boss profile datas -*/ -native StringMap SF2_GetChaserBossProfileData(); + * Useful for creating custom projectiles, see sf2/entities/projectiles to see how projectiles are created + */ +methodmap SF2_Projectile_Base < CBaseAnimating +{ + /** + * Initializes a projectile's stats + * + * @param type Projectile type + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius If configured to explode, the blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param explosion Explosion particle + * @param impactSound Impact/explosion sound + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + */ + public native void Initialize(const int type = SF2BossProjectileType_Custom, + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Triggers a explosion on the projectile and removes it + */ + public native void DoExplosion(); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Base(int entity) + { + return view_as(entity); + } +} /** - * Returns the StringMap variable containing every precached statue boss profile - * - * @return StringMap containing all statue boss profile datas -*/ -native StringMap SF2_GetStatueBossProfileData(); + * The methodmap for arrow projectiles + */ +methodmap SF2_Projectile_Arrow < SF2_Projectile_Base +{ + /** + * Creates a arrow projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param impactSound Impact sound when it hits a player + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Arrow Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] trail, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a arrow + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Arrow(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for baseball projectiles + */ +methodmap SF2_Projectile_Baseball < SF2_Projectile_Base +{ + /** + * Creates a baseball projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Baseball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] model, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a baseball + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Baseball(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for cow mangler projectiles + */ +methodmap SF2_Projectile_CowMangler < SF2_Projectile_Base +{ + /** + * Creates a cow mangler projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_CowMangler Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a cow mangler projectile + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_CowMangler(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for fireball projectiles + */ +methodmap SF2_Projectile_Fireball < SF2_Projectile_Base +{ + /** + * Creates a fireball projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param impactSound Impact sound when it hits a player + * @param trail Trail material name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Fireball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a fireball + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Fireball(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for grenade projectiles + */ +methodmap SF2_Projectile_Grenade < SF2_Projectile_Base +{ + /** + * Creates a grenade projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param explosion Explosion particle + * @param impactSound Impact/explosion sound + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Grenade Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a grenade + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Grenade(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for iceball projectiles + */ +methodmap SF2_Projectile_Iceball < SF2_Projectile_Base +{ + /** + * Creates a iceball projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param impactSound Impact sound when it hits a player + * @param trail Trail material name + * @param slowDuration How long the slowdown effect lasts + * @param slowMultiplier The speed multiplier of the slowdown effect + * @param freezeSound The sound that plays when a player gets slowed down + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Iceball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const float slowDuration, + const float slowMultiplier, + const char[] freezeSound, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a iceball + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Iceball(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for rocket projectiles + */ +methodmap SF2_Projectile_Rocket < SF2_Projectile_Base +{ + /** + * Creates a rocket projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param explosion Explosion particle + * @param impactSound Impact/explosion sound + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Rocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a rocket + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Rocket(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for sentry rocket projectiles + */ +methodmap SF2_Projectile_SentryRocket < SF2_Projectile_Base +{ + /** + * Creates a sentry rocket projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_SentryRocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const bool attackWaiters = false); + + /** + * Whether or not the entity is actually a sentry rocket + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_SentryRocket(int entity) + { + return view_as(entity); + } +} /** * Returns the distance of 2 vectors without utilizing the square root function and converting the values properly. @@ -1840,20 +3476,40 @@ native float SF2_GetVectorSquareMagnitude(const float vec1[3], const float vec2[ native void SF2_InitiateBossPackVote(int initiator); /** - * Returns the flags of the projectile. + * Adds a boss to the pool of PvE bosses. Note this takes entity names * - * @param projectile Entity index. - * @return Flags. + * @param bossName Boss entity name */ -native int SF2_GetProjectileFlags(int projectile); +native void SF2_RegisterPvEBoss(char[] bossName); /** - * Sets the flags of the projectile. + * Removes a boss from the pool of PvE bosses. Note this takes entity names + * You MUST call this whenever your boss sub plugin ends or the gamemode will spawn an invalid entity * - * @param projectile Entity index. - * @param flags Flags. + * @param bossName Boss entity name + */ +native void SF2_UnregisterPvEBoss(char[] bossName); + +/** + * Adds a boss onto the PvE boss list. This is required when you spawn your bosses in. + * + * @param bossEnt Entity to add + */ +native void SF2_AddPvEBoss(int bossEnt); + +/** + * Removes a boss from the PvE boss list. This is required when your boss dies. + * + * @param bossEnt Entity to remove + */ +native void SF2_KillPvEBoss(int bossEnt); + +/** + * Returns the array list of the current active bosses, this list only contains entity references. + * + * @return List of active PvE bosses or null if no bosses are active. */ -native void SF2_SetProjectileFlags(int projectile, int flags); +native ArrayList SF2_GetActivePvEBosses(); /** * Returns if current map is a survival map or not. @@ -1907,3 +3563,395 @@ public SharedPlugin __pl_sf2 = required = 0, #endif }; + +#if !defined REQUIRE_PLUGIN +public void __pl_sf2_SetNTVOptional() +{ + // sf2/methodmaps.sp + MarkNativeAsOptional("SF2_Player.UserID.get"); + MarkNativeAsOptional("SF2_Player.IsValid.get"); + MarkNativeAsOptional("SF2_Player.IsAlive.get"); + MarkNativeAsOptional("SF2_Player.IsInGame.get"); + MarkNativeAsOptional("SF2_Player.IsBot.get"); + MarkNativeAsOptional("SF2_Player.IsSourceTV.get"); + MarkNativeAsOptional("SF2_Player.IsReplay.get"); + MarkNativeAsOptional("SF2_Player.InThirdPerson.get"); + MarkNativeAsOptional("SF2_Player.Buttons.get"); + MarkNativeAsOptional("SF2_Player.IsMoving"); + MarkNativeAsOptional("SF2_Player.Health.get"); + MarkNativeAsOptional("SF2_Player.MaxHealth.get"); + MarkNativeAsOptional("SF2_Player.Ducking.get"); + MarkNativeAsOptional("SF2_Player.Ducked.get"); + MarkNativeAsOptional("SF2_Player.GetDataEnt"); + MarkNativeAsOptional("SF2_Player.Class.get"); + MarkNativeAsOptional("SF2_Player.Team.get"); + MarkNativeAsOptional("SF2_Player.HasRegenItem.get"); + MarkNativeAsOptional("SF2_Player.HasRegenItem.set"); + MarkNativeAsOptional("SF2_Player.InCondition"); + MarkNativeAsOptional("SF2_Player.ChangeCondition"); + MarkNativeAsOptional("SF2_Player.IsCritBoosted"); + MarkNativeAsOptional("SF2_Player.IsMiniCritBoosted"); + MarkNativeAsOptional("SF2_Player.Ignite"); + MarkNativeAsOptional("SF2_Player.Bleed"); + MarkNativeAsOptional("SF2_Player.Stun"); + MarkNativeAsOptional("SF2_Player.Regenerate"); + MarkNativeAsOptional("SF2_Player.SetClass"); + MarkNativeAsOptional("SF2_Player.GetEyePosition"); + MarkNativeAsOptional("SF2_Player.GetEyeAngles"); + MarkNativeAsOptional("SF2_Player.GetDataVector"); + MarkNativeAsOptional("SF2_Player.SetDataVector"); + MarkNativeAsOptional("SF2_Player.GetDistanceFromEntity"); + MarkNativeAsOptional("SF2_Player.GetWeaponSlot"); + MarkNativeAsOptional("SF2_Player.SwitchToWeaponSlot"); + MarkNativeAsOptional("SF2_Player.RemoveWeaponSlot"); + MarkNativeAsOptional("SF2_Player.ScreenShake"); + MarkNativeAsOptional("SF2_Player.ViewPunch"); + MarkNativeAsOptional("SF2_Player.TakeDamage"); + MarkNativeAsOptional("SF2_Player.Respawn"); + MarkNativeAsOptional("SF2_Player.UpdateListeningFlags"); + MarkNativeAsOptional("SF2_Player.IsParticipating.get"); + MarkNativeAsOptional("SF2_Player.GetName"); + MarkNativeAsOptional("SF2_Player.LastButtons.get"); + MarkNativeAsOptional("SF2_Player.LastButtons.set"); + MarkNativeAsOptional("SF2_Player.ScreenFade"); + MarkNativeAsOptional("SF2_Player.IsEliminated.get"); + MarkNativeAsOptional("SF2_Player.IsEliminated.set"); + MarkNativeAsOptional("SF2_Player.IsInGhostMode.get"); + MarkNativeAsOptional("SF2_Player.SetGhostState"); + MarkNativeAsOptional("SF2_Player.IsProxy.get"); + MarkNativeAsOptional("SF2_Player.IsProxy.set"); + MarkNativeAsOptional("SF2_Player.ProxyControl.get"); + MarkNativeAsOptional("SF2_Player.ProxyControl.set"); + MarkNativeAsOptional("SF2_Player.ProxyMaster.get"); + MarkNativeAsOptional("SF2_Player.ProxyMaster.set"); + MarkNativeAsOptional("SF2_Player.IsInPvP.get"); + MarkNativeAsOptional("SF2_Player.IsInPvE.get"); + MarkNativeAsOptional("SF2_Player.IsInDeathCam.get"); + MarkNativeAsOptional("SF2_Player.StartDeathCam"); + MarkNativeAsOptional("SF2_Player.HasEscaped.get"); + MarkNativeAsOptional("SF2_Player.Escape"); + MarkNativeAsOptional("SF2_Player.TeleportToEscapePoint"); + MarkNativeAsOptional("SF2_Player.ForceEscape"); + MarkNativeAsOptional("SF2_Player.UsingFlashlight.get"); + MarkNativeAsOptional("SF2_Player.HandleFlashlight"); + MarkNativeAsOptional("SF2_Player.FlashlightBatteryLife.get"); + MarkNativeAsOptional("SF2_Player.FlashlightBatteryLife.set"); + MarkNativeAsOptional("SF2_Player.ResetFlashlight"); + MarkNativeAsOptional("SF2_Player.GetFlashlightNextInputTime"); + MarkNativeAsOptional("SF2_Player.IsSprinting.get"); + MarkNativeAsOptional("SF2_Player.IsReallySprinting.get"); + MarkNativeAsOptional("SF2_Player.HandleSprint"); + MarkNativeAsOptional("SF2_Player.Stamina.get"); + MarkNativeAsOptional("SF2_Player.Stamina.set"); + MarkNativeAsOptional("SF2_Player.SetStaminaRechargeTime"); + MarkNativeAsOptional("SF2_Player.HasStartedBlinking.get"); + MarkNativeAsOptional("SF2_Player.IsBlinking.get"); + MarkNativeAsOptional("SF2_Player.IsHoldingBlink"); + MarkNativeAsOptional("SF2_Player.SetHoldingBlink"); + MarkNativeAsOptional("SF2_Player.BlinkMeter.get"); + MarkNativeAsOptional("SF2_Player.BlinkMeter.set"); + MarkNativeAsOptional("SF2_Player.BlinkCount.get"); + MarkNativeAsOptional("SF2_Player.Blink"); + MarkNativeAsOptional("SF2_Player.StartPeeking"); + MarkNativeAsOptional("SF2_Player.EndPeeking"); + MarkNativeAsOptional("SF2_Player.PageCount.get"); + MarkNativeAsOptional("SF2_Player.PageCount.set"); + MarkNativeAsOptional("SF2_Player.ShowHint"); + MarkNativeAsOptional("SF2_Player.IsTrapped.get"); + MarkNativeAsOptional("SF2_Player.IsTrapped.set"); + MarkNativeAsOptional("SF2_Player.TrapCount.get"); + MarkNativeAsOptional("SF2_Player.TrapCount.set"); + MarkNativeAsOptional("SF2_Player.IsLatched.get"); + MarkNativeAsOptional("SF2_Player.IsLatched.set"); + MarkNativeAsOptional("SF2_Player.LatchCount.get"); + MarkNativeAsOptional("SF2_Player.LatchCount.set"); + MarkNativeAsOptional("SF2_Player.Latcher.get"); + MarkNativeAsOptional("SF2_Player.Latcher.set"); + MarkNativeAsOptional("SF2_Player.UpdateMusicSystem"); + MarkNativeAsOptional("SF2_Player.HasConstantGlow.get"); + MarkNativeAsOptional("SF2_Player.SetPlayState"); + MarkNativeAsOptional("SF2_Player.CanSeeSlender"); + MarkNativeAsOptional("SF2_Player.SetAFKTime"); + MarkNativeAsOptional("SF2_Player.SetAFKState"); + MarkNativeAsOptional("SF2_Player.CheckAFKTime"); + MarkNativeAsOptional("SF2_Player.ShouldBeForceChased"); + MarkNativeAsOptional("SF2_Player.SetForceChaseState"); + MarkNativeAsOptional("SF2_Player.IsLookingAtBoss"); + + // sf2/npc.sp + MarkNativeAsOptional("SF2_GetMaxBossCount"); + MarkNativeAsOptional("SF2_EntIndexToBossIndex"); + MarkNativeAsOptional("SF2_BossIndexToEntIndex"); + MarkNativeAsOptional("SF2_BossIndexToEntIndexEx"); + MarkNativeAsOptional("SF2_BossIDToBossIndex"); + MarkNativeAsOptional("SF2_BossIndexToBossID"); + + MarkNativeAsOptional("SF2_AddBoss"); + MarkNativeAsOptional("SF2_RemoveBoss"); + + MarkNativeAsOptional("SF2_GetBossName"); + MarkNativeAsOptional("SF2_GetBossType"); + + MarkNativeAsOptional("SF2_GetBossFlags"); + MarkNativeAsOptional("SF2_SetBossFlags"); + + MarkNativeAsOptional("SF2_SpawnBoss"); + MarkNativeAsOptional("SF2_IsBossSpawning"); + MarkNativeAsOptional("SF2_DespawnBoss"); + + MarkNativeAsOptional("SF2_GetBossPathFollower"); + MarkNativeAsOptional("SF2_GetBossMaster"); + MarkNativeAsOptional("SF2_GetBossIdleLifetime"); + MarkNativeAsOptional("SF2_GetBossState"); + MarkNativeAsOptional("SF2_SetBossState"); + + MarkNativeAsOptional("SF2_GetBossEyePosition"); + MarkNativeAsOptional("SF2_GetBossEyePositionOffset"); + + MarkNativeAsOptional("SF2_GetBossTeleportThinkTimer"); + MarkNativeAsOptional("SF2_SetBossTeleportThinkTimer"); + MarkNativeAsOptional("SF2_GetBossTeleportTarget"); + + MarkNativeAsOptional("SF2_GetBossGoalPosition"); + + MarkNativeAsOptional("SF2_GetBossTimeUntilNoPersistence"); + MarkNativeAsOptional("SF2_SetBossTimeUntilNoPersistence"); + MarkNativeAsOptional("SF2_GetBossTimeUntilAlert"); + MarkNativeAsOptional("SF2_SetBossTimeUntilAlert"); + + MarkNativeAsOptional("SF2_GetProfileFromBossIndex"); + MarkNativeAsOptional("SF2_GetProfileFromName"); + + MarkNativeAsOptional("SF2_SpawnBossEffects"); + + MarkNativeAsOptional("SF2_CanBossBeSeen"); + + // sf2/profiles.sp + MarkNativeAsOptional("SF2_IsBossProfileValid"); + MarkNativeAsOptional("SF2_GetBossProfileNum"); + MarkNativeAsOptional("SF2_GetBossProfileFloat"); + MarkNativeAsOptional("SF2_GetBossProfileString"); + MarkNativeAsOptional("SF2_GetBossProfileVector"); + MarkNativeAsOptional("SF2_GetBossAttackProfileNum"); + MarkNativeAsOptional("SF2_GetBossAttackProfileFloat"); + MarkNativeAsOptional("SF2_GetBossAttackProfileString"); + MarkNativeAsOptional("SF2_GetBossAttackProfileVector"); + MarkNativeAsOptional("SF2_GetRandomStringFromBossProfile"); + MarkNativeAsOptional("SF2_GetBossAttributeName"); + MarkNativeAsOptional("SF2_GetBossAttributeValue"); + + MarkNativeAsOptional("SF2_GetBossProfileData"); + MarkNativeAsOptional("SF2_GetChaserBossProfileData"); + MarkNativeAsOptional("SF2_GetStatueBossProfileData"); + MarkNativeAsOptional("SF2_TranslateProfileActivityFromName"); + MarkNativeAsOptional("SF2_LookupProfileAnimation"); + + // sf2/pve.sp + MarkNativeAsOptional("SF2_IsClientInPvE"); + MarkNativeAsOptional("SF2_RegisterPvEBoss"); + MarkNativeAsOptional("SF2_SetOverridePvEMusic"); + MarkNativeAsOptional("SF2_UnregisterPvEBoss"); + MarkNativeAsOptional("SF2_AddPvEBoss"); + MarkNativeAsOptional("SF2_KillPvEBoss"); + MarkNativeAsOptional("SF2_GetActivePvEBosses"); + + // pvp.sp + MarkNativeAsOptional("SF2_IsClientInPvP"); + + // specialround.sp + MarkNativeAsOptional("SF2_IsSpecialRoundRunning"); + MarkNativeAsOptional("SF2_GetSpecialRoundType"); + + // client/sprint.sp + MarkNativeAsOptional("SF2_GetClientSprintPoints"); + MarkNativeAsOptional("SF2_SetClientSprintPoints"); + + MarkNativeAsOptional("SF2_IsClientSprinting"); + MarkNativeAsOptional("SF2_IsClientReallySprinting"); + MarkNativeAsOptional("SF2_SetClientSprintState"); + + // entities/sf2_base_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Base.Initialize"); + MarkNativeAsOptional("SF2_Projectile_Base.DoExplosion"); + + // entities/projectiles/sf2_arrow_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Arrow.Create"); + MarkNativeAsOptional("SF2_projectile_Arrow.IsValid.get"); + + // entities/projectiles/sf2_baseball_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Baseball.Create"); + MarkNativeAsOptional("SF2_Projectile_Baseball.IsValid.get"); + + // entities/projectiles/sf2_cow_mangler_projectile.sp + MarkNativeAsOptional("SF2_Projectile_CowMangler.Create"); + MarkNativeAsOptional("SF2_Projectile_CowMangler.IsValid.get"); + + // entities/projectiles/sf2_fireball_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Fireball.Create"); + MarkNativeAsOptional("SF2_Projectile_Fireball.IsValid.get"); + + // entities/projectiles/sf2_grenade_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Grenade.Create"); + MarkNativeAsOptional("SF2_Projectile_Grenade.IsValid.get"); + + // entities/projectiles/sf2_iceball_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Iceball.Create"); + MarkNativeAsOptional("SF2_Projectile_Iceball.IsValid.get"); + + // entities/projectiles/sf2_rocket_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Rocket.Create"); + MarkNativeAsOptional("SF2_Projectile_Rocket.IsValid.get"); + + // entities/projectiles/sf2_sentry_rocket_projectile.sp + MarkNativeAsOptional("SF2_Projectile_SentryRocket.Create"); + MarkNativeAsOptional("SF2_Projectile_SentryRocket.IsValid.get"); + + // extras/natives.sp + MarkNativeAsOptional("SF2_GetConfig"); + MarkNativeAsOptional("SF2_IsRunning"); + MarkNativeAsOptional("SF2_GetRoundState"); + MarkNativeAsOptional("SF2_IsRoundInGracePeriod"); + MarkNativeAsOptional("SF2_GetCurrentDifficulty"); + MarkNativeAsOptional("SF2_GetBossDifficulty"); + MarkNativeAsOptional("SF2_GetDifficultyModifier"); + MarkNativeAsOptional("SF2_IsInSpecialRound"); + MarkNativeAsOptional("SF2_GetCurrentBossPack"); + + MarkNativeAsOptional("SF2_GetClientGroup"); + MarkNativeAsOptional("SF2_GetClientQueuePoints"); + MarkNativeAsOptional("SF2_SetClientQueuePoints"); + + MarkNativeAsOptional("SF2_IsValidClient"); + MarkNativeAsOptional("SF2_IsClientCritBoosted"); + MarkNativeAsOptional("SF2_IsClientMiniCritBoosted"); + MarkNativeAsOptional("SF2_IsClientUbercharged"); + MarkNativeAsOptional("SF2_IsClientInKart"); + MarkNativeAsOptional("SF2_IsClientInCondition"); + MarkNativeAsOptional("SF2_IsClientEliminated"); + MarkNativeAsOptional("SF2_IsClientInGhostMode"); + MarkNativeAsOptional("SF2_IsClientProxy"); + + MarkNativeAsOptional("SF2_GetClientBlinkCount"); + MarkNativeAsOptional("SF2_IsClientBlinking"); + MarkNativeAsOptional("SF2_GetClientBlinkMeter"); + MarkNativeAsOptional("SF2_SetClientBlinkMeter"); + + MarkNativeAsOptional("SF2_GetClientProxyMaster"); + MarkNativeAsOptional("SF2_GetClientProxyControlAmount"); + MarkNativeAsOptional("SF2_GetClientProxyControlRate"); + MarkNativeAsOptional("SF2_SetClientProxyMaster"); + MarkNativeAsOptional("SF2_SetClientProxyControlAmount"); + MarkNativeAsOptional("SF2_SetClientProxyControlRate"); + + MarkNativeAsOptional("SF2_IsClientLookingAtBoss"); + MarkNativeAsOptional("SF2_DidClientEscape"); + MarkNativeAsOptional("SF2_ForceClientEscape"); + + MarkNativeAsOptional("SF2_GetClientFlashlightBatteryLife"); + MarkNativeAsOptional("SF2_SetClientFlashlightBatteryLife"); + MarkNativeAsOptional("SF2_IsClientUsingFlashlight"); + + MarkNativeAsOptional("SF2_IsClientTrapped"); + MarkNativeAsOptional("SF2_IsClientInDeathCam"); + MarkNativeAsOptional("SF2_ClientStartDeathCam"); + + MarkNativeAsOptional("SF2_ClientSpawnProxy"); + MarkNativeAsOptional("SF2_ClientForceProxy"); + + MarkNativeAsOptional("SF2_CollectAsPage"); + MarkNativeAsOptional("SF2_GetEmptyPageSpawnPoints"); + + MarkNativeAsOptional("SF2_ForceBossJump"); + + MarkNativeAsOptional("SF2_GetBossModelEntity"); + + MarkNativeAsOptional("SF2_GetBossTarget"); + MarkNativeAsOptional("SF2_SetBossTarget"); + + MarkNativeAsOptional("SF2_IsBossStunnable"); + MarkNativeAsOptional("SF2_IsBossStunnableByFlashlight"); + MarkNativeAsOptional("SF2_IsBossCloaked"); + MarkNativeAsOptional("SF2_GetBossStunHealth"); + MarkNativeAsOptional("SF2_SetBossStunHealth"); + + MarkNativeAsOptional("SF2_GetVectorSquareMagnitude"); + MarkNativeAsOptional("SF2_InitiateBossPackVote"); + MarkNativeAsOptional("SF2_IsSurvivalMap"); + MarkNativeAsOptional("SF2_IsBoxingMap"); + MarkNativeAsOptional("SF2_IsRaidMap"); + MarkNativeAsOptional("SF2_IsProxyMap"); + MarkNativeAsOptional("SF2_IsSlaughterRunMap"); + + // gamemodes/renevant.sp + MarkNativeAsOptional("SF2_IsRenevantMap"); + + // npc/npc_chaser.sp + MarkNativeAsOptional("SF2_GetBossCurrentAttackIndex"); + MarkNativeAsOptional("SF2_GetBossAttackIndexType"); + MarkNativeAsOptional("SF2_GetBossAttackIndexDamage"); + MarkNativeAsOptional("SF2_UpdateBossAnimation"); + MarkNativeAsOptional("SF2_GetBossAttackIndexDamageType"); + + MarkNativeAsOptional("SF2_PerformBossVoice"); + MarkNativeAsOptional("SF2_CreateBossSoundHint"); + + MarkNativeAsOptional("SF2_GetChaserProfileFromBossIndex"); + MarkNativeAsOptional("SF2_GetChaserProfileFromName"); + MarkNativeAsOptional("SF2_SetEntityForceChaseState"); + + // npc/npc_creeper.sp + MarkNativeAsOptional("SF2_GetStatueProfileFromBossIndex"); + MarkNativeAsOptional("SF2_GetStatueProfileFromName"); + + // npc/entities/base/entity.sp + MarkNativeAsOptional("SF2_BaseBossEntity.IsValid.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.Controller.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.Target.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.State.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.CurrentChaseDuration.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.CurrentChaseDuration.set"); + MarkNativeAsOptional("SF2_BaseBossEntity.InitialChaseDuration.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.InitialChaseDuration.set"); + MarkNativeAsOptional("SF2_BaseBossEntity.IsKillingSomeone.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.EyePosition"); + MarkNativeAsOptional("SF2_BaseBossEntity.GetProfileName"); + MarkNativeAsOptional("SF2_BaseBossEntity.GetName"); + MarkNativeAsOptional("SF2_BaseBossEntity.ProfileData"); + MarkNativeAsOptional("SF2_BaseBossEntity.ResetProfileAnimation"); + + // npc/entities/base/actions/playsequenceandwait.sp + MarkNativeAsOptional("SF2_PlaySequenceAndWaitAction.SF2_PlaySequenceAndWaitAction"); + + // npc/entities/chaser/entity.sp + MarkNativeAsOptional("SF2_ChaserBossEntity.IsValid.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsAttemptingToMove.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsAttacking.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsStunned.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.StunHealth.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.MaxStunHealth.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.CanBeStunned.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.CanTakeDamage.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsRaging.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsRunningAway.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsSelfHealing.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.ProfileData"); + MarkNativeAsOptional("SF2_ChaserBossEntity.PerformVoice"); + MarkNativeAsOptional("SF2_ChaserBossEntity.PerformCustomVoice"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GetDefaultPosture"); + MarkNativeAsOptional("SF2_ChaserBossEntity.SetDefaultPosture"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GetAttackName"); + MarkNativeAsOptional("SF2_ChaserBossEntity.AttackIndex.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GetNextAttackTime"); + MarkNativeAsOptional("SF2_ChaserBossEntity.SetNextAttackTime"); + MarkNativeAsOptional("SF2_ChaserBossEntity.DropItem"); + MarkNativeAsOptional("SF2_ChaserBossEntity.CreateSoundHint"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GroundSpeedOverride.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GroundSpeedOverride.set"); + + // npc/entities/statue/entity.sp + MarkNativeAsOptional("SF2_StatueBossEntity.IsValid.get"); + MarkNativeAsOptional("SF2_StatueBossEntity.IsMoving.get"); + MarkNativeAsOptional("SF2_StatueBossEntity.LastKillTime.get"); + MarkNativeAsOptional("SF2_StatueBossEntity.ProfileData"); +} +#endif \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/sf2/defines.inc b/addons/sourcemod/scripting/include/sf2/defines.inc index eaf37f34..f0d011f0 100644 --- a/addons/sourcemod/scripting/include/sf2/defines.inc +++ b/addons/sourcemod/scripting/include/sf2/defines.inc @@ -1,30 +1,23 @@ #define MASK_RED 33640459 #define MASK_BLUE 33638411 -#define EF_ITEM_BLINK 0x100 - #define FILE_RESTRICTEDWEAPONS "configs/sf2/restrictedweapons.cfg" #define FILE_RESTRICTEDWEAPONS_DATA "data/sf2/restrictedweapons.cfg" #define FILE_CLASSCONFIGS "configs/sf2/class_stats.cfg" #define FILE_CLASSCONFIGS_DATA "data/sf2/class_stats.cfg" -#define BOSS_THINKRATE 0.1 // doesn't really matter much since timers go at a minimum of 0.1 seconds anyways - #define CRIT_SOUND "player/crit_hit.wav" #define CRIT_PARTICLENAME "crit_text" #define MINICRIT_SOUND "player/crit_hit_mini.wav" #define MINICRIT_PARTICLENAME "minicrit_text" -#define ZAP_SOUND "weapons/barret_arm_zap.wav" +#define ZAP_SOUND ")weapons/barret_arm_zap.wav" #define ZAP_PARTICLENAME "dxhr_arm_muzzleflash" #define FIREWORKSBLU_PARTICLENAME "utaunt_firework_teamcolor_blue" #define FIREWORKSRED_PARTICLENAME "utaunt_firework_teamcolor_red" #define TELEPORTEDINBLU_PARTICLENAME "teleported_red" -#define SOUND_THUNDER "ambient/explosions/explode_9.wav" - -#define EXPLOSIVEDANCE_EXPLOSION1 "weapons/explode1.wav" -#define EXPLOSIVEDANCE_EXPLOSION2 "weapons/explode2.wav" -#define EXPLOSIVEDANCE_EXPLOSION3 "weapons/explode3.wav" +#define SOUND_THUNDER ")ambient/explosions/explode_9.wav" +#define DEBUG_PAGEREVEALSOUND "#ui/cyoa_node_activate.wav" #define SPECIAL1UPSOUND "mvm/mvm_revive.wav" @@ -36,8 +29,8 @@ #define PAGE_MODEL "models/slender/pickups/sheet.mdl" #define PAGE_MODELSCALE 1.0 -#define DEFAULT_CLOAKONSOUND "weapons/medi_shield_deploy.wav" -#define DEFAULT_CLOAKOFFSOUND "weapons/medi_shield_retract.wav" +#define DEFAULT_CLOAKONSOUND ")weapons/medi_shield_deploy.wav" +#define DEFAULT_CLOAKOFFSOUND ")weapons/medi_shield_retract.wav" #define SF_KEYMODEL "models/demani_sf/key_australium.mdl" @@ -45,7 +38,7 @@ #define FLASHLIGHT_CLICKSOUND_NIGHTVISION "slender/nightvision.mp3" #define FLASHLIGHT_BREAKSOUND "ambient/energy/spark6.wav" #define FLASHLIGHT_NOSOUND "player/suit_denydevice.wav" -#define PAGE_GRABSOUND "slender/slenderpagegrab.wav" +#define PAGE_GRABSOUND ")slender/slenderpagegrab.wav" #define MUSIC_CHAN SNDCHAN_AUTO #define MUSIC_GOTPAGES1_SOUND "slender/newambience_1.wav" @@ -58,32 +51,34 @@ #define PROXY_RAGE_MODE_SOUND "slender/proxyrage.mp3" -#define FIREBALL_SHOOT "misc/halloween/spell_fireball_cast.wav" -#define FIREBALL_IMPACT "misc/halloween/spell_fireball_impact.wav" +#define FIREBALL_SHOOT ")misc/halloween/spell_fireball_cast.wav" +#define FIREBALL_IMPACT ")misc/halloween/spell_fireball_impact.wav" #define FIREBALL_TRAIL "spell_fireball_small_red" -#define ICEBALL_IMPACT "weapons/icicle_freeze_victim_01.wav" +#define ICEBALL_IMPACT ")weapons/icicle_freeze_victim_01.wav" #define ICEBALL_TRAIL "spell_fireball_small_blue" -#define ROCKET_SHOOT "weapons/rocket_shoot.wav" -#define ROCKET_IMPACT "weapons/explode1.wav" +#define ROCKET_SHOOT ")weapons/rocket_shoot.wav" +#define ROCKET_IMPACT ")weapons/explode1.wav" +#define ROCKET_IMPACT2 ")weapons/explode2.wav" +#define ROCKET_IMPACT3 ")weapons/explode3.wav" #define ROCKET_MODEL "models/weapons/w_models/w_rocket.mdl" #define ROCKET_TRAIL "rockettrail" #define ROCKET_EXPLODE_PARTICLE "ExplosionCore_MidAir" -#define GRENADE_SHOOT "weapons/grenade_launcher_shoot.wav" -#define SENTRYROCKET_SHOOT "weapons/sentry_rocket.wav" -#define ARROW_SHOOT "weapons/bow_shoot.wav" +#define GRENADE_SHOOT ")weapons/grenade_launcher_shoot.wav" +#define SENTRYROCKET_SHOOT ")weapons/sentry_rocket.wav" +#define ARROW_SHOOT ")weapons/bow_shoot.wav" #define GRENADE_MODEL "models/weapons/w_models/w_grenade_grenadelauncher.mdl" -#define MANGLER_SHOOT "weapons/cow_mangler_main_shot.wav" -#define MANGLER_EXPLODE1 "weapons/cow_mangler_explosion_normal_01.wav" -#define MANGLER_EXPLODE2 "weapons/cow_mangler_explosion_normal_02.wav" -#define MANGLER_EXPLODE3 "weapons/cow_mangler_explosion_normal_03.wav" -#define BASEBALL_SHOOT "weapons/bat_baseball_hit1.wav" +#define MANGLER_SHOOT ")weapons/cow_mangler_main_shot.wav" +#define MANGLER_EXPLODE1 ")weapons/cow_mangler_explosion_normal_01.wav" +#define MANGLER_EXPLODE2 ")weapons/cow_mangler_explosion_normal_02.wav" +#define MANGLER_EXPLODE3 ")weapons/cow_mangler_explosion_normal_03.wav" +#define BASEBALL_SHOOT ")weapons/bat_baseball_hit1.wav" #define BASEBALL_MODEL "weapons/w_models/w_baseball.mdl" -#define JARATE_HITPLAYER "weapons/jar_single.wav" +#define JARATE_HITPLAYER ")weapons/jar_single.wav" #define JARATE_PARTICLE "peejar_impact" #define MILK_PARTICLE "peejar_impact_milk" #define GAS_PARTICLE "gas_can_impact_blue" -#define STUN_HITPLAYER "weapons/icicle_freeze_victim_01.wav" +#define STUN_HITPLAYER ")weapons/icicle_freeze_victim_01.wav" #define STUN_PARTICLE "xms_icicle_melt" #define ELECTRIC_RED_PARTICLE "electrocuted_gibbed_red" #define ELECTRIC_BLUE_PARTICLE "electrocuted_gibbed_red" @@ -98,10 +93,6 @@ #define JARATE_ROLL "weapons/jar_explode.wav" #define GAS_ROLL "weapons/gas_can_explode.wav" -#define FIREWORK_EXPLOSION "weapons/flare_detonator_explode.wav" -#define FIREWORK_START "weapons/flare_detonator_launch.wav" -#define FIREWORK_PARTICLE "burningplayer_rainbow_flame" - #define GENERIC_ROLL_TICK "ui/buttonrollover.wav" #define MINICRIT_BUFF "weapons/buff_banner_flag.wav" @@ -121,8 +112,8 @@ #define NINETYSMUSIC "slender/sf2modified_runninginthe90s_v2.wav" -#define TRAP_DEPLOY "slender/modified_traps/beartrap/trap_deploy.mp3" -#define TRAP_CLOSE "slender/modified_traps/beartrap/trap_close.mp3" +#define TRAP_DEPLOY ")slender/modified_traps/beartrap/trap_deploy.mp3" +#define TRAP_CLOSE ")slender/modified_traps/beartrap/trap_close.mp3" #define TRAP_MODEL "models/mentrillum/traps/beartrap.mdl" #define LASER_MODEL "sprites/laser.vmt" @@ -142,4 +133,4 @@ #define SF2_HUD_TEXT_COLOR_B 141 #define SF2_HUD_TEXT_COLOR_A 255 -#define SF2_CHASER_BOSS_MAX_ATTACKS 32 +#define SF2_PROFILE_CHASER_DEFAULT_POSTURE "default" \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 04aed187..8cb7c864 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -1,14 +1,37 @@ #include +static const char g_SoundModifiers[][] = { + "*", + "#", + "@", + ">", + "<", + "^", + ")", + "}", + "$", + "!", + "?" +}; + // ====================================== // FUNCTIONS // ====================================== -stock void PrecacheSound2(const char[] path, bool checkFile) +stock void PrecacheSound2(const char[] path, bool checkFile, bool replace = true) { - PrecacheSound(path, true); + char tempPath[PLATFORM_MAX_PATH]; + strcopy(tempPath, sizeof(tempPath), path); + PrecacheSound(tempPath, true); + if (replace) + { + for (int i = 0; i < sizeof(g_SoundModifiers); i++) + { + ReplaceStringEx(tempPath, sizeof(tempPath), g_SoundModifiers[i], ""); + } + } char buffer[PLATFORM_MAX_PATH]; - FormatEx(buffer, sizeof(buffer), "sound/%s", path); + FormatEx(buffer, sizeof(buffer), "sound/%s", tempPath); if (checkFile) { if (FileExists(buffer, true)) @@ -35,34 +58,37 @@ stock void PrecacheSound2(const char[] path, bool checkFile) * @param disableWarning Disables the warning if a file doesn't exist. * @param checkFile Determines if missing files should be checked. */ -stock void TryPrecacheBossProfileSoundPath(const char[] soundPath, bool disableWarning = false, bool checkFile) +stock void TryPrecacheBossProfileSoundPath(const char[] soundPath, bool checkFile) { if (soundPath[0] == '\0') { return; } - char fullPath[PLATFORM_MAX_PATH]; - FormatEx(fullPath, sizeof(fullPath), "sound/%s", soundPath); + char fullPath[PLATFORM_MAX_PATH], tempPath[PLATFORM_MAX_PATH]; + strcopy(tempPath, sizeof(tempPath), soundPath); + PrecacheSound(tempPath); + for (int i = 0; i < sizeof(g_SoundModifiers); i++) + { + ReplaceStringEx(tempPath, sizeof(tempPath), g_SoundModifiers[i], ""); + } + FormatEx(fullPath, sizeof(fullPath), "sound/%s", tempPath); if (checkFile) { if (FileExists(fullPath, false) || FileExists(fullPath, true)) { - PrecacheSound2(soundPath, checkFile); + PrecacheSound2(tempPath, checkFile, false); } else { - if (!disableWarning) - { - PrintToServer("[SF2] Sound file %s does not exist, excluding from downloads!", fullPath); - } - PrecacheSound(soundPath); + PrintToServer("[SF2] Sound file %s does not exist, excluding from downloads!", fullPath); + PrecacheSound(tempPath); } } else { - PrecacheSound2(soundPath, checkFile); + PrecacheSound2(tempPath, checkFile, false); } } @@ -74,11 +100,11 @@ stock void TryPrecacheBossProfileSoundPath(const char[] soundPath, bool disableW * @param xbox Determines if the .xbox file extension should be checked. * @param checkFile Determines if missing files should be checked. */ -stock void PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, bool checkFile) +stock int PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, bool checkFile) { if (path[0] == '\0') { - return; + return -1; } char fixedPath[PLATFORM_MAX_PATH]; @@ -130,9 +156,10 @@ stock void PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, } FormatEx(buffer, sizeof(buffer), "models/%s.mdl", fixedPath); + int returnValue = -1; if (!IsModelPrecached(buffer)) { - PrecacheModel(buffer); + returnValue = PrecacheModel(buffer); } if (checkFile) @@ -220,6 +247,8 @@ stock void PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, AddFileToDownloadsTable(buffer); } } + + return returnValue; } /** @@ -388,6 +417,20 @@ stock void GetProfileDifficultyNumValues(KeyValues kv, const char[] baseKeyName, buffer[j] = value; } } + else + { + if (defaultValues[i] != 0) + { + buffer[i] = defaultValues[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } + } } } @@ -426,6 +469,20 @@ stock void GetProfileDifficultyBoolValues(KeyValues kv, const char[] baseKeyName buffer[j] = value; } } + else + { + if (defaultValues[i]) + { + buffer[i] = defaultValues[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } + } } } @@ -464,6 +521,20 @@ stock void GetProfileDifficultyFloatValues(KeyValues kv, const char[] baseKeyNam buffer[j] = value; } } + else + { + if (defaultValues[i] != 0.0) + { + buffer[i] = defaultValues[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } + } } } @@ -518,6 +589,28 @@ stock void GetProfileDifficultyVectorValues(KeyValues kv, const char[] baseKeyNa buffer[j] = value; } } + else + { + bool def = false; + for (int k = 0; k < 3; k++) + { + if (defaultValueVectors[i][k] != 0.0) + { + def = true; + } + } + if (def) + { + buffer[i] = defaultValueVectors[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } + } } } @@ -572,6 +665,20 @@ void GetProfileDifficultyStringValues(KeyValues kv, const char[] baseKeyName, ch strcopy(bufferArray[j], maxStringLen, value); } } + else + { + if (defaultValueStrings[i][0] != '\0') + { + strcopy(bufferArray[i], maxStringLen, defaultValueStrings[i]); + } + else + { + if (i > 0) + { + strcopy(bufferArray[i], maxStringLen, bufferArray[i - 1]); + } + } + } } } @@ -579,12 +686,12 @@ stock void SetProfileDifficultyStringArrayValues(KeyValues kv, const char[] base { char key[64]; char temporaryKeys[PLATFORM_MAX_PATH]; + char[] value = new char[PLATFORM_MAX_PATH]; for (int i = 0; i < Difficulty_Max; i++) { GetProfileKeyWithDifficultySuffix(baseKeyName, i == 0 ? 1 : i, key, sizeof(key)); - char[] value = new char[PLATFORM_MAX_PATH]; - kv.GetString(key, value, PLATFORM_MAX_PATH, temporaryKeys); + kv.GetString(key, value, PLATFORM_MAX_PATH, value); if (models && strncmp(value, "models/", 7, false) != 0 && strncmp(value, "models\\", 7, false) != 0 && value[0] != '\0') { Format(value, PLATFORM_MAX_PATH, "models/%s", value); @@ -603,10 +710,10 @@ bool GetProfileColorNoBacks(KeyValues kv, int &g, int &b, int &a, - int dr=255, - int dg=255, - int db=255, - int da=255) + int dr = 255, + int dg = 255, + int db = 255, + int da = 255) { r = dr; g = dg; @@ -674,6 +781,13 @@ void GetProfileDifficultyColorValues(KeyValues kv, const char[] baseKeyName, int buffer[j] = value; } } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } } } @@ -683,6 +797,7 @@ void GetProfileDifficultyColorValues(KeyValues kv, const char[] baseKeyName, int enum struct SF2BossProfileSoundInfo { + char SectionName[64]; int Channel; float Volume; int Flags; @@ -699,6 +814,7 @@ enum struct SF2BossProfileSoundInfo void Init() { + this.SectionName[0] = '\0'; this.Channel = SNDCHAN_AUTO; this.Volume = SNDVOL_NORMAL; this.Flags = SND_NOFLAGS; @@ -711,13 +827,15 @@ enum struct SF2BossProfileSoundInfo this.Radius = 850.0; this.Chance = 1.0; - this.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + this.Paths = null; } void Load(KeyValues kv, bool checkFile) { + kv.GetSectionName(this.SectionName, sizeof(this.SectionName)); if (kv.JumpToKey("paths")) { + this.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); char soundPath[PLATFORM_MAX_PATH], section[64]; for (int i = 1;; i++) { @@ -728,7 +846,7 @@ enum struct SF2BossProfileSoundInfo break; } - TryPrecacheBossProfileSoundPath(soundPath, _, checkFile); + TryPrecacheBossProfileSoundPath(soundPath, checkFile); this.Paths.PushString(soundPath); } @@ -756,6 +874,59 @@ enum struct SF2BossProfileSoundInfo this.Chance = 1.0; } } + else + { + this.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char soundPath[PLATFORM_MAX_PATH], section[64], formatter[90]; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + kv.GetString(section, soundPath, sizeof(soundPath)); + if (soundPath[0] == '\0') + { + break; + } + + TryPrecacheBossProfileSoundPath(soundPath, checkFile); + + this.Paths.PushString(soundPath); + } + kv.GoBack(); + FormatEx(formatter, sizeof(formatter), "%s_channel", this.SectionName); + this.Channel = kv.GetNum(formatter, this.Channel); + FormatEx(formatter, sizeof(formatter), "%s_level", this.SectionName); + this.Level = kv.GetNum(formatter, this.Level); + FormatEx(formatter, sizeof(formatter), "%s_flags", this.SectionName); + this.Flags = kv.GetNum(formatter, this.Flags); + FormatEx(formatter, sizeof(formatter), "%s_volume", this.SectionName); + this.Volume = kv.GetFloat(formatter, this.Volume); + FormatEx(formatter, sizeof(formatter), "%s_pitch", this.SectionName); + this.Pitch = kv.GetNum(formatter, this.Pitch); + this.PitchRandomMin = this.Pitch; + this.PitchRandomMax = this.Pitch; + FormatEx(formatter, sizeof(formatter), "%s_cooldown_min", this.SectionName); + this.CooldownMin = kv.GetFloat(formatter, this.CooldownMin); + FormatEx(formatter, sizeof(formatter), "%s_cooldown_max", this.SectionName); + this.CooldownMax = kv.GetFloat(formatter, this.CooldownMax); + FormatEx(formatter, sizeof(formatter), "%s_pitch_random_min", this.SectionName); + this.PitchRandomMin = kv.GetNum(formatter, this.PitchRandomMin); + FormatEx(formatter, sizeof(formatter), "%s_pitch_random_max", this.SectionName); + this.PitchRandomMax = kv.GetNum(formatter, this.PitchRandomMax); + FormatEx(formatter, sizeof(formatter), "%s_radius", this.SectionName); + this.Radius = kv.GetFloat(formatter, this.Radius); + FormatEx(formatter, sizeof(formatter), "%s_chance", this.SectionName); + this.Chance = kv.GetFloat(formatter, this.Chance); + if (this.Chance < 0.0) + { + this.Chance = 0.0; + } + if (this.Chance > 1.0) + { + this.Chance = 1.0; + } + + kv.JumpToKey(this.SectionName); + } } void PostLoad() @@ -793,14 +964,34 @@ enum struct SF2BossProfileSoundInfo } } - void EmitSound(bool toClient = false, int entity = -1, int emitter = SOUND_FROM_PLAYER, float origin[3] = NULL_VECTOR, int addPitch = 0) + void EmitSound(bool toClient = false, int entity = -1, int emitter = SOUND_FROM_PLAYER, float origin[3] = NULL_VECTOR, int addPitch = 0, char returnSound[PLATFORM_MAX_PATH] = "", int definedIndex = -1) { if (this.Paths == null || this.Paths.Length <= 0) { return; } char buffer[PLATFORM_MAX_PATH]; - this.Paths.GetString(GetRandomInt(0, this.Paths.Length - 1), buffer, sizeof(buffer)); + if (definedIndex != -1) + { + this.Paths.GetString(definedIndex, buffer, sizeof(buffer)); + } + else + { + this.Paths.GetString(GetRandomInt(0, this.Paths.Length - 1), buffer, sizeof(buffer)); + } + returnSound = buffer; + if (StrContains(buffer, ".mp3", true) == -1 && StrContains(buffer, ".wav", true) == -1) + { + if (!toClient) + { + EmitGameSoundToAll(buffer, entity, this.Flags); + } + else + { + EmitGameSoundToClient(entity, buffer, emitter, this.Flags); + } + return; + } int pitch = GetRandomInt(this.PitchRandomMin, this.PitchRandomMax); int volumeCount = RoundToCeil(this.Volume); for (int i = 0; i < volumeCount; i++) @@ -862,6 +1053,98 @@ enum struct SF2BossProfileSoundInfo } } +enum struct SF2ParticleData +{ + char ParticleName[128]; + bool AttachParticles; + bool BeamParticles; + ParticleAttachment Type; + char Attachment[64]; + + void Init() + { + this.ParticleName[0] = '\0'; + this.AttachParticles = true; + this.BeamParticles = false; + this.Type = PATTACH_CUSTOMORIGIN; + this.Attachment[0] = '\0'; + } + + void Load(KeyValues kv) + { + kv.GetString("particle", this.ParticleName, sizeof(this.ParticleName), this.ParticleName); + this.AttachParticles = kv.GetNum("attach", this.AttachParticles) != 0; + this.BeamParticles = kv.GetNum("beam", this.BeamParticles) != 0; + this.Type = view_as(kv.GetNum("attach_type", view_as(this.Type))); + kv.GetString("attachment", this.Attachment, sizeof(this.Attachment), this.Attachment); + } + + void Apply(CBaseAnimating player, CBaseAnimating caster) + { + CBaseAnimating castTo; + float myPos[3], myAng[3], targetPos[3]; + if (!this.AttachParticles || this.BeamParticles) + { + caster.WorldSpaceCenter(myPos); + caster.GetAbsAngles(myAng); + player.WorldSpaceCenter(targetPos); + if (this.AttachParticles) + { + castTo = caster; + } + } + else + { + player.WorldSpaceCenter(myPos); + player.GetAbsAngles(myAng); + castTo = player; + } + + int attachment = 0; + if (this.Attachment[0] != '\0') + { + attachment = castTo.LookupAttachment(this.Attachment); + } + if (attachment != 0) + { + castTo.GetAttachment(attachment, myPos, myAng); + } + + if (this.Type == PATTACH_ROOTBONE_FOLLOW) + { + myPos = NULL_VECTOR; + myAng = NULL_VECTOR; + } + + if (this.BeamParticles) + { + DispatchParticleEffectBeam(castTo.index, this.ParticleName, myPos, myAng, targetPos, attachment, this.Type, true); + } + else + { + if (!DispatchParticleEffect(castTo.index, this.ParticleName, myPos, myAng, myPos, attachment, this.Type, true)) + { + if (this.Type == PATTACH_ROOTBONE_FOLLOW) + { + castTo.WorldSpaceCenter(myPos); + castTo.GetAbsAngles(myAng); + } + CBaseEntity particleEnt = CBaseEntity(CreateEntityByName("info_particle_system")); + if (particleEnt.IsValid()) + { + particleEnt.Teleport(myPos, myAng, NULL_VECTOR); + + particleEnt.KeyValue("effect_name", this.ParticleName); + particleEnt.Spawn(); + particleEnt.Activate(); + particleEnt.AcceptInput("start"); + RemoveEntity(particleEnt.index); + } + } + } + } +} + enum EffectEvent { EffectEvent_Invalid = -1, @@ -879,11 +1162,15 @@ enum EffectType EffectType_Trail, EffectType_PropDynamic, EffectType_PointSpotlight, - EffectType_Sprite + EffectType_Sprite, + EffectType_TempEntBeamRing, + EffectType_TempEntParticle, + EffectType_Sound }; enum struct SF2BossProfileBaseEffectInfo { + char SectionName[64]; EffectEvent Event; EffectType Type; float Origin[3]; @@ -898,6 +1185,8 @@ enum struct SF2BossProfileBaseEffectInfo char AttachmentName[PLATFORM_MAX_PATH]; ArrayList Colors; int DifficultyIndexes; + float Delay; + bool AttachPlayer; int SteamSpreadSpeed; int SteamSpeed; @@ -927,18 +1216,45 @@ enum struct SF2BossProfileBaseEffectInfo float SpotlightWidth; float SpotlightLength; + float SpotlightHaloScale; char SpriteName[PLATFORM_MAX_PATH]; float SpriteScale; + float BeamRingStartRadius; + float BeamRingEndRadius; + int BeamRingStartFrame; + int BeamRingFrameRate; + float BeamRingWidth; + float BeamRingAmplitude; + int BeamRingSpeed; + int BeamRingFlags; + char BeamRingColor[32]; + char BeamRingBeamSprite[PLATFORM_MAX_PATH]; + int BeamRingBeamModel; + char BeamRingHaloSprite[PLATFORM_MAX_PATH]; + int BeamRingHaloModel; + + float TEParticleStartPos[3]; + int TEParticleAttachType; + bool TEParticleReset; + bool TEParticleHasControlPoint; + int TEParticleControlPointAttachType; + float TEParticleControlPointOffset[3]; + + SF2BossProfileSoundInfo SoundSounds; + void Init() { + this.SectionName[0] = '\0'; this.Event = EffectEvent_Invalid; this.Type = EffectType_Invalid; for (int i = 0; i < 3; i++) { this.Origin[i] = 0.0; this.Angles[i] = 0.0; + this.TEParticleStartPos[i] = 0.0; + this.TEParticleControlPointOffset[i] = 0.0; } this.RenderModes = RENDER_NORMAL; this.RenderEffects = RENDERFX_NONE; @@ -948,6 +1264,7 @@ enum struct SF2BossProfileBaseEffectInfo this.LifeTime = -1.0; this.Colors = null; this.DifficultyIndexes = 123456; + this.Delay = 0.0; this.SteamSpreadSpeed = 0; this.SteamSpeed = 0; @@ -977,14 +1294,37 @@ enum struct SF2BossProfileBaseEffectInfo this.SpotlightWidth = 40.0; this.SpotlightLength = 512.0; + this.SpotlightHaloScale = 40.0; this.SpriteName[0] = '\0'; this.SpriteScale = 1.0; + + this.BeamRingStartRadius = 5.0; + this.BeamRingEndRadius = 10.0; + this.BeamRingStartFrame = 0; + this.BeamRingFrameRate = 12; + this.BeamRingWidth = 100.0; + this.BeamRingAmplitude = 1.0; + this.BeamRingSpeed = 5; + this.BeamRingFlags = 0; + this.BeamRingColor = "255 255 255 255"; + this.BeamRingBeamSprite = "sprites/laser.vmt"; + this.BeamRingBeamModel = -1; + this.BeamRingHaloSprite = "sprites/halo01.vmt"; + this.BeamRingHaloModel = -1; + + this.TEParticleAttachType = 0; + this.TEParticleReset = true; + this.TEParticleHasControlPoint = false; + this.TEParticleControlPointAttachType = 0; + + this.SoundSounds.Init(); } - void Load(KeyValues kv) + void Load(KeyValues kv, bool checkFile) { - char effectTypeString[64]; + kv.GetSectionName(this.SectionName, sizeof(this.SectionName)); + char effectTypeString[64], buffer[PLATFORM_MAX_PATH]; kv.GetString("type", effectTypeString, sizeof(effectTypeString)); if (strcmp(effectTypeString, "steam", false) == 0) { @@ -998,7 +1338,7 @@ enum struct SF2BossProfileBaseEffectInfo this.SteamRollSpeed = kv.GetFloat("rollspeed", this.SteamRollSpeed); this.SteamType = kv.GetNum("particletype", this.SteamType); } - if (strcmp(effectTypeString, "dynamiclight", false) == 0) + else if (strcmp(effectTypeString, "dynamiclight", false) == 0) { this.Type = EffectType_DynamicLight; this.LightBrightness = kv.GetNum("brightness", this.LightBrightness); @@ -1008,12 +1348,12 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } - if (strcmp(effectTypeString, "particle", false) == 0) + else if (strcmp(effectTypeString, "particle", false) == 0) { this.Type = EffectType_Particle; kv.GetString("particlename", this.ParticleName, sizeof(this.ParticleName), this.ParticleName); } - if (strcmp(effectTypeString, "trail", false) == 0) + else if (strcmp(effectTypeString, "trail", false) == 0) { this.Type = EffectType_Trail; this.TrailTime = kv.GetFloat("trailtime", this.TrailTime); @@ -1023,7 +1363,7 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } - if (strcmp(effectTypeString, "propdynamic", false) == 0) + else if (strcmp(effectTypeString, "propdynamic", false) == 0) { this.Type = EffectType_PropDynamic; kv.GetString("modelname", this.ModelName, sizeof(this.ModelName), this.ModelName); @@ -1033,16 +1373,20 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } - if (strcmp(effectTypeString, "pointspotlight", false) == 0) + else if (strcmp(effectTypeString, "pointspotlight", false) == 0) { this.Type = EffectType_PointSpotlight; this.RenderModes = RENDER_TRANSTEXTURE; this.SpotlightWidth = kv.GetFloat("spotlightwidth", this.SpotlightWidth); this.SpotlightLength = kv.GetFloat("spotlightlength", this.SpotlightLength); + this.SpotlightHaloScale = kv.GetFloat("halo_scale", this.SpotlightHaloScale); + this.LightBrightness = kv.GetNum("brightness", this.LightBrightness); + this.LightMaxDistance = kv.GetFloat("distance", this.LightMaxDistance); + this.LightCone = kv.GetNum("cone", this.LightCone); this.SetupColors(kv); } - if (strcmp(effectTypeString, "sprite", false) == 0) + else if (strcmp(effectTypeString, "sprite", false) == 0) { this.Type = EffectType_Sprite; kv.GetString("spritename", this.SpriteName, sizeof(this.SpriteName), this.SpriteName); @@ -1050,8 +1394,78 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } + else if (strcmp(effectTypeString, "te_beamring", false) == 0) + { + this.Type = EffectType_TempEntBeamRing; + this.BeamRingStartRadius = kv.GetFloat("start_radius", this.BeamRingStartRadius); + this.BeamRingEndRadius = kv.GetFloat("end_radius", this.BeamRingEndRadius); + this.BeamRingStartFrame = kv.GetNum("start_frame", this.BeamRingStartFrame); + this.BeamRingFrameRate = kv.GetNum("framerate", this.BeamRingFrameRate); + this.BeamRingWidth = kv.GetFloat("width", this.BeamRingWidth); + this.BeamRingAmplitude = kv.GetFloat("amplitude", this.BeamRingAmplitude); + this.BeamRingSpeed = kv.GetNum("speed", this.BeamRingSpeed); + this.BeamRingFlags = kv.GetNum("flags", this.BeamRingFlags); + kv.GetString("color", this.BeamRingColor, sizeof(this.BeamRingColor), this.BeamRingColor); + kv.GetString("beam_sprite", this.BeamRingBeamSprite, sizeof(this.BeamRingBeamSprite), this.BeamRingBeamSprite); + if (this.BeamRingBeamSprite[0] != '\0') + { + this.BeamRingBeamModel = PrecacheModel(this.BeamRingBeamSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", this.BeamRingBeamSprite); + AddFileToDownloadsTable(buffer); + } + kv.GetString("halo_sprite", this.BeamRingHaloSprite, sizeof(this.BeamRingHaloSprite), this.BeamRingHaloSprite); + if (this.BeamRingHaloSprite[0] != '\0') + { + this.BeamRingHaloModel = PrecacheModel(this.BeamRingHaloSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", this.BeamRingHaloSprite); + AddFileToDownloadsTable(buffer); + } + } + else if (strcmp(effectTypeString, "te_particle", false) == 0) + { + this.Type = EffectType_TempEntParticle; + kv.GetString("particlename", this.ParticleName, sizeof(this.ParticleName), this.ParticleName); + kv.GetVector("start", this.TEParticleStartPos, this.TEParticleStartPos); + + char attachType[64]; + kv.GetString("attach_type", attachType, sizeof(attachType)); + if (strcmp(attachType, "follow_origin", false) == 0) + { + this.TEParticleAttachType = 1; + } + else if (strcmp(attachType, "start_at_customorigin", false) == 0) + { + this.TEParticleAttachType = 2; + } + else if (strcmp(attachType, "start_at_attachment", false) == 0) + { + this.TEParticleAttachType = 3; + } + else if (strcmp(attachType, "follow_attachment", false) == 0) + { + this.TEParticleAttachType = 4; + } + else if (strcmp(attachType, "start_at_worldorigin", false) == 0) + { + this.TEParticleAttachType = 5; + } + else if (strcmp(attachType, "follow_rootbone", false) == 0) + { + this.TEParticleAttachType = 6; + } + + this.TEParticleReset = kv.GetNum("reset_particles", this.TEParticleReset) != 0; + this.TEParticleHasControlPoint = kv.GetNum("control_point", this.TEParticleHasControlPoint) != 0; + this.TEParticleControlPointAttachType = kv.GetNum("control_point_attach_type", this.TEParticleControlPointAttachType); + kv.GetVector("control_point_offset", this.TEParticleControlPointOffset, this.TEParticleControlPointOffset); + } + else if (strcmp(effectTypeString, "sound", false) == 0) + { + this.Type = EffectType_Sound; + this.SoundSounds.Load(kv, checkFile); + } - kv.GetString("event", effectTypeString, sizeof(effectTypeString)); + kv.GetString("event", effectTypeString, sizeof(effectTypeString), "constant"); if (strcmp(effectTypeString, "constant", false) == 0) { this.Event = EffectEvent_Constant; @@ -1072,8 +1486,11 @@ enum struct SF2BossProfileBaseEffectInfo kv.GetVector("origin", this.Origin, this.Origin); kv.GetVector("angles", this.Angles, this.Angles); this.FadeAlpha = kv.GetNum("renderamt", this.FadeAlpha); - this.Attachment = view_as(kv.GetNum("attach_point", this.Attachment)); + this.Attachment = kv.GetNum("attach_point", this.Attachment) != 0; kv.GetString("attachment_point", this.AttachmentName, sizeof(this.AttachmentName), this.AttachmentName); + this.LifeTime = kv.GetFloat("lifetime", this.LifeTime); + this.Delay = kv.GetFloat("delay", this.Delay); + this.AttachPlayer = kv.GetNum("attach_player", this.AttachPlayer) != 0; } void SetupColors(KeyValues kv) @@ -1088,12 +1505,18 @@ enum struct SF2BossProfileBaseEffectInfo } } + void PostLoad() + { + this.SoundSounds.PostLoad(); + } + void Destroy() { if (this.Colors != null) { delete this.Colors; } + this.SoundSounds.Destroy(); } } @@ -1106,16 +1529,20 @@ enum struct SF2BossProfileCompanionsInfo { for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - this.Weight[difficulty] = difficulty > 0 ? this.Weight[difficulty - 1] : 1.0; + this.Weight[difficulty] = 1.0; } - this.Bosses = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); + this.Bosses = null; } void Load(KeyValues kv) { char compProfile[SF2_MAX_PROFILE_NAME_LENGTH], num[32]; - kv.JumpToKey("bosses"); - for (int i = 1;;i++) + if (!kv.JumpToKey("bosses")) + { + return; + } + this.Bosses = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); + for (int i = 1;; i++) { FormatEx(num, sizeof(num), "%d", i); kv.GetString(num, compProfile, sizeof(compProfile)); @@ -1179,46 +1606,66 @@ enum struct SF2BossProfileAnimationSectionNameData // This does NOT cover the en float Playbackrate[Difficulty_Max]; float Cycle[Difficulty_Max]; float FootstepInterval[Difficulty_Max]; + bool OverrideLoop[Difficulty_Max]; + bool Loop[Difficulty_Max]; char SectionName[64]; + char SubSectionName[64]; - void Init(char optionalSectionName[64] = "") + void Init(char optionalSectionName[64] = "", char optionalSubSection[64] = "") { - this.Sequences = new ArrayList(ByteCountToCells(64)); - this.Sequences.Resize(Difficulty_Max); - if (optionalSectionName[0] != '\0') + this.Sequences = null; + if (optionalSectionName[0] == '\0') { this.SectionName[0] = '\0'; } else { - this.SectionName = optionalSectionName; + strcopy(this.SectionName, sizeof(this.SectionName), optionalSectionName); } + strcopy(this.SubSectionName, sizeof(this.SubSectionName), optionalSubSection); for (int i = 0; i < Difficulty_Max; i++) { - if (strcmp(optionalSectionName, "stun") == 0) - { - this.Duration[i] = this.Duration[i] = i > 0 ? this.Duration[i - 1] : 3.5; - } - else - { - this.Duration[i] = this.Duration[i] = i > 0 ? this.Duration[i - 1] : 0.0; - } - this.Playbackrate[i] = this.Playbackrate[i] = i > 0 ? this.Playbackrate[i - 1] : 1.0; - this.Cycle[i] = this.Cycle[i] = i > 0 ? this.Cycle[i - 1] : 0.0; - this.FootstepInterval[i] = this.FootstepInterval[i] = i > 0 ? this.FootstepInterval[i - 1] : 0.0; + this.Duration[i] = 0.0; + this.Playbackrate[i] = 1.0; + this.Cycle[i] = 0.0; + this.FootstepInterval[i] = -1.0; + this.OverrideLoop[i] = false; + this.Loop[i] = false; } } - void Load(KeyValues kv) + void Load(KeyValues kv, bool newStuff = true) { - char animNames[Difficulty_Max][64]; + char animNames[Difficulty_Max][64], formatter[90]; + this.Sequences = new ArrayList(ByteCountToCells(64)); + this.Sequences.Resize(Difficulty_Max); + + if (newStuff) + { + GetProfileDifficultyStringValues(kv, "name", animNames, sizeof(animNames[]), animNames); + GetProfileDifficultyFloatValues(kv, "playbackrate", this.Playbackrate, this.Playbackrate); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); + GetProfileDifficultyFloatValues(kv, "cycle", this.Cycle, this.Cycle); + GetProfileDifficultyFloatValues(kv, "footstepinterval", this.FootstepInterval, this.FootstepInterval); + GetProfileDifficultyBoolValues(kv, "override_loop", this.OverrideLoop, this.OverrideLoop); + GetProfileDifficultyBoolValues(kv, "loop", this.Loop, this.Loop); + } + + FormatEx(formatter, sizeof(formatter), "animation_%s", this.SectionName); + GetProfileDifficultyStringValues(kv, formatter, animNames, sizeof(animNames[]), animNames); + + FormatEx(formatter, sizeof(formatter), "animation_%s_playbackrate", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.Playbackrate, this.Playbackrate); + + FormatEx(formatter, sizeof(formatter), "animation_%s_duration", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.Duration, this.Duration); - GetProfileDifficultyStringValues(kv, "name", animNames, sizeof(animNames[]), animNames); - GetProfileDifficultyFloatValues(kv, "playbackrate", this.Playbackrate, this.Playbackrate); - GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); - GetProfileDifficultyFloatValues(kv, "cycle", this.Cycle, this.Cycle); - GetProfileDifficultyFloatValues(kv, "footstepinterval", this.FootstepInterval, this.FootstepInterval); + FormatEx(formatter, sizeof(formatter), "animation_%s_cycle", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.Cycle, this.Cycle); + + FormatEx(formatter, sizeof(formatter), "animation_%s_cycle", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.FootstepInterval, this.FootstepInterval); for (int i = 0; i < Difficulty_Max; i++) { @@ -1249,23 +1696,31 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima { StringMap Animations; StringMap Gestures; + bool Classic; void Init() { - this.Animations = new StringMap(); - this.Gestures = new StringMap(); + this.Animations = null; + this.Gestures = null; + this.Classic = false; } - void Load(KeyValues kv) + void Load(KeyValues kv, bool newStuff = false) { + SF2BossProfileAnimationSectionNameData animData; + this.Animations = null; if (kv.JumpToKey("animations")) { + this.Gestures = new StringMap(); char animType[64]; - SF2BossProfileAnimationSectionNameData animData; SF2BossProfileAttackGestureData gestureData; if (kv.GotoFirstSubKey()) // Find the animation name { + if (this.Animations == null) + { + this.Animations = new StringMap(); + } do { kv.GetSectionName(animType, sizeof(animType)); @@ -1275,9 +1730,15 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima if (kv.GotoFirstSubKey()) { + if (this.Gestures == null) + { + this.Gestures = new StringMap(); + } do { - gestureData.Init(); + char animType2[64]; + kv.GetSectionName(animType2, sizeof(animType2)); + gestureData.Init(animType2); gestureData.Load(kv); gestureDataArray.PushArray(gestureData); } @@ -1289,43 +1750,20 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima } ArrayList animDataArray = new ArrayList(sizeof(animData)); - bool doResize = false; - if (strcmp(animType, "shoot") == 0) - { - doResize = true; - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - SF2BossProfileAnimationSectionNameData tempData; - animDataArray.PushArray(tempData, sizeof(tempData)); - } - } - if (kv.GotoFirstSubKey()) // Find the indexes { do { char animType2[64]; kv.GetSectionName(animType2, sizeof(animType2)); - animData.Init(animType); + animData.Init(animType, animType2); animData.Load(kv); - if (!doResize) - { - animDataArray.PushArray(animData); - } - else - { - int index = StringToInt(animType2); - if (index > -1) - { - animDataArray.SetArray(index - 1, animData, sizeof(animData)); - } - } + animDataArray.PushArray(animData); } while (kv.GotoNextKey()); kv.GoBack(); } - this.Animations.SetValue(animType, animDataArray); } while (kv.GotoNextKey()); @@ -1335,58 +1773,111 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima kv.GoBack(); } + else + { + if (!newStuff) + { + return; + } + for (int i = 0; i < SF2BossAnimation_MaxAnimations; i++) + { + char formatter[90], section[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s", g_SlenderAnimationsList[i]); + if (kv.GetDataType(formatter) == KvData_None) + { + continue; + } + if (this.Animations == null) + { + this.Animations = new StringMap(); + } + ArrayList animDataArray = new ArrayList(sizeof(animData)); + strcopy(section, sizeof(section), g_SlenderAnimationsList[i]); + animData.Init(section); + animData.Load(kv, false); + animDataArray.PushArray(animData); + this.Animations.SetValue(section, animDataArray); + this.Classic = true; + } + } } void Destroy() { - StringMapSnapshot snapshot = this.Animations.Snapshot(); - StringMapSnapshot gestureSnapshot = this.Gestures.Snapshot(); - char animType[64]; ArrayList animDataArray; SF2BossProfileAnimationSectionNameData animData; SF2BossProfileAttackGestureData gestureData; - - for (int i = 0; i < snapshot.Length; i++) + if (this.Animations != null) { - snapshot.GetKey(i, animType, sizeof(animType)); - this.Animations.GetValue(animType, animDataArray); - - for (int j = 0; j < animDataArray.Length; j++) + StringMapSnapshot snapshot = this.Animations.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) { - animDataArray.GetArray(j, animData, sizeof(animData)); - animData.Destroy(); + snapshot.GetKey(i, animType, sizeof(animType)); + this.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } } + delete snapshot; + delete this.Animations; } - for (int i = 0; i < gestureSnapshot.Length; i++) + if (this.Gestures != null) { - gestureSnapshot.GetKey(i, animType, sizeof(animType)); - this.Gestures.GetValue(animType, animDataArray); - - for (int j = 0; j < animDataArray.Length; j++) + StringMapSnapshot gestureSnapshot = this.Gestures.Snapshot(); + for (int i = 0; i < gestureSnapshot.Length; i++) { - animDataArray.GetArray(j, gestureData, sizeof(gestureData)); - gestureData.Destroy(); - } - } + gestureSnapshot.GetKey(i, animType, sizeof(animType)); + this.Gestures.GetValue(animType, animDataArray); - if (animDataArray != null) + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, gestureData, sizeof(gestureData)); + gestureData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + } + delete gestureSnapshot; + delete this.Gestures; + } + } + + bool HasAnimationSection(const char[] animType) + { + if (this.Animations == null) { - delete animDataArray; + return false; } + ArrayList tempArray; - delete snapshot; - delete this.Animations; - delete this.Gestures; + return this.Animations.GetValue(animType, tempArray); } - bool GetAnimation(const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &duration = 0.0, float &cycle = 0.0, float &footstepinterval = 0.0, int &index = 0, int preDefinedIndex = -1) + bool GetAnimation(const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &duration = 0.0, float &cycle = 0.0, float &footstepinterval = 0.0, int &index = 0, int preDefinedIndex = -1, const char[] preDefinedName = "", bool &overrideLoop = false, bool &loop = false, int findIndex = -1) { + if (this.Animations == null) + { + return false; + } ArrayList animDataArray; if (this.Animations.GetValue(animType, animDataArray)) { + if (findIndex >= 0) + { + preDefinedIndex = animDataArray.FindValue(findIndex); + } int randomAnimation; if (preDefinedIndex <= -1) { @@ -1400,14 +1891,42 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima } randomAnimation = preDefinedIndex; } + + if (animDataArray == null || animDataArray.Length <= 0) + { + return false; + } + SF2BossProfileAnimationSectionNameData animData; - animDataArray.GetArray(randomAnimation, animData, sizeof(animData)); + if (!this.Classic && preDefinedName[0] != '\0') + { + bool found = false; + for (int i = 0; i < animDataArray.Length; i++) + { + animDataArray.GetArray(i, animData, sizeof(animData)); + if (strcmp(animData.SubSectionName, preDefinedName) == 0) + { + found = true; + break; + } + } + if (!found) + { + return false; + } + } + else + { + animDataArray.GetArray(randomAnimation, animData, sizeof(animData)); + } animData.GetSequence(difficulty, buffer, bufferLen); rate = animData.Playbackrate[difficulty]; duration = animData.Duration[difficulty]; cycle = animData.Cycle[difficulty]; footstepinterval = animData.FootstepInterval[difficulty]; + overrideLoop = animData.OverrideLoop[difficulty]; + loop = animData.Loop[difficulty]; index = randomAnimation; return true; @@ -1416,14 +1935,38 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima return false; } - bool GetGesture(int definedIndex, const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &cycle = 0.0) + bool GetGesture(int definedIndex, const char[] definedName = "", const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &cycle = 0.0) { + if (this.Gestures == null) + { + return false; + } ArrayList gestureDataArray; if (this.Gestures.GetValue(animType, gestureDataArray)) { SF2BossProfileAttackGestureData gestureData; - gestureDataArray.GetArray(definedIndex, gestureData, sizeof(gestureData)); + if (definedName[0] != '\0') + { + bool found = false; + for (int i = 0; i < gestureDataArray.Length; i++) + { + gestureDataArray.GetArray(i, gestureData, sizeof(gestureData)); + if (strcmp(gestureData.SectionName, definedName) == 0) + { + found = true; + break; + } + } + if (!found) + { + return false; + } + } + else + { + gestureDataArray.GetArray(definedIndex, gestureData, sizeof(gestureData)); + } gestureData.GetGesture(difficulty, buffer, bufferLen); rate = gestureData.Playbackrate[difficulty]; @@ -1436,6 +1979,179 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima } } +enum struct SF2BossProfileCopiesInfo +{ + bool Enabled[Difficulty_Max]; + int MinCopies[Difficulty_Max]; + int MaxCopies[Difficulty_Max]; + float TeleportDistanceSpacing[Difficulty_Max]; + bool Fakes[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.MinCopies[difficulty] = 0; + this.MaxCopies[difficulty] = 1; + this.TeleportDistanceSpacing[difficulty] = 800.0; + this.Fakes[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + if (!kv.JumpToKey("copies")) + { + GetProfileDifficultyBoolValues(kv, "copy", this.Enabled, this.Enabled); + GetProfileDifficultyNumValues(kv, "copy_max", this.MaxCopies, this.MaxCopies); + GetProfileDifficultyFloatValues(kv, "copy_teleport_dist_from_others", this.TeleportDistanceSpacing, this.TeleportDistanceSpacing); + GetProfileDifficultyBoolValues(kv, "fake_copies", this.Fakes, this.Fakes); + } + else + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyNumValues(kv, "min", this.MinCopies, this.MinCopies); + GetProfileDifficultyNumValues(kv, "max", this.MaxCopies, this.MaxCopies); + GetProfileDifficultyFloatValues(kv, "teleport_spacing_between", this.TeleportDistanceSpacing, this.TeleportDistanceSpacing); + GetProfileDifficultyBoolValues(kv, "fakes", this.Fakes, this.Fakes); + kv.GoBack(); + } + } +} + +enum struct SF2BossProfilePublicDeathCamData +{ + bool Enabled; + bool Blackout; + SF2BossProfileSoundInfo ExecutionSounds; + char Attachment[64]; + char TargetAttachment[64]; + float DownwardOffset; + float BackwardOffset; + + void Init() + { + this.Enabled = false; + this.Blackout = false; + this.ExecutionSounds.Init(); + this.Attachment[0] = '\0'; + this.TargetAttachment[0] = '\0'; + this.DownwardOffset = 0.0; + this.BackwardOffset = 0.0; + } + + void Load(KeyValues kv, bool checkFile) + { + this.Enabled = true; + this.Blackout = kv.GetNum("blackout", this.Blackout) != 0; + if (kv.JumpToKey("sounds")) + { + if (kv.JumpToKey("execution")) + { + this.ExecutionSounds.Load(kv, checkFile); + kv.GoBack(); + } + kv.GoBack(); + } + kv.GetString("attachment", this.Attachment, sizeof(this.Attachment), this.Attachment); + kv.GetString("target_attachment", this.TargetAttachment, sizeof(this.TargetAttachment), this.TargetAttachment); + if (kv.JumpToKey("offset")) + { + this.DownwardOffset = kv.GetFloat("downward", this.DownwardOffset); + this.BackwardOffset = kv.GetFloat("backward", this.BackwardOffset); + kv.GoBack(); + } + } + + void PostLoad() + { + this.ExecutionSounds.PostLoad(); + } + + void Destroy() + { + this.ExecutionSounds.Destroy(); + } +} + +enum struct SF2BossProfileEyeData +{ + int Type; + float OffsetPos[3]; + float OffsetAng[3]; + char Bone[128]; + + void Init() + { + this.Type = 0; + for (int i = 0; i < 3; i++) + { + this.OffsetPos[i] = 0.0; + this.OffsetAng[i] = 0.0; + } + + this.OffsetPos[2] = 45.0; + this.Bone[0] = '\0'; + } + + void Load(KeyValues kv) + { + char type[64]; + kv.GetString("mode", type, sizeof(type), "default"); + if (strcmp(type, "bone") == 0) + { + this.Type = 1; + kv.GetString("bone", this.Bone, sizeof(this.Bone), this.Bone); + this.OffsetPos[2] = 0.0; + } + kv.GetVector("offset_pos", this.OffsetPos, this.OffsetPos); + kv.GetVector("offset_ang", this.OffsetAng, this.OffsetAng); + } +} + +enum struct SF2BossProfileDescriptionData +{ + bool Hidden; + char Type[32]; + char Information[128]; + + void Init() + { + this.Hidden = false; + this.Type[0] = '\0'; + this.Information = "No description provided."; + } + + void Load(KeyValues kv) + { + this.Hidden = kv.GetNum("hidden", this.Hidden) != 0; + kv.GetString("type", this.Type, sizeof(this.Type), this.Type); + kv.GetString("description", this.Information, sizeof(this.Information), this.Information); + } +} + +enum struct SF2BossProfileSlaughterRunData +{ + bool CustomMinimumSpeed[Difficulty_Max]; + float SpawnTime[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.CustomMinimumSpeed[i] = false; + this.SpawnTime[i] = -1.0; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyBoolValues(kv, "custom_minimum_speed", this.CustomMinimumSpeed, this.CustomMinimumSpeed); + GetProfileDifficultyFloatValues(kv, "spawn_time", this.SpawnTime, this.SpawnTime); + } +} + enum struct SF2BossProfileData { ArrayList Models; @@ -1458,6 +2174,8 @@ enum struct SF2BossProfileData int RenderFX; int RenderMode; + SF2BossProfileDescriptionData Description; + char WeaponString[PLATFORM_MAX_PATH]; int WeaponInt; @@ -1480,19 +2198,6 @@ enum struct SF2BossProfileData float SoundMusicLoop[Difficulty_Max]; - bool EnableSpawnParticles; - char SpawnParticle[PLATFORM_MAX_PATH]; - char SpawnParticleSound[PLATFORM_MAX_PATH]; - float SpawnParticleSoundVolume; - int SpawnParticleSoundPitch; - float SpawnParticleOrigin[3]; - - bool EnableDespawnParticles; - char DespawnParticle[PLATFORM_MAX_PATH]; - char DespawnParticleSound[PLATFORM_MAX_PATH]; - float DespawnParticleSoundVolume; - int DespawnParticleSoundPitch; - bool DeathCam; bool DeathCamScareSound; bool PublicDeathCam; @@ -1508,10 +2213,9 @@ enum struct SF2BossProfileData float DeathCamTime; float DeathCamPos[3]; - int Copies[Difficulty_Max]; + SF2BossProfilePublicDeathCamData DeathCamData; float RunSpeed[Difficulty_Max]; - float MaxRunSpeed[Difficulty_Max]; float Acceleration[Difficulty_Max]; float IdleLifeTime[Difficulty_Max]; @@ -1544,7 +2248,7 @@ enum struct SF2BossProfileData char ScareReactionCustom[PLATFORM_MAX_PATH]; bool ScareReplenishSprint; - int ScareReplenishSprintAmount; + float ScareReplenishSprintAmount; float StaticRadius[Difficulty_Max]; float StaticRate[Difficulty_Max]; @@ -1564,7 +2268,6 @@ enum struct SF2BossProfileData float TeleportPersistencyPeriod[Difficulty_Max]; bool TeleportIgnoreChases; bool TeleportIgnoreVis; - float TeleportCopyDistance; float JumpscareDistance[Difficulty_Max]; float JumpscareDuration[Difficulty_Max]; @@ -1601,14 +2304,15 @@ enum struct SF2BossProfileData ArrayList ProxyDeathAnimations; int ProxyDeathAnimFrames[10]; bool ProxyZombies; + bool ProxyRobots; bool ProxyDifficultyModels; ArrayList ProxyModels[10]; ArrayList ProxyModelsHard[10]; ArrayList ProxyModelsInsane[10]; ArrayList ProxyModelsNightmare[10]; ArrayList ProxyModelsApollyon[10]; - - bool FakeCopies; + bool ProxyOverrideMaxSpeed; + float ProxyMaxSpeed[Difficulty_Max]; bool DrainCredits; int DrainCreditAmount[Difficulty_Max]; @@ -1649,6 +2353,12 @@ enum struct SF2BossProfileData int EngineSoundLevel; float EngineSoundVolume; + bool IsPvEBoss; + ArrayList PvESpawnMessagesArray; + char PvESpawnMessagePrefix[PLATFORM_MAX_PATH]; + bool DisplayPvEHealth; + float PvETeleportEndTimer; + // Sounds SF2BossProfileSoundInfo LocalDeathCamSounds; SF2BossProfileSoundInfo ClientDeathCamSounds; @@ -1687,17 +2397,33 @@ enum struct SF2BossProfileData ArrayList CompanionsArray; char CompanionSpawnType[64]; + SF2BossProfileCopiesInfo CopiesInfo; + SF2BossProfileAttributesInfo AttributesInfo; SF2BossProfileMasterAnimationsData AnimationData; ArrayList EffectsArray; + StringMap SpawnEffects; + StringMap DespawnEffects; + bool HideDespawnEffectsOnDeath; + + SF2BossProfileEyeData EyeData; + + SF2BossProfileSlaughterRunData SlaughterRunData; + + bool EnableRandomSelection; + bool AdminOnly; + bool EnableRandomSelectionBoxing; + bool EnableRandomSelectionRenevant; + bool EnableRandomSelectionRenevantAdmin; + void Init() { this.Models = null; this.Names = null; - this.Type = SF2BossType_Unknown; + this.Type = SF2BossType_Chaser; this.ModelScale = 1.0; this.SkinMax = 0; this.SkinDifficultiesOn = false; @@ -1711,6 +2437,8 @@ enum struct SF2BossProfileData this.RenderFX = view_as(RENDERFX_NONE); this.RenderMode = view_as(RENDER_NORMAL); + this.Description.Init(); + this.WeaponString[0] = '\0'; this.WeaponInt = 0; @@ -1728,18 +2456,6 @@ enum struct SF2BossProfileData this.FestiveLightDistance = 0.0; this.FestiveLightRadius = 0.0; - this.EnableSpawnParticles = false; - this.SpawnParticle[0] = '\0'; - this.SpawnParticleSound[0] = '\0'; - this.SpawnParticleSoundVolume = 1.0; - this.SpawnParticleSoundPitch = 100; - - this.EnableDespawnParticles = false; - this.DespawnParticle[0] = '\0'; - this.DespawnParticleSound[0] = '\0'; - this.DespawnParticleSoundVolume = 1.0; - this.DespawnParticleSoundPitch = 100; - this.BlinkLookRate = 1.0; this.BlinkStaticRate = 1.0; @@ -1757,6 +2473,8 @@ enum struct SF2BossProfileData this.DeathCamOverlayStartTime = 0.0; this.DeathCamTime = 0.0; + this.DeathCamData.Init(); + this.FOV = 90.0; this.TurnRate = 250.0; @@ -1781,14 +2499,13 @@ enum struct SF2BossProfileData this.ScareReactionCustom[0] = '\0'; this.ScareReplenishSprint = false; - this.ScareReplenishSprintAmount = 0; + this.ScareReplenishSprintAmount = 0.0; this.StaticScareAmount = 0.0; - this.TeleportType = 0; + this.TeleportType = 2; this.TeleportIgnoreChases = false; this.TeleportIgnoreVis = false; - this.TeleportCopyDistance = 800.0; this.JumpscareOnScare = false; this.JumpscareNoSight = false; @@ -1814,9 +2531,9 @@ enum struct SF2BossProfileData this.ProxySpawnEffectZOffset = 0.0; this.ProxyDeathAnimations = null; this.ProxyZombies = false; + this.ProxyRobots = false; this.ProxyDifficultyModels = false; - - this.FakeCopies = false; + this.ProxyOverrideMaxSpeed = false; this.DrainCredits = false; @@ -1851,20 +2568,28 @@ enum struct SF2BossProfileData this.OutroMusic = false; + this.IsPvEBoss = false; + this.PvESpawnMessagesArray = null; + this.PvESpawnMessagePrefix[0] = '\0'; + this.DisplayPvEHealth = true; + this.PvETeleportEndTimer = 5.0; + this.EngineSound[0] = '\0'; this.EngineSoundLevel = 83; this.EngineSoundVolume = 0.8; this.LocalDeathCamSounds.Init(); this.ClientDeathCamSounds.Init(); + this.ClientDeathCamSounds.Channel = SNDCHAN_STATIC; + this.ClientDeathCamSounds.Level = SNDLEVEL_NONE; this.GlobalDeathCamSounds.Init(); this.ScareSounds.Init(); - this.ScareSounds.Channel = MUSIC_CHAN; + this.ScareSounds.Channel = SNDCHAN_STATIC; this.ScareSounds.Level = SNDLEVEL_NONE; this.SightSounds.Init(); this.SightSounds.CooldownMin = 8.0; this.SightSounds.CooldownMax = 14.0; - this.SightSounds.Channel = MUSIC_CHAN; + this.SightSounds.Channel = SNDCHAN_STATIC; this.SightSounds.Level = SNDLEVEL_NONE; this.IntroSounds.Init(); this.IntroSounds.Channel = SNDCHAN_STATIC; @@ -1909,70 +2634,83 @@ enum struct SF2BossProfileData this.CompanionsArray = null; this.CompanionSpawnType = "on_spawn on_difficulty_change"; + this.CopiesInfo.Init(); this.AttributesInfo.Init(); this.AnimationData.Init(); // Save this here whenever I do statue boss animations this.EffectsArray = null; - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - this.Skin[difficulty] = difficulty > 0 ? this.Skin[difficulty - 1] : 0; - this.Body[difficulty] = difficulty > 0 ? this.Body[difficulty - 1] : 0; - - this.SoundMusicLoop[difficulty] = difficulty > 0 ? this.SoundMusicLoop[difficulty - 1] : 0.0; - - this.Copies[difficulty] = difficulty > 0 ? this.Copies[difficulty - 1] : 1; + this.SpawnEffects = null; + this.DespawnEffects = null; + this.HideDespawnEffectsOnDeath = false; - this.RunSpeed[difficulty] = difficulty > 0 ? this.RunSpeed[difficulty - 1] : 150.0; - this.MaxRunSpeed[difficulty] = difficulty > 0 ? this.MaxRunSpeed[difficulty - 1] : 150.0; - this.Acceleration[difficulty] = difficulty > 0 ? this.Acceleration[difficulty - 1] : 4000.0; + this.EyeData.Init(); - this.IdleLifeTime[difficulty] = difficulty > 0 ? this.IdleLifeTime[difficulty - 1] : 10.0; + this.SlaughterRunData.Init(); - this.SearchRange[difficulty] = difficulty > 0 ? this.SearchRange[difficulty - 1] : 1024.0; - this.SearchSoundRange[difficulty] = difficulty > 0 ? this.SearchSoundRange[difficulty - 1] : 1024.0; - this.TauntAlertRange[difficulty] = difficulty > 0 ? this.TauntAlertRange[difficulty - 1] : 512.0; + this.EnableRandomSelection = false; + this.AdminOnly = false; + this.EnableRandomSelectionBoxing = false; + this.EnableRandomSelectionRenevant = false; + this.EnableRandomSelectionRenevantAdmin = false; - this.InstantKillCooldown[difficulty] = difficulty > 0 ? this.InstantKillCooldown[difficulty - 1] : 0.0; - - this.StaticRadius[difficulty] = difficulty > 0 ? this.StaticRadius[difficulty - 1] : 0.0; - this.StaticRate[difficulty] = difficulty > 0 ? this.StaticRate[difficulty - 1] : 0.0; - this.StaticRateDecay[difficulty] = difficulty > 0 ? this.StaticRateDecay[difficulty - 1] : 0.0; - this.StaticGraceTime[difficulty] = difficulty > 0 ? this.StaticGraceTime[difficulty - 1] : 1.0; - - this.TeleportAllowed[difficulty] = difficulty > 0 ? this.TeleportAllowed[difficulty - 1] : true; - this.TeleportRangeMin[difficulty] = difficulty > 0 ? this.TeleportRangeMin[difficulty - 1] : 325.0; - this.TeleportRangeMax[difficulty] = difficulty > 0 ? this.TeleportRangeMax[difficulty - 1] : 1024.0; - this.TeleportTimeMin[difficulty] = difficulty > 0 ? this.TeleportTimeMin[difficulty - 1] : 5.0; - this.TeleportTimeMax[difficulty] = difficulty > 0 ? this.TeleportTimeMax[difficulty - 1] : 9.0; - this.TeleportRestPeriod[difficulty] = difficulty > 0 ? this.TeleportRestPeriod[difficulty - 1] : 15.0; - this.TeleportStressMin[difficulty] = difficulty > 0 ? this.TeleportStressMin[difficulty - 1] : 0.2; - this.TeleportStressMax[difficulty] = difficulty > 0 ? this.TeleportStressMax[difficulty - 1] : 0.9; - this.TeleportPersistencyPeriod[difficulty] = difficulty > 0 ? this.TeleportPersistencyPeriod[difficulty - 1] : 13.0; - - this.JumpscareDistance[difficulty] = difficulty > 0 ? this.JumpscareDistance[difficulty - 1] : 0.0; - this.JumpscareDuration[difficulty] = difficulty > 0 ? this.JumpscareDuration[difficulty - 1] : 0.0; - this.JumpscareCooldown[difficulty] = difficulty > 0 ? this.JumpscareCooldown[difficulty - 1] : 0.0; - - this.ProxyDamageVsEnemy[difficulty] = difficulty > 0 ? this.ProxyDamageVsEnemy[difficulty - 1] : 1.0; - this.ProxyDamageVsBackstab[difficulty] = difficulty > 0 ? this.ProxyDamageVsBackstab[difficulty - 1] : 0.25; - this.ProxyDamageVsSelf[difficulty] = difficulty > 0 ? this.ProxyDamageVsSelf[difficulty - 1] : 1.0; - this.ProxyControlGainHitEnemy[difficulty] = difficulty > 0 ? this.ProxyControlGainHitEnemy[difficulty - 1] : 0; - this.ProxyControlGainHitByEnemy[difficulty] = difficulty > 0 ? this.ProxyControlGainHitByEnemy[difficulty - 1] : 0; - this.ProxyControlDrainRate[difficulty] = difficulty > 0 ? this.ProxyControlDrainRate[difficulty - 1] : 0.0; - this.MaxProxies[difficulty] = difficulty > 0 ? this.MaxProxies[difficulty - 1] : 0; - this.ProxySpawnChanceMin[difficulty] = difficulty > 0 ? this.ProxySpawnChanceMin[difficulty - 1] : 0.0; - this.ProxySpawnChaceMax[difficulty] = difficulty > 0 ? this.ProxySpawnChaceMax[difficulty - 1] : 0.0; - this.ProxySpawnChanceThreshold[difficulty] = difficulty > 0 ? this.ProxySpawnChanceThreshold[difficulty - 1] : 0.0; - this.ProxySpawnNumMin[difficulty] = difficulty > 0 ? this.ProxySpawnNumMin[difficulty - 1] : 0; - this.ProxySpawnNumMax[difficulty] = difficulty > 0 ? this.ProxySpawnNumMax[difficulty - 1] : 0; - this.ProxySpawnCooldownMin[difficulty] = difficulty > 0 ? this.ProxySpawnCooldownMin[difficulty - 1] : 0.0; - this.ProxySpawnCooldownMax[difficulty] = difficulty > 0 ? this.ProxySpawnCooldownMax[difficulty - 1] : 0.0; - this.ProxyTeleportRangeMin[difficulty] = difficulty > 0 ? this.ProxyTeleportRangeMin[difficulty - 1] : 500.0; - this.ProxyTeleportRangeMax[difficulty] = difficulty > 0 ? this.ProxyTeleportRangeMax[difficulty - 1] : 3200.0; - - this.DrainCreditAmount[difficulty] = difficulty > 0 ? this.DrainCreditAmount[difficulty - 1] : 50; + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Skin[difficulty] = 0; + this.Body[difficulty] = 0; + + this.SoundMusicLoop[difficulty] = 0.0; + + this.RunSpeed[difficulty] = 150.0; + this.Acceleration[difficulty] = 6000.0; + + this.IdleLifeTime[difficulty] = 10.0; + + this.SearchRange[difficulty] = 1024.0; + this.SearchSoundRange[difficulty] = 1024.0; + this.TauntAlertRange[difficulty] = 512.0; + + this.InstantKillCooldown[difficulty] = 0.0; + + this.StaticRadius[difficulty] = 150.0; + this.StaticRate[difficulty] = 0.65; + this.StaticRateDecay[difficulty] = 0.2; + this.StaticGraceTime[difficulty] = 1.0; + + this.TeleportAllowed[difficulty] = true; + this.TeleportRangeMin[difficulty] = 450.0; + this.TeleportRangeMax[difficulty] = 1500.0; + this.TeleportTimeMin[difficulty] = 5.0; + this.TeleportTimeMax[difficulty] = 9.0; + this.TeleportRestPeriod[difficulty] = 15.0; + this.TeleportStressMin[difficulty] = 0.2; + this.TeleportStressMax[difficulty] = 1.0; + this.TeleportPersistencyPeriod[difficulty] = 13.0; + + this.JumpscareDistance[difficulty] = 0.0; + this.JumpscareDuration[difficulty] = 0.0; + this.JumpscareCooldown[difficulty] = 0.0; + + this.ProxyDamageVsEnemy[difficulty] = 1.0; + this.ProxyDamageVsBackstab[difficulty] = 0.25; + this.ProxyDamageVsSelf[difficulty] = 1.0; + this.ProxyControlGainHitEnemy[difficulty] = 0; + this.ProxyControlGainHitByEnemy[difficulty] = 0; + this.ProxyControlDrainRate[difficulty] = 0.0; + this.MaxProxies[difficulty] = 0; + this.ProxySpawnChanceMin[difficulty] = 0.0; + this.ProxySpawnChaceMax[difficulty] = 0.0; + this.ProxySpawnChanceThreshold[difficulty] = 0.0; + this.ProxySpawnNumMin[difficulty] = 0; + this.ProxySpawnNumMax[difficulty] = 0; + this.ProxySpawnCooldownMin[difficulty] = 0.0; + this.ProxySpawnCooldownMax[difficulty] = 0.0; + this.ProxyTeleportRangeMin[difficulty] = 500.0; + this.ProxyTeleportRangeMax[difficulty] = 3200.0; + this.ProxyMaxSpeed[difficulty] = 230.0; + + this.DrainCreditAmount[difficulty] = 50; } for (int i = 0; i < 3; i++) @@ -1985,8 +2723,6 @@ enum struct SF2BossProfileData this.FestiveLightPos[i] = 0.0; this.FestiveLightAng[i] = 0.0; - this.SpawnParticleOrigin[i] = 0.0; - this.DeathCamPos[i] = 0.0; this.PushRagdollForce[i] = 0.0; @@ -1994,10 +2730,12 @@ enum struct SF2BossProfileData this.EyePosOffset[i] = 0.0; this.EyeAngOffset[i] = 0.0; } + this.EyePosOffset[2] = 45.0; } void PostLoad() { + this.DeathCamData.PostLoad(); this.ClientDeathCamSounds.PostLoad(); this.GlobalDeathCamSounds.PostLoad(); this.LocalDeathCamSounds.PostLoad(); @@ -2016,6 +2754,7 @@ enum struct SF2BossProfileData this.ProxyDeathSounds.PostLoad(); this.PlayerDeathCamOverlaySounds.PostLoad(); this.OutroMusics.PostLoad(); + if (this.CompanionsArray != null && this.CompanionsArray.Length <= 0) { delete this.CompanionsArray; @@ -2030,10 +2769,65 @@ enum struct SF2BossProfileData this.CompanionsArray.SetArray(i, companions, sizeof(companions)); } } + if (this.EffectsArray != null && this.EffectsArray.Length <= 0) { delete this.EffectsArray; } + + if (this.EffectsArray != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.EffectsArray.Length; i++) + { + this.EffectsArray.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.SpawnEffects != null) + { + StringMapSnapshot snapshot = this.SpawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.SpawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + delete snapshot; + } + + if (this.DespawnEffects != null) + { + StringMapSnapshot snapshot = this.DespawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.DespawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + delete snapshot; + } + for (int i = 0; i < 10; i++) { if (this.ProxyModels[i] != null && this.ProxyModels[i].Length <= 0) @@ -2057,26 +2851,56 @@ enum struct SF2BossProfileData delete this.ProxyModelsApollyon[i]; } } + if (this.FootstepEventSounds != null && this.FootstepEventSounds.Length <= 0) { delete this.FootstepEventSounds; } + if (this.FootstepEventIndexes != null && this.FootstepEventIndexes.Length <= 0) { delete this.FootstepEventIndexes; } + if (this.EventSounds != null && this.EventSounds.Length <= 0) { delete this.EventSounds; } + if (this.EventIndexes != null && this.EventIndexes.Length <= 0) { delete this.EventIndexes; } + + if (this.ProxyWeaponClassNames != null && this.ProxyWeaponClassNames.Length <= 0) + { + delete this.ProxyWeaponClassNames; + } + + if (this.ProxyWeaponStats != null && this.ProxyWeaponStats.Length <= 0) + { + delete this.ProxyWeaponStats; + } + + if (this.ProxyDeathAnimations != null && this.ProxyDeathAnimations.Length <= 0) + { + delete this.ProxyDeathAnimations; + } + + if (this.DeathMessagesArray != null && this.DeathMessagesArray.Length <= 0) + { + delete this.DeathMessagesArray; + } + + if (this.PvESpawnMessagesArray != null && this.PvESpawnMessagesArray.Length <= 0) + { + delete this.PvESpawnMessagesArray; + } } void Destroy() { + this.DeathCamData.Destroy(); // Sounds this.LocalDeathCamSounds.Destroy(); this.ClientDeathCamSounds.Destroy(); @@ -2097,6 +2921,7 @@ enum struct SF2BossProfileData this.PlayerDeathCamOverlaySounds.Destroy(); this.OutroMusics.Destroy(); this.AnimationData.Destroy(); + if (this.CompanionsArray != null) { SF2BossProfileCompanionsInfo companions; @@ -2108,6 +2933,7 @@ enum struct SF2BossProfileData } delete this.CompanionsArray; } + if (this.EffectsArray != null) { SF2BossProfileBaseEffectInfo effects; @@ -2119,30 +2945,88 @@ enum struct SF2BossProfileData } delete this.EffectsArray; } + + if (this.SpawnEffects != null) + { + StringMapSnapshot snapshot = this.SpawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.SpawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.Destroy(); + } + delete arrayEffects; + } + + delete snapshot; + delete this.SpawnEffects; + } + + if (this.DespawnEffects != null) + { + StringMapSnapshot snapshot = this.DespawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.DespawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.Destroy(); + } + delete arrayEffects; + } + + delete snapshot; + delete this.DespawnEffects; + } + if (this.Models != null) { delete this.Models; } + if (this.Names != null) { delete this.Names; } + if (this.DeathMessagesArray != null) { delete this.DeathMessagesArray; } + + if (this.PvESpawnMessagesArray != null) + { + delete this.PvESpawnMessagesArray; + } + if (this.ProxyWeaponClassNames != null) { delete this.ProxyWeaponClassNames; } + if (this.ProxyWeaponStats != null) { delete this.ProxyWeaponStats; } + if (this.ProxyDeathAnimations != null) { delete this.ProxyDeathAnimations; } + if (this.FootstepEventSounds != null) { SF2BossProfileSoundInfo soundInfo; @@ -2154,10 +3038,12 @@ enum struct SF2BossProfileData } delete this.FootstepEventSounds; } + if (this.FootstepEventIndexes != null) { delete this.FootstepEventIndexes; } + if (this.EventSounds != null) { SF2BossProfileSoundInfo soundInfo; @@ -2169,10 +3055,35 @@ enum struct SF2BossProfileData } delete this.EventSounds; } + if (this.EventIndexes != null) { delete this.EventIndexes; } + + for (int i = 0; i < 10; i++) + { + if (this.ProxyModels[i] != null) + { + delete this.ProxyModels[i]; + } + if (this.ProxyModelsHard[i] != null) + { + delete this.ProxyModelsHard[i]; + } + if (this.ProxyModelsInsane[i] != null) + { + delete this.ProxyModelsInsane[i]; + } + if (this.ProxyModelsNightmare[i] != null) + { + delete this.ProxyModelsNightmare[i]; + } + if (this.ProxyModelsApollyon[i] != null) + { + delete this.ProxyModelsApollyon[i]; + } + } } void SortSoundSections(KeyValues kv, const char[] sectionName, bool checkFile) @@ -2253,4 +3164,4 @@ enum struct SF2BossProfileData } #include -#include \ No newline at end of file +#include diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 6134d88d..77f7e686 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -1,4 +1,4 @@ -ConVar g_DefaultBossVisibilityStateConVar; +#include enum { @@ -9,7 +9,7 @@ enum SF2BossAttackType_ExplosiveDance = 3, SF2BossAttackType_LaserBeam = 4, SF2BossAttackType_Custom = 5, - SF2BossAttackType_Unused + SF2BossAttackType_Tongue = 6 }; enum @@ -23,7 +23,6 @@ enum SF2BossProjectileType_Arrow = 5, SF2BossProjectileType_Mangler = 6, SF2BossProjectileType_Baseball = 7, - SF2BossProjectileType_Unused, SF2BossProjectileType_Custom }; @@ -31,31 +30,47 @@ enum { SF2BossTrapType_Invalid = -1, SF2BossTrapType_BearTrap = 0, - SF2BossTrapType_Unused, SF2BossTrapType_Custom }; +enum +{ + SF2DamageType_Invalid = -1, + SF2DamageType_Jarate = 0, + SF2DamageType_Milk, + SF2DamageType_Gas, + SF2DamageType_Mark, + SF2DamageType_Ignite, + SF2DamageType_Stun, + SF2DamageType_Bleed, + SF2DamageType_Smite, + SF2DamageType_Random +} + enum struct SF2BossProfileAttackGestureData // This one covers the gestures { ArrayList Names; float Playbackrate[Difficulty_Max]; float Cycle[Difficulty_Max]; + char SectionName[64]; - void Init() + void Init(char optionalSectionName[64] = "") { - this.Names = new ArrayList(ByteCountToCells(64)); - this.Names.Resize(Difficulty_Max); + this.Names = null; + this.SectionName = optionalSectionName; for (int i = 0; i < Difficulty_Max; i++) { - this.Playbackrate[i] = this.Playbackrate[i] = i > 0 ? this.Playbackrate[i - 1] : 1.0; - this.Cycle[i] = this.Cycle[i] = i > 0 ? this.Cycle[i - 1] : 0.0; + this.Playbackrate[i] = 1.0; + this.Cycle[i] = 0.0; } } void Load(KeyValues kv) { char animNames[Difficulty_Max][64]; + this.Names = new ArrayList(ByteCountToCells(64)); + this.Names.Resize(Difficulty_Max); GetProfileDifficultyStringValues(kv, "gesture_name", animNames, sizeof(animNames[]), animNames); GetProfileDifficultyFloatValues(kv, "gesture_playbackrate", this.Playbackrate, this.Playbackrate); @@ -86,176 +101,760 @@ enum struct SF2BossProfileAttackGestureData // This one covers the gestures } } -enum struct SF2ChaserBossProfileAttackData +enum struct SF2ChaserBossProfileDamageEffectData // Can be any data { int Type; - - bool CanUseAgainstProps; - - float Damage[Difficulty_Max]; - float DamageVsProps; - float DamageForce[Difficulty_Max]; - int DamageType[Difficulty_Max]; - float DamageDelay[Difficulty_Max]; - - float Range[Difficulty_Max]; + bool Enabled[Difficulty_Max]; float Duration[Difficulty_Max]; - float Spread[Difficulty_Max]; - float BeginRange[Difficulty_Max]; - float BeginFOV[Difficulty_Max]; - float PunchVelocity[3]; + SF2BossProfileSoundInfo Sounds; + ArrayList Particles; - float Cooldown[Difficulty_Max]; - - bool Disappear[Difficulty_Max]; - int Repeat; - int MaxRepeats; - ArrayList RepeatTimers; - - bool IgnoreAlwaysLooking[Difficulty_Max]; + bool MarkSilent[Difficulty_Max]; - int WeaponInt; - char WeaponString[PLATFORM_MAX_PATH]; + float StunSlowdown[Difficulty_Max]; + ArrayList StunFlags; - bool LifeSteal[Difficulty_Max]; - float LifeStealDuration[Difficulty_Max]; + int SmiteSprite; + int SmiteSmokeSprite; + float SmiteDamage[Difficulty_Max]; + int SmiteDamageType[Difficulty_Max]; + int SmiteColor[4]; + bool SmiteMessage; + char SmiteHitSound[PLATFORM_MAX_PATH]; - float ProjectileDamage[Difficulty_Max]; - float ProjectileSpeed[Difficulty_Max]; - float ProjectileRadius[Difficulty_Max]; - float ProjectileDeviation[Difficulty_Max]; - int ProjectileCount[Difficulty_Max]; - bool CritProjectiles[Difficulty_Max]; - int ProjectileType; - float ProjectileOffset[3]; - char FireballTrail[PLATFORM_MAX_PATH]; - char RocketModel[PLATFORM_MAX_PATH]; + ArrayList Types; - float IceballSlowdownPercent[Difficulty_Max]; - float IceballSlowdownDuration[Difficulty_Max]; - char IceballTrail[PLATFORM_MAX_PATH]; + void Init() + { + this.Type = SF2DamageType_Invalid; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.Duration[i] = 8.0; - int BulletCount[Difficulty_Max]; - float BulletDamage[Difficulty_Max]; - float BulletSpread[Difficulty_Max]; - char BulletTrace[PLATFORM_MAX_PATH]; - float BulletOffset[3]; + this.MarkSilent[i] = false; - float LaserDamage[Difficulty_Max]; - float LaserSize; - int LaserColor[3]; - bool LaserAttachment; - float LaserDuration[Difficulty_Max]; - float LaserNoise; - float LaserOffset[3]; - char LaserAttachmentName[PLATFORM_MAX_PATH]; + this.StunSlowdown[i] = 0.5; - bool PullIn; + this.SmiteDamage[i] = 9001.0; + this.SmiteDamageType[i] = (1 << 20); + } + this.Sounds.Init(); + this.Particles = null; - bool AttackWhileRunning[Difficulty_Max]; - float RunSpeed[Difficulty_Max]; - float RunDuration[Difficulty_Max]; - float RunDelay[Difficulty_Max]; + this.StunFlags = null; - int UseOnDifficulty; - int BlockOnDifficulty; - float UseOnHealth; - float BlockOnHealth; + this.SmiteSprite = PrecacheModel("sprites/lgtning.vmt"); + this.SmiteSmokeSprite = PrecacheModel("sprites/steam1.vmt"); + for (int i = 0; i < 4; i++) + { + this.SmiteColor[i] = 255; + } + this.SmiteMessage = false; + this.SmiteHitSound = SOUND_THUNDER; - int ExplosiveDanceRadius[Difficulty_Max]; + this.Types = null; + } - bool Gestures; + bool SetType(const char[] section) + { + if (strcmp(section, "ignite", false) == 0) + { + this.Type = SF2DamageType_Ignite; + } + else if (strcmp(section, "gas", false) == 0) + { + this.Type = SF2DamageType_Gas; + } + else if (strcmp(section, "bleed", false) == 0) + { + this.Type = SF2DamageType_Bleed; + } + else if (strcmp(section, "mark", false) == 0) + { + this.Type = SF2DamageType_Mark; + } + else if (strcmp(section, "jarate", false) == 0) + { + this.Type = SF2DamageType_Jarate; + } + else if (strcmp(section, "milk", false) == 0) + { + this.Type = SF2DamageType_Milk; + } + else if (strcmp(section, "stun", false) == 0) + { + this.Type = SF2DamageType_Stun; + } + else if (strcmp(section, "smite", false) == 0) + { + this.Type = SF2DamageType_Smite; + } + else if (strcmp(section, "random", false) == 0) + { + this.Types = new ArrayList(); + this.Type = SF2DamageType_Random; + } - bool DeathCamLowHealth; + if (this.Type == SF2DamageType_Invalid) + { + return false; + } - bool DontInterruptChaseInitial[Difficulty_Max]; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } - bool CancelLos[Difficulty_Max]; - float CancelDistance[Difficulty_Max]; + return true; + } - void Init() + void Load(KeyValues kv, bool checkFile) { - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - this.Damage[difficulty] = 0.0; - this.DamageForce[difficulty] = 0.0; - this.DamageType[difficulty] = 0; - this.DamageDelay[difficulty] = 0.0; - - this.Range[difficulty] = 0.0; - this.Duration[difficulty] = 0.0; - this.Spread[difficulty] = 45.0; - this.BeginRange[difficulty] = this.Range[difficulty]; - this.BeginFOV[difficulty] = this.Spread[difficulty]; + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); - this.Cooldown[difficulty] = 0.0; + GetProfileDifficultyBoolValues(kv, "silent", this.MarkSilent, this.MarkSilent); - this.Disappear[difficulty] = false; + char flags[Difficulty_Max][256]; + GetProfileDifficultyFloatValues(kv, "slow_multiplier", this.StunSlowdown, this.StunSlowdown); + this.StunFlags = new ArrayList(ByteCountToCells(256)); + for (int i = 0; i < Difficulty_Max; i++) + { + flags[i] = "slow"; + } + GetProfileDifficultyStringValues(kv, "flags", flags, sizeof(flags[]), flags); + for (int i = 0; i < Difficulty_Max; i++) + { + this.StunFlags.PushString(flags[i]); + } - this.IgnoreAlwaysLooking[difficulty] = false; + GetProfileDifficultyFloatValues(kv, "damage", this.SmiteDamage, this.SmiteDamage); + GetProfileDifficultyNumValues(kv, "damagetype", this.SmiteDamageType, this.SmiteDamageType); - this.LifeSteal[difficulty] = false; - this.LifeStealDuration[difficulty] = 0.0; + GetProfileColorNoBacks(kv, "color", this.SmiteColor[0], this.SmiteColor[1], this.SmiteColor[2], this.SmiteColor[3], + this.SmiteColor[0], this.SmiteColor[1], this.SmiteColor[2], this.SmiteColor[3]); + this.SmiteMessage = kv.GetNum("message", this.SmiteMessage) != 0; + kv.GetString("hit_sound", this.SmiteHitSound, sizeof(this.SmiteHitSound), this.SmiteHitSound); - this.ProjectileDamage[difficulty] = 20.0; - this.ProjectileSpeed[difficulty] = 1100.0; - this.ProjectileRadius[difficulty] = 128.0; - this.ProjectileDeviation[difficulty] = 0.0; - this.ProjectileCount[difficulty] = 1; - this.CritProjectiles[difficulty] = false; + if (this.Types != null) + { + char type[256]; + kv.GetString("random_types", type, sizeof(type)); + char types[32][32]; + int nums = ExplodeString(type, " ", types, sizeof(types), sizeof(types[])); + for (int i = 0; i < nums; i++) + { + if (strcmp(types[i], "ignite", false) == 0) + { + this.Types.Push(SF2DamageType_Ignite); + } + else if (strcmp(types[i], "gas", false) == 0) + { + this.Types.Push(SF2DamageType_Gas); + } + else if (strcmp(types[i], "bleed", false) == 0) + { + this.Types.Push(SF2DamageType_Bleed); + } + else if (strcmp(types[i], "mark", false) == 0) + { + this.Types.Push(SF2DamageType_Mark); + } + else if (strcmp(types[i], "jarate", false) == 0) + { + this.Types.Push(SF2DamageType_Jarate); + } + else if (strcmp(types[i], "milk", false) == 0) + { + this.Types.Push(SF2DamageType_Milk); + } + else if (strcmp(types[i], "stun", false) == 0) + { + this.Types.Push(SF2DamageType_Stun); + } + else if (strcmp(types[i], "smite", false) == 0) + { + this.Types.Push(SF2DamageType_Smite); + } + } + } - this.IceballSlowdownPercent[difficulty] = 0.55; - this.IceballSlowdownDuration[difficulty] = 2.0; + if (kv.JumpToKey("sounds")) + { + this.Sounds.Load(kv, checkFile); + kv.GoBack(); + } - this.BulletCount[difficulty] = 4; - this.BulletDamage[difficulty] = 8.0; - this.BulletSpread[difficulty] = 0.1; + if (kv.JumpToKey("particles")) + { + this.Particles = new ArrayList(sizeof(SF2ParticleData)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2ParticleData particle; + particle.Init(); + particle.Load(kv); + this.Particles.PushArray(particle, sizeof(particle)); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + } - this.LaserDamage[difficulty] = 25.0; - this.LaserDuration[difficulty] = this.Duration[difficulty]; + void PostLoad() + { + this.Sounds.PostLoad(); + } - this.AttackWhileRunning[difficulty] = false; - this.RunDuration[difficulty] = 0.0; - this.RunDelay[difficulty] = 0.0; - this.RunSpeed[difficulty] = 0.0; + int GetStunType(int difficulty) + { + char flag[256]; + this.StunFlags.GetString(difficulty, flag, sizeof(flag)); + char flags[32][64]; + int nums = ExplodeString(flag, " ", flags, sizeof(flags), sizeof(flags[])); + int value = 0; - this.ExplosiveDanceRadius[difficulty] = 350; + for (int i = 0; i < nums; i++) + { + if (strcmp(flags[i], "slow", false) == 0) + { + value |= (1 << 0); + } + else if (strcmp(flags[i], "stuck", false) == 0) + { + value |= (1 << 1); + } + else if (strcmp(flags[i], "cheer_fx", false) == 0) + { + value |= (1 << 3); + } + else if (strcmp(flags[i], "no_fx", false) == 0) + { + value |= (1 << 5); + } + else if (strcmp(flags[i], "thirdperson", false) == 0) + { + value |= (1 << 6); + } + else if (strcmp(flags[i], "ghost_fx", false) == 0) + { + value |= (1 << 7); + } + else if (strcmp(flags[i], "loser", false) == 0) + { + value |= (1 << 0) | (1 << 5) | (1 << 6); + } + else if (strcmp(flags[i], "boo", false) == 0) + { + value |= (1 << 7) | (1 << 6); + } + } - this.DontInterruptChaseInitial[difficulty] = false; + return value; + } - this.CancelLos[difficulty] = false; - this.CancelDistance[difficulty] = -1.0; + void Destroy() + { + if (this.Particles != null) + { + delete this.Particles; } - for (int i = 0; i < 3; i++) + if (this.StunFlags != null) { - this.PunchVelocity[i] = 0.0; + delete this.StunFlags; + } + if (this.Types != null) + { + delete this.Types; + } + this.Sounds.Destroy(); + } - this.ProjectileOffset[i] = 0.0; + void Apply(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = view_as(-1)) + { + if (!this.Enabled[difficulty]) + { + return; + } + this.Sounds.EmitSound(_, chaser.index); + if (this.Particles != null) + { + SF2ParticleData particle; + this.Particles.GetArray(GetRandomInt(0, this.Particles.Length - 1), particle, sizeof(particle)); + particle.Apply(player, chaser); + } + int type = this.Type; + if (type == SF2DamageType_Random) + { + type = this.Types.Get(GetRandomInt(0, this.Types.Length - 1)); + } - this.BulletOffset[i] = 0.0; + switch (type) + { + case SF2DamageType_Ignite: + { + TF2_IgnitePlayer(player.index, player.index, this.Duration[difficulty]); + } + case SF2DamageType_Gas: + { + TF2_AddCondition(player.index, TFCond_Gas, this.Duration[difficulty], player.index); + } + case SF2DamageType_Mark: + { + TF2_AddCondition(player.index, this.MarkSilent[difficulty] ? TFCond_MarkedForDeathSilent : TFCond_MarkedForDeath, this.Duration[difficulty], player.index); + } + case SF2DamageType_Bleed: + { + if (!TF2_IsPlayerInCondition(player.index, TFCond_Bleeding)) + { + TF2_MakeBleed(player.index, player.index, this.Duration[difficulty]); + } + } + case SF2DamageType_Milk: + { + TF2_AddCondition(player.index, TFCond_Milked, this.Duration[difficulty], player.index); + } + case SF2DamageType_Jarate: + { + TF2_AddCondition(player.index, TFCond_Jarated, this.Duration[difficulty], player.index); + } + case SF2DamageType_Stun: + { + TF2_StunPlayer(player.index, this.Duration[difficulty], this.StunSlowdown[difficulty], this.GetStunType(difficulty)); + } + case SF2DamageType_Smite: + { + float targetPos[3]; + player.GetAbsOrigin(targetPos); + targetPos[2] -= 26.0; - this.LaserColor[i] = 0; - this.LaserOffset[i] = 0.0; - } - this.Type = SF2BossAttackType_Melee; + int randomX = GetRandomInt(-500, 500); + int randomY = GetRandomInt(-500, 500); - this.CanUseAgainstProps = false; + float startPos[3]; + startPos[0] = targetPos[0] + randomX; + startPos[1] = targetPos[1] + randomY; + startPos[2] = targetPos[2] + 800.0; - this.DamageVsProps = this.Damage[1]; + float origin[3]; + TE_SetupBeamPoints(startPos, targetPos, this.SmiteSprite, 0, 0, 0, 0.2, 20.0, 10.0, 0, 1.0, this.SmiteColor, 3); + TE_SendToAll(); - this.Repeat = 0; - this.MaxRepeats = 0; - this.RepeatTimers = null; + TE_SetupSparks(targetPos, origin, 5000, 1000); + TE_SendToAll(); - this.WeaponInt = 0; - this.WeaponString[0] = '\0'; + TE_SetupEnergySplash(targetPos, origin, false); + TE_SendToAll(); - this.ProjectileType = 0; - this.FireballTrail = FIREBALL_TRAIL; - this.IceballTrail = ICEBALL_TRAIL; - this.RocketModel = ROCKET_MODEL; + TE_SetupSmoke(targetPos, this.SmiteSmokeSprite, 5.0, 10); + TE_SendToAll(); - this.BulletTrace = "bullet_tracer02_blue"; + EmitAmbientSound(this.SmiteHitSound, startPos, player.index, SNDLEVEL_SCREAMING); + SDKHooks_TakeDamage(player.index, chaser.index, chaser.index, this.SmiteDamage[difficulty], this.SmiteDamageType[difficulty]); + + int controller = chaser.Controller; + if (this.SmiteMessage && controller > -1 && GetClientTeam(player.index) == 2) + { + char playerName[32], bossName[SF2_MAX_NAME_LENGTH]; + GetClientName(player.index, playerName, sizeof(playerName)); + chaser.GetName(bossName, sizeof(bossName)); + CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Smote target", bossName, playerName); + } + } + } + } +} + +enum struct SF2ChaserBossProfileShockwaveData +{ + bool Enabled; + float Height[Difficulty_Max]; + float Radius[Difficulty_Max]; + float Force[Difficulty_Max]; + float BatteryDrainPercent[Difficulty_Max]; + float StaminaDrainPercent[Difficulty_Max]; + + ArrayList Effects; + ArrayList DamageEffects; + + void Init() + { + this.Enabled = false; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Height[i] = 80.0; + this.Radius[i] = 200.0; + this.Force[i] = 600.0; + this.BatteryDrainPercent[i] = 0.0; + this.StaminaDrainPercent[i] = 0.0; + } + + this.Effects = null; + this.DamageEffects = null; + } + + void Load(KeyValues kv, bool checkFile) + { + this.Enabled = true; + GetProfileDifficultyFloatValues(kv, "height", this.Height, this.Height); + GetProfileDifficultyFloatValues(kv, "radius", this.Radius, this.Radius); + GetProfileDifficultyFloatValues(kv, "force", this.Force, this.Force); + GetProfileDifficultyFloatValues(kv, "battery_drain", this.BatteryDrainPercent, this.BatteryDrainPercent); + GetProfileDifficultyFloatValues(kv, "stamina_drain", this.StaminaDrainPercent, this.StaminaDrainPercent); + + if (kv.JumpToKey("effects")) + { + this.Effects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.Effects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("apply_conditions")) + { + this.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + SF2ChaserBossProfileDamageEffectData damageData; + damageData.Init(); + kv.GetSectionName(section, sizeof(section)); + if (!damageData.SetType(section)) + { + continue; + } + damageData.Load(kv, checkFile); + this.DamageEffects.PushArray(damageData, sizeof(damageData)); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + } + + void PostLoad() + { + if (this.Effects != null && this.Effects.Length <= 0) + { + delete this.Effects; + } + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.PostLoad(); + } + } + + if (this.Effects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.Effects.Length; i++) + { + this.Effects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + } + + void Destroy() + { + if (this.Effects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.Effects.Length; i++) + { + this.Effects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.Effects; + } + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.Destroy(); + } + delete this.DamageEffects; + } + } + + void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = view_as(-1)) + { + if (this.DamageEffects == null) + { + return; + } + + if (!SF2_Player(player.index).IsValid) + { + return; + } + + for (int i = 0; i < this.DamageEffects.Length; i++) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i2 = 0; i2 < this.DamageEffects.Length; i2++) + { + this.DamageEffects.GetArray(i2, damageData, sizeof(damageData)); + damageData.Apply(player, difficulty, chaser); + } + } + } +} + +enum struct SF2ChaserBossProfileAttackData +{ + int Index; + char Name[64]; + + int Type; + + bool CanUseAgainstProps; + + float Damage[Difficulty_Max]; + float DamagePercent[Difficulty_Max]; + float DamageVsProps; + float DamageForce[Difficulty_Max]; + int DamageType[Difficulty_Max]; + float DamageDelay[Difficulty_Max]; + + float Range[Difficulty_Max]; + float Duration[Difficulty_Max]; + float Spread[Difficulty_Max]; + float BeginRange[Difficulty_Max]; + float BeginFOV[Difficulty_Max]; + float PunchVelocity[3]; + + float Cooldown[Difficulty_Max]; + + bool Disappear[Difficulty_Max]; + bool DisappearOnHit[Difficulty_Max]; + int Repeat; + int MaxRepeats; + ArrayList RepeatTimers; + + bool IgnoreAlwaysLooking[Difficulty_Max]; + + bool StartThroughWalls[Difficulty_Max]; + bool HitThroughWalls[Difficulty_Max]; + + int WeaponInt; + char WeaponString[PLATFORM_MAX_PATH]; + + bool LifeSteal[Difficulty_Max]; + float LifeStealDuration[Difficulty_Max]; + + float ProjectileDamage[Difficulty_Max]; + float ProjectileSpeed[Difficulty_Max]; + float ProjectileRadius[Difficulty_Max]; + float ProjectileDeviation[Difficulty_Max]; + int ProjectileCount[Difficulty_Max]; + bool CritProjectiles[Difficulty_Max]; + int ProjectileType; + float ProjectileOffset[3]; + char FireballTrail[PLATFORM_MAX_PATH]; + char RocketModel[PLATFORM_MAX_PATH]; + + float IceballSlowdownPercent[Difficulty_Max]; + float IceballSlowdownDuration[Difficulty_Max]; + char IceballTrail[PLATFORM_MAX_PATH]; + + int BulletCount[Difficulty_Max]; + float BulletDamage[Difficulty_Max]; + float BulletSpread[Difficulty_Max]; + char BulletTrace[PLATFORM_MAX_PATH]; + float BulletOffset[3]; + + float LaserDamage[Difficulty_Max]; + float LaserSize; + int LaserColor[3]; + bool LaserAttachment; + float LaserDuration[Difficulty_Max]; + float LaserNoise; + float LaserOffset[3]; + char LaserAttachmentName[PLATFORM_MAX_PATH]; + float LaserTicks[Difficulty_Max]; + + bool PullIn; + + float RunSpeed[Difficulty_Max]; + float RunDuration[Difficulty_Max]; + float RunDelay[Difficulty_Max]; + float RunAcceleration[Difficulty_Max]; + bool RunGroundSpeed[Difficulty_Max]; + + int UseOnDifficulty; + int BlockOnDifficulty; + float UseOnHealth; + float BlockOnHealth; + + float ExplosiveDanceRadius[Difficulty_Max]; + + bool Gestures; + + bool DeathCamLowHealth; + + bool DontInterruptChaseInitial[Difficulty_Max]; + + bool CancelLos[Difficulty_Max]; + float CancelDistance[Difficulty_Max]; + float MinCancelDistance[Difficulty_Max]; + + int EventNumber; + + char SubType[64]; + + SF2ChaserBossProfileShockwaveData Shockwave; + + ArrayList StartEffects; + ArrayList HitEffects; + ArrayList MissEffects; + ArrayList KillEffects; + ArrayList DamageEffects; + + bool ImmuneToDamage[Difficulty_Max]; + + ArrayList PostureWhitelist; + + SF2BossProfileSoundInfo TongueLaunchSound; + SF2BossProfileSoundInfo TongueHitSound; + SF2BossProfileSoundInfo TongueTiedSound; + float TongueSpeed[Difficulty_Max]; + float TonguePullScale[Difficulty_Max]; + bool TongueCanEscape[Difficulty_Max]; + char TongueMaterial[PLATFORM_MAX_PATH]; + char TongueAttachment[PLATFORM_MAX_PATH]; + float TongueOffset[3]; + + SF2BossProfileMasterAnimationsData Animations; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Damage[difficulty] = 50.0; + this.DamagePercent[difficulty] = 0.0; + this.DamageForce[difficulty] = 0.0; + this.DamageType[difficulty] = 128; + this.DamageDelay[difficulty] = 0.0; + + this.Range[difficulty] = 180.0; + this.Duration[difficulty] = 0.0; + this.Spread[difficulty] = 45.0; + this.BeginRange[difficulty] = 80.0; + this.BeginFOV[difficulty] = this.Spread[difficulty]; + + this.Cooldown[difficulty] = 0.0; + + this.Disappear[difficulty] = false; + this.DisappearOnHit[difficulty] = false; + + this.IgnoreAlwaysLooking[difficulty] = false; + + this.StartThroughWalls[difficulty] = false; + this.HitThroughWalls[difficulty] = false; + + this.LifeSteal[difficulty] = false; + this.LifeStealDuration[difficulty] = 0.0; + + this.ProjectileDamage[difficulty] = 20.0; + this.ProjectileSpeed[difficulty] = 1100.0; + this.ProjectileRadius[difficulty] = 128.0; + this.ProjectileDeviation[difficulty] = 0.0; + this.ProjectileCount[difficulty] = 1; + this.CritProjectiles[difficulty] = false; + + this.IceballSlowdownPercent[difficulty] = 0.55; + this.IceballSlowdownDuration[difficulty] = 2.0; + + this.BulletCount[difficulty] = 4; + this.BulletDamage[difficulty] = 8.0; + this.BulletSpread[difficulty] = 0.1; + + this.LaserDamage[difficulty] = 25.0; + this.LaserDuration[difficulty] = this.Duration[difficulty]; + this.LaserTicks[difficulty] = 0.1; + + this.RunDuration[difficulty] = 0.0; + this.RunDelay[difficulty] = 0.0; + this.RunSpeed[difficulty] = 0.0; + this.RunAcceleration[difficulty] = 4000.0; + this.RunGroundSpeed[difficulty] = false; + + this.ExplosiveDanceRadius[difficulty] = 350.0; + + this.DontInterruptChaseInitial[difficulty] = false; + + this.CancelLos[difficulty] = false; + this.CancelDistance[difficulty] = -1.0; + this.MinCancelDistance[difficulty] = -1.0; + + this.ImmuneToDamage[difficulty] = false; + + this.TongueSpeed[difficulty] = 900.0; + this.TonguePullScale[difficulty] = 6.0; + this.TongueCanEscape[difficulty] = true; + } + for (int i = 0; i < 3; i++) + { + this.PunchVelocity[i] = 0.0; + + this.ProjectileOffset[i] = 0.0; + + this.BulletOffset[i] = 0.0; + + this.LaserColor[i] = 0; + this.LaserOffset[i] = 0.0; + + this.TongueOffset[i] = 0.0; + } + this.Index = -1; + this.Name[0] = '\0'; + + this.Type = SF2BossAttackType_Melee; + + this.CanUseAgainstProps = false; + + this.DamageVsProps = this.Damage[1]; + + this.Repeat = 0; + this.MaxRepeats = 0; + this.RepeatTimers = null; + + this.WeaponInt = 0; + this.WeaponString[0] = '\0'; + + this.ProjectileType = 0; + this.FireballTrail = FIREBALL_TRAIL; + this.IceballTrail = ICEBALL_TRAIL; + this.RocketModel = ROCKET_MODEL; + + this.BulletTrace = "bullet_tracer02_blue"; this.LaserSize = 12.0; this.LaserAttachment = false; @@ -272,28 +871,1578 @@ enum struct SF2ChaserBossProfileAttackData this.Gestures = false; this.DeathCamLowHealth = false; + + this.EventNumber = -1; + + this.SubType[0] = '\0'; + + this.Shockwave.Init(); + + this.StartEffects = null; + this.HitEffects = null; + this.MissEffects = null; + this.KillEffects = null; + this.DamageEffects = null; + + this.PostureWhitelist = null; + + this.TongueLaunchSound.Init(); + this.TongueHitSound.Init(); + this.TongueTiedSound.Init(); + this.TongueMaterial[0] = '\0'; + this.TongueAttachment[0] = '\0'; + } + + void PostLoad() + { + this.Shockwave.PostLoad(); + if (this.StartEffects != null && this.StartEffects.Length <= 0) + { + delete this.StartEffects; + } + + if (this.HitEffects != null && this.HitEffects.Length <= 0) + { + delete this.HitEffects; + } + + if (this.MissEffects != null && this.MissEffects.Length <= 0) + { + delete this.MissEffects; + } + + if (this.KillEffects != null && this.KillEffects.Length <= 0) + { + delete this.KillEffects; + } + + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.PostLoad(); + } + } + + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.HitEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.HitEffects.Length; i++) + { + this.HitEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.MissEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.MissEffects.Length; i++) + { + this.MissEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.KillEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.KillEffects.Length; i++) + { + this.KillEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.PostureWhitelist != null && this.PostureWhitelist.Length == 0) + { + delete this.PostureWhitelist; + } + + this.TongueLaunchSound.PostLoad(); + this.TongueHitSound.PostLoad(); + this.TongueTiedSound.PostLoad(); + } + + void Destroy() + { + if (this.RepeatTimers != null) + { + delete this.RepeatTimers; + } + + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.StartEffects; + } + + if (this.HitEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.HitEffects.Length; i++) + { + this.HitEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.HitEffects; + } + + if (this.MissEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.MissEffects.Length; i++) + { + this.MissEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.MissEffects; + } + + if (this.KillEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.KillEffects.Length; i++) + { + this.KillEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.KillEffects; + } + + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.Destroy(); + } + delete this.DamageEffects; + } + this.Shockwave.Destroy(); + + if (this.PostureWhitelist != null) + { + delete this.PostureWhitelist; + } + + this.TongueLaunchSound.Destroy(); + this.TongueHitSound.Destroy(); + this.TongueTiedSound.Destroy(); + + this.Animations.Destroy(); + } + + bool IsSubType(const char[] subType) + { + return strcmp(subType, this.SubType) == 0; + } + + void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = view_as(-1)) + { + if (this.DamageEffects == null) + { + return; + } + + if (!SF2_Player(player.index).IsValid) + { + return; + } + + for (int i = 0; i < this.DamageEffects.Length; i++) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i2 = 0; i2 < this.DamageEffects.Length; i2++) + { + this.DamageEffects.GetArray(i2, damageData, sizeof(damageData)); + damageData.Apply(player, difficulty, chaser); + } + } + } + + bool CanBeUsedWithPosture(const char[] posture) + { + if (this.PostureWhitelist == null) + { + return true; + } + + return this.PostureWhitelist.FindString(posture) != -1; + } +} + +enum struct SF2ChaserBossProfileSoundSenseData +{ + int AddCount[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.AddCount[difficulty] = 2; + this.Cooldown[difficulty] = 0.0; + } + } +} + +enum struct SF2ChaserBossProfileSmellData +{ + bool Enabled[Difficulty_Max]; + float CooldownMin[Difficulty_Max]; + float CooldownMax[Difficulty_Max]; + float CooldownAfterStateMin[Difficulty_Max]; + float CooldownAfterStateMax[Difficulty_Max]; + int PlayerCount[Difficulty_Max]; + float PlayerRange[Difficulty_Max]; + float SmellRange[Difficulty_Max]; + bool ShouldChase[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.CooldownMin[difficulty] = 6.0; + this.CooldownMax[difficulty] = 12.0; + this.CooldownAfterStateMin[difficulty] = 16.0; + this.CooldownAfterStateMax[difficulty] = 24.0; + this.PlayerCount[difficulty] = 1; + this.PlayerRange[difficulty] = 1000.0; + this.SmellRange[difficulty] = 1500.0; + this.ShouldChase[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "cooldown_min", this.CooldownMin, this.CooldownMin); + GetProfileDifficultyFloatValues(kv, "cooldown_max", this.CooldownMax, this.CooldownMax); + GetProfileDifficultyFloatValues(kv, "cooldown_after_state_min", this.CooldownAfterStateMin, this.CooldownAfterStateMin); + GetProfileDifficultyFloatValues(kv, "cooldown_after_state_max", this.CooldownAfterStateMax, this.CooldownAfterStateMax); + GetProfileDifficultyNumValues(kv, "required_players", this.PlayerCount, this.PlayerCount); + GetProfileDifficultyFloatValues(kv, "required_player_range", this.PlayerRange, this.PlayerRange); + GetProfileDifficultyFloatValues(kv, "smelling_range", this.SmellRange, this.SmellRange); + GetProfileDifficultyBoolValues(kv, "should_chase_upon_smelled", this.ShouldChase, this.ShouldChase); + } +} + +enum struct SF2ChaserBossProfileStunData +{ + bool Enabled[Difficulty_Max]; + float Health[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + bool Disappear[Difficulty_Max]; + + bool FlashlightStun[Difficulty_Max]; + float FlashlightStunDamage[Difficulty_Max]; + + bool ChaseInitialOnEnd[Difficulty_Max]; + + bool KeyDrop; + char KeyModel[PLATFORM_MAX_PATH]; + char KeyTrigger[PLATFORM_MAX_PATH]; + + bool ItemDrop[Difficulty_Max]; + int ItemDropType[Difficulty_Max]; + + float AddHealthPerPlayer[Difficulty_Max]; + float AddHealthPerScout[Difficulty_Max]; + float AddHealthPerSoldier[Difficulty_Max]; + float AddHealthPerPyro[Difficulty_Max]; + float AddHealthPerDemoman[Difficulty_Max]; + float AddHealthPerHeavy[Difficulty_Max]; + float AddHealthPerEngineer[Difficulty_Max]; + float AddHealthPerMedic[Difficulty_Max]; + float AddHealthPerSniper[Difficulty_Max]; + float AddHealthPerSpy[Difficulty_Max]; + float AddHealthPerStun[Difficulty_Max]; + float AddSpeedPerStun[Difficulty_Max]; + float AddAccelerationPerStun[Difficulty_Max]; + + ArrayList StartEffects; + ArrayList EndEffects; + + void Init() + { + this.KeyDrop = false; + this.KeyModel = SF_KEYMODEL; + this.KeyTrigger[0] = '\0'; + + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.Health[difficulty] = 50.0; + this.Cooldown[difficulty] = 3.5; + this.Disappear[difficulty] = false; + + this.FlashlightStun[difficulty] = false; + this.FlashlightStunDamage[difficulty] = 0.0; + + this.ChaseInitialOnEnd[difficulty] = false; + + this.ItemDrop[difficulty] = false; + this.ItemDropType[difficulty] = 1; + + this.AddHealthPerPlayer[difficulty] = 0.0; + this.AddHealthPerScout[difficulty] = 0.0; + this.AddHealthPerSoldier[difficulty] = 0.0; + this.AddHealthPerPyro[difficulty] = 0.0; + this.AddHealthPerDemoman[difficulty] = 0.0; + this.AddHealthPerHeavy[difficulty] = 0.0; + this.AddHealthPerEngineer[difficulty] = 0.0; + this.AddHealthPerMedic[difficulty] = 0.0; + this.AddHealthPerSniper[difficulty] = 0.0; + this.AddHealthPerSpy[difficulty] = 0.0; + this.AddHealthPerStun[difficulty] = 0.0; + this.AddSpeedPerStun[difficulty] = 0.0; + this.AddAccelerationPerStun[difficulty] = 0.0; + } + + this.StartEffects = null; + this.EndEffects = null; + } + + void Load(KeyValues kv, bool checkFile) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "health", this.Health, this.Health); + GetProfileDifficultyFloatValues(kv, "cooldown", this.Cooldown, this.Cooldown); + GetProfileDifficultyBoolValues(kv, "disappear", this.Disappear, this.Disappear); + + if (kv.JumpToKey("flashlight_stun")) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.FlashlightStun[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.FlashlightStun, this.FlashlightStun); + GetProfileDifficultyFloatValues(kv, "damage", this.FlashlightStunDamage, this.FlashlightStunDamage); + kv.GoBack(); + } + + GetProfileDifficultyBoolValues(kv, "chase_initial_on_end", this.ChaseInitialOnEnd, this.ChaseInitialOnEnd); + + this.KeyDrop = kv.GetNum("key_drop", this.KeyDrop) != 0; + kv.GetString("key_model", this.KeyModel, sizeof(this.KeyModel), this.KeyModel); + PrecacheModel2(this.KeyModel, _, _, checkFile); + kv.GetString("key_trigger", this.KeyTrigger, sizeof(this.KeyTrigger), this.KeyTrigger); + + GetProfileDifficultyBoolValues(kv, "item_drop", this.ItemDrop, this.ItemDrop); + GetProfileDifficultyNumValues(kv, "item_drop_type", this.ItemDropType, this.ItemDropType); + + if (kv.JumpToKey("effects")) + { + if (kv.JumpToKey("on_start")) + { + this.StartEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.StartEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("on_end")) + { + this.EndEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.EndEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("add_health")) + { + GetProfileDifficultyFloatValues(kv, "player", this.AddHealthPerPlayer, this.AddHealthPerPlayer); + GetProfileDifficultyFloatValues(kv, "scout", this.AddHealthPerScout, this.AddHealthPerScout); + GetProfileDifficultyFloatValues(kv, "soldier", this.AddHealthPerSoldier, this.AddHealthPerSoldier); + GetProfileDifficultyFloatValues(kv, "pyro", this.AddHealthPerPyro, this.AddHealthPerPyro); + GetProfileDifficultyFloatValues(kv, "demoman", this.AddHealthPerDemoman, this.AddHealthPerDemoman); + GetProfileDifficultyFloatValues(kv, "heavy", this.AddHealthPerHeavy, this.AddHealthPerHeavy); + GetProfileDifficultyFloatValues(kv, "engineer", this.AddHealthPerEngineer, this.AddHealthPerEngineer); + GetProfileDifficultyFloatValues(kv, "medic", this.AddHealthPerMedic, this.AddHealthPerMedic); + GetProfileDifficultyFloatValues(kv, "sniper", this.AddHealthPerSniper, this.AddHealthPerSniper); + GetProfileDifficultyFloatValues(kv, "spy", this.AddHealthPerSpy, this.AddHealthPerSpy); + GetProfileDifficultyFloatValues(kv, "on_stun", this.AddHealthPerStun, this.AddHealthPerStun); + kv.GoBack(); + } + + GetProfileDifficultyFloatValues(kv, "add_speed", this.AddSpeedPerStun, this.AddSpeedPerStun); + GetProfileDifficultyFloatValues(kv, "add_acceleration", this.AddAccelerationPerStun, this.AddAccelerationPerStun); + } + + void PostLoad() + { + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + } + + void Destroy() + { + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.StartEffects; + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.EndEffects; + } + } +} + +enum struct SF2ChaserBossProfileDeathData +{ + bool Enabled[Difficulty_Max]; + float Health[Difficulty_Max]; + bool RemoveOnDeath; + bool DisappearOnDeath; + bool RagdollOnDeath; + + bool KeyDrop; + char KeyModel[PLATFORM_MAX_PATH]; + char KeyTrigger[PLATFORM_MAX_PATH]; + + bool ItemDrop[Difficulty_Max]; + int ItemDropType[Difficulty_Max]; + + float AddHealthPerPlayer[Difficulty_Max]; + float AddHealthPerScout[Difficulty_Max]; + float AddHealthPerSoldier[Difficulty_Max]; + float AddHealthPerPyro[Difficulty_Max]; + float AddHealthPerDemoman[Difficulty_Max]; + float AddHealthPerHeavy[Difficulty_Max]; + float AddHealthPerEngineer[Difficulty_Max]; + float AddHealthPerMedic[Difficulty_Max]; + float AddHealthPerSniper[Difficulty_Max]; + float AddHealthPerSpy[Difficulty_Max]; + float AddHealthPerDeath[Difficulty_Max]; + + ArrayList Gibs; + int GibSkin; + + ArrayList StartEffects; + ArrayList EndEffects; + + void Init() + { + this.RemoveOnDeath = false; + this.DisappearOnDeath = false; + this.RagdollOnDeath = false; + + this.KeyDrop = false; + this.KeyModel = SF_KEYMODEL; + this.KeyTrigger[0] = '\0'; + + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.Health[difficulty] = 400.0; + + this.ItemDrop[difficulty] = false; + this.ItemDropType[difficulty] = 1; + + this.AddHealthPerPlayer[difficulty] = 0.0; + this.AddHealthPerScout[difficulty] = 0.0; + this.AddHealthPerSoldier[difficulty] = 0.0; + this.AddHealthPerPyro[difficulty] = 0.0; + this.AddHealthPerDemoman[difficulty] = 0.0; + this.AddHealthPerHeavy[difficulty] = 0.0; + this.AddHealthPerEngineer[difficulty] = 0.0; + this.AddHealthPerMedic[difficulty] = 0.0; + this.AddHealthPerSniper[difficulty] = 0.0; + this.AddHealthPerSpy[difficulty] = 0.0; + this.AddHealthPerDeath[difficulty] = 0.0; + } + + this.Gibs = null; + this.GibSkin = 0; + + this.StartEffects = null; + this.EndEffects = null; + } + + void Load(KeyValues kv, bool checkFile) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "health", this.Health, this.Health); + this.RemoveOnDeath = kv.GetNum("remove", this.RemoveOnDeath) != 0; + this.DisappearOnDeath = kv.GetNum("disappear", this.DisappearOnDeath) != 0; + this.RagdollOnDeath = kv.GetNum("become_ragdoll", this.RagdollOnDeath) != 0; + + this.KeyDrop = kv.GetNum("key_drop", this.KeyDrop) != 0; + kv.GetString("key_model", this.KeyModel, sizeof(this.KeyModel), this.KeyModel); + PrecacheModel2(this.KeyModel, _, _, checkFile); + kv.GetString("key_trigger", this.KeyTrigger, sizeof(this.KeyTrigger), this.KeyTrigger); + + GetProfileDifficultyBoolValues(kv, "item_drop", this.ItemDrop, this.ItemDrop); + GetProfileDifficultyNumValues(kv, "item_drop_type", this.ItemDropType, this.ItemDropType); + + if (kv.JumpToKey("effects")) + { + if (kv.JumpToKey("on_start")) + { + this.StartEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.StartEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("on_end")) + { + this.EndEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.EndEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("add_health")) + { + GetProfileDifficultyFloatValues(kv, "player", this.AddHealthPerPlayer, this.AddHealthPerPlayer); + GetProfileDifficultyFloatValues(kv, "scout", this.AddHealthPerScout, this.AddHealthPerScout); + GetProfileDifficultyFloatValues(kv, "soldier", this.AddHealthPerSoldier, this.AddHealthPerSoldier); + GetProfileDifficultyFloatValues(kv, "pyro", this.AddHealthPerPyro, this.AddHealthPerPyro); + GetProfileDifficultyFloatValues(kv, "demoman", this.AddHealthPerDemoman, this.AddHealthPerDemoman); + GetProfileDifficultyFloatValues(kv, "heavy", this.AddHealthPerHeavy, this.AddHealthPerHeavy); + GetProfileDifficultyFloatValues(kv, "engineer", this.AddHealthPerEngineer, this.AddHealthPerEngineer); + GetProfileDifficultyFloatValues(kv, "medic", this.AddHealthPerMedic, this.AddHealthPerMedic); + GetProfileDifficultyFloatValues(kv, "sniper", this.AddHealthPerSniper, this.AddHealthPerSniper); + GetProfileDifficultyFloatValues(kv, "spy", this.AddHealthPerSpy, this.AddHealthPerSpy); + GetProfileDifficultyFloatValues(kv, "on_death", this.AddHealthPerDeath, this.AddHealthPerDeath); + kv.GoBack(); + } + + if (kv.JumpToKey("gibs")) + { + this.GibSkin = kv.GetNum("skin", this.GibSkin); + this.Gibs = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char value[PLATFORM_MAX_PATH], key[64]; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + kv.GetString(key, value, sizeof(value)); + if (value[0] == '\0') + { + break; + } + PrecacheModel2(value, _, _, checkFile); + this.Gibs.PushString(value); + } + kv.GoBack(); + } + } + + void PostLoad() + { + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + } + + void Destroy() + { + if (this.Gibs != null) + { + delete this.Gibs; + } + + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.StartEffects; + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.EndEffects; + } + } +} + +enum struct SF2ChaserBossProfileChaseOnLookData +{ + bool Enabled[Difficulty_Max]; + bool AddTargets[Difficulty_Max]; + float RequiredLookPosition[3]; + float MinimumXAngle[Difficulty_Max]; + float MaximumXAngle[Difficulty_Max]; + float MinimumYAngle[Difficulty_Max]; + float MaximumYAngle[Difficulty_Max]; + float RequiredYAngle[Difficulty_Max]; + float RequiredFOV[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.AddTargets[difficulty] = true; + this.MinimumXAngle[difficulty] = -45.0; + this.MaximumXAngle[difficulty] = 180.0; + this.MinimumYAngle[difficulty] = 0.0; + this.MaximumYAngle[difficulty] = 105.0; + this.RequiredFOV[difficulty] = -1.0; + } + this.RequiredLookPosition[2] = 35.0; + for (int i = 0; i < 2; i++) + { + this.RequiredLookPosition[i] = 0.0; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyBoolValues(kv, "add_targets", this.AddTargets, this.AddTargets); + kv.GetVector("look_position", this.RequiredLookPosition, this.RequiredLookPosition); + GetProfileDifficultyFloatValues(kv, "minimum_x_angle", this.MinimumXAngle, this.MinimumXAngle); + GetProfileDifficultyFloatValues(kv, "maximum_x_angle", this.MaximumXAngle, this.MaximumXAngle); + GetProfileDifficultyFloatValues(kv, "minimum_y_angle", this.MinimumYAngle, this.MinimumYAngle); + GetProfileDifficultyFloatValues(kv, "maximum_y_angle", this.MaximumYAngle, this.MaximumYAngle); + GetProfileDifficultyFloatValues(kv, "required_fov", this.RequiredFOV, this.RequiredFOV); + } +} + +enum struct SF2PostureConditionWithinBoundsInfo +{ + bool Enabled; + float Mins[3]; + float Maxs[3]; + + void Init() + { + this.Enabled = false; + for (int i = 0; i < 3; i++) + { + this.Mins[0] = 0.0; + this.Maxs[0] = 0.0; + } + } + + void Load(KeyValues kv) + { + this.Enabled = true; + kv.GetVector("mins", this.Mins, this.Mins); + kv.GetVector("maxs", this.Maxs, this.Maxs); + } +} + +enum struct SF2PostureConditionRunAwayInfo +{ + bool Enabled; + + void Init() + { + this.Enabled = false; + } + + void Load() + { + this.Enabled = true; + } +} + +enum struct SF2PostureConditionRagePhase +{ + bool Enabled; + ArrayList Names; + + void Init() + { + this.Enabled = false; + this.Names = null; + } + + void Load(KeyValues kv) + { + this.Enabled = true; + this.Names = new ArrayList(ByteCountToCells(64)); + char value[1024]; + kv.GetString("phases", value, sizeof(value), value); + char phase[64][64]; + int maxLength = ExplodeString(value, " ", phase, sizeof(phase), sizeof(phase)); + for (int i = 0; i < maxLength; i++) + { + this.Names.PushString(phase[i]); + } + } + + void Destroy() + { + if (this.Names != null) + { + delete this.Names; + } + } +} + +enum struct SF2PostureConditionWithinRangeInfo +{ + bool Enabled[Difficulty_Max]; + float MinRange[Difficulty_Max]; + float MaxRange[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + float CurrentCooldown; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.MinRange[i] = 0.0; + this.MaxRange[i] = 512.0; + this.Cooldown[i] = 1.0; + } + this.CurrentCooldown = 0.0; + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "min_range", this.MinRange, this.MinRange); + GetProfileDifficultyFloatValues(kv, "max_range", this.MaxRange, this.MaxRange); + GetProfileDifficultyFloatValues(kv, "cooldown", this.Cooldown, this.Cooldown); + } +} + +enum struct SF2PostureConditionLookAtInfo +{ + bool Enabled[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + } + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + } +} + +enum struct SF2PostureConditionAlertInfo +{ + bool Enabled[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + } + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + } +} + +enum struct SF2PostureConditionCloakInfo +{ + bool Enabled[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + } + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + } +} + +enum struct SF2ChaserBossProfilePostureInfo +{ + char Name[64]; + float Speed[Difficulty_Max]; + float WalkSpeed[Difficulty_Max]; + float Acceleration[Difficulty_Max]; + SF2BossProfileMasterAnimationsData Animations; + SF2PostureConditionWithinBoundsInfo BoundsInfo; + SF2PostureConditionRunAwayInfo RunAwayInfo; + SF2PostureConditionRagePhase RagePhaseInfo; + SF2PostureConditionWithinRangeInfo RangeInfo; + SF2PostureConditionLookAtInfo LookAtInfo; + SF2PostureConditionAlertInfo AlertInfo; + SF2PostureConditionCloakInfo CloakInfo; + + void Init() + { + this.Name[0] = '\0'; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Speed[i] = 300.0; + this.WalkSpeed[i] = 100.0; + this.Acceleration[i] = 4000.0; + } + this.Animations.Init(); + this.BoundsInfo.Init(); + this.RunAwayInfo.Init(); + this.RagePhaseInfo.Init(); + this.RangeInfo.Init(); + this.LookAtInfo.Init(); + this.AlertInfo.Init(); + this.CloakInfo.Init(); + } + + void Load(KeyValues kv) + { + kv.GetSectionName(this.Name, sizeof(this.Name)); + GetProfileDifficultyFloatValues(kv, "speed", this.Speed, this.Speed); + GetProfileDifficultyFloatValues(kv, "walkspeed", this.WalkSpeed, this.WalkSpeed); + GetProfileDifficultyFloatValues(kv, "acceleration", this.Acceleration, this.Acceleration); + this.Animations.Load(kv, false); + if (kv.JumpToKey("conditions")) + { + if (kv.JumpToKey("within_bounds")) + { + this.BoundsInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("running_away")) + { + this.RunAwayInfo.Load(); + kv.GoBack(); + } + + if (kv.JumpToKey("in_phases")) + { + this.RagePhaseInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("within_range")) + { + this.RangeInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("on_look")) + { + this.LookAtInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("on_alert")) + { + this.AlertInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("on_cloak")) + { + this.CloakInfo.Load(kv); + kv.GoBack(); + } + kv.GoBack(); + } + } + + void Destroy() + { + this.Animations.Destroy(); + this.RagePhaseInfo.Destroy(); + } +} + +enum struct SF2ChaserBossProfileAlertOnAlertInfo +{ + bool Enabled[Difficulty_Max]; + bool OnChangeState[Difficulty_Max]; + float Radius[Difficulty_Max]; + bool ShouldBeVisible[Difficulty_Max]; + bool Follow[Difficulty_Max]; + float FollowCooldown[Difficulty_Max]; + bool Patrol[Difficulty_Max]; + float PatrolDurationMin[Difficulty_Max]; + float PatrolDurationMax[Difficulty_Max]; + float PatrolRadiusMin[Difficulty_Max]; + float PatrolRadiusMax[Difficulty_Max]; + bool Copies[Difficulty_Max]; + bool Companions[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.OnChangeState[difficulty] = true; + this.Radius[difficulty] = 1024.0; + this.ShouldBeVisible[difficulty] = false; + this.Follow[difficulty] = false; + this.FollowCooldown[difficulty] = 10.0; + this.Patrol[difficulty] = false; + this.PatrolDurationMin[difficulty] = 10.0; + this.PatrolDurationMax[difficulty] = 15.0; + this.PatrolRadiusMin[difficulty] = 750.0; + this.PatrolRadiusMax[difficulty] = 1500.0; + this.Copies[difficulty] = false; + this.Companions[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + if (kv.JumpToKey("sync")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyBoolValues(kv, "on_change_state", this.OnChangeState, this.OnChangeState); + GetProfileDifficultyFloatValues(kv, "radius", this.Radius, this.Radius); + GetProfileDifficultyBoolValues(kv, "should_be_visible", this.ShouldBeVisible, this.ShouldBeVisible); + GetProfileDifficultyBoolValues(kv, "follow_leader", this.Follow, this.Follow); + GetProfileDifficultyFloatValues(kv, "follow_cooldown", this.FollowCooldown, this.FollowCooldown); + GetProfileDifficultyBoolValues(kv, "copies", this.Copies, this.Copies); + GetProfileDifficultyBoolValues(kv, "companions", this.Companions, this.Companions); + + if (kv.JumpToKey("patrol")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Patrol, this.Patrol); + GetProfileDifficultyFloatValues(kv, "duration_min", this.PatrolDurationMin, this.PatrolDurationMin); + GetProfileDifficultyFloatValues(kv, "duration_max", this.PatrolDurationMax, this.PatrolDurationMax); + GetProfileDifficultyFloatValues(kv, "radius_min", this.PatrolRadiusMin, this.PatrolRadiusMin); + GetProfileDifficultyFloatValues(kv, "radius_max", this.PatrolRadiusMax, this.PatrolRadiusMax); + + kv.GoBack(); + } + + kv.GoBack(); + } + } +} + +enum struct SF2ChaserBossProfileAlertOnChaseInfo +{ + bool Enabled[Difficulty_Max]; + bool OnChangeState[Difficulty_Max]; + float Radius[Difficulty_Max]; + bool ShouldBeVisible[Difficulty_Max]; + bool Follow[Difficulty_Max]; + float FollowCooldown[Difficulty_Max]; + bool Copies[Difficulty_Max]; + bool Companions[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.OnChangeState[difficulty] = true; + this.Radius[difficulty] = 1024.0; + this.ShouldBeVisible[difficulty] = false; + this.Follow[difficulty] = false; + this.FollowCooldown[difficulty] = 10.0; + this.Copies[difficulty] = false; + this.Companions[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + if (kv.JumpToKey("chase_together")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyBoolValues(kv, "on_change_state", this.OnChangeState, this.OnChangeState); + GetProfileDifficultyFloatValues(kv, "radius", this.Radius, this.Radius); + GetProfileDifficultyBoolValues(kv, "should_be_visible", this.ShouldBeVisible, this.ShouldBeVisible); + GetProfileDifficultyBoolValues(kv, "follow_leader", this.Follow, this.Follow); + GetProfileDifficultyFloatValues(kv, "follow_cooldown", this.FollowCooldown, this.FollowCooldown); + GetProfileDifficultyBoolValues(kv, "copies", this.Copies, this.Copies); + GetProfileDifficultyBoolValues(kv, "companions", this.Companions, this.Companions); + + kv.GoBack(); + } + } +} + +enum struct SF2ChaserRageInfo +{ + char Name[64]; + float PercentageThreshold; + + bool IncreaseDifficulty; + + bool Invincible; + + SF2BossProfileSoundInfo StartSounds; + SF2BossProfileSoundInfo HealSounds; + + bool IsHealing; + bool HealCloak; + float FleeRange[2]; + float HealAmount; + float HealDelay; + float HealDuration; + + SF2BossProfileMasterAnimationsData Animations; + + void Init() + { + this.Name[0] = '\0'; + this.PercentageThreshold = 0.75; + this.IncreaseDifficulty = true; + this.Invincible = false; + + this.StartSounds.Init(); + this.HealSounds.Init(); + + this.IsHealing = false; + this.HealCloak = false; + this.FleeRange[0] = 512.0; + this.FleeRange[1] = 1024.0; + this.HealAmount = 0.5; + this.HealDelay = 0.0; + this.HealDuration = 1.0; + + this.Animations.Init(); + } + + void Load(KeyValues kv, bool checkFile) + { + kv.GetSectionName(this.Name, sizeof(this.Name)); + this.PercentageThreshold = kv.GetFloat("health_percent", this.PercentageThreshold); + this.IncreaseDifficulty = kv.GetNum("increase_difficulty", this.IncreaseDifficulty) != 0; + this.Invincible = kv.GetNum("invincible", this.Invincible) != 0; + + if (kv.JumpToKey("heal")) + { + this.IsHealing = true; + this.HealCloak = kv.GetNum("cloak", this.HealCloak) != 0; + this.FleeRange[0] = kv.GetFloat("flee_range_min", this.FleeRange[0]); + this.FleeRange[1] = kv.GetFloat("flee_range_max", this.FleeRange[1]); + this.HealAmount = kv.GetFloat("amount", this.HealAmount); + this.HealDelay = kv.GetFloat("delay", this.HealDelay); + this.HealDuration = kv.GetFloat("duration", this.HealDuration); + kv.GoBack(); + } + + if (kv.JumpToKey("sounds")) + { + if (kv.JumpToKey("start")) + { + this.StartSounds.Load(kv, checkFile); + kv.GoBack(); + } + + if (kv.JumpToKey("healing")) + { + this.HealSounds.Load(kv, checkFile); + kv.GoBack(); + } + + kv.GoBack(); + } + + this.Animations.Load(kv, false); + } + + void PostLoad() + { + this.StartSounds.PostLoad(); + this.HealSounds.PostLoad(); + } + + void Destroy() + { + this.StartSounds.Destroy(); + this.HealSounds.Destroy(); + this.Animations.Destroy(); + } +} + +enum struct SF2ChaserBossProfileCloakData +{ + bool Enabled[Difficulty_Max]; + float Duration[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + float CloakRange[Difficulty_Max]; + float DecloakRange[Difficulty_Max]; + int CloakRenderColor[4]; + int CloakRenderMode; + ArrayList CloakEffects; + ArrayList DecloakEffects; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.Cooldown[i] = 8.0; + this.Duration[i] = 10.0; + this.CloakRange[i] = 350.0; + this.DecloakRange[i] = 150.0; + } + + this.CloakRenderColor[0] = 0; + this.CloakRenderColor[1] = 0; + this.CloakRenderColor[2] = 0; + this.CloakRenderColor[3] = 0; + this.CloakRenderMode = 1; + this.CloakEffects = null; + this.DecloakEffects = null; + } + + void Load(KeyValues kv, bool checkFile) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); + GetProfileDifficultyFloatValues(kv, "cooldown", this.Cooldown, this.Cooldown); + GetProfileDifficultyFloatValues(kv, "cloak_range", this.CloakRange, this.CloakRange); + GetProfileDifficultyFloatValues(kv, "decloak_range", this.DecloakRange, this.DecloakRange); + + GetProfileColorNoBacks(kv, "color", this.CloakRenderColor[0], this.CloakRenderColor[1], this.CloakRenderColor[2], this.CloakRenderColor[3], + this.CloakRenderColor[0], this.CloakRenderColor[1], this.CloakRenderColor[2], this.CloakRenderColor[3]); + this.CloakRenderMode = kv.GetNum("rendermode", this.CloakRenderMode); + + if (kv.JumpToKey("effects")) + { + if (kv.JumpToKey("cloak")) + { + this.CloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.CloakEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + + kv.GoBack(); + } + + if (kv.JumpToKey("decloak")) + { + this.DecloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.DecloakEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + + kv.GoBack(); + } + + kv.GoBack(); + } + } + + void PostLoad() + { + if (this.CloakEffects != null && this.CloakEffects.Length <= 0) + { + delete this.CloakEffects; + } + + if (this.DecloakEffects != null && this.DecloakEffects.Length <= 0) + { + delete this.DecloakEffects; + } + + if (this.CloakEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.CloakEffects.Length; i++) + { + this.CloakEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.DecloakEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.DecloakEffects.Length; i++) + { + this.DecloakEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + } + + void Destroy() + { + if (this.CloakEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.CloakEffects.Length; i++) + { + this.CloakEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.CloakEffects; + } + + if (this.DecloakEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.DecloakEffects.Length; i++) + { + this.DecloakEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.DecloakEffects; + } + } +} + +enum struct SF2ChaserBossProfileIdleData +{ + bool TurnEnabled[Difficulty_Max]; + float TurnAngle[Difficulty_Max]; + float TurnMinTime[Difficulty_Max]; + float TurnMaxTime[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.TurnEnabled[i] = true; + this.TurnAngle[i] = 360.0; + this.TurnMinTime[i] = 1.5; + this.TurnMaxTime[i] = 3.0; + } + } + + void Load(KeyValues kv) + { + if (kv.JumpToKey("turn")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.TurnEnabled, this.TurnEnabled); + GetProfileDifficultyFloatValues(kv, "angle", this.TurnAngle, this.TurnAngle); + GetProfileDifficultyFloatValues(kv, "cooldown_min", this.TurnMinTime, this.TurnMinTime); + GetProfileDifficultyFloatValues(kv, "cooldown_max", this.TurnMaxTime, this.TurnMaxTime); + kv.GoBack(); + } + } +} + +enum struct SF2ChaserBossProfileAlertData +{ + float GraceTime[Difficulty_Max]; + float Duration[Difficulty_Max]; + bool RunOnWander[Difficulty_Max]; + bool RunOnSuspect[Difficulty_Max]; + + bool TurnEnabled[Difficulty_Max]; + float TurnAngle[Difficulty_Max]; + float TurnMinTime[Difficulty_Max]; + float TurnMaxTime[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Duration[i] = 10.0; + this.GraceTime[i] = 0.5; + this.RunOnSuspect[i] = false; + this.RunOnWander[i] = false; + + this.TurnEnabled[i] = true; + this.TurnAngle[i] = 360.0; + this.TurnMinTime[i] = 1.5; + this.TurnMaxTime[i] = 3.0; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyFloatValues(kv, "gracetime", this.GraceTime, this.GraceTime); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); + GetProfileDifficultyBoolValues(kv, "run_on_wander", this.RunOnWander, this.RunOnWander); + GetProfileDifficultyBoolValues(kv, "run_on_suspect", this.RunOnSuspect, this.RunOnSuspect); + + if (kv.JumpToKey("turn")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.TurnEnabled, this.TurnEnabled); + GetProfileDifficultyFloatValues(kv, "angle", this.TurnAngle, this.TurnAngle); + GetProfileDifficultyFloatValues(kv, "cooldown_min", this.TurnMinTime, this.TurnMinTime); + GetProfileDifficultyFloatValues(kv, "cooldown_max", this.TurnMaxTime, this.TurnMaxTime); + kv.GoBack(); + } + } +} + +enum struct SF2ChaserBossProfileResistanceData +{ + bool Enabled[Difficulty_Max]; + float Multiplier[Difficulty_Max]; + + ArrayList DamageTypes; + ArrayList HitboxGroups; + ArrayList Weapons; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.Multiplier[i] = 1.0; + } + + this.DamageTypes = null; + this.HitboxGroups = null; + this.Weapons = null; + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "multiplier", this.Multiplier, this.Multiplier); + char key[64]; + int resistance = -1; + if (kv.JumpToKey("damage_type")) + { + this.DamageTypes = new ArrayList(); + resistance = -1; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + resistance = kv.GetNum(key, -1); + if (resistance == -1) + { + break; + } + this.DamageTypes.Push(resistance); + } + kv.GoBack(); + } + + if (kv.JumpToKey("hitbox")) + { + this.HitboxGroups = new ArrayList(); + resistance = -1; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + resistance = kv.GetNum(key, -1); + if (resistance == -1) + { + break; + } + this.HitboxGroups.Push(resistance); + } + kv.GoBack(); + } + + if (kv.JumpToKey("weapon")) + { + this.Weapons = new ArrayList(); + resistance = -1; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + resistance = kv.GetNum(key, -1); + if (resistance == -1) + { + break; + } + this.Weapons.Push(resistance); + } + kv.GoBack(); + } } void Destroy() { - if (this.RepeatTimers != null) + if (this.DamageTypes != null) { - delete this.RepeatTimers; + delete this.DamageTypes; } - } -} -enum struct SF2ChaserBossProfileSoundSenseData -{ - int AddCount[Difficulty_Max]; - float Cooldown[Difficulty_Max]; + if (this.HitboxGroups != null) + { + delete this.HitboxGroups; + } - void Init() - { - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + if (this.Weapons != null) { - this.AddCount[difficulty] = this.AddCount[difficulty] = difficulty > 0 ? this.AddCount[difficulty - 1] : 2; - this.Cooldown[difficulty] = this.Cooldown[difficulty] = difficulty > 0 ? this.Cooldown[difficulty - 1] : 0.0; + delete this.Weapons; } } } @@ -301,7 +2450,6 @@ enum struct SF2ChaserBossProfileSoundSenseData enum struct SF2ChaserBossProfileData { float WalkSpeed[Difficulty_Max]; - float MaxWalkSpeed[Difficulty_Max]; float WakeRadius; @@ -309,21 +2457,12 @@ enum struct SF2ChaserBossProfileData ArrayList AttackPropModels; - float AlertDuration[Difficulty_Max]; - float AlertGracetime[Difficulty_Max]; float ChaseDuration[Difficulty_Max]; - float ChaseDurationAddVisibleMin; - float ChaseDurationAddVisibleMax; - - float ChasePersistencyTimeInit; - float ChaseAttackPersistencyTimeInit; - float ChaseAttackPersistencyTimeAdd; - float ChaseNewTargetPersistencyTimeInit; - float ChaseNewTargetPersistencyTimeAdd; - float ChasePersistencyAddVisibleMin; - float ChasePersistencyAddVisibleMax; - float ChaseStunPersistencyTimeInit; - float ChaseStunPersistencyTimeAdd; + float ChaseDurationAddMaxRange[Difficulty_Max]; + float ChaseDurationAddVisibleMin[Difficulty_Max]; + float ChaseDurationAddVisibleMax[Difficulty_Max]; + float ChaseDurationAddOnAttack[Difficulty_Max]; + float ChaseDurationAddOnStun[Difficulty_Max]; int SoundCountToAlert[Difficulty_Max]; float SoundPosDiscardTime[Difficulty_Max]; @@ -335,6 +2474,9 @@ enum struct SF2ChaserBossProfileData SF2ChaserBossProfileSoundSenseData WeaponSenses; SF2ChaserBossProfileSoundSenseData FlashlightSenses; + SF2ChaserBossProfileSmellData SmellData; + + bool CanWander[Difficulty_Max]; float WanderRangeMin[Difficulty_Max]; float WanderRangeMax[Difficulty_Max]; float WanderTimeMin[Difficulty_Max]; @@ -342,34 +2484,25 @@ enum struct SF2ChaserBossProfileData float WanderEnterTimeMin[Difficulty_Max]; float WanderEnterTimeMax[Difficulty_Max]; - bool StunEnabled; - float StunCooldown; - float StunHealth; - float StunHealthPerPlayer; - float StunHealthPerClass[10]; - bool FlashlightStun; - float FlashlightDamage; - bool DisappearOnStun; - bool ItemDropOnStun; - int StunItemDropType; - bool ChaseInitialOnStun; ArrayList DamageResistances; + SF2ChaserBossProfileStunData StunData; + SF2ChaserBossProfileDeathData DeathData; + bool KeyDrop; char KeyModel[PLATFORM_MAX_PATH]; char KeyTrigger[PLATFORM_MAX_PATH]; - float AwarenessIncreaseRate[Difficulty_Max]; - float AwarenessDecreaseRate[Difficulty_Max]; - - bool AutoChaseEnabled; + bool AutoChaseEnabled[Difficulty_Max]; int AutoChaseCount[Difficulty_Max]; int AutoChaseAdd[Difficulty_Max]; int AutoChaseAddFootstep[Difficulty_Max]; int AutoChaseAddLoudFootstep[Difficulty_Max]; + int AutoChaseAddQuietFootstep[Difficulty_Max]; int AutoChaseAddVoice[Difficulty_Max]; int AutoChaseAddWeapon[Difficulty_Max]; - bool AutoChaseSprinters; + bool AutoChaseSprinters[Difficulty_Max]; + float AutoChaseAfterChaseCooldown[Difficulty_Max]; bool ChasesEndlessly; @@ -380,19 +2513,6 @@ enum struct SF2ChaserBossProfileData float EarthquakeFootstepAmplitude; float EarthquakeFootstepFrequency; - char CloakOnSound[PLATFORM_MAX_PATH]; - char CloakOffSound[PLATFORM_MAX_PATH]; - bool CanCloak; - bool CloakOnFlee; - float CloakCooldown[Difficulty_Max]; - float CloakRange[Difficulty_Max]; - float DecloakRange[Difficulty_Max]; - float CloakDuration[Difficulty_Max]; - float CloakSpeedMultiplier[Difficulty_Max]; - char CloakParticle[PLATFORM_MAX_PATH]; - int CloakRenderColor[4]; - int CloakRenderMode; - bool ProjectilesEnabled; float ProjectileCooldownMin[Difficulty_Max]; float ProjectileCooldownMax[Difficulty_Max]; @@ -404,7 +2524,6 @@ enum struct SF2ChaserBossProfileData bool CriticalProjectiles; bool ShootGestures; char ShootGestureName[PLATFORM_MAX_PATH]; - bool ShootAnimations; bool ProjectileClips; int ProjectileClipSize[Difficulty_Max]; float ProjectileReloadTime[Difficulty_Max]; @@ -434,119 +2553,14 @@ enum struct SF2ChaserBossProfileData float IceballSlowDuration[Difficulty_Max]; float IceballSlowPercent[Difficulty_Max]; - bool AdvancedDamageEffects; - bool RandomAdvancedDamageEffects; - bool AdvancedDamageEffectParticles; - - int RandomEffectIndexes; - char RandomEffectIndexesString[88]; - float RandomEffectDuration[Difficulty_Max]; - float RandomEffectSlowdown[Difficulty_Max]; - int RandomEffectStunType; - - bool JarateEffects; - int JarateIndexes; - char JarateIndexesString[88]; - float JarateDuration[Difficulty_Max]; - bool JarateBeamParticle; - char JarateParticle[PLATFORM_MAX_PATH]; - char JarateHitSound[PLATFORM_MAX_PATH]; - - bool MilkEffects; - int MilkIndexes; - char MilkIndexesString[88]; - float MilkDuration[Difficulty_Max]; - bool MilkBeamParaticle; - char MilkParticle[PLATFORM_MAX_PATH]; - char MilkHitSound[PLATFORM_MAX_PATH]; - - bool GasEffects; - int GasIndexes; - char GasIndexesString[88]; - float GasDuration[Difficulty_Max]; - bool GasBeamParticle; - char GasParticle[PLATFORM_MAX_PATH]; - char GasHitSound[PLATFORM_MAX_PATH]; - - bool MarkEffects; - int MarkIndexes; - char MarkIndexesString[88]; - float MarkDuration[Difficulty_Max]; - - bool SilentMarkEffects; - int SilentMarkIndexes; - char SilentMarkIndexesString[88]; - float SilentMarkDuration[Difficulty_Max]; - - bool IgniteEffects; - int IgniteIndexes; - char IgniteIndexesString[88]; - float IgniteDuration[Difficulty_Max]; - float IgniteDelay[Difficulty_Max]; - - bool StunEffects; - int StunEffectIndexes; - char StunEffectIndexesString[88]; - float StunEffectDuration[Difficulty_Max]; - float StunEffectSlowdown[Difficulty_Max]; - int StunEffectType; - bool StunEffectBeamParticle; - char StunParticle[PLATFORM_MAX_PATH]; - char StunHitSound[PLATFORM_MAX_PATH]; - - bool BleedEffects; - int BleedIndexes; - char BleedIndexesString[88]; - float BleedDuration[Difficulty_Max]; - - bool ElectricEffects; - int ElectricIndexes; - char ElectricIndexesString[88]; - float ElectricDuration[Difficulty_Max]; - float ElectricSlowdown[Difficulty_Max]; - int ElectricType; - bool ElectricBeamParticle; - char ElectricParticleRed[PLATFORM_MAX_PATH]; - char ElectricParticleBlue[PLATFORM_MAX_PATH]; - - bool SmiteEffects; - int SmiteIndexes; - char SmiteIndexesString[88]; - float SmiteDamage; - int SmiteDamageType; - int SmiteColor[4]; - bool SmiteMessage; - char SmiteHitSound[PLATFORM_MAX_PATH]; - bool XenobladeCombo; float XenobladeDuration; float XenobladeToppleDuration; float XenobladeToppleSlowdown; float XenobladeDazeDuration; - bool Shockwaves; - float ShockwaveHeight[Difficulty_Max]; - float ShockwaveRange[Difficulty_Max]; - float ShockwaveDrain[Difficulty_Max]; - float ShockwaveForce[Difficulty_Max]; - bool ShockwaveStun; - float ShockwaveStunDuration[Difficulty_Max]; - float ShockwaveStunSlowdown[Difficulty_Max]; - int ShockwaveIndexes; - char ShockwaveIndexesString[88]; - float ShockwaveWidths[2]; - float ShockwaveAmplitude; - int ShockwaveColor1[3]; - int ShockwaveColor2[3]; - int ShockwaveAlpha1; - int ShockwaveAlpha2; - char ShockwaveBeamSprite[PLATFORM_MAX_PATH]; - char ShockwaveHaloSprite[PLATFORM_MAX_PATH]; - int ShockwaveBeamModel; - int ShockwaveHaloModel; - - bool Traps; - int TrapType; + bool Traps[Difficulty_Max]; + int TrapType[Difficulty_Max]; float TrapCooldown[Difficulty_Max]; char TrapModel[PLATFORM_MAX_PATH]; char TrapDeploySound[PLATFORM_MAX_PATH]; @@ -556,32 +2570,14 @@ enum struct SF2ChaserBossProfileData char TrapAnimClose[PLATFORM_MAX_PATH]; char TrapAnimOpen[PLATFORM_MAX_PATH]; - bool DamageParticles; - char DamageParticleName[PLATFORM_MAX_PATH]; - bool DamageParticleBeam; - char DamageParticleSound[PLATFORM_MAX_PATH]; - - bool SelfHeal; - float SelfHealPercentageStart; - float SelfHealRecover[3]; - bool CloakToHeal; + ArrayList Rages; bool BoxingBoss; bool NormalSoundHook; - bool ChaseInitialAnimations; bool OldAnimationAI; - bool AlertWalkingAnimation; - bool SpawnAnimationsEnabled; - - bool Crawling; - float CrawlSpeedMultiplier[Difficulty_Max]; - float CrawlDetectMins[3]; - float CrawlDetectMaxs[3]; - bool ChaseOnLook; - - bool UnnerfedVisibility; + SF2ChaserBossProfileChaseOnLookData ChaseOnLookData; bool ClearLayersOnAnimUpdate; @@ -603,107 +2599,93 @@ enum struct SF2ChaserBossProfileData SF2BossProfileSoundInfo AttackKilledSounds; SF2BossProfileSoundInfo AttackKilledAllSounds; SF2BossProfileSoundInfo AttackKilledClientSounds; - SF2BossProfileSoundInfo RageAllSounds; - SF2BossProfileSoundInfo RageTwoSounds; - SF2BossProfileSoundInfo RageThreeSounds; - SF2BossProfileSoundInfo SelfHealSounds; SF2BossProfileSoundInfo StunnedSounds; + SF2BossProfileSoundInfo DeathSounds; + SF2BossProfileSoundInfo TauntKillSounds; + SF2BossProfileSoundInfo SmellSounds; SF2BossProfileSoundInfo FootstepSounds; + SF2BossProfileSoundInfo SelfHealSounds; + SF2BossProfileSoundInfo RageSounds1; + SF2BossProfileSoundInfo RageSounds2; + SF2BossProfileSoundInfo RageSounds3; + SF2BossProfileSoundInfo DespawnSounds; ArrayList AttackSounds; ArrayList HitSounds; ArrayList MissSounds; ArrayList BulletShootSounds; ArrayList ProjectileShootSounds; + ArrayList AttackBeginSounds; + ArrayList AttackEndSounds; + ArrayList AttackLoopSounds; SF2BossProfileSoundInfo ChaseMusics; SF2BossProfileSoundInfo ChaseVisibleMusics; SF2BossProfileSoundInfo AlertMusics; SF2BossProfileSoundInfo IdleMusics; - void Init() - { - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - this.WalkSpeed[difficulty] = this.WalkSpeed[difficulty] = difficulty > 0 ? this.WalkSpeed[difficulty - 1] : 30.0; - this.MaxWalkSpeed[difficulty] = this.MaxWalkSpeed[difficulty] = difficulty > 0 ? this.MaxWalkSpeed[difficulty - 1] : 30.0; - - this.AlertDuration[difficulty] = this.AlertDuration[difficulty] = difficulty > 0 ? this.AlertDuration[difficulty - 1] : 5.0; - this.AlertGracetime[difficulty] = this.AlertGracetime[difficulty] = difficulty > 0 ? this.AlertGracetime[difficulty - 1] : 0.5; - this.ChaseDuration[difficulty] = this.ChaseDuration[difficulty] = difficulty > 0 ? this.ChaseDuration[difficulty - 1] : 10.0; + StringMap Postures; - this.SoundCountToAlert[difficulty] = this.SoundCountToAlert[difficulty] = difficulty > 0 ? this.SoundCountToAlert[difficulty - 1] : 8; - this.SoundPosDiscardTime[difficulty] = this.SoundPosDiscardTime[difficulty] = difficulty > 0 ? this.SoundPosDiscardTime[difficulty - 1] : 2.0; - this.SoundPosDistanceTolerance[difficulty] = this.SoundPosDistanceTolerance[difficulty] = difficulty > 0 ? this.SoundPosDistanceTolerance[difficulty - 1] : 512.0; + SF2ChaserBossProfileAlertOnAlertInfo AlertOnAlertInfo; + SF2ChaserBossProfileAlertOnChaseInfo AlertOnChaseInfo; - this.WanderRangeMin[difficulty] = this.WanderRangeMin[difficulty] = difficulty > 0 ? this.WanderRangeMin[difficulty - 1] : 400.0; - this.WanderRangeMax[difficulty] = this.WanderRangeMax[difficulty] = difficulty > 0 ? this.WanderRangeMax[difficulty - 1] : 1024.0; - this.WanderTimeMin[difficulty] = this.WanderTimeMin[difficulty] = difficulty > 0 ? this.WanderTimeMin[difficulty - 1] : 8.0; - this.WanderTimeMax[difficulty] = this.WanderTimeMax[difficulty] = difficulty > 0 ? this.WanderTimeMax[difficulty - 1] : 12.0; - this.WanderEnterTimeMin[difficulty] = this.WanderEnterTimeMin[difficulty] = difficulty > 0 ? this.WanderEnterTimeMin[difficulty - 1] : 2.0; - this.WanderEnterTimeMax[difficulty] = this.WanderEnterTimeMax[difficulty] = difficulty > 0 ? this.WanderEnterTimeMax[difficulty - 1] : 4.5; + SF2ChaserBossProfileCloakData CloakData; - this.AwarenessIncreaseRate[difficulty] = this.AwarenessIncreaseRate[difficulty] = difficulty > 0 ? this.AwarenessIncreaseRate[difficulty - 1] : 75.0; - this.AwarenessDecreaseRate[difficulty] = this.AwarenessDecreaseRate[difficulty] = difficulty > 0 ? this.AwarenessDecreaseRate[difficulty - 1] : 150.0; + SF2ChaserBossProfileIdleData IdleData; + SF2ChaserBossProfileAlertData AlertData; - this.AutoChaseCount[difficulty] = this.AutoChaseCount[difficulty] = difficulty > 0 ? this.AutoChaseCount[difficulty - 1] : 30; - this.AutoChaseAdd[difficulty] = this.AutoChaseAdd[difficulty] = difficulty > 0 ? this.AutoChaseAdd[difficulty - 1] : 0; - this.AutoChaseAddFootstep[difficulty] = this.AutoChaseAddFootstep[difficulty] = difficulty > 0 ? this.AutoChaseAddFootstep[difficulty - 1] : 2; + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.WalkSpeed[difficulty] = 90.0; + + this.ChaseDuration[difficulty] = 10.0; + this.ChaseDurationAddMaxRange[difficulty] = 1024.0; + this.ChaseDurationAddVisibleMin[difficulty] = 0.01; + this.ChaseDurationAddVisibleMax[difficulty] = 0.05; + this.ChaseDurationAddOnAttack[difficulty] = 0.0; + this.ChaseDurationAddOnStun[difficulty] = 0.0; + + this.SoundCountToAlert[difficulty] = 8; + this.SoundPosDiscardTime[difficulty] = 2.0; + this.SoundPosDistanceTolerance[difficulty] = 512.0; + + this.CanWander[difficulty] = true; + this.WanderRangeMin[difficulty] = 800.0; + this.WanderRangeMax[difficulty] = 1600.0; + this.WanderTimeMin[difficulty] = 8.0; + this.WanderTimeMax[difficulty] = 12.0; + this.WanderEnterTimeMin[difficulty] = 2.0; + this.WanderEnterTimeMax[difficulty] = 4.5; + + this.AutoChaseEnabled[difficulty] = false; + this.AutoChaseCount[difficulty] = 100; + this.AutoChaseAdd[difficulty] = 0; + this.AutoChaseAddFootstep[difficulty] = 2; this.AutoChaseAddLoudFootstep[difficulty] = this.AutoChaseAddFootstep[difficulty]; - this.AutoChaseAddVoice[difficulty] = this.AutoChaseAddVoice[difficulty] = difficulty > 0 ? this.AutoChaseAddVoice[difficulty - 1] : 8; - this.AutoChaseAddWeapon[difficulty] = this.AutoChaseAddWeapon[difficulty] = difficulty > 0 ? this.AutoChaseAddWeapon[difficulty - 1] : 4; - - this.CloakCooldown[difficulty] = this.CloakCooldown[difficulty] = difficulty > 0 ? this.CloakCooldown[difficulty - 1] : 8.0; - this.CloakRange[difficulty] = this.CloakRange[difficulty] = difficulty > 0 ? this.CloakRange[difficulty - 1] : 350.0; - this.DecloakRange[difficulty] = this.DecloakRange[difficulty] = difficulty > 0 ? this.DecloakRange[difficulty - 1] : 150.0; - this.CloakDuration[difficulty] = this.CloakDuration[difficulty] = difficulty > 0 ? this.CloakDuration[difficulty - 1] : 10.0; - this.CloakSpeedMultiplier[difficulty] = this.CloakSpeedMultiplier[difficulty] = difficulty > 0 ? this.CloakSpeedMultiplier[difficulty - 1] : 1.0; - - this.ProjectileCooldownMin[difficulty] = this.ProjectileCooldownMin[difficulty] = difficulty > 0 ? this.ProjectileCooldownMin[difficulty - 1] : 1.0; - this.ProjectileCooldownMax[difficulty] = this.ProjectileCooldownMax[difficulty] = difficulty > 0 ? this.ProjectileCooldownMax[difficulty - 1] : 2.0; - this.ProjectileSpeed[difficulty] = this.ProjectileSpeed[difficulty] = difficulty > 0 ? this.ProjectileSpeed[difficulty - 1] : 400.0; - this.ProjectileDamage[difficulty] = this.ProjectileDamage[difficulty] = difficulty > 0 ? this.ProjectileDamage[difficulty - 1] : 50.0; - this.ProjectileRadius[difficulty] = this.ProjectileRadius[difficulty] = difficulty > 0 ? this.ProjectileRadius[difficulty - 1] : 128.0; - this.ProjectileDeviation[difficulty] = this.ProjectileDeviation[difficulty] = difficulty > 0 ? this.ProjectileDeviation[difficulty - 1] : 0.0; - this.ProjectileClipSize[difficulty] = this.ProjectileClipSize[difficulty] = difficulty > 0 ? this.ProjectileClipSize[difficulty - 1] : 3; - this.ProjectileReloadTime[difficulty] = this.ProjectileReloadTime[difficulty] = difficulty > 0 ? this.ProjectileReloadTime[difficulty - 1] : 2.0; - this.ProjectileChargeUp[difficulty] = this.ProjectileChargeUp[difficulty] = difficulty > 0 ? this.ProjectileChargeUp[difficulty - 1] : 5.0; - this.ProjectileCount[difficulty] = this.ProjectileCount[difficulty] = difficulty > 0 ? this.ProjectileCount[difficulty - 1] : 1; - - this.IceballSlowDuration[difficulty] = this.IceballSlowDuration[difficulty] = difficulty > 0 ? this.IceballSlowDuration[difficulty - 1] : 2.0; - this.IceballSlowPercent[difficulty] = this.IceballSlowPercent[difficulty] = difficulty > 0 ? this.IceballSlowPercent[difficulty - 1] : 0.55; - - this.RandomEffectDuration[difficulty] = this.RandomEffectDuration[difficulty] = difficulty > 0 ? this.RandomEffectDuration[difficulty - 1] : 0.0; - this.RandomEffectSlowdown[difficulty] = this.RandomEffectSlowdown[difficulty] = difficulty > 0 ? this.RandomEffectSlowdown[difficulty - 1] : 0.0; - this.JarateDuration[difficulty] = this.JarateDuration[difficulty] = difficulty > 0 ? this.JarateDuration[difficulty - 1] : 0.0; - this.MilkDuration[difficulty] = this.MilkDuration[difficulty] = difficulty > 0 ? this.MilkDuration[difficulty - 1] : 0.0; - this.GasDuration[difficulty] = this.GasDuration[difficulty] = difficulty > 0 ? this.GasDuration[difficulty - 1] : 0.0; - this.MarkDuration[difficulty] = this.MarkDuration[difficulty] = difficulty > 0 ? this.MarkDuration[difficulty - 1] : 0.0; - this.SilentMarkDuration[difficulty] = this.SilentMarkDuration[difficulty] = difficulty > 0 ? this.SilentMarkDuration[difficulty - 1] : 0.0; - this.IgniteDuration[difficulty] = this.IgniteDuration[difficulty] = difficulty > 0 ? this.IgniteDuration[difficulty - 1] : 10.0; - this.IgniteDelay[difficulty] = this.IgniteDelay[difficulty] = difficulty > 0 ? this.IgniteDelay[difficulty - 1] : 0.0; - this.StunEffectDuration[difficulty] = this.StunEffectDuration[difficulty] = difficulty > 0 ? this.StunEffectDuration[difficulty - 1] : 0.0; - this.StunEffectSlowdown[difficulty] = this.StunEffectSlowdown[difficulty] = difficulty > 0 ? this.StunEffectSlowdown[difficulty - 1] : 0.0; - this.BleedDuration[difficulty] = this.BleedDuration[difficulty] = difficulty > 0 ? this.BleedDuration[difficulty - 1] : 0.0; - this.ElectricDuration[difficulty] = this.ElectricDuration[difficulty] = difficulty > 0 ? this.ElectricDuration[difficulty - 1] : 0.0; - this.ElectricSlowdown[difficulty] = this.ElectricSlowdown[difficulty] = difficulty > 0 ? this.ElectricSlowdown[difficulty - 1] : 0.0; - - this.ShockwaveHeight[difficulty] = this.ShockwaveHeight[difficulty] = difficulty > 0 ? this.ShockwaveHeight[difficulty - 1] : 64.0; - this.ShockwaveRange[difficulty] = this.ShockwaveRange[difficulty] = difficulty > 0 ? this.ShockwaveRange[difficulty - 1] : 200.0; - this.ShockwaveDrain[difficulty] = this.ShockwaveDrain[difficulty] = difficulty > 0 ? this.ShockwaveDrain[difficulty - 1] : 0.2; - this.ShockwaveForce[difficulty] = this.ShockwaveForce[difficulty] = difficulty > 0 ? this.ShockwaveForce[difficulty - 1] : 6.0; - - this.ShockwaveStunDuration[difficulty] = this.ShockwaveStunDuration[difficulty] = difficulty > 0 ? this.ShockwaveStunDuration[difficulty - 1] : 2.0; - this.ShockwaveStunSlowdown[difficulty] = this.ShockwaveStunSlowdown[difficulty] = difficulty > 0 ? this.ShockwaveStunSlowdown[difficulty - 1] : 0.7; - - this.TrapCooldown[difficulty] = this.TrapCooldown[difficulty] = difficulty > 0 ? this.TrapCooldown[difficulty - 1] : 8.0; - - this.CrawlSpeedMultiplier[difficulty] = this.CrawlSpeedMultiplier[difficulty] = difficulty > 0 ? this.CrawlSpeedMultiplier[difficulty - 1] : 0.5; - } + this.AutoChaseAddQuietFootstep[difficulty] = 0; + this.AutoChaseAddVoice[difficulty] = 8; + this.AutoChaseAddWeapon[difficulty] = 4; + this.AutoChaseSprinters[difficulty] = false; + this.AutoChaseAfterChaseCooldown[difficulty] = 3.0; + + this.ProjectileCooldownMin[difficulty] = 1.0; + this.ProjectileCooldownMax[difficulty] = 2.0; + this.ProjectileSpeed[difficulty] = 400.0; + this.ProjectileDamage[difficulty] = 50.0; + this.ProjectileRadius[difficulty] = 128.0; + this.ProjectileDeviation[difficulty] = 0.0; + this.ProjectileClipSize[difficulty] = 3; + this.ProjectileReloadTime[difficulty] = 2.0; + this.ProjectileChargeUp[difficulty] = 5.0; + this.ProjectileCount[difficulty] = 1; - for (int i = 0; i < 3; i++) - { - this.ShockwaveColor1[i] = 128; - this.ShockwaveColor2[i] = 255; + this.IceballSlowDuration[difficulty] = 2.0; + this.IceballSlowPercent[difficulty] = 0.55; + + this.Traps[difficulty] = false; + this.TrapType[difficulty] = 0; + this.TrapCooldown[difficulty] = 8.0; } this.WakeRadius = 0.0; @@ -712,19 +2694,6 @@ enum struct SF2ChaserBossProfileData this.AttackPropModels = null; - this.ChaseDurationAddVisibleMin = 0.025; - this.ChaseDurationAddVisibleMax = 0.2; - - this.ChasePersistencyTimeInit = 5.0; - this.ChaseAttackPersistencyTimeInit = -1.0; - this.ChaseAttackPersistencyTimeAdd = 2.0; - this.ChaseNewTargetPersistencyTimeInit = -1.0; - this.ChaseNewTargetPersistencyTimeAdd = 2.0; - this.ChasePersistencyAddVisibleMin = 0.05; - this.ChasePersistencyAddVisibleMax = 0.15; - this.ChaseStunPersistencyTimeInit = -1.0; - this.ChaseStunPersistencyTimeAdd = 2.0; - this.FootstepSenses.Init(); this.LoudFootstepSenses.Init(); this.QuietFootstepSenses.Init(); @@ -732,40 +2701,28 @@ enum struct SF2ChaserBossProfileData this.WeaponSenses.Init(); this.FlashlightSenses.Init(); + this.SmellData.Init(); + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { this.FootstepSenses.AddCount[difficulty] = 1; this.LoudFootstepSenses.AddCount[difficulty] = 2; - this.QuietFootstepSenses.AddCount[difficulty] = 1; + this.QuietFootstepSenses.AddCount[difficulty] = 0; this.VoiceSenses.AddCount[difficulty] = 10; this.WeaponSenses.AddCount[difficulty] = 5; this.FlashlightSenses.AddCount[difficulty] = 5; } - this.StunEnabled = false; - this.StunCooldown = 3.5; - this.StunHealth = 0.0; - this.StunHealthPerPlayer = 0.0; - for (int i = 0; i < 10; i++) - { - this.StunHealthPerClass[i] = 0.0; - } - this.FlashlightStun = false; - this.FlashlightDamage = 0.0; - this.DisappearOnStun = false; - this.ItemDropOnStun = false; - this.StunItemDropType = 1; - this.ChaseInitialOnStun = false; this.DamageResistances = null; + this.StunData.Init(); + this.DeathData.Init(); + this.KeyDrop = false; this.KeyModel = SF_KEYMODEL; this.KeyTrigger[0] = '\0'; - this.AutoChaseEnabled = false; - this.AutoChaseSprinters = false; - this.ChasesEndlessly = false; this.EarthquakeFootsteps = false; @@ -775,23 +2732,11 @@ enum struct SF2ChaserBossProfileData this.EarthquakeFootstepAmplitude = 5.0; this.EarthquakeFootstepFrequency = 25.0; - this.CanCloak = false; - this.CloakOnFlee = false; - this.CloakParticle = "drg_cow_explosioncore_charged_blue"; - this.CloakRenderColor[0] = 0; - this.CloakRenderColor[1] = 1; - this.CloakRenderColor[2] = 2; - this.CloakRenderColor[3] = 3; - this.CloakRenderMode = 1; - this.CloakOnSound = DEFAULT_CLOAKONSOUND; - this.CloakOffSound = DEFAULT_CLOAKOFFSOUND; - this.ProjectilesEnabled = false; this.ProjectileType = SF2BossProjectileType_Fireball; this.CriticalProjectiles = false; this.ShootGestures = false; this.ShootGestureName[0] = '\0'; - this.ShootAnimations = false; this.ProjectileClips = false; this.ChargeUpProjectiles = false; this.ProjectileRandomPosMin = 1; @@ -814,101 +2759,12 @@ enum struct SF2ChaserBossProfileData this.BaseballShootSound = BASEBALL_SHOOT; this.BaseballModel = BASEBALL_MODEL; - this.AdvancedDamageEffects = false; - this.AdvancedDamageEffectParticles = true; - - this.RandomAdvancedDamageEffects = false; - this.RandomEffectIndexes = 1; - this.RandomEffectIndexesString = "1"; - this.RandomEffectStunType = 0; - - this.JarateEffects = false; - this.JarateIndexes = 1; - this.JarateIndexesString = "1"; - this.JarateBeamParticle = false; - this.JarateParticle = JARATE_PARTICLE; - this.JarateHitSound = JARATE_HITPLAYER; - - this.MilkEffects = false; - this.MilkIndexes = 1; - this.MilkIndexesString = "1"; - this.MilkBeamParaticle = false; - this.MilkParticle = MILK_PARTICLE; - this.MilkHitSound = JARATE_HITPLAYER; - - this.GasEffects = false; - this.GasIndexes = 1; - this.GasIndexesString = "1"; - this.GasBeamParticle = false; - this.GasParticle = GAS_PARTICLE; - this.GasHitSound = JARATE_HITPLAYER; - - this.MarkEffects = false; - this.MarkIndexes = 1; - this.MarkIndexesString = "1"; - - this.SilentMarkEffects = false; - this.SilentMarkIndexes = 1; - this.SilentMarkIndexesString = "1"; - - this.IgniteEffects = false; - this.IgniteIndexes = 1; - this.IgniteIndexesString = "1"; - - this.StunEffects = false; - this.StunEffectIndexes = 1; - this.StunEffectIndexesString = "1"; - this.StunEffectType = 0; - this.StunEffectBeamParticle = false; - this.StunParticle = STUN_PARTICLE; - this.StunHitSound = STUN_HITPLAYER; - - this.BleedEffects = false; - this.BleedIndexes = 1; - this.BleedIndexesString = "1"; - - this.ElectricEffects = false; - this.ElectricIndexes = 1; - this.ElectricIndexesString = "1"; - this.ElectricType = 0; - this.ElectricBeamParticle = false; - this.ElectricParticleRed = ELECTRIC_RED_PARTICLE; - this.ElectricParticleBlue = ELECTRIC_BLUE_PARTICLE; - - this.SmiteEffects = false; - this.SmiteIndexes = 1; - this.SmiteIndexesString = "1"; - this.SmiteDamage = 9001.0; - this.SmiteDamageType = 1048576; //Critical cause we're mean - this.SmiteColor[0] = 255; - this.SmiteColor[1] = 255; - this.SmiteColor[2] = 255; - this.SmiteColor[3] = 255; - this.SmiteMessage = false; - this.SmiteHitSound = SOUND_THUNDER; - this.XenobladeCombo = false; this.XenobladeDuration = 0.0; this.XenobladeToppleDuration = 0.0; this.XenobladeToppleSlowdown = 0.5; this.XenobladeDazeDuration = 0.0; - this.Shockwaves = false; - this.ShockwaveStun = false; - this.ShockwaveIndexes = 1; - this.ShockwaveIndexesString = "1"; - this.ShockwaveWidths[0] = 40.0; - this.ShockwaveWidths[1] = 20.0; - this.ShockwaveAmplitude = 5.0; - this.ShockwaveAlpha1 = 255; - this.ShockwaveAlpha2 = 255; - this.ShockwaveBeamSprite = "sprites/laser.vmt"; - this.ShockwaveHaloSprite = "sprites/halo01.vmt"; - this.ShockwaveBeamModel = -1; - this.ShockwaveHaloModel = -1; - - this.Traps = false; - this.TrapType = 0; this.TrapModel = TRAP_MODEL; this.TrapDeploySound = TRAP_DEPLOY; this.TrapMissSound = TRAP_CLOSE; @@ -917,33 +2773,14 @@ enum struct SF2ChaserBossProfileData this.TrapAnimClose = "trapclosed"; this.TrapAnimOpen[0] = '\0'; - this.DamageParticles = false; - this.DamageParticleName[0] = '\0'; - this.DamageParticleBeam = false; - this.DamageParticleSound[0] = '\0'; - - this.SelfHeal = false; - this.SelfHealPercentageStart = 0.35; - this.SelfHealRecover[0] = 0.75; - this.SelfHealRecover[1] = 0.5; - this.SelfHealRecover[2] = 0.25; - this.CloakToHeal = false; + this.Rages = null; this.BoxingBoss = false; this.NormalSoundHook = false; - this.ChaseInitialAnimations = false; this.OldAnimationAI = false; - this.AlertWalkingAnimation = false; - this.SpawnAnimationsEnabled = false; - - this.Crawling = false; - this.CrawlDetectMins = view_as({0.0, 0.0, 0.0}); - this.CrawlDetectMaxs = view_as({0.0, 0.0, 0.0}); - this.ChaseOnLook = false; - - this.UnnerfedVisibility = g_DefaultBossVisibilityStateConVar.BoolValue; + this.ChaseOnLookData.Init(); this.ClearLayersOnAnimUpdate = true; @@ -970,17 +2807,24 @@ enum struct SF2ChaserBossProfileData this.AttackKilledClientSounds.Init(); this.AttackKilledClientSounds.Channel = SNDCHAN_STATIC; this.AttackKilledClientSounds.Level = SNDLEVEL_HELICOPTER; - this.RageAllSounds.Init(); - this.RageTwoSounds.Init(); - this.RageThreeSounds.Init(); - this.SelfHealSounds.Init(); this.StunnedSounds.Init(); + this.DeathSounds.Init(); + this.TauntKillSounds.Init(); + this.SmellSounds.Init(); + this.SelfHealSounds.Init(); + this.RageSounds1.Init(); + this.RageSounds2.Init(); + this.RageSounds3.Init(); + this.DespawnSounds.Init(); this.FootstepSounds.Init(); this.AttackSounds = null; this.HitSounds = null; this.MissSounds = null; this.BulletShootSounds = null; this.ProjectileShootSounds = null; + this.AttackBeginSounds = null; + this.AttackEndSounds = null; + this.AttackLoopSounds = null; this.ChaseMusics.Init(); this.ChaseMusics.Channel = MUSIC_CHAN; @@ -990,6 +2834,16 @@ enum struct SF2ChaserBossProfileData this.AlertMusics.Channel = MUSIC_CHAN; this.IdleMusics.Init(); this.IdleMusics.Channel = MUSIC_CHAN; + + this.Postures = null; + + this.AlertOnAlertInfo.Init(); + this.AlertOnChaseInfo.Init(); + + this.CloakData.Init(); + + this.IdleData.Init(); + this.AlertData.Init(); } void PostLoad() @@ -1001,11 +2855,15 @@ enum struct SF2ChaserBossProfileData this.AttackKilledSounds.PostLoad(); this.AttackKilledAllSounds.PostLoad(); this.AttackKilledClientSounds.PostLoad(); - this.RageAllSounds.PostLoad(); - this.RageTwoSounds.PostLoad(); - this.RageThreeSounds.PostLoad(); - this.SelfHealSounds.PostLoad(); this.StunnedSounds.PostLoad(); + this.DeathSounds.PostLoad(); + this.TauntKillSounds.PostLoad(); + this.SmellSounds.PostLoad(); + this.SelfHealSounds.PostLoad(); + this.RageSounds1.PostLoad(); + this.RageSounds2.PostLoad(); + this.RageSounds3.PostLoad(); + this.DespawnSounds.PostLoad(); this.FootstepSounds.PostLoad(); this.ChaseMusics.PostLoad(); @@ -1013,6 +2871,11 @@ enum struct SF2ChaserBossProfileData this.AlertMusics.PostLoad(); this.IdleMusics.PostLoad(); + this.CloakData.PostLoad(); + + this.StunData.PostLoad(); + this.DeathData.PostLoad(); + if (this.ProjectilePosOffsets != null && this.ProjectilePosOffsets.Length <= 0) { delete this.ProjectilePosOffsets; @@ -1022,22 +2885,70 @@ enum struct SF2ChaserBossProfileData { delete this.AttackSounds; } + if (this.HitSounds != null && this.HitSounds.Length <= 0) { delete this.HitSounds; } + if (this.MissSounds != null && this.MissSounds.Length <= 0) { delete this.MissSounds; } + if (this.BulletShootSounds != null && this.BulletShootSounds.Length <= 0) { delete this.BulletShootSounds; } + if (this.ProjectileShootSounds != null && this.ProjectileShootSounds.Length <= 0) { delete this.ProjectileShootSounds; } + + if (this.AttackBeginSounds != null && this.AttackBeginSounds.Length <= 0) + { + delete this.AttackBeginSounds; + } + + if (this.AttackEndSounds != null && this.AttackEndSounds.Length <= 0) + { + delete this.AttackEndSounds; + } + + if (this.AttackLoopSounds != null && this.AttackLoopSounds.Length <= 0) + { + delete this.AttackLoopSounds; + } + + if (this.Rages != null) + { + SF2ChaserRageInfo rageData; + + for (int i = 0; i < this.Rages.Length; i++) + { + this.Rages.GetArray(i, rageData, sizeof(rageData)); + + rageData.PostLoad(); + } + } + + if (this.Attacks != null) + { + SF2ChaserBossProfileAttackData attackData; + + for (int i = 0; i < this.Attacks.Length; i++) + { + this.Attacks.GetArray(i, attackData, sizeof(attackData)); + + if (attackData.Type == SF2BossAttackType_Invalid) + { + continue; + } + + attackData.PostLoad(); + } + } } void SortSoundSections(KeyValues kv, const char[] sectionName, bool checkFile) @@ -1070,25 +2981,41 @@ enum struct SF2ChaserBossProfileData { this.AttackKilledClientSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageAll]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Stun]) == 0) + { + this.StunnedSounds.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Death]) == 0) { - this.RageAllSounds.Load(kv, checkFile); + this.DeathSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageTwo]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_TauntKill]) == 0) { - this.RageTwoSounds.Load(kv, checkFile); + this.TauntKillSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageThree]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Smell]) == 0) { - this.RageThreeSounds.Load(kv, checkFile); + this.SmellSounds.Load(kv, checkFile); } else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_SelfHeal]) == 0) { this.SelfHealSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Stun]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageAll]) == 0) { - this.StunnedSounds.Load(kv, checkFile); + this.RageSounds1.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageTwo]) == 0) + { + this.RageSounds2.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageThree]) == 0) + { + this.RageSounds3.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Despawn]) == 0) + { + this.DespawnSounds.Load(kv, checkFile); } else if (strcmp(sectionName, "sound_footsteps") == 0) { @@ -1112,6 +3039,153 @@ enum struct SF2ChaserBossProfileData } } + void LoadNestedSoundSections(const char[] section, KeyValues kv, bool checkFile, ArrayList &list, bool soundHook = false, SF2ChaserBossProfileData data) + { + list = new ArrayList(sizeof(SF2BossProfileSoundInfo)); + bool classic = false; + kv.GoBack(); + for (int i = 0; i < data.Attacks.Length; i++) + { + char formatter[90]; + SF2ChaserBossProfileAttackData attackData; + data.Attacks.GetArray(i, attackData, sizeof(attackData)); + if (strcmp(attackData.Name, "1") == 0) + { + continue; + } + FormatEx(formatter, sizeof(formatter), "%s_%s", section, attackData.Name); + + if (kv.JumpToKey(formatter)) + { + classic = true; + kv.GoBack(); + break; + } + } + if (classic) + { + for (int i = 0; i < data.Attacks.Length; i++) + { + char formatter[90]; + SF2ChaserBossProfileAttackData attackData; + data.Attacks.GetArray(i, attackData, sizeof(attackData)); + if (strcmp(attackData.Name, "1") == 0) + { + FormatEx(formatter, sizeof(formatter), "%s", section); + } + else + { + FormatEx(formatter, sizeof(formatter), "%s_%s", section, attackData.Name); + } + + if (kv.JumpToKey(formatter)) + { + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + strcopy(soundInfo.SectionName, sizeof(soundInfo.SectionName), attackData.Name); + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } + kv.GoBack(); + } + } + return; + } + else + { + kv.JumpToKey(section); + } + if (kv.GotoFirstSubKey()) + { + char sectionName[64]; + kv.GetSectionName(sectionName, sizeof(sectionName)); + if (strcmp(sectionName, "paths") == 0) + { + kv.GoBack(); + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + soundInfo.SectionName[0] = '\0'; + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } + } + else + { + do + { + kv.GetSectionName(sectionName, sizeof(sectionName)); + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.SectionName = sectionName; + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + + } + else + { + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + soundInfo.SectionName[0] = '\0'; + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } + } + kv.GoBack(); + } + + void DestroyNestedSection(ArrayList list) + { + if (list != null) + { + SF2BossProfileSoundInfo soundInfo; + + for (int i = 0; i < list.Length; i++) + { + list.GetArray(i, soundInfo, sizeof(soundInfo)); + + if (soundInfo.Paths == null) + { + continue; + } + soundInfo.Destroy(); + } + delete list; + } + } + void Destroy() { this.IdleSounds.Destroy(); @@ -1121,17 +3195,27 @@ enum struct SF2ChaserBossProfileData this.AttackKilledSounds.Destroy(); this.AttackKilledAllSounds.Destroy(); this.AttackKilledClientSounds.Destroy(); - this.RageAllSounds.Destroy(); - this.RageTwoSounds.Destroy(); - this.RageThreeSounds.Destroy(); - this.SelfHealSounds.Destroy(); this.StunnedSounds.Destroy(); + this.DeathSounds.Destroy(); + this.TauntKillSounds.Destroy(); + this.SmellSounds.Destroy(); + this.SelfHealSounds.Destroy(); + this.RageSounds1.Destroy(); + this.RageSounds2.Destroy(); + this.RageSounds3.Destroy(); + this.DespawnSounds.Destroy(); this.FootstepSounds.Destroy(); this.ChaseMusics.Destroy(); this.ChaseVisibleMusics.Destroy(); this.AlertMusics.Destroy(); this.IdleMusics.Destroy(); + + this.CloakData.Destroy(); + + this.StunData.Destroy(); + this.DeathData.Destroy(); + if (this.Attacks != null) { SF2ChaserBossProfileAttackData attackData; @@ -1144,99 +3228,134 @@ enum struct SF2ChaserBossProfileData { continue; } - attackData.Destroy(); } delete this.Attacks; } + if (this.ProjectilePosOffsets != null) { delete this.ProjectilePosOffsets; } + if (this.AttackPropModels != null) { delete this.AttackPropModels; } - if (this.AttackSounds != null) + + if (this.DamageResistances != null) { - SF2BossProfileSoundInfo soundInfo; + SF2ChaserBossProfileResistanceData resistanceData; - for (int i = 0; i < this.AttackSounds.Length; i++) + for (int i = 0; i < this.DamageResistances.Length; i++) { - this.AttackSounds.GetArray(i, soundInfo, sizeof(soundInfo)); + this.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); + resistanceData.Destroy(); } + + delete this.DamageResistances; } - if (this.HitSounds != null) + + this.DestroyNestedSection(this.AttackSounds); + this.DestroyNestedSection(this.HitSounds); + this.DestroyNestedSection(this.MissSounds); + this.DestroyNestedSection(this.BulletShootSounds); + this.DestroyNestedSection(this.ProjectileShootSounds); + this.DestroyNestedSection(this.AttackBeginSounds); + this.DestroyNestedSection(this.AttackEndSounds); + this.DestroyNestedSection(this.AttackLoopSounds); + + if (this.Rages != null) { - SF2BossProfileSoundInfo soundInfo; + SF2ChaserRageInfo rageData; - for (int i = 0; i < this.HitSounds.Length; i++) + for (int i = 0; i < this.Rages.Length; i++) { - this.HitSounds.GetArray(i, soundInfo, sizeof(soundInfo)); + this.Rages.GetArray(i, rageData, sizeof(rageData)); - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); + rageData.Destroy(); } + + delete this.Rages; } - if (this.MissSounds != null) + + if (this.Postures != null) { - SF2BossProfileSoundInfo soundInfo; + StringMapSnapshot snapshot = this.Postures.Snapshot(); + SF2ChaserBossProfilePostureInfo posture; - for (int i = 0; i < this.MissSounds.Length; i++) + char postureName[64]; + for (int i = 0; i < snapshot.Length; i++) { - this.MissSounds.GetArray(i, soundInfo, sizeof(soundInfo)); - - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); + snapshot.GetKey(i, postureName, sizeof(postureName)); + this.Postures.GetArray(postureName, posture, sizeof(posture)); + posture.Destroy(); } + + delete snapshot; + delete this.Postures; } - if (this.BulletShootSounds != null) + + } + + bool GetAttackFromIndex(int attackIndex, SF2ChaserBossProfileAttackData data) + { + if (this.Attacks == null || this.Attacks.Length <= attackIndex) { - SF2BossProfileSoundInfo soundInfo; + return false; + } + this.Attacks.GetArray(attackIndex, data, sizeof(data)); + return true; + } - for (int i = 0; i < this.BulletShootSounds.Length; i++) + bool GetAttack(const char[] attackName, SF2ChaserBossProfileAttackData data) + { + if (this.Attacks == null || this.Attacks.Length == 0) + { + return false; + } + for (int i = 0; i < this.Attacks.Length; i++) + { + SF2ChaserBossProfileAttackData tempData; + this.Attacks.GetArray(i, tempData, sizeof(tempData)); + if (strcmp(attackName, tempData.Name) == 0) { - this.BulletShootSounds.GetArray(i, soundInfo, sizeof(soundInfo)); - - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); + data = tempData; + return true; } } - if (this.ProjectileShootSounds != null) + return false; + } + + bool GetPosture(const char[] posture, SF2ChaserBossProfilePostureInfo postureInfo) + { + if (this.Postures == null) { - SF2BossProfileSoundInfo soundInfo; + return false; + } - for (int i = 0; i < this.ProjectileShootSounds.Length; i++) - { - this.ProjectileShootSounds.GetArray(i, soundInfo, sizeof(soundInfo)); + return this.Postures.GetArray(posture, postureInfo, sizeof(postureInfo)); + } - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); - } + bool GetPostureFromIndex(int index, SF2ChaserBossProfilePostureInfo postureInfo) + { + if (this.Postures == null) + { + return false; } - if (this.DamageResistances != null) + StringMapSnapshot snapshot = this.Postures.Snapshot(); + if (index >= snapshot.Length) { - delete this.DamageResistances; + delete snapshot; + return false; } + char postureName[64]; + snapshot.GetKey(index, postureName, sizeof(postureName)); + bool value = this.Postures.GetArray(postureName, postureInfo, sizeof(postureInfo)); + delete snapshot; + return value; } } diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc index 976733f5..08cca9af 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc @@ -5,6 +5,7 @@ enum struct SF2StatueBossProfileData ArrayList ModelsCloseDist; float ChaseDuration[Difficulty_Max]; + float ChaseDurationAddMaxRange[Difficulty_Max]; float ChaseDurationAddVisibilityMin[Difficulty_Max]; float ChaseDurationAddVisibilityMax[Difficulty_Max]; @@ -20,15 +21,17 @@ enum struct SF2StatueBossProfileData this.ModelsCloseDist = null; this.MoveSounds.Init(); + this.MoveSounds.Flags = SND_CHANGEVOL; this.SingleMoveSounds.Init(); for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - this.ChaseDuration[difficulty] = difficulty > 0 ? this.ChaseDuration[difficulty - 1] : 5.0; - this.ChaseDurationAddVisibilityMin[difficulty] = difficulty > 0 ? this.ChaseDurationAddVisibilityMin[difficulty - 1] : 0.025; - this.ChaseDurationAddVisibilityMax[difficulty] = difficulty > 0 ? this.ChaseDurationAddVisibilityMax[difficulty - 1] : 0.15; - this.ModelChangeDistanceMax[difficulty] = difficulty > 0 ? this.ModelChangeDistanceMax[difficulty - 1] : 1024.0; + this.ChaseDuration[difficulty] = 5.0; + this.ChaseDurationAddMaxRange[difficulty] = 1024.0; + this.ChaseDurationAddVisibilityMin[difficulty] = 0.025; + this.ChaseDurationAddVisibilityMax[difficulty] = 0.15; + this.ModelChangeDistanceMax[difficulty] = 1024.0; - this.IdleLifeTime[difficulty] = difficulty > 0 ? this.IdleLifeTime[difficulty - 1] : 3.0; + this.IdleLifeTime[difficulty] = 3.0; } } diff --git a/addons/sourcemod/scripting/include/sf2/stocks.inc b/addons/sourcemod/scripting/include/sf2/stocks.inc new file mode 100644 index 00000000..ffe87262 --- /dev/null +++ b/addons/sourcemod/scripting/include/sf2/stocks.inc @@ -0,0 +1,131 @@ +#pragma semicolon 1 + +bool DispatchParticleEffect(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], + int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) +{ + char particleReal[PLATFORM_MAX_PATH]; + FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); + if (particle[0] != '\0') + { + int tblidx = FindStringTable("ParticleEffectNames"); + if (tblidx == INVALID_STRING_TABLE) + { + LogError("Could not find string table: ParticleEffectNames"); + return false; + } + char tmp[256]; + int count = GetStringTableNumStrings(tblidx); + int stridx = INVALID_STRING_INDEX; + for (int i = 0; i < count; i++) + { + ReadStringTable(tblidx, i, tmp, sizeof(tmp)); + if (strcmp(tmp, particleReal, false) == 0) + { + stridx = i; + break; + } + } + if (stridx == INVALID_STRING_INDEX) + { + LogError("Could not find particle: %s", particleReal); + return false; + } + + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", startPos[0]); + TE_WriteFloat("m_vecOrigin[1]", startPos[1]); + TE_WriteFloat("m_vecOrigin[2]", startPos[2]); + TE_WriteVector("m_vecAngles", angles); + TE_WriteNum("m_iParticleSystemIndex", stridx); + TE_WriteNum("entindex", entity); + TE_WriteNum("m_iAttachType", view_as(attachType)); + TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); + TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); + TE_WriteNum("m_bControlPoint1", 0); + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 0); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); + TE_SendToAll(); + } + else + { + return false; + } + return true; +} + +bool DispatchParticleEffectBeam(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], + int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) +{ + char particleReal[PLATFORM_MAX_PATH]; + FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); + if (particle[0] != '\0') + { + int tblidx = FindStringTable("ParticleEffectNames"); + if (tblidx == INVALID_STRING_TABLE) + { + LogError("Could not find string table: ParticleEffectNames"); + return false; + } + char tmp[256]; + int count = GetStringTableNumStrings(tblidx); + int stridx = INVALID_STRING_INDEX; + for (int i = 0; i < count; i++) + { + ReadStringTable(tblidx, i, tmp, sizeof(tmp)); + if (strcmp(tmp, particleReal, false) == 0) + { + stridx = i; + break; + } + } + if (stridx == INVALID_STRING_INDEX) + { + LogError("Could not find particle: %s", particleReal); + return false; + } + + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", startPos[0]); + TE_WriteFloat("m_vecOrigin[1]", startPos[1]); + TE_WriteFloat("m_vecOrigin[2]", startPos[2]); + TE_WriteVector("m_vecAngles", angles); + TE_WriteNum("m_iParticleSystemIndex", stridx); + TE_WriteNum("entindex", entity); + TE_WriteNum("m_iAttachType", view_as(attachType)); + TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); + TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); + TE_WriteNum("m_bControlPoint1", 1); + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); + TE_SendToAll(); + } + else + { + //LogError("There is no valid particle to use for effects."); + return false; + } + return true; +} + +float AngleNormalize(float angle) +{ + while (angle > 180.0) + { + angle -= 360.0; + } + while (angle < -180.0) + { + angle += 360.0; + } + return angle; +} + +float AngleDiff(float firstAngle, float secondAngle) +{ + float diff = secondAngle - firstAngle; + return AngleNormalize(diff); +} diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index ce6993d1..86c84ce4 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include #pragma semicolon 1 @@ -42,36 +45,17 @@ bool steamworks; #define TFTeam_Blue 3 #define TFTeam_Boss 5 -#define MAXTF2PLAYERS 36 +#define MAXTF2PLAYERS 101 public Plugin myinfo = { - name = "Slender Fortress", + name = "Slender Fortress Modified", author = "KitRifty, Kenzzer, Mentrillum, The Gaben", description = "Based on the game Slender: The Eight Pages.", version = PLUGIN_VERSION, url = "https://discord.gg/7Zz7RYTCC4" } -char g_SlenderAnimationsList[SF2BossAnimation_MaxAnimations][] = -{ - "idle", - "walk", - "walkalert", - "attack", - "shoot", - "run", - "stun", - "chaseinitial", - "rage", - "spawn", - "fleestart", - "heal", - "deathcam", - "crawlwalk", - "crawlrun" -}; - enum struct MuteMode { int MuteMode_Normal; @@ -95,27 +79,28 @@ enum struct FlashlightTemperature char g_SoundNightmareMode[][] = { - "ambient/halloween/thunder_04.wav", - "ambient/halloween/thunder_05.wav", - "ambient/halloween/thunder_08.wav", - "ambient/halloween/mysterious_perc_09.wav", - "ambient/halloween/mysterious_perc_09.wav", - "ambient/halloween/windgust_08.wav" + "#ambient/halloween/thunder_04.wav", + "#ambient/halloween/thunder_05.wav", + "#ambient/halloween/thunder_08.wav", + "#ambient/halloween/mysterious_perc_09.wav", + "#ambient/halloween/mysterious_perc_09.wav", + "#ambient/halloween/windgust_08.wav" }; static const char g_PageCollectDuckSounds[][] = { - "ambient/bumper_car_quack1.wav", - "ambient/bumper_car_quack2.wav", - "ambient/bumper_car_quack3.wav", - "ambient/bumper_car_quack4.wav", - "ambient/bumper_car_quack5.wav", - "ambient/bumper_car_quack9.wav", - "ambient/bumper_car_quack11.wav" + ")ambient/bumper_car_quack1.wav", + ")ambient/bumper_car_quack2.wav", + ")ambient/bumper_car_quack3.wav", + ")ambient/bumper_car_quack4.wav", + ")ambient/bumper_car_quack5.wav", + ")ambient/bumper_car_quack9.wav", + ")ambient/bumper_car_quack11.wav" }; -//Update -bool g_SeeUpdateMenu[MAXTF2PLAYERS] = { false, ... }; +bool g_ClientInGame[MAXTF2PLAYERS] = { false, ... }; +bool g_ClientInCondition[MAXTF2PLAYERS][TFCond_PowerupModeDominant + view_as(2)]; + //Command bool g_PlayerNoPoints[MAXTF2PLAYERS] = { false, ... }; bool g_AdminNoPoints[MAXTF2PLAYERS] = { false, ... }; @@ -161,21 +146,10 @@ Handle g_SlenderEntityThink[MAX_BOSSES]; Handle g_SlenderFakeTimer[MAX_BOSSES]; Handle g_SlenderDeathCamTimer[MAX_BOSSES]; int g_SlenderDeathCamTarget[MAX_BOSSES]; -float g_SlenderLastKill[MAX_BOSSES]; -int g_SlenderState[MAX_BOSSES]; -int g_SlenderTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -bool g_SlenderTargetIsVisible[MAX_BOSSES] = { false, ... }; -bool g_SlenderSpawning[MAX_BOSSES] = { false, ... }; -float g_SlenderAcceleration[MAX_BOSSES][Difficulty_Max]; -float g_SlenderGoalPos[MAX_BOSSES][3]; float g_SlenderStaticRadius[MAX_BOSSES][Difficulty_Max]; float g_SlenderStaticRate[MAX_BOSSES][Difficulty_Max]; float g_SlenderStaticRateDecay[MAX_BOSSES][Difficulty_Max]; float g_SlenderStaticGraceTime[MAX_BOSSES][Difficulty_Max]; -float g_SlenderChaseDeathPosition[MAX_BOSSES][3]; -bool g_SlenderChaseDeathPositionBool[MAX_BOSSES]; -bool g_SlenderHasAutoChaseEnabled[MAX_BOSSES]; -bool g_SlenderChasesEndlessly[MAX_BOSSES] = { false, ... }; bool g_SlenderAddCompanionsOnDifficulty[MAX_BOSSES] = { false, ... }; float g_SlenderStatueIdleLifeTime[MAX_BOSSES]; @@ -191,75 +165,14 @@ float g_SlenderDeathCamOverlayTimeStart[MAX_BOSSES]; float g_SlenderDeathCamTime[MAX_BOSSES]; //The Gaben's stuff -bool g_SlenderHasBurnKillEffect[MAX_BOSSES]; -bool g_SlenderHasCloakKillEffect[MAX_BOSSES]; -bool g_SlenderHasDecapKillEffect[MAX_BOSSES]; -bool g_SlenderHasGibKillEffect[MAX_BOSSES]; -bool g_SlenderHasGoldKillEffect[MAX_BOSSES]; -bool g_SlenderHasIceKillEffect[MAX_BOSSES]; -bool g_SlenderHasElectrocuteKillEffect[MAX_BOSSES]; -bool g_SlenderHasAshKillEffect[MAX_BOSSES]; -bool g_SlenderHasDeleteKillEffect[MAX_BOSSES]; -bool g_SlenderHasPushRagdollOnKill[MAX_BOSSES]; -bool g_SlenderHasDissolveRagdollOnKill[MAX_BOSSES]; -int g_SlenderDissolveRagdollType[MAX_BOSSES]; -bool g_SlenderHasPlasmaRagdollOnKill[MAX_BOSSES]; -bool g_SlenderHasResizeRagdollOnKill[MAX_BOSSES]; -float g_SlenderResizeRagdollHands[MAX_BOSSES]; -float g_SlenderResizeRagdollHead[MAX_BOSSES]; -float g_SlenderResizeRagdollTorso[MAX_BOSSES]; bool g_SlenderCustomOutroSong[MAX_BOSSES]; -bool g_SlenderHasDecapOrGibKillEffect[MAX_BOSSES]; -bool g_SlenderHasSilentKill[MAX_BOSSES]; -bool g_SlenderHasMultiKillEffect[MAX_BOSSES]; -bool g_SlenderPlayerCustomDeathFlag[MAX_BOSSES]; -int g_SlenderPlayerSetDeathFlag[MAX_BOSSES]; bool g_SlenderUseCustomOutlines[MAX_BOSSES]; -int g_SlenderOutlineColorR[MAX_BOSSES]; -int g_SlenderOutlineColorG[MAX_BOSSES]; -int g_SlenderOutlineColorB[MAX_BOSSES]; -int g_SlenderOutlineTransparency[MAX_BOSSES]; bool g_SlenderUseRainbowOutline[MAX_BOSSES]; -float g_SlenderRainbowCycleRate[MAX_BOSSES]; -int g_ProjectileFlags[2049] = { 0, ... }; int g_TrapEntityCount; float g_RoundTimeMessage = 0.0; -char g_SlenderCloakOnSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderCloakOffSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderJarateHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderMilkHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderGasHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderStunHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderFireballExplodeSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderFireballShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderFireballTrail[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderIceballImpactSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderIceballTrail[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketExplodeSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketModel[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketTrailParticle[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketExplodeParticle[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderGrenadeShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderSentryRocketShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderArrowShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderManglerShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderBaseballShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderEngineSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderShockwaveBeamSprite[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderShockwaveHaloSprite[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderSmiteSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapModel[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapDeploySound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapMissSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapAnimIdle[MAX_BOSSES][65]; -char g_SlenderTrapAnimOpen[MAX_BOSSES][65]; -char g_SlenderTrapAnimClose[MAX_BOSSES][65]; - int g_SlenderTeleportTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; int g_SlenderProxyTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; bool g_SlenderTeleportTargetIsCamping[MAX_BOSSES] = { false, ... }; @@ -274,8 +187,6 @@ float g_SlenderTeleportPlayersRestTime[MAX_BOSSES][MAXTF2PLAYERS]; bool g_SlenderTeleportIgnoreChases[MAX_BOSSES]; bool g_SlenderTeleportIgnoreVis[MAX_BOSSES]; -bool g_SlenderInDeathcam[MAX_BOSSES] = { false, ... }; - bool g_SlenderProxiesAllowNormalVoices[MAX_BOSSES]; int g_SlenderBoxingBossCount = 0; @@ -288,75 +199,14 @@ Handle g_OnGameFrameTimer = null; // For boss type 2 // General variables PathFollower g_BossPathFollower[MAX_BOSSES]; -bool g_IsSlenderAttacking[MAX_BOSSES]; -bool g_SlenderGiveUp[MAX_BOSSES]; -Handle g_SlenderAttackTimer[MAX_BOSSES]; -Handle g_SlenderLaserTimer[MAX_BOSSES]; -Handle g_SlenderBackupAtkTimer[MAX_BOSSES]; -Handle g_SlenderChaseInitialTimer[MAX_BOSSES]; -Handle g_SlenderRage1Timer[MAX_BOSSES]; -Handle g_SlenderRage2Timer[MAX_BOSSES]; -Handle g_SlenderRage3Timer[MAX_BOSSES]; -Handle g_SlenderSpawnTimer[MAX_BOSSES]; -Handle g_SlenderHealTimer[MAX_BOSSES]; -Handle g_SlenderHealDelayTimer[MAX_BOSSES]; -Handle g_SlenderHealEventTimer[MAX_BOSSES]; -Handle g_SlenderStartFleeTimer[MAX_BOSSES]; - -int g_SlenderInterruptConditions[MAX_BOSSES]; -float g_SlenderLastFoundPlayer[MAX_BOSSES][MAXTF2PLAYERS]; -float g_SlenderLastFoundPlayerPos[MAX_BOSSES][MAXTF2PLAYERS][3]; -float g_SlenderNextPathTime[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderLastCalculPathTime[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderCalculatedWalkSpeed[MAX_BOSSES]; -float g_SlenderCalculatedSpeed[MAX_BOSSES]; -float g_SlenderCalculatedAcceleration[MAX_BOSSES]; -float g_SlenderCalculatedMaxWalkSpeed[MAX_BOSSES]; -float g_SlenderCalculatedMaxSpeed[MAX_BOSSES]; -float g_SlenderSpeedMultiplier[MAX_BOSSES]; -float g_SlenderTimeUntilNoPersistence[MAX_BOSSES]; -int g_SlenderTauntAlertCount[MAX_BOSSES]; float g_SlenderProxyTeleportMinRange[MAX_BOSSES][Difficulty_Max]; float g_SlenderProxyTeleportMaxRange[MAX_BOSSES][Difficulty_Max]; -// Sound variables -float g_SlenderTargetSoundLastTime[MAX_BOSSES] = { -1.0, ... }; -SoundType g_SlenderTargetSoundType[MAX_BOSSES] = { SoundType_None, ... }; -float g_SlenderTargetSoundMasterPos[MAX_BOSSES][3]; // to determine hearing focus -float g_SlenderTargetSoundTempPos[MAX_BOSSES][3]; -float g_SlenderTargetSoundDiscardMasterPosTime[MAX_BOSSES]; -bool g_SlenderInvestigatingSound[MAX_BOSSES]; -int g_SlenderTargetSoundCount[MAX_BOSSES]; -int g_SlenderAutoChaseCount[MAX_BOSSES]; -float g_SlenderAutoChaseCooldown[MAX_BOSSES]; -float g_SlenderSoundPositionSetCooldown[MAX_BOSSES]; -int g_SlenderSoundTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -int g_SlenderSeeTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -bool g_SlenderIsAutoChasingLoudPlayer[MAX_BOSSES]; -float g_SlenderLastHeardVoice[MAX_BOSSES]; -float g_SlenderLastHeardFootstep[MAX_BOSSES]; -float g_SlenderLastHeardWeapon[MAX_BOSSES]; - -float g_SlenderNextStunTime[MAX_BOSSES] = { -1.0, ... }; float g_SlenderNextJumpScare[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextVoiceSound[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextMoanSound[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextWanderPos[MAX_BOSSES][Difficulty_Max]; -float g_SlenderNextCloakTime[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextTrapPlacement[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextFootstepSound[MAX_BOSSES] = { -1.0, ... }; - -float g_SlenderFootstepTime[MAX_BOSSES]; -float g_SlenderAnimationDuration[MAX_BOSSES]; - -float g_SlenderTimeUntilRecover[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilAlert[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilIdle[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilChase[MAX_BOSSES] = { -1.0, ... }; + float g_SlenderTimeUntilKill[MAX_BOSSES] = { -1.0, ... }; float g_SlenderTimeUntilNextProxy[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilAttackEnd[MAX_BOSSES] = { -1.0, ... }; float g_SlenderProxyDamageVsEnemy[MAX_BOSSES][Difficulty_Max]; float g_SlenderProxyDamageVsBackstab[MAX_BOSSES][Difficulty_Max]; @@ -366,9 +216,7 @@ int g_SlenderProxyControlGainHitByEnemy[MAX_BOSSES][Difficulty_Max]; float g_SlenderProxyControlDrainRate[MAX_BOSSES][Difficulty_Max]; int g_SlenderMaxProxies[MAX_BOSSES][Difficulty_Max]; -bool g_SlenderInBacon[MAX_BOSSES]; - -int g_NightvisionType = 0; +int g_NightVisionType = 0; //Healthbar int g_HealthBar; @@ -379,9 +227,12 @@ enum struct SF2PageEntityData int EntRef; char CollectSound[PLATFORM_MAX_PATH]; int CollectSoundPitch; + float Pos[3]; + float Ang[3]; } ArrayList g_Pages; +ArrayList g_EmptySpawnPagePoints; int g_PageCount; int g_PageMax; bool g_PageRef; @@ -390,8 +241,6 @@ float g_PageRefModelScale; static Handle g_PlayerIntroMusicTimer[MAXTF2PLAYERS] = { null, ... }; -// Seeing Mr. Slendy data. - float g_LastVisibilityProcess[MAXTF2PLAYERS]; bool g_PlayerSeesSlender[MAXTF2PLAYERS][MAX_BOSSES]; float g_PlayerSeesSlenderLastTime[MAXTF2PLAYERS][MAX_BOSSES]; @@ -402,21 +251,7 @@ float g_PlayerScareLastTime[MAXTF2PLAYERS][MAX_BOSSES]; float g_PlayerScareNextTime[MAXTF2PLAYERS][MAX_BOSSES]; float g_PlayerStaticAmount[MAXTF2PLAYERS]; -int g_NpcPlayerScareVictin[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -bool g_NpcChasingScareVictin[MAX_BOSSES]; -bool g_NpcLostChasingScareVictim[MAX_BOSSES]; -bool g_PlayerScaredByBoss[MAXTF2PLAYERS][MAX_BOSSES]; - -bool g_NpcVelocityCancel[MAX_BOSSES]; - //Boxing data -Handle g_SlenderBurnTimer[MAX_BOSSES]; -Handle g_SlenderBleedTimer[MAX_BOSSES]; -Handle g_SlenderMarkedTimer[MAX_BOSSES]; -float g_SlenderStopBurningTimer[MAX_BOSSES]; -float g_SlenderStopBleedingTimer[MAX_BOSSES]; -bool g_SlenderIsBurning[MAX_BOSSES]; //This is for the Sun-on-a-Stick -bool g_SlenderIsMarked[MAX_BOSSES]; //For mini-crits and Bushwacka int g_PlayerHitsToCrits[MAXTF2PLAYERS]; int g_PlayerHitsToHeads[MAXTF2PLAYERS]; @@ -427,32 +262,15 @@ bool g_PlayerIn1UpCondition[MAXTF2PLAYERS]; bool g_PlayerDied1Up[MAXTF2PLAYERS]; bool g_PlayerFullyDied1Up[MAXTF2PLAYERS]; -float g_PlayerLastChaseBossEncounterTime[MAXTF2PLAYERS][MAX_BOSSES]; - -// Player static data. -int g_PlayerStaticMode[MAXTF2PLAYERS][MAX_BOSSES]; -float g_PlayerStaticIncreaseRate[MAXTF2PLAYERS]; -float g_PlayerStaticDecreaseRate[MAXTF2PLAYERS]; -Handle g_PlayerStaticTimer[MAXTF2PLAYERS]; -int g_PlayerStaticMaster[MAXTF2PLAYERS] = { -1, ... }; -char g_PlayerStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -char g_PlayerLastStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -float g_PlayerLastStaticTime[MAXTF2PLAYERS]; -float g_PlayerLastStaticVolume[MAXTF2PLAYERS]; -Handle g_PlayerLastStaticTimer[MAXTF2PLAYERS]; - -// Static shake data. -int g_PlayerStaticShakeMaster[MAXTF2PLAYERS]; -bool g_PlayerInStaticShake[MAXTF2PLAYERS]; -char g_PlayerStaticShakeSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -float g_PlayerStaticShakeMinVolume[MAXTF2PLAYERS]; -float g_PlayerStaticShakeMaxVolume[MAXTF2PLAYERS]; - float g_PlayerProxyNextVoiceSound[MAXTF2PLAYERS]; bool g_PlayerTrapped[MAXTF2PLAYERS]; int g_PlayerTrapCount[MAXTF2PLAYERS]; +bool g_PlayerLatchedByTongue[MAXTF2PLAYERS]; +int g_PlayerLatchCount[MAXTF2PLAYERS]; +int g_PlayerLatcher[MAXTF2PLAYERS]; + int g_PlayerBossKillSubject[MAXTF2PLAYERS]; // Difficulty @@ -479,6 +297,7 @@ enum struct PlayerPreferences int PlayerPreference_GhostModeToggleState; //0 = Nothing, 1 = Ghost on grace end, 2 = Ghost on death int PlayerPreference_GhostModeTeleportState; //0 = Players, 1 = Bosses + float PlayerPreference_MusicVolume; } PlayerPreferences g_PlayerPreferences[MAXTF2PLAYERS]; @@ -534,8 +353,6 @@ int g_PlayerProxyAskSpawnPoint[MAXTF2PLAYERS] = { -1, ... }; int g_PlayerDesiredFOV[MAXTF2PLAYERS]; Handle g_PlayerPostWeaponsTimer[MAXTF2PLAYERS] = { null, ... }; -float g_PlayerIgniteDurationEffect[MAXTF2PLAYERS] = { 10.0, ... }; -Handle g_PlayerIgniteTimer[MAXTF2PLAYERS] = { null, ... }; Handle g_PlayerPageRewardCycleTimer[MAXTF2PLAYERS] = { null, ... }; static float g_PlayerPageRewardCycleCooldown[MAXTF2PLAYERS] = { 0.0, ... }; static int g_PlayerPageRewardCycleCount[MAXTF2PLAYERS] = { 0, ... }; @@ -663,13 +480,6 @@ ConVar g_GraceTimeConVar; ConVar g_AllChatConVar; ConVar g_MaxPlayersConVar; ConVar g_MaxPlayersOverrideConVar; -ConVar g_CampingEnabledConVar; -ConVar g_CampingMaxStrikesConVar; -ConVar g_CampingStrikesWarnConVar; -ConVar g_ExitCampingTimeAllowedConVar; -ConVar g_CampingMinDistanceConVar; -ConVar g_CampingNoStrikeSanityConVar; -ConVar g_CampingNoStrikeBossDistanceConVar; ConVar g_DifficultyConVar; ConVar g_CameraOverlayConVar; ConVar g_OverlayNoGrainConVar; @@ -725,6 +535,8 @@ ConVar g_RenevantMaxWaves; ConVar g_SlaughterRunMapConVar; ConVar g_TimeEscapeSurvivalConVar; ConVar g_SlaughterRunDivisibleTimeConVar; +ConVar g_SlaughterRunDefaultClassRunSpeedConVar; +ConVar g_SlaughterRunMinimumBossRunSpeedConVar; ConVar g_UseAlternateConfigDirectoryConVar; ConVar g_PlayerKeepWeaponsConVar; ConVar g_FullyEnableSpectatorConVar; @@ -733,8 +545,15 @@ ConVar g_UsePlayersForKillFeedConVar; ConVar g_DefaultLegacyHudConVar; ConVar g_DifficultyVoteOptionsConVar; ConVar g_DifficultyVoteRandomConVar; +ConVar g_DifficultyVoteRevoteConVar; ConVar g_DifficultyNoGracePageConVar; +ConVar g_HighDifficultyPercentConVar; ConVar g_FileCheckConVar; +ConVar g_LoadOutsideMapsConVar; +ConVar g_DefaultBossTeamConVar; +ConVar g_EngineerBuildInBLUConVar; +ConVar g_DisableTauntLoopsConVar; +ConVar g_BossPreviewWikiConVar; ConVar g_RestartSessionConVar; bool g_RestartSessionEnabled; @@ -743,14 +562,12 @@ ConVar g_PlayerInfiniteSprintOverrideConVar; ConVar g_PlayerInfiniteFlashlightOverrideConVar; ConVar g_PlayerInfiniteBlinkOverrideConVar; -ConVar g_GravityConVar; -float g_Gravity; ConVar g_MaxRoundsConVar; ConVar g_ForcedHolidayConVar; ConVar g_WeaponCriticalsConVar; ConVar g_PhysicsPushScaleConVar; -ConVar g_DragonsFuryBurningBonus; -ConVar g_DragonsFuryBurnDuration; +ConVar g_DragonsFuryBurningBonusConVar; +ConVar g_DragonsFuryBurnDurationConVar; bool g_IsPlayerShakeEnabled; bool g_PlayerViewbobHurtEnabled; @@ -759,32 +576,40 @@ bool g_PlayerViewbobSprintEnabled; Handle g_HudSync; Handle g_HudSync2; Handle g_HudSync3; +Handle g_HudSync4; Handle g_RoundTimerSync; Handle g_Cookie; -int g_SmokeSprite; -int g_LightningSprite; int g_ShockwaveBeam; int g_ShockwaveHalo; +char g_DebugBeamSound[PLATFORM_MAX_PATH]; + +ArrayList g_Buildings; +ArrayList g_WhitelistedEntities; +ArrayList g_BreakableProps; // Global forwards. GlobalForward g_OnBossAddedFwd; GlobalForward g_OnBossSpawnFwd; GlobalForward g_OnBossDespawnFwd; GlobalForward g_OnBossChangeStateFwd; -GlobalForward g_OnBossAnimationUpdateFwd; GlobalForward g_OnBossGetSpeedFwd; GlobalForward g_OnBossGetWalkSpeedFwd; GlobalForward g_OnBossSeeEntityFwd; GlobalForward g_OnBossHearEntityFwd; GlobalForward g_OnBossRemovedFwd; GlobalForward g_OnBossStunnedFwd; +GlobalForward g_OnBossKilledFwd; GlobalForward g_OnBossCloakedFwd; GlobalForward g_OnBossDecloakedFwd; GlobalForward g_OnBossFinishSpawningFwd; GlobalForward g_OnBossPreAttackFwd; GlobalForward g_OnBossAttackedFwd; +GlobalForward g_OnBossPreTakeDamageFwd; +GlobalForward g_OnBossPreFlashlightDamageFwd; +GlobalForward g_OnBossAnimationUpdateFwd; +GlobalForward g_OnChaserBossGetSuspendActionFwd; GlobalForward g_OnPagesSpawnedFwd; GlobalForward g_OnRoundStateChangeFwd; GlobalForward g_OnClientCollectPageFwd; @@ -801,6 +626,7 @@ GlobalForward g_OnClientEscapeFwd; GlobalForward g_OnClientLooksAtBossFwd; GlobalForward g_OnClientLooksAwayFromBossFwd; GlobalForward g_OnClientStartDeathCamFwd; +GlobalForward g_OnClientPreKillDeathCamFwd; GlobalForward g_OnClientEndDeathCamFwd; GlobalForward g_OnClientGetDefaultWalkSpeedFwd; GlobalForward g_OnClientGetDefaultSprintSpeedFwd; @@ -808,19 +634,70 @@ GlobalForward g_OnClientTakeDamageFwd; GlobalForward g_OnClientSpawnedAsProxyFwd; GlobalForward g_OnClientDamagedByBossFwd; GlobalForward g_OnGroupGiveQueuePointsFwd; -GlobalForward g_OnRenevantTriggerWaveFwd; GlobalForward g_OnBossPackVoteStartFwd; GlobalForward g_OnDifficultyChangeFwd; GlobalForward g_OnClientEnterGameFwd; GlobalForward g_OnGroupEnterGameFwd; GlobalForward g_OnEverythingLoadedFwd; +GlobalForward g_OnDifficultyVoteFinishedFwd; +GlobalForward g_OnIsBossCustomAttackPossibleFwd; +GlobalForward g_OnBossGetCustomAttackActionFwd; +GlobalForward g_OnProjectileTouchFwd; +GlobalForward g_OnPreSelectStartingBosses; +GlobalForward g_OnPostSelectStartingBosses; + +// Private forwards +PrivateForward g_OnGamemodeStartPFwd; +PrivateForward g_OnGamemodeEndPFwd; +PrivateForward g_OnMapStartPFwd; +PrivateForward g_OnMapEndPFwd; +PrivateForward g_OnGameFramePFwd; +PrivateForward g_OnRoundStartPFwd; +PrivateForward g_OnRoundEndPFwd; +PrivateForward g_OnEntityCreatedPFwd; +PrivateForward g_OnEntityDestroyedPFwd; +PrivateForward g_OnEntityTeleportedPFwd; +PrivateForward g_OnAdminMenuCreateOptionsPFwd; +PrivateForward g_OnPlayerJumpPFwd; +PrivateForward g_OnPlayerSpawnPFwd; +PrivateForward g_OnPlayerTakeDamagePFwd; +PrivateForward g_OnPlayerTakeDamagePostPFwd; +PrivateForward g_OnPlayerDeathPrePFwd; +PrivateForward g_OnPlayerDeathPFwd; +PrivateForward g_OnPlayerPutInServerPFwd; +PrivateForward g_OnPlayerDisconnectedPFwd; +PrivateForward g_OnPlayerEscapePFwd; +PrivateForward g_OnPlayerTeamPFwd; +PrivateForward g_OnPlayerClassPFwd; +PrivateForward g_OnPlayerLookAtBossPFwd; +PrivateForward g_OnPlayerChangePlayStatePFwd; +PrivateForward g_OnPlayerChangeGhostStatePFwd; +PrivateForward g_OnPlayerChangeProxyStatePFwd; +PrivateForward g_OnPlayerConditionAddedPFwd; +PrivateForward g_OnPlayerConditionRemovedPFwd; +PrivateForward g_OnPlayerTurnOnFlashlightPFwd; +PrivateForward g_OnPlayerTurnOffFlashlightPFwd; +PrivateForward g_OnPlayerFlashlightBreakPFwd; +PrivateForward g_OnPlayerAverageUpdatePFwd; +PrivateForward g_OnSpecialRoundStartPFwd; +PrivateForward g_OnBossSpawnPFwd; +PrivateForward g_OnBossRemovedPFwd; +PrivateForward g_OnChaserGetAttackActionPFwd; +PrivateForward g_OnChaserGetCustomAttackPossibleStatePFwd; +PrivateForward g_OnChaserUpdatePosturePFwd; +PrivateForward g_OnDifficultyChangePFwd; +PrivateForward g_OnDifficultyVoteFinishedPFwd; +PrivateForward g_OnRenevantTriggerWavePFwd; +PrivateForward g_OnWallHaxDebugPFwd; Handle g_SDKGetMaxHealth; Handle g_SDKEquipWearable; Handle g_SDKPlaySpecificSequence; Handle g_SDKPointIsWithin; Handle g_SDKPassesTriggerFilters; -//Handle g_SDKGetSmoothedVelocity; +Handle g_SDKLookupBone; +Handle g_SDKGetBonePosition; +Handle g_SDKSequenceVelocity; Handle g_SDKStartTouch; Handle g_SDKEndTouch; Handle g_SDKWeaponSwitch; @@ -847,6 +724,7 @@ bool g_RenevantBeaconEffect = false; bool g_Renevant90sEffect = false; bool g_RenevantMarkForDeath = false; bool g_RenevantWallHax = false; +bool g_RenevantBossesChaseEndlessly = false; bool g_IsRenevantMap = false; Handle g_RenevantWaveTimer = null; ArrayList g_RenevantWaveList; @@ -860,29 +738,38 @@ stock ArrayList g_FuncNavPrefer; int g_FlashlightHaloModel = -1; +bool g_PagesRevealed = false; +ArrayList g_PageLocations; +ArrayList g_PageLocationsGlow; + #if defined DEBUG #include "sf2/debug.sp" #endif #include "sf2/stocks.sp" #include "sf2/logging.sp" +#include "sf2/glow.sp" #include "sf2/methodmaps.sp" #include "sf2/classconfigs.sp" #include "sf2/profiles.sp" #include "sf2/effects.sp" #include "sf2/playergroups.sp" #include "sf2/mapentities.sp" +#include "sf2/entities/initialize.sp" #include "sf2/menus.sp" #include "sf2/npc.sp" -#include "sf2/pvp.sp" +#include "sf2/old_pvp.sp" +#include "sf2/pve.sp" #include "sf2/client.sp" #include "sf2/specialround.sp" +#include "sf2/anticamping.sp" #include "sf2/adminmenu.sp" #include "sf2/traps.sp" -#include "sf2/extras/renevant_mode.sp" +#include "sf2/gamemodes/renevant.sp" #include "sf2/extras/natives.sp" #include "sf2/extras/commands.sp" #include "sf2/extras/game_events.sp" #include "sf2/extras/afk_mode.sp" +#include "sf2/extras/bosspreview.sp" SF2LogicRenevantEntity g_RenevantLogicEntity = view_as(-1); @@ -925,12 +812,13 @@ public void OnLibraryRemoved(const char[] name) public void OnMapStart() { g_TimerFail = null; - PvP_OnMapStart(); + g_PagesRevealed = false; + g_PageLocations.Clear(); + g_PageLocationsGlow.Clear(); FindHealthBar(); PrecacheSound(SOUND_THUNDER, true); + PrecacheSound(DEBUG_PAGEREVEALSOUND); PrecacheSound("weapons/teleporter_send.wav"); - g_SmokeSprite = PrecacheModel("sprites/steam1.vmt"); - g_LightningSprite = PrecacheModel("sprites/lgtning.vmt"); g_ShockwaveBeam = PrecacheModel("sprites/laser.vmt"); g_ShockwaveHalo = PrecacheModel("sprites/halo01.vmt"); PrecacheModel(LASER_MODEL, true); @@ -941,11 +829,14 @@ public void OnMapStart() PrecacheMaterial2(overlay, true); g_GhostOverlayConVar.GetString(overlay, sizeof(overlay)); PrecacheMaterial2(overlay, true); + g_DebugBeamSound = "buttons/blip1.wav"; + PrecacheSound(g_DebugBeamSound); PrecacheModel(SF2_FLASHLIGHT_BEAM_MATERIAL); g_FlashlightHaloModel = PrecacheModel(SF2_FLASHLIGHT_HALO_MATERIAL, true); - SF2MapEntity_OnMapStart(); + Call_StartForward(g_OnMapStartPFwd); + Call_Finish(); } public void OnConfigsExecuted() @@ -969,6 +860,76 @@ public void OnConfigsExecuted() { LogMessage("%s is not a Slender Fortress map. Plugin disabled!", map); StopPlugin(); + if (g_LoadOutsideMapsConVar.BoolValue) + { + InitializeLogging(); + PrecacheStuff(); + ReloadBossProfiles(); + NPCOnConfigsExecuted(); + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_sentrygun")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_teleporter")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_dispenser")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tank_boss")) != -1) + { + g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_physics")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_dynamic")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_breakable")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i)) + { + continue; + } + g_ClientInGame[i] = true; + Call_StartForward(g_OnPlayerPutInServerPFwd); + Call_PushCell(SF2_BasePlayer(i)); + Call_Finish(); + } + } } } else @@ -979,6 +940,10 @@ public void OnConfigsExecuted() for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) { + if (g_BossPathFollower[bossIndex]) + { + continue; + } g_BossPathFollower[bossIndex] = PathFollower(_, TraceRayDontHitAnyEntity_Pathing, Path_FilterOnlyActors); } } @@ -1040,8 +1005,6 @@ static void StartPlugin() } } - g_Gravity = g_GravityConVar.FloatValue; - g_IsPlayerShakeEnabled = g_PlayerShakeEnabledConVar.BoolValue; g_PlayerViewbobHurtEnabled = g_PlayerViewbobHurtEnabledConVar.BoolValue; g_PlayerViewbobSprintEnabled = g_PlayerViewbobSprintEnabledConVar.BoolValue; @@ -1049,14 +1012,14 @@ static void StartPlugin() #if defined _SteamWorks_Included if (steamworks) { - SteamWorks_SetGameDescription("Slender Fortress ("...PLUGIN_VERSION_DISPLAY...")"); + SteamWorks_SetGameDescription("SF2M Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); steamtools = false; } #endif #if defined _steamtools_included if (steamtools) { - Steam_SetGameDescription("Slender Fortress ("...PLUGIN_VERSION_DISPLAY...")"); + Steam_SetGameDescription("SF2M Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); steamworks = false; } #endif @@ -1116,6 +1079,7 @@ static void StartPlugin() ReloadClassConfigs(); NPCOnConfigsExecuted(); + BossPreview_ReloadPack(); InitializeBossPackVotes(); SetupTimeLimitTimerForBossPackVote(); @@ -1129,6 +1093,60 @@ static void StartPlugin() } OnClientPutInServer(i); } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_sentrygun")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_teleporter")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_dispenser")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tank_boss")) != -1) + { + g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_physics")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_dynamic")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_breakable")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + Call_StartForward(g_OnGamemodeStartPFwd); + Call_Finish(); } static void PrecacheStuff() @@ -1141,9 +1159,9 @@ static void PrecacheStuff() g_Particles[FireworksBLU] = PrecacheParticleSystem(FIREWORKSBLU_PARTICLENAME); g_Particles[TeleportedInBlu] = PrecacheParticleSystem(TELEPORTEDINBLU_PARTICLENAME); - for (int i = 0; i < sizeof(g_SoundNightmareMode) - 1; i++) + for (int i = 0; i < sizeof(g_SoundNightmareMode); i++) { - PrecacheSound2(g_SoundNightmareMode[i], true); + PrecacheSound(g_SoundNightmareMode[i], true); } PrecacheSound("ui/itemcrate_smash_ultrarare_short.wav"); @@ -1152,9 +1170,6 @@ static void PrecacheStuff() PrecacheSound(CRIT_SOUND); PrecacheSound(ZAP_SOUND); PrecacheSound(PAGE_DETECTOR_BEEP); - PrecacheSound(EXPLOSIVEDANCE_EXPLOSION1); - PrecacheSound(EXPLOSIVEDANCE_EXPLOSION2); - PrecacheSound(EXPLOSIVEDANCE_EXPLOSION3); PrecacheSound(SPECIAL1UPSOUND); PrecacheSound("player/spy_shield_break.wav"); @@ -1165,8 +1180,6 @@ static void PrecacheStuff() PrecacheSound(BLEED_ROLL); PrecacheSound(GENERIC_ROLL_TICK); PrecacheSound(LOSE_SPRINT_ROLL); - PrecacheSound(FIREWORK_EXPLOSION); - PrecacheSound(FIREWORK_START); PrecacheSound(MINICRIT_BUFF); PrecacheSound(NULLSOUND); PrecacheSound(JARATE_ROLL); @@ -1193,6 +1206,8 @@ static void PrecacheStuff() PrecacheSound(ICEBALL_IMPACT); PrecacheSound(ROCKET_SHOOT); PrecacheSound(ROCKET_IMPACT); + PrecacheSound(ROCKET_IMPACT2); + PrecacheSound(ROCKET_IMPACT3); PrecacheSound(GRENADE_SHOOT); PrecacheSound(SENTRYROCKET_SHOOT); PrecacheSound(ARROW_SHOOT); @@ -1317,9 +1332,6 @@ static void StopPlugin() g_Enabled = false; - g_RestartSessionEnabled = false; - g_RestartSessionConVar.SetBool(false); - // Reset CVars. ConVar cvar = FindConVar("mp_friendlyfire"); if (cvar != null) @@ -1344,16 +1356,11 @@ static void StopPlugin() NPCStopMusic(); } - // Cleanup bosses. - NPCRemoveAll(); - // Cleanup clients. for (int i = 1; i <= MaxClients; i++) { ClientResetFlashlight(i); ClientDeactivateUltravision(i); - ClientDisableConstantGlow(i); - ClientRemoveInteractiveGlow(i); g_TimerChangeClientName[i] = null; } @@ -1366,13 +1373,8 @@ static void StopPlugin() } } - g_RenevantMultiEffect = false; - g_RenevantBeaconEffect = false; - g_Renevant90sEffect = false; - g_RenevantMarkForDeath = false; - g_RenevantWallHax = false; - - BossProfilesOnMapEnd(); + Call_StartForward(g_OnGamemodeEndPFwd); + Call_Finish(); if (g_FuncNavPrefer != null) { @@ -1384,7 +1386,28 @@ static void StopPlugin() public void OnMapEnd() { + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) + { + return; + } + StopPlugin(); + + g_RestartSessionEnabled = false; + g_RestartSessionConVar.SetBool(false); + + g_RenevantMultiEffect = false; + g_RenevantBeaconEffect = false; + g_Renevant90sEffect = false; + g_RenevantMarkForDeath = false; + g_RenevantBossesChaseEndlessly = false; + g_RenevantWallHax = false; + + NPCRemoveAll(); + BossProfilesOnMapEnd(); + + Call_StartForward(g_OnMapEndPFwd); + Call_Finish(); } public void OnMapTimeLeftChanged() @@ -1397,6 +1420,12 @@ public void OnMapTimeLeftChanged() public void TF2_OnConditionAdded(int client, TFCond cond) { + g_ClientInCondition[client][cond] = true; + + if (!g_Enabled) + { + return; + } SF2_BasePlayer player = SF2_BasePlayer(client); if (cond == TFCond_Taunting) { @@ -1416,7 +1445,8 @@ public void TF2_OnConditionAdded(int client, TFCond cond) player.HandleFlashlight(); } } - if (cond == view_as(82)) + + if (cond == TFCond_HalloweenKart) { if (player.IsProxy) { @@ -1428,6 +1458,26 @@ public void TF2_OnConditionAdded(int client, TFCond cond) player.ChangeCondition(TFCond_SpawnOutline, true); } } + + Call_StartForward(g_OnPlayerConditionAddedPFwd); + Call_PushCell(player); + Call_PushCell(cond); + Call_Finish(); +} + +public void TF2_OnConditionRemoved(int client, TFCond condition) +{ + g_ClientInCondition[client][condition] = false; + + if (!g_Enabled) + { + return; + } + + Call_StartForward(g_OnPlayerConditionRemovedPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(condition); + Call_Finish(); } static Action Timer_GlobalGameFrame(Handle timer) @@ -1460,18 +1510,42 @@ static Action Timer_GlobalGameFrame(Handle timer) { continue; } + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(i)); + if (!chaser.IsValid()) + { + continue; + } + + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.GetProfileData().BoxingBoss) + { + continue; + } + + SF2NPC_BaseNPC baseNPC = view_as(controller); + if (baseNPC.GetProfileData().IsPvEBoss) + { + continue; + } NPCGetBossName(i, boxingBossName, sizeof(boxingBossName)); - int stunHealth = RoundToNearest(NPCChaserGetStunHealth(i)); - if (stunHealth < 0 || NPCGetEntRef(i) == INVALID_ENT_REFERENCE) + float health = float(chaser.GetProp(Prop_Data, "m_iHealth")); + float maxHealth = chaser.MaxHealth; + if (chaser.GetProp(Prop_Data, "m_takedamage") == DAMAGE_EVENTS_ONLY) + { + health = chaser.StunHealth; + maxHealth = chaser.MaxStunHealth; + } + int displayHealth = RoundToFloor(health); + if (displayHealth < 0) { - stunHealth = 0; + displayHealth = 0; } - int stunInitHealth = RoundToNearest(NPCChaserGetStunInitialHealth(i)); - Format(message, sizeof(message), "%s\n%s's current health is %i of %i", message, boxingBossName, stunHealth, stunInitHealth); + int displayMaxHealth = RoundToFloor(maxHealth); + Format(message, sizeof(message), "%s\n%s's current health is %i of %i", message, boxingBossName, displayHealth, displayMaxHealth); } for (int client = 1; client <= MaxClients; client++) { - if (!IsClientInGame(client) || IsFakeClient(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client)) || DidClientEscape(client)) + if (!IsValidClient(client) || IsFakeClient(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client)) || DidClientEscape(client)) { continue; } @@ -1483,206 +1557,207 @@ static Action Timer_GlobalGameFrame(Handle timer) // Check if we can add some proxies. if (IsRoundPlaying() && !SF_IsRenevantMap() && !SF_IsSlaughterRunMap() && !SF_IsBoxingMap()) { - ArrayList proxyCandidates = new ArrayList(); + ArrayList proxyCandidates = new ArrayList(); - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + if (NPCGetUniqueID(bossIndex) == -1) + { + continue; + } + + if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) + { + continue; + } + + if (g_SlenderCopyMaster[bossIndex] != -1) + { + continue; // Copies cannot generate proxies. + } + + if (GetGameTime() < g_SlenderTimeUntilNextProxy[bossIndex]) + { + continue; // Proxy spawning hasn't cooled down yet. + } + + int teleportTarget = EntRefToEntIndex(g_SlenderProxyTarget[bossIndex]); + if (!teleportTarget || teleportTarget == INVALID_ENT_REFERENCE) + { + continue; // No teleport target. + } + + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; + if (g_InProxySurvivalRageMode) { - if (NPCGetUniqueID(bossIndex) == -1) + maxProxies += 5; + } + + int numActiveProxies = 0; + + for (int client = 1; client <= MaxClients; client++) + { + if (!IsValidClient(client) || !g_PlayerEliminated[client]) { continue; } - - if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) + if (!g_PlayerProxy[client]) { continue; } - if (g_SlenderCopyMaster[bossIndex] != -1) + if (NPCGetFromUniqueID(g_PlayerProxyMaster[client]) == bossIndex) { - continue; // Copies cannot generate proxies. + numActiveProxies++; } + } + if (numActiveProxies >= maxProxies) + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d has too many active proxies!", bossIndex); + #endif + continue; + } - if (GetGameTime() < g_SlenderTimeUntilNextProxy[bossIndex]) - { - continue; // Proxy spawning hasn't cooled down yet. - } + float spawnChanceMin = NPCGetProxySpawnChanceMin(bossIndex, difficulty); + float spawnChanceMax = NPCGetProxySpawnChanceMax(bossIndex, difficulty); + float spawnChanceThreshold = NPCGetProxySpawnChanceThreshold(bossIndex, difficulty); - int teleportTarget = EntRefToEntIndex(g_SlenderProxyTarget[bossIndex]); - if (!teleportTarget || teleportTarget == INVALID_ENT_REFERENCE) - { - continue; // No teleport target. - } + float chance = GetRandomFloat(spawnChanceMin, spawnChanceMax); + if (chance > spawnChanceThreshold && !g_InProxySurvivalRageMode) + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d's chances weren't in his favor!", bossIndex); + #endif + continue; + } - int difficulty = GetLocalGlobalDifficulty(bossIndex); + int availableProxies = maxProxies - numActiveProxies; - int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; - if (g_InProxySurvivalRageMode) - { - maxProxies += 5; - } + int spawnNumMin = NPCGetProxySpawnNumMin(bossIndex, difficulty); + int spawnNumMax = NPCGetProxySpawnNumMax(bossIndex, difficulty); - int numActiveProxies = 0; + int spawnNum = 0; - for (int client = 1; client <= MaxClients; client++) - { - if (!IsClientInGame(client) || !g_PlayerEliminated[client]) - { - continue; - } - if (!g_PlayerProxy[client]) - { - continue; - } + // Get a list of people we can transform into a good Proxy. + proxyCandidates.Clear(); - if (NPCGetFromUniqueID(g_PlayerProxyMaster[client]) == bossIndex) - { - numActiveProxies++; - } + for (int client = 1; client <= MaxClients; client++) + { + if (!IsValidClient(client) || !g_PlayerEliminated[client] || GetClientTeam(client) == TFTeam_Red) + { + continue; } - if (numActiveProxies >= maxProxies) + if (g_PlayerProxy[client]) { - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d has too many active proxies!", bossIndex); - //PrintToChatAll("[PROXIES] Boss %d has too many active proxies!", bossIndex); - #endif continue; } - float spawnChanceMin = NPCGetProxySpawnChanceMin(bossIndex, difficulty); - float spawnChanceMax = NPCGetProxySpawnChanceMax(bossIndex, difficulty); - float spawnChanceThreshold = NPCGetProxySpawnChanceThreshold(bossIndex, difficulty); - - float chance = GetRandomFloat(spawnChanceMin, spawnChanceMax); - if (chance > spawnChanceThreshold && !g_InProxySurvivalRageMode) + if (!g_PlayerPreferences[client].PlayerPreference_EnableProxySelection && !IsFakeClient(client)) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d's chances weren't in his favor!", bossIndex); - //PrintToChatAll("[PROXIES] Boss %d's chances weren't in his favor!", bossIndex); + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your preferences.", bossIndex); #endif continue; } - int availableProxies = maxProxies - numActiveProxies; - - int spawnNumMin = NPCGetProxySpawnNumMin(bossIndex, difficulty); - int spawnNumMax = NPCGetProxySpawnNumMax(bossIndex, difficulty); - - int spawnNum = 0; - - // Get a list of people we can transform into a good Proxy. - proxyCandidates.Clear(); - - for (int client = 1; client <= MaxClients; client++) + if (!g_PlayerProxyAvailable[client]) { - if (!IsClientInGame(client) || !g_PlayerEliminated[client] || GetClientTeam(client) == TFTeam_Red) - { - continue; - } - if (g_PlayerProxy[client]) - { - continue; - } - - if (!g_PlayerPreferences[client].PlayerPreference_EnableProxySelection && !IsFakeClient(client)) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your preferences.", bossIndex); - //PrintToChatAll("[PROXIES] You were rejected for being a proxy for boss %d because of your preferences.", bossIndex); - #endif - continue; - } - - if (!g_PlayerProxyAvailable[client]) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your cooldown.", bossIndex); - #endif - continue; - } - - if (g_PlayerProxyAvailableInForce[client]) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're already being forced into a Proxy.", bossIndex); - #endif - continue; - } - - if (!IsClientParticipating(client)) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're not participating.", bossIndex); - #endif - continue; - } - - proxyCandidates.Push(client); - spawnNum++; + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your cooldown.", bossIndex); + #endif + continue; } - if (spawnNum >= spawnNumMax) - { - spawnNum = GetRandomInt(spawnNumMin, spawnNumMax); - } - else if (spawnNum >= spawnNumMin) + if (g_PlayerProxyAvailableInForce[client]) { - spawnNum = GetRandomInt(spawnNumMin, spawnNum); + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're already being forced into a Proxy.", bossIndex); + #endif + continue; } - if (spawnNum <= 0) + if (!IsClientParticipating(client)) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d had a set spawn number of 0!", bossIndex); + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're not participating.", bossIndex); #endif continue; } - bool cooldown = false; - // Randomize the array. - SortADTArray(proxyCandidates, Sort_Random, Sort_Integer); - - float destinationPos[3]; - for (int num = 0; num < spawnNum && num < availableProxies; num++) - { - int client = proxyCandidates.Get(num); - int spawnPointEnt = -1; + proxyCandidates.Push(client); + spawnNum++; + } - if (!SpawnProxy(client, bossIndex, destinationPos, spawnPointEnt)) - { - break; - } - cooldown = true; - if (!g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage) - { - ClientStartProxyForce(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); - } - else - { - if (!IsRoundEnding() && !IsRoundInWarmup() && !IsRoundInIntro()) DisplayProxyAskMenu(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); - } + if (spawnNum >= spawnNumMax) + { + spawnNum = GetRandomInt(spawnNumMin, spawnNumMax); + } + else if (spawnNum >= spawnNumMin) + { + spawnNum = GetRandomInt(spawnNumMin, spawnNum); + } + + if (spawnNum <= 0) + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d had a set spawn number of 0!", bossIndex); + #endif + continue; + } + bool cooldown = false; + // Randomize the array. + SortADTArray(proxyCandidates, Sort_Random, Sort_Integer); + + float destinationPos[3]; + + for (int num = 0; num < spawnNum && num < availableProxies; num++) + { + int client = proxyCandidates.Get(num); + int spawnPointEnt = -1; + + if (!SpawnProxy(client, bossIndex, destinationPos, spawnPointEnt)) + { + break; } - // Set the cooldown time! - if (cooldown) + cooldown = true; + if (!g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage) { - float spawnCooldownMin = NPCGetProxySpawnCooldownMin(bossIndex, difficulty); - float spawnCooldownMax = NPCGetProxySpawnCooldownMax(bossIndex, difficulty); - - g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(spawnCooldownMin, spawnCooldownMax); + ClientStartProxyForce(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); } else { - g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(3.0, 4.0); + if (!IsRoundEnding() && !IsRoundInWarmup() && !IsRoundInIntro()) + { + DisplayProxyAskMenu(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); + } } + } + // Set the cooldown time! + if (cooldown) + { + float spawnCooldownMin = NPCGetProxySpawnCooldownMin(bossIndex, difficulty); + float spawnCooldownMax = NPCGetProxySpawnCooldownMax(bossIndex, difficulty); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0,"[PROXIES] Boss %d finished proxy process!", bossIndex); - #endif + g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(spawnCooldownMin, spawnCooldownMax); } + else + { + g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(3.0, 4.0); + } + + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0,"[PROXIES] Boss %d finished proxy process!", bossIndex); + #endif + } - delete proxyCandidates; + delete proxyCandidates; } - PvP_OnGameFrame(); + Call_StartForward(g_OnGameFramePFwd); + Call_Finish(); return Plugin_Continue; } @@ -1693,9 +1768,31 @@ Action Hook_CommandBuild(int client, const char[] command, int argc) { return Plugin_Continue; } - if (!IsClientInPvP(client)) + + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsEliminated) { - return Plugin_Handled; + if (player.HasEscaped && !player.IsInPvE && !player.IsInPvE) + { + return Plugin_Handled; + } + } + else + { + if (g_EngineerBuildInBLUConVar.BoolValue) + { + if (!player.IsEliminated || player.IsInGhostMode) + { + return Plugin_Handled; + } + } + else + { + if (!player.IsInPvP && !player.IsInPvE) + { + return Plugin_Handled; + } + } } return Plugin_Continue; @@ -1752,13 +1849,54 @@ static Action Timer_BossCountUpdate(Handle timer) { SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (Npc.UniqueID == -1 || - g_SlenderCopyMaster[Npc.Index] != -1 || - (Npc.Flags & SFF_FAKE)) + Npc.IsCopy || + (Npc.Flags & SFF_FAKE) || Npc.GetProfileData().IsPvEBoss) { continue; } bossPreferredCount++; + + int difficulty = GetLocalGlobalDifficulty(Npc.Index); + SF2BossProfileData data; + data = Npc.GetProfileData(); + if (!data.CopiesInfo.Enabled[GetLocalGlobalDifficulty(i)] || (Npc.Flags & SFF_NOCOPIES) != 0) + { + continue; + } + + int minCount = data.CopiesInfo.MinCopies[difficulty]; + if (minCount > 0) + { + for (int i2 = 0; i2 < MAX_BOSSES; i2++) + { + if (i2 == Npc.Index) + { + continue; + } + if (minCount <= 0) + { + break; + } + SF2NPC_BaseNPC tempNPC = SF2NPC_BaseNPC(i2); + if (tempNPC.UniqueID != -1) + { + if ((tempNPC.Flags & SFF_FAKE) != 0) + { + continue; + } + + if (tempNPC.IsCopy && tempNPC.CopyMaster == Npc) + { + minCount--; + continue; + } + } + + minCount--; + bossPreferredCount++; + } + } } for (int i = 1; i <= MaxClients; i++) @@ -1775,6 +1913,7 @@ static Action Timer_BossCountUpdate(Handle timer) // Check if we're near any bosses. int closest = -1; + bool bossIsClose = false; float bestDist = SquareFloat(SF2_BOSS_PAGE_CALCULATION); for (int bossEnt = 0; bossEnt < MAX_BOSSES; bossEnt++) @@ -1792,8 +1931,21 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } + SF2_BaseBoss boss = SF2_BaseBoss(Npc.EntIndex); + if (boss.Target == CBaseEntity(i)) + { + continue; + } + SF2_ChaserEntity chaser = SF2_ChaserEntity(Npc.EntIndex); + if (chaser.IsValid() && chaser.AlertTriggerTarget == SF2_BasePlayer(i)) + { + continue; + } - float dist = Npc.GetDistanceFrom(i); + float myPos[3], bossPos[3]; + boss.GetAbsOrigin(bossPos); + GetClientAbsOrigin(i, myPos); + float dist = GetVectorDistance(bossPos, myPos, true); if (dist < bestDist) { closest = bossEnt; @@ -1804,12 +1956,9 @@ static Action Timer_BossCountUpdate(Handle timer) if (closest != -1) { - continue; + bossIsClose = true; } - closest = -1; - bestDist = SquareFloat(SF2_BOSS_PAGE_CALCULATION); - for (int client = 1; client <= MaxClients; client++) { if (!IsValidClient(client) || @@ -1817,7 +1966,8 @@ static Action Timer_BossCountUpdate(Handle timer) g_PlayerEliminated[client] || IsClientInGhostMode(client) || IsClientInDeathCam(client) || - DidClientEscape(client)) + DidClientEscape(client) || + client == i) { continue; } @@ -1834,8 +1984,13 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } + if (Npc.GetProfileData().IsPvEBoss) + { + continue; + } - if (EntRefToEntIndex(g_SlenderTarget[Npc.Index]) == client) + SF2_BaseBoss boss = SF2_BaseBoss(Npc.EntIndex); + if (boss.IsValid() && boss.Target == CBaseEntity(client)) { busy = true; break; @@ -1855,7 +2010,7 @@ static Action Timer_BossCountUpdate(Handle timer) } } - if (!IsValidClient(closest)) + if (!IsValidClient(closest) && !bossIsClose) { // No one's close to this dude? DUDE! WE NEED ANOTHER BOSS! bossPreferredCount++; @@ -1863,7 +2018,7 @@ static Action Timer_BossCountUpdate(Handle timer) } int diff = bossCount - bossPreferredCount; - if (diff) + if (diff != 0) { if (diff > 0) { @@ -1877,23 +2032,69 @@ static Action Timer_BossCountUpdate(Handle timer) continue; } - if (g_SlenderCopyMaster[Npc.Index] == -1) + if (!Npc.IsCopy) { continue; } + if (Npc.CanBeSeen(_, false)) { continue; } + if (Npc.Flags & SFF_FAKE) { continue; } + if (Npc.GetProfileData().IsPvEBoss) + { + continue; + } + if (Npc.CanRemove) { - Npc.Remove(); - count--; + SF2NPC_BaseNPC master = Npc.CopyMaster; + SF2BossProfileData data; + data = master.GetProfileData(); + int difficulty = GetLocalGlobalDifficulty(master.Index); + if (data.CopiesInfo.MinCopies[difficulty] > 0) + { + int copyCount = 0; + for (int i2 = 0; i2 < MAX_BOSSES; i2++) + { + if (i2 == master.Index) + { + continue; + } + SF2NPC_BaseNPC tempNPC = SF2NPC_BaseNPC(i2); + if (tempNPC.UniqueID == -1) + { + continue; + } + + if (Npc.Flags & SFF_FAKE) + { + continue; + } + + if (tempNPC.CopyMaster != master) + { + continue; + } + copyCount++; + } + if (copyCount > data.CopiesInfo.MinCopies[difficulty]) + { + Npc.Remove(); + count--; + } + } + else + { + Npc.Remove(); + count--; + } } if (count <= 0) @@ -1915,14 +2116,24 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } - if (g_SlenderCopyMaster[Npc.Index] != -1) + + if (Npc.IsCopy) + { + continue; + } + + SF2BossProfileData data; + data = Npc.GetProfileData(); + if (!data.CopiesInfo.Enabled[GetLocalGlobalDifficulty(i)] || (Npc.Flags & SFF_NOCOPIES) != 0) { continue; } - if (!(Npc.Flags & SFF_COPIES)) + + if (data.IsPvEBoss) { continue; } + if (Npc.Flags & SFF_FAKE) { continue; @@ -1937,7 +2148,7 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } - if (g_SlenderCopyMaster[tempNpc.Index] != i) + if (tempNpc.CopyMaster != Npc) { continue; } @@ -1947,12 +2158,11 @@ static Action Timer_BossCountUpdate(Handle timer) int difficulty = GetLocalGlobalDifficulty(Npc.Index); - int copyDifficulty = g_SlenderMaxCopies[Npc.Index][difficulty]; + int copyDifficulty = Npc.GetMaxCopies(difficulty); if (copyCount >= copyDifficulty) { continue; } - Npc.GetProfile(profile, sizeof(profile)); AddProfile(profile, _, Npc); @@ -2059,10 +2269,6 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { switch (StringToInt(intValue)) { - case Difficulty_Easy: - { - g_RoundDifficultyModifier = DIFFICULTYMODIFIER_NORMAL; - } case Difficulty_Hard: { g_RoundDifficultyModifier = DIFFICULTYMODIFIER_HARD; @@ -2091,26 +2297,6 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) g_RoundDifficultyModifier = DIFFICULTYMODIFIER_NORMAL; } } - CheckIfMusicValid(); - if (MusicActive()) - { - for (int i = 1; i <= MaxClients; i++) - { - SF2_BasePlayer client = SF2_BasePlayer(i); - if (!client.IsValid || client.IsSourceTV) - { - continue; - } - - char path[PLATFORM_MAX_PATH]; - GetBossMusic(path, sizeof(path)); - if (path[0] != '\0') - { - StopSound(i, MUSIC_CHAN, path); - } - client.UpdateMusicSystem(); - } - } ChangeAllSlenderModels(); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { @@ -2119,7 +2305,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { continue; } - SF2NPC_BaseNPC masterNpc = SF2NPC_BaseNPC(g_SlenderCompanionMaster[Npc.Index]); + SF2NPC_BaseNPC masterNpc = Npc.CompanionMaster; if (masterNpc.IsValid() && g_SlenderAddCompanionsOnDifficulty[masterNpc.Index]) { Npc.RemoveFromGame(); @@ -2147,19 +2333,15 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) } else if (cvar == g_PlayerShakeEnabledConVar) { - g_IsPlayerShakeEnabled = !!StringToInt(intValue); + g_IsPlayerShakeEnabled = StringToInt(intValue) != 0; } else if (cvar == g_PlayerViewbobHurtEnabledConVar) { - g_PlayerViewbobHurtEnabled = !!StringToInt(intValue); + g_PlayerViewbobHurtEnabled = StringToInt(intValue) != 0; } else if (cvar == g_PlayerViewbobSprintEnabledConVar) { - g_PlayerViewbobSprintEnabled = !!StringToInt(intValue); - } - else if (cvar == g_GravityConVar) - { - g_Gravity = StringToFloat(intValue); + g_PlayerViewbobSprintEnabled = StringToInt(intValue) != 0; } else if (cvar == g_AllChatConVar || SF_IsBoxingMap()) { @@ -2183,16 +2365,13 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { if (g_RestartSessionConVar.BoolValue) { - ArrayList selectableBossesAdmin = GetSelectableAdminBossProfileList().Clone(); - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - for (int i = 0; i < sizeof(g_SoundNightmareMode) - 1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + ArrayList selectableBossesAdmin = GetSelectableAdminBossProfileList(); + ArrayList selectableBosses = GetSelectableBossProfileList(); + PlayNightmareSound(); SpecialRoundGameText("Its Restart Session time!", "leaderboard_streak"); CPrintToChatAll("{royalblue}%t {default}Your thirst for blood continues? Very well, let the blood spill. Let the demons feed off your unfortunate soul... Difficulty set to {mediumslateblue}%t!", "SF2 Prefix", "SF2 Calamity Difficulty"); g_RestartSessionEnabled = true; - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + SetDifficulty(Difficulty_Apollyon); g_IgnoreRoundWinConditionsConVar.SetBool(true); g_IgnoreRedPlayerDeathSwapConVar.SetBool(true); g_BossChaseEndlesslyConVar.SetBool(true); @@ -2270,7 +2449,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) if (IsValidEntity(ent)) { GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - SF2NPC_BaseNPC Npc = view_as(npcIndex); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); if (!Npc.IsValid()) { continue; @@ -2281,20 +2460,69 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) } delete spawnPoint; } - delete selectableBosses; - delete selectableBossesAdmin; } else { CPrintToChatAll("{royalblue}%t {default}You're done? Ok. Difficulty set to {darkgray}Apollyon.", "SF2 Prefix"); g_RestartSessionEnabled = false; - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + SetDifficulty(Difficulty_Apollyon); g_IgnoreRoundWinConditionsConVar.SetBool(false); g_IgnoreRedPlayerDeathSwapConVar.SetBool(false); g_BossChaseEndlesslyConVar.SetBool(false); g_RoundDifficultyModifier = DIFFICULTYMODIFIER_APOLLYON; } } + else if (cvar == g_RaidMapConVar) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + if (client.IsEliminated && !client.IsInPvP) + { + ToggleWeaponCooldowns(client, true); + } + else + { + ToggleWeaponCooldowns(client, false); + } + } + } + else if (cvar == g_NightvisionEnabledConVar) + { + if (!IsInfiniteFlashlightEnabled()) + { + g_NightVisionType = GetRandomInt(0, 2); + } + else + { + g_NightVisionType = 1; + } + } + else if (cvar == g_EnableWallHaxConVar) + { + Call_StartForward(g_OnWallHaxDebugPFwd); + Call_Finish(); + } +} + +static void ToggleWeaponCooldowns(SF2_BasePlayer client, bool invert) +{ + for (int i = 0; i <= 5; i++) + { + int weapon = client.GetWeaponSlot(i); + if (!IsValidEntity(weapon)) + { + continue; + } + + CBaseEntity weaponEnt = CBaseEntity(weapon); + weaponEnt.SetPropFloat(Prop_Send, "m_flNextPrimaryAttack", invert ? 0.0 : 99999999.9); + weaponEnt.SetPropFloat(Prop_Send, "m_flNextSecondaryAttack", invert ? 0.0 : 99999999.9); + } } // ========================================================== @@ -2303,6 +2531,32 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) public void OnEntityCreated(int ent, const char[] classname) { + if (strcmp(classname, "obj_sentrygun", false) == 0 || strcmp(classname, "obj_dispenser", false) == 0 || + strcmp(classname, "obj_teleporter", false) == 0) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + if (strcmp(classname, "tank_boss", false) == 0) + { + g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); + } + + if (strcmp(classname, "prop_physics", false) == 0 && GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + + if (strcmp(classname, "prop_dynamic", false) == 0 && GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + + if (strcmp(classname, "func_breakable", false) == 0 && GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + if (!g_Enabled) { return; @@ -2329,64 +2583,45 @@ public void OnEntityCreated(int ent, const char[] classname) { RemoveEntity(ent); } - PvP_OnEntityCreated(ent, classname); -} - -MRESReturn Hook_WeaponGetCustomDamageType(int weapon, DHookReturn returnHandle, DHookParam params) -{ - if (!g_Enabled) - { - return MRES_Ignored; - } - SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(weapon, Prop_Data, "m_hOwnerEntity")); - if (ownerEntity.IsValid && ownerEntity.IsInPvP && IsValidEntity(weapon) && ownerEntity) - { - int customDamageType = returnHandle.Value; - if (customDamageType != -1) - { - MRESReturn hookResult = PvP_GetWeaponCustomDamageType(weapon, ownerEntity.index, customDamageType); - if (hookResult != MRES_Ignored) - { - returnHandle.Value = customDamageType; - return hookResult; - } - } - else - { - return MRES_Ignored; - } - } - else - { - return MRES_Ignored; - } - - return MRES_Ignored; + Call_StartForward(g_OnEntityCreatedPFwd); + Call_PushCell(CBaseEntity(ent)); + Call_PushString(classname); + Call_Finish(); } public void OnEntityDestroyed(int ent) { - if (!g_Enabled) + if (!IsValidEntity(ent) || ent <= 0) { return; } - if (!IsValidEntity(ent) || ent <= 0) + int index = g_Buildings.FindValue(EntIndexToEntRef(ent)); + if (index != -1) { - return; + g_Buildings.Erase(index); } - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(ent)); - if (Npc != SF2_INVALID_NPC) + index = g_WhitelistedEntities.FindValue(EntIndexToEntRef(ent)); + if (index != -1) { - Npc.UnSpawn(); - return; + g_WhitelistedEntities.Erase(index); } char classname[64]; GetEntityClassname(ent, classname, sizeof(classname)); + Call_StartForward(g_OnEntityDestroyedPFwd); + Call_PushCell(CBaseEntity(ent)); + Call_PushString(classname); + Call_Finish(); + + if (!g_Enabled) + { + return; + } + if (strcmp(classname, "light_dynamic", false) == 0) { AcceptEntityInput(ent, "TurnOff"); @@ -2412,8 +2647,6 @@ public void OnEntityDestroyed(int ent) RemoveEntity(glow); } } - - PvP_OnEntityDestroyed(ent, classname); } Action Hook_BlockUserMessage(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init) @@ -2442,6 +2675,11 @@ Action Hook_TauntUserMessage(UserMsg msg_id, BfRead msg, const int[] players, in return Plugin_Handled; //Don't allow proxies to play a taunt sound } + if (g_DisableTauntLoopsConVar.BoolValue) + { + return Plugin_Continue; + } + char tauntSound[PLATFORM_MAX_PATH]; msg.ReadString(tauntSound, PLATFORM_MAX_PATH); @@ -2515,8 +2753,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M return Plugin_Continue; } - int difficulty = g_DifficultyConVar.IntValue; - SF2_BasePlayer client = SF2_BasePlayer(entity); if (client.IsValid) { @@ -2530,26 +2766,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M } } } - else if (client.IsProxy) - { - int master = NPCGetFromUniqueID(client.ProxyMaster); - if (master != -1) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(master, profile, sizeof(profile)); - - switch (channel) - { - case SNDCHAN_VOICE: - { - if (!g_SlenderProxiesAllowNormalVoices[master]) - { - return Plugin_Handled; - } - } - } - } - } else if (!client.IsEliminated && !client.HasEscaped) { switch (channel) @@ -2560,32 +2776,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M { return Plugin_Handled; } - if (!StrContains(sample, "vo/halloween_scream")) - { - return Plugin_Handled; - } - - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Voice) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Voice)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDVOICE; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddVoice(bossIndex, difficulty); - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = SoundType_Voice; - } - } } case SNDCHAN_BODY: { @@ -2606,146 +2796,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M client.ViewPunch(punchVelStep); } - - bool isLoudStep = false; - - bool isCrouchStep = false; - - if (client.IsSprinting && !(client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked"))) - { - isLoudStep = true; - } - else if (client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked")) - { - isCrouchStep = true; - } - - SoundType soundType = SoundType_Footstep; - if (isLoudStep) - { - soundType = SoundType_LoudFootstep; - } - else if (isCrouchStep) - { - soundType = SoundType_QuietFootstep; - } - - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, soundType) && NPCShouldHearEntity(bossIndex, client.index, soundType)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - if (isLoudStep) - { - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFOOTSTEPLOUD; - } - else if (isCrouchStep) - { - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFOOTSTEPQUIET; - } - else - { - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFOOTSTEP; - } - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - if (isLoudStep) - { - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddLoudFootstep(bossIndex, difficulty); - } - else - { - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddFootstep(bossIndex, difficulty); - } - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = soundType; - } - } - } - } - case SNDCHAN_ITEM, SNDCHAN_WEAPON: - { - if (StrContains(sample, "swing", false) || StrContains(sample, "impact", false) != -1 || StrContains(sample, "hit", false) != -1 || StrContains(sample, "slice", false) != -1 || StrContains(sample, "reload", false) != -1 || StrContains(sample, "woosh", false) != -1 || StrContains(sample, "eviction", false) != -1 || StrContains(sample, "holy", false) != -1) - { - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Weapon) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Weapon)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDWEAPON; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddWeapon(bossIndex, difficulty); - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = SoundType_Weapon; - } - } - } - } - case SNDCHAN_STATIC: - { - if (StrContains(sample, FLASHLIGHT_CLICKSOUND, false) != -1) - { - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Flashlight) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Flashlight)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFLASHLIGHT; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddWeapon(bossIndex, difficulty); - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = SoundType_Flashlight; - } - } - } - if (StrContains(sample, "happy_birthday_tf", false) != -1 || StrContains(sample, "jingle_bells_nm", false) != -1) - { - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Voice) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Voice)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDVOICE; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddVoice(bossIndex, difficulty) * 2; - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - } - } } } } @@ -2793,7 +2843,7 @@ MRESReturn Hook_EntityShouldTransmit(int entity, DHookReturn returnHandle, DHook SF2_BasePlayer client = SF2_BasePlayer(entity); if (client.IsValid) { - if (client.HasConstantGlow) + if (DoesEntityHaveGlow(client.index)) { returnHandle.Value = FL_EDICT_ALWAYS; // Should always transmit, but our SetTransmit hook gets the final say. return MRES_Supercede; @@ -2848,6 +2898,7 @@ static Action Hook_TriggerOnTouchEx(int trigger, int other) } return Plugin_Continue; } + static Action Hook_TriggerOnEndTouchEx(int trigger, int other) { if (MaxClients >= other >= 1 && IsClientInGhostMode(other)) @@ -2920,6 +2971,7 @@ void Hook_TriggerOnStartTouch(const char[] output, int caller, int activator, fl } PvP_OnTriggerStartTouch(caller, activator); + PvE_OnTriggerStartTouch(caller, activator); } void Hook_TriggerOnEndTouch(const char[] output, int caller, int activator, float delay) @@ -2944,94 +2996,29 @@ void Hook_TriggerOnEndTouch(const char[] output, int caller, int activator, floa #endif } -void Hook_TriggerTeleportOnStartTouch(const char[] output, int caller, int activator, float delay) +void Hook_TriggerTeleportOnStartTouch(const char[] output, int teleporter, int activator, float delay) { if (!g_Enabled) { return; } - if (!IsValidEntity(caller)) + if (!IsValidEntity(teleporter)) { return; } - int flags = GetEntProp(caller, Prop_Data, "m_spawnflags"); - if (((flags & TRIGGER_CLIENTS) && (flags & TRIGGER_NPCS)) || (flags & TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS)) - { - if (IsValidClient(activator)) - { - bool chase = ClientHasMusicFlag(activator, MUSICF_CHASE); - if (chase) - { - // The player took a teleporter and is chased, and the boss can take it too, add the teleporter to the temp boss' goals. - for (int i = 0; i < MAX_BOSSES; i++) - { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); - if (Npc.UniqueID == -1) - { - continue; - } - if (EntRefToEntIndex(g_SlenderTarget[i]) == activator) - { - for (int ii = 0; ii < MAX_NPCTELEPORTER; ii++) - { - if (Npc.GetTeleporter(ii) == INVALID_ENT_REFERENCE) - { - Npc.SetTeleporter(ii, EntIndexToEntRef(caller)); - break; - } - } - } - } - } - return; - } - SF2NPC_BaseNPC Npc = view_as(NPCGetFromEntIndex(activator)); - if (Npc.IsValid()) - { - //A boss took a teleporter - int teleporter = Npc.GetTeleporter(0); - if (teleporter == EntIndexToEntRef(caller)) //Remove our temp goal, and go back chase our target! GRAAAAAAAAAAAAh! Unless we have some other teleporters to take....fak. - { - Npc.SetTeleporter(0, INVALID_ENT_REFERENCE); - } - if (MAX_NPCTELEPORTER > 2 && Npc.GetTeleporter(1) != INVALID_ENT_REFERENCE) - { - for (int i = 0; i + 1 < MAX_NPCTELEPORTER; i++) - { - if (Npc.GetTeleporter(i + 1) != INVALID_ENT_REFERENCE) - { - Npc.SetTeleporter(i, Npc.GetTeleporter(i + 1)); - } - else - { - Npc.SetTeleporter(i, INVALID_ENT_REFERENCE); - } - } - } - } - } - if (IsValidClient(activator)) + if (SDKCall(g_SDKPassesTriggerFilters, teleporter, activator) == 0) { - bool chase = ClientHasMusicFlag(activator, MUSICF_CHASE); - if (chase) - { - // The player took a teleporter and is chased, but the boss can't follow. - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - if (EntRefToEntIndex(g_SlenderTarget[i]) == activator) - { - g_SlenderGiveUp[i] = true; - } - } - } + return; } + + Call_StartForward(g_OnEntityTeleportedPFwd); + Call_PushCell(CBaseEntity(teleporter)); + Call_PushCell(CBaseEntity(activator)); + Call_Finish(); } + static Action Hook_PageOnTakeDamage(int page, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { if (!g_Enabled) @@ -3095,7 +3082,7 @@ void CollectPage(int page, int activator) { if (g_DifficultyConVar.IntValue < 4 || GetSelectableAdminBossProfileList().Length <= 0) { - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -3105,11 +3092,10 @@ void CollectPage(int page, int activator) SpecialRoundGameText(bossName, "d_purgatory"); CPrintToChatAll("{royalblue}%t {default}Next on the roulette: {valve}%s", "SF2 Prefix", bossName); //Minimized HUD } - delete selectableBosses; } else { - ArrayList selectableBosses = GetSelectableAdminBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableAdminBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -3119,7 +3105,6 @@ void CollectPage(int page, int activator) SpecialRoundGameText(bossName, "d_purgatory"); CPrintToChatAll("{royalblue}%t {default}Next on the roulette: {valve}%s", "SF2 Prefix", bossName); } - delete selectableBosses; } } else @@ -3143,8 +3128,8 @@ void CollectPage(int page, int activator) } int maxHealth = SDKCall(g_SDKGetMaxHealth, reds); float healthToRecover = float(maxHealth) / 10.0; - int iHealthToRecover = RoundToNearest(healthToRecover) + GetEntProp(reds, Prop_Send, "m_iHealth"); - SetEntityHealth(reds, iHealthToRecover); + int healthToRecoverInt = RoundToNearest(healthToRecover) + GetEntProp(reds, Prop_Send, "m_iHealth"); + SetEntityHealth(reds, healthToRecoverInt); } } @@ -3227,16 +3212,17 @@ static void EmitRollSound(int client) EmitSoundToClient(client, GENERIC_ROLL_TICK, client); g_PlayerPageRewardCycleCount[client] = 0; g_PlayerPageRewardCycleCooldown[client] = 0.0; - g_PlayerPageRewardCycleTimer[client] = CreateTimer(0.1, Timer_RollTick, client, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerPageRewardCycleTimer[client] = CreateTimer(0.1, Timer_RollTick, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } -static Action Timer_RollTick(Handle timer, any client) +static Action Timer_RollTick(Handle timer, any id) { if (!g_Enabled) { return Plugin_Stop; } + int client = GetClientOfUserId(id); if (!IsValidClient(client) || timer != g_PlayerPageRewardCycleTimer[client] || g_PlayerEliminated[client]) { return Plugin_Stop; @@ -3347,8 +3333,7 @@ static void GiveRandomPageReward(int player) case 15, 16: { EmitSoundToClient(player, LOSE_SPRINT_ROLL, player, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - ClientStopSprint(player); - g_PlayerSprintPoints[player] = 0; + SF2_Player(player).Stamina = 0.0; } case 17: { @@ -3434,6 +3419,13 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 return Plugin_Continue; } + if (!IsValidClient(client)) + { + return Plugin_Continue; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + bool changed = false; // Check impulse (block spraying and built-in flashlight) @@ -3445,7 +3437,7 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 } case 201, 202: { - if (IsClientInGhostMode(client)) + if (player.IsInGhostMode) { impulse = 0; } @@ -3457,34 +3449,34 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 if ((buttons & button)) { - if (!(g_PlayerLastButtons[client] & button)) + if (!(player.LastButtons & button)) { - AFK_SetTime(client); - ClientOnButtonPress(client, button); + player.SetAFKTime(); + ClientOnButtonPress(player, button); if (button == IN_ATTACK2) { - if (IsClientInPvP(client) && !(buttons & IN_ATTACK)) + if (player.IsInPvP && !(buttons & IN_ATTACK)) { - if (TF2_GetPlayerClass(client) == TFClass_Medic) + if (player.Class == TFClass_Medic) { - int weaponEnt = GetPlayerWeaponSlot(client, 0); - if (weaponEnt > MaxClients) + CBaseEntity weaponEnt = CBaseEntity(player.GetWeaponSlot(0)); + if (weaponEnt.IsValid()) { char weaponClass[64]; - GetEdictClassname(weaponEnt, weaponClass, sizeof(weaponClass)); + weaponEnt.GetClassname(weaponClass, sizeof(weaponClass)); if (strcmp(weaponClass, "tf_weapon_crossbow") == 0) { - int clip = GetEntProp(weaponEnt, Prop_Send, "m_iClip1"); + int clip = weaponEnt.GetProp(Prop_Send, "m_iClip1"); if (clip > 0) { buttons |= IN_ATTACK; - g_PlayerLastButtons[client] = buttons; + player.LastButtons = buttons; buttons &= ~IN_ATTACK2; changed = true; - RequestFrame(Frame_ClientHealArrow, client); + RequestFrame(Frame_ClientHealArrow, player.index); - EmitSoundToAll(")weapons/crusaders_crossbow_shoot.wav", client, SNDCHAN_WEAPON, SNDLEVEL_MINIBIKE); //Fix client's predictions. + EmitSoundToAll(")weapons/crusaders_crossbow_shoot.wav", player.index, SNDCHAN_WEAPON, SNDLEVEL_MINIBIKE); //Fix client's predictions. } } } @@ -3494,11 +3486,11 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 } if (button == IN_ATTACK2) { - if (!g_PlayerEliminated[client]) + if (!player.IsEliminated) { - g_PlayerLastButtons[client] = buttons; - int weaponActive = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); - if (weaponActive > MaxClients && IsTauntWep(weaponActive)) + player.LastButtons = buttons; + CBaseEntity weaponActive = CBaseEntity(player.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (weaponActive.IsValid() && IsTauntWep(weaponActive.index)) { buttons &= ~IN_ATTACK2; //Tough break update made players able to taunt with secondary attack. Block this feature. changed = true; @@ -3506,17 +3498,17 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 } } } - else if ((g_PlayerLastButtons[client] & button)) + else if ((player.LastButtons & button)) { - ClientOnButtonRelease(client, button); + ClientOnButtonRelease(player, button); } } - AFK_CheckTime(client); + player.CheckAFKTime(); if (!changed) { - g_PlayerLastButtons[client] = buttons; + player.LastButtons = buttons; } return (changed) ? Plugin_Changed : Plugin_Continue; } @@ -3547,11 +3539,12 @@ public void OnClientCookiesCached(int client) g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = true; g_PlayerPreferences[client].PlayerPreference_ViewBobbing = g_PlayerViewbobEnabledConVar.BoolValue; g_PlayerPreferences[client].PlayerPreference_LegacyHud = g_DefaultLegacyHudConVar.BoolValue; + g_PlayerPreferences[client].PlayerPreference_MusicVolume = 1.0; if (cookie[0] != '\0') { - char s2[15][32]; - int count = ExplodeString(cookie, " ; ", s2, 15, 32); + char s2[16][64]; + int count = ExplodeString(cookie, " ; ", s2, 16, 64); if (count > 0) { @@ -3559,11 +3552,11 @@ public void OnClientCookiesCached(int client) } if (count > 1) { - g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn = !!StringToInt(s2[1]); + g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn = StringToInt(s2[1]) != 0; } if (count > 2) { - g_PlayerPreferences[client].PlayerPreference_ShowHints = !!StringToInt(s2[2]); + g_PlayerPreferences[client].PlayerPreference_ShowHints = StringToInt(s2[2]) != 0; } if (count > 3) { @@ -3571,11 +3564,11 @@ public void OnClientCookiesCached(int client) } if (count > 4) { - g_PlayerPreferences[client].PlayerPreference_FilmGrain = !!StringToInt(s2[4]); + g_PlayerPreferences[client].PlayerPreference_FilmGrain = StringToInt(s2[4]) != 0; } if (count > 5) { - g_PlayerPreferences[client].PlayerPreference_EnableProxySelection = !!StringToInt(s2[5]); + g_PlayerPreferences[client].PlayerPreference_EnableProxySelection = StringToInt(s2[5]) != 0; } if (count > 6) { @@ -3583,15 +3576,15 @@ public void OnClientCookiesCached(int client) } if (count > 7) { - g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = !!StringToInt(s2[7]); + g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = StringToInt(s2[7]) != 0; } if (count > 8) { - g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage = !!StringToInt(s2[8]); + g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage = StringToInt(s2[8]) != 0; } if (count > 9) { - g_PlayerPreferences[client].PlayerPreference_ViewBobbing = !!StringToInt(s2[9]); + g_PlayerPreferences[client].PlayerPreference_ViewBobbing = StringToInt(s2[9]) != 0; } if (count > 10) { @@ -3599,7 +3592,7 @@ public void OnClientCookiesCached(int client) } if (count > 11) { - g_PlayerPreferences[client].PlayerPreference_GroupOutline = !!StringToInt(s2[11]); + g_PlayerPreferences[client].PlayerPreference_GroupOutline = StringToInt(s2[11]) != 0; } if (count > 12) { @@ -3607,15 +3600,26 @@ public void OnClientCookiesCached(int client) } if (count > 13) { - g_PlayerPreferences[client].PlayerPreference_LegacyHud = !!StringToInt(s2[13]); + g_PlayerPreferences[client].PlayerPreference_LegacyHud = StringToInt(s2[13]) != 0; + } + if (count > 14) + { + g_PlayerPreferences[client].PlayerPreference_MusicVolume = StringToFloat(s2[14]) / 100.0; } } } public void OnClientPutInServer(int client) { + g_ClientInGame[client] = true; if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue) + { + Call_StartForward(g_OnPlayerPutInServerPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); + } return; } @@ -3631,6 +3635,11 @@ public void OnClientPutInServer(int client) } #endif + if (AreClientCookiesCached(client)) + { + OnClientCookiesCached(client); + } + ClientSetPlayerGroup(client, -1); g_LastCommandTime[client] = GetEngineTime(); @@ -3650,10 +3659,6 @@ public void OnClientPutInServer(int client) SDKHook(client, SDKHook_PreThink, Hook_ClientPreThink); SDKHook(client, SDKHook_PreThinkPost, Hook_OnFlashlightThink); SDKHook(client, SDKHook_SetTransmit, Hook_ClientSetTransmit); - SDKHook(client, SDKHook_TraceAttack, Hook_PvPPlayerTraceAttack); - SDKHook(client, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); - - SDKHook(client, SDKHook_WeaponEquipPost, Hook_ClientWeaponEquipPost); g_DHookWantsLagCompensationOnEntity.HookEntity(Hook_Pre, client, Hook_ClientWantsLagCompensationOnEntity); @@ -3671,38 +3676,14 @@ public void OnClientPutInServer(int client) SetPlayerGroupInvitedPlayerTime(i, client, 0.0); } - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); - ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - ClientSetScareBoostEndTime(client, -1.0); - ClientStartProxyAvailableTimer(client); - - AFK_SetAFK(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -3729,7 +3710,11 @@ public void OnClientPutInServer(int client) QueryClientConVar(client, "fov_desired", OnClientGetDesiredFOV); } - PvP_OnClientPutInServer(client); + AFK_SetTime(client); + + Call_StartForward(g_OnPlayerPutInServerPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); #if defined DEBUG g_PlayerDebugFlags[client] = 0; @@ -3793,7 +3778,6 @@ public void OnClientDisconnect(int client) delete message; EndMessage(); - g_SeeUpdateMenu[client] = false; g_PlayerEscaped[client] = false; g_PlayerNoPoints[client] = false; g_AdminNoPoints[client] = false; @@ -3820,23 +3804,17 @@ public void OnClientDisconnect(int client) g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = true; g_PlayerPreferences[client].PlayerPreference_ViewBobbing = g_PlayerViewbobEnabledConVar.BoolValue; g_PlayerPreferences[client].PlayerPreference_LegacyHud = g_DefaultLegacyHudConVar.BoolValue; + g_PlayerPreferences[client].PlayerPreference_MusicVolume = 1.0; // Reset any client functions that may be still active. ClientResetOverlay(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientSetGhostModeState(client, false); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - - ClientStopProxyForce(client); if (SF_IsBoxingMap() && IsRoundInEscapeObjective()) { CreateTimer(0.2, Timer_CheckAlivePlayers, _, TIMER_FLAG_NO_MAPCHANGE); } - if (!IsRoundInWarmup()) + if (!IsRoundInWarmup() && GameRules_GetRoundState() != RoundState_TeamWin) { if (g_PlayerPlaying[client] && !g_PlayerEliminated[client]) { @@ -3860,8 +3838,12 @@ public void OnClientDisconnect(int client) // Reset queue points global variable. g_PlayerQueuePoints[client] = 0; - PvP_OnClientDisconnect(client); AFK_SetTime(client, false); + BossPreview_Remove(client); + + Call_StartForward(g_OnPlayerDisconnectedPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) @@ -3874,6 +3856,7 @@ public void OnClientDisconnect(int client) public void OnClientDisconnect_Post(int client) { g_PlayerLastButtons[client] = 0; + g_ClientInGame[client] = false; } public void TF2_OnWaitingForPlayersStart() @@ -3891,9 +3874,9 @@ SF2RoundState GetRoundState() return g_RoundState; } -void SetRoundTimerPaused(bool bPaused) +void SetRoundTimerPaused(bool paused) { - g_RoundTimerPaused = bPaused; + g_RoundTimerPaused = paused; } void SetRoundTime(int currentTime) @@ -3941,19 +3924,11 @@ void SetRoundState(SF2RoundState roundState) case SF2RoundState_Intro: { g_RoundIntroTimer = null; - if (!IsInfiniteFlashlightEnabled()) - { - g_NightvisionType = GetRandomInt(0, 1); - } - else - { - g_NightvisionType = 1; - } // Enable movement on players. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -3966,7 +3941,7 @@ void SetRoundState(SF2RoundState roundState) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -3998,9 +3973,6 @@ void SetRoundState(SF2RoundState roundState) if (g_RestartSessionEnabled) { ArrayList spawnPoint = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for spawnPoint in SetRoundState(SF2RoundState_Grace).", spawnPoint); - #endif float teleportPos[3]; int ent = -1, spawnTeam = 0; while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) @@ -4032,9 +4004,6 @@ void SetRoundState(SF2RoundState roundState) } } delete spawnPoint; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for spawnPoint in SetRoundState(SF2RoundState_Grace).", spawnPoint); - #endif } CPrintToChatAll("{dodgerblue}%t", "SF2 Grace Period End"); @@ -4072,6 +4041,8 @@ void SetRoundState(SF2RoundState roundState) g_RenevantBeaconEffect = false; g_Renevant90sEffect = false; g_RenevantMarkForDeath = false; + g_RenevantBossesChaseEndlessly = false; + SetDifficulty(Difficulty_Normal); if (g_RestartSessionConVar.BoolValue) { g_RestartSessionEnabled = false; @@ -4098,6 +4069,15 @@ void SetRoundState(SF2RoundState roundState) } case SF2RoundState_Grace: { + if (!IsInfiniteFlashlightEnabled()) + { + g_NightVisionType = GetRandomInt(0, 2); + } + else + { + g_NightVisionType = 1; + } + // Start the grace period timer. g_RoundGraceTimer = CreateTimer(g_GraceTimeConVar.FloatValue, Timer_RoundGrace, _, TIMER_FLAG_NO_MAPCHANGE); @@ -4105,7 +4085,7 @@ void SetRoundState(SF2RoundState roundState) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -4115,7 +4095,10 @@ void SetRoundState(SF2RoundState roundState) } case SF2RoundState_Active: { - if (SF_IsRenevantMap()) NPCRemoveAll(); + if (SF_IsRenevantMap()) + { + NPCRemoveAll(); + } // Initialize the main round timer. if (g_RoundTimeLimit > 0) { @@ -4166,20 +4149,19 @@ void SetRoundState(SF2RoundState roundState) } if (SF_IsBoxingMap()) { - g_DifficultyConVar.IntValue = Difficulty_Normal; CPrintToChatAll("%t", "SF2 Boxing Initiate"); CreateTimer(0.2, Timer_CheckAlivePlayers, _, TIMER_FLAG_NO_MAPCHANGE); for (int bossEnt = 0; bossEnt < MAX_BOSSES; bossEnt++) { - SF2NPC_BaseNPC Npc = view_as(bossEnt); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(bossEnt); if (!Npc.IsValid()) { continue; } - if (NPCChaserIsBoxingBoss(Npc.Index)) + if (NPCChaserIsBoxingBoss(Npc.Index) && !Npc.GetProfileData().IsPvEBoss) { - g_SlenderBoxingBossCount += 1; + g_SlenderBoxingBossCount++; } } } @@ -4195,7 +4177,7 @@ void SetRoundState(SF2RoundState roundState) // Teleport winning players to the escape point. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -4209,7 +4191,7 @@ void SetRoundState(SF2RoundState roundState) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -4269,24 +4251,14 @@ bool IsRoundEnding() return GetRoundState() == SF2RoundState_Outro; } -bool IsInfiniteBlinkEnabled() -{ - return g_RoundInfiniteBlink || (g_PlayerInfiniteBlinkOverrideConVar.IntValue == 1); -} - -bool IsInfiniteSprintEnabled() -{ - return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1); -} - -stock bool IsClientParticipating(int client) +bool IsClientParticipating(int client) { if (!IsValidClient(client)) { return false; } - if (!!GetEntProp(client, Prop_Send, "m_bIsCoaching")) + if (GetEntProp(client, Prop_Send, "m_bIsCoaching") != 0) { // Who would coach in this game? return false; @@ -4302,7 +4274,7 @@ stock bool IsClientParticipating(int client) } } - if (view_as(TF2_GetPlayerClass(client)) == 0) + if (TF2_GetPlayerClass(client) == TFClass_Unknown) { // Player hasn't chosen a class? What. return false; @@ -4314,9 +4286,6 @@ stock bool IsClientParticipating(int client) ArrayList GetQueueList() { ArrayList array = new ArrayList(3); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for array in GetQueueList.", array); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsClientParticipating(i)) @@ -4351,19 +4320,24 @@ ArrayList GetQueueList() return array; } -stock int GetOppositeTeam(int team) +void SetClientPlayState(int client, bool state, bool enablePlay = true, bool queue = true) { - return team == 2 ? 3 : 2; -} + if (g_PlayerEliminated[client] == !state) + { + return; + } -stock int GetOppositeTeamOf(int client) -{ - int team = GetClientTeam(client); - return GetOppositeTeam(team); -} + if (g_PlayerProxy[client]) + { + return; + } + + Call_StartForward(g_OnPlayerChangePlayStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(state); + Call_PushCell(queue); + Call_Finish(); -void SetClientPlayState(int client, bool state, bool enablePlay=true) -{ Handle message = StartMessageAll("PlayerTauntSoundLoopEnd", USERMSG_RELIABLE); BfWriteByte(message, client); delete message; @@ -4371,15 +4345,6 @@ void SetClientPlayState(int client, bool state, bool enablePlay=true) if (state) { - if (!g_PlayerEliminated[client]) - { - return; - } - if (g_PlayerProxy[client]) - { - return; - } - g_PlayerCalledForNightmare[client] = false; g_PlayerEliminated[client] = false; g_PlayerPlaying[client] = enablePlay; @@ -4388,6 +4353,7 @@ void SetClientPlayState(int client, bool state, bool enablePlay=true) ClientSetGhostModeState(client, false); PvP_SetPlayerPvPState(client, false, false, false); + PvE_SetPlayerPvEState(client, false, false); if (g_IsSpecialRound) { @@ -4399,7 +4365,7 @@ void SetClientPlayState(int client, bool state, bool enablePlay=true) SetClientPlayNewBossRoundState(client, true); } - if (TF2_GetPlayerClass(client) == view_as(0)) + if (TF2_GetPlayerClass(client) == TFClass_Unknown) { // Player hasn't chosen a class for some reason. Choose one for him. TF2_SetPlayerClass(client, view_as(GetRandomInt(1, 9)), true, true); @@ -4409,17 +4375,11 @@ void SetClientPlayState(int client, bool state, bool enablePlay=true) } else { - if (g_PlayerEliminated[client]) - { - return; - } - g_PlayerEliminated[client] = true; g_PlayerPlaying[client] = false; ChangeClientTeamNoSuicide(client, TFTeam_Blue); } - } /* bool DidClientPlayNewBossRound(int client) @@ -4444,7 +4404,7 @@ void SetClientPlaySpecialRoundState(int client, bool state) void TeleportClientToEscapePoint(int client) { - if (!IsClientInGame(client)) + if (!IsValidClient(client)) { return; } @@ -4474,7 +4434,7 @@ void TeleportClientToEscapePoint(int client) delete spawnPoints; - if (ent && IsValidEntity(ent)) + if (ent && ent != INVALID_ENT_REFERENCE) { SF2PlayerEscapeSpawnEntity spawnPoint = SF2PlayerEscapeSpawnEntity(ent); @@ -4482,7 +4442,7 @@ void TeleportClientToEscapePoint(int client) GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); - TeleportEntity(client, pos, ang, view_as( { 0.0, 0.0, 0.0 } )); + TeleportEntity(client, pos, ang, NULL_VECTOR); if (spawnPoint.IsValid()) { @@ -4502,10 +4462,6 @@ void ForceInNextPlayersInQueue(int amount, bool showMessage = false) ArrayList players = new ArrayList(); ArrayList array = GetQueueList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for players in ForceInNextPlayersInQueue.", players); - #endif - for (int i = 0, size = array.Length; i < size && amountLeft > 0; i++) { if (!array.Get(i, 2)) @@ -4568,9 +4524,6 @@ void ForceInNextPlayersInQueue(int amount, bool showMessage = false) } delete array; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for array in ForceInNextPlayersInQueue.", array); - #endif for (int i = 0, size = players.Length; i < size; i++) { @@ -4596,9 +4549,6 @@ void ForceInNextPlayersInQueue(int amount, bool showMessage = false) } delete players; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for players in ForceInNextPlayersInQueue.", players); - #endif } static int SortQueueList(int index1, int index2, Handle arrayHandle, Handle hndl) @@ -4689,6 +4639,7 @@ static Action Hook_SlenderObjectSetTransmitEx(int ent, int other) void SlenderOnClientStressUpdate(int client) { int difficulty = g_DifficultyConVar.IntValue; + float gameTime = GetGameTime(); float stress = g_PlayerStressAmount[client]; @@ -4703,21 +4654,22 @@ void SlenderOnClientStressUpdate(int client) } int bossFlags = Npc.Flags; - if (bossFlags & SFF_MARKEDASFAKE) + if ((bossFlags & SFF_MARKEDASFAKE) != 0) { continue; } - if ((bossFlags & SFF_NOTELEPORT) && (bossFlags & SFF_PROXIES) && g_SlenderCopyMaster[Npc.Index] == -1) + if ((bossFlags & SFF_NOTELEPORT) != 0 && (bossFlags & SFF_PROXIES) != 0 && !Npc.IsCopy) { - //Go get a proxy target anyways + // Go get a proxy target anyways ArrayList proxyArray = new ArrayList(); for (int i = 1; i <= MaxClients; i++) { - if (IsClientInGame(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) + if (IsValidClient(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) { proxyArray.Push(i); } } + if (proxyArray.Length > 0) { int proxyClient = proxyArray.Get(GetRandomInt(0, proxyArray.Length - 1)); @@ -4732,20 +4684,28 @@ void SlenderOnClientStressUpdate(int client) Npc.GetProfile(profile, sizeof(profile)); - int teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[Npc.Index]); + int teleportTarget = EntRefToEntIndex(g_SlenderProxyTarget[Npc.Index]); + if (teleportTarget && teleportTarget != INVALID_ENT_REFERENCE) + { + if (g_PlayerEliminated[teleportTarget] || DidClientEscape(teleportTarget)) + { + g_SlenderProxyTarget[Npc.Index] = INVALID_ENT_REFERENCE; + } + } + + teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[Npc.Index]); if (teleportTarget && teleportTarget != INVALID_ENT_REFERENCE && !g_PlayerIsExitCamping[teleportTarget]) { - if (g_PlayerEliminated[teleportTarget] || - DidClientEscape(teleportTarget) || + if (g_PlayerEliminated[teleportTarget] || DidClientEscape(teleportTarget) || (!SF_BossesChaseEndlessly() && !SF_IsRenevantMap() && !SF_IsSurvivalMap() && !g_SlenderTeleportIgnoreChases[Npc.Index] && stress >= g_SlenderTeleportMaxTargetStress[bossIndex]) || - GetGameTime() >= g_SlenderTeleportMaxTargetTime[Npc.Index]) + (g_SlenderTeleportMaxTargetTime[Npc.Index] > 0.0 && gameTime >= g_SlenderTeleportMaxTargetTime[Npc.Index])) { // Queue for a new target and mark the old target in the rest period. float restPeriod = Npc.GetTeleportRestPeriod(difficulty); restPeriod = (restPeriod * GetRandomFloat(0.92, 1.08)) / (g_RoundDifficultyModifier); g_SlenderTeleportTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderTeleportPlayersRestTime[Npc.Index][teleportTarget] = GetGameTime() + restPeriod; + g_SlenderTeleportPlayersRestTime[Npc.Index][teleportTarget] = gameTime + restPeriod; g_SlenderTeleportMaxTargetStress[Npc.Index] = 9999.0; g_SlenderTeleportMaxTargetTime[Npc.Index] = -1.0; g_SlenderTeleportTargetTime[Npc.Index] = -1.0; @@ -4770,7 +4730,7 @@ void SlenderOnClientStressUpdate(int client) for (int i = 1; i <= MaxClients; i++) { - if (IsClientInGame(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) + if (IsValidClient(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) { if (g_PlayerIsExitCamping[i]) { @@ -4781,9 +4741,9 @@ void SlenderOnClientStressUpdate(int client) break; } } - if (g_PlayerStressAmount[i] < preferredTeleportTargetStress || g_RestartSessionEnabled) + if (g_PlayerStressAmount[i] < preferredTeleportTargetStress || g_RestartSessionEnabled || g_SlenderTeleportIgnoreChases[Npc.Index]) { - if (g_SlenderTeleportPlayersRestTime[Npc.Index][i] <= GetGameTime()) + if (g_SlenderTeleportPlayersRestTime[Npc.Index][i] <= gameTime) { preferredTeleportTargetStress = g_PlayerStressAmount[i]; raidArrays.Push(i); @@ -4791,6 +4751,7 @@ void SlenderOnClientStressUpdate(int client) } } } + if (raidArrays != null && raidArrays.Length > 0) { int raidClient = raidArrays.Get(GetRandomInt(0, raidArrays.Length - 1)); @@ -4809,14 +4770,9 @@ void SlenderOnClientStressUpdate(int client) if (IsValidClient(preferredTeleportTarget)) { // Set our preferred target to the new guy. - float targetDuration = Npc.GetTeleportPersistencyPeriod(difficulty); - float deviation = GetRandomFloat(0.92, 1.08); - targetDuration = Pow(deviation * targetDuration, ((g_RoundDifficultyModifier) / 2.0)) + ((deviation * targetDuration) - 1.0); - g_SlenderTeleportTarget[Npc.Index] = EntIndexToEntRef(preferredTeleportTarget); g_SlenderTeleportPlayersRestTime[Npc.Index][preferredTeleportTarget] = -1.0; - g_SlenderTeleportMaxTargetTime[Npc.Index] = GetGameTime() + targetDuration; - g_SlenderTeleportTargetTime[Npc.Index] = GetGameTime(); + g_SlenderTeleportTargetTime[Npc.Index] = gameTime; g_SlenderTeleportMaxTargetStress[Npc.Index] = targetStress; teleportTarget = preferredTeleportTarget; @@ -4834,7 +4790,7 @@ void GetPageEntities(ArrayList array) for (int i = g_Pages.Length - 1; i >= 0; i--) { int pageEntIndex = EntRefToEntIndex(g_Pages.Get(i, SF2PageEntityData::EntRef)); - if (pageEntIndex != INVALID_ENT_REFERENCE) + if (pageEntIndex && pageEntIndex != INVALID_ENT_REFERENCE) { array.Push(pageEntIndex); } @@ -4960,23 +4916,56 @@ void SetPageCount(int num) ArrayList clientSwap = new ArrayList(); for (int client = 1; client <= MaxClients; client++) { - if (!IsValidClient(client)) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) { continue; } - if (!IsPlayerAlive(client)) + if (!player.IsAlive) { continue; } - if (g_PlayerEliminated[client]) + if (player.IsEliminated) { continue; } - if (DidClientEscape(client)) + if (player.HasEscaped) { continue; } - if (IsClientInDeathCam(client)) + if (player.IsInDeathCam) + { + continue; + } + bool isBossNear = false; + float clientPos[3]; + player.GetAbsOrigin(clientPos); + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(bossIndex); + if (!npc.IsValid()) + { + continue; + } + if (!npc.EntIndex || npc.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + float bossPos[3]; + SF2_BaseBoss baseBoss = SF2_BaseBoss(npc.EntIndex); + baseBoss.GetAbsOrigin(bossPos); + if (GetVectorSquareMagnitude(bossPos, clientPos) <= Pow(750.0, 2.0)) + { + isBossNear = true; + break; + } + if (baseBoss.Target == player) + { + isBossNear = true; + break; + } + } + if (isBossNear) { continue; } @@ -5105,12 +5094,22 @@ void SetPageCount(int num) { continue; } + SF2BossProfileData data; + data = NPCGetProfileData(npcIndex); + + if (data.SlaughterRunData.SpawnTime[difficulty] > 0.0) + { + times[bosses] = data.SlaughterRunData.SpawnTime[difficulty]; + bosses++; + continue; + } float originalSpeed, speed, timerCheck; - originalSpeed = NPCGetSpeed(npcIndex, difficulty) + NPCGetAddSpeed(npcIndex); - if (originalSpeed < 600.0) + originalSpeed = data.RunSpeed[difficulty] + NPCGetAddSpeed(npcIndex); + float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; + if (originalSpeed < slaughterSpeed) { - originalSpeed = 600.0; + originalSpeed = slaughterSpeed; } if (g_RoundDifficultyModifier > 1.0) { @@ -5137,29 +5136,6 @@ void SetPageCount(int num) { averageTime += (times[i3] / GetRandomFloat(12.0, 22.0)); } - switch (g_DifficultyConVar.IntValue) - { - case Difficulty_Normal: - { - averageTime += 1.0; - } - case Difficulty_Hard: - { - averageTime += 2.0; - } - case Difficulty_Insane: - { - averageTime += 3.0; - } - case Difficulty_Nightmare: - { - averageTime += 4.0; - } - case Difficulty_Apollyon: - { - averageTime += 5.0; - } - } PrintToChatAll("Time before bosses spawn: %f seconds", averageTime); CreateTimer(averageTime, Timer_SlaughterRunSpawnBosses, _, TIMER_FLAG_NO_MAPCHANGE); } @@ -5190,11 +5166,16 @@ void SetPageCount(int num) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + if (player.IsInDeathCam) { continue; } - if (!g_PlayerEliminated[i] || IsClientInGhostMode(i)) + if (!player.IsEliminated || player.IsInGhostMode) { if (g_PageCount) { @@ -5244,13 +5225,12 @@ void SetPageCount(int num) if (SF_SpecialRound(SPECIALROUND_LASTRESORT)) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); AddProfile(buffer); } - delete selectableBosses; } } else @@ -5315,7 +5295,7 @@ static Action Timer_SlaughterRunSpawnBosses(Handle timer) { continue; } - Npc.UnSpawn(); + Npc.UnSpawn(true); if (spawnPoint.Length > 0) { ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); @@ -5331,11 +5311,14 @@ static Action Timer_SlaughterRunSpawnBosses(Handle timer) return Plugin_Stop; } -bool Player_FindFreePosition2(int client, float position[3], float mins[3], float maxs[3]) +static bool Player_FindFreePosition2(int client, float position[3], float mins[3], float maxs[3]) { int team = GetClientTeam(client); int mask = MASK_RED; - if (team != TFTeam_Red) mask = MASK_BLUE; + if (team != TFTeam_Red) + { + mask = MASK_BLUE; + } // -90 to 90 float pitchMin = 75.0; // down @@ -5387,24 +5370,22 @@ bool Player_FindFreePosition2(int client, float position[3], float mins[3], floa } return false; } + static bool TraceFilter_NotTeam(int entity, int contentsMask, int team) { if (entity >= 1 && entity <= MaxClients && GetClientTeam(entity) == team) { return false; } - if (IsValidEdict(entity)) + + if (IsValidEdict(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } + return false; } return true; } -static int GetTextEntity(const char[] tempTargetName, bool bCaseSensitive = true) + +static int GetTextEntity(const char[] tempTargetName, bool caseSensitive = true) { // Try to see if we can use a custom message instead of the default. char targetName[64]; @@ -5414,7 +5395,7 @@ static int GetTextEntity(const char[] tempTargetName, bool bCaseSensitive = true GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); if (targetName[0] != '\0') { - if (strcmp(targetName, tempTargetName, bCaseSensitive) == 0) + if (strcmp(targetName, tempTargetName, caseSensitive) == 0) { return ent; } @@ -5532,7 +5513,7 @@ void DistributeQueuePointsToPlayers() for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -5576,9 +5557,9 @@ void DistributeQueuePointsToPlayers() /** * Sets the player to the correct team if needed. Returns true if a change was necessary, false if no change occurred. */ -bool HandlePlayerTeam(int client, bool respawn=true) +bool HandlePlayerTeam(int client, bool respawn = true) { - if (!IsClientInGame(client) || !IsClientParticipating(client)) + if (!IsValidClient(client) || !IsClientParticipating(client)) { return false; } @@ -5589,10 +5570,10 @@ bool HandlePlayerTeam(int client, bool respawn=true) { if (respawn) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); TF2_RemoveCondition(client, TFCond_SpawnOutline); ChangeClientTeamNoSuicide(client, TFTeam_Red); } @@ -5610,10 +5591,10 @@ bool HandlePlayerTeam(int client, bool respawn=true) { if (respawn) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); TF2_RemoveCondition(client, TFCond_SpawnOutline); ChangeClientTeamNoSuicide(client, TFTeam_Blue); } @@ -5631,7 +5612,7 @@ bool HandlePlayerTeam(int client, bool respawn=true) void HandlePlayerIntroState(int client) { - if (!IsClientInGame(client) || !IsPlayerAlive(client) || !IsClientParticipating(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client) || !IsClientParticipating(client)) { return; } @@ -5833,7 +5814,7 @@ int GetClientForDeath(int exclude1, int exclude2 = 0) // Use AFKs first for (int i = 1; i <= MaxClients; i++) { - if (i != exclude1 && i != exclude2 && IsClientInGame(i) && GetClientTeam(i) > TFTeam_Spectator && g_PlayerNoPoints[i]) + if (i != exclude1 && i != exclude2 && IsValidClient(i) && GetClientTeam(i) > TFTeam_Spectator && g_PlayerNoPoints[i]) { return i; } @@ -5842,7 +5823,7 @@ int GetClientForDeath(int exclude1, int exclude2 = 0) // Use BLU second for (int i = 1; i <= MaxClients; i++) { - if (i != exclude1 && i != exclude2 && IsClientInGame(i) && GetClientTeam(i) == TFTeam_Blue) + if (i != exclude1 && i != exclude2 && IsValidClient(i) && GetClientTeam(i) == TFTeam_Blue) { return i; } @@ -5851,7 +5832,7 @@ int GetClientForDeath(int exclude1, int exclude2 = 0) // Anyone else last for (int i = 1; i <= MaxClients; i++) { - if (i != exclude1 && i != exclude2 && IsClientInGame(i)) + if (i != exclude1 && i != exclude2 && IsValidClient(i)) { return i; } @@ -5878,6 +5859,7 @@ Action Timer_ToggleGhostModeCommand(Handle timer, any userid) CPrintToChat(client, "{red}%T", "SF2 Ghost Mode Not Allowed", client); return Plugin_Stop; } + if (!IsClientInGhostMode(client)) { TF2_RespawnPlayer(client); @@ -5896,23 +5878,15 @@ Action Timer_SendDeath(Handle timer, Event event) int client = GetClientOfUserId(event.GetInt("userid")); if (client > 0) { - int ignore = event.GetInt("ignore"); - if (!ignore) - { - //Delay event until their name is correct - int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (attacker > 0 && attacker <= MaxClients && g_TimerChangeClientName[attacker]) - { - return Plugin_Continue; - } - } - //Send it to the clients - for (int i = 1; i<=MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { - if (i != ignore && IsValidClient(i)) + if (IsValidClient(i)) { - if (!g_PlayerEliminated[client] || g_PlayerEliminated[i] || GetClientTeam(client) == GetClientTeam(i)) event.FireToClient(i); + if (!g_PlayerEliminated[client] || g_PlayerEliminated[i] || GetClientTeam(client) == GetClientTeam(i)) + { + event.FireToClient(i); + } } } } @@ -5920,22 +5894,10 @@ Action Timer_SendDeath(Handle timer, Event event) return Plugin_Stop; } -Action Timer_SendDeathToSpecific(Handle timer, Event event) -{ - int client = GetClientOfUserId(event.GetInt("send")); - if (client > 0) - { - event.FireToClient(client); - } - - event.Cancel(); - return Plugin_Stop; -} - Action Timer_RevertClientName(Handle timer, int index) { g_TimerChangeClientName[index] = null; - if (IsClientInGame(index)) + if (IsValidClient(index)) { SetClientName(index, g_OldClientName[index]); SetEntPropString(index, Prop_Data, "m_szNetname", g_OldClientName[index]); @@ -5955,7 +5917,7 @@ Action Timer_CheckAlivePlayers(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i) || !IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -5979,35 +5941,24 @@ Action Timer_CheckAlivePlayers(Handle timer) { SetRoundTime(120); CPrintToChatAll("Only 1 {red}RED{default} player is alive, 2 minutes left on the timer..."); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); - if (Npc.UniqueID == -1) - { - continue; - } - Npc.SetAddSpeed(50.0); - Npc.SetAddMaxSpeed(75.0); - Npc.SetAddAcceleration(250.0); - } - g_PlayersAreCritted = true; + } else { CPrintToChatAll("Only 1 {red}RED{default} player is alive..."); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + } + + for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(npcIndex); + if (npc.UniqueID == -1) { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); - if (Npc.UniqueID == -1) - { - continue; - } - Npc.SetAddSpeed(50.0); - Npc.SetAddMaxSpeed(75.0); - Npc.SetAddAcceleration(250.0); + continue; } - g_PlayersAreCritted = true; + npc.SetAddSpeed(50.0); + npc.SetAddAcceleration(250.0); } + g_PlayersAreCritted = true; } } case 2, 3: @@ -6146,7 +6097,7 @@ Action Timer_ReplacePlayerRagdoll(Handle timer, any userid) force[0] = 40.0; force[1] = 40.0; force[2] = 40.0; - MakeVectorFromPoints(pos, view_as( { 0.0, 0.0, 0.0 } ), velocity); + MakeVectorFromPoints(pos, { 0.0, 0.0, 0.0 }, velocity); ScaleVector(velocity, 20000.0); ScaleVector(force, 20000.0); SetEntPropVector(ent, Prop_Send, "m_vecForce", force); @@ -6421,24 +6372,19 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) { return Plugin_Stop; } - int slender = EntRefToEntIndex(g_PlayerBossKillSubject[client]); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - int bossIndex = NPCGetFromEntIndex(slender); + int bossIndex = g_PlayerBossKillSubject[client]; if (bossIndex == -1) { return Plugin_Stop; } - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); int ragdoll = GetEntPropEnt(client, Prop_Send, "m_hRagdoll"); if (!IsValidEntity(ragdoll)) { return Plugin_Stop; } - if (!g_SlenderHasDeleteKillEffect[bossIndex]) + if (!data.DeleteRagdoll) { int ent = CreateEntityByName("tf_ragdoll", -1); if (ent != -1) @@ -6450,23 +6396,22 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) GetEntPropVector(ragdoll, Prop_Data, "m_angAbsRotation", ang); TeleportEntity(ent, pos, ang, NULL_VECTOR); SetEntPropVector(ent, Prop_Send, "m_vecRagdollOrigin", pos); - if (g_SlenderHasPushRagdollOnKill[bossIndex]) + if (data.PushRagdoll) { - float vectorForce[3]; - GetBossProfilePushRagdollForce(profile, vectorForce); - SetEntPropVector(ent, Prop_Send, "m_vecRagdollVelocity", vectorForce); - SetEntPropVector(ent, Prop_Send, "m_vecForce", vectorForce); + force = data.PushRagdollForce; + SetEntPropVector(ent, Prop_Send, "m_vecRagdollVelocity", force); + SetEntPropVector(ent, Prop_Send, "m_vecForce", force); } else { SetEntPropVector(ent, Prop_Send, "m_vecRagdollVelocity", velocity); SetEntPropVector(ent, Prop_Send, "m_vecForce", force); } - if (g_SlenderHasResizeRagdollOnKill[bossIndex]) + if (data.ResizeRagdoll) { - SetEntPropFloat(ent, Prop_Send, "m_flHeadScale", g_SlenderResizeRagdollHead[bossIndex]); - SetEntPropFloat(ent, Prop_Send, "m_flTorsoScale", g_SlenderResizeRagdollTorso[bossIndex]); - SetEntPropFloat(ent, Prop_Send, "m_flHandScale", g_SlenderResizeRagdollHands[bossIndex]); + SetEntPropFloat(ent, Prop_Send, "m_flHeadScale", data.ResizeRagdollHead); + SetEntPropFloat(ent, Prop_Send, "m_flTorsoScale", data.ResizeRagdollTorso); + SetEntPropFloat(ent, Prop_Send, "m_flHandScale", data.ResizeRagdollHands); } else { @@ -6477,7 +6422,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_nForceBone", GetEntProp(ragdoll, Prop_Send, "m_nForceBone")); SetEntProp(ent, Prop_Send, "m_bOnGround", GetEntProp(ragdoll, Prop_Send, "m_bOnGround")); - if (g_SlenderHasCloakKillEffect[bossIndex]) + if (data.CloakRagdoll) { SetEntProp(ent, Prop_Send, "m_bCloaked", true); } @@ -6492,7 +6437,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bWasDisguised", GetEntProp(ragdoll, Prop_Send, "m_bWasDisguised")); SetEntProp(ent, Prop_Send, "m_bFeignDeath", GetEntProp(ragdoll, Prop_Send, "m_bFeignDeath")); - if (g_SlenderHasGibKillEffect[bossIndex]) + if (data.GibRagdoll) { SetEntProp(ent, Prop_Send, "m_bGib", true); } @@ -6501,11 +6446,11 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bGib", GetEntProp(ragdoll, Prop_Send, "m_bGib")); } - if (g_SlenderHasDecapKillEffect[bossIndex]) + if (data.DecapRagdoll) { SetEntProp(ent, Prop_Send, "m_iDamageCustom", TF_CUSTOM_DECAPITATION); } - else if (g_SlenderHasPlasmaRagdollOnKill[bossIndex]) + else if (data.PlasmaRagdoll) { SetEntProp(ent, Prop_Send, "m_iDamageCustom", TF_CUSTOM_PLASMA); } @@ -6514,7 +6459,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_iDamageCustom", GetEntProp(ragdoll, Prop_Send, "m_iDamageCustom")); } - if (g_SlenderHasBurnKillEffect[bossIndex]) + if (data.BurnRagdoll) { SetEntProp(ent, Prop_Send, "m_bBurning", true); } @@ -6523,7 +6468,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bBurning", GetEntProp(ragdoll, Prop_Send, "m_bBurning")); } - if (g_SlenderHasAshKillEffect[bossIndex]) + if (data.AshRagdoll) { SetEntProp(ent, Prop_Send, "m_bBecomeAsh", true); } @@ -6532,7 +6477,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bBecomeAsh", GetEntProp(ragdoll, Prop_Send, "m_bBecomeAsh")); } - if (g_SlenderHasGoldKillEffect[bossIndex]) + if (data.GoldRagdoll) { SetEntProp(ent, Prop_Send, "m_bGoldRagdoll", true); } @@ -6541,7 +6486,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bGoldRagdoll", GetEntProp(ragdoll, Prop_Send, "m_bGoldRagdoll")); } - if (g_SlenderHasIceKillEffect[bossIndex]) + if (data.IceRagdoll) { SetEntProp(ent, Prop_Send, "m_bIceRagdoll", true); } @@ -6550,7 +6495,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bIceRagdoll", GetEntProp(ragdoll, Prop_Send, "m_bIceRagdoll")); } - if (g_SlenderHasElectrocuteKillEffect[bossIndex]) + if (data.ElectrocuteRagdoll) { SetEntProp(ent, Prop_Send, "m_bElectrocuted", true); } @@ -6563,7 +6508,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) ActivateEntity(ent); SetEntPropEnt(client, Prop_Send, "m_hRagdoll", ent, 0); } - if (g_SlenderHasDissolveRagdollOnKill[bossIndex]) + if (data.DissolveRagdoll) { int dissolver = CreateEntityByName("env_entity_dissolver"); if (!IsValidEntity(dissolver)) @@ -6571,7 +6516,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) return Plugin_Stop; } char type[2]; - int typeInt = g_SlenderDissolveRagdollType[bossIndex]; + int typeInt = data.DissolveKillType; FormatEx(type, sizeof(type), "%d", typeInt); DispatchKeyValue(dissolver, "dissolvetype", type); DispatchKeyValue(dissolver, "magnitude", "1"); @@ -6623,7 +6568,7 @@ Action Timer_PlayerSwitchToBlue(Handle timer, any userid) ChangeClientTeam(client, TFTeam_Blue); - if (TF2_GetPlayerClass(client) == view_as(0)) + if (TF2_GetPlayerClass(client) == TFClass_Unknown) { // Player hasn't chosen a class for some reason. Choose one for him. TF2_SetPlayerClass(client, view_as(GetRandomInt(1, 9)), true, true); @@ -6632,38 +6577,6 @@ Action Timer_PlayerSwitchToBlue(Handle timer, any userid) return Plugin_Stop; } -stock int ProjectileGetFlags(int projectile) -{ - return g_ProjectileFlags[projectile]; -} - -stock void ProjectileSetFlags(int projectile, int iFlags) -{ - g_ProjectileFlags[projectile] = iFlags; -} - -stock int AttachParticle(int entity, char[] particleType, float posOffset[3] = { 0.0, 0.0, 0.0 } ) -{ - int particle = CreateEntityByName("info_particle_system"); - - if (IsValidEntity(particle)) - { - SetEntPropEnt(particle, Prop_Data, "m_hOwnerEntity", entity); - DispatchKeyValue(particle, "effect_name", particleType); - SetVariantString("!activator"); - AcceptEntityInput(particle, "SetParent", entity, particle, 0); - float vec_start[3]; - TeleportEntity(particle, vec_start, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(particle); - - AcceptEntityInput(particle, "start"); - ActivateEntity(particle); - - return EntIndexToEntRef(particle); - } - return -1; -} - void CreateGeneralParticle(int entity, const char[] sectionName, float particleZPos = 0.0) { if (entity == -1) @@ -6701,21 +6614,17 @@ static Action Timer_RoundStart(Handle timer) { if (g_PageMax > 0) { - ArrayList arrayClients = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for arrayClients in Timer_RoundStart.", arrayClients); - #endif int clients[MAXTF2PLAYERS]; int clientsNum = 0; for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i) || g_PlayerEliminated[i]) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsBot || player.IsEliminated) { continue; } - arrayClients.Push(GetClientUserId(i)); clients[clientsNum] = i; clientsNum++; } @@ -6723,12 +6632,9 @@ static Action Timer_RoundStart(Handle timer) // Show difficulty menu. if (!SF_IsBoxingMap() && !SF_IsRenevantMap() && !SF_SpecialRound(SPECIALROUND_MODBOSSES)) { - if (clientsNum) + if (clientsNum > 0) { - // Automatically set it to Normal. - g_DifficultyConVar.SetInt(Difficulty_Normal); - - g_VoteTimer = CreateTimer(1.0, Timer_VoteDifficulty, arrayClients, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_VoteTimer = CreateTimer(1.0, Timer_VoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); TriggerTimer(g_VoteTimer, true); int gameText = -1; @@ -6753,20 +6659,6 @@ static Action Timer_RoundStart(Handle timer) } } } - else - { - delete arrayClients; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for arrayClients in Timer_RoundStart due to 0 clients.", arrayClients); - #endif - } - } - else - { - delete arrayClients; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for arrayClients in Timer_RoundStart.", arrayClients); - #endif } } @@ -6805,14 +6697,13 @@ static Action Timer_RoundTime(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) { continue; } float buffer[3]; GetClientAbsOrigin(i, buffer); - ClientStartDeathCam(i, 0, buffer, true); if (SF_SpecialRound(SPECIALROUND_1UP)) { g_PlayerDied1Up[i] = false; @@ -6837,7 +6728,7 @@ static Action Timer_RoundTime(Handle timer) g_SpecialRoundTime++; } - if (!g_RoundTimerPaused) + if (!g_RoundTimerPaused && !IsBeatBoxBeating(2)) { SetRoundTime(g_RoundTime - 1); } @@ -6854,7 +6745,7 @@ static Action Timer_RoundTime(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) + if (!IsValidClient(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) { continue; } @@ -6886,14 +6777,13 @@ static Action Timer_RoundTimeEscape(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i) || DidClientEscape(i)) + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i) || DidClientEscape(i)) { continue; } float buffer[3]; GetClientAbsOrigin(i, buffer); - ClientStartDeathCam(i, 0, buffer, true); if (SF_SpecialRound(SPECIALROUND_1UP)) { g_PlayerDied1Up[i] = false; @@ -6940,7 +6830,7 @@ static Action Timer_RoundTimeEscape(Handle timer) int alivePlayer = 0; for (int client = 1; client <= MaxClients; client++) { - if (IsClientInGame(client) && IsPlayerAlive(client) && !g_PlayerEliminated[client]) + if (IsValidClient(client) && IsPlayerAlive(client) && !g_PlayerEliminated[client]) { alivePlayer++; } @@ -6971,7 +6861,7 @@ static Action Timer_RoundTimeEscape(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) + if (!IsValidClient(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) { continue; } @@ -7020,46 +6910,40 @@ static Action Timer_RoundTimeEscape(Handle timer) return Plugin_Continue; } -static Action Timer_VoteDifficulty(Handle timer, any data) +static Action Timer_VoteDifficulty(Handle timer) { - ArrayList arrayClients = view_as(data); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for arrayClients in Timer_VoteDifficulty.", arrayClients); - #endif - if (timer != g_VoteTimer || IsRoundEnding()) { - delete arrayClients; return Plugin_Stop; } - if (IsVoteInProgress()) + if (IsVoteInProgress() || NativeVotes_IsVoteInProgress()) { return Plugin_Continue; // There's another vote in progess. Wait. } int clients[MAXTF2PLAYERS] = { -1, ... }; int clientsNum; - for (int i = 0, size = arrayClients.Length; i < size; i++) + for (int i = 1; i <= MaxClients; i++) { - int client = GetClientOfUserId(arrayClients.Get(i)); - if (client <= 0) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsBot || player.IsEliminated) { continue; } - clients[clientsNum] = client; + clients[clientsNum] = player.index; clientsNum++; } - delete arrayClients; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for arrayClients in Timer_VoteDifficulty.", arrayClients); - #endif + if (clientsNum == 0) + { + return Plugin_Stop; + } RandomizeVoteMenu(); - VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, 15); - if (GetMenuItemCount(g_MenuVoteDifficulty) == 1) + g_MenuVoteDifficulty.DisplayVote(clients, clientsNum, 15); + if (g_MenuVoteDifficulty.ItemCount == 1) { for (int i = 0; i < clientsNum; i++) { @@ -7092,7 +6976,7 @@ void SF_FailEnd() static Action Timer_Fail(Handle timer) { - LogSF2Message("Wow you hit a rare bug, where the round doesn't end after the timer ran out. Collecting info on your game...\nContact Mentrillum or The Gaben and give them the following log:"); + LogSF2Message("Wow you hit a rare bug, where the round doesn't end after the timer ran out. Collecting info on your game...\nContact Mentrillum and give them the following log:"); int escapedPlayers = 0; int clientInGame = 0; int redPlayers = 0; @@ -7440,6 +7324,7 @@ static void InitializeMapEntities() static void SpawnPages() { g_Pages.Clear(); + g_EmptySpawnPagePoints.Clear(); ArrayList array = new ArrayList(2); StringMap pageGroupsByName = new StringMap(); @@ -7450,7 +7335,6 @@ static void SpawnPages() char targetName[64]; // Collect all possible page spawn points. - ent = -1; while ((ent = FindEntityByClassname(ent, "info_target")) != -1) { GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); @@ -7540,7 +7424,7 @@ static void SpawnPages() // Spawn all pages. array.Sort(Sort_Random, Sort_Integer); - float vecPos[3], vecAng[3]; + float pos[3], angle[3]; int page; char pageModel[PLATFORM_MAX_PATH]; @@ -7553,12 +7437,28 @@ static void SpawnPages() int pageRenderColor[4]; char pageAnimation[64]; + for (int i = 0, size = array.Length; i < size; i++) + { + if (array.Get(i, 1) != 0) + { + ArrayList grouped = array.Get(i); + for (int i2 = 0; i2 < grouped.Length; i2++) + { + g_EmptySpawnPagePoints.Push(EntRefToEntIndex(grouped.Get(i2))); + } + } + else + { + g_EmptySpawnPagePoints.Push(EntRefToEntIndex(array.Get(i))); + } + } + for (int i = 0; i < pageCount && (i + 1) <= g_PageMax; i++) { int spawnPointEnt = -1; - if (!!array.Get(i, 1)) + if (array.Get(i, 1) != 0) { - ArrayList buttStallion = view_as(array.Get(i)); + ArrayList buttStallion = array.Get(i); spawnPointEnt = buttStallion.Get(GetRandomInt(0, buttStallion.Length - 1)); } else @@ -7568,8 +7468,8 @@ static void SpawnPages() SF2PageSpawnEntity spawnPoint = SF2PageSpawnEntity(spawnPointEnt); - GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", vecPos); - GetEntPropVector(spawnPointEnt, Prop_Data, "m_angAbsRotation", vecAng); + GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(spawnPointEnt, Prop_Data, "m_angAbsRotation", angle); GetEntPropString(spawnPointEnt, Prop_Data, "m_iParent", pageParentName, sizeof(pageParentName)); // Get model, scale, skin, and animation. @@ -7616,7 +7516,7 @@ static void SpawnPages() DispatchKeyValue(page2, "parentname", pageParentName); DispatchKeyValue(page2, "solid", "0"); SetEntityModel(page2, pageModel); - TeleportEntity(page2, vecPos, vecAng, NULL_VECTOR); + TeleportEntity(page2, pos, angle, NULL_VECTOR); DispatchSpawn(page2); ActivateEntity(page2); SetVariantInt(pageSkin); @@ -7651,7 +7551,7 @@ static void SpawnPages() DispatchKeyValue(page, "parentname", pageParentName); DispatchKeyValue(page, "solid", "2"); SetEntityModel(page, pageModel); - TeleportEntity(page, vecPos, vecAng, NULL_VECTOR); + TeleportEntity(page, pos, angle, NULL_VECTOR); DispatchSpawn(page); ActivateEntity(page); SetVariantInt(pageSkin); @@ -7680,6 +7580,8 @@ static void SpawnPages() SF2PageEntityData pageData; pageData.EntRef = EnsureEntRef(page); + pageData.Pos = pos; + pageData.Ang = angle; if (spawnPoint.IsValid()) { @@ -7694,12 +7596,18 @@ static void SpawnPages() g_Pages.PushArray(pageData, sizeof(pageData)); } + + int index = g_EmptySpawnPagePoints.FindValue(EntRefToEntIndex(spawnPointEnt)); + if (index != -1) + { + g_EmptySpawnPagePoints.Erase(index); + } } // Safely remove all handles. for (int i = 0, size = array.Length; i < size; i++) { - if (!!array.Get(i, 1)) + if (array.Get(i, 1) != 0) { delete view_as(array.Get(i)); } @@ -7716,13 +7624,14 @@ static void SpawnPages() static Action Page_RemoveAlwaysTransmit(Handle timer, int ref) { int page = EntRefToEntIndex(ref); - if (page > MaxClients) + if (page && page != INVALID_ENT_REFERENCE) { //All the pages are now "registred" by the client, nuke the always transmit flag. CBaseEntity(page).DispatchUpdateTransmitState(); } return Plugin_Stop; } + static bool HandleSpecialRoundState() { #if defined DEBUG @@ -7747,7 +7656,7 @@ static bool HandleSpecialRoundState() // Check if there are players who haven't played the special round yet. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedSpecialRound[i] = true; continue; @@ -7825,10 +7734,6 @@ static ArrayList GetNewBossRoundProfileList() { ArrayList bossList = GetSelectableBossProfileQueueList().Clone(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been created for bossList in GetNewBossRoundProfileList.", bossList); - #endif - if (bossList.Length > 0) { char mainBoss[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -7874,7 +7779,7 @@ static void HandleNewBossRoundState() // Check if there are players who haven't played the boss round yet. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedNewBossRound[i] = true; continue; @@ -7913,7 +7818,7 @@ static void HandleNewBossRoundState() // Check if we have enough bosses. if (g_NewBossRound) { - ArrayList bossList = GetNewBossRoundProfileList().Clone(); + ArrayList bossList = GetNewBossRoundProfileList(); if (bossList.Length < 1) { @@ -7921,10 +7826,6 @@ static void HandleNewBossRoundState() } delete bossList; - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been deleted for bossList in HandleNewBossRoundState which comes from GetNewBossRoundProfileList.", bossList); - #endif } if (g_NewBossRound) @@ -7969,7 +7870,7 @@ int GetActivePlayerCount() for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { continue; } @@ -7992,11 +7893,10 @@ static void SelectStartingBossesForRound() } #endif - ArrayList selectableBossList = GetSelectableBossProfileQueueList().Clone(); + Call_StartForward(g_OnPreSelectStartingBosses); + Call_Finish(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been created for selectableBossList in SelectStartingBossesForRound.", selectableBossList); - #endif + ArrayList selectableBossList = GetSelectableBossProfileQueueList(); // Select which boss profile to use. char profileOverride[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -8019,10 +7919,6 @@ static void SelectStartingBossesForRound() bossList.GetString(GetRandomInt(0, bossList.Length - 1), g_NewBossRoundProfileRoundProfile, sizeof(g_NewBossRoundProfileRoundProfile)); delete bossList; - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been deleted for bossList in SelectStartingBossesForRound which comes from GetNewBossRoundProfileList.", bossList); - #endif } strcopy(g_RoundBossProfile, sizeof(g_RoundBossProfile), g_NewBossRoundProfileRoundProfile); @@ -8075,10 +7971,6 @@ static void SelectStartingBossesForRound() bossList.GetString(GetRandomInt(0, bossList.Length - 1), g_NewBossRoundProfileRoundProfile, sizeof(g_NewBossRoundProfileRoundProfile)); delete bossList; - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been deleted for bossList in SelectStartingBossesForRound which comes from GetNewBossRoundProfileList.", bossList); - #endif } strcopy(g_RoundBoxingBossProfile, sizeof(g_RoundBoxingBossProfile), g_NewBossRoundProfileRoundProfile); @@ -8114,7 +8006,6 @@ static void SelectStartingBossesForRound() } #endif } - delete selectableBossList; } static void GetRoundIntroParameters() @@ -8216,6 +8107,7 @@ void InitializeNewGame() g_RenevantBeaconEffect = false; g_Renevant90sEffect = false; g_RenevantMarkForDeath = false; + g_RenevantBossesChaseEndlessly = false; Renevant_SetWave(0); } @@ -8245,7 +8137,7 @@ void InitializeNewGame() HandleSpecialRoundState(); // Was a new special round initialized? - if (g_IsSpecialRound && !SF_IsRenevantMap()) + if (g_IsSpecialRound && !SF_IsRenevantMap() && !SF_IsBoxingMap() && !SF_IsRaidMap()) { if (g_IsSpecialRoundNew) { @@ -8259,7 +8151,7 @@ void InitializeNewGame() // Initialize all players' values. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedSpecialRound[i] = true; continue; @@ -8306,7 +8198,7 @@ void InitializeNewGame() // Initialize all players' values. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedNewBossRound[i] = true; continue; @@ -8326,6 +8218,10 @@ void InitializeNewGame() ForceInNextPlayersInQueue(GetMaxPlayersForRound()); + Call_StartForward(g_OnPostSelectStartingBosses); + Call_PushStringEx(g_RoundBossProfile, sizeof(g_RoundBossProfile), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + Call_Finish(); + // Respawn all players, if needed. for (int i = 1; i <= MaxClients; i++) { @@ -8346,7 +8242,7 @@ void InitializeNewGame() { for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -8376,7 +8272,7 @@ void InitializeNewGame() { if (!SF_IsBoxingMap() && !SF_IsRenevantMap()) { - if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_DOOMBOX) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_2DOOM)) + if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_SILENTSLENDER) || SF_SpecialRound(SPECIALROUND_2DOUBLE)) { AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); @@ -8388,28 +8284,30 @@ void InitializeNewGame() AddProfile(g_RoundBossProfile, _, _, _, false); RemoveBossProfileFromQueueList(g_RoundBossProfile); } - else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_DOOMBOX) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_2DOOM) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) + else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_SILENTSLENDER) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) { - SelectProfile(view_as(0), g_RoundBossProfile); + AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); } } else if (SF_IsBoxingMap()) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBoxingBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBoxingBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); AddProfile(buffer); } - delete selectableBosses; } } } #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 0) DebugMessage("END InitializeNewGame()"); + if (g_DebugDetailConVar.IntValue > 0) + { + DebugMessage("END InitializeNewGame()"); + } #endif } @@ -8420,10 +8318,6 @@ static Action Timer_PlayIntroMusicToPlayer(Handle timer, any userid) { return Plugin_Stop; } - if (!IsClientInGame(client)) - { - return Plugin_Stop; - } if (timer != g_PlayerIntroMusicTimer[client]) { @@ -8481,7 +8375,10 @@ static Action Timer_NewIntroTextSequence(Handle timer, any data) for (int client = 1; client <= MaxClients; client++) { - if (!IsClientInGame(client) || g_PlayerEliminated[client]) continue; + if (!IsValidClient(client) || g_PlayerEliminated[client]) + { + continue; + } clients[clientsNum] = client; clientsNum++; @@ -8527,7 +8424,8 @@ static Action Timer_IntroTextSequence(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsEliminated) { continue; } @@ -8538,10 +8436,10 @@ static Action Timer_IntroTextSequence(Handle timer) if (!g_RoundIntroTextDefault) { - char sTargetname[64]; - FormatEx(sTargetname, sizeof(sTargetname), "sf2_intro_text_%d", g_RoundIntroText); + char targetname[64]; + FormatEx(targetname, sizeof(targetname), "sf2_intro_text_%d", g_RoundIntroText); - int gameText = FindEntityByTargetname(sTargetname, "game_text"); + int gameText = FindEntityByTargetname(targetname, "game_text"); if (gameText && gameText != INVALID_ENT_REFERENCE) { foundGameText = true; @@ -8614,7 +8512,7 @@ static Action Timer_ActivateRoundFromIntro(Handle timer) { if (!SF_IsBoxingMap() && !SF_IsRenevantMap()) { - if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_DOOMBOX) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_2DOOM)) + if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_SILENTSLENDER) || SF_SpecialRound(SPECIALROUND_2DOUBLE)) { AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); @@ -8626,22 +8524,21 @@ static Action Timer_ActivateRoundFromIntro(Handle timer) AddProfile(g_RoundBossProfile, _, _, _, false); RemoveBossProfileFromQueueList(g_RoundBossProfile); } - else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_DOOMBOX) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_2DOOM) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) + else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_SILENTSLENDER) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) { - SelectProfile(view_as(0), g_RoundBossProfile); + AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); } } else if (SF_IsBoxingMap()) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBoxingBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBoxingBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); AddProfile(buffer); } - delete selectableBosses; } } return Plugin_Stop; @@ -8660,7 +8557,7 @@ void CheckRoundWinConditions() for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -8711,3 +8608,17 @@ void CheckRoundWinConditions() } } } + +// Fixes setting the same difficulty not calling our forwards +void SetDifficulty(int difficulty) +{ + char last[16], newDiff[16]; + g_DifficultyConVar.GetString(last, sizeof(last)); + IntToString(difficulty, newDiff, sizeof(newDiff)); + if (strcmp(last, newDiff, false) == 0) + { + StrCat(newDiff, sizeof(newDiff), " "); + } + + g_DifficultyConVar.SetString(newDiff); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/adminmenu.sp b/addons/sourcemod/scripting/sf2/adminmenu.sp index 5f2f34c9..81e458e0 100644 --- a/addons/sourcemod/scripting/sf2/adminmenu.sp +++ b/addons/sourcemod/scripting/sf2/adminmenu.sp @@ -7,9 +7,11 @@ static Handle g_TopMenu = null; static int g_PlayerAdminMenuTargetUserId[MAXTF2PLAYERS] = { -1, ... }; +static SF2NPC_Chaser g_SelectedBoss[MAXTF2PLAYERS]; void SetupAdminMenu() { + g_OnAdminMenuCreateOptionsPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); /* Account for late loading */ Handle topMenu = null; if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) @@ -18,8 +20,10 @@ void SetupAdminMenu() } } -public void OnAdminMenuReady(Handle topMenu) +public void OnAdminMenuReady(Handle topMenuHndl) { + TopMenu topMenu = TopMenu.FromHandle(topMenuHndl); + if (topMenu == g_TopMenu) { return; @@ -27,35 +31,46 @@ public void OnAdminMenuReady(Handle topMenu) g_TopMenu = topMenu; - TopMenuObject hServerCommands = FindTopMenuCategory(topMenu, ADMINMENU_SERVERCOMMANDS); - if (hServerCommands != INVALID_TOPMENUOBJECT) + TopMenuObject commands = topMenu.FindCategory("SF2Commands"); + if (commands == INVALID_TOPMENUOBJECT) { - AddToTopMenu(topMenu, "sf2_boss_admin_main", TopMenuObject_Item, AdminTopMenu_BossMain, hServerCommands, "sm_sf2_add_boss", ADMFLAG_SLAY); + commands = topMenu.AddCategory("SF2Commands", AdminTopMenu_Main); } - TopMenuObject hPlayerCommands = FindTopMenuCategory(topMenu, ADMINMENU_PLAYERCOMMANDS); - if (hPlayerCommands != INVALID_TOPMENUOBJECT) + if (commands != INVALID_TOPMENUOBJECT) + { + topMenu.AddItem("sf2_boss_admin_main", AdminTopMenu_BossMain, commands, "sm_sf2_add_boss", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_setplaystate", AdminTopMenu_PlayerSetPlayState, commands, "sm_sf2_setplaystate", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_force_proxy", AdminTopMenu_PlayerForceProxy, commands, "sm_sf2_force_proxy", ADMFLAG_SLAY); + } +} + +static void AdminTopMenu_Main(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if (action == TopMenuAction_DisplayTitle) + { + FormatEx(buffer, maxlength, "%T:", "SF2 Admin Menu Title", param); + } + else if (action == TopMenuAction_DisplayOption) { - AddToTopMenu(topMenu, "sf2_player_setplaystate", TopMenuObject_Item, AdminTopMenu_PlayerSetPlayState, hPlayerCommands, "sm_sf2_setplaystate", ADMFLAG_SLAY); - AddToTopMenu(topMenu, "sf2_player_force_proxy", TopMenuObject_Item, AdminTopMenu_PlayerForceProxy, hPlayerCommands, "sm_sf2_force_proxy", ADMFLAG_SLAY); + FormatEx(buffer, maxlength, "%T", "SF2 Admin Menu Title", param); } - delete topMenu; } static void DisplayPlayerForceProxyAdminMenu(int client) { - Handle menuHandle = CreateMenu(AdminMenu_PlayerForceProxy); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", client); + Menu menuHandle = new Menu(AdminMenu_PlayerForceProxy); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", client); AddTargetsToMenu(menuHandle, client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } -static int AdminTopMenu_PlayerForceProxy(Handle topmenu, TopMenuAction action, TopMenuObject object_id,int param, char[] buffer,int maxlength) +static int AdminTopMenu_PlayerForceProxy(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { - FormatEx(buffer, maxlength, "%t%T", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", param); + FormatEx(buffer, maxlength, "%t %T", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", param); } else if (action == TopMenuAction_SelectOption) { @@ -64,7 +79,7 @@ static int AdminTopMenu_PlayerForceProxy(Handle topmenu, TopMenuAction action, T return 0; } -static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerForceProxy(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -80,7 +95,7 @@ static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1, else if (action == MenuAction_Select) { char userId[64]; - GetMenuItem(menu, param2, userId, sizeof(userId)); + menu.GetItem(param2, userId, sizeof(userId)); int client = GetClientOfUserId(StringToInt(userId)); if (client <= 0) @@ -95,7 +110,7 @@ static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1, char name[MAX_NAME_LENGTH]; FormatEx(name, sizeof(name), "%N", client); - Handle menuHandle = CreateMenu(AdminMenu_PlayerForceProxyBoss); + Menu menuHandle = new Menu(AdminMenu_PlayerForceProxyBoss); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -104,16 +119,16 @@ static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1, } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy Boss", param1, name); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy Boss", param1, name); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int AdminMenu_PlayerForceProxyBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerForceProxyBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -136,7 +151,7 @@ static int AdminMenu_PlayerForceProxyBoss(Handle menu, MenuAction action,int par else { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -171,18 +186,18 @@ static int AdminMenu_PlayerForceProxyBoss(Handle menu, MenuAction action,int par static void DisplayPlayerSetPlayStateAdminMenu(int client) { - Handle menuHandle = CreateMenu(AdminMenu_PlayerSetPlayState); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", client); + Menu menuHandle = new Menu(AdminMenu_PlayerSetPlayState); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", client); AddTargetsToMenu(menuHandle, client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } -static int AdminTopMenu_PlayerSetPlayState(Handle topmenu, TopMenuAction action, TopMenuObject object_id,int param, char[] buffer,int maxlength) +static int AdminTopMenu_PlayerSetPlayState(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { - FormatEx(buffer, maxlength, "%t%T", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", param); + FormatEx(buffer, maxlength, "%t %T", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", param); } else if (action == TopMenuAction_SelectOption) { @@ -191,7 +206,7 @@ static int AdminTopMenu_PlayerSetPlayState(Handle topmenu, TopMenuAction action, return 0; } -static int AdminMenu_PlayerSetPlayState(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerSetPlayState(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -207,7 +222,7 @@ static int AdminMenu_PlayerSetPlayState(Handle menu, MenuAction action,int param else if (action == MenuAction_Select) { char userId[64]; - GetMenuItem(menu, param2, userId, sizeof(userId)); + menu.GetItem(param2, userId, sizeof(userId)); int client = GetClientOfUserId(StringToInt(userId)); if (client <= 0) { @@ -219,21 +234,21 @@ static int AdminMenu_PlayerSetPlayState(Handle menu, MenuAction action,int param char name[MAX_NAME_LENGTH]; FormatEx(name, sizeof(name), "%N", client); - Handle menuHandle = CreateMenu(AdminMenu_PlayerSetPlayStateConfirm); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State Confirm", param1, name); + Menu menuHandle = new Menu(AdminMenu_PlayerSetPlayStateConfirm); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State Confirm", param1, name); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "SF2 In", param1); - AddMenuItem(menuHandle, userId, buffer); + menuHandle.AddItem(userId, buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Out", param1); - AddMenuItem(menuHandle, userId, buffer); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.AddItem(userId, buffer); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } return 0; } -static int AdminMenu_PlayerSetPlayStateConfirm(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerSetPlayStateConfirm(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -249,7 +264,7 @@ static int AdminMenu_PlayerSetPlayStateConfirm(Handle menu, MenuAction action,in else if (action == MenuAction_Select) { char userId[64]; - GetMenuItem(menu, param2, userId, sizeof(userId)); + menu.GetItem(param2, userId, sizeof(userId)); int client = GetClientOfUserId(StringToInt(userId)); if (client <= 0) { @@ -276,32 +291,40 @@ static int AdminMenu_PlayerSetPlayStateConfirm(Handle menu, MenuAction action,in return 0; } -static void DisplayBossMainAdminMenu(int client) +void DisplayBossMainAdminMenu(int client) { - Handle menuHandle = CreateMenu(AdminMenu_BossMain); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Main", client); + Menu menuHandle = new Menu(AdminMenu_BossMain); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Main", client); char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Add Boss", client); - AddMenuItem(menuHandle, "add_boss", buffer); + menuHandle.AddItem("add_boss", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Add Fake Boss", client); - AddMenuItem(menuHandle, "add_boss_fake", buffer); + menuHandle.AddItem("add_boss_fake", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Remove Boss", client); - AddMenuItem(menuHandle, "remove_boss", buffer); + menuHandle.AddItem("remove_boss", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Spawn Boss", client); - AddMenuItem(menuHandle, "spawn_boss", buffer); + menuHandle.AddItem("spawn_boss", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Boss Attack Waiters", client); - AddMenuItem(menuHandle, "boss_attack_waiters", buffer); + menuHandle.AddItem("boss_attack_waiters", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Boss Teleport", client); - AddMenuItem(menuHandle, "boss_no_teleport", buffer); + menuHandle.AddItem("boss_no_teleport", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Override Boss", client); - AddMenuItem(menuHandle, "override_boss", buffer); - - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.AddItem("override_boss", buffer); + FormatEx(buffer, sizeof(buffer), "Make a boss wander to aimed position"); + menuHandle.AddItem("force_wander", buffer); + FormatEx(buffer, sizeof(buffer), "Make a boss alerted to aimed position"); + menuHandle.AddItem("force_alert", buffer); + FormatEx(buffer, sizeof(buffer), "Make a boss use a attack"); + menuHandle.AddItem("force_attack", buffer); + FormatEx(buffer, sizeof(buffer), "Force a mod boss to be loaded next pack"); + menuHandle.AddItem("force_bossload", buffer); + + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } -static int AdminMenu_BossMain(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossMain(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -317,7 +340,7 @@ static int AdminMenu_BossMain(Handle menu, MenuAction action,int param1,int para else if (action == MenuAction_Select) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); if (strcmp(info, "add_boss") == 0) { DisplayAddBossAdminMenu(param1); @@ -346,15 +369,31 @@ static int AdminMenu_BossMain(Handle menu, MenuAction action,int param1,int para { DisplayOverrideBossAdminMenu(param1); } + else if (strcmp(info, "force_wander") == 0) + { + DisplayBossWanderAdminMenu(param1); + } + else if (strcmp(info, "force_alert") == 0) + { + DisplayBossAlertAdminMenu(param1); + } + else if (strcmp(info, "force_attack") == 0) + { + DisplayBossAttackAdminMenu(param1); + } + else if (strcmp(info, "force_bossload") == 0) + { + Command_BossOverride(param1, 0); + } } return 0; } -static int AdminTopMenu_BossMain(Handle topmenu, TopMenuAction action, TopMenuObject object_id,int param, char[] buffer,int maxlength) +static int AdminTopMenu_BossMain(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { - FormatEx(buffer, maxlength, "%t%T", "SF2 Prefix", "SF2 Admin Menu Boss Main", param); + FormatEx(buffer, maxlength, "%t %T", "SF2 Prefix", "SF2 Admin Menu Boss Main", param); } else if (action == TopMenuAction_SelectOption) { @@ -363,30 +402,42 @@ static int AdminTopMenu_BossMain(Handle topmenu, TopMenuAction action, TopMenuOb return 0; } +static void AddAllBossesToMenu(Menu menu) +{ + ArrayList bossList = GetBossProfileList(); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + char displayName[SF2_MAX_NAME_LENGTH]; + for (int i = 0; i < bossList.Length; i++) + { + bossList.GetString(i, profile, sizeof(profile)); + NPCGetBossName(_, displayName, sizeof(displayName), profile); + if (displayName[0] == '\0') + { + strcopy(displayName, sizeof(displayName), profile); + } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + continue; + } + menu.AddItem(profile, displayName); + } +} + static bool DisplayAddBossAdminMenu(int client) //Use for view boss list { ArrayList bossList = GetBossProfileList(); if (bossList != null) { - Handle menuHandle = CreateMenu(AdminMenu_AddBoss); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Boss", client); + Menu menuHandle = new Menu(AdminMenu_AddBoss); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Boss", client); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - char displayName[SF2_MAX_NAME_LENGTH]; + AddAllBossesToMenu(menuHandle); - for (int i = 0; i < bossList.Length; i++) - { - bossList.GetString(i, profile, sizeof(profile)); - NPCGetBossName(_, displayName, sizeof(displayName), profile); - if (displayName[0] == '\0') - { - strcopy(displayName, sizeof(displayName), profile); - } - AddMenuItem(menuHandle, profile, displayName); - } - SetMenuExitBackButton(menuHandle, true); + menuHandle.ExitBackButton = true; - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } @@ -395,7 +446,7 @@ static bool DisplayAddBossAdminMenu(int client) //Use for view boss list return false; } -static int AdminMenu_AddBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_AddBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -411,7 +462,7 @@ static int AdminMenu_AddBoss(Handle menu, MenuAction action,int param1,int param else if (action == MenuAction_Select) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - GetMenuItem(menu, param2, profile, sizeof(profile)); + menu.GetItem(param2, profile, sizeof(profile)); FakeClientCommand(param1, "sm_sf2_add_boss %s", profile); @@ -425,26 +476,14 @@ static bool DisplayAddFakeBossAdminMenu(int client) ArrayList bossList = GetBossProfileList(); if (bossList != null) { - Handle menuHandle = CreateMenu(AdminMenu_AddFakeBoss); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Fake Boss", client); + Menu menuHandle = new Menu(AdminMenu_AddFakeBoss); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Fake Boss", client); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - char displayName[SF2_MAX_NAME_LENGTH]; + AddAllBossesToMenu(menuHandle); - for (int i = 0; i < bossList.Length; i++) - { - bossList.GetString(i, profile, sizeof(profile)); - NPCGetBossName(_, displayName, sizeof(displayName), profile); - if (displayName[0] == '\0') - { - strcopy(displayName, sizeof(displayName), profile); - } - AddMenuItem(menuHandle, profile, displayName); - } - - SetMenuExitBackButton(menuHandle, true); + menuHandle.ExitBackButton = true; - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } @@ -453,7 +492,7 @@ static bool DisplayAddFakeBossAdminMenu(int client) return false; } -static int AdminMenu_AddFakeBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_AddFakeBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -469,7 +508,7 @@ static int AdminMenu_AddFakeBoss(Handle menu, MenuAction action,int param1,int p else if (action == MenuAction_Select) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - GetMenuItem(menu, param2, profile, sizeof(profile)); + menu.GetItem(param2, profile, sizeof(profile)); FakeClientCommand(param1, "sm_sf2_add_boss_fake %s", profile); @@ -478,7 +517,7 @@ static int AdminMenu_AddFakeBoss(Handle menu, MenuAction action,int param1,int p return 0; } -static int AddBossTargetsToMenu(Handle menuHandle) +static int AddBossTargetsToMenu(Menu menuHandle) { char buffer[512]; char display[512], info[64]; @@ -514,9 +553,17 @@ static int AddBossTargetsToMenu(Handle menuHandle) StrCat(display, sizeof(display), " (fake)"); } + SF2BossProfileData data; + data = NPCGetProfileData(i); + + if (data.IsPvEBoss) + { + continue; + } + FormatEx(info, sizeof(info), "%d", uniqueID); - AddMenuItem(menuHandle, info, display); + menuHandle.AddItem(info, display); count++; } @@ -527,7 +574,7 @@ static bool DisplayRemoveBossAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_RemoveBoss); + Menu menuHandle = new Menu(AdminMenu_RemoveBoss); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -535,9 +582,9 @@ static bool DisplayRemoveBossAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Remove Boss", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Remove Boss", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -546,7 +593,7 @@ static bool DisplayRemoveBossAdminMenu(int client) return false; } -static int AdminMenu_RemoveBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_RemoveBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -562,7 +609,7 @@ static int AdminMenu_RemoveBoss(Handle menu, MenuAction action,int param1,int pa else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -582,7 +629,7 @@ static bool DisplaySpawnBossAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_SpawnBoss); + Menu menuHandle = new Menu(AdminMenu_SpawnBoss); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -590,9 +637,9 @@ static bool DisplaySpawnBossAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Spawn Boss", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Spawn Boss", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -601,7 +648,7 @@ static bool DisplaySpawnBossAdminMenu(int client) return false; } -static int AdminMenu_SpawnBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_SpawnBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -617,7 +664,7 @@ static int AdminMenu_SpawnBoss(Handle menu, MenuAction action,int param1,int par else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -637,7 +684,7 @@ static bool DisplayBossAttackWaitersAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_BossAttackWaiters); + Menu menuHandle = new Menu(AdminMenu_BossAttackWaiters); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -645,9 +692,9 @@ static bool DisplayBossAttackWaitersAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -656,7 +703,7 @@ static bool DisplayBossAttackWaitersAdminMenu(int client) return false; } -static int AdminMenu_BossAttackWaiters(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossAttackWaiters(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -672,7 +719,7 @@ static int AdminMenu_BossAttackWaiters(Handle menu, MenuAction action,int param1 else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -691,21 +738,21 @@ static int AdminMenu_BossAttackWaiters(Handle menu, MenuAction action,int param1 strcopy(name, sizeof(name), profile); } - Handle menuHandle = CreateMenu(AdminMenu_BossAttackWaitersConfirm); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters Confirm", param1, name); + Menu menuHandle = new Menu(AdminMenu_BossAttackWaitersConfirm); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters Confirm", param1, name); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - AddMenuItem(menuHandle, id, buffer); + menuHandle.AddItem(id, buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - AddMenuItem(menuHandle, id, buffer); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.AddItem(id, buffer); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } return 0; } -static int AdminMenu_BossAttackWaitersConfirm(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossAttackWaitersConfirm(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -721,7 +768,7 @@ static int AdminMenu_BossAttackWaitersConfirm(Handle menu, MenuAction action,int else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -751,7 +798,7 @@ static bool DisplayBossTeleportAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_BossTeleport); + Menu menuHandle = new Menu(AdminMenu_BossTeleport); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -759,9 +806,9 @@ static bool DisplayBossTeleportAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -770,7 +817,7 @@ static bool DisplayBossTeleportAdminMenu(int client) return false; } -static int AdminMenu_BossTeleport(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossTeleport(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -786,7 +833,7 @@ static int AdminMenu_BossTeleport(Handle menu, MenuAction action,int param1,int else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -805,21 +852,21 @@ static int AdminMenu_BossTeleport(Handle menu, MenuAction action,int param1,int strcopy(name, sizeof(name), profile); } - Handle menuHandle = CreateMenu(AdminMenu_BossTeleportConfirm); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport Confirm", param1, name); + Menu menuHandle = new Menu(AdminMenu_BossTeleportConfirm); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport Confirm", param1, name); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - AddMenuItem(menuHandle, id, buffer); + menuHandle.AddItem(id, buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - AddMenuItem(menuHandle, id, buffer); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.AddItem(id, buffer); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } return 0; } -static int AdminMenu_BossTeleportConfirm(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossTeleportConfirm(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -835,7 +882,7 @@ static int AdminMenu_BossTeleportConfirm(Handle menu, MenuAction action,int para else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -866,7 +913,7 @@ static bool DisplayOverrideBossAdminMenu(int client) ArrayList bossList = GetBossProfileList(); if (bossList != null) { - Handle menuHandle = CreateMenu(AdminMenu_OverrideBoss); + Menu menuHandle = new Menu(AdminMenu_OverrideBoss); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; char displayName[SF2_MAX_NAME_LENGTH]; @@ -879,10 +926,16 @@ static bool DisplayOverrideBossAdminMenu(int client) { strcopy(displayName, sizeof(displayName), profile); } - AddMenuItem(menuHandle, profile, displayName); + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + continue; + } + menuHandle.AddItem(profile, displayName); } - SetMenuExitBackButton(menuHandle, true); + menuHandle.ExitBackButton = true; char profileOverride[SF2_MAX_PROFILE_NAME_LENGTH], profileDisplayName[SF2_MAX_PROFILE_NAME_LENGTH]; g_BossProfileOverrideConVar.GetString(profileOverride, sizeof(profileOverride)); @@ -901,9 +954,9 @@ static bool DisplayOverrideBossAdminMenu(int client) strcopy(profileDisplayName, sizeof(profileDisplayName), "---"); } - SetMenuTitle(menuHandle, "%t%T\n%T\n \n", "SF2 Prefix", "SF2 Admin Menu Override Boss", client, "SF2 Admin Menu Current Boss Override", client, profileDisplayName); + menuHandle.SetTitle("%t %T\n%T\n \n", "SF2 Prefix", "SF2 Admin Menu Override Boss", client, "SF2 Admin Menu Current Boss Override", client, profileDisplayName); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } @@ -912,7 +965,7 @@ static bool DisplayOverrideBossAdminMenu(int client) return false; } -static int AdminMenu_OverrideBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_OverrideBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -928,7 +981,7 @@ static int AdminMenu_OverrideBoss(Handle menu, MenuAction action,int param1,int else if (action == MenuAction_Select) { char profile[SF2_MAX_PROFILE_NAME_LENGTH], name[SF2_MAX_NAME_LENGTH]; - GetMenuItem(menu, param2, profile, sizeof(profile)); + menu.GetItem(param2, profile, sizeof(profile)); g_BossProfileOverrideConVar.SetString(profile); @@ -941,4 +994,414 @@ static int AdminMenu_OverrideBoss(Handle menu, MenuAction action,int param1,int DisplayOverrideBossAdminMenu(param1); } return 0; -} \ No newline at end of file +} + +static bool DisplayBossWanderAdminMenu(int client) +{ + if (GetBossProfileList() != null) + { + Menu menuHandle = new Menu(AdminMenu_BossWanderToPos); + if (!AddBossTargetsToMenu(menuHandle)) + { + delete menuHandle; + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 No Active Bosses", client); + } + else + { + menuHandle.SetTitle("%t Make a boss wander to aimed position\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; + } + } + + DisplayBossMainAdminMenu(client); + return false; +} + +static int AdminMenu_BossWanderToPos(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + int index = NPCGetFromUniqueID(StringToInt(id)); + if (index == -1) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = NPCGetEntIndex(index); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(index); + switch (npc.Type) + { + case SF2BossType_Chaser: + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not idling!", "SF2 Prefix"); + } + else + { + SF2_BasePlayer admin = SF2_BasePlayer(param1); + float eyePos[3], eyeAng[3], endPos[3]; + admin.GetEyePosition(eyePos); + admin.GetEyeAngles(eyeAng); + + Handle trace = TR_TraceRayFilterEx(eyePos, eyeAng, MASK_NPCSOLID, RayType_Infinite, TraceRayDontHitEntity, admin.index); + TR_GetEndPosition(endPos, trace); + delete trace; + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 15, 0.5, 5.0, 0.0, ( { 128, 128, 128, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 10, 0.6, 10.0, 0.5, ( { 0, 255, 0, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + EmitSoundToClient(admin.index, g_DebugBeamSound); + + chaser.SetForceWanderPosition(endPos); + chaser.DebugShouldGoToPos = true; + } + } + case SF2BossType_Statue: + { + + } + } + } + } + + DisplayBossWanderAdminMenu(param1); + } + return 0; +} + +static bool DisplayBossAlertAdminMenu(int client) +{ + if (GetBossProfileList() != null) + { + Menu menuHandle = new Menu(AdminMenu_BossAlertToPos); + if (!AddBossTargetsToMenu(menuHandle)) + { + delete menuHandle; + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 No Active Bosses", client); + } + else + { + menuHandle.SetTitle("%t Make a boss alert to aimed position\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; + } + } + + DisplayBossMainAdminMenu(client); + return false; +} + +static int AdminMenu_BossAlertToPos(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + int index = NPCGetFromUniqueID(StringToInt(id)); + if (index == -1) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = NPCGetEntIndex(index); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(index); + switch (npc.Type) + { + case SF2BossType_Chaser: + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE && chaser.State != STATE_ALERT) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is busy right now!", "SF2 Prefix"); + } + else + { + SF2_BasePlayer admin = SF2_BasePlayer(param1); + float eyePos[3], eyeAng[3], endPos[3]; + admin.GetEyePosition(eyePos); + admin.GetEyeAngles(eyeAng); + + Handle trace = TR_TraceRayFilterEx(eyePos, eyeAng, MASK_NPCSOLID, RayType_Infinite, TraceRayDontHitEntity, admin.index); + TR_GetEndPosition(endPos, trace); + delete trace; + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 15, 0.5, 5.0, 0.0, ( { 128, 128, 128, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 10, 0.6, 10.0, 0.5, ( { 255, 0, 0, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + EmitSoundToClient(admin.index, g_DebugBeamSound); + + chaser.SetForceWanderPosition(endPos); + chaser.InterruptConditions |= COND_DEBUG; + } + } + case SF2BossType_Statue: + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not a chaser!", "SF2 Prefix"); + } + } + } + } + + DisplayBossAlertAdminMenu(param1); + } + return 0; +} + +static bool DisplayBossAttackAdminMenu(int client) +{ + if (GetBossProfileList() != null) + { + Menu menuHandle = new Menu(AdminMenu_BossAttack); + char buffer[512]; + char display[512], info[64]; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + int count; + + for (int i = 0; i < MAX_BOSSES; i++) + { + int uniqueID = NPCGetUniqueID(i); + if (uniqueID == -1) + { + continue; + } + + if (NPCGetType(i) != SF2BossType_Chaser) + { + continue; + } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); + + SF2ChaserBossProfileData chaserData; + chaserData = controller.GetProfileData(); + if (chaserData.Attacks == null || chaserData.Attacks.Length == 0) + { + continue; + } + + controller.GetProfile(profile, sizeof(profile)); + controller.GetName(buffer, sizeof(buffer)); + if (buffer[0] == '\0') + { + strcopy(buffer, sizeof(buffer), profile); + } + + FormatEx(display, sizeof(display), "%d - %s", i, buffer); + if (g_SlenderCopyMaster[i] != -1) + { + FormatEx(buffer, sizeof(buffer), " (copy of boss %d)", g_SlenderCopyMaster[i]); + StrCat(display, sizeof(display), buffer); + } + + if (NPCGetFlags(i) & SFF_FAKE) + { + StrCat(display, sizeof(display), " (fake)"); + } + + SF2BossProfileData data; + data = NPCGetProfileData(i); + + if (data.IsPvEBoss) + { + StrCat(display, sizeof(display), " (PvE)"); + } + + FormatEx(info, sizeof(info), "%d", i); + + menuHandle.AddItem(info, display); + count++; + } + + if (count <= 0) + { + delete menuHandle; + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 No Active Bosses", client); + } + else + { + menuHandle.SetTitle("%t Make a boss use a attack\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; + } + } + + DisplayBossMainAdminMenu(client); + return false; +} + +static int AdminMenu_BossAttack(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + int index = StringToInt(id); + if (index == -1) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = NPCGetEntIndex(index); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE && chaser.State != STATE_ALERT && chaser.State != STATE_CHASE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is busy right now!", "SF2 Prefix"); + } + else + { + g_SelectedBoss[param1] = SF2NPC_Chaser(index); + DisplayBossAttackListAdminMenu(param1); + return 0; + } + } + } + + DisplayBossAttackAdminMenu(param1); + } + return 0; +} + +static bool DisplayBossAttackListAdminMenu(int client) +{ + if (!g_SelectedBoss[client].IsValid()) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", client); + DisplayBossMainAdminMenu(client); + return false; + } + + SF2ChaserBossProfileData data; + data = g_SelectedBoss[client].GetProfileData(); + Menu menuHandle = new Menu(AdminMenu_BossAttackList); + for (int i = 0; i < data.Attacks.Length; i++) + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(i, attackData); + menuHandle.AddItem(attackData.Name, attackData.Name); + } + + menuHandle.SetTitle("%t Make a boss use a attack\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; +} + +static int AdminMenu_BossAttackList(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossAttackAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + SF2NPC_Chaser npc = g_SelectedBoss[param1]; + if (!npc.IsValid()) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = npc.EntIndex; + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE && chaser.State != STATE_ALERT && chaser.State != STATE_CHASE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is busy right now!", "SF2 Prefix"); + } + else + { + char formatter[128]; + FormatEx(formatter, sizeof(formatter), "debug attack %s", id); + chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString(formatter); + } + DisplayBossAttackListAdminMenu(param1); + return 0; + } + } + + DisplayBossAttackAdminMenu(param1); + } + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/anticamping.sp b/addons/sourcemod/scripting/sf2/anticamping.sp new file mode 100644 index 00000000..b7477de5 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/anticamping.sp @@ -0,0 +1,234 @@ +#pragma semicolon 1 + +// Anti-camping data. +int g_PlayerCampingStrikes[MAXTF2PLAYERS] = { 0, ... }; +Handle g_PlayerCampingTimer[MAXTF2PLAYERS] = { null, ... }; +float g_PlayerCampingLastPosition[MAXTF2PLAYERS][3]; +bool g_IsPlayerCampingFirstTime[MAXTF2PLAYERS]; +static float g_ClientAllowedTimeNearEscape[MAXTF2PLAYERS]; + +static ConVar g_CampingEnabledConVar; +static ConVar g_CampingMaxStrikesConVar; +static ConVar g_CampingStrikesWarnConVar; +static ConVar g_ExitCampingTimeAllowedConVar; +static ConVar g_CampingMinDistanceConVar; +static ConVar g_CampingNoStrikeSanityConVar; +static ConVar g_CampingNoStrikeBossDistanceConVar; + +void SetupAntiCamping() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + + g_CampingEnabledConVar = CreateConVar("sf2_anticamping_enabled", "1", "Enable/Disable anti-camping system for RED.", _, true, 0.0, true, 1.0); + g_CampingMaxStrikesConVar = CreateConVar("sf2_anticamping_maxstrikes", "4", "How many 5-second intervals players are allowed to stay in one spot before he/she is forced to suicide.", _, true, 0.0); + g_CampingStrikesWarnConVar = CreateConVar("sf2_anticamping_strikeswarn", "2", "The amount of strikes left where the player will be warned of camping."); + g_ExitCampingTimeAllowedConVar = CreateConVar("sf2_exitcamping_allowedtime", "25.0", "The amount of time a player can stay near the exit before being flagged as camper."); + g_CampingMinDistanceConVar = CreateConVar("sf2_anticamping_mindistance", "128.0", "Every 5 seconds the player has to be at least this far away from his last position 5 seconds ago or else he'll get a strike."); + g_CampingNoStrikeSanityConVar = CreateConVar("sf2_anticamping_no_strike_sanity", "0.1", "The camping system will NOT give any strikes under any circumstances if the players's Sanity is missing at least this much of his maximum Sanity (max is 1.0)."); + g_CampingNoStrikeBossDistanceConVar = CreateConVar("sf2_anticamping_no_strike_boss_distance", "512.0", "The camping system will NOT give any strikes under any circumstances if the player is this close to a boss (ignoring LOS)."); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetCampingStats(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetCampingStats(client.index); + + if (!client.IsEliminated) + { + ClientStartCampingTimer(client.index); + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetCampingStats(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetCampingStats(client.index); +} + +static void ClientResetCampingStats(int client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetCampingStats(%d)", client); + } + #endif + + g_PlayerCampingStrikes[client] = 0; + g_PlayerIsExitCamping[client] = false; + g_PlayerCampingTimer[client] = null; + g_IsPlayerCampingFirstTime[client] = true; + g_PlayerCampingLastPosition[client][0] = 0.0; + g_PlayerCampingLastPosition[client][1] = 0.0; + g_PlayerCampingLastPosition[client][2] = 0.0; + g_ClientAllowedTimeNearEscape[client] = g_ExitCampingTimeAllowedConVar.FloatValue; + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetCampingStats(%d)", client); + } + #endif +} + +static void ClientStartCampingTimer(int client) +{ + g_PlayerCampingTimer[client] = CreateTimer(5.0, Timer_ClientCheckCamp, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); +} + +/*void ClientNavAreaUpdate(int client, CNavArea newArea, CNavArea oldArea) +{ + if (GetRoundState() != SF2RoundState_Active) return; + + if (g_PlayerEliminated[client]) return; + + if (newArea == INVALID_NAV_AREA) return; + + if ((oldArea != INVALID_NAV_AREA && oldArea.Attributes & NAV_MESH_DONT_HIDE) || newArea.Attributes & NAV_MESH_DONT_HIDE) + { + g_ClientAllowedTimeNearEscape[client] -= 0.3;//Remove 0.3sec this function is called every ~0.3sec + } + else + { + g_ClientAllowedTimeNearEscape[client] += 0.1;//Forgive the player of 0.1 + } + if (g_ClientAllowedTimeNearEscape[client] <= 0.0 && SF_IsSurvivalMap()) + { + g_PlayerIsExitCamping[client] = true; + } + else + { + g_PlayerIsExitCamping[client] = false; + } + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_NAV, 1, "Old area: %i DHF:%s, New area: %i DHF:%s, is considered as exit camper: %s", oldArea.Index, (oldArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", newArea.Index, (newArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", (g_PlayerIsExitCamping[client]) ? "true" : "false" ); + #endif +}*/ + +static Action Timer_ClientCheckCamp(Handle timer, any userid) +{ + if (IsRoundInWarmup()) + { + return Plugin_Stop; + } + + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerCampingTimer[client]) + { + return Plugin_Stop; + } + + if (IsRoundEnding() || !IsPlayerAlive(client) || g_PlayerEliminated[client] || DidClientEscape(client)) + { + return Plugin_Stop; + } + + if (!g_IsPlayerCampingFirstTime[client]) + { + bool isCamping = false; + float pos[3], maxs[3], mins[3]; + GetClientAbsOrigin(client, pos); + GetEntPropVector(client, Prop_Send, "m_vecMins", mins); + GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); + + // Only do something if the player is NOT stuck. + float distFromLastPosition = GetVectorSquareMagnitude(g_PlayerCampingLastPosition[client], pos); + float distFromClosestBoss = 9999999.0; + int closestBoss = -1; + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + int slender = NPCGetEntIndex(i); + if (!slender || slender == INVALID_ENT_REFERENCE) + { + continue; + } + + float slenderPos[3]; + CBaseEntity(slender).GetAbsOrigin(slenderPos); + + float dist = GetVectorSquareMagnitude(slenderPos, pos); + if (dist < distFromClosestBoss) + { + closestBoss = i; + distFromClosestBoss = dist; + } + } + /*if (IsSpaceOccupiedIgnorePlayers(pos, mins, maxs, client)) + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is stuck, no actions taken", client, client);*/ + if (!SF_IsBoxingMap() && g_CampingEnabledConVar.BoolValue && + IsRoundPlaying() && + g_PlayerStaticAmount[client] <= g_CampingNoStrikeSanityConVar.FloatValue && + (closestBoss == -1 || distFromClosestBoss >= g_CampingNoStrikeBossDistanceConVar.FloatValue) && + distFromLastPosition <= SquareFloat(g_CampingMinDistanceConVar.FloatValue)) + { + isCamping = true; + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk, or camping", client, client); + } + if (isCamping) + { + g_PlayerCampingStrikes[client]++; + if (g_PlayerCampingStrikes[client] < g_CampingMaxStrikesConVar.IntValue) + { + if (g_PlayerCampingStrikes[client] >= g_CampingStrikesWarnConVar.IntValue) + { + CPrintToChat(client, "{red}%T", "SF2 Camping System Warning", client, (g_CampingMaxStrikesConVar.IntValue - g_PlayerCampingStrikes[client]) * 5); + } + } + else + { + g_PlayerCampingStrikes[client] = 0; + ClientStartDeathCam(client, 0, pos, true); + } + } + else + { + // Forgiveness. + if (g_PlayerCampingStrikes[client] > 0) + { + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is forgiven of one strike.", client, client); + g_PlayerCampingStrikes[client]--; + } + } + + g_PlayerCampingLastPosition[client][0] = pos[0]; + g_PlayerCampingLastPosition[client][1] = pos[1]; + g_PlayerCampingLastPosition[client][2] = pos[2]; + } + else + { + g_IsPlayerCampingFirstTime[client] = false; + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk/camping for the 1st time since the reset, don't take any actions for now.....", client, client); + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/changelog.sp b/addons/sourcemod/scripting/sf2/changelog.sp new file mode 100644 index 00000000..9be812cc --- /dev/null +++ b/addons/sourcemod/scripting/sf2/changelog.sp @@ -0,0 +1,137 @@ +#pragma semicolon 1 + +static Menu g_ChangelogUpdate; +static KeyValues g_ChangelogConfig; + +#define FILE_CHANGELOG "configs/sf2/changelog.cfg" +#define FILE_CHANGELOG_DATA "data/sf2/changelog.cfg" + +static bool g_SeeUpdateMenu[MAXTF2PLAYERS] = { false, ... }; + +void InitializeChangelog() +{ + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + + RegConsoleCmd("sm_slupdate", Command_Update); + RegAdminCmd("sm_sf2_reloadchangelog", Command_ReloadChangelog, ADMFLAG_SLAY); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + if (client.Team > 1 && !g_SeeUpdateMenu[client.index]) + { + g_SeeUpdateMenu[client.index] = true; + g_ChangelogUpdate.Display(client.index, 30); + } +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + g_SeeUpdateMenu[client.index] = false; +} + +static void OnGamemodeStart() +{ + CreateChangelog(); +} + +static Action Command_Update(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + g_ChangelogUpdate.Display(client, 30); + return Plugin_Handled; +} + +static Action Command_ReloadChangelog(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + CreateChangelog(); + PrintToChat(client, "Reloaded the changelog."); + return Plugin_Handled; +} + +static void CreateChangelog() +{ + if (g_ChangelogConfig != null) + { + delete g_ChangelogConfig; + } + + char buffer[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, buffer, sizeof(buffer), g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_CHANGELOG_DATA : FILE_CHANGELOG); + g_ChangelogConfig = new KeyValues("root"); + if (!g_ChangelogConfig.ImportFromFile(buffer)) + { + LogSF2Message("Failed to load changelog! File not found!"); + delete g_ChangelogConfig; + return; + } + + char list[1024]; + g_ChangelogUpdate = new Menu(Menu_Update); + FormatEx(list, sizeof(list), "%tSlender Fortress\n \n", "SF2 Prefix"); + StrCat(list, sizeof(list), "Coders: KitRifty, Kenzzer, Mentrillum, The Gaben\n"); + StrCat(list, sizeof(list), "Version: "); + StrCat(list, sizeof(list), PLUGIN_VERSION); + StrCat(list, sizeof(list), "\n \n"); + Format(list, sizeof(list), "%s%t\n", list, "SF2 Recent Changes"); + StrCat(list, sizeof(list), "\n \n"); + + g_ChangelogUpdate.SetTitle(list); + g_ChangelogConfig.Rewind(); + char section[5], name[64], message[64]; + if (g_ChangelogConfig.GotoFirstSubKey()) + { + do + { + g_ChangelogConfig.GetString("name", name, sizeof(name)); + FormatEx(message, sizeof(message), " ----- %s ----- ", name); + g_ChangelogUpdate.AddItem("", message, ITEMDRAW_DISABLED); + + int count = 1; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + g_ChangelogConfig.GetString(section, message, sizeof(message)); + if (message[0] == '\0') + { + break; + } + + g_ChangelogUpdate.AddItem("", message, ITEMDRAW_DISABLED); + count++; + } + while (count % 7 != 0) + { + g_ChangelogUpdate.AddItem("", " ", ITEMDRAW_SPACER); + count++; + } + } + while (g_ChangelogConfig.GotoNextKey()); + + g_ChangelogUpdate.AddItem("", " ", ITEMDRAW_SPACER); + g_ChangelogUpdate.AddItem("0", "Display Main Menu"); + } +} + +static int Menu_Update(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select && param2 == 0) + { + g_MenuMain.Display(param1, 30); + } + return 0; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/classconfigs.sp b/addons/sourcemod/scripting/sf2/classconfigs.sp index 3ab74d8b..b2a8b1dd 100644 --- a/addons/sourcemod/scripting/sf2/classconfigs.sp +++ b/addons/sourcemod/scripting/sf2/classconfigs.sp @@ -10,7 +10,7 @@ float g_ClassWalkSpeed[MAX_CLASSES + 1]; float g_ClassDangerSpeedMultipler[MAX_CLASSES + 1]; float g_ClassSprintDurationMultipler[MAX_CLASSES + 1]; float g_ClassScareSprintDurationMultipler[MAX_CLASSES + 1]; -int g_ClassSprintPointLossJumping[MAX_CLASSES + 1]; +float g_ClassSprintPointLossJumping[MAX_CLASSES + 1]; float g_ClassProxyDamageVulnerability[MAX_CLASSES + 1]; bool g_ClassCanPickUpHealth[MAX_CLASSES + 1]; @@ -242,10 +242,10 @@ static void PrecacheClassProfiles() g_ClassDangerSpeedMultipler[i] = GetClassStatFloat(className, "danger_speed_multiplier", 1.34); g_ClassSprintDurationMultipler[i] = GetClassStatFloat(className, "sprint_multiplier", 1.0); g_ClassScareSprintDurationMultipler[i] = GetClassStatFloat(className, "scare_sprint_multiplier", 1.0); - g_ClassSprintPointLossJumping[i] = GetClassStatNum(className, "sprint_loss_while_jumping", 7); + g_ClassSprintPointLossJumping[i] = GetClassStatFloat(className, "sprint_loss_while_jumping", 0.07); g_ClassProxyDamageVulnerability[i] = GetClassStatFloat(className, "proxy_damage_vulnerability"); - g_ClassCanPickUpHealth[i] = !!GetClassStatNum(className, "can_pickup_health", 1); + g_ClassCanPickUpHealth[i] = GetClassStatNum(className, "can_pickup_health", 1) != 0; g_ClassHealthPickupMultiplier[i] = GetClassStatFloat(className, "health_pickup_multiplier", 1.0); g_ClassBossPriorityMultiplier[i] = GetClassStatFloat(className, "boss_priority"); @@ -271,8 +271,8 @@ static void PrecacheClassProfiles() g_ClassFlashlightRechargeRate[i] = GetClassStatFloat(className, "flashlight_recharge_rate", 1.0); g_ClassFlashlightSoundRadius[i] = GetClassStatFloat(className, "flashlight_sound_radius", 0.5); - g_ClassBlockedOnThanatophobia[i] = !!GetClassStatNum(className, "blocked_on_thanatophobia"); + g_ClassBlockedOnThanatophobia[i] = GetClassStatNum(className, "blocked_on_thanatophobia") != 0; - g_ClassInvulnerableToTraps[i] = !!GetClassStatNum(className, "immune_to_traps"); + g_ClassInvulnerableToTraps[i] = GetClassStatNum(className, "immune_to_traps") != 0; } } diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index c9d03ac7..23d2072e 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -7,85 +7,18 @@ #define SF2_OVERLAY_DEFAULT "overlays/slender/newcamerahud_3" #define SF2_OVERLAY_DEFAULT_NO_FILMGRAIN "overlays/slender/nofilmgrain" -#define SF2_OVERLAY_GHOST "overlays/slender/ghostcamera" -#define SF2_ULTRAVISION_WIDTH 800.0 -#define SF2_ULTRAVISION_LENGTH 800.0 -#define SF2_ULTRAVISION_BRIGHTNESS -4 // Intensity of Ultravision. - -#define SF2_PLAYER_BREATH_COOLDOWN_MIN 0.8 -#define SF2_PLAYER_BREATH_COOLDOWN_MAX 2.0 - -#define SF2_FLASHLIGHT_WIDTH 512.0 // How wide the player's Flashlight should be in world units. -#define SF2_FLASHLIGHT_BRIGHTNESS 0 // Intensity of the players' Flashlight. -#define SF2_FLASHLIGHT_DRAIN_RATE 0.65 // How long (in seconds) each bar on the player's Flashlight meter lasts. -#define SF2_FLASHLIGHT_RECHARGE_RATE 0.68 // How long (in seconds) it takes each bar on the player's Flashlight meter to recharge. -#define SF2_FLASHLIGHT_FLICKERAT 0.25 // The percentage of the Flashlight battery where the Flashlight will start to blink. -#define SF2_FLASHLIGHT_ENABLEAT 0.3 // The percentage of the Flashlight battery where the Flashlight will be able to be used again (if the player shortens out the Flashlight from excessive use). -#define SF2_FLASHLIGHT_COOLDOWN 0.4 // How much time players have to wait before being able to switch their flashlight on again after turning it off. - -char g_PlayerBreathSounds[][] = -{ - "slender/fastbreath1.wav" -}; +static int g_ClientModifiedMaxHealth[MAXTF2PLAYERS]; //Client Special Round Timer static Handle g_ClientSpecialRoundTimer[MAXTF2PLAYERS]; -// Deathcam data. -static int g_PlayerDeathCamBoss[MAXTF2PLAYERS] = { -1, ... }; -static bool g_PlayerDeathCam[MAXTF2PLAYERS] = { false, ... }; -static bool g_PlayerDeathCamShowOverlay[MAXTF2PLAYERS] = { false, ... }; -int g_PlayerDeathCamEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerDeathCamEnt2[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerDeathCamTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static Handle g_PlayerDeathCamTimer[MAXTF2PLAYERS] = { null, ... }; -bool g_CameraInDeathCamAdvanced[2049] = { false, ... }; -float g_CameraPlayerOffsetBackward[2049] = { 0.0, ... }; -float g_CameraPlayerOffsetDownward[2049] = { 0.0, ... }; -static float g_vecPlayerOriginalDeathcamPosition[MAXTF2PLAYERS][3]; - -// Ultravision data. -bool g_PlayerHasUltravision[MAXTF2PLAYERS] = { false, ... }; -int g_PlayerUltravisionEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; - -// Sprint data. -static bool g_PlayerSprint[MAXTF2PLAYERS] = { false, ... }; -int g_PlayerSprintPoints[MAXTF2PLAYERS] = { 100, ... }; -Handle g_PlayerSprintTimer[MAXTF2PLAYERS] = { null, ... }; - -// Blink data. -static Handle g_PlayerBlinkTimer[MAXTF2PLAYERS] = { null, ... }; -static bool g_PlayerBlink[MAXTF2PLAYERS] = { false, ... }; -bool g_PlayerHoldingBlink[MAXTF2PLAYERS] = { false, ... }; -static float g_PlayerBlinkMeter[MAXTF2PLAYERS] = { 0.0, ... }; -static float g_TimeUntilUnblink[MAXTF2PLAYERS] = { 0.0, ... }; -static int g_PlayerBlinkCount[MAXTF2PLAYERS] = { 0, ... }; - -// Breathing data. -bool g_PlayerBreath[MAXTF2PLAYERS] = { false, ... }; -static Handle g_PlayerBreathTimer[MAXTF2PLAYERS] = { null, ... }; - -// Interactive glow data. -static int g_PlayerInteractiveGlowEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerInteractiveGlowTargetEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; - -// Constant glow data. -static int g_PlayerGlowEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static bool g_PlayerConstantGlowEnabled[MAXTF2PLAYERS] = { false, ... }; - // Jumpscare data. static int g_PlayerJumpScareBoss[MAXTF2PLAYERS] = { -1, ... }; static float g_PlayerJumpScareLifeTime[MAXTF2PLAYERS] = { -1.0, ... }; static float g_PlayerScareBoostEndTime[MAXTF2PLAYERS] = { -1.0, ... }; -// Anti-camping data. -int g_PlayerCampingStrikes[MAXTF2PLAYERS] = { 0, ... }; -Handle g_PlayerCampingTimer[MAXTF2PLAYERS] = { null, ... }; -float g_PlayerCampingLastPosition[MAXTF2PLAYERS][3]; -bool g_IsPlayerCampingFirstTime[MAXTF2PLAYERS]; - // Frame data int g_ClientMaxFrameDeathAnim[MAXTF2PLAYERS]; int g_ClientFrame[MAXTF2PLAYERS]; @@ -93,58 +26,523 @@ int g_ClientFrame[MAXTF2PLAYERS]; //Nav Data //static CNavArea g_lastNavArea[MAXTF2PLAYERS]; -static float g_ClientAllowedTimeNearEscape[MAXTF2PLAYERS]; -//Peeking Data -static bool g_PlayerPeeking[MAXTF2PLAYERS] = { false, ... }; +#include "client/glow.sp" +#include "client/interactables.sp" +#include "client/hints.sp" +#include "client/think.sp" +#include "client/static.sp" +#include "client/blink.sp" +#include "client/deathcam.sp" +#include "client/ultravision.sp" +#include "client/flashlight.sp" +#include "client/peek.sp" +#include "client/sprint.sp" +#include "client/breathing.sp" +#include "client/ghostmode.sp" +#include "client/music.sp" +#include "client/proxy.sp" -// ========================================================== -// GENERAL CLIENT HOOK FUNCTIONS -// ========================================================== +void SetupClients() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); +} -#define SF2_PLAYER_VIEWBOB_TIMER 10.0 -#define SF2_PLAYER_VIEWBOB_SCALE_X 0.05 -#define SF2_PLAYER_VIEWBOB_SCALE_Y 0.0 -#define SF2_PLAYER_VIEWBOB_SCALE_Z 0.0 +void Client_SetupAPI() +{ + Sprint_SetupAPI(); +} -MRESReturn Hook_ClientWantsLagCompensationOnEntity(int client, DHookReturn returnHandle, DHookParam params) +static void OnPutInServer(SF2_BasePlayer client) { - if (!g_Enabled || IsFakeClient(client)) - { - return MRES_Ignored; - } + SDKHook(client.index, SDKHook_OnTakeDamage, OnTakeDamage); + SDKHook(client.index, SDKHook_OnTakeDamageAlivePost, OnTakeDamageAlivePost); +} - returnHandle.Value = true; - return MRES_Supercede; +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + CreateTimer(0.1, Timer_CheckHealth, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } -public Action CH_ShouldCollide(int ent1,int ent2, bool &result) +static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { - SF2RoundState state = GetRoundState(); - if (state == SF2RoundState_Intro || state == SF2RoundState_Outro) + SF2_BasePlayer victimPlayer = SF2_BasePlayer(victim); + if (!g_Enabled) { + if (NPCGetFromEntIndex(attacker) != -1 && GetEntProp(attacker, Prop_Data, "m_iTeamNum") == victimPlayer.Team) + { + damage = 0.0; + return Plugin_Changed; + } return Plugin_Continue; } - if (MaxClients >= ent1 > 0) + Action action = Plugin_Continue; + + float damage2 = damage; + Call_StartForward(g_OnClientTakeDamageFwd); + Call_PushCell(victimPlayer.index); + Call_PushCellRef(attacker); + Call_PushCellRef(inflictor); + Call_PushFloatRef(damage2); + Call_Finish(action); + + if (action == Plugin_Changed) { - if (IsClientInGhostMode(ent1)) + damage = damage2; + return Plugin_Changed; + } + + Call_StartForward(g_OnPlayerTakeDamagePFwd); + Call_PushCell(victimPlayer); + Call_PushCellRef(attacker); + Call_PushCellRef(inflictor); + Call_PushFloatRef(damage2); + Call_PushCellRef(damagetype); + Call_Finish(action); + + if (action == Plugin_Changed) + { + damage = damage2; + return Plugin_Changed; + } + + TFClassType class = victimPlayer.Class; + int classToInt = view_as(class); + + if (IsRoundInWarmup() && IsValidClient(attacker)) + { + float modelScale = GetEntPropFloat(attacker, Prop_Send, "m_flModelScale"); + float headScale = GetEntPropFloat(attacker, Prop_Send, "m_flHeadScale"); + float torsoScale = GetEntPropFloat(attacker, Prop_Send, "m_flTorsoScale"); + float handScale = GetEntPropFloat(attacker, Prop_Send, "m_flHandScale"); + if (modelScale < 1.0 || modelScale > 1.0 || headScale < 1.0 || headScale > 1.0 || torsoScale < 1.0 || torsoScale > 1.0 || handScale < 1.0 || handScale > 1.0) { - result = false; + damage = 0.0; //So how does it feel? return Plugin_Changed; } } - if (MaxClients >= ent2 > 0) + + if (IsClientInKart(victim) && (attacker == -1 || inflictor == -1)) { - if (IsClientInGhostMode(ent2)) + damage = 0.0; + return Plugin_Changed; + } + + char inflictorClass[32]; + if (inflictor >= 0) + { + GetEdictClassname(inflictor, inflictorClass, sizeof(inflictorClass)); + } + + if (IsValidClient(attacker) && IsValidClient(victim) && g_PlayerProxy[attacker] && GetClientTeam(victim) == TFTeam_Red && TF2_IsPlayerInCondition(victim, TFCond_Gas)) + { + TF2_IgnitePlayer(victim, victim); + TF2_RemoveCondition(victim, TFCond_Gas); + } + + if (TF2_IsPlayerInCondition(victim, TFCond_Gas) && SF2_ChaserEntity(attacker).IsValid()) + { + TF2_IgnitePlayer(victim, victim); + TF2_RemoveCondition(victim, TFCond_Gas); + } + + if (IsValidClient(attacker) && IsValidClient(victim) && (IsClientInPvP(victim) || IsClientInPvE(victim)) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) + { + damage = 0.0; + return Plugin_Changed; + } + + if (IsValidClient(attacker) && victimPlayer.IsValid && victimPlayer.Team == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && (victimPlayer.IsTrapped || victimPlayer.IsLatched)) + { + if (!g_PlayerEliminated[attacker] && !victimPlayer.IsEliminated && (damagetype & 0x80) != 0) { - result = false; + victimPlayer.IsTrapped = false; + if (victimPlayer.IsLatched) + { + victimPlayer.ChangeCondition(TFCond_Dazed, true); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + if (victimPlayer.Latcher != npc.Index) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + + chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString("break tongue"); + } + } + victimPlayer.IsLatched = false; + victimPlayer.LatchCount = 0; + TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); + TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); + } + } + + if (IsValidClient(attacker) && !g_PlayerEliminated[attacker] && !DidClientEscape(attacker) && class == TFClass_Soldier && !(GetEntityFlags(attacker) & FL_ONGROUND)) + { + int weaponEnt = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); + if (IsValidEntity(weaponEnt)) + { + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + float zVelocity[3]; + GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); + if (itemDefInt == 416 && zVelocity[2] < 0.0 && weaponEnt == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) //A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. + { + damagetype |= DMG_ACID; + } + } + } + + if (IsValidEntity(inflictor) && !SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) + { + int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); + if (npcIndex != -1) + { + bool attackEliminated = (NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS) != 0; + if (!attackEliminated && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Blue) + { + damage = 0.0; + return Plugin_Changed; + } + } + } + + bool canDamage = false; + if (attacker != victim && IsValidClient(attacker)) + { + if (IsClientInPvP(victim) && IsClientInPvP(attacker)) + { + canDamage = true; + } + if (IsClientLeavingPvP(victim) && !IsClientInPvP(attacker)) + { + canDamage = true; + } + if (!IsRoundEnding()) + { + if (canDamage) + { + if (attacker == inflictor) + { + if (IsValidEdict(weapon)) + { + char weaponClass[64]; + GetEdictClassname(weapon, weaponClass, sizeof(weaponClass)); + + // Backstab check! + if ((strcmp(weaponClass, "tf_weapon_knife") == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy") == 0)) && + (damagecustom != TF_CUSTOM_TAUNT_FENCING)) + { + float myPos[3], hisPos[3], myDirection[3]; + GetClientAbsOrigin(victim, myPos); + GetClientAbsOrigin(attacker, hisPos); + GetClientEyeAngles(victim, myDirection); + GetAngleVectors(myDirection, myDirection, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(myDirection, myDirection); + ScaleVector(myDirection, 32.0); + AddVectors(myDirection, myPos, myDirection); + + float p[3], s[3]; + MakeVectorFromPoints(myPos, hisPos, p); + MakeVectorFromPoints(myPos, myDirection, s); + if (GetVectorDotProduct(p, s) <= 0.0)//We can backstab him m8 + { + if (GetClientTeam(victim) == GetClientTeam(attacker) && class == TFClass_Sniper) + { + //look if the player has a razorback + int wearableEnt = INVALID_ENT_REFERENCE; + while ((wearableEnt = FindEntityByClassname(wearableEnt, "tf_wearable")) != -1) + { + if (GetEntPropEnt(wearableEnt, Prop_Send, "m_hOwnerEntity") == victim && GetEntProp(wearableEnt, Prop_Send, "m_iItemDefinitionIndex") == 57) + { + RemoveEntity(wearableEnt); + damage = 0.0; + EmitSoundToClient(victim, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + + SetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); + SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); + int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); + if (vm > MaxClients) + { + int meleeIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); + int anim = 41; + switch (meleeIndex) + { + case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: + { + anim = 15; + } + case 638: + { + anim = 31; + } + } + SetEntProp(vm, Prop_Send, "m_nSequence", anim); + } + return Plugin_Changed; + } + } + } + if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. + { + damage = 120.0; + if (damagetype & DMG_ACID) + { + damage = 120.0; + } + } + + if (g_WeaponCriticalsConVar != null && g_WeaponCriticalsConVar.BoolValue) + { + damagetype |= DMG_ACID; + } + + if (!IsClientCritUbercharged(victim)) + { + if (GetClientTeam(victim) == GetClientTeam(attacker)) + { + int pistol = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); + if (pistol > MaxClients && GetEntProp(pistol, Prop_Send, "m_iItemDefinitionIndex") == 525) //Give one crit fort the backstab + { + int crits = GetEntProp(attacker, Prop_Send, "m_iRevengeCrits"); + crits++; + SetEntProp(attacker, Prop_Send, "m_iRevengeCrits", crits); + } + } + if (GetEntProp(victim, Prop_Send, "m_iHealth") <= 120) + { + g_PlayerBackStabbed[victim] = true; + } + else + { + g_PlayerBackStabbed[victim] = false; + } + } + return Plugin_Changed; + } + } + } + } + } + else if (g_PlayerProxy[victim] || g_PlayerProxy[attacker]) + { + if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) + { + damage = 0.0; + return Plugin_Changed; + } + + if (attacker == victim)//Don't allow proxy to self regenerate control. + { + return Plugin_Continue; + } + + if (g_PlayerProxy[attacker]) + { + int maxHealth = SDKCall(g_SDKGetMaxHealth, victim); + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[attacker]); + if (master != -1) + { + int difficulty = GetLocalGlobalDifficulty(master); + if (damagecustom == TF_CUSTOM_TAUNT_GRAND_SLAM || + damagecustom == TF_CUSTOM_TAUNT_FENCING || + damagecustom == TF_CUSTOM_TAUNT_ARROW_STAB || + damagecustom == TF_CUSTOM_TAUNT_GRENADE || + damagecustom == TF_CUSTOM_TAUNT_BARBARIAN_SWING || + damagecustom == TF_CUSTOM_TAUNT_ENGINEER_ARM || + damagecustom == TF_CUSTOM_TAUNT_ARMAGEDDON) + { + if (damage >= float(maxHealth)) + { + damage = float(maxHealth) * 0.5; + } + else + { + damage = 0.0; + } + } + else if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. + { + damage = float(maxHealth) * g_SlenderProxyDamageVsBackstab[master][difficulty]; + if (damagetype & DMG_ACID) + { + damage /= 2.0; + } + } + + g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitEnemy[master][difficulty]; + if (g_PlayerProxyControl[attacker] > 100) + { + g_PlayerProxyControl[attacker] = 100; + } + + float originalPercentage = g_SlenderProxyDamageVsEnemy[master][difficulty]; + float additionPercentage = 0.15; + if (!IsClassConfigsValid()) + { + if (class == TFClass_Medic) + { + damage *= (originalPercentage + additionPercentage); + } + else + { + damage *= originalPercentage; + } + } + else + { + damage *= originalPercentage + g_ClassProxyDamageVulnerability[classToInt]; + } + } + return Plugin_Changed; + } + else if (g_PlayerProxy[victim]) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[victim]); + if (master != -1) + { + NPCGetProfile(master, profile, sizeof(profile)); + int difficulty = GetLocalGlobalDifficulty(master); + g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitByEnemy[master][difficulty]; + if (g_PlayerProxyControl[attacker] > 100) + { + g_PlayerProxyControl[attacker] = 100; + } + + damage *= g_SlenderProxyDamageVsSelf[master][difficulty]; + } + if (TF2_IsPlayerInCondition(victim, view_as(87))) + { + damage = 0.0; + } + if (damage * (damagetype & DMG_CRIT ? 3.0 : 1.0) >= float(GetClientHealth(victim)) && !TF2_IsPlayerInCondition(victim, view_as(87)))//The proxy is about to die + { + char buffer[PLATFORM_MAX_PATH]; + int classIndex = view_as(TF2_GetPlayerClass(victim)); + ArrayList deathAnims = GetBossProfileProxyDeathAnimations(profile); + if (deathAnims != null) + { + deathAnims.GetString(classIndex, buffer, sizeof(buffer)); + if (buffer[0] == '\0') + { + deathAnims.GetString(0, buffer, sizeof(buffer)); + } + if (buffer[0] != '\0') + { + g_ClientMaxFrameDeathAnim[victim]=GetBossProfileProxyDeathAnimFrames(profile, classIndex); + if (g_ClientMaxFrameDeathAnim[victim] == 0) + { + g_ClientMaxFrameDeathAnim[victim] = GetBossProfileProxyDeathAnimFrames(profile, 0); + } + if (g_ClientMaxFrameDeathAnim[victim] > 0) + { + // Cancel out any other taunts. + if (TF2_IsPlayerInCondition(victim, TFCond_Taunting)) + { + TF2_RemoveCondition(victim, TFCond_Taunting); + } + //The model has a death anim play it. + SDK_PlaySpecificSequence(victim,buffer); + g_ClientFrame[victim] = 0; + RequestFrame(ProxyDeathAnimation,victim); + TF2_AddCondition(victim, view_as(87), 5.0); + //Prevent death, and show the damage to the attacker. + TF2_AddCondition(victim, view_as(70), 0.5); + return Plugin_Changed; + } + } + } + + //the player has no death anim leave him die. + } + return Plugin_Changed; + } + } + else + { + damage = 0.0; + return Plugin_Changed; + } + } + else + { + if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) + { + damage = 0.0; + return Plugin_Changed; + } + } + + if (IsClientInGhostMode(victim)) + { + damage = 0.0; return Plugin_Changed; } } + return Plugin_Continue; } -public Action CH_PassFilter(int ent1,int ent2, bool &result) +static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float damage, int damageType, int weaponEntIndex, const float vecDamageForce[3], const float vecDamagePosition[3]) +{ + Call_StartForward(g_OnPlayerTakeDamagePostPFwd); + Call_PushCell(SF2_BasePlayer(victim)); + Call_PushCell(attacker); + Call_PushCell(inflictor); + Call_PushFloat(damage); + Call_PushCell(damageType); + Call_Finish(); +} + +static Action Timer_CheckHealth(Handle timer, any id) +{ + int client = GetClientOfUserId(id); + if (!IsValidClient(client)) + { + return Plugin_Stop; + } + + g_ClientModifiedMaxHealth[client] = SF2_BasePlayer(client).Health; + + return Plugin_Stop; +} + +// ========================================================== +// GENERAL CLIENT HOOK FUNCTIONS +// ========================================================== + +#define SF2_PLAYER_VIEWBOB_TIMER 10.0 +#define SF2_PLAYER_VIEWBOB_SCALE_X 0.05 +#define SF2_PLAYER_VIEWBOB_SCALE_Y 0.0 +#define SF2_PLAYER_VIEWBOB_SCALE_Z 0.0 + +MRESReturn Hook_ClientWantsLagCompensationOnEntity(int client, DHookReturn returnHandle, DHookParam params) +{ + if (!g_Enabled || IsFakeClient(client)) + { + return MRES_Ignored; + } + + returnHandle.Value = true; + return MRES_Supercede; +} + +public Action CH_PassFilter(int ent1, int ent2, bool &result) { SF2RoundState state = GetRoundState(); if (state == SF2RoundState_Intro || state == SF2RoundState_Outro) @@ -181,10 +579,20 @@ void ClientSetScareBoostEndTime(int client, float time) g_PlayerScareBoostEndTime[client] = time; } +int ClientGetModifiedMaxHealth(int client) +{ + return g_ClientModifiedMaxHealth[client]; +} + Action Hook_HealthKitOnTouch(int healthKit, int client) { - if (MaxClients >= client > 0 && IsClientInGame(client)) + if (IsValidClient(client)) { + if (IsClientInPvE(client) || IsClientInPvP(client)) + { + return Plugin_Continue; + } + TFClassType class = TF2_GetPlayerClass(client); int classToInt = view_as(class); if (!SF_IsBoxingMap()) @@ -204,6 +612,7 @@ Action Hook_HealthKitOnTouch(int healthKit, int client) } } } + if (IsClientInGhostMode(client)) { return Plugin_Handled; @@ -212,7 +621,7 @@ Action Hook_HealthKitOnTouch(int healthKit, int client) return Plugin_Continue; } -Action Hook_ClientSetTransmit(int client,int other) +Action Hook_ClientSetTransmit(int client, int other) { if (!g_Enabled) { @@ -253,6 +662,10 @@ Action Hook_ClientSetTransmit(int client,int other) return Plugin_Handled; } } + else + { + + } } return Plugin_Continue; @@ -260,7 +673,9 @@ Action Hook_ClientSetTransmit(int client,int other) public Action TF2_CalcIsAttackCritical(int client, int weapon, char[] weaponName, bool &result) { - if (!g_Enabled || g_RestartSessionEnabled || g_PlayerEliminated[client]) + BossPreview_OnClientAttack(client); + + if (!g_Enabled || g_RestartSessionEnabled || (g_PlayerEliminated[client] && !g_PlayerProxy[client])) { return Plugin_Continue; } @@ -294,16 +709,7 @@ public Action TF2_CalcIsAttackCritical(int client, int weapon, char[] weaponName return Plugin_Continue; } -void Hook_ClientWeaponEquipPost(int client, int weapon) -{ - if (!IsValidClient(client) || !IsClientInGame(client) || !IsValidEdict(weapon)) - { - return; - } - g_DHookWeaponGetCustomDamageType.HookEntity(Hook_Pre, weapon, Hook_WeaponGetCustomDamageType); -} - -Action Hook_TEFireBullets(const char[] te_name,const int[] players,int numClients, float delay) +Action Hook_TEFireBullets(const char[] te_name, const int[] players, int numClients, float delay) { if (!g_Enabled) { @@ -319,50 +725,14 @@ Action Hook_TEFireBullets(const char[] te_name,const int[] players,int numClient return Plugin_Continue; } -void ClientResetStatic(int client) +bool DidClientEscape(int client) +{ + return g_PlayerEscaped[client]; +} + +void ClientEscape(int client) { - g_PlayerStaticMaster[client] = -1; - g_PlayerStaticTimer[client] = null; - g_PlayerStaticIncreaseRate[client] = 0.0; - g_PlayerStaticDecreaseRate[client] = 0.0; - g_PlayerLastStaticTimer[client] = null; - g_PlayerLastStaticTime[client] = 0.0; - g_PlayerLastStaticVolume[client] = 0.0; - g_PlayerInStaticShake[client] = false; - g_PlayerStaticShakeMaster[client] = -1; - g_PlayerStaticShakeMinVolume[client] = 0.0; - g_PlayerStaticShakeMaxVolume[client] = 0.0; - g_PlayerStaticAmount[client] = 0.0; - - if (IsClientInGame(client)) - { - if (g_PlayerStaticSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticSound[client]); - } - if (g_PlayerLastStaticSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); - } - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); - } - } - - g_PlayerStaticSound[client][0] = '\0'; - g_PlayerLastStaticSound[client][0] = '\0'; - g_PlayerStaticShakeSound[client][0] = '\0'; -} - -bool DidClientEscape(int client) -{ - return g_PlayerEscaped[client]; -} - -void ClientEscape(int client) -{ - if (DidClientEscape(client)) + if (DidClientEscape(client)) { return; } @@ -378,33 +748,14 @@ void ClientEscape(int client) g_PlayerPageCount[client] = 0; - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - - ClientHandleGhostMode(client); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { @@ -412,12 +763,6 @@ void ClientEscape(int client) { continue; } - if (EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderInterruptConditions[npcIndex] |= COND_CHASETARGETINVALIDATED; - GetClientAbsOrigin(client, g_SlenderChaseDeathPosition[npcIndex]); - g_BossPathFollower[npcIndex].Invalidate(); - } if (g_NpcChaseOnLookTarget[npcIndex] == null) { continue; @@ -427,16 +772,6 @@ void ClientEscape(int client) { g_NpcChaseOnLookTarget[npcIndex].Erase(foundClient); } - switch (NPCGetType(npcIndex)) - { - case SF2BossType_Chaser: - { - if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderGiveUp[npcIndex] = true; - } - } - } } // Speed recalculation. Props to the creators of FF2/VSH for this snippet. @@ -452,17 +787,16 @@ void ClientEscape(int client) CPrintToChatAll("%t", "SF2 Player Escaped", name); } - if (SF_IsRenevantMap() && g_RenevantMarkForDeath) - { - TF2_RemoveCondition(client, TFCond_MarkedForDeathSilent); - } - CheckRoundWinConditions(); Call_StartForward(g_OnClientEscapeFwd); Call_PushCell(client); Call_Finish(); + Call_StartForward(g_OnPlayerEscapePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 1) { @@ -491,7 +825,7 @@ Action Timer_TeleportPlayerToEscapePoint(Handle timer, any userid) return Plugin_Stop; } -float ClientGetDistanceFromEntity(int client,int entity) +float ClientGetDistanceFromEntity(int client, int entity) { float startPos[3], endPos[3]; GetClientAbsOrigin(client, startPos); @@ -566,231 +900,9 @@ float ClientGetDefaultWalkSpeed(int client, TFClassType class = TFClass_Unknown) return returnFloat; } -float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknown) -{ - float returnFloat = 340.0; - float returnFloat2 = returnFloat; - Action action = Plugin_Continue; - if (IsValidClient(client)) - { - class = TF2_GetPlayerClass(client); - } - - switch (class) - { - case TFClass_Scout: - { - returnFloat = 305.0; - } - case TFClass_Sniper: - { - returnFloat = 295.0; - } - case TFClass_Soldier: - { - returnFloat = 280.0; - } - case TFClass_DemoMan: - { - returnFloat = 280.0; - } - case TFClass_Heavy: - { - returnFloat = 280.0; - } - case TFClass_Medic: - { - returnFloat = 290.0; - } - case TFClass_Pyro: - { - returnFloat = 290.0; - } - case TFClass_Spy: - { - returnFloat = 300.0; - } - case TFClass_Engineer: - { - returnFloat = 295.0; - } - } - - if (IsValidClient(client)) - { - // Call our forward. - Call_StartForward(g_OnClientGetDefaultSprintSpeedFwd); - Call_PushCell(client); - Call_PushCellRef(returnFloat2); - Call_Finish(action); - } - - if (action == Plugin_Changed) - { - returnFloat = returnFloat2; - } - - return returnFloat; -} - -// Static shaking should only affect the x, y portion of the player's view, not roll. -// This is purely for cosmetic effect. - -void ClientProcessStaticShake(int client) -{ - if (!IsClientInGame(client) || !IsPlayerAlive(client)) - { - return; - } - - bool oldStaticShake = g_PlayerInStaticShake[client]; - int oldStaticShakeMaster = NPCGetFromUniqueID(g_PlayerStaticShakeMaster[client]); - int newStaticShakeMaster = -1; - - float oldPunchAng[3], oldPunchAngVel[3]; - GetEntDataVector(client, g_PlayerPunchAngleOffset, oldPunchAng); - GetEntDataVector(client, g_PlayerPunchAngleOffsetVel, oldPunchAngVel); - - float newPunchAng[3], newPunchAngVel[3]; - - for (int i = 0; i < 3; i++) - { - newPunchAng[i] = oldPunchAng[i]; - newPunchAngVel[i] = oldPunchAngVel[i]; - } - - if (newStaticShakeMaster != -1) - { - g_PlayerStaticShakeMaster[client] = NPCGetUniqueID(newStaticShakeMaster); - - if (newStaticShakeMaster != oldStaticShakeMaster) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(newStaticShakeMaster, profile, sizeof(profile)); - - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); - } - - g_PlayerStaticShakeMinVolume[client] = GetBossProfileStaticShakeLocalVolumeMin(profile); - g_PlayerStaticShakeMaxVolume[client] = GetBossProfileStaticShakeLocalVolumeMax(profile); - - char staticSound[PLATFORM_MAX_PATH]; - GetBossProfileStaticShakeSound(profile, staticSound, sizeof(staticSound)); - if (staticSound[0] != '\0') - { - strcopy(g_PlayerStaticShakeSound[client], sizeof(g_PlayerStaticShakeSound[]), staticSound); - } - else - { - g_PlayerStaticShakeSound[client][0] = '\0'; - } - } - } - - if (g_PlayerInStaticShake[client]) - { - if (g_PlayerStaticAmount[client] <= 0.0) - { - g_PlayerInStaticShake[client] = false; - } - } - else - { - if (newStaticShakeMaster != -1) - { - g_PlayerInStaticShake[client] = true; - } - } - - if (g_PlayerInStaticShake[client] && !oldStaticShake) - { - for (int i = 0; i < 2; i++) - { - newPunchAng[i] = 0.0; - newPunchAngVel[i] = 0.0; - } - - SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); - SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); - } - else if (!g_PlayerInStaticShake[client] && oldStaticShake) - { - for (int i = 0; i < 2; i++) - { - newPunchAng[i] = 0.0; - newPunchAngVel[i] = 0.0; - } - - g_PlayerStaticShakeMaster[client] = -1; - - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); - } - - g_PlayerStaticShakeSound[client][0] = '\0'; - - g_PlayerStaticShakeMinVolume[client] = 0.0; - g_PlayerStaticShakeMaxVolume[client] = 0.0; - - SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); - SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); - } - - if (g_PlayerInStaticShake[client]) - { - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - float volume = g_PlayerStaticAmount[client]; - if (GetRandomFloat(0.0, 1.0) <= 0.35) - { - volume = 0.0; - } - else - { - if (volume < g_PlayerStaticShakeMinVolume[client]) - { - volume = g_PlayerStaticShakeMinVolume[client]; - } - - if (volume > g_PlayerStaticShakeMaxVolume[client]) - { - volume = g_PlayerStaticShakeMaxVolume[client]; - } - } - - EmitSoundToClient(client, g_PlayerStaticShakeSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL | SND_STOP, volume); - } - - // Spazz our view all over the place. - for (int i = 0; i < 2; i++) - { - newPunchAng[i] = AngleNormalize(GetRandomFloat(0.0, 360.0)); - } - NormalizeVector(newPunchAng, newPunchAng); - - float angVelocityScalar = 5.0 * g_PlayerStaticAmount[client]; - if (angVelocityScalar < 1.0) - { - angVelocityScalar = 1.0; - } - ScaleVector(newPunchAng, angVelocityScalar); - - for (int i = 0; i < 2; i++) - { - newPunchAngVel[i] = 0.0; - } - - SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); - SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); - } -} - void ClientProcessVisibility(int client) { - if (!IsClientInGame(client) || !IsPlayerAlive(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client)) { return; } @@ -830,7 +942,7 @@ void ClientProcessVisibility(int client) if (boss && boss != INVALID_ENT_REFERENCE) { - SlenderGetAbsOrigin(i, slenderPos); + CBaseEntity(boss).GetAbsOrigin(slenderPos); NPCGetEyePosition(i, slenderEyePos); float slenderMins[3], slenderMaxs[3]; @@ -902,7 +1014,7 @@ void ClientProcessVisibility(int client) // Process death cam sequence with static if (g_PlayerStaticAmount[client] >= 1.0) { - ClientStartDeathCam(client, NPCGetFromUniqueID(g_PlayerStaticMaster[client]), slenderPos, true); + ClientStartDeathCam(client, NPCGetFromUniqueID(g_PlayerStaticMaster[client]), slenderPos, false, true); } } } @@ -922,7 +1034,6 @@ void ClientProcessVisibility(int client) if (GetGameTime() >= g_PlayerScareNextTime[client][master]) { - g_PlayerScaredByBoss[client][master] = false; if (GetVectorSquareMagnitude(myPos, slenderPos) <= SquareFloat(NPCGetScareRadius(i))) { ClientPerformScare(client, master); @@ -955,8 +1066,7 @@ void ClientProcessVisibility(int client) if (NPCGetScareReplenishSprintState(master)) { - int clientSprintPoints = ClientGetSprintPoints(client); - g_PlayerSprintPoints[client] = clientSprintPoints + NPCGetScareReplenishSprintAmount(master); + SF2_BasePlayer(client).Stamina += NPCGetScareReplenishSprintAmount(master); } float value = NPCGetAttributeValue(master, SF2Attribute_IgnitePlayerOnScare); @@ -964,51 +1074,29 @@ void ClientProcessVisibility(int client) { TF2_IgnitePlayer(client, client); } + value = NPCGetAttributeValue(master, SF2Attribute_MarkPlayerForDeathOnScare); if (value > 0.0) { TF2_AddCondition(client, TFCond_MarkedForDeath, value); } + value = NPCGetAttributeValue(master, SF2Attribute_SilentMarkPlayerForDeathOnScare); if (value > 0.0) { TF2_AddCondition(client, TFCond_MarkedForDeathSilent, value); } + if (NPCHasAttribute(master, SF2Attribute_ChaseTargetOnScare)) { - if (g_SlenderState[i] != STATE_CHASE && g_SlenderState[i] != STATE_ATTACK && g_SlenderState[i] != STATE_STUN) + SF2_ChaserEntity chaser = SF2_ChaserEntity(boss); + if (chaser.IsValid() && chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) { - int slender = NPCGetEntIndex(i); - g_NpcPlayerScareVictin[i] = EntIndexToEntRef(client); - g_SlenderState[i] = STATE_CHASE; - GetClientAbsOrigin(client, g_SlenderGoalPos[i]); - g_SlenderTarget[i] = EntIndexToEntRef(client); - g_SlenderTimeUntilNoPersistence[i] = GetGameTime() + NPCChaserGetChaseDuration(i, difficulty); - g_SlenderTimeUntilAlert[i] = GetGameTime() + NPCChaserGetChaseDuration(i, difficulty); - SlenderPerformVoice(i, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(i) && !g_NpcUsesChaseInitialAnimation[i] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[i] == null) - { - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - g_NpcUsesChaseInitialAnimation[i] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - NPCChaserUpdateBossAnimation(i, slender, g_SlenderState[i]); - g_SlenderChaseInitialTimer[i] = CreateTimer(g_SlenderAnimationDuration[i], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - } - } - else - { - if (i != -1 && slender && slender != INVALID_ENT_REFERENCE) - { - NPCChaserUpdateBossAnimation(i, slender, g_SlenderState[i]); - } - } - g_PlayerScaredByBoss[client][i] = true; - SlenderAlertAllValidBosses(i, client, client); + SF2_BasePlayer(client).SetForceChaseState(SF2NPC_BaseNPC(i), true); + SetTargetMarkState(SF2NPC_BaseNPC(i), CBaseEntity(client), true); } } + if (NPCGetJumpscareOnScare(master)) { float jumpScareDuration = NPCGetJumpscareDuration(master, difficulty); @@ -1030,6 +1118,11 @@ void ClientProcessVisibility(int client) } } + Call_StartForward(g_OnPlayerLookAtBossPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(SF2NPC_BaseNPC(i)); + Call_Finish(); + Call_StartForward(g_OnClientLooksAtBossFwd); Call_PushCell(client); Call_PushCell(i); @@ -1112,6 +1205,15 @@ void ClientProcessVisibility(int client) } } + if (bossNewStatic != -1) + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(bossNewStatic)); + if (chaser.IsValid() && chaser.State == STATE_DEATH) + { + bossNewStatic = -1; + } + } + if (bossNewStatic != -1) { int copyMaster = NPCGetFromUniqueID(g_SlenderCopyMaster[bossNewStatic]); @@ -1130,7 +1232,7 @@ void ClientProcessVisibility(int client) g_PlayerStaticMaster[client] = -1; } - if (bossNewStatic != bossLastStatic) + if (bossNewStatic != bossLastStatic && !DidClientEscape(client)) { if (strcmp(g_PlayerLastStaticSound[client], g_PlayerStaticSound[client], false) != 0) { @@ -1183,8 +1285,8 @@ void ClientProcessVisibility(int client) TriggerTimer(g_PlayerLastStaticTimer[client], true); // Start up our own static timer. - float staticIncreaseRate = (g_SlenderStaticRate[bossNewStatic][difficulty] - (g_SlenderStaticRate[bossNewStatic][difficulty] * g_RoundDifficultyModifier)/10); - float staticDecreaseRate = (g_SlenderStaticRateDecay[bossNewStatic][difficulty] + (g_SlenderStaticRateDecay[bossNewStatic][difficulty] * g_RoundDifficultyModifier)/10); + float staticIncreaseRate = (g_SlenderStaticRate[bossNewStatic][difficulty] - (g_SlenderStaticRate[bossNewStatic][difficulty] * GetDifficultyModifier(difficulty)) / 10); + float staticDecreaseRate = (g_SlenderStaticRateDecay[bossNewStatic][difficulty] + (g_SlenderStaticRateDecay[bossNewStatic][difficulty] * GetDifficultyModifier(difficulty)) / 10); if (!IsClassConfigsValid()) { if (class == TFClass_Heavy) @@ -1326,202 +1428,69 @@ void ClientProcessViewAngles(int client) } } -static Action Timer_ClientIncreaseStatic(Handle timer, any userid) +// ========================================================== +// SPECIAL ROUND FUNCTIONS +// ========================================================== + +void ClientSetSpecialRoundTimer(int client, float time, Timer callback, any data, int flags=0) { - int client = GetClientOfUserId(userid); - if (client <= 0) + g_ClientSpecialRoundTimer[client] = CreateTimer(time, callback, data, flags); +} + +Action Timer_ClientPageDetector(Handle timer, int userid) +{ + if (!SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) { return Plugin_Stop; } - - if (timer != g_PlayerStaticTimer[client]) + if (GetRoundState() == SF2RoundState_Escape) { return Plugin_Stop; } - g_PlayerStaticAmount[client] += 0.05; - if (g_PlayerStaticAmount[client] > 1.0) - { - g_PlayerStaticAmount[client] = 1.0; - } + int client = GetClientOfUserId(userid); - if (g_PlayerStaticSound[client][0] != '\0') + if (!IsValidClient(client)) { - EmitSoundToClient(client, g_PlayerStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerStaticAmount[client]); - - if (g_PlayerStaticAmount[client] >= 0.5) - { - ClientAddStress(client, 0.03); - } - else - { - ClientAddStress(client, 0.02); - } + return Plugin_Stop; } - return Plugin_Continue; -} - -Action Timer_ClientDecreaseStatic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) + if (g_ClientSpecialRoundTimer[client] != timer) { return Plugin_Stop; } - if (timer != g_PlayerStaticTimer[client]) + if (g_PlayerEliminated[client]) { return Plugin_Stop; } - g_PlayerStaticAmount[client] -= 0.05; - if (g_PlayerStaticAmount[client] < 0.0) - { - g_PlayerStaticAmount[client] = 0.0; - } + int closestPageEntIndex = -1; - if (g_PlayerLastStaticSound[client][0] != '\0') - { - float volume = g_PlayerStaticAmount[client]; - if (volume > 0.0) - { - EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); - } - } + float distance = SquareFloat(99999.0); + float clientPos[3], pagePos[3]; + GetClientAbsOrigin(client, clientPos); - if (g_PlayerStaticAmount[client] <= 0.0) + ArrayList pageEntities = new ArrayList(); + GetPageEntities(pageEntities); + + for (int i = 0; i < pageEntities.Length; i++) { - // I've done my job; no point to keep on doing it. - if (g_PlayerLastStaticSound[client][0] != '\0') + CBaseEntity pageEnt = CBaseEntity(pageEntities.Get(i)); + pageEnt.GetAbsOrigin(pagePos); + + float squareDistance = GetVectorSquareMagnitude(clientPos, pagePos); + + if (closestPageEntIndex == -1 || squareDistance < distance) { - StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + closestPageEntIndex = pageEnt.index; + distance = squareDistance; } - g_PlayerStaticTimer[client] = null; - return Plugin_Stop; } - return Plugin_Continue; -} + delete pageEntities; -static Action Timer_ClientFadeOutLastStaticSound(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerLastStaticTimer[client]) - { - return Plugin_Stop; - } - - if (strcmp(g_PlayerLastStaticSound[client], g_PlayerStaticSound[client], false) == 0) - { - // Wait, the player's current static sound is the same one we're stopping. Abort! - g_PlayerLastStaticTimer[client] = null; - return Plugin_Stop; - } - - if (g_PlayerLastStaticSound[client][0] != '\0') - { - float diff = (GetGameTime() - g_PlayerLastStaticTime[client]) / 1.0; - if (diff > 1.0) - { - diff = 1.0; - } - - float volume = g_PlayerLastStaticVolume[client] - diff; - if (volume < 0.0) - { - volume = 0.0; - } - - if (volume <= 0.0) - { - // I've done my job; no point to keep on doing it. - StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); - g_PlayerLastStaticTimer[client] = null; - return Plugin_Stop; - } - else - { - EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); - } - } - else - { - // I've done my job; no point to keep on doing it. - g_PlayerLastStaticTimer[client] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -// ========================================================== -// SPECIAL ROUND FUNCTIONS -// ========================================================== - -void ClientSetSpecialRoundTimer(int client, float time, Timer callback, any data, int flags=0) -{ - g_ClientSpecialRoundTimer[client] = CreateTimer(time, callback, data, flags); -} - -Action Timer_ClientPageDetector(Handle timer, int userid) -{ - if (!SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) - { - return Plugin_Stop; - } - if (GetRoundState() == SF2RoundState_Escape) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - if (g_ClientSpecialRoundTimer[client] != timer) - { - return Plugin_Stop; - } - - if (!IsValidClient(client)) - { - return Plugin_Stop; - } - - if (g_PlayerEliminated[client]) - { - return Plugin_Stop; - } - - int closestPageEntIndex = -1; - - float distance = SquareFloat(99999.0); - float clientPos[3], pagePos[3]; - GetClientAbsOrigin(client, clientPos); - - ArrayList pageEntities = new ArrayList(); - GetPageEntities(pageEntities); - - for (int i = 0; i < pageEntities.Length; i++) - { - CBaseEntity pageEnt = CBaseEntity(pageEntities.Get(i)); - pageEnt.GetAbsOrigin(pagePos); - - float squareDistance = GetVectorSquareMagnitude(clientPos, pagePos); - - if (closestPageEntIndex == -1 || squareDistance < distance) - { - closestPageEntIndex = pageEnt.index; - distance = squareDistance; - } - } - - delete pageEntities; - - float nextBeepTime = distance/SquareFloat(800.0); + float nextBeepTime = distance / SquareFloat(800.0); if (nextBeepTime > 5.0) { @@ -1532,1562 +1501,104 @@ Action Timer_ClientPageDetector(Handle timer, int userid) nextBeepTime = 0.1; } - EmitSoundToClient(client, PAGE_DETECTOR_BEEP, _, _, _, _, _, 100 - RoundToNearest(nextBeepTime * 10.0)); + EmitSoundToClient(client, PAGE_DETECTOR_BEEP, _, _, _, _, 0.7, 100 - RoundToNearest(nextBeepTime * 10.0)); g_ClientSpecialRoundTimer[client] = CreateTimer(nextBeepTime, Timer_ClientPageDetector, userid, TIMER_FLAG_NO_MAPCHANGE); return Plugin_Stop; } // ========================================================== -// INTERACTIVE GLOW FUNCTIONS +// GHOST FUNCTIONS // ========================================================== -void ClientProcessInteractiveGlow(int client) -{ - if (!IsClientInGame(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !g_PlayerProxy[client]) || IsClientInGhostMode(client)) - { - return; - } - - int oldLookEntity = EntRefToEntIndex(g_PlayerInteractiveGlowTargetEntity[client]); - - float startPos[3], myEyeAng[3]; - GetClientEyePosition(client, startPos); - GetClientEyeAngles(client, myEyeAng); - - Handle trace = TR_TraceRayFilterEx(startPos, myEyeAng, MASK_VISIBLE, RayType_Infinite, TraceRayDontHitPlayers, -1); - int entity = TR_GetEntityIndex(trace); - delete trace; - - if (IsValidEntity(entity)) - { - g_PlayerInteractiveGlowTargetEntity[client] = EntRefToEntIndex(entity); - } - else - { - g_PlayerInteractiveGlowTargetEntity[client] = INVALID_ENT_REFERENCE; - } - - if (entity != oldLookEntity) - { - ClientRemoveInteractiveGlow(client); - - if (IsEntityClassname(entity, "prop_dynamic", false) || IsEntityClassname(entity, "tf_taunt_prop", false)) - { - char targetName[64]; - GetEntPropString(entity, Prop_Data, "m_iName", targetName, sizeof(targetName)); - - if (StrContains(targetName, "sf2_page", false) == 0 || StrContains(targetName, "sf2_interact", false) == 0) - { - ClientCreateInteractiveGlow(client, entity); - } - } - } -} - -void ClientResetInteractiveGlow(int client) -{ - ClientRemoveInteractiveGlow(client); - g_PlayerInteractiveGlowTargetEntity[client] = INVALID_ENT_REFERENCE; -} - -/** - * Removes the player's current interactive glow entity. - */ -void ClientRemoveInteractiveGlow(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientRemoveInteractiveGlow(%d)", client); - } - #endif - - int ent = EntRefToEntIndex(g_PlayerInteractiveGlowEntity[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - RemoveEntity(ent); - } - - g_PlayerInteractiveGlowEntity[client] = INVALID_ENT_REFERENCE; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientRemoveInteractiveGlow(%d)", client); - } - #endif -} - -/** - * Creates an interactive glow for an entity to show to a player. - */ -static bool ClientCreateInteractiveGlow(int client,int entity, const char[] attachment="") -{ - ClientRemoveInteractiveGlow(client); - - if (!IsClientInGame(client)) - { - return false; - } - - if (!entity || !IsValidEdict(entity)) - { - return false; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientCreateInteractiveGlow(%d)", client); - } - #endif - - char buffer[PLATFORM_MAX_PATH]; - GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - - if (buffer[0] == '\0') - { - return false; - } - - int ent = CreateEntityByName("tf_taunt_prop"); - if (ent != -1) - { - g_PlayerInteractiveGlowEntity[client] = EntIndexToEntRef(ent); - - float modelScale = GetEntPropFloat(entity, Prop_Send, "m_flModelScale"); - - SetEntityModel(ent, buffer); - DispatchSpawn(ent); - ActivateEntity(ent); - SetEntityRenderMode(ent, RENDER_TRANSCOLOR); - SetEntityRenderColor(ent, 0, 0, 0, 0); - SetEntPropFloat(ent, Prop_Send, "m_flModelScale", modelScale); - - int flags = GetEntProp(ent, Prop_Send, "m_fEffects"); - SetEntProp(ent, Prop_Send, "m_fEffects", flags | (1 << 0)); - SetEntProp(ent, Prop_Send, "m_bGlowEnabled", true); - - SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", entity); - - if (attachment[0] != '\0') - { - SetVariantString(attachment); - AcceptEntityInput(ent, "SetParentAttachment"); - } - - SDKHook(ent, SDKHook_SetTransmit, Hook_InterativeGlowSetTransmit); - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientCreateInteractiveGlow(%d) -> true", client); - } - #endif - - return true; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientCreateInteractiveGlow(%d) -> false", client); - } - #endif - - return false; -} - -static Action Hook_InterativeGlowSetTransmit(int ent,int other) +Action Timer_ClassScramblePlayer(Handle timer, any userid) { if (!g_Enabled) - { - return Plugin_Continue; - } - - if (EntRefToEntIndex(g_PlayerInteractiveGlowEntity[other]) != ent) - { - return Plugin_Handled; - } - - return Plugin_Continue; -} - -// ========================================================== -// BREATHING FUNCTIONS -// ========================================================== - -void ClientResetBreathing(int client) -{ - g_PlayerBreath[client] = false; - g_PlayerBreathTimer[client] = null; -} - -static float ClientCalculateBreathingCooldown(int client) -{ - float average = 0.0; - int averageNum = 0; - - // Sprinting only, for now. - average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow((float(g_PlayerSprintPoints[client]) / 100.0), 1.65)); - averageNum++; - - average /= float(averageNum); - - if (average < SF2_PLAYER_BREATH_COOLDOWN_MIN) - { - average = SF2_PLAYER_BREATH_COOLDOWN_MIN; - } - - return average; -} - -void ClientStartBreathing(int client) -{ - g_PlayerBreath[client] = true; - g_PlayerBreathTimer[client] = CreateTimer(ClientCalculateBreathingCooldown(client), Timer_ClientBreath, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); -} - -static void ClientStopBreathing(int client) -{ - g_PlayerBreath[client] = false; - g_PlayerBreathTimer[client] = null; -} - -bool ClientCanBreath(int client) -{ - return ClientCalculateBreathingCooldown(client) < SF2_PLAYER_BREATH_COOLDOWN_MAX; -} - -static Action Timer_ClientBreath(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerBreathTimer[client]) { return Plugin_Stop; } - if (!g_PlayerBreath[client]) - { - return Plugin_Stop; - } + int client = GetClientOfUserId(userid); - if (ClientCanBreath(client)) + if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) { - EmitSoundToAll(g_PlayerBreathSounds[GetRandomInt(0, sizeof(g_PlayerBreathSounds) - 1)], client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - - ClientStartBreathing(client); return Plugin_Stop; } + g_PlayerRandomClassNumber[client] = GetRandomInt(1, 9); - ClientStopBreathing(client); + // Regenerate player but keep health the same. + int health = GetEntProp(client, Prop_Send, "m_iHealth"); + TF2_RegeneratePlayer(client); + SetEntProp(client, Prop_Data, "m_iHealth", health); + SetEntProp(client, Prop_Send, "m_iHealth", health); return Plugin_Stop; } -// ========================================================== -// SPRINTING FUNCTIONS -// ========================================================== - -bool IsClientSprinting(int client) -{ - return g_PlayerSprint[client]; -} - -int ClientGetSprintPoints(int client) -{ - return g_PlayerSprintPoints[client]; -} - -void ClientResetSprint(int client) +Action Timer_ClassScramblePlayer2(Handle timer, any userid) { - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetSprint(%d)", client); - } - #endif - - bool wasSprinting = IsClientSprinting(client); - - g_PlayerSprint[client] = false; - g_PlayerSprintPoints[client] = 100; - g_PlayerSprintTimer[client] = null; - - if (IsValidClient(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } - - if (wasSprinting) - { - Call_StartForward(g_OnClientStopSprintingFwd); - Call_PushCell(client); - Call_Finish(); - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetSprint(%d)", client); - } - #endif -} - -void ClientStartSprint(int client) -{ - if (IsClientSprinting(client)) - { - return; - } - - g_PlayerSprint[client] = true; - g_PlayerSprintTimer[client] = null; - ClientSprintTimer(client); - TriggerTimer(g_PlayerSprintTimer[client], true); - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) - { - Client90sMusicStart(client); - } - - SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - - Call_StartForward(g_OnClientStartSprintingFwd); - Call_PushCell(client); - Call_Finish(); -} - -void ClientSprintTimer(int client, bool recharge=false) -{ - float rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 0.38 : 0.28; - if (recharge) - { - rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 1.4 : 0.8; - } - - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - float velocity[3]; - GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); - - if (recharge) - { - if (!(GetEntityFlags(client) & FL_ONGROUND)) - { - rate *= 0.75; - } - else if (GetVectorLength(velocity, true) == 0.0) - { - if (GetEntProp(client, Prop_Send, "m_bDucked")) - { - rate *= 0.66; - } - else - { - rate *= 0.75; - } - } - } - else - { - if (!IsClassConfigsValid()) - { - if (class == TFClass_DemoMan) - { - rate *= 1.15; - } - else if (class == TFClass_Medic || class == TFClass_Spy) - { - rate *= 1.05; - } - else if (class == TFClass_Scout) - { - rate *= 1.08; - } - } - else - { - rate *= g_ClassSprintDurationMultipler[classToInt]; - } - } - - if (recharge) - { - g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientRechargeSprint, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientSprinting, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } -} - -void ClientStopSprint(int client) -{ - if (!IsClientSprinting(client)) - { - return; - } - g_PlayerSprint[client] = false; - g_PlayerSprintTimer[client] = null; - ClientSprintTimer(client, true); - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) - { - Client90sMusicStop(client); - } - - SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - - Call_StartForward(g_OnClientStopSprintingFwd); - Call_PushCell(client); - Call_Finish(); -} - -bool IsClientReallySprinting(int client) -{ - if (!IsClientSprinting(client)) - { - return false; - } - if (!(GetEntityFlags(client) & FL_ONGROUND)) - { - return false; - } - - float velocity[3]; - GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); - if (GetVectorLength(velocity, true) < SquareFloat(30.0)) - { - return false; - } - - return true; -} - -// ========================================================== -// GHOST AND GLOW FUNCTIONS -// ========================================================== - -Action Timer_ClassScramblePlayer(Handle timer, any userid) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - - if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) - { - return Plugin_Stop; - } - g_PlayerRandomClassNumber[client] = GetRandomInt(1, 9); - - // Regenerate player but keep health the same. - int health = GetEntProp(client, Prop_Send, "m_iHealth"); - TF2_RegeneratePlayer(client); - SetEntProp(client, Prop_Data, "m_iHealth", health); - SetEntProp(client, Prop_Send, "m_iHealth", health); - - return Plugin_Stop; -} - -Action Timer_ClassScramblePlayer2(Handle timer, any userid) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - - if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) - { - return Plugin_Stop; - } - - // Regenerate player but keep health the same. - int health = GetEntProp(client, Prop_Send, "m_iHealth"); - TF2_RegeneratePlayer(client); - SetEntProp(client, Prop_Data, "m_iHealth", health); - SetEntProp(client, Prop_Send, "m_iHealth", health); - - return Plugin_Stop; -} - -bool DoesClientHaveConstantGlow(int client) -{ - return g_PlayerConstantGlowEnabled[client]; -} - -void ClientDisableConstantGlow(int client) -{ - if (!DoesClientHaveConstantGlow(client)) - { - return; - } - - int glow = EntRefToEntIndex(g_PlayerGlowEntity[client]); - if (glow != INVALID_ENT_REFERENCE) - { - RemoveEntity(glow); - g_PlayerGlowEntity[client] = INVALID_ENT_REFERENCE; - } - - g_PlayerConstantGlowEnabled[client] = false; -} - -void ClientEnableConstantGlow(int client, int color[4] = {255, 255, 255, 255}) -{ - if (!IsValidClient(client)) - { - return; - } - - if (DoesClientHaveConstantGlow(client) || g_PlayerGlowEntity[client] != INVALID_ENT_REFERENCE) - { - return; - } - - char model[PLATFORM_MAX_PATH]; - GetEntPropString(client, Prop_Data, "m_ModelName", model, sizeof(model)); - - if (model[0] == '\0') - { - // For some reason the model couldn't be found, so no. - return; - } - - int glow = TF2_CreateGlow(client); - if (IsValidEntity(glow)) - { - g_PlayerConstantGlowEnabled[client] = true; - - SDKHook(glow, SDKHook_SetTransmit, Hook_ConstantGlowSetTransmit); - - g_PlayerGlowEntity[client] = EntIndexToEntRef(glow); - //Set our desired glow color - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); - g_DHookShouldTransmit.HookEntity(Hook_Pre, glow, Hook_EntityShouldTransmit); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow, Hook_GlowUpdateTransmitState); - } -} - -void ClientResetJumpScare(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetJumpScare(%d)", client); - } - #endif - - g_PlayerJumpScareBoss[client] = -1; - g_PlayerJumpScareLifeTime[client] = -1.0; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetJumpScare(%d)", client); - } - #endif -} - -void ClientDoJumpScare(int client,int bossIndex, float flLifeTime) -{ - g_PlayerJumpScareBoss[client] = NPCGetUniqueID(bossIndex); - g_PlayerJumpScareLifeTime[client] = GetGameTime() + flLifeTime; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char buffer[PLATFORM_MAX_PATH]; - GetBossProfileJumpscareSound(profile, buffer, sizeof(buffer)); - - if (buffer[0] != '\0') - { - EmitSoundToClient(client, buffer, _, MUSIC_CHAN); - } -} - - /** - * Handles sprinting upon player input. - */ -void ClientHandleSprint(int client, bool sprint) -{ - if (!IsPlayerAlive(client) || - g_PlayerEliminated[client] || - DidClientEscape(client) || - g_PlayerProxy[client] || - IsClientInGhostMode(client)) - { - return; - } - - if (sprint) - { - if (g_PlayerSprintPoints[client] > 0) - { - ClientStartSprint(client); - } - else - { - EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); - } - } - else - { - if (IsClientSprinting(client)) - { - ClientStopSprint(client); - } - } -} -/** - * Handles thirdperson peeking - */ -bool ClientStartPeeking(int client) -{ - if (!g_PlayerPeeking[client] && g_AllowPlayerPeekingConVar.BoolValue && !TF2_IsPlayerInCondition(client, TFCond_Dazed) && GetClientButtons(client) & IN_DUCK) - { - TF2_StunPlayer(client, 999.9, 1.0, TF_STUNFLAGS_LOSERSTATE); - g_PlayerPeeking[client] = true; - return true; - } - return false; -} - -void ClientEndPeeking(int client) -{ - if (g_PlayerPeeking[client]) - { - TF2_RemoveCondition(client, TFCond_Dazed); - g_PlayerPeeking[client] = false; - } -} - -// ========================================================== -// DEATH CAM FUNCTIONS -// ========================================================== - -bool IsClientInDeathCam(int client) -{ - return g_PlayerDeathCam[client]; -} - -static Action Hook_DeathCamSetTransmit(int slender,int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - if (EntRefToEntIndex(g_PlayerDeathCamEnt2[other]) != slender) - { - return Plugin_Handled; - } - return Plugin_Continue; -} - -void ClientResetDeathCam(int client) -{ - if (!IsClientInDeathCam(client)) - { - return; // no really need to reset if it wasn't set. - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetDeathCam(%d)", client); - } - #endif - - int deathCamBoss = NPCGetFromUniqueID(g_PlayerDeathCamBoss[client]); - - g_PlayerDeathCamBoss[client] = -1; - g_PlayerDeathCam[client] = false; - g_PlayerDeathCamShowOverlay[client] = false; - g_PlayerDeathCamTimer[client] = null; - - int ent = EntRefToEntIndex(g_PlayerDeathCamEnt[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - g_CameraInDeathCamAdvanced[ent] = false; - AcceptEntityInput(ent, "Disable"); - RemoveEntity(ent); - } - - ent = EntRefToEntIndex(g_PlayerDeathCamEnt2[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - AcceptEntityInput(ent, "Kill"); - } - - ent = EntRefToEntIndex(g_PlayerDeathCamTarget[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - RemoveEntity(ent); - } - - g_PlayerDeathCamEnt[client] = INVALID_ENT_REFERENCE; - g_PlayerDeathCamEnt2[client] = INVALID_ENT_REFERENCE; - g_PlayerDeathCamTarget[client] = INVALID_ENT_REFERENCE; - - if (IsClientInGame(client)) - { - SetClientViewEntity(client, client); - } - - Call_StartForward(g_OnClientEndDeathCamFwd); - Call_PushCell(client); - Call_PushCell(deathCamBoss); - Call_Finish(); - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetDeathCam(%d)", client); - } - #endif -} - -void ClientStartDeathCam(int client,int bossIndex, const float vecLookPos[3], bool antiCamp = false) -{ - if (IsClientInDeathCam(client)) - { - return; - } - if (!NPCIsValid(bossIndex)) - { - return; - } - - for (int npcIndex; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - switch (NPCGetType(npcIndex)) - { - case SF2BossType_Chaser: - { - if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderGiveUp[npcIndex] = true; - } - } - } - } - - GetClientAbsOrigin(client, g_vecPlayerOriginalDeathcamPosition[client]); - - char buffer[PLATFORM_MAX_PATH]; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - if (g_SlenderDeathCamScareSound[bossIndex]) - { - GetBossProfileScareSounds(profile, soundInfo); - soundInfo.EmitSound(true, client); - } - - GetBossProfileClientDeathCamSounds(profile, soundInfo); - soundInfo.EmitSound(true, client); - - GetBossProfileGlobalDeathCamSounds(profile, soundInfo); - for (int i = 0; i < MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - soundInfo.EmitSound(true, i); - } - - // Call our forward. - Call_StartForward(g_OnClientCaughtByBossFwd); - Call_PushCell(client); - Call_PushCell(bossIndex); - Call_Finish(); - - if (!NPCHasDeathCamEnabled(bossIndex) && !(NPCGetFlags(bossIndex) & SFF_FAKE)) - { - SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. - - float value = NPCGetAttributeValue(bossIndex, SF2Attribute_IgnitePlayerOnDeath); - if (value > 0.0) - { - TF2_IgnitePlayer(client, client); - } - - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt > MaxClients) - { - SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - } - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. - KillClient(client); - return; - } - else if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - } - - g_PlayerDeathCamBoss[client] = NPCGetUniqueID(bossIndex); - g_PlayerDeathCam[client] = true; - g_PlayerDeathCamShowOverlay[client] = false; - - float eyePos[3], eyeAng[3], vecAng[3]; - GetClientEyePosition(client, eyePos); - GetClientEyeAngles(client, eyeAng); - SubtractVectors(eyePos, vecLookPos, vecAng); - GetVectorAngles(vecAng, vecAng); - vecAng[0] = 0.0; - vecAng[2] = 0.0; - - // Create fake model. - int slender = SpawnSlenderModel(bossIndex, vecLookPos, true); - TeleportEntity(slender, vecLookPos, vecAng, NULL_VECTOR); - g_PlayerDeathCamEnt2[client] = EntIndexToEntRef(slender); - if (!g_SlenderPublicDeathCam[bossIndex]) - { - SDKHook(slender, SDKHook_SetTransmit, Hook_DeathCamSetTransmit); - } - else - { - GetBossProfileLocalDeathCamSounds(profile, soundInfo); - soundInfo.EmitSound(_, slender); - SetEntityMoveType(client, MOVETYPE_NOCLIP); - if (!antiCamp) - { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderInDeathcam[bossIndex] = true; - SetEntityRenderMode(slenderEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(slenderEnt, g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], 0); - NPCChaserUpdateBossAnimation(bossIndex, slenderEnt, STATE_IDLE); - g_SlenderDeathCamTarget[bossIndex] = EntIndexToEntRef(client); - if (g_SlenderEntityThink[bossIndex] != null) - { - KillTimer(g_SlenderEntityThink[bossIndex]); - } - g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderPublicDeathCamThink, EntIndexToEntRef(slenderEnt), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - } - } - - // Create camera look point. - char name[64]; - FormatEx(name, sizeof(name), "sf2_boss_%d", EntIndexToEntRef(slender)); - - float offsetPos[3]; - int target = CreateEntityByName("info_target"); - if (!g_SlenderPublicDeathCam[bossIndex]) - { - GetBossProfileDeathCamPosition(profile, offsetPos); - AddVectors(vecLookPos, offsetPos, offsetPos); - TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); - DispatchKeyValue(target, "targetname", name); - SetVariantString("!activator"); - AcceptEntityInput(target, "SetParent", slender); - } - else - { - char boneName[PLATFORM_MAX_PATH]; - AddVectors(vecLookPos, offsetPos, offsetPos); - TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); - DispatchKeyValue(target, "targetname", name); - SetVariantString("!activator"); - AcceptEntityInput(target, "SetParent", slender); - GetBossProfilePublicDeathCamTargetAttachment(profile, boneName, sizeof(boneName)); - if (boneName[0] != '\0') - { - SetVariantString(boneName); - AcceptEntityInput(target, "SetParentAttachment"); - } - } - g_PlayerDeathCamTarget[client] = EntIndexToEntRef(target); - - // Create the camera itself. - int camera = CreateEntityByName("point_viewcontrol"); - TeleportEntity(camera, eyePos, eyeAng, NULL_VECTOR); - DispatchKeyValue(camera, "spawnflags", "12"); - DispatchKeyValue(camera, "target", name); - DispatchSpawn(camera); - AcceptEntityInput(camera, "Enable", client); - g_PlayerDeathCamEnt[client] = EntIndexToEntRef(camera); - if (g_SlenderPublicDeathCam[bossIndex]) - { - float camSpeed, camAcceleration, camDeceleration; - - camSpeed = g_SlenderPublicDeathCamSpeed[bossIndex]; - camAcceleration = g_SlenderPublicDeathCamAcceleration[bossIndex]; - camDeceleration = g_SlenderPublicDeathCamDeceleration[bossIndex]; - FloatToString(camSpeed, buffer, sizeof(buffer)); - DispatchKeyValue(camera, "acceleration", buffer); - FloatToString(camAcceleration, buffer, sizeof(buffer)); - DispatchKeyValue(camera, "deceleration", buffer); - FloatToString(camDeceleration, buffer, sizeof(buffer)); - DispatchKeyValue(camera, "speed", buffer); - - SetVariantString("!activator"); - AcceptEntityInput(camera, "SetParent", slender); - char attachmentName[PLATFORM_MAX_PATH]; - GetBossProfilePublicDeathCamAttachment(profile, attachmentName, sizeof(attachmentName)); - if (attachmentName[0] != '\0') - { - SetVariantString(attachmentName); - AcceptEntityInput(camera, "SetParentAttachment"); - } - - g_CameraInDeathCamAdvanced[camera] = true; - g_CameraPlayerOffsetBackward[camera] = g_SlenderPublicDeathCamBackwardOffset[bossIndex]; - g_CameraPlayerOffsetDownward[camera] = g_SlenderPublicDeathCamDownwardOffset[bossIndex]; - RequestFrame(Frame_PublicDeathCam, camera); //Resend taunt sound to eliminated players only - } - - if (g_SlenderDeathCamOverlay[bossIndex] && g_SlenderDeathCamOverlayTimeStart[bossIndex] >= 0.0) - { - if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) - { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_BossDeathCamDelay, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); - } - } - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_ClientResetDeathCam1, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) - { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_BossDeathCamDuration, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); - } - } - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_ClientResetDeathCamEnd, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - - TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); - - Call_StartForward(g_OnClientStartDeathCamFwd); - Call_PushCell(client); - Call_PushCell(bossIndex); - Call_Finish(); -} - -static void Frame_PublicDeathCam(int cameraRef) -{ - int camera = EntRefToEntIndex(cameraRef); - if (IsValidEntity(camera)) - { - int slender = GetEntPropEnt(camera, Prop_Data, "m_hOwnerEntity"); - int client = GetEntPropEnt(camera, Prop_Data, "m_hPlayer"); - if (IsValidEntity(slender) && IsValidClient(client)) - { - float camPos[3], camAngs[3]; - GetEntPropVector(camera, Prop_Data, "m_angAbsRotation", camAngs); - GetEntPropVector(camera, Prop_Data, "m_vecAbsOrigin", camPos); - - camPos[0] -= g_CameraPlayerOffsetBackward[camera]; - camPos[2] -= g_CameraPlayerOffsetDownward[camera]; - - TeleportEntity(client, camPos, camAngs, NULL_VECTOR); - - RequestFrame(Frame_PublicDeathCam, EntIndexToEntRef(cameraRef)); - } - } -} - -static Action Timer_ClientResetDeathCam1(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerDeathCamTimer[client]) - { - return Plugin_Stop; - } - - SF2NPC_BaseNPC Npc = view_as(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - if (Npc.IsValid()) - { - g_PlayerDeathCamShowOverlay[client] = true; - Npc.GetProfile(profile, sizeof(profile)); - SF2BossProfileSoundInfo soundInfo; - GetBossProfilePlayerDeathcamOverlaySounds(profile, soundInfo); - soundInfo.EmitSound(true, client); - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_ClientResetDeathCamEnd, userid, TIMER_FLAG_NO_MAPCHANGE); - } - - return Plugin_Stop; -} - -static Action Timer_BossDeathCamDelay(Handle timer, any entref) -{ - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(slender)); - - if (timer != g_SlenderDeathCamTimer[Npc.Index]) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.GetProfile(profile, sizeof(profile)); - - g_SlenderDeathCamTimer[Npc.Index] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_BossDeathCamDuration, slender, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Stop; -} - -static Action Timer_BossDeathCamDuration(Handle timer, any entref) -{ - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_Chaser Npc = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); - - if (timer != g_SlenderDeathCamTimer[Npc.Index]) - { - return Plugin_Stop; - } - - if (g_SlenderInDeathcam[Npc.Index]) - { - SetEntityRenderMode(slender, RENDER_NORMAL); - if (!Npc.CloakEnabled) - { - SetEntityRenderColor(slender, Npc.GetRenderColor(0), Npc.GetRenderColor(1), Npc.GetRenderColor(2), Npc.GetRenderColor(3)); - } - g_SlenderEntityThink[Npc.Index] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - if (!(Npc.Flags & SFF_FAKE)) - { - g_SlenderInDeathcam[Npc.Index] = false; - } - Npc.UpdateAnimation(slender, Npc.State); - } - if ((Npc.Flags & SFF_FAKE)) - { - if (g_SlenderInDeathcam[Npc.Index]) - { - g_SlenderInDeathcam[Npc.Index] = false; - } - Npc.MarkAsFake(); - } - g_SlenderDeathCamTimer[Npc.Index] = null; - - return Plugin_Stop; -} - -static Action Timer_ClientResetDeathCamEnd(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerDeathCamTimer[client]) - { - return Plugin_Stop; - } - - SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol entity changes our damage state. - - SF2NPC_BaseNPC deathCamBoss = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); - if (deathCamBoss != SF2_INVALID_NPC) - { - float value = deathCamBoss.GetAttributeValue(SF2Attribute_IgnitePlayerOnDeath); - if (value > 0.0) - { - TF2_IgnitePlayer(client, client); - } - if (!(deathCamBoss.Flags & SFF_FAKE)) - { - int slenderEnt = deathCamBoss.EntIndex; - if (slenderEnt > MaxClients) - { - SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - } - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. - KillClient(client); - } - else - { - SetEntityMoveType(client, MOVETYPE_WALK); - TeleportEntity(client, g_vecPlayerOriginalDeathcamPosition[client], NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); - } - } - else//The boss is invalid? But the player got a death cam? - { - //Then kill him anyways. - KillClient(client); - ForcePlayerSuicide(client); - } - ClientResetDeathCam(client); - - return Plugin_Stop; -} -// ========================================================== -// NAV AREA FUNCTIONS -// ========================================================== -/*void ClientNavAreaUpdate(int client, CNavArea newArea, CNavArea oldArea) -{ - if (GetRoundState() != SF2RoundState_Active) return; - - if (g_PlayerEliminated[client]) return; - - if (newArea == INVALID_NAV_AREA) return; - - if ((oldArea != INVALID_NAV_AREA && oldArea.Attributes & NAV_MESH_DONT_HIDE) || newArea.Attributes & NAV_MESH_DONT_HIDE) - { - g_ClientAllowedTimeNearEscape[client] -= 0.3;//Remove 0.3sec this function is called every ~0.3sec - } - else - { - g_ClientAllowedTimeNearEscape[client] += 0.1;//Forgive the player of 0.1 - } - if (g_ClientAllowedTimeNearEscape[client] <= 0.0 && SF_IsSurvivalMap()) - { - g_PlayerIsExitCamping[client] = true; - } - else - { - g_PlayerIsExitCamping[client] = false; - } - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_NAV, 1, "Old area: %i DHF:%s, New area: %i DHF:%s, is considered as exit camper: %s", oldArea.Index, (oldArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", newArea.Index, (newArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", (g_PlayerIsExitCamping[client]) ? "true" : "false" ); - #endif -}*/ - -// ========================================================== -// GHOST MODE FUNCTIONS -// ========================================================== - -static bool g_PlayerInGhostMode[MAXTF2PLAYERS] = { false, ... }; -static int g_PlayerGhostModeTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerGhostModeBossTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -Handle g_PlayerGhostModeConnectionCheckTimer[MAXTF2PLAYERS] = { null, ... }; -float g_PlayerGhostModeConnectionTimeOutTime[MAXTF2PLAYERS] = { -1.0, ... }; -float g_PlayerGhostModeConnectionBootTime[MAXTF2PLAYERS] = { -1.0, ... }; - -/** - * Enables/Disables ghost mode on the player. - */ -void ClientSetGhostModeState(int client, bool state) -{ - if (state == g_PlayerInGhostMode[client]) - { - return; - } - - Handle message = StartMessageAll("PlayerTauntSoundLoopEnd", USERMSG_RELIABLE); - BfWriteByte(message, client); - delete message; - EndMessage(); - - if (state && !IsClientInGame(client)) - { - return; - } - - g_PlayerInGhostMode[client] = state; - g_PlayerGhostModeTarget[client] = INVALID_ENT_REFERENCE; - g_PlayerGhostModeBossTarget[client] = INVALID_ENT_REFERENCE; - - if (state) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Entered ghost mode."); - #endif - //Strip the always edict flag - SetEntityFlags(client,GetEntityFlags(client)^FL_EDICT_ALWAYS); - //Remove the fire cond - TF2_RemoveCondition(client,TFCond_OnFire); - //Call the spawn event. - TF2_RespawnPlayer(client); - TF2_RemoveCondition(client,TFCond_Taunting); - - TFClassType desiredClass = TF2_GetPlayerClass(client); - if (desiredClass == TFClass_Unknown) - { - desiredClass = TFClass_Spy; - } - - //Set player's class to spy, this replaces old ghost mode mechanics. - TF2_SetPlayerClass(client, TFClass_Spy); - TF2_RegeneratePlayer(client); - - //Set player's old class as desired class. - SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", desiredClass); - - ClientHandleGhostMode(client, true); - if (g_GhostModeConnectionCheckConVar.BoolValue) - { - g_PlayerGhostModeConnectionCheckTimer[client] = CreateTimer(0.0, Timer_GhostModeConnectionCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; - g_PlayerGhostModeConnectionBootTime[client] = -1.0; - } - - PvP_OnClientGhostModeEnable(client); - } - else - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Exited ghost mode."); - #endif - TF2Attrib_SetByName(client, "mod see enemy health", 0.0); - g_PlayerGhostModeConnectionCheckTimer[client] = null; - g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; - g_PlayerGhostModeConnectionBootTime[client] = -1.0; - - if (IsClientInGame(client)) - { - SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_YES); - TF2_RemoveCondition(client, TFCond_Stealthed); - SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 1); - SetEntityGravity(client, 1.0); - SetEntProp(client, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_PLAYER); - SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); - SetEntityFlags(client, GetEntityFlags(client) &~ FL_NOTARGET); - SetEntProp(client, Prop_Data, "m_usSolidFlags", 16); - SetEntProp(client, Prop_Send, "m_nSolidType", 2); - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - SetEntityRenderMode(client, RENDER_NORMAL); - SetEntityRenderColor(client, _, _, _, 255); - } - } - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - if (!NPCGetRainbowOutlineState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - SlenderAddGlow(npcIndex, view_as({0, 0, 0, 0})); - } - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } - } - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } - } -} - -/** - * Makes sure that the player is a ghost when ghost mode is activated. - */ -void ClientHandleGhostMode(int client, bool forceSpawn=false) -{ - if (!IsClientInGhostMode(client)) - { - return; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientHandleGhostMode(%d, %d)", client, forceSpawn); - } - #endif - - if (!TF2_IsPlayerInCondition(client, TFCond_Stealthed) || forceSpawn) - { - TF2_StripWearables(client); - DestroyAllActiveWeapons(client); - TF2_DestroySpyWeapons(); - SetEntityGravity(client, 0.5); - TF2_AddCondition(client, TFCond_Stealthed, -1.0); - SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 0); - SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_NO); - SetEntProp(client, Prop_Send, "m_usSolidFlags", 4); - SetEntProp(client, Prop_Data, "m_nSolidType", 0); - SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); - SetEntProp(client, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); - SetEntityFlags(client, GetEntityFlags(client) | FL_NOTARGET); - SetEntityRenderMode(client, RENDER_TRANSCOLOR); - SetEntityRenderColor(client, _, _, _, 0); - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - - // Set first observer target. - ClientGhostModeNextTarget(client, true); - ClientActivateUltravision(client); - - // screen overlay timer - g_PlayerOverlayCheck[client] = CreateTimer(0.0, Timer_PlayerOverlayCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerOverlayCheck[client], true); - - CreateTimer(0.2, Timer_ClientGhostStripWearables, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientHandleGhostMode(%d, %d)", client, forceSpawn); - } - #endif -} - -static Action Timer_ClientGhostStripWearables(Handle timer, int userid) -{ - int client = GetClientOfUserId(userid); - if (!IsValidClient(client)) + if (!g_Enabled) { return Plugin_Stop; } - if (!IsClientInGhostMode(client)) - { - return Plugin_Stop; - } - TF2_StripWearables(client); - DestroyAllActiveWeapons(client); - TF2_DestroySpyWeapons(); - return Plugin_Stop; -} - -void ClientGhostModeNextTarget(int client, bool ignoreSetting = false) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientGhostModeNextTarget(%d)", client); - } - #endif - - if (g_PlayerPreferences[client].PlayerPreference_GhostModeTeleportState == 0 || ignoreSetting) - { - int lastTarget = EntRefToEntIndex(g_PlayerGhostModeTarget[client]); - int nextTarget = -1; - int firstTarget = -1; - for (int i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && (!g_PlayerEliminated[i] || g_PlayerProxy[i]) && !IsClientInGhostMode(i) && !DidClientEscape(i) && IsPlayerAlive(i)) - { - if (firstTarget == -1) - { - firstTarget = i; - } - if (i > lastTarget) - { - nextTarget = i; - break; - } - } - } - - int target = -1; - if (IsValidClient(nextTarget)) - { - target = nextTarget; - } - else - { - target = firstTarget; - } - if (IsValidClient(target)) - { - g_PlayerGhostModeTarget[client] = EntIndexToEntRef(target); - - float pos[3], ang[3], velocity[3]; - GetClientAbsOrigin(target, pos); - GetClientEyeAngles(target, ang); - GetEntPropVector(target, Prop_Data, "m_vecAbsVelocity", velocity); - TeleportEntity(client, pos, ang, velocity); - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientGhostModeNextTarget(%d)", client); - } - #endif - } - else + int client = GetClientOfUserId(userid); + + if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) { - int lastTarget = NPCGetFromEntIndex(EntRefToEntIndex(g_PlayerGhostModeBossTarget[client])); - int nextTarget = -1; - int firstTarget = -1; - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1 || !IsValidEntity(NPCGetEntIndex(bossIndex))) - { - continue; - } + return Plugin_Stop; + } - if (firstTarget == -1) - { - firstTarget = bossIndex; - } - if (bossIndex > lastTarget) - { - nextTarget = bossIndex; - break; - } - } + // Regenerate player but keep health the same. + int health = GetEntProp(client, Prop_Send, "m_iHealth"); + TF2_RegeneratePlayer(client); + SetEntProp(client, Prop_Data, "m_iHealth", health); + SetEntProp(client, Prop_Send, "m_iHealth", health); - int target = -1; - if (nextTarget != - 1 && NPCGetEntIndex(nextTarget) && NPCGetEntIndex(nextTarget) != INVALID_ENT_REFERENCE) - { - target = nextTarget; - } - else - { - target = firstTarget; - } + return Plugin_Stop; +} - if (target != -1 && IsValidEntity(NPCGetEntIndex(target))) - { - g_PlayerGhostModeBossTarget[client] = EntIndexToEntRef(NPCGetEntIndex(target)); +void ClientResetJumpScare(int client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetJumpScare(%d)", client); + } + #endif - float pos[3], ang[3], velocity[3]; - GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsOrigin", pos); - GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_angAbsRotation", ang); - GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsVelocity", velocity); - TeleportEntity(client, pos, ang, velocity); - } + g_PlayerJumpScareBoss[client] = -1; + g_PlayerJumpScareLifeTime[client] = -1.0; - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientGhostModeNextTarget(%d)", client); - } - #endif + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetJumpScare(%d)", client); } + #endif } -bool IsClientInGhostMode(int client) +void ClientDoJumpScare(int client, int bossIndex, float lifeTime) { - return g_PlayerInGhostMode[client]; -} + g_PlayerJumpScareBoss[client] = NPCGetUniqueID(bossIndex); + g_PlayerJumpScareLifeTime[client] = GetGameTime() + lifeTime; -Action Hook_GhostNoTouch(int entity, int other) -{ - if (0 < other <= MaxClients && IsClientInGame(other)) + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + char buffer[PLATFORM_MAX_PATH]; + GetBossProfileJumpscareSound(profile, buffer, sizeof(buffer)); + + if (buffer[0] != '\0') { - if (IsClientInGhostMode(other)) - { - return Plugin_Handled; - } + EmitSoundToClient(client, buffer, _, MUSIC_CHAN); } - return Plugin_Continue; } // ========================================================== // SCARE FUNCTIONS // ========================================================== -void ClientPerformScare(int client,int bossIndex) +void ClientPerformScare(int client, int bossIndex) { if (NPCGetUniqueID(bossIndex) == -1) { @@ -3124,10 +1635,7 @@ void ClientPerformScare(int client,int bossIndex) { soundInfo.EmitSound(true, client); - if (NPCGetFlags(bossIndex) & SFF_HASSIGHTSOUNDS) - { - g_PlayerSightSoundNextTime[client][bossIndex] = GetGameTime() + GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); - } + g_PlayerSightSoundNextTime[client][bossIndex] = GetGameTime() + GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); if (g_PlayerStressAmount[client] > 0.4) { @@ -3156,7 +1664,7 @@ void ClientPerformScare(int client,int bossIndex) Call_Finish(); } -static void ClientPerformSightSound(int client,int bossIndex) +static void ClientPerformSightSound(int client, int bossIndex) { if (NPCGetUniqueID(bossIndex) == -1) { @@ -3164,11 +1672,6 @@ static void ClientPerformSightSound(int client,int bossIndex) return; } - if (!(NPCGetFlags(bossIndex) & SFF_HASSIGHTSOUNDS)) - { - return; - } - int master = NPCGetFromUniqueID(g_SlenderCopyMaster[bossIndex]); if (master == -1) { @@ -3223,267 +1726,6 @@ void ClientResetScare(int client) #endif } -// ========================================================== -// ANTI-CAMPING FUNCTIONS -// ========================================================== - -void ClientResetCampingStats(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetCampingStats(%d)", client); - } - #endif - - g_PlayerCampingStrikes[client] = 0; - g_PlayerIsExitCamping[client] = false; - g_PlayerCampingTimer[client] = null; - g_IsPlayerCampingFirstTime[client] = true; - g_PlayerCampingLastPosition[client][0] = 0.0; - g_PlayerCampingLastPosition[client][1] = 0.0; - g_PlayerCampingLastPosition[client][2] = 0.0; - g_ClientAllowedTimeNearEscape[client] = g_ExitCampingTimeAllowedConVar.FloatValue; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetCampingStats(%d)", client); - } - #endif -} - -void ClientStartCampingTimer(int client) -{ - g_PlayerCampingTimer[client] = CreateTimer(5.0, Timer_ClientCheckCamp, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} - -// ========================================================== -// BLINK FUNCTIONS -// ========================================================== - -bool IsClientBlinking(int client) -{ - return g_PlayerBlink[client]; -} - -float ClientGetBlinkMeter(int client) -{ - return g_PlayerBlinkMeter[client]; -} - -void ClientSetBlinkMeter(int client, float amount) -{ - g_PlayerBlinkMeter[client] = amount; -} - -int ClientGetBlinkCount(int client) -{ - return g_PlayerBlinkCount[client]; -} - -/** - * Resets all data on blinking. - */ -void ClientResetBlink(int client) -{ - g_PlayerBlinkTimer[client] = null; - g_PlayerBlink[client] = false; - g_PlayerHoldingBlink[client] = false; - g_TimeUntilUnblink[client] = 0.0; - g_PlayerBlinkMeter[client] = 1.0; - g_PlayerBlinkCount[client] = 0; -} - -/** - * Sets the player into a blinking state and blinds the player - */ -void ClientBlink(int client) -{ - if (IsRoundInWarmup() || DidClientEscape(client)) - { - return; - } - - if (IsClientBlinking(client)) - { - return; - } - - if (SF_IsRaidMap() || SF_IsBoxingMap()) - { - return; - } - - g_PlayerBlink[client] = true; - g_PlayerBlinkCount[client]++; - g_PlayerBlinkMeter[client] = 0.0; - g_PlayerBlinkTimer[client] = CreateTimer(0.0, Timer_TryUnblink, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - g_TimeUntilUnblink[client] = GetGameTime() + g_PlayerBlinkHoldTimeConVar.FloatValue; - - UTIL_ScreenFade(client, 100, RoundToFloor(g_PlayerBlinkHoldTimeConVar.FloatValue * 1000.0), FFADE_IN, 0, 0, 0, 255); - - Call_StartForward(g_OnClientBlinkFwd); - Call_PushCell(client); - Call_Finish(); -} - -/** - * Unsets the player from the blinking state. - */ -static void ClientUnblink(int client) -{ - if (!IsClientBlinking(client)) - { - return; - } - - g_PlayerBlink[client] = false; - g_PlayerBlinkTimer[client] = null; - g_PlayerBlinkMeter[client] = 1.0; -} - -void ClientStartDrainingBlinkMeter(int client) -{ - g_PlayerBlinkTimer[client] = CreateTimer(ClientGetBlinkRate(client), Timer_BlinkTimer, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} - -static Action Timer_BlinkTimer(Handle timer, any userid) -{ - if (IsRoundInWarmup()) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerBlinkTimer[client]) - { - return Plugin_Stop; - } - - if (IsPlayerAlive(client) && !IsClientInDeathCam(client) && !g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !IsRoundEnding()) - { - int override = g_PlayerInfiniteBlinkOverrideConVar.IntValue; - if ((!g_RoundInfiniteBlink && override != 1) || override == 0) - { - g_PlayerBlinkMeter[client] -= 0.05; - } - - if (g_PlayerBlinkMeter[client] <= 0.0) - { - ClientBlink(client); - return Plugin_Stop; - } - } - - return Plugin_Continue; -} - -static Action Timer_TryUnblink(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0 || timer != g_PlayerBlinkTimer[client] || !g_PlayerBlink[client]) - { - return Plugin_Stop; - } - - if (g_PlayerHoldingBlink[client]) - { - // Some maps use the env_fade entity, so don't use FFADE_PURGE. - // Instead, we resort to spamming fade messages to the client to keep - // them blind. - UTIL_ScreenFade(client, 100, 150, FFADE_IN, 0, 0, 0, 255); - return Plugin_Continue; - } - - if (GetGameTime() < g_TimeUntilUnblink[client]) - { - return Plugin_Continue; - } - - ClientUnblink(client); - ClientStartDrainingBlinkMeter(client); - - return Plugin_Stop; -} - -static float ClientGetBlinkRate(int client) -{ - float value = g_PlayerBlinkRateConVar.FloatValue; - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (GetEntProp(client, Prop_Send, "m_nWaterLevel") >= 3) - { - // Being underwater makes you blink faster, obviously. - value *= 0.75; - } - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (g_PlayerSeesSlender[client][i]) - { - value *= NPCGetBlinkLookRate(i); - } - - else if (g_PlayerStaticMode[client][i] == Static_Increase) - { - value *= NPCGetBlinkStaticRate(i); - } - } - - if (!IsClassConfigsValid()) - { - if (class == TFClass_Sniper) - { - value *= 2.0; - } - } - else - { - value *= g_ClassBlinkRateMultiplier[classToInt]; - } - - if (IsClientUsingFlashlight(client)) - { - float startPos[3], endPos[3], direction[3]; - float length = SF2_FLASHLIGHT_LENGTH; - GetClientEyePosition(client, startPos); - GetClientEyePosition(client, endPos); - GetClientEyeAngles(client, direction); - GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(direction, direction); - ScaleVector(direction, length); - AddVectors(endPos, direction, endPos); - Handle trace = TR_TraceRayFilterEx(startPos, endPos, MASK_VISIBLE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, client); - TR_GetEndPosition(endPos, trace); - bool hit = TR_DidHit(trace); - delete trace; - - if (hit) - { - float percent = ((GetVectorSquareMagnitude(startPos, endPos) / length)); - percent *= 3.5; - if (percent > 1.0) - { - percent = 1.0; - } - value *= percent; - } - } - - return value; -} - // ========================================================== // SCREEN OVERLAY FUNCTIONS // ========================================================== @@ -3516,7 +1758,7 @@ void ClientResetOverlay(int client) g_PlayerOverlayCheck[client] = null; - if (IsClientInGame(client)) + if (IsValidClient(client)) { ClientCommand(client, "r_screenoverlay \"\""); } @@ -3594,16 +1836,16 @@ Action Timer_PlayerOverlayCheck(Handle timer, any userid) // MISC FUNCTIONS // ========================================================== -void ClientUpdateListeningFlags(int client, bool reset=false) +void ClientUpdateListeningFlags(int client, bool reset = false) { - if (!IsClientInGame(client)) + if (!IsValidClient(client)) { return; } for (int i = 1; i <= MaxClients; i++) { - if (i == client || !IsClientInGame(i) || IsClientSourceTV(i)) + if (i == client || !IsValidClient(i) || IsClientSourceTV(i)) { continue; } @@ -3773,7 +2015,6 @@ void ClientResetSlenderStats(int client) g_PlayerSeesSlender[client][i] = false; g_PlayerSeesSlenderLastTime[client][i] = -1.0; g_PlayerSightSoundNextTime[client][i] = -1.0; - g_PlayerScaredByBoss[client][i] = false; } #if defined DEBUG @@ -3804,7 +2045,8 @@ void ClientSaveCookies(int client) // Save and reset our queue points. char s[512]; - FormatEx(s, sizeof(s), "%d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d", g_PlayerQueuePoints[client], + FormatEx(s, sizeof(s), "%d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d", + g_PlayerQueuePoints[client], g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn, g_PlayerPreferences[client].PlayerPreference_ShowHints, g_PlayerPreferences[client].PlayerPreference_MuteMode, @@ -3817,7 +2059,8 @@ void ClientSaveCookies(int client) g_PlayerPreferences[client].PlayerPreference_GhostModeToggleState, g_PlayerPreferences[client].PlayerPreference_GroupOutline, g_PlayerPreferences[client].PlayerPreference_GhostModeTeleportState, - g_PlayerPreferences[client].PlayerPreference_LegacyHud); + g_PlayerPreferences[client].PlayerPreference_LegacyHud, + RoundToNearest(g_PlayerPreferences[client].PlayerPreference_MusicVolume * 100)); SetClientCookie(client, g_Cookie, s); } @@ -3853,72 +2096,18 @@ void ClientViewPunch(int client, const float angleOffset[3]) SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, offset, true); } -static Action Hook_ConstantGlowSetTransmit(int ent, int other) +void ClientSetFOV(int client, int fov) { if (!g_Enabled) { - return Plugin_Continue; - } - - int owner = GetEntPropEnt(ent, Prop_Send, "m_hTarget"); - if (owner == other) - { - return Plugin_Handled; - } - if (!IsValidClient(other)) - { - return Plugin_Handled; - } - if (!IsPlayerAlive(other)) - { - return Plugin_Handled; - } - if (g_PlayerProxy[other]) - { - if (!IsValidClient(owner)) - { - return Plugin_Handled; - } - if (g_PlayerProxy[owner] || TF2_IsPlayerInCondition(owner, TFCond_Taunting)) - { - return Plugin_Continue; - } - - float velocity[3], moveSpeed; - GetEntPropVector(other, Prop_Data, "m_vecAbsVelocity", velocity); - moveSpeed = GetVectorLength(velocity, true); - - if (moveSpeed <= SquareFloat(150.0)) - { - float ownerPos[3], proxyPos[3]; - GetClientEyePosition(owner, ownerPos); - GetClientEyePosition(other, proxyPos); - - float distance = SquareFloat(500.0); - if (GetVectorSquareMagnitude(ownerPos, proxyPos) <= distance) - { - return Plugin_Continue; - } - } - } - if (IsClientInGhostMode(other)) - { - return Plugin_Continue; - } - if ((SF_SpecialRound(SPECIALROUND_WALLHAX) || g_EnableWallHaxConVar.BoolValue || g_RenevantWallHax) && ((GetClientTeam(other) == TFTeam_Red && !DidClientEscape(other) && !g_PlayerEliminated[other]) || (g_PlayerProxy[other]))) - { - return Plugin_Continue; + return; } - return Plugin_Handled; -} -void ClientSetFOV(int client,int fov) -{ SetEntData(client, g_PlayerFOVOffset, fov); SetEntData(client, g_PlayerDefaultFOVOffset, fov); } -void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) +void TF2_GetClassName(TFClassType class, char[] buffer, int bufferLen, bool alt = false) { switch (class) { @@ -3940,7 +2129,7 @@ void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) } case TFClass_Heavy: { - strcopy(buffer, bufferLen, "heavyweapons"); + strcopy(buffer, bufferLen, !alt ? "heavyweapons" : "heavy"); } case TFClass_Medic: { @@ -3965,15 +2154,15 @@ void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) } } -bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV=true, bool checkBlink=false) +bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true, bool ignoreFog = false) { for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } - if (IsPointVisibleToPlayer(i, pos, checkFOV, checkBlink)) + if (IsPointVisibleToPlayer(i, pos, checkFOV, checkBlink, checkEliminated, ignoreFog)) { return true; } @@ -3982,7 +2171,7 @@ bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV=true, bool checkB return false; } -bool IsPointVisibleToPlayer(int client, const float pos[3], bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true, bool ignoreFog = false) +bool IsPointVisibleToPlayer(int client, const float pos[3], bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true, bool ignoreFog = false) { if (!IsValidClient(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) { @@ -3994,7 +2183,7 @@ bool IsPointVisibleToPlayer(int client, const float pos[3], bool checkFOV=true, return false; } - if (checkBlink && IsClientBlinking(client)) + if (checkBlink && IsClientBlinking(client) && !g_PlayerEliminated[client]) { return false; } @@ -4056,7 +2245,7 @@ Action Timer_RespawnPlayer(Handle timer, any userid) return Plugin_Stop; } - if (!IsValidClient(client) || !IsClientInGame(client) || IsPlayerAlive(client)) + if (!IsValidClient(client) || IsPlayerAlive(client)) { return Plugin_Stop; } @@ -4080,10 +2269,3 @@ Action Timer_RespawnPlayer(Handle timer, any userid) return Plugin_Stop; } - -#include "sf2/functionclients/client_hints.sp" -#include "sf2/functionclients/clients_think.sp" -#include "sf2/functionclients/client_flashlight.sp" -#include "sf2/functionclients/client_music.sp" -#include "sf2/functionclients/client_proxy_functions.sp" -#include "sf2/methodmaps.sp" diff --git a/addons/sourcemod/scripting/sf2/client/blink.sp b/addons/sourcemod/scripting/sf2/client/blink.sp new file mode 100644 index 00000000..e8e9935b --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/blink.sp @@ -0,0 +1,318 @@ +#pragma semicolon 1 + +// Blink data. +static Handle g_PlayerBlinkTimer[MAXTF2PLAYERS] = { null, ... }; +static bool g_StartBlinking[MAXTF2PLAYERS] = { false, ... }; +static float g_TimeUntilStopBlinking[MAXTF2PLAYERS] = { -1.0, ... }; +static bool g_PlayerBlink[MAXTF2PLAYERS] = { false, ... }; +bool g_PlayerHoldingBlink[MAXTF2PLAYERS] = { false, ... }; +static float g_PlayerBlinkMeter[MAXTF2PLAYERS] = { 0.0, ... }; +static float g_TimeUntilUnblink[MAXTF2PLAYERS] = { 0.0, ... }; +static int g_PlayerBlinkCount[MAXTF2PLAYERS] = { 0, ... }; + +void SetupBlink() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerLookAtBossPFwd.AddFunction(null, OnPlayerLookAtBoss); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + ClientResetBlink(client.index); + + if (!client.IsSourceTV) + { + SDKHook(client.index, SDKHook_PreThinkPost, BlinkThink); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetBlink(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetBlink(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetBlink(client.index); +} + +static void OnPlayerLookAtBoss(SF2_BasePlayer client, SF2NPC_BaseNPC boss) +{ + if (boss.IsAffectedBySight()) + { + g_StartBlinking[client.index] = true; + g_TimeUntilStopBlinking[client.index] = GetGameTime() + 25.0; + } +} + +bool IsClientBlinking(int client) +{ + return g_PlayerBlink[client]; +} + +bool IsClientHoldingBlink(int client) +{ + return g_PlayerHoldingBlink[client]; +} + +void ClientSetHoldingBlink(int client, bool value) +{ + g_PlayerHoldingBlink[client] = value; +} + +float ClientGetBlinkMeter(int client) +{ + return g_PlayerBlinkMeter[client]; +} + +void ClientSetBlinkMeter(int client, float amount) +{ + g_PlayerBlinkMeter[client] = amount; +} + +int ClientGetBlinkCount(int client) +{ + return g_PlayerBlinkCount[client]; +} + +bool ClientHasStartedBlinking(int client) +{ + return g_StartBlinking[client]; +} + +/** + * Resets all data on blinking. + */ +void ClientResetBlink(int client) +{ + g_PlayerBlinkTimer[client] = null; + g_StartBlinking[client] = false; + g_TimeUntilStopBlinking[client] = -1.0; + g_PlayerBlink[client] = false; + g_PlayerHoldingBlink[client] = false; + g_TimeUntilUnblink[client] = 0.0; + g_PlayerBlinkMeter[client] = 1.0; + g_PlayerBlinkCount[client] = 0; +} + +/** + * Sets the player into a blinking state and blinds the player + */ +void ClientBlink(int client) +{ + if (IsRoundInWarmup() || DidClientEscape(client)) + { + return; + } + + if (IsClientBlinking(client)) + { + return; + } + + if (SF_IsRaidMap() || SF_IsBoxingMap()) + { + return; + } + + g_PlayerBlink[client] = true; + g_PlayerBlinkCount[client]++; + g_PlayerBlinkMeter[client] = 0.0; + g_PlayerBlinkTimer[client] = CreateTimer(0.0, Timer_TryUnblink, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_TimeUntilUnblink[client] = GetGameTime() + g_PlayerBlinkHoldTimeConVar.FloatValue; + + UTIL_ScreenFade(client, 100, RoundToFloor(g_PlayerBlinkHoldTimeConVar.FloatValue * 1000.0), FFADE_IN, 0, 0, 0, 255); + + Call_StartForward(g_OnClientBlinkFwd); + Call_PushCell(client); + Call_Finish(); +} + +/** + * Unsets the player from the blinking state. + */ +static void ClientUnblink(int client) +{ + if (!IsClientBlinking(client)) + { + return; + } + + g_PlayerBlink[client] = false; + g_PlayerBlinkTimer[client] = null; + g_PlayerBlinkMeter[client] = 1.0; +} + +static void BlinkThink(int client) +{ + if (IsRoundInWarmup() || IsRoundInIntro()) + { + return; + } + + if (IsInfiniteBlinkEnabled()) + { + return; + } + + if (!g_StartBlinking[client]) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(bossIndex); + if (!controller.IsValid()) + { + continue; + } + + if (controller.IsAffectedBySight() && player.IsLookingAtBoss(controller)) + { + g_TimeUntilStopBlinking[player.index] = GetGameTime() + 25.0; + } + } + + if (g_TimeUntilStopBlinking[player.index] < GetGameTime()) + { + g_StartBlinking[player.index] = false; + g_TimeUntilStopBlinking[player.index] = -1.0; + g_PlayerBlinkMeter[player.index] = 1.0; + } + + if (player.IsAlive && !player.IsInDeathCam && !player.IsEliminated && !player.IsInGhostMode && !IsRoundEnding()) + { + int override = g_PlayerInfiniteBlinkOverrideConVar.IntValue; + if ((!g_RoundInfiniteBlink && override != 1) || override == 0) + { + g_PlayerBlinkMeter[player.index] -= (0.15 * ClientGetBlinkRate(player.index) * GetGameFrameTime()); + } + + if (g_PlayerBlinkMeter[player.index] <= 0.0) + { + player.Blink(); + } + } +} + +static Action Timer_TryUnblink(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0 || timer != g_PlayerBlinkTimer[client] || !g_PlayerBlink[client]) + { + return Plugin_Stop; + } + + if (g_PlayerHoldingBlink[client]) + { + // Some maps use the env_fade entity, so don't use FFADE_PURGE. + // Instead, we resort to spamming fade messages to the client to keep + // them blind. + UTIL_ScreenFade(client, 100, 150, FFADE_IN, 0, 0, 0, 255); + return Plugin_Continue; + } + + if (GetGameTime() < g_TimeUntilUnblink[client]) + { + return Plugin_Continue; + } + + ClientUnblink(client); + + return Plugin_Stop; +} + +static float ClientGetBlinkRate(int client) +{ + float value = g_PlayerBlinkRateConVar.FloatValue; + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + if (GetEntProp(client, Prop_Send, "m_nWaterLevel") >= 3) + { + // Being underwater makes you blink faster, obviously. + value *= 0.75; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if (g_PlayerSeesSlender[client][i]) + { + value *= NPCGetBlinkLookRate(i); + } + + else if (g_PlayerStaticMode[client][i] == Static_Increase) + { + value *= NPCGetBlinkStaticRate(i); + } + } + + if (!IsClassConfigsValid()) + { + if (class == TFClass_Sniper) + { + value *= 2.0; + } + } + else + { + value *= g_ClassBlinkRateMultiplier[classToInt]; + } + + if (IsClientUsingFlashlight(client)) + { + float startPos[3], endPos[3], direction[3]; + float length = SF2_FLASHLIGHT_LENGTH; + GetClientEyePosition(client, startPos); + GetClientEyePosition(client, endPos); + GetClientEyeAngles(client, direction); + GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(direction, direction); + ScaleVector(direction, length); + AddVectors(endPos, direction, endPos); + Handle trace = TR_TraceRayFilterEx(startPos, endPos, MASK_VISIBLE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, client); + TR_GetEndPosition(endPos, trace); + bool hit = TR_DidHit(trace); + delete trace; + + if (hit) + { + float percent = ((GetVectorSquareMagnitude(startPos, endPos) / length)); + percent *= 3.5; + if (percent > 1.0) + { + percent = 1.0; + } + value *= percent; + } + } + + return value; +} diff --git a/addons/sourcemod/scripting/sf2/client/breathing.sp b/addons/sourcemod/scripting/sf2/client/breathing.sp new file mode 100644 index 00000000..7e841929 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/breathing.sp @@ -0,0 +1,148 @@ +#pragma semicolon 1 + +#define SF2_PLAYER_BREATH_COOLDOWN_MIN 0.8 +#define SF2_PLAYER_BREATH_COOLDOWN_MAX 2.0 + +char g_PlayerBreathSounds[][] = +{ + "slender/fastbreath1.wav" +}; + +static bool g_PlayerBreath[MAXTF2PLAYERS] = { false, ... }; +static Handle g_PlayerBreathTimer[MAXTF2PLAYERS] = { null, ... }; + +void SetupBreathing() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + ClientResetBreathing(client.index); + + SDKHook(client.index, SDKHook_PreThink, Hook_BreathingThink); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetBreathing(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetBreathing(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetBreathing(client.index); +} + +static void ClientResetBreathing(int client) +{ + g_PlayerBreath[client] = false; + g_PlayerBreathTimer[client] = null; +} + +static float ClientCalculateBreathingCooldown(int client) +{ + float average = 0.0; + int averageNum = 0; + + // Sprinting only, for now. + average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow(SF2_BasePlayer(client).Stamina, 1.65)); + averageNum++; + + average /= float(averageNum); + + if (average < SF2_PLAYER_BREATH_COOLDOWN_MIN) + { + average = SF2_PLAYER_BREATH_COOLDOWN_MIN; + } + + return average; +} + +static void ClientStartBreathing(int client) +{ + g_PlayerBreath[client] = true; + g_PlayerBreathTimer[client] = CreateTimer(ClientCalculateBreathingCooldown(client), Timer_ClientBreath, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); +} + +static void ClientStopBreathing(int client) +{ + g_PlayerBreath[client] = false; + g_PlayerBreathTimer[client] = null; +} + +static bool ClientCanBreath(int client) +{ + return ClientCalculateBreathingCooldown(client) < SF2_PLAYER_BREATH_COOLDOWN_MAX; +} + +static Action Timer_ClientBreath(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerBreathTimer[client]) + { + return Plugin_Stop; + } + + if (!g_PlayerBreath[client]) + { + return Plugin_Stop; + } + + if (ClientCanBreath(client)) + { + EmitSoundToAll(g_PlayerBreathSounds[GetRandomInt(0, sizeof(g_PlayerBreathSounds) - 1)], client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + + ClientStartBreathing(client); + return Plugin_Stop; + } + + ClientStopBreathing(client); + + return Plugin_Stop; +} + +static void Hook_BreathingThink(int client) +{ + if (!g_Enabled) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + RoundState roundState = GameRules_GetRoundState(); + if (player.IsEliminated || roundState != RoundState_RoundRunning || + IsRoundEnding() || IsRoundInWarmup() || player.HasEscaped) + { + return; + } + + if (ClientCanBreath(player.index) && !g_PlayerBreath[player.index]) + { + ClientStartBreathing(player.index); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp new file mode 100644 index 00000000..7d933138 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -0,0 +1,575 @@ +#pragma semicolon 1 + +// Deathcam data. +int g_PlayerDeathCamBoss[MAXTF2PLAYERS] = { -1, ... }; +static bool g_PlayerDeathCam[MAXTF2PLAYERS] = { false, ... }; +static float g_PlayerDeathCamTimer[MAXTF2PLAYERS]; +static bool g_PlayerDeathCamMustDoOverlay[MAXTF2PLAYERS]; +bool g_PlayerDeathCamShowOverlay[MAXTF2PLAYERS] = { false, ... }; +int g_PlayerDeathCamEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerDeathCamEnt2[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerDeathCamTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +bool g_CameraInDeathCamAdvanced[2049] = { false, ... }; +float g_CameraPlayerOffsetBackward[2049] = { 0.0, ... }; +float g_CameraPlayerOffsetDownward[2049] = { 0.0, ... }; +static float g_PlayerOriginalDeathcamPosition[MAXTF2PLAYERS][3]; +static bool g_Hooked = false; + +void SetupDeathCams() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetDeathCam(client.index); + + SDKHook(client.index, SDKHook_PreThink, Hook_DeathCamThink); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetDeathCam(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetDeathCam(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetDeathCam(client.index); +} + +bool IsClientInDeathCam(int client) +{ + return g_PlayerDeathCam[client]; +} + +static Action Hook_DeathCamSetTransmit(int slender,int other) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (EntRefToEntIndex(g_PlayerDeathCamEnt2[other]) != slender) + { + return Plugin_Handled; + } + return Plugin_Continue; +} + +static void ClientResetDeathCam(int client) +{ + if (!IsClientInDeathCam(client)) + { + return; // no really need to reset if it wasn't set. + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetDeathCam(%d)", client); + } + #endif + + int deathCamBoss = NPCGetFromUniqueID(g_PlayerDeathCamBoss[client]); + + g_PlayerDeathCamBoss[client] = -1; + g_PlayerDeathCam[client] = false; + g_PlayerDeathCamShowOverlay[client] = false; + g_PlayerDeathCamTimer[client] = 0.0; + + int ent = EntRefToEntIndex(g_PlayerDeathCamEnt[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + g_CameraInDeathCamAdvanced[ent] = false; + AcceptEntityInput(ent, "Disable"); + RemoveEntity(ent); + } + + ent = EntRefToEntIndex(g_PlayerDeathCamEnt2[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + if (!SF2_BaseBoss(ent).IsValid() && !SF2_ChaserEntity(ent).IsValid() && !SF2_StatueEntity(ent).IsValid()) + { + RemoveEntity(ent); + } + } + + ent = EntRefToEntIndex(g_PlayerDeathCamTarget[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + RemoveEntity(ent); + } + + g_PlayerDeathCamEnt[client] = INVALID_ENT_REFERENCE; + g_PlayerDeathCamEnt2[client] = INVALID_ENT_REFERENCE; + g_PlayerDeathCamTarget[client] = INVALID_ENT_REFERENCE; + + SetClientViewEntity(client, client); + + if (deathCamBoss != -1) + { + if ((NPCGetFlags(deathCamBoss) & SFF_FAKE) == 0) + { + SF2BossProfileData data; + data = NPCGetProfileData(deathCamBoss); + if (data.DeathCamData.Enabled && data.DeathCamData.Blackout) + { + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + CreateTimer(0.1, Timer_DeleteRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + TeleportEntity(client, {16000.0, 16000.0, 16000.0}); + + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.DeathCamData.ExecutionSounds; + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + for (int i = 0; i < soundInfo.Paths.Length; i++) + { + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER, _, _, _, i); + } + } + + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + } + } + + Call_StartForward(g_OnClientEndDeathCamFwd); + Call_PushCell(client); + Call_PushCell(deathCamBoss); + Call_PushCell(SF2_BaseBoss(ent).IsValid() || SF2_ChaserEntity(ent).IsValid() || SF2_StatueEntity(ent).IsValid()); + Call_Finish(); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetDeathCam(%d)", client); + } + #endif +} + +static Action NoPlayerVoiceHook(int clients[64], int& numClients, char sample[PLATFORM_MAX_PATH], int& client, int& channel, float& volume, int& level, int& pitch, int& flags) +{ + if (!IsValidClient(client)) + { + return Plugin_Continue; + } + + if (channel == SNDCHAN_VOICE) + { + RemoveNormalSoundHook(NoPlayerVoiceHook); + g_Hooked = false; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +static Action Timer_DeleteRagdoll(Handle timer, int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(GetClientOfUserId(client)); + if (!player.IsValid) + { + return Plugin_Continue; + } + + int ragdoll = -1; + while ((ragdoll = FindEntityByClassname(ragdoll, "tf_ragdoll")) != -1) + { + if (GetEntPropEnt(ragdoll, Prop_Send, "m_hPlayer") == player.index) + { + RemoveEntity(ragdoll); + break; + } + } + + return Plugin_Continue; +} + +void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false) +{ + if (IsClientInDeathCam(client)) + { + return; + } + if (!NPCIsValid(bossIndex)) + { + return; + } + + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + GetClientAbsOrigin(client, g_PlayerOriginalDeathcamPosition[client]); + + char buffer[PLATFORM_MAX_PATH]; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); + + SF2BossProfileSoundInfo soundInfo; + if (g_SlenderDeathCamScareSound[bossIndex]) + { + soundInfo = data.ScareSounds; + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER); + } + + soundInfo = data.ClientDeathCamSounds; + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER); + + if ((NPCGetFlags(bossIndex) & SFF_FAKE) == 0) + { + soundInfo = data.GlobalDeathCamSounds; + for (int i = 0; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + soundInfo.EmitSound(true, i); + } + } + + // Call our forward. + Call_StartForward(g_OnClientCaughtByBossFwd); + Call_PushCell(client); + Call_PushCell(bossIndex); + Call_Finish(); + + bool publicDeathcam = data.PublicDeathCam || data.DeathCamData.Enabled; + if ((NPCGetFlags(bossIndex) & SFF_FAKE) == 0) + { + if ((!NPCHasDeathCamEnabled(bossIndex) || antiCamp || (staticDeath && publicDeathcam))) + { + SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. + + if (NPCHasAttribute(bossIndex, SF2Attribute_IgnitePlayerOnDeath)) + { + TF2_IgnitePlayer(client, client); + } + + CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); + float damage = float(GetEntProp(client, Prop_Send, "m_iHealth")) * 4.0; + SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, damage, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); + ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. + KillClient(client); + return; + } + } + else + { + if (!NPCHasDeathCamEnabled(bossIndex)) + { + SlenderMarkAsFake(bossIndex); + return; + } + } + + g_PlayerDeathCamBoss[client] = NPCGetUniqueID(bossIndex); + g_PlayerDeathCamShowOverlay[client] = false; + + float eyePos[3], eyeAng[3], angle[3]; + GetClientEyePosition(client, eyePos); + GetClientEyeAngles(client, eyeAng); + SubtractVectors(eyePos, lookPos, angle); + GetVectorAngles(angle, angle); + angle[0] = 0.0; + angle[2] = 0.0; + + // Create fake model. + int slender = -1; + if (!publicDeathcam) + { + slender = SpawnSlenderModel(bossIndex, lookPos, true); + TeleportEntity(slender, lookPos, angle, NULL_VECTOR); + SDKHook(slender, SDKHook_SetTransmit, Hook_DeathCamSetTransmit); + } + else + { + SetEntityMoveType(client, MOVETYPE_NOCLIP); + slender = NPCGetEntIndex(bossIndex); + if (SF2_ChaserEntity(slender).IsValid() || SF2_StatueEntity(slender).IsValid()) + { + SF2_BaseBoss(slender).IsKillingSomeone = true; + SF2_BaseBoss(slender).KillTarget = CBaseEntity(client); + } + } + g_PlayerDeathCamEnt2[client] = EntIndexToEntRef(slender); + + char name[64]; + FormatEx(name, sizeof(name), "sf2_boss_%d", EntIndexToEntRef(slender)); + + float offsetPos[3]; + int target = CreateEntityByName("info_target"); + if (!publicDeathcam) + { + offsetPos = data.DeathCamPos; + AddVectors(lookPos, offsetPos, offsetPos); + TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); + DispatchKeyValue(target, "targetname", name); + SetVariantString("!activator"); + AcceptEntityInput(target, "SetParent", slender); + } + else + { + char boneName[PLATFORM_MAX_PATH]; + AddVectors(lookPos, offsetPos, offsetPos); + TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); + DispatchKeyValue(target, "targetname", name); + SetVariantString("!activator"); + AcceptEntityInput(target, "SetParent", slender); + strcopy(boneName, sizeof(boneName), data.PublicDeathCamAttachment); + if (data.DeathCamData.Attachment[0] != '\0') + { + strcopy(boneName, sizeof(boneName), data.DeathCamData.Attachment); + } + if (boneName[0] != '\0') + { + SetVariantString(boneName); + AcceptEntityInput(target, "SetParentAttachment"); + } + } + g_PlayerDeathCamTarget[client] = EntIndexToEntRef(target); + + // Create the camera itself. + int camera = CreateEntityByName("point_viewcontrol"); + TeleportEntity(camera, eyePos, eyeAng, NULL_VECTOR); + DispatchKeyValue(camera, "spawnflags", "12"); + DispatchKeyValue(camera, "target", name); + DispatchSpawn(camera); + AcceptEntityInput(camera, "Enable", client); + g_PlayerDeathCamEnt[client] = EntIndexToEntRef(camera); + int light = ClientGetFlashlightEntity(client); + if (light && light != INVALID_ENT_REFERENCE) + { + SetVariantString("!activator"); + AcceptEntityInput(light, "ClearParent"); + TeleportEntity(light, eyePos, eyeAng, NULL_VECTOR); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", camera); + } + if (publicDeathcam) + { + float camSpeed, camAcceleration, camDeceleration; + + camSpeed = g_SlenderPublicDeathCamSpeed[bossIndex]; + camAcceleration = g_SlenderPublicDeathCamAcceleration[bossIndex]; + camDeceleration = g_SlenderPublicDeathCamDeceleration[bossIndex]; + FloatToString(camSpeed, buffer, sizeof(buffer)); + DispatchKeyValue(camera, "acceleration", buffer); + FloatToString(camAcceleration, buffer, sizeof(buffer)); + DispatchKeyValue(camera, "deceleration", buffer); + FloatToString(camDeceleration, buffer, sizeof(buffer)); + DispatchKeyValue(camera, "speed", buffer); + + SetVariantString("!activator"); + AcceptEntityInput(camera, "SetParent", slender); + char attachmentName[PLATFORM_MAX_PATH]; + strcopy(attachmentName, sizeof(attachmentName), data.PublicDeathCamAttachment); + if (data.DeathCamData.Attachment[0] != '\0') + { + strcopy(attachmentName, sizeof(attachmentName), data.DeathCamData.Attachment); + } + if (attachmentName[0] != '\0') + { + SetVariantString(attachmentName); + AcceptEntityInput(camera, "SetParentAttachment"); + } + + strcopy(attachmentName, sizeof(attachmentName), data.PublicDeathCamAttachmentTarget); + if (data.DeathCamData.Attachment[0] != '\0') + { + strcopy(attachmentName, sizeof(attachmentName), data.DeathCamData.TargetAttachment); + } + DispatchKeyValue(camera, "targetname", attachmentName); + + g_CameraInDeathCamAdvanced[camera] = true; + g_CameraPlayerOffsetBackward[camera] = g_SlenderPublicDeathCamBackwardOffset[bossIndex]; + g_CameraPlayerOffsetDownward[camera] = g_SlenderPublicDeathCamDownwardOffset[bossIndex]; + RequestFrame(Frame_PublicDeathCam, camera); //Resend taunt sound to eliminated players only + } + + float duration = g_SlenderDeathCamTime[bossIndex]; + if (g_SlenderDeathCamOverlay[bossIndex] && g_SlenderDeathCamOverlayTimeStart[bossIndex] >= 0.0) + { + duration = g_SlenderDeathCamOverlayTimeStart[bossIndex]; + } + if (duration <= 0.0 && publicDeathcam) + { + char animation[64]; + SF2BossProfileMasterAnimationsData animData; + animData = data.AnimationData; + float rate = 1.0, cycle = 0.0; + animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, animation, sizeof(animation), rate, duration, cycle); + CBaseAnimating animator = CBaseAnimating(slender); + int sequence = animator.LookupSequence(animation); + if (duration <= 0.0 && sequence != -1) + { + duration = animator.SequenceDuration(sequence) / rate; + duration *= (1.0 - cycle); + } + } + + g_PlayerDeathCamTimer[client] = duration; + g_PlayerDeathCamMustDoOverlay[client] = g_SlenderDeathCamOverlay[bossIndex]; + g_PlayerDeathCam[client] = true; + + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, { 0.0, 0.0, 0.0 }); + + Call_StartForward(g_OnClientStartDeathCamFwd); + Call_PushCell(client); + Call_PushCell(bossIndex); + Call_Finish(); +} + +static void Frame_PublicDeathCam(int cameraRef) +{ + int camera = EntRefToEntIndex(cameraRef); + if (!camera || camera == INVALID_ENT_REFERENCE) + { + return; + } + int slender = GetEntPropEnt(camera, Prop_Data, "m_hOwnerEntity"); + int client = GetEntPropEnt(camera, Prop_Data, "m_hPlayer"); + if (IsValidEntity(slender) && IsValidClient(client)) + { + float camPos[3], camAngs[3]; + GetEntPropVector(camera, Prop_Data, "m_angAbsRotation", camAngs); + GetEntPropVector(camera, Prop_Data, "m_vecAbsOrigin", camPos); + + camPos[0] -= g_CameraPlayerOffsetBackward[camera]; + camPos[2] -= g_CameraPlayerOffsetDownward[camera]; + + TeleportEntity(client, camPos, camAngs, NULL_VECTOR); + + RequestFrame(Frame_PublicDeathCam, EntIndexToEntRef(cameraRef)); + } +} + +static void StopDeathCam(int client) +{ + if (!IsValidClient(client)) + { + return; + } + + SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol entity changes our damage state. + + SF2NPC_BaseNPC deathCamBoss = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); + if (deathCamBoss != SF2_INVALID_NPC) + { + if ((deathCamBoss.Flags & SFF_FAKE) == 0) + { + if (deathCamBoss.HasAttribute(SF2Attribute_IgnitePlayerOnDeath)) + { + TF2_IgnitePlayer(client, client); + } + + SF2BossProfileData data; + data = deathCamBoss.GetProfileData(); + if (data.DeathCamData.Enabled && data.DeathCamData.Blackout && !g_Hooked) + { + AddNormalSoundHook(NoPlayerVoiceHook); + g_Hooked = true; + } + + Call_StartForward(g_OnClientPreKillDeathCamFwd); + Call_PushCell(client); + Call_PushCell(deathCamBoss.Index); + Call_Finish(); + + CBaseEntity boss = CBaseEntity(deathCamBoss.EntIndex); + float damage = float(GetEntProp(client, Prop_Send, "m_iHealth")) * 4.0; + SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, damage, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); + ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. + KillClient(client); + } + else + { + deathCamBoss.MarkAsFake(); + SetEntityMoveType(client, MOVETYPE_WALK); + TeleportEntity(client, g_PlayerOriginalDeathcamPosition[client], NULL_VECTOR, { 0.0, 0.0, 0.0 }); + int light = ClientGetFlashlightEntity(client); + if (light && light != INVALID_ENT_REFERENCE) + { + float eyePos[3], eyeAng[3]; + GetClientEyePosition(client, eyePos); + GetClientEyeAngles(client, eyeAng); + SetVariantString("!activator"); + AcceptEntityInput(light, "ClearParent"); + TeleportEntity(light, eyePos, eyeAng, NULL_VECTOR); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", client); + } + } + } + else // The boss is invalid? But the player got a death cam? + { + // Then kill him anyways. + KillClient(client); + ForcePlayerSuicide(client); + } + ClientResetDeathCam(client); +} + +static void Hook_DeathCamThink(int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid || !player.IsInDeathCam || player.IsInGhostMode) + { + return; + } + + CBaseEntity ent = CBaseEntity(EntRefToEntIndex(g_PlayerDeathCamEnt[client])); + if (ent.IsValid() && g_CameraInDeathCamAdvanced[ent.index]) + { + float camPos[3], camAngs[3]; + ent.GetAbsAngles(camAngs); + ent.GetAbsOrigin(camPos); + + camPos[0] -= g_CameraPlayerOffsetBackward[ent.index]; + camPos[2] -= g_CameraPlayerOffsetDownward[ent.index]; + + player.SetLocalOrigin(camPos); + player.SetLocalAngles(camAngs); + } + + g_PlayerDeathCamTimer[player.index] -= GetGameFrameTime(); + if (g_PlayerDeathCamTimer[player.index] <= 0.0) + { + if (g_PlayerDeathCamMustDoOverlay[player.index]) + { + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[player.index])); + float duration = 0.0; + if (Npc.IsValid()) + { + g_PlayerDeathCamShowOverlay[player.index] = true; + SF2BossProfileData data; + data = Npc.GetProfileData(); + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.PlayerDeathCamOverlaySounds; + soundInfo.EmitSound(true, player.index); + duration = g_SlenderDeathCamTime[Npc.Index]; + } + g_PlayerDeathCamTimer[player.index] = duration; + g_PlayerDeathCamMustDoOverlay[player.index] = false; + return; + } + else + { + StopDeathCam(player.index); + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_flashlight.sp b/addons/sourcemod/scripting/sf2/client/flashlight.sp similarity index 61% rename from addons/sourcemod/scripting/sf2/functionclients/client_flashlight.sp rename to addons/sourcemod/scripting/sf2/client/flashlight.sp index 281af18c..23b78234 100644 --- a/addons/sourcemod/scripting/sf2/functionclients/client_flashlight.sp +++ b/addons/sourcemod/scripting/sf2/client/flashlight.sp @@ -3,7 +3,13 @@ #endif #define _sf2_client_flashlight_included -#define SF2_ULTRAVISION_CONE 180.0 +#define SF2_FLASHLIGHT_WIDTH 512.0 // How wide the player's Flashlight should be in world units. +#define SF2_FLASHLIGHT_BRIGHTNESS 0 // Intensity of the players' Flashlight. +#define SF2_FLASHLIGHT_DRAIN_RATE 0.65 // How long (in seconds) each bar on the player's Flashlight meter lasts. +#define SF2_FLASHLIGHT_RECHARGE_RATE 0.68 // How long (in seconds) it takes each bar on the player's Flashlight meter to recharge. +#define SF2_FLASHLIGHT_FLICKERAT 0.25 // The percentage of the Flashlight battery where the Flashlight will start to blink. +#define SF2_FLASHLIGHT_ENABLEAT 0.3 // The percentage of the Flashlight battery where the Flashlight will be able to be used again (if the player shortens out the Flashlight from excessive use). +#define SF2_FLASHLIGHT_COOLDOWN 0.4 // How much time players have to wait before being able to switch their flashlight on again after turning it off. #pragma semicolon 1 @@ -17,6 +23,52 @@ static int g_PlayerFlashlightEntAng[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, .. static int g_ClientFlashlightStartEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; static int g_ClientFlashlightEndEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +void SetupFlashlight() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + ClientResetFlashlight(client.index); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + ClientResetFlashlight(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetFlashlight(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetFlashlight(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetFlashlight(client.index); +} + static Action Timer_DrainFlashlight(Handle timer, any userid) { int client = GetClientOfUserId(userid); @@ -94,9 +146,9 @@ float ClientGetFlashlightBatteryLife(int client) return g_PlayerFlashlightBatteryLife[client]; } -void ClientSetFlashlightBatteryLife(int client, float flPercent) +void ClientSetFlashlightBatteryLife(int client, float percent) { - g_PlayerFlashlightBatteryLife[client] = flPercent; + g_PlayerFlashlightBatteryLife[client] = percent; } /** @@ -104,22 +156,26 @@ void ClientSetFlashlightBatteryLife(int client, float flPercent) */ void ClientProcessFlashlightAngles(int client) { - if (!IsClientInGame(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client)) { return; } - if (IsPlayerAlive(client)) - { - int fl; + int fl; - if (IsClientUsingFlashlight(client)) + if (IsClientUsingFlashlight(client)) + { + fl = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); + if (fl && fl != INVALID_ENT_REFERENCE) { - fl = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); - if (fl && fl != INVALID_ENT_REFERENCE) - { - TeleportEntity(fl, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 }), NULL_VECTOR); - } + TeleportEntity(fl, NULL_VECTOR, { 0.0, 0.0, 0.0 }, NULL_VECTOR); + } + fl = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); + if (fl && fl != INVALID_ENT_REFERENCE) + { + float ang[3]; + GetClientEyeAngles(client, ang); + TeleportEntity(fl, NULL_VECTOR, ang, NULL_VECTOR); } } } @@ -129,7 +185,7 @@ void ClientProcessFlashlightAngles(int client) */ void ClientHandleFlashlightFlickerState(int client) { - if (!IsClientInGame(client) || !IsPlayerAlive(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client)) { return; } @@ -176,6 +232,11 @@ float ClientGetFlashlightNextInputTime(int client) return g_PlayerFlashlightNextInputTime[client]; } +int ClientGetFlashlightEntity(int client) +{ + return EntRefToEntIndex(g_PlayerFlashlightEnt[client]); +} + /** * Breaks the player's flashlight. Nothing else. */ @@ -200,6 +261,10 @@ void ClientBreakFlashlight(int client) Call_StartForward(g_OnClientBreakFlashlightFwd); Call_PushCell(client); Call_Finish(); + + Call_StartForward(g_OnPlayerFlashlightBreakPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } /** @@ -220,6 +285,14 @@ void ClientResetFlashlight(int client) g_PlayerFlashlightBatteryTimer[client] = null; g_PlayerFlashlightNextInputTime[client] = -1.0; + SF2PointSpotlightEntity spotlight = SF2PointSpotlightEntity(EntRefToEntIndex(g_PlayerFlashlightEntAng[client])); + if (spotlight.IsValid()) + { + spotlight.TurnOff(); + RemoveEntity(spotlight.index); + } + g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 2) { @@ -264,7 +337,7 @@ static Action Hook_FlashlightSetTransmit(int ent,int other) if (ownerEntity == other) { - if (!!(GetEntProp(ownerEntity, Prop_Send, "m_nForceTauntCam")) && !TF2_IsPlayerInCondition(ownerEntity, TFCond_Taunting)) + if ((GetEntProp(ownerEntity, Prop_Send, "m_nForceTauntCam") != 0) && !TF2_IsPlayerInCondition(ownerEntity, TFCond_Taunting)) { return Plugin_Handled; } @@ -295,7 +368,7 @@ static Action Hook_FlashlightSetTransmit(int ent,int other) */ void ClientTurnOnFlashlight(int client) { - if (!IsClientInGame(client) || !IsPlayerAlive(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client)) { return; } @@ -310,15 +383,19 @@ void ClientTurnOnFlashlight(int client) float eyePos[3], eyeAng[3]; float length = SF2_FLASHLIGHT_LENGTH; - float doubleLength = SF2_FLASHLIGHT_LENGTH*3.0; + float doubleLength = SF2_FLASHLIGHT_LENGTH * 3.0; float radius = SF2_FLASHLIGHT_WIDTH; - float doubleRadius = SF2_FLASHLIGHT_WIDTH*2.0; + float doubleRadius = SF2_FLASHLIGHT_WIDTH * 2.0; GetClientEyePosition(client, eyePos); GetClientEyeAngles(client, eyeAng); TFClassType class = TF2_GetPlayerClass(client); int classToInt = view_as(class); + // Convert WU to inches. + float cone = 55.0; + cone *= 0.75; + if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) { // If the player is using the projected flashlight, just set effect flags. @@ -424,10 +501,6 @@ void ClientTurnOnFlashlight(int client) } AcceptEntityInput(ent, "brightness"); - // Convert WU to inches. - float cone = 55.0; - cone *= 0.75; - SetVariantInt(RoundToFloor(cone)); AcceptEntityInput(ent, "_inner_cone"); SetVariantInt(RoundToFloor(cone)); @@ -445,126 +518,105 @@ void ClientTurnOnFlashlight(int client) } // Spawn the light that only everyone else will see. - /*int ent = CreateEntityByName("env_beam"); - if (ent != -1) + /*SF2PointSpotlightEntity spotlight = SF2PointSpotlightEntity(CreateEntityByName("sf2_point_spotlight")); + if (spotlight.IsValid()) { - int startEnt = CreateEntityByName("info_target"); - int endEnt = CreateEntityByName("info_target"); - if (startEnt != -1) // Start - { - SetEntPropString(startEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_start"); - SetEntProp(startEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(startEnt, eyePos, eyeAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(startEnt, "SetParent", client); - - DispatchSpawn(startEnt); - SetEntityOwner(startEnt, client); - - g_ClientFlashlightStartEntity[client] = EntIndexToEntRef(startEnt); - - SetEntityTransmitState(startEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectShouldTransmit); - } - if (endEnt != -1) // End - { - SetEntPropString(endEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_end"); - SetEntProp(endEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(endEnt, eyePos, eyeAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(endEnt, "SetParent", client); - - DispatchSpawn(endEnt); - SetEntityOwner(endEnt, client); - - g_ClientFlashlightEndEntity[client] = EntIndexToEntRef(endEnt); - - SetEntityTransmitState(endEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectShouldTransmit); - } - + TeleportEntity(spotlight.index, eyePos, eyeAng); switch (g_PlayerPreferences[client].PlayerPreference_FlashlightTemperature) { case 1: { - SetEntityRenderColor(ent, 255, 150, 50, 64); + spotlight.SetRenderColor(255, 150, 50, 255); } case 2: { - SetEntityRenderColor(ent, 255, 210, 100, 64); + spotlight.SetRenderColor(255, 210, 100, 255); } case 3: { - SetEntityRenderColor(ent, 255, 255, 120, 64); + spotlight.SetRenderColor(255, 255, 120, 255); } case 4: { - SetEntityRenderColor(ent, 255, 255, 185, 64); + spotlight.SetRenderColor(255, 255, 185, 255); } case 5: { - SetEntityRenderColor(ent, 255, 255, 210, 64); + spotlight.SetRenderColor(255, 255, 210, 255); } case 6: { - SetEntityRenderColor(ent, 255, 255, 255, 64); + spotlight.SetRenderColor(255, 255, 255, 255); } case 7: { - SetEntityRenderColor(ent, 210, 255, 255, 64); + spotlight.SetRenderColor(210, 255, 255, 255); } case 8: { - SetEntityRenderColor(ent, 185, 255, 255, 64); + spotlight.SetRenderColor(185, 255, 255, 255); } case 9: { - SetEntityRenderColor(ent, 150, 255, 255, 64); + spotlight.SetRenderColor(150, 255, 255, 255); } case 10: { - SetEntityRenderColor(ent, 125, 255, 255, 64); + spotlight.SetRenderColor(125, 255, 255, 255); } } - - SetEntityModel(ent, SF2_FLASHLIGHT_BEAM_MATERIAL); - SetEntityRenderMode(ent, RENDER_TRANSTEXTURE); - SetEntPropFloat(ent, Prop_Data, "m_life", 0.0); - - TeleportEntity(ent, eyePos, eyeAng, NULL_VECTOR); - - DispatchSpawn(ent); - ActivateEntity(ent); - - SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", startEnt, 0); - SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", endEnt, 1); - SetEntProp(ent, Prop_Send, "m_nNumBeamEnts", 2); - SetEntProp(ent, Prop_Send, "m_nBeamType", 2); - - SetEntPropFloat(ent, Prop_Send, "m_fWidth", 40.0); - SetEntPropFloat(ent, Prop_Data, "m_fEndWidth", 102.0); - - SetEntPropFloat(ent, Prop_Send, "m_fFadeLength", 0.0); - SetEntProp(ent, Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); - SetEntPropFloat(ent, Prop_Send, "m_fHaloScale", 40.0); - SetEntProp(ent, Prop_Send, "m_nBeamFlags", 0x80 | 0x200); - SetEntProp(ent, Prop_Data, "m_spawnflags", 0x8000); - - AcceptEntityInput(ent, "TurnOn"); - + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer) + { + spotlight.Length = length; + } + else + { + spotlight.Length = doubleLength; + } + } + else + { + float customLength = length * g_ClassFlashlightLength[classToInt]; + spotlight.Length = customLength; + } + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer) + { + spotlight.Width = radius; + } + else + { + spotlight.Width = doubleRadius; + } + } + else + { + float customRadius = radius * g_ClassFlashlightRadius[classToInt]; + spotlight.Width = customRadius; + } + if (!IsClassConfigsValid()) + { + spotlight.Brightness = SF2_FLASHLIGHT_BRIGHTNESS; + } + else + { + int customBrightness = SF2_FLASHLIGHT_BRIGHTNESS + g_ClassFlashlightBrightness[classToInt]; + spotlight.Brightness = customBrightness; + } + spotlight.Cone = RoundToFloor(cone); + spotlight.EndWidth = spotlight.Width * 2.0; + spotlight.Distance = spotlight.EndWidth; + spotlight.SpotlightRadius = spotlight.Length; + spotlight.HaloScale = 20.0; + spotlight.Spawn(); + spotlight.Activate(); SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", client); - - SetEntityOwner(ent, client); - - SDKHook(ent, SDKHook_SetTransmit, Hook_Flashlight2SetTransmit); - SetEntityTransmitState(ent, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectShouldTransmit); - - g_PlayerFlashlightEntAng[client] = EntIndexToEntRef(ent); + spotlight.AcceptInput("SetParent", client); + spotlight.TurnOn(); + g_PlayerFlashlightEntAng[client] = EntIndexToEntRef(spotlight.index); }*/ Call_StartForward(g_OnClientActivateFlashlightFwd); @@ -650,8 +702,7 @@ void ClientTurnOffFlashlight(int client) ent = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); if (ent && ent != INVALID_ENT_REFERENCE) { - AcceptEntityInput(ent, "TurnOff"); - CreateTimer(0.1, Timer_KillEntity, g_PlayerFlashlightEntAng[client], TIMER_FLAG_NO_MAPCHANGE); + RemoveEntity(ent); } ent = EntRefToEntIndex(g_ClientFlashlightStartEntity[client]); if (ent && ent != INVALID_ENT_REFERENCE) @@ -665,11 +716,11 @@ void ClientTurnOffFlashlight(int client) } g_PlayerFlashlightEnt[client] = INVALID_ENT_REFERENCE; - g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; g_ClientFlashlightStartEntity[client] = INVALID_ENT_REFERENCE; g_ClientFlashlightEndEntity[client] = INVALID_ENT_REFERENCE; + g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; - if (IsClientInGame(client)) + if (IsValidClient(client)) { if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) { @@ -725,6 +776,32 @@ void ClientStartDrainingFlashlightBattery(int client) { drainRate *= g_ClassFlashlightDrainRate[classToInt]; } + if (IsNightVisionEnabled() && g_NightVisionType == 2) //Blue nightvision + { + switch (g_DifficultyConVar.IntValue) + { + case Difficulty_Normal: + { + drainRate *= 0.3; + } + case Difficulty_Hard: + { + drainRate *= 0.25; + } + case Difficulty_Insane: + { + drainRate *= 0.2; + } + case Difficulty_Nightmare: + { + drainRate *= 0.15; + } + case Difficulty_Apollyon: + { + drainRate *= 0.1; + } + } + } g_PlayerFlashlightBatteryTimer[client] = CreateTimer(drainRate, Timer_DrainFlashlight, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); } @@ -736,7 +813,12 @@ void ClientHandleFlashlight(int client) return; } - bool nightVision = (g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)); + if (IsClientInDeathCam(client)) + { + return; + } + + bool nightVision = IsNightVisionEnabled(); if (IsClientUsingFlashlight(client) || TF2_IsPlayerInCondition(client, TFCond_Taunting)) { @@ -751,13 +833,17 @@ void ClientHandleFlashlight(int client) { if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) { - EmitSoundToAll(FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToAll(FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } else { - EmitSoundToClient(client, FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToClient(client, FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } } + + Call_StartForward(g_OnPlayerTurnOffFlashlightPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } else { @@ -776,6 +862,7 @@ void ClientHandleFlashlight(int client) ClientDeactivateUltravision(client); if (nightVision) { + g_PlayerHasFlashlight[client] = true; ClientActivateUltravision(client, nightVision); } else @@ -787,263 +874,25 @@ void ClientHandleFlashlight(int client) g_PlayerFlashlightNextInputTime[client] = GetGameTime(); if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) { - EmitSoundToAll((nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToAll((nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } else { - EmitSoundToClient(client, (nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToClient(client, (nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } - } - else - { - EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); - } - } - } -} - -bool IsClientUsingUltravision(int client) -{ - return g_PlayerHasUltravision[client]; -} -void ClientActivateUltravision(int client, bool nightVision = false) -{ - if (!IsClientInGame(client) || IsClientUsingUltravision(client)) - { - return; - } - - if (!g_PlayerEliminated[client] && (SF_SpecialRound(SPECIALROUND_NOULTRAVISION) && !nightVision)) - { - return; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientActivateUltravision(%d)", client); - } - #endif - - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - g_PlayerHasUltravision[client] = true; - g_PlayerHasFlashlight[client] = nightVision; - - int ent = CreateEntityByName("light_dynamic"); - if (ent != -1) - { - float eyePos[3]; - GetClientEyePosition(client, eyePos); - - TeleportEntity(ent, eyePos, view_as({ 90.0, 0.0, 0.0 }), NULL_VECTOR); - if (nightVision && !g_PlayerEliminated[client]) - { - switch (g_NightvisionType) - { - case 0: - { - DispatchKeyValue(ent, "rendercolor", "50 255 50"); - } - case 1: - { - DispatchKeyValue(ent, "rendercolor", "255 50 50"); - } - } - } - else - { - DispatchKeyValue(ent, "rendercolor", "100 200 255"); - } - - float radius = 0.0; - if (g_PlayerEliminated[client]) - { - radius = g_UltravisionRadiusBlueConVar.FloatValue; - } - else - { - radius = g_UltravisionRadiusRedConVar.FloatValue; - if (nightVision) - { - radius = g_NightvisionRadiusConVar.FloatValue; - if (!IsClassConfigsValid()) - { - radius = g_NightvisionRadiusConVar.FloatValue; - } - else - { - radius = g_NightvisionRadiusConVar.FloatValue * g_ClassNightvisionRadiusMultiplier[classToInt]; - } - } - if (!nightVision && class == TFClass_Sniper && !IsClassConfigsValid()) - { - radius *= 2.0; - } - else if (!nightVision && IsClassConfigsValid()) - { - radius *= g_ClassUltravisionRadiusMultiplier[classToInt]; - } - } - - SetVariantFloat(radius); - AcceptEntityInput(ent, "spotlight_radius"); - SetVariantFloat(radius); - AcceptEntityInput(ent, "distance"); - - SetVariantInt(-15); // Start dark, then fade in via the Timer_UltravisionFadeInEffect timer func. - AcceptEntityInput(ent, "brightness"); - if (nightVision && !g_PlayerEliminated[client]) - { - if (!IsClassConfigsValid()) - { - SetVariantInt(5); - AcceptEntityInput(ent, "brightness"); + Call_StartForward(g_OnPlayerTurnOnFlashlightPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } else { - int roundedBrightness = RoundToNearest(5.0 * g_ClassNightvisionBrightnessMultiplier[classToInt]); - SetVariantInt(roundedBrightness); - AcceptEntityInput(ent, "brightness"); - } - } - - // Convert WU to inches. - float cone = SF2_ULTRAVISION_CONE; - cone *= 0.75; - - SetVariantInt(RoundToFloor(cone)); - AcceptEntityInput(ent, "_inner_cone"); - SetVariantInt(0); - AcceptEntityInput(ent, "_cone"); - DispatchSpawn(ent); - ActivateEntity(ent); - SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", client); - AcceptEntityInput(ent, "TurnOn"); - //SetEntityRenderFx(ent, RENDERFX_SOLID_FAST); - SetEntityRenderColor(ent, 150, 225, 255, 255); - if (nightVision && !g_PlayerEliminated[client]) - { - switch (g_NightvisionType) - { - case 0: - { - DispatchKeyValue(ent, "rendercolor", "50 255 50"); - } - case 1: - { - DispatchKeyValue(ent, "rendercolor", "255 50 50"); - } - } - } - - g_PlayerUltravisionEnt[client] = EntIndexToEntRef(ent); - - SDKHook(ent, SDKHook_SetTransmit, Hook_UltravisionSetTransmit); - - // Fade in effect. - if (!IsClassConfigsValid()) - { - if (class != TFClass_Engineer || IsClientInGhostMode(client)) - { - CreateTimer(0.0, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - else if (class == TFClass_Engineer && !IsClientInGhostMode(client)) - { - CreateTimer(0.15, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); } } - else - { - CreateTimer(g_ClassUltravisionFadeInTimer[classToInt], Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientActivateUltravision(%d)", client); - } - #endif } -static Action Timer_UltravisionFadeInEffect(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); - if (!ent || ent == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - int brightness = GetEntProp(ent, Prop_Send, "m_Exponent"); - int maxBrightness = g_UltravisionBrightnessConVar.IntValue; - if (!IsClassConfigsValid()) - { - if (class == TFClass_Sniper) - { - maxBrightness = RoundToNearest(float(maxBrightness) * 0.75); - } - } - else - { - maxBrightness = RoundToNearest(float(maxBrightness) * g_ClassUltravisionBrightnessMultiplier[classToInt]); - } - if (brightness >= maxBrightness) - { - return Plugin_Stop; - } - - brightness++; - SetVariantInt(brightness); - AcceptEntityInput(ent, "brightness"); - - return Plugin_Continue; -} - -void ClientDeactivateUltravision(int client) -{ - if (!IsClientUsingUltravision(client)) - { - return; - } - - g_PlayerHasUltravision[client] = false; - - int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); - if (ent != INVALID_ENT_REFERENCE) - { - AcceptEntityInput(ent, "TurnOff"); - RemoveEntity(ent); - } - - g_PlayerUltravisionEnt[client] = INVALID_ENT_REFERENCE; -} - -static Action Hook_UltravisionSetTransmit(int ent,int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - if (!g_UltravisionEnabledConVar.BoolValue || EntRefToEntIndex(g_PlayerUltravisionEnt[other]) != ent || !IsPlayerAlive(other)) - { - return Plugin_Handled; - } - return Plugin_Continue; -} /* void ClientSDKFlashlightTurnOn(int client) { diff --git a/addons/sourcemod/scripting/sf2/client/ghostmode.sp b/addons/sourcemod/scripting/sf2/client/ghostmode.sp new file mode 100644 index 00000000..6a387e9c --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/ghostmode.sp @@ -0,0 +1,422 @@ +#pragma semicolon 1 + +#define SF2_OVERLAY_GHOST "overlays/slender/ghostcamera" + +static bool g_PlayerInGhostMode[MAXTF2PLAYERS] = { false, ... }; +static int g_PlayerGhostModeTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerGhostModeBossTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +Handle g_PlayerGhostModeConnectionCheckTimer[MAXTF2PLAYERS] = { null, ... }; +float g_PlayerGhostModeConnectionTimeOutTime[MAXTF2PLAYERS] = { -1.0, ... }; +float g_PlayerGhostModeConnectionBootTime[MAXTF2PLAYERS] = { -1.0, ... }; + +void SetupGhost() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerTeamPFwd.AddFunction(null, OnPlayerTeam); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + ClientSetGhostModeState(client.index, false); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!IsClientParticipating(client.index)) + { + ClientSetGhostModeState(client.index, false); + } + ClientHandleGhostMode(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (fake || IsRoundInWarmup() || client.IsEliminated) + { + return; + } + + if (g_PlayerPreferences[client.index].PlayerPreference_GhostModeToggleState == 2) + { + CreateTimer(0.25, Timer_ToggleGhostModeCommand, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientHandleGhostMode(client.index); +} + +static void OnPlayerTeam(SF2_BasePlayer client, int team) +{ + if (team <= TFTeam_Spectator) + { + ClientSetGhostModeState(client.index, false); + } +} + +bool IsClientInGhostMode(int client) +{ + return g_PlayerInGhostMode[client]; +} + +/** + * Enables/Disables ghost mode on the player. + */ +void ClientSetGhostModeState(int client, bool state) +{ + if (state == g_PlayerInGhostMode[client]) + { + return; + } + + Handle message = StartMessageAll("PlayerTauntSoundLoopEnd", USERMSG_RELIABLE); + BfWriteByte(message, client); + delete message; + EndMessage(); + + if (state && !IsValidClient(client)) + { + return; + } + + Call_StartForward(g_OnPlayerChangeGhostStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(state); + Call_Finish(); + + g_PlayerInGhostMode[client] = state; + g_PlayerGhostModeTarget[client] = INVALID_ENT_REFERENCE; + g_PlayerGhostModeBossTarget[client] = INVALID_ENT_REFERENCE; + + if (state) + { + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Entered ghost mode."); + #endif + //Strip the always edict flag + SetEntityFlags(client, GetEntityFlags(client) ^ FL_EDICT_ALWAYS); + //Remove the fire cond + TF2_RemoveCondition(client,TFCond_OnFire); + //Call the spawn event. + TF2_RespawnPlayer(client); + TF2_RemoveCondition(client,TFCond_Taunting); + + TFClassType desiredClass = TF2_GetPlayerClass(client); + if (desiredClass == TFClass_Unknown) + { + desiredClass = TFClass_Spy; + } + + //Set player's class to spy, this replaces old ghost mode mechanics. + TF2_SetPlayerClass(client, TFClass_Spy); + TF2_RegeneratePlayer(client); + + //Set player's old class as desired class. + SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", desiredClass); + + ClientHandleGhostMode(client, true); + if (g_GhostModeConnectionCheckConVar.BoolValue) + { + g_PlayerGhostModeConnectionCheckTimer[client] = CreateTimer(0.0, Timer_GhostModeConnectionCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; + g_PlayerGhostModeConnectionBootTime[client] = -1.0; + } + + SetEntityCollisionGroup(client, 1); + + PvP_OnClientGhostModeEnable(client); + } + else + { + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Exited ghost mode."); + #endif + TF2Attrib_SetByName(client, "mod see enemy health", 0.0); + g_PlayerGhostModeConnectionCheckTimer[client] = null; + g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; + g_PlayerGhostModeConnectionBootTime[client] = -1.0; + + if (IsValidClient(client)) + { + SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_YES); + TF2_RemoveCondition(client, TFCond_Stealthed); + SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 1); + SetEntityGravity(client, 1.0); + SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); + SetEntityFlags(client, GetEntityFlags(client) &~ FL_NOTARGET); + SetEntProp(client, Prop_Data, "m_usSolidFlags", 16); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + SetEntityRenderMode(client, RENDER_NORMAL); + SetEntityRenderColor(client, _, _, _, 255); + SetEntityCollisionGroup(client, COLLISION_GROUP_PLAYER); + } + } +} + +/** + * Makes sure that the player is a ghost when ghost mode is activated. + */ +void ClientHandleGhostMode(int client, bool forceSpawn = false) +{ + if (!IsClientInGhostMode(client)) + { + return; + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientHandleGhostMode(%d, %d)", client, forceSpawn); + } + #endif + + if (!TF2_IsPlayerInCondition(client, TFCond_Stealthed) || forceSpawn) + { + TF2_StripWearables(client); + DestroyAllActiveWeapons(client); + TF2_DestroySpyWeapons(); + SetEntityGravity(client, 0.5); + TF2_AddCondition(client, TFCond_Stealthed, -1.0); + SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 0); + SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_NO); + SetEntProp(client, Prop_Send, "m_usSolidFlags", 4); + SetEntProp(client, Prop_Data, "m_nSolidType", 0); + SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); + SetEntityCollisionGroup(client, 26); + SetEntityFlags(client, GetEntityFlags(client) | FL_NOTARGET); + SetEntityRenderMode(client, RENDER_TRANSCOLOR); + SetEntityRenderColor(client, _, _, _, 0); + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + + // Set first observer target. + ClientGhostModeNextTarget(client, true); + ClientActivateUltravision(client); + + // screen overlay timer + g_PlayerOverlayCheck[client] = CreateTimer(0.0, Timer_PlayerOverlayCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerOverlayCheck[client], true); + + CreateTimer(0.2, Timer_ClientGhostStripWearables, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientHandleGhostMode(%d, %d)", client, forceSpawn); + } + #endif +} + +static Action Timer_ClientGhostStripWearables(Handle timer, int userid) +{ + int client = GetClientOfUserId(userid); + if (!IsValidClient(client)) + { + return Plugin_Stop; + } + if (!IsClientInGhostMode(client)) + { + return Plugin_Stop; + } + TF2_StripWearables(client); + DestroyAllActiveWeapons(client); + TF2_DestroySpyWeapons(); + return Plugin_Stop; +} + +void ClientGhostModeNextTarget(int client, bool ignoreSetting = false) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientGhostModeNextTarget(%d)", client); + } + #endif + + if (g_PlayerPreferences[client].PlayerPreference_GhostModeTeleportState == 0 || ignoreSetting) + { + int lastTarget = EntRefToEntIndex(g_PlayerGhostModeTarget[client]); + int nextTarget = -1; + int firstTarget = -1; + for (int i = 1; i <= MaxClients; i++) + { + if (IsValidClient(i) && (!g_PlayerEliminated[i] || g_PlayerProxy[i]) && !IsClientInGhostMode(i) && !DidClientEscape(i) && IsPlayerAlive(i)) + { + if (firstTarget == -1) + { + firstTarget = i; + } + if (i > lastTarget) + { + nextTarget = i; + break; + } + } + } + + int target = -1; + if (IsValidClient(nextTarget)) + { + target = nextTarget; + } + else + { + target = firstTarget; + } + + if (IsValidClient(target)) + { + g_PlayerGhostModeTarget[client] = EntIndexToEntRef(target); + + float pos[3], ang[3], velocity[3]; + GetClientAbsOrigin(target, pos); + GetClientEyeAngles(target, ang); + GetEntPropVector(target, Prop_Data, "m_vecAbsVelocity", velocity); + TeleportEntity(client, pos, ang, velocity); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientGhostModeNextTarget(%d)", client); + } + #endif + } + else + { + int lastTarget = NPCGetFromEntIndex(EntRefToEntIndex(g_PlayerGhostModeBossTarget[client])); + int nextTarget = -1; + int firstTarget = -1; + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + if (NPCGetUniqueID(bossIndex) == -1 || !IsValidEntity(NPCGetEntIndex(bossIndex))) + { + continue; + } + + if (NPCGetProfileData(bossIndex).IsPvEBoss) + { + continue; + } + + if (firstTarget == -1) + { + firstTarget = bossIndex; + } + if (bossIndex > lastTarget) + { + nextTarget = bossIndex; + break; + } + } + + int target = -1; + if (nextTarget != - 1 && NPCGetEntIndex(nextTarget) && NPCGetEntIndex(nextTarget) != INVALID_ENT_REFERENCE) + { + target = nextTarget; + } + else + { + target = firstTarget; + } + + if (target != -1 && IsValidEntity(NPCGetEntIndex(target))) + { + g_PlayerGhostModeBossTarget[client] = EntIndexToEntRef(NPCGetEntIndex(target)); + + float pos[3], ang[3], velocity[3]; + GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_angAbsRotation", ang); + GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsVelocity", velocity); + TeleportEntity(client, pos, ang, velocity); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientGhostModeNextTarget(%d)", client); + } + #endif + } +} + +Action Hook_GhostNoTouch(int entity, int other) +{ + if (IsValidClient(other)) + { + if (IsClientInGhostMode(other)) + { + return Plugin_Handled; + } + } + return Plugin_Continue; +} + +Action Timer_GhostModeConnectionCheck(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerGhostModeConnectionCheckTimer[client]) + { + return Plugin_Stop; + } + + if (!IsFakeClient(client) && IsClientTimingOut(client)) + { + float bootTime = g_PlayerGhostModeConnectionBootTime[client]; + bool checkBool = g_GhostModeConnectionConVar.BoolValue; + if (bootTime < 0.0 && !checkBool) + { + bootTime = GetGameTime() + g_GhostModeConnectionToleranceConVar.FloatValue; + g_PlayerGhostModeConnectionBootTime[client] = bootTime; + g_PlayerGhostModeConnectionTimeOutTime[client] = GetGameTime(); + } + + if (GetGameTime() >= bootTime || checkBool) + { + ClientSetGhostModeState(client, false); + TF2_RespawnPlayer(client); + + char authString[128]; + GetClientAuthId(client, AuthId_Engine, authString, sizeof(authString)); + + LogSF2Message("Removed %N (%s) from ghost mode due to timing out for %f seconds", client, authString, g_GhostModeConnectionToleranceConVar.FloatValue); + + float timeOutTime = g_PlayerGhostModeConnectionTimeOutTime[client]; + CPrintToChat(client, "\x08FF4040FF%T", "SF2 Ghost Mode Bad Connection", client, RoundFloat(bootTime - timeOutTime)); + + return Plugin_Stop; + } + } + else + { + // Player regained connection; reset. + g_PlayerGhostModeConnectionBootTime[client] = -1.0; + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/client/glow.sp b/addons/sourcemod/scripting/sf2/client/glow.sp new file mode 100644 index 00000000..3c0ec072 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/glow.sp @@ -0,0 +1,296 @@ +static const int g_DefaultRedColor[4] = { 184, 56, 59, 255 }; +static const int g_DefaultProxyColor[4] = { 255, 208, 0, 255 }; + +static float g_NextCheckTime[MAXTF2PLAYERS]; + +void SetupPlayerGlows() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerChangePlayStatePFwd.AddFunction(null, OnPlayerChangePlayState); + g_OnPlayerChangeProxyStatePFwd.AddFunction(null, OnPlayerChangeProxyState); + g_OnPlayerConditionAddedPFwd.AddFunction(null, OnPlayerConditionAdded); + g_OnPlayerConditionRemovedPFwd.AddFunction(null, OnPlayerConditionRemoved); + g_OnSpecialRoundStartPFwd.AddFunction(null, OnSpecialRoundStart); + g_OnRenevantTriggerWavePFwd.AddFunction(null, OnRenevantTriggerWave); + g_OnWallHaxDebugPFwd.AddFunction(null, OnWallHaxDebug); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + SDKHook(client.index, SDKHook_PreThinkPost, PreThinkPost); + + g_NextCheckTime[client.index] = -1.0; +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + DestroyGlowEntity(client.index); + + if (ShouldHaveGlow(client)) + { + int color[4]; + GetGlowColor(client, color); + CreateGlowEntity(client.index, color); + UpdateVisibility(client); + } + + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + DestroyGlowEntity(client.index); + UpdateVisibilityOfOtherGlows(client); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + DestroyGlowEntity(client.index); + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangePlayState(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangeProxyState(SF2_BasePlayer client, bool state) +{ + if (state) + { + if (!DoesEntityHaveGlow(client.index)) + { + CreateGlowEntity(client.index); + } + int color[4]; + GetGlowColor(client, color); + SetGlowColor(client.index, color); + UpdateVisibility(client); + UpdateVisibilityOfOtherGlows(client); + } +} + +static void OnPlayerConditionAdded(SF2_BasePlayer client, TFCond condition) +{ + if (DoesEntityHaveGlow(client.index) && (TF2_IsInvisible(client.index))) + { + UpdateVisibility(client); + } +} + +static void OnPlayerConditionRemoved(SF2_BasePlayer client, TFCond condition) +{ + if (DoesEntityHaveGlow(client.index) && (TF2_IsInvisible(client.index))) + { + UpdateVisibility(client); + } +} + +static void OnSpecialRoundStart(int specialRound) +{ + if (specialRound == SPECIALROUND_WALLHAX) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (!client.IsEliminated) + { + UpdateVisibilityOfOtherGlows(client); + } + } + } +} + +static void OnRenevantTriggerWave(int wave, RenevantWave condition) +{ + if (condition == RenevantWave_WallHax) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (!client.IsEliminated) + { + UpdateVisibilityOfOtherGlows(client); + } + } + } +} + +static void OnWallHaxDebug() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (!client.IsEliminated) + { + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static void PreThinkPost(int clientIndex) +{ + SF2_BasePlayer client = SF2_BasePlayer(clientIndex); + + float gameTime = GetGameTime(); + if (gameTime >= g_NextCheckTime[clientIndex]) + { + g_NextCheckTime[clientIndex] = gameTime + 0.1; + + if (client.IsProxy) + { + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static bool ShouldBeVisibleToPlayer(SF2_BasePlayer client, SF2_BasePlayer other) +{ + if (client.index == other.index) + { + return false; + } + + if (!client.IsEliminated) + { + if (other.IsInGhostMode) + { + return true; + } + + if (!other.IsEliminated) + { + if (other.HasEscaped) + { + return false; + } + + if (SF_SpecialRound(SPECIALROUND_WALLHAX) || g_EnableWallHaxConVar.BoolValue || g_RenevantWallHax) + { + return true; + } + + return false; + } + else if (other.IsProxy) + { + if (TF2_IsInvisible(client.index)) + { + return false; + } + + if (client.IsProxy || client.InCondition(TFCond_Taunting)) + { + return true; + } + + float vecVel[3], movespeed; + other.GetAbsVelocity(vecVel); + movespeed = GetVectorLength(vecVel, true); + + if (movespeed <= Pow(163.0, 2.0)) + { + float ownerPos[3], proxyPos[3]; + client.GetEyePosition(ownerPos); + other.GetEyePosition(proxyPos); + + float distance = Pow(500.0, 2.0); + return GetVectorSquareMagnitude(ownerPos, proxyPos) <= distance; + } + } + } + else + { + if (client.IsProxy) + { + if (other.IsProxy || other.IsInGhostMode) + { + return true; + } + } + } + + return false; +} + +static void UpdateVisibility(SF2_BasePlayer client) +{ + int clients[MAXPLAYERS + 1]; + int numClients = 0; + bool states[MAXPLAYERS + 1]; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer other = SF2_BasePlayer(i); + if (!other.IsValid) + { + continue; + } + + int i2 = numClients++; + clients[i2] = i; + states[i2] = ShouldBeVisibleToPlayer(client, other); + } + + SetGlowVisibility(client.index, clients, numClients, states); +} + +static void UpdateVisibilityOfOtherGlows(SF2_BasePlayer client) +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer other = SF2_BasePlayer(i); + if (!other.IsValid) + { + continue; + } + + if (ShouldHaveGlow(other)) + { + SetGlowVisibilityForClient(other.index, client.index, ShouldBeVisibleToPlayer(other, client)); + } + } +} + +static bool ShouldHaveGlow(SF2_BasePlayer client) +{ + return client.IsAlive && ((!client.IsEliminated && !client.HasEscaped) || client.IsProxy); +} + +static void GetGlowColor(SF2_BasePlayer client, int color[4]) +{ + if (!client.IsEliminated && !client.HasEscaped) + { + color = g_DefaultRedColor; + } + else if (client.IsProxy) + { + color = g_DefaultProxyColor; + } +} diff --git a/addons/sourcemod/scripting/sf2/client/hints.sp b/addons/sourcemod/scripting/sf2/client/hints.sp new file mode 100644 index 00000000..71202ee4 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/hints.sp @@ -0,0 +1,260 @@ +#if defined _sf2_client_hints_included + #endinput +#endif +#define _sf2_client_hints_included + +bool g_PlayerHints[MAXTF2PLAYERS][PlayerHint_MaxNum]; +static Handle g_ShowHintTimer[MAXPLAYERS + 1]; + +void SetupHints() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerChangePlayStatePFwd.AddFunction(null, OnPlayerChangePlayState); + g_OnDifficultyVoteFinishedPFwd.AddFunction(null, OnDifficultyVoteFinished); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetHints(client.index); + + g_ShowHintTimer[client.index] = null; +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetHints(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetHints(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetHints(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetHints(client.index); +} + +static void OnPlayerChangePlayState(SF2_BasePlayer client, bool state, bool queue) +{ + if (!state) + { + g_ShowHintTimer[client.index] = null; + } +} + +static Action OnDifficultyVoteFinished(int difficulty, int& newDifficulty) +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || !g_PlayerPreferences[player.index].PlayerPreference_ShowHints) + { + continue; + } + + if (player.IsEliminated) + { + if (!player.HasHint(PlayerHint_MainMenu)) + { + player.ShowHint(PlayerHint_MainMenu); + } + } + else + { + if (!player.HasHint(PlayerHint_Flashlight)) + { + player.ShowHint(PlayerHint_Flashlight); + + if (!player.HasHint(PlayerHint_Sprint)) + { + g_ShowHintTimer[player.index] = CreateTimer(10.0, Timer_ShowSprintHint, player.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + else if (!player.HasHint(PlayerHint_Sprint)) + { + player.ShowHint(PlayerHint_Sprint); + + if (!player.HasHint(PlayerHint_Noises)) + { + g_ShowHintTimer[player.index] = CreateTimer(10.0, Timer_ShowNoisesHint, player.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + else if (!player.HasHint(PlayerHint_Noises)) + { + player.ShowHint(PlayerHint_Noises); + } + } + } + return Plugin_Continue; +} + +static Action Timer_ShowSprintHint(Handle timer, any userId) +{ + int clientIndex = GetClientOfUserId(userId); + if (clientIndex == 0) + { + return Plugin_Stop; + } + + if (timer != g_ShowHintTimer[clientIndex]) + { + return Plugin_Stop; + } + + SF2_BasePlayer player = SF2_BasePlayer(clientIndex); + + if (player.IsEliminated || player.HasEscaped || !g_PlayerPreferences[player.index].PlayerPreference_ShowHints) + { + return Plugin_Stop; + } + + if (!player.HasHint(PlayerHint_Sprint)) + { + player.ShowHint(PlayerHint_Sprint); + } + + if (!player.HasHint(PlayerHint_Noises)) + { + g_ShowHintTimer[player.index] = CreateTimer(10.0, Timer_ShowNoisesHint, player.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + return Plugin_Stop; +} + +static Action Timer_ShowNoisesHint(Handle timer, any userId) +{ + int clientIndex = GetClientOfUserId(userId); + if (clientIndex == 0) + { + return Plugin_Stop; + } + + if (timer != g_ShowHintTimer[clientIndex]) + { + return Plugin_Stop; + } + + SF2_BasePlayer player = SF2_BasePlayer(clientIndex); + + if (player.IsEliminated || player.HasEscaped || !g_PlayerPreferences[player.index].PlayerPreference_ShowHints) + { + return Plugin_Stop; + } + + if (!player.HasHint(PlayerHint_Noises)) + { + player.ShowHint(PlayerHint_Noises); + } + + return Plugin_Stop; +} + +// Hint data. +enum +{ + PlayerHint_Sprint = 0, + PlayerHint_Flashlight, + PlayerHint_MainMenu, + PlayerHint_Blink, + PlayerHint_Trap, + PlayerHint_Noises, + PlayerHint_Crouch, + PlayerHint_MaxNum +}; + +void ClientResetHints(int client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetHints(%d)", client); + } + #endif + + for (int i = 0; i < PlayerHint_MaxNum; i++) + { + g_PlayerHints[client][i] = false; + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetHints(%d)", client); + } + #endif +} + +void ClientShowHint(int client, int hint) +{ + g_PlayerHints[client][hint] = true; + + switch (hint) + { + case PlayerHint_Sprint: + { + PrintHintText(client, "%T", "SF2 Hint Sprint", client); + } + case PlayerHint_Flashlight: + { + PrintHintText(client, "%T", "SF2 Hint Flashlight", client); + } + case PlayerHint_Blink: + { + PrintHintText(client, "%T", "SF2 Hint Blink", client); + } + case PlayerHint_MainMenu: + { + PrintHintText(client, "%T", "SF2 Hint Main Menu", client); + } + case PlayerHint_Trap: + { + PrintHintText(client, "%T", "SF2 Hint Trap", client); + } + case PlayerHint_Noises: + { + PrintHintText(client, "%T", "SF2 Hint Noises", client); + } + case PlayerHint_Crouch: + { + PrintHintText(client, "%T", "SF2 Hint Crouch", client); + } + } +} + +bool ClientHasHint(int client, int hint) +{ + return g_PlayerHints[client][hint]; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/interactables.sp b/addons/sourcemod/scripting/sf2/client/interactables.sp new file mode 100644 index 00000000..e9bbd7fb --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/interactables.sp @@ -0,0 +1,235 @@ +static int g_PlayerInteractiveGlowEntity[MAXPLAYERS + 1] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerInteractiveGlowTargetEntity[MAXPLAYERS + 1] = { INVALID_ENT_REFERENCE, ... }; +static float g_NextInteractiveThink[MAXPLAYERS + 1] = { 0.0, ... }; + +static const char g_InteractableClasses[][64] = { + "prop_dynamic_override", + "func_button" +}; + +static StringMap g_Interactables; + +void SetupInteractables() +{ + g_Interactables = new StringMap(); + + g_OnMapEndPFwd.AddFunction(null, MapEnd); + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); +} + +static void MapEnd() +{ + g_Interactables.Clear(); +} + +static void OnGamemodeEnd() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + + RemoveInteractiveGlow(player); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + g_PlayerInteractiveGlowEntity[client.index] = INVALID_ENT_REFERENCE; + g_NextInteractiveThink[client.index] = 0.0; + + SDKHook(client.index, SDKHook_PreThinkPost, PreThinkPost); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + RemoveInteractiveGlow(client); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + g_NextInteractiveThink[client.index] = 0.0; + + RemoveInteractiveGlow(client); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + RemoveInteractiveGlow(client); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + RemoveInteractiveGlow(client); +} + +static void EntityCreated(CBaseEntity ent, const char[] classname) +{ + for (int i = 0; i < sizeof(g_InteractableClasses); i++) + { + if (g_InteractableClasses[i][0] == classname[0] && strcmp(classname, g_InteractableClasses[i]) == 0) + { + // Possible interactable. Wait until it spawns. + SDKHook(ent.index, SDKHook_SpawnPost, CheckIfInteractableSpawnPost); + break; + } + } +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + int entRef = EnsureEntRef(ent.index); + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + + if (g_PlayerInteractiveGlowTargetEntity[player.index] == entRef) + { + g_PlayerInteractiveGlowTargetEntity[player.index] = INVALID_ENT_REFERENCE; + RemoveInteractiveGlow(player); + } + } + + char key[32]; + IntToString(entRef, key, sizeof(key)); + g_Interactables.Remove(key); +} + +static void CheckIfInteractableSpawnPost(int entity) +{ + if (IsValidEdict(entity)) + { + char className[64]; + GetEntityClassname(entity, className, sizeof(className)); + + bool isInteractable = false; + + if (strncmp(className, "prop_dynamic", 12) == 0) + { + char targetName[64]; + GetEntPropString(entity, Prop_Data, "m_iName", targetName, sizeof(targetName)); + + if (targetName[0] && (strncmp(targetName, "sf2_page", 8) == 0 || strncmp(targetName, "sf2_interact", 12) == 0)) + { + isInteractable = true; + } + } + else if (strncmp(className, "func_button", 11) == 0) + { + isInteractable = true; + } + + if (isInteractable) + { + char key[32]; + IntToString(EnsureEntRef(entity), key, sizeof(key)); + g_Interactables.SetValue(key, true); + } + } +} + +bool IsEntityInteractable(int ent) +{ + if (!IsValidEdict(ent)) + { + return false; + } + + bool value; + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + return g_Interactables.GetValue(key, value); +} + +static void PreThinkPost(int clientIndex) +{ + SF2_BasePlayer client = SF2_BasePlayer(clientIndex); + + if (!client.IsEliminated && !client.HasEscaped && client.IsAlive) + { + float gameTime = GetGameTime(); + if (gameTime >= g_NextInteractiveThink[clientIndex]) + { + g_NextInteractiveThink[clientIndex] = gameTime + 0.1; + UpdateInteractiveGlow(client); + } + } +} + +static void UpdateInteractiveGlow(SF2_BasePlayer client) +{ + float startPos[3], endPos[3]; + client.GetEyePosition(startPos); + client.GetEyeAngles(endPos); + + static const float maxRange = 2000.0; + GetAngleVectors(endPos, endPos, NULL_VECTOR, NULL_VECTOR); + ScaleVector(endPos, maxRange); + AddVectors(startPos, endPos, endPos); + + TR_TraceRayFilter(startPos, endPos, MASK_VISIBLE, RayType_EndPoint, TraceRayDontHitPlayers, -1); + int oldTargetEnt = EntRefToEntIndex(g_PlayerInteractiveGlowTargetEntity[client.index]); + int targetEnt = TR_GetEntityIndex(); + if (!IsEntityInteractable(targetEnt)) + { + targetEnt = -1; + } + + g_PlayerInteractiveGlowTargetEntity[client.index] = targetEnt != -1 ? EntIndexToEntRef(targetEnt) : INVALID_ENT_REFERENCE; + + if (targetEnt != oldTargetEnt) + { + RemoveInteractiveGlow(client); + + if (IsValidEntity(targetEnt)) + { + CBaseEntity glow = CBaseEntity(CreateGlowEntityDataless(targetEnt)); + SDKHook(glow.index, SDKHook_SetTransmit, InteractableSetTransmit); + glow.SetPropEnt(Prop_Data, "m_hOwnerEntity", client.index); + g_PlayerInteractiveGlowEntity[client.index] = EntIndexToEntRef(glow.index); + } + } + + if (targetEnt == -1) + { + RemoveInteractiveGlow(client); + } +} + +static Action InteractableSetTransmit(int ent, int other) +{ + return GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity") == other ? Plugin_Continue : Plugin_Handled; +} + +static void RemoveInteractiveGlow(SF2_BasePlayer player) +{ + int glow = EntRefToEntIndex(g_PlayerInteractiveGlowEntity[player.index]); + if (IsValidEntity(glow)) + { + RemoveEntity(glow); + } + + g_PlayerInteractiveGlowEntity[player.index] = INVALID_ENT_REFERENCE; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_music.sp b/addons/sourcemod/scripting/sf2/client/music.sp similarity index 83% rename from addons/sourcemod/scripting/sf2/functionclients/client_music.sp rename to addons/sourcemod/scripting/sf2/client/music.sp index cc5ee42b..d0d3458f 100644 --- a/addons/sourcemod/scripting/sf2/functionclients/client_music.sp +++ b/addons/sourcemod/scripting/sf2/client/music.sp @@ -5,8 +5,151 @@ #pragma semicolon 1 -void ClientUpdateMusicSystem(int client, bool initialize=false) +void SetupMusic() { + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyChange); +} + +static void OnGamemodeEnd() +{ + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + ClientUpdateMusicSystem(i); + ClientResetChannels(i); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientUpdateMusicSystem(client.index); + ClientResetChannels(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetChannels(client.index); + ClientUpdateMusicSystem(client.index); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetChannels(client.index); + ClientUpdateMusicSystem(client.index); +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetChannels(client.index); + ClientUpdateMusicSystem(client.index); +} + +static void OnBossRemoved(SF2NPC_BaseNPC npc) +{ + if (!MusicActive()) + { + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + + // Remove chase music. + if (g_PlayerChaseMusicMaster[i] == npc.Index) + { + ClientChaseMusicReset(i); + } + + // Don't forget search theme + if (g_PlayerAlertMusicMaster[i] == npc.Index) + { + ClientAlertMusicReset(i); + } + + if (g_PlayerChaseMusicSeeMaster[i] == npc.Index) + { + ClientChaseMusicSeeReset(i); + } + + if (g_PlayerIdleMusicMaster[i] == npc.Index) + { + ClientIdleMusicReset(i); + } + + ClientUpdateMusicSystem(i); + } + } +} + +static void OnDifficultyChange(int oldDifficulty, int newDifficulty) +{ + CheckIfMusicValid(); + if (MusicActive()) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.IsSourceTV) + { + continue; + } + + char path[PLATFORM_MAX_PATH]; + GetBossMusic(path, sizeof(path)); + if (path[0] != '\0') + { + StopSound(i, MUSIC_CHAN, path); + } + client.UpdateMusicSystem(); + } + } +} + +void ClientResetChannels(int client) +{ + ClientChaseMusicReset(client); + ClientChaseMusicSeeReset(client); + ClientAlertMusicReset(client); + ClientIdleMusicReset(client); + Client90sMusicReset(client); + ClientMusicReset(client); +} + +void ClientUpdateMusicSystem(int client, bool initialize = false) +{ + if (!g_Enabled) + { + return; + } + int oldPageMusicMaster = EntRefToEntIndex(g_PlayerPageMusicMaster[client]); int oldPageMusicActiveIndex = g_PageMusicActiveIndex[client]; int oldMusicFlags = g_PlayerMusicFlags[client]; @@ -15,7 +158,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) int alertBoss = -1; int idleBoss = -1; - if (IsRoundEnding() || !IsClientInGame(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) + if (IsRoundEnding() || !IsValidClient(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client]) || SF_SpecialRound(SPECIALROUND_BEATBOX)) { g_PlayerMusicFlags[client] = 0; g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; @@ -24,7 +167,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) if (MusicActive()) //A boss is overriding the music. { char path[PLATFORM_MAX_PATH]; - GetBossMusic(path,sizeof(path)); + GetBossMusic(path, sizeof(path)); if (path[0] != '\0') { StopSound(client, MUSIC_CHAN, path); @@ -145,6 +288,13 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) continue; } + SF2BossProfileData data; + data = NPCGetProfileData(i); + if (data.IsPvEBoss) + { + continue; + } + NPCGetProfile(i, profile, sizeof(profile)); int bossType = NPCGetType(i); @@ -153,23 +303,39 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) { case SF2BossType_Chaser: { + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(i)); + + if (chaser.State == STATE_DEATH) + { + continue; + } + SF2BossProfileSoundInfo soundInfo; ArrayList soundList; GetClientAbsOrigin(client, buffer); - SlenderGetAbsOrigin(i, buffer3); + chaser.GetAbsOrigin(buffer3); + float pos[3]; + if (!chaser.Controller.Path.IsValid()) + { + pos = buffer3; + } + else + { + chaser.Controller.Path.GetEndPosition(pos); + } - int target = EntRefToEntIndex(g_SlenderTarget[i]); - if (target != -1) + CBaseEntity target = chaser.Target; + if (target.IsValid()) { - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", buffer2); + target.GetAbsOrigin(buffer2); - if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && + if ((chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || (chaser.State == STATE_STUN && (chaser.PreviousState == STATE_CHASE || chaser.PreviousState == STATE_ATTACK))) && !(NPCGetFlags(i) & SFF_MARKEDASFAKE)) { GetChaserProfileChaseMusics(profile, soundInfo); - if ((target == client || GetVectorSquareMagnitude(buffer, buffer2) <= SquareFloat(soundInfo.Radius) || - GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius))) + if ((target.index == client || GetVectorSquareMagnitude(buffer, buffer2) <= SquareFloat(soundInfo.Radius) || + GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, pos) <= SquareFloat(soundInfo.Radius))) { soundList = soundInfo.Paths; if (soundList != null && soundList.Length > 0) @@ -177,7 +343,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) chasingBoss = i; } - if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && + if ((chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN) && PlayerCanSeeSlender(client, i, false)) { GetChaserProfileChaseVisibleMusics(profile, soundInfo); @@ -191,7 +357,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) } } - if (g_SlenderState[i] == STATE_ALERT) + if (chaser.State == STATE_ALERT || (chaser.State == STATE_STUN && chaser.PreviousState == STATE_ALERT)) { GetChaserProfileAlertMusics(profile, soundInfo); soundList = soundInfo.Paths; @@ -202,14 +368,14 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) { - if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) + if (GetVectorSquareMagnitude(buffer, pos) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius)) { alertBoss = i; } } } - if (g_SlenderState[i] == STATE_IDLE || g_SlenderState[i] == STATE_WANDER) + if (chaser.State == STATE_IDLE || (chaser.State == STATE_STUN && chaser.PreviousState == STATE_IDLE)) { GetChaserProfileIdleMusics(profile, soundInfo); soundList = soundInfo.Paths; @@ -220,7 +386,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) { - if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) + if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, pos) <= SquareFloat(soundInfo.Radius)) { idleBoss = i; } @@ -280,7 +446,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) } } - if (IsValidClient(client)) + if (IsValidClient(client) && IsPlayerAlive(client)) { bool wasChase = ClientHasMusicFlag2(oldMusicFlags, MUSICF_CHASE); bool inChase = ClientHasMusicFlag(client, MUSICF_CHASE); @@ -292,14 +458,23 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) bool wasIdle = ClientHasMusicFlag2(oldMusicFlags, MUSICF_IDLE); char path[PLATFORM_MAX_PATH]; - if (IsRoundEnding() || !IsClientInGame(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) + if (SF_SpecialRound(SPECIALROUND_BEATBOX)) { + return; } - else if (MusicActive()) //A boss is overriding the music. + + if (IsRoundEnding() || !IsValidClient(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) { - GetBossMusic(path,sizeof(path)); - ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME); - return; + // Do nothing + } + else + { + if (MusicActive()) //A boss is overriding the music. + { + GetBossMusic(path, sizeof(path)); + ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME); + return; + } } // Custom system. @@ -556,7 +731,7 @@ void ClientMusicReset(int client) g_PageMusicActiveIndex[client] = -1; } -void ClientMusicStart(int client, const char[] newMusic, float volume=-1.0, float targetVolume=-1.0, bool copyOnly=false) +void ClientMusicStart(int client, const char[] newMusic, float volume = -1.0, float targetVolume = -1.0, bool copyOnly = false) { if (!IsValidClient(client) || !IsPlayerAlive(client)) { @@ -575,9 +750,9 @@ void ClientMusicStart(int client, const char[] newMusic, float volume=-1.0, floa StopSound(client, MUSIC_CHAN, oldMusic); } strcopy(g_PlayerMusicString[client], sizeof(g_PlayerMusicString[]), newMusic); - if (MusicActive())//A boss is overriding the music. + if (MusicActive()) // A boss is overriding the music. { - GetBossMusic(g_PlayerMusicString[client],sizeof(g_PlayerMusicString[])); + GetBossMusic(g_PlayerMusicString[client], sizeof(g_PlayerMusicString[])); } if (volume >= 0.0) { @@ -624,7 +799,7 @@ void ClientAlertMusicReset(int client) g_PlayerAlertMusicVolumes[client][i] = 0.0; g_PlayerAlertMusicString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { @@ -728,7 +903,7 @@ void ClientIdleMusicReset(int client) g_PlayerIdleMusicVolumes[client][i] = 0.0; g_PlayerIdleMusicString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { @@ -832,7 +1007,7 @@ void ClientChaseMusicReset(int client) g_PlayerChaseMusicTimer[client][i] = null; g_PlayerChaseMusicString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { @@ -893,7 +1068,7 @@ void ClientMusicChaseStart(int client,int bossIndex) } return; } - if (MusicActive())//A boss is overriding the music. + if (MusicActive()) // A boss is overriding the music. { GetBossMusic(g_PlayerChaseMusicString[client][bossIndex],sizeof(g_PlayerChaseMusicString[][])); } @@ -940,7 +1115,7 @@ void ClientChaseMusicSeeReset(int client) g_PlayerChaseMusicSeeVolumes[client][i] = 0.0; g_PlayerChaseMusicSeeString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { @@ -1110,7 +1285,7 @@ Action Timer_PlayerFadeInMusic(Handle timer, any userid) if (g_PlayerMusicString[client][0] != '\0') { - EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); + EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_PlayerMusicVolume[client] >= g_PlayerMusicTargetVolume[client]) @@ -1143,7 +1318,7 @@ Action Timer_PlayerFadeOutMusic(Handle timer, any userid) if (g_PlayerMusicString[client][0] != '\0') { - EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); + EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_PlayerMusicVolume[client] <= 0.0) @@ -1178,6 +1353,11 @@ Action Timer_PlayerFadeInAlertMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.AlertMusics; + /*int oldBoss = g_PlayerAlertMusicOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerAlertMusicString[client][bossIndex], g_PlayerAlertMusicString[client][oldBoss], false) == 0) @@ -1195,7 +1375,7 @@ Action Timer_PlayerFadeInAlertMusic(Handle timer, any userid) if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerAlertMusicVolumes[client][bossIndex] >= 1.0) @@ -1230,6 +1410,11 @@ Action Timer_PlayerFadeOutAlertMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.AlertMusics; + int oldBoss = g_PlayerAlertMusicOldMaster[client]; int newBoss = g_PlayerAlertMusicMaster[client]; @@ -1250,7 +1435,7 @@ Action Timer_PlayerFadeOutAlertMusic(Handle timer, any userid) if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerAlertMusicVolumes[client][bossIndex] <= 0.0) @@ -1287,6 +1472,11 @@ Action Timer_PlayerFadeInIdleMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.IdleMusics; + /*int oldBoss = g_PlayerIdleMusicOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerIdleMusicString[client][bossIndex], g_PlayerIdleMusicString[client][oldBoss], false) == 0) @@ -1304,7 +1494,7 @@ Action Timer_PlayerFadeInIdleMusic(Handle timer, any userid) if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerIdleMusicVolumes[client][bossIndex] >= 1.0) @@ -1339,6 +1529,11 @@ Action Timer_PlayerFadeOutIdleMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.IdleMusics; + int oldBoss = g_PlayerIdleMusicOldMaster[client]; int newBoss = g_PlayerIdleMusicMaster[client]; @@ -1359,7 +1554,7 @@ Action Timer_PlayerFadeOutIdleMusic(Handle timer, any userid) if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerIdleMusicVolumes[client][bossIndex] <= 0.0) @@ -1396,6 +1591,11 @@ Action Timer_PlayerFadeInChaseMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseMusics; + /*int oldBoss = g_PlayerChaseMusicOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicString[client][bossIndex], g_PlayerChaseMusicString[client][oldBoss], false) == 0) @@ -1413,7 +1613,7 @@ Action Timer_PlayerFadeInChaseMusic(Handle timer, any userid) if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicVolumes[client][bossIndex] >= 1.0) @@ -1448,6 +1648,11 @@ Action Timer_PlayerFadeInChaseMusicSee(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseVisibleMusics; + /*int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicSeeString[client][bossIndex], g_PlayerChaseMusicSeeString[client][oldBoss], false) == 0) @@ -1465,7 +1670,7 @@ Action Timer_PlayerFadeInChaseMusicSee(Handle timer, any userid) if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] >= 1.0) @@ -1500,6 +1705,11 @@ Action Timer_PlayerFadeOutChaseMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseMusics; + int oldBoss = g_PlayerChaseMusicOldMaster[client]; int newBoss = g_PlayerChaseMusicMaster[client]; @@ -1520,7 +1730,7 @@ Action Timer_PlayerFadeOutChaseMusic(Handle timer, any userid) if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicVolumes[client][bossIndex] <= 0.0) @@ -1557,6 +1767,11 @@ Action Timer_PlayerFadeOutChaseMusicSee(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseVisibleMusics; + int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; int newBoss = g_PlayerChaseMusicSeeMaster[client]; @@ -1577,7 +1792,7 @@ Action Timer_PlayerFadeOutChaseMusicSee(Handle timer, any userid) if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] <= 0.0) @@ -1612,7 +1827,7 @@ Action Timer_PlayerFadeIn90sMusic(Handle timer, any userid) if (g_Player90sMusicString[client][0] != '\0') { - EmitSoundToClient(client, g_Player90sMusicString[client], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); + EmitSoundToClient(client, g_Player90sMusicString[client], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_Player90sMusicVolumes[client] >= 0.5) @@ -1654,7 +1869,7 @@ Action Timer_PlayerFadeOut90sMusic(Handle timer, any userid) if (buffer[0] != '\0') { - EmitSoundToClient(client, buffer, _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); + EmitSoundToClient(client, buffer, _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_Player90sMusicVolumes[client] <= 0.0) @@ -1666,17 +1881,17 @@ Action Timer_PlayerFadeOut90sMusic(Handle timer, any userid) return Plugin_Continue; } -bool ClientHasMusicFlag(int client,int flag) +bool ClientHasMusicFlag(int client, int flag) { - return !!(g_PlayerMusicFlags[client] & flag); + return (g_PlayerMusicFlags[client] & flag) != 0; } -bool ClientHasMusicFlag2(int value,int flag) +bool ClientHasMusicFlag2(int value, int flag) { - return !!(value & flag); + return (value & flag) != 0; } -void ClientAddMusicFlag(int client,int flag) +void ClientAddMusicFlag(int client, int flag) { if (!ClientHasMusicFlag(client, flag)) { @@ -1684,7 +1899,7 @@ void ClientAddMusicFlag(int client,int flag) } } -void ClientRemoveMusicFlag(int client,int flag) +void ClientRemoveMusicFlag(int client, int flag) { if (ClientHasMusicFlag(client, flag)) { diff --git a/addons/sourcemod/scripting/sf2/client/new_music.sp b/addons/sourcemod/scripting/sf2/client/new_music.sp new file mode 100644 index 00000000..1bc7e0a9 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/new_music.sp @@ -0,0 +1,1622 @@ +#pragma newdecls required +#pragma semicolon 1 + +enum SF2MusicFadeState +{ + SF2MusicFadeState_None = 0, + SF2MusicFadeState_FadeIn = 1, + SF2MusicFadeState_FadeOut = 2 +} + +enum SF2BossMusicState +{ + SF2BossMusicState_Invalid = -1, + SF2BossMusicState_Idle = 0, + SF2BossMusicState_Alert = 1, + SF2BossMusicState_Chase = 2, + SF2BossMusicState_Max = 3 +} + +enum SF2BossMusicTrackType +{ + SF2BossMusicTrackType_Unknown = 0, + SF2BossMusicTrackType_Normal, + SF2BossMusicTrackType_Visible, + SF2BossMusicTrackType_Global, + SF2BossMusicTrackType_Max +} + +enum SF2BossGlobalMusicShuffleType +{ + SF2BossGlobalMusicShuffleType_None = 0, + SF2BossGlobalMusicShuffleType_Order, + SF2BossGlobalMusicShuffleType_Random, + SF2BossGlobalMusicShuffleType_RandomOrder, + SF2BossGlobalMusicShuffleType_Range +} + +methodmap ProfileGlobalTracks < ProfileObject +{ + public SF2BossMusicState GetPriority(int difficulty = Difficulty_Normal) + { + return view_as(this.GetDifficultyInt("priority", difficulty, view_as(SF2BossMusicState_Max))); + } + + public SF2BossGlobalMusicShuffleType GetShuffleType(int difficulty) + { + char value[64]; + this.GetDifficultyString("shuffle_type", difficulty, value, sizeof(value), "none"); + + if (strcmp(value, "order", false) == 0) + { + return SF2BossGlobalMusicShuffleType_Order; + } + else if (strcmp(value, "random", false) == 0) + { + return SF2BossGlobalMusicShuffleType_Random; + } + else if (strcmp(value, "random_order", false) == 0) + { + return SF2BossGlobalMusicShuffleType_RandomOrder; + } + else if (strcmp(value, "pages", false) == 0) + { + return SF2BossGlobalMusicShuffleType_Range; + } + return SF2BossGlobalMusicShuffleType_None; + } + + public KeyMap_Array GetTracks() + { + return this.GetArray("tracks"); + } + + public void Precache() + { + this.ConvertSectionsSectionToArray("tracks"); + if (this.GetTracks() != null) + { + for (int i = 0; i < this.GetTracks().Length; i++) + { + ProfileGlobalTrack track = view_as(this.GetTracks().GetSection(i)); + if (track == null) + { + continue; + } + track.Precache(); + } + } + } +} + +methodmap ProfileGlobalTrack < ProfileObject +{ + public bool IsEnabled(int difficulty) + { + if (this == null) + { + return false; + } + return this.GetDifficultyBool("enabled", difficulty, true); + } + + public void GetPath(int difficulty, char[] buffer, int bufferSize) + { + this.GetDifficultyString("path", difficulty, buffer, bufferSize); + } + + public float GetVolume(int difficulty) + { + return this.GetDifficultyFloat("volume", difficulty, SNDVOL_NORMAL); + } + + public int GetPitch(int difficulty) + { + return this.GetDifficultyInt("pitch", difficulty, SNDPITCH_NORMAL); + } + + public float GetLength(int difficulty) + { + return this.GetDifficultyFloat("length", difficulty, -1.0); + } + + public float GetMinPageRange(int difficulty) + { + return this.GetDifficultyFloat("min_range", difficulty, 0.0); + } + + public float GetMaxPageRange(int difficulty) + { + return this.GetDifficultyFloat("max_range", difficulty, 0.0); + } + + public void Precache() + { + char path[PLATFORM_MAX_PATH]; + for (int i = 0; i < Difficulty_Max; i++) + { + this.GetPath(i, path, sizeof(path)); + if (path[0] != '\0') + { + PrecacheSound2(path, g_FileCheckConVar.BoolValue); + } + } + } +} + +methodmap ProfileMusic < ProfileSound +{ + public float GetRadius(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("radius", difficulty, 850.0); + } + + public float GetFadeSpeed(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("fade_speed", difficulty, 1.0); + } + + public bool CanInstantFade(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyBool("instant_fade", difficulty, false); + } + + public SF2BossMusicState GetGlobalMusicPriority(int difficulty = Difficulty_Normal) + { + return view_as(this.GetDifficultyInt("priority", difficulty, view_as(SF2BossMusicState_Max))); + } +} + +enum struct MusicState +{ + bool IsNull; + char MusicPath[PLATFORM_MAX_PATH]; + float MaxVolume; + float CurrentVolume; + int Pitch; + float FadeSpeed; + SF2MusicFadeState FadeState; + float Length; + bool StopAfterFade; + + void Init() + { + this.IsNull = false; + this.MusicPath[0] = '\0'; + this.MaxVolume = 1.0; + this.CurrentVolume = 0.0; + this.Pitch = 100; + this.FadeSpeed = 1.0; + this.FadeState = SF2MusicFadeState_None; + this.Length = -1.0; + this.StopAfterFade = false; + } + + void FadeIn(SF2_BasePlayer client, float delta, bool instant = false) + { + if (this.IsNull) + { + return; + } + + if (this.MusicPath[0] == '\0') + { + return; + } + + if (this.CurrentVolume >= this.MaxVolume) + { + this.SetFadeState(SF2MusicFadeState_None); + return; + } + + this.CurrentVolume += this.FadeSpeed * delta; + if (instant) + { + this.CurrentVolume = this.MaxVolume; + } + if (this.CurrentVolume >= this.MaxVolume) + { + this.CurrentVolume = this.MaxVolume; + this.SetFadeState(SF2MusicFadeState_None); + } + float volume = this.CurrentVolume * g_PlayerPreferences[client.index].PlayerPreference_MusicVolume; + EmitSoundToClient(client.index, this.MusicPath, _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, volume, this.Pitch); + } + + void FadeOut(SF2_BasePlayer client, float delta, bool instant = false) + { + if (this.IsNull) + { + return; + } + + if (this.MusicPath[0] == '\0') + { + return; + } + + this.CurrentVolume -= this.FadeSpeed * delta; + if (instant) + { + this.CurrentVolume = 0.0; + } + if (this.CurrentVolume <= 0.0) + { + this.CurrentVolume = 0.0; + this.SetFadeState(SF2MusicFadeState_None); + if (this.StopAfterFade) + { + EmitSoundToClient(client.index, this.MusicPath, _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, 0.015, this.Pitch); + this.Stop(client); + return; + } + } + float volume = this.CurrentVolume * g_PlayerPreferences[client.index].PlayerPreference_MusicVolume; + EmitSoundToClient(client.index, this.MusicPath, _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, volume, this.Pitch); + } + + void Stop(SF2_BasePlayer client) + { + if (this.IsNull) + { + return; + } + + if (this.MusicPath[0] == '\0') + { + return; + } + + StopSound(client.index, MUSIC_CHAN, this.MusicPath); + this.SetFadeState(SF2MusicFadeState_None); + } + + void Reset() + { + if (this.MusicPath[0] != '\0') + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + continue; + } + + if (!client.IsEliminated && client.HasEscaped) + { + continue; + } + StopSound(client.index, MUSIC_CHAN, this.MusicPath); + } + } + this.Init(); + } + + void SetFadeState(SF2MusicFadeState state) + { + if (this.IsNull) + { + return; + } + + this.FadeState = state; + } + + bool IsValid() + { + return this.MusicPath[0] != '\0'; + } +} + +enum struct PageMusicInfo +{ + int MinRequiredPages; + int MaxRequiredPages; + MusicState State; +} + +static ArrayList g_PageMusicRanges = null; +static ArrayList g_BossesWithGlobalMusic = null; +static bool g_CanSwapBackToDefault[MAXTF2PLAYERS] = { true, ... }; +static float g_CurrentTrackLength[MAXTF2PLAYERS]; +static StringMap g_QueuedThemes[MAXTF2PLAYERS]; // Fade out queue +static StringMap g_LayeredThemes[MAXTF2PLAYERS]; // Layered tracks +static MusicState g_CurrentTheme[MAXTF2PLAYERS]; +static MusicState g_DefaultMusicState; // Pages +static MusicState g_OldDefaultMusicState; +static MusicState g_GlobalMusicState[MAXTF2PLAYERS]; // Boss global theme +static MusicState g_OldGlobalMusicState[MAXTF2PLAYERS]; +static MusicState g_NullMusicState; +static float g_LastMusicThink; +static int g_PlayerActiveBoss[MAXTF2PLAYERS] = { -1, ... }; +static int g_PlayerActiveLayerBoss[MAXTF2PLAYERS] = { -1, ... }; +static SF2BossMusicState g_PlayerActiveBossState[MAXTF2PLAYERS] = { SF2BossMusicState_Invalid, ... }; +static SF2BossMusicState g_PlayerActiveLayerBossState[MAXTF2PLAYERS] = { SF2BossMusicState_Invalid, ... }; +static SF2BossMusicState g_GlobalMusicUpdateLimit[MAXTF2PLAYERS] = { SF2BossMusicState_Max, ... }; +static int g_GlobalMusicTrackMasterID = -1; +static ArrayList g_CurrentTracks; +static int g_CurrentTrackIndex; +static SF2BossGlobalMusicShuffleType g_CurrentTrackShuffleType; + +void SetupNewMusic() +{ + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnConfigsExecutedPFwd.AddFunction(null, ConfigsExecuted); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPostInitMapEntitiesPFwd.AddFunction(null, OnPostInitMapEntities); + g_OnPostInitNewGamePFwd.AddFunction(null, OnPostInitNewGame); + g_OnPageCountChangedPFwd.AddFunction(null, OnPageCountChanged); + g_OnBossAddedPFwd.AddFunction(null, OnBossAdded); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyChange); + + g_PageMusicRanges = new ArrayList(sizeof(PageMusicInfo)); + g_BossesWithGlobalMusic = new ArrayList(); + g_NullMusicState.Init(); + g_NullMusicState.IsNull = true; +} + +static void OnGamemodeEnd() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + ClearMusic(client); + } +} + +static void MapStart() +{ + g_LastMusicThink = -1.0; + RequestFrame(MusicThink); + g_BossesWithGlobalMusic.Clear(); +} + +static void ConfigsExecuted() +{ + g_GlobalMusicTrackMasterID = -1; +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClearMusic(client); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClearMusic(client); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + g_QueuedThemes[client.index] = null; + g_LayeredThemes[client.index] = null; + ClearMusic(client); +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClearMusic(client); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + if (g_QueuedThemes[client.index] != null) + { + delete g_QueuedThemes[client.index]; + } + + if (g_LayeredThemes[client.index] != null) + { + delete g_LayeredThemes[client.index]; + } +} + +static void OnPostInitMapEntities() +{ + GetPageMusicRanges(); +} + +static void OnPostInitNewGame() +{ + UpdatePageMusic(); +} + +static void OnPageCountChanged(int pageCount, int oldPageCount) +{ + UpdatePageMusic(); +} + +static void OnBossAdded(SF2NPC_BaseNPC npc) +{ + if (SF_IsRaidMap() || SF_IsRenevantMap() || SF_IsProxyMap()) + { + return; + } + + if (npc.GetProfileDataEx().IsPvEBoss) + { + return; + } + + if (npc.IsCopy) + { + return; + } + + int oldId = g_GlobalMusicTrackMasterID; + char path[PLATFORM_MAX_PATH]; + GetBossMusicTrack(npc, SF2BossMusicTrackType_Global, path, sizeof(path)); + + if (path[0] != '\0') + { + g_BossesWithGlobalMusic.Push(npc.UniqueID); + if (NPCGetFromUniqueID(oldId) == -1) + { + UpdateBossGlobalTracks(); + } + } +} + +static void OnBossRemoved(SF2NPC_BaseNPC npc) +{ + if (SF_IsRaidMap() || SF_IsRenevantMap() || SF_IsProxyMap()) + { + return; + } + + if (npc.GetProfileDataEx().IsPvEBoss) + { + return; + } + + if (npc.IsCopy) + { + return; + } + + int index; + if ((index = g_BossesWithGlobalMusic.FindValue(npc.UniqueID)) != -1) + { + g_BossesWithGlobalMusic.Erase(index); + } + + if (g_GlobalMusicTrackMasterID == npc.UniqueID) + { + UpdateBossGlobalTracks(); + } +} + +static void OnDifficultyChange(int oldDifficulty, int newDifficulty) +{ + if (SF_IsRaidMap() || SF_IsRenevantMap() || SF_IsProxyMap()) + { + return; + } + + g_BossesWithGlobalMusic.Clear(); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + if (npc.GetProfileDataEx().IsPvEBoss) + { + continue; + } + + char path[PLATFORM_MAX_PATH]; + GetBossMusicTrack(npc, SF2BossMusicTrackType_Global, path, sizeof(path)); + + if (path[0] != '\0') + { + g_BossesWithGlobalMusic.Push(npc.UniqueID); + UpdateBossGlobalTracks(); + } + } +} + +static void GetPageMusicRanges() +{ + g_PageMusicRanges.Clear(); + + char name[64], path[PLATFORM_MAX_PATH]; + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "ambient_generic")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); + + if (name[0] != '\0' && !StrContains(name, "sf2_page_music_", false)) + { + ReplaceString(name, sizeof(name), "sf2_page_music_", "", false); + + char pageRanges[2][32]; + ExplodeString(name, "-", pageRanges, 2, 32); + + PageMusicInfo musicInfo; + GetEntPropString(ent, Prop_Data, "m_iszSound", path, sizeof(path)); + + if (path[0] == '\0') + { + continue; + } + + if (path[0] != '#' && path[1] != '#') + { + Format(path, sizeof(path), "#%s", path); + } + + PrecacheSound(path, true); + + musicInfo.MinRequiredPages = StringToInt(pageRanges[0]); + musicInfo.MaxRequiredPages = StringToInt(pageRanges[1]); + musicInfo.State.MusicPath = path; + + g_PageMusicRanges.PushArray(musicInfo); + } + } + + /*while ((ent = FindEntityByClassname(ent, "sf2_info_page_music")) != -1) + { + SF2PageMusicEntity pageMusic = SF2PageMusicEntity(ent); + if (!pageMusic.IsValid()) + { + continue; + } + + pageMusic.InsertRanges(g_PageMusicRanges); + } + + // precache + if (g_PageMusicRanges.Length > 0) + { + char path[PLATFORM_MAX_PATH]; + + for (int i = 0; i < g_PageMusicRanges.Length; i++) + { + ent = EntRefToEntIndex(g_PageMusicRanges.Get(i)); + if (!ent || ent == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2PageMusicEntity pageMusic = SF2PageMusicEntity(ent); + if (pageMusic.IsValid()) + { + // Don't do anything; entity already precached its own music. + } + else + { + GetEntPropString(ent, Prop_Data, "m_iszSound", path, sizeof(path)); + if (path[0] != '\0') + { + PrecacheSound(path); + } + } + } + }*/ + + if (g_PageMusicRanges.Length == 0 && g_PageMax > 0) + { + int page25Pct = RoundToFloor(float(g_PageMax) * 0.25); + int page50Pct = RoundToFloor(float(g_PageMax) * 0.5); + int page75Pct = RoundToFloor(float(g_PageMax) * 0.75); + + PageMusicInfo musicInfo; + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = 1; + musicInfo.MaxRequiredPages = page25Pct; + strcopy(path, sizeof(path), MUSIC_GOTPAGES1_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = page25Pct + 1; + musicInfo.MaxRequiredPages = page50Pct; + strcopy(path, sizeof(path), MUSIC_GOTPAGES2_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = page50Pct + 1; + musicInfo.MaxRequiredPages = page75Pct; + strcopy(path, sizeof(path), MUSIC_GOTPAGES3_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = page75Pct + 1; + musicInfo.MaxRequiredPages = g_PageMax; + strcopy(path, sizeof(path), MUSIC_GOTPAGES4_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + } + + LogSF2Message("Loaded page music ranges successfully!"); +} + +static void ClearMusic(SF2_BasePlayer client) +{ + g_CanSwapBackToDefault[client.index] = true; + g_CurrentTrackLength[client.index] = 0.0; + g_PlayerActiveBoss[client.index] = -1; + g_PlayerActiveLayerBoss[client.index] = -1; + g_PlayerActiveBossState[client.index] = SF2BossMusicState_Invalid; + g_PlayerActiveLayerBossState[client.index] = SF2BossMusicState_Invalid; + + if (g_QueuedThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_QueuedThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState state; + g_QueuedThemes[client.index].GetArray(key, state, sizeof(state)); + state.Stop(client); + } + delete g_QueuedThemes[client.index]; + delete snapshot; + } + + if (g_LayeredThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState state; + g_LayeredThemes[client.index].GetArray(key, state, sizeof(state)); + state.Stop(client); + } + delete g_LayeredThemes[client.index]; + delete snapshot; + } + + g_CurrentTheme[client.index].Stop(client); + g_CurrentTheme[client.index] = g_NullMusicState; +} + +static void UpdatePageMusic() +{ + if (g_BossesWithGlobalMusic.Length != 0) + { + return; + } + + MusicState oldState; + oldState = g_DefaultMusicState; + g_OldDefaultMusicState = g_DefaultMusicState; + g_DefaultMusicState = g_NullMusicState; + + if (g_PageCount < g_PageMax || !g_RoundStopPageMusicOnEscape) + { + PageMusicInfo info; + + for (int i = 0; i < g_PageMusicRanges.Length; i++) + { + g_PageMusicRanges.GetArray(i, info); + if (g_PageCount >= info.MinRequiredPages && g_PageCount <= info.MaxRequiredPages) + { + g_DefaultMusicState = info.State; + if (strcmp(oldState.MusicPath, info.State.MusicPath, false) != 0) + { + for (int i2 = 1; i2 <= MaxClients; i2++) + { + SF2_BasePlayer client = SF2_BasePlayer(i2); + if (g_CanSwapBackToDefault[client.index]) + { + AddMusicToQueue(client, g_DefaultMusicState); + } + } + } + break; + } + } + } +} + +static void UpdateBossGlobalTracks() +{ + if (g_BossesWithGlobalMusic.Length == 0) + { + UpdatePageMusic(); + if (g_CurrentTracks != null) + { + delete g_CurrentTracks; + } + g_CurrentTrackIndex = 0; + g_GlobalMusicTrackMasterID = -1; + g_CurrentTrackShuffleType = SF2BossGlobalMusicShuffleType_None; + return; + } + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + UpdateBossGlobalTrackForPlayer(client); + } + + UpdatePageMusic(); +} + +static void UpdateBossGlobalTrackForPlayer(SF2_BasePlayer client) +{ + if (!client.IsValid) + { + return; + } + g_OldGlobalMusicState[client.index] = g_GlobalMusicState[client.index]; + g_OldGlobalMusicState[client.index].StopAfterFade = true; + + for (int i = 0; i < g_BossesWithGlobalMusic.Length; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_BossesWithGlobalMusic.Get(i))); + if (!npc.IsValid()) + { + g_BossesWithGlobalMusic.Erase(i); + i--; + continue; + } + + char path[PLATFORM_MAX_PATH]; + float volume = 1.0, length = -1.0; + int pitch = 100; + SF2BossMusicState priority = SF2BossMusicState_Max; + GetBossMusicTrack(npc, SF2BossMusicTrackType_Global, path, sizeof(path), volume, pitch, .priority = priority, .length = length, .client = client); + + if (path[0] != '\0') + { + MusicState state; + state.Init(); + state.MusicPath = path; + state.MaxVolume = volume; + state.Pitch = pitch; + state.Length = length; + g_GlobalMusicState[client.index] = state; + g_GlobalMusicUpdateLimit[client.index] = priority; + g_CurrentTrackLength[client.index] = 0.0; + g_CurrentTheme[client.index].StopAfterFade = true; + + AddMusicToQueue(client, g_GlobalMusicState[client.index]); + g_OldGlobalMusicState[client.index].Init(); + g_OldGlobalMusicState[client.index].IsNull = true; + return; + } + } + + g_GlobalMusicState[client.index] = g_NullMusicState; +} + +static void AddMusicToQueue(SF2_BasePlayer client, MusicState state) +{ + if (!client.IsValid) + { + return; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + return; + } + + if (!client.IsEliminated && client.HasEscaped) + { + return; + } + + MusicState currentState; + currentState = g_CurrentTheme[client.index]; + if (strcmp(currentState.MusicPath, state.MusicPath, false) == 0) + { + return; + } + + if (!currentState.IsNull) + { + if (g_QueuedThemes[client.index] == null) + { + g_QueuedThemes[client.index] = new StringMap(); + } + else + { + MusicState temp; + if (g_QueuedThemes[client.index].SetArray(state.MusicPath, temp, sizeof(temp))) + { + state.CurrentVolume = temp.CurrentVolume; + } + } + if (strcmp(currentState.MusicPath, g_OldGlobalMusicState[client.index].MusicPath, false) == 0) + { + currentState.StopAfterFade = true; + } + currentState.SetFadeState(SF2MusicFadeState_FadeOut); + g_QueuedThemes[client.index].SetArray(currentState.MusicPath, currentState, sizeof(currentState)); + if (g_LayeredThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + g_LayeredThemes[client.index].GetArray(key, currentState, sizeof(currentState)); + currentState.SetFadeState(SF2MusicFadeState_FadeOut); + g_QueuedThemes[client.index].SetArray(currentState.MusicPath, currentState, sizeof(currentState)); + } + delete g_LayeredThemes[client.index]; + delete snapshot; + } + g_QueuedThemes[client.index].Remove(state.MusicPath); + } + state.SetFadeState(SF2MusicFadeState_FadeIn); + g_CurrentTheme[client.index] = state; + g_CurrentTrackLength[client.index] = 0.0; +} + +static void AddLayeredTrack(SF2_BasePlayer client, MusicState state, int index) +{ + if (!client.IsValid) + { + return; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + return; + } + + if (!client.IsEliminated && client.HasEscaped) + { + return; + } + + if (g_QueuedThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_QueuedThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState oldState; + g_QueuedThemes[client.index].GetArray(key, oldState, sizeof(oldState)); + if (strcmp(oldState.MusicPath, state.MusicPath, false) == 0) + { + g_QueuedThemes[client.index].Remove(key); + } + } + } + + char intString[16]; + IntToString(index, intString, sizeof(intString)); + if (g_LayeredThemes[client.index] == null) + { + g_LayeredThemes[client.index] = new StringMap(); + state.SetFadeState(SF2MusicFadeState_FadeIn); + g_LayeredThemes[client.index].SetArray(intString, state, sizeof(state)); + } + else + { + RemoveLayeredTracks(client); + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + bool exists = false; + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState oldState; + g_LayeredThemes[client.index].GetArray(key, oldState, sizeof(oldState)); + if (strcmp(oldState.MusicPath, state.MusicPath, false) == 0) + { + oldState.SetFadeState(SF2MusicFadeState_FadeIn); + g_LayeredThemes[client.index].SetArray(key, oldState, sizeof(oldState)); + exists = true; + continue; + } + } + delete snapshot; + if (!exists) + { + state.SetFadeState(SF2MusicFadeState_FadeIn); + g_LayeredThemes[client.index].SetArray(intString, state, sizeof(state)); + } + } +} + +static void RemoveLayeredTracks(SF2_BasePlayer client, int index = -1) +{ + if (!client.IsValid) + { + return; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + return; + } + + if (!client.IsEliminated && client.HasEscaped) + { + return; + } + + if (g_LayeredThemes[client.index] == null) + { + return; + } + + char intString[16]; + IntToString(index, intString, sizeof(intString)); + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState oldState; + g_LayeredThemes[client.index].GetArray(key, oldState, sizeof(oldState)); + oldState.SetFadeState(SF2MusicFadeState_FadeOut); + g_LayeredThemes[client.index].SetArray(key, oldState, sizeof(oldState)); + } + delete snapshot; +} + +static void MusicThink() +{ + float gameTime = GetGameTime(); + float delta = g_LastMusicThink < 0.0 ? 0.0 : gameTime - g_LastMusicThink; + g_LastMusicThink = gameTime; + bool globalMusic = g_BossesWithGlobalMusic.Length > 0; + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + continue; + } + + if (!client.IsEliminated && client.HasEscaped) + { + continue; + } + + bool dontAdd = true; + + if (g_CurrentTheme[client.index].IsNull) + { + AddMusicToQueue(client, globalMusic ? g_GlobalMusicState[client.index] : g_DefaultMusicState); + } + + if (globalMusic) + { + if (g_GlobalMusicState[client.index].Length > 0.0 && g_CurrentTrackLength[client.index] > g_GlobalMusicState[client.index].Length) + { + UpdateBossGlobalTrackForPlayer(client); + } + } + + if (g_CurrentTheme[client.index].FadeState == SF2MusicFadeState_FadeIn) + { + g_CurrentTheme[client.index].FadeIn(client, delta); + } + + if (g_QueuedThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_QueuedThemes[client.index].Snapshot(); + for (int i2 = 0; i2 < snapshot.Length; i2++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i2, key, sizeof(key)); + MusicState state; + g_QueuedThemes[client.index].GetArray(key, state, sizeof(state)); + if (globalMusic && strcmp(state.MusicPath, g_GlobalMusicState[client.index].MusicPath, false) == 0 && state.FadeState == SF2MusicFadeState_FadeOut) + { + // The global music track still lingers as fading out, keep adding to the delta to avoid weird offsets + dontAdd = false; + } + if (strcmp(state.MusicPath, g_CurrentTheme[client.index].MusicPath, false) == 0) + { + g_QueuedThemes[client.index].Remove(key); + g_CurrentTheme[client.index].CurrentVolume = state.CurrentVolume; + continue; + } + if (state.FadeState == SF2MusicFadeState_FadeOut) + { + state.FadeOut(client, delta); + // Seriously, why the fuck can I not get reference values out of arrays? + g_QueuedThemes[client.index].SetArray(key, state, sizeof(state)); + } + else + { + g_QueuedThemes[client.index].Remove(key); + } + } + if (snapshot.Length == 0) + { + delete g_QueuedThemes[client.index]; + } + delete snapshot; + } + else + { + if (globalMusic) + { + if (strcmp(g_CurrentTheme[client.index].MusicPath, g_GlobalMusicState[client.index].MusicPath, false) != 0) + { + // Our music is not the same as the global music, stop adding to the delta time + dontAdd = true; + } + else + { + dontAdd = false; + } + } + } + + if (!dontAdd) + { + g_CurrentTrackLength[client.index] += delta; + } + + if (g_LayeredThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i2 = 0; i2 < snapshot.Length; i2++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i2, key, sizeof(key)); + MusicState state; + g_LayeredThemes[client.index].GetArray(key, state, sizeof(state)); + if (state.FadeState == SF2MusicFadeState_FadeIn) + { + state.FadeIn(client, delta); + g_LayeredThemes[client.index].SetArray(key, state, sizeof(state)); + } + else if (state.FadeState == SF2MusicFadeState_FadeOut) + { + state.FadeOut(client, delta); + g_LayeredThemes[client.index].SetArray(key, state, sizeof(state)); + } + } + delete snapshot; + } + + if (SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsRenevantMap()) + { + continue; + } + + float clientPos[3], bossPos[3], targetPos[3]; + client.GetAbsOrigin(clientPos); + char path[PLATFORM_MAX_PATH]; + float volume = 1.0, radius = 850.0; + int pitch = 100; + g_CanSwapBackToDefault[client.index] = true; + int priorityBoss = -1, layeredBoss = -1; + int priorityState = ConvertMusicStateToBossState(g_PlayerActiveBossState[client.index]); + + for (int i2 = MAX_BOSSES; i2 >= 0; i2--) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i2); + path[0] = '\0'; + if (!controller.IsValid()) + { + continue; + } + + if (controller.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + BaseBossProfile data = SF2NPC_BaseNPC(i).GetProfileDataEx(); + if (data.IsPvEBoss) + { + continue; + } + + if (data.Type != SF2BossType_Chaser) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + + if (chaser.State == STATE_DEATH) + { + continue; + } + + chaser.GetAbsOrigin(bossPos); + + GetBossMusicTrack(controller, SF2BossMusicTrackType_Normal, path, sizeof(path), volume, pitch, radius); + if (path[0] == '\0') + { + continue; + } + + CBaseEntity target = chaser.Target; + + if (chaser.State != priorityState) + { + if ((priorityState == STATE_CHASE || priorityState == STATE_ATTACK || priorityState == STATE_STUN) + && chaser.State <= STATE_CHASE) + { + continue; + } + + if (priorityState == STATE_ALERT && chaser.State <= priorityState) + { + continue; + } + } + + if (GetVectorDistance(clientPos, bossPos, true) <= SquareFloat(radius)) + { + priorityBoss = i2; + priorityState = chaser.State; + continue; + } + + if (chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN) + { + if (target.IsValid()) + { + target.GetAbsOrigin(targetPos); + if (target.index == client.index || GetVectorDistance(clientPos, targetPos, true) <= SquareFloat(radius)) + { + priorityBoss = i2; + priorityState = chaser.State; + continue; + } + } + } + + if (controller.Path.IsValid()) + { + controller.Path.GetEndPosition(targetPos); + if (GetVectorDistance(clientPos, targetPos, true) <= SquareFloat(radius)) + { + priorityBoss = i2; + priorityState = chaser.State; + continue; + } + } + } + + SF2NPC_BaseNPC priorityController = SF2NPC_BaseNPC(priorityBoss); + SF2BossMusicState newState = ConvertBossStateToMusicState(priorityState); + if (priorityController.IsValid() && (!globalMusic || (globalMusic && newState >= g_GlobalMusicUpdateLimit[client.index]))) + { + g_CanSwapBackToDefault[client.index] = false; + int oldBoss = g_PlayerActiveBoss[client.index]; + SF2BossMusicState oldState = g_PlayerActiveBossState[client.index]; + if (oldBoss != priorityBoss || (newState != SF2BossMusicState_Invalid && oldState != ConvertBossStateToMusicState(priorityState))) + { + GetBossMusicTrack(priorityController, SF2BossMusicTrackType_Normal, path, sizeof(path), volume, pitch, radius); + MusicState musicState; + musicState.Init(); + musicState.MaxVolume = volume; + musicState.Pitch = pitch; + musicState.MusicPath = path; + AddMusicToQueue(client, musicState); + } + g_PlayerActiveBoss[client.index] = priorityBoss; + g_PlayerActiveBossState[client.index] = newState; + } + else + { + g_PlayerActiveBoss[client.index] = -1; + g_PlayerActiveBossState[client.index] = SF2BossMusicState_Invalid; + AddMusicToQueue(client, globalMusic ? g_GlobalMusicState[client.index] : g_DefaultMusicState); + } + + for (int i2 = 0; i2 < MAX_BOSSES; i2++) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i2); + path[0] = '\0'; + if (!controller.IsValid()) + { + continue; + } + + if (controller.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + BaseBossProfile data = SF2NPC_BaseNPC(i).GetProfileDataEx(); + if (data.IsPvEBoss) + { + continue; + } + + if (data.Type != SF2BossType_Chaser) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + + if (chaser.State == STATE_DEATH || chaser.State == STATE_STUN) + { + continue; + } + + GetBossMusicTrack(controller, SF2BossMusicTrackType_Visible, path, sizeof(path), volume, pitch, radius); + if (path[0] == '\0') + { + continue; + } + + if (priorityState != -1) + { + if ((priorityState == STATE_CHASE || priorityState == STATE_ATTACK || priorityState == STATE_STUN) + && chaser.State < STATE_CHASE) + { + continue; + } + + if (priorityState == STATE_ALERT && chaser.State < priorityState) + { + continue; + } + } + + if (PlayerCanSeeSlender(client.index, i2, false)) + { + layeredBoss = i2; + priorityState = chaser.State; + break; + } + } + + SF2NPC_BaseNPC priorityLayer = SF2NPC_BaseNPC(layeredBoss); + newState = ConvertBossStateToMusicState(priorityState); + if (priorityLayer.IsValid() && (!globalMusic || (globalMusic && newState >= g_GlobalMusicUpdateLimit[client.index]))) + { + int oldBoss = g_PlayerActiveLayerBoss[client.index]; + SF2BossMusicState oldState = g_PlayerActiveLayerBossState[client.index]; + if (oldBoss != priorityBoss || (newState != SF2BossMusicState_Invalid && oldState != ConvertBossStateToMusicState(priorityState))) + { + MusicState musicState; + musicState.Init(); + musicState.MaxVolume = volume; + musicState.Pitch = pitch; + musicState.MusicPath = path; + AddLayeredTrack(client, musicState, layeredBoss); + } + g_PlayerActiveLayerBoss[client.index] = layeredBoss; + g_PlayerActiveLayerBossState[client.index] = newState; + } + else if (!priorityLayer.IsValid()) + { + RemoveLayeredTracks(client, g_PlayerActiveLayerBoss[client.index]); + g_PlayerActiveLayerBoss[client.index] = -1; + g_PlayerActiveLayerBossState[client.index] = SF2BossMusicState_Invalid; + } + } + + if (IsRoundInEscapeObjective() && g_RoundStopPageMusicOnEscape) + { + g_OldDefaultMusicState.Reset(); + } + + RequestFrame(MusicThink); +} + +static SF2BossMusicState ConvertBossStateToMusicState(int state) +{ + if (state == STATE_IDLE) + { + return SF2BossMusicState_Idle; + } + else if (state == STATE_ALERT) + { + return SF2BossMusicState_Alert; + } + else if (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) + { + return SF2BossMusicState_Chase; + } + return SF2BossMusicState_Invalid; +} + +static int ConvertMusicStateToBossState(SF2BossMusicState state) +{ + if (state == SF2BossMusicState_Idle) + { + return STATE_IDLE; + } + else if (state == SF2BossMusicState_Alert) + { + return STATE_ALERT; + } + else if (state == SF2BossMusicState_Chase) + { + return STATE_CHASE; + } + return -1; +} + +static void GetBossMusicTrack(SF2NPC_BaseNPC controller, + SF2BossMusicTrackType trackType, + char[] buffer, + int bufferSize, + float& volume = 1.0, + int& pitch = 100, + float& radius = 850.0, + bool& instantFade = false, + float& fadeSpeed = 1.0, + SF2BossMusicState& priority = SF2BossMusicState_Max, + float& length = -1.0, + SF2BossGlobalMusicShuffleType& shuffleType = SF2BossGlobalMusicShuffleType_None, + SF2_BasePlayer client = SF2_INVALID_PLAYER) +{ + if (!controller.IsValid()) + { + return; + } + + BaseBossProfile data = controller.GetProfileDataEx(); + if (data.IsPvEBoss) + { + return; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + + if (chaser.IsValid() && chaser.State == STATE_DEATH) + { + return; + } + + ProfileMusic soundInfo; + ProfileGlobalTracks globalTrack; + int difficulty = controller.Difficulty; + switch (trackType) + { + case SF2BossMusicTrackType_Normal: + { + if (!chaser.IsValid()) + { + return; + } + + if (data.Type != SF2BossType_Chaser) + { + return; + } + + switch (chaser.State) + { + case STATE_IDLE: + { + soundInfo = view_as(data).GetIdleMusics(); + } + + case STATE_ALERT: + { + soundInfo = view_as(data).GetAlertMusics(); + } + + case STATE_CHASE, STATE_ATTACK, STATE_STUN: + { + soundInfo = view_as(data).GetChaseMusics(); + } + } + } + + case SF2BossMusicTrackType_Visible: + { + if (!chaser.IsValid()) + { + return; + } + + if (data.Type != SF2BossType_Chaser) + { + return; + } + + switch (chaser.State) + { + case STATE_IDLE: + { + soundInfo = view_as(data).GetVisibleIdleMusics(); + } + + case STATE_ALERT: + { + soundInfo = view_as(data).GetVisibleAlertMusics(); + } + + case STATE_CHASE, STATE_ATTACK: + { + soundInfo = view_as(data).GetVisibleChaseMusics(); + } + } + } + + case SF2BossMusicTrackType_Global: + { + globalTrack = data.GetGlobalTracks(); + if (globalTrack == null) + { + return; + } + KeyMap_Array tracks = globalTrack.GetTracks(); + if (tracks == null || tracks.Length == 0) + { + return; + } + priority = globalTrack.GetPriority(difficulty); + shuffleType = globalTrack.GetShuffleType(difficulty); + bool changed = false; + SF2BossGlobalMusicShuffleType oldType = g_CurrentTrackShuffleType; + if (g_GlobalMusicTrackMasterID != controller.UniqueID) + { + if (g_CurrentTracks != null) + { + delete g_CurrentTracks; + } + g_CurrentTrackIndex = 0; + g_GlobalMusicTrackMasterID = controller.UniqueID; + g_CurrentTrackShuffleType = SF2BossGlobalMusicShuffleType_Random; + } + + if (g_CurrentTracks == null) + { + for (int i = 0; i < tracks.Length; i++) + { + ProfileGlobalTrack track = view_as(tracks.GetSection(i)); + if (track == null) + { + continue; + } + if (!track.IsEnabled(difficulty)) + { + continue; + } + if (g_CurrentTracks == null) + { + g_CurrentTracks = new ArrayList(); + } + g_CurrentTracks.Push(track); + } + } + + if (g_CurrentTracks == null) + { + // Wait that's illegal, fallback to the first track + g_CurrentTracks = new ArrayList(); + g_CurrentTracks.Push(view_as(tracks.GetSection(0))); + } + ProfileGlobalTrack track = null; + g_CurrentTrackShuffleType = globalTrack.GetShuffleType(difficulty); + if (oldType != g_CurrentTrackShuffleType) + { + changed = true; + } + switch (g_CurrentTrackShuffleType) + { + case SF2BossGlobalMusicShuffleType_None: + { + track = g_CurrentTracks.Get(g_CurrentTrackIndex); + } + + case SF2BossGlobalMusicShuffleType_Random: + { + ArrayList tempTracks = g_CurrentTracks.Clone(); + if (client.IsValid) + { + for (int i = 0; i < tempTracks.Length; i++) + { + ProfileGlobalTrack tempTrack = view_as(tempTracks.Get(i)); + if (tempTrack == null) + { + continue; + } + char temp[PLATFORM_MAX_PATH]; + tempTrack.GetPath(difficulty, temp, sizeof(temp)); + if (client.IsValid && !client.IsBot && strcmp(g_GlobalMusicState[client.index].MusicPath, temp, false) == 0) + { + tempTracks.Erase(i); + i--; + } + } + } + + track = tempTracks.Get(GetRandomInt(0, tempTracks.Length - 1)); + delete tempTracks; + } + + case SF2BossGlobalMusicShuffleType_Order: + { + g_CurrentTrackIndex++; + if (g_CurrentTrackIndex >= g_CurrentTracks.Length) + { + g_CurrentTrackIndex = 0; + } + track = g_CurrentTracks.Get(g_CurrentTrackIndex); + } + + case SF2BossGlobalMusicShuffleType_RandomOrder: + { + if (!changed) + { + g_CurrentTracks.Sort(Sort_Random, Sort_Integer); + } + g_CurrentTrackIndex++; + if (g_CurrentTrackIndex >= g_CurrentTracks.Length) + { + g_CurrentTrackIndex = 0; + } + track = g_CurrentTracks.Get(g_CurrentTrackIndex); + } + } + track.GetPath(difficulty, buffer, bufferSize); + volume = track.GetVolume(difficulty); + pitch = track.GetPitch(difficulty); + length = track.GetLength(difficulty); + return; + } + } + + if (soundInfo != null) + { + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, bufferSize); + volume = soundInfo.GetVolume(); + pitch = soundInfo.GetPitch(); + radius = soundInfo.GetRadius(); + instantFade = soundInfo.CanInstantFade(); + fadeSpeed = soundInfo.GetFadeSpeed(); + return; + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/peek.sp b/addons/sourcemod/scripting/sf2/client/peek.sp new file mode 100644 index 00000000..b191ee64 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/peek.sp @@ -0,0 +1,30 @@ +#pragma semicolon 1 + +//Peeking Data +bool g_PlayerPeeking[MAXTF2PLAYERS] = { false, ... }; + +/** + * Handles thirdperson peeking + */ +bool ClientStartPeeking(int client) +{ + if (!g_PlayerPeeking[client] && g_AllowPlayerPeekingConVar.BoolValue && !TF2_IsPlayerInCondition(client, TFCond_Dazed) && GetClientButtons(client) & IN_DUCK) + { + SetVariantInt(1); + AcceptEntityInput(client, "SetForcedTauntCam"); + g_PlayerPeeking[client] = true; + return true; + } + return false; +} + +void ClientEndPeeking(int client) +{ + if (g_PlayerPeeking[client]) + { + SetVariantInt(0); + AcceptEntityInput(client, "SetForcedTauntCam"); + TF2_RemoveCondition(client, TFCond_Dazed); + g_PlayerPeeking[client] = false; + } +} diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_proxy_functions.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp similarity index 87% rename from addons/sourcemod/scripting/sf2/functionclients/client_proxy_functions.sp rename to addons/sourcemod/scripting/sf2/client/proxy.sp index de19032f..da894e96 100644 --- a/addons/sourcemod/scripting/sf2/functionclients/client_proxy_functions.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -14,7 +14,174 @@ static char g_ClientProxyModelInsane[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; static char g_ClientProxyModelNightmare[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; static char g_ClientProxyModelApollyon[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -void ClientResetProxy(int client, bool resetFull=true) +void SetupProxy() +{ + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + + AddNormalSoundHook(Hook_ProxySoundHook); +} + +static void MapStart() +{ + for (int i = 1; i <= 18; i++) + { + char sound[PLATFORM_MAX_PATH]; + FormatEx(sound, sizeof(sound), "mvm/player/footsteps/robostep_%s%i.wav", (i < 10) ? "0" : "", i); + PrecacheSound(sound, true); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + ClientResetProxy(client.index); + ClientStartProxyAvailableTimer(client.index); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + ClientStopProxyForce(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetProxy(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + if (!client.IsProxy) + { + return; + } + + // We're a proxy, so play some sounds. + + int proxyMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client.index]); + if (proxyMaster != -1) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(proxyMaster, profile, sizeof(profile)); + + SF2BossProfileSoundInfo soundInfo; + GetBossProfileProxyDeathSounds(profile, soundInfo); + soundInfo.EmitSound(_, client.index); + } + + CreateTimer(0.1, Timer_ResetProxy, client.UserID, TIMER_FLAG_NO_MAPCHANGE); +} + +static Action Timer_ResetProxy(Handle timer, any userid) +{ + SF2_BasePlayer client = SF2_BasePlayer(GetClientOfUserId(userid)); + if (!client.IsValid) + { + return Plugin_Stop; + } + + ClientResetProxy(client.index, true); + return Plugin_Stop; +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetProxy(client.index); +} + +static Action Hook_ProxySoundHook(int clients[64], int &numClients, char sample[PLATFORM_MAX_PATH], int &entity, int &channel, float &volume, int &level, int &pitch, int &flags) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + SF2_BasePlayer client = SF2_BasePlayer(entity); + if (!client.IsValid || !client.IsProxy) + { + return Plugin_Continue; + } + + int master = NPCGetFromUniqueID(client.ProxyMaster); + if (master == -1) + { + return Plugin_Continue; + } + + SF2BossProfileData data; + data = NPCGetProfileData(master); + + switch (channel) + { + case SNDCHAN_VOICE: + { + if (!data.ProxyAllowVoices) + { + return Plugin_Handled; + } + } + } + + if (data.ProxyRobots) + { + switch (channel) + { + case SNDCHAN_VOICE: + { + if (StrContains(sample, "vo/", false) == -1) + { + return Plugin_Continue; + } + + ReplaceString(sample, sizeof(sample), "vo/", "vo/mvm/norm/", false); + ReplaceString(sample, sizeof(sample), ".wav", ".mp3", false); + char className[10], classMvM[15]; + TF2_GetClassName(client.Class, className, sizeof(className), true); + FormatEx(classMvM, sizeof(classMvM), "%s_mvm", className); + ReplaceString(sample, sizeof(sample), className, classMvM, false); + for (int i = 0; i < strlen(sample); i++) + { + sample[i] = CharToLower(sample[i]); + } + PrecacheSound(sample); + return Plugin_Changed; + } + case SNDCHAN_BODY: + { + if (StrContains(sample, "player/footsteps/", false) == -1 || client.Class == TFClass_Medic) + { + return Plugin_Handled; + } + int random = GetRandomInt(1, 18); + pitch = GetRandomInt(95, 100); + FormatEx(sample, sizeof(sample), "mvm/player/footsteps/robostep_%s%i.wav", (random < 10) ? "0" : "", random); + EmitSoundToAll(sample, client.index, _, _, _, 0.25, pitch); + return Plugin_Changed; + } + } + } + + return Plugin_Continue; +} + +void ClientResetProxy(int client, bool resetFull = true) { #if defined DEBUG if (g_DebugDetailConVar.IntValue > 2) @@ -23,14 +190,15 @@ void ClientResetProxy(int client, bool resetFull=true) } #endif - int oldMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); - char oldProfileName[SF2_MAX_PROFILE_NAME_LENGTH]; - if (oldMaster >= 0) + bool oldProxy = g_PlayerProxy[client]; + if (IsValidClient(client) && oldProxy != g_PlayerProxy[client]) { - NPCGetProfile(oldMaster, oldProfileName, sizeof(oldProfileName)); + Call_StartForward(g_OnPlayerChangeProxyStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(false); + Call_Finish(); } - bool oldProxy = g_PlayerProxy[client]; if (resetFull) { g_PlayerProxy[client] = false; @@ -42,7 +210,7 @@ void ClientResetProxy(int client, bool resetFull=true) g_PlayerProxyControlRate[client] = 0.0; g_PlayerProxyVoiceTimer[client] = null; - if (IsClientInGame(client)) + if (IsValidClient(client)) { if (oldProxy) { @@ -50,21 +218,9 @@ void ClientResetProxy(int client, bool resetFull=true) if (resetFull) { - ClientDisableConstantGlow(client); SetVariantString(""); AcceptEntityInput(client, "SetCustomModel"); } - - if (oldProfileName[0] != '\0') - { - SF2BossProfileSoundInfo soundInfo; - GetBossProfileProxySpawnSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - GetBossProfileProxyHurtSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - GetBossProfileProxyIdleSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - } } } @@ -155,7 +311,7 @@ Action Timer_ClientForceProxy(Handle timer, any userid) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !g_PlayerEliminated[i]) + if (!IsValidClient(i) || !g_PlayerEliminated[i]) { continue; } @@ -211,13 +367,13 @@ void DisplayProxyAskMenu(int client, int askMaster, const float pos[3], int spaw return; } char buffer[512]; - Handle menu = CreateMenu(Menu_ProxyAsk); - SetMenuTitle(menu, "%T\n \n%T\n \n", "SF2 Proxy Ask Menu Title", client, "SF2 Proxy Ask Menu Description", client); + Menu menu = new Menu(Menu_ProxyAsk); + menu.SetTitle("%T\n \n%T\n \n", "SF2 Proxy Ask Menu Title", client, "SF2 Proxy Ask Menu Description", client); FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "1", buffer); + menu.AddItem("1", buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); g_PlayerProxyAskMaster[client] = askMaster; for (int i = 0; i < 3; i++) @@ -226,10 +382,10 @@ void DisplayProxyAskMenu(int client, int askMaster, const float pos[3], int spaw } g_PlayerProxyAskSpawnPoint[client] = EnsureEntRef(spawnPoint); - DisplayMenu(menu, client, 15); + menu.Display(client, 15); } -int Menu_ProxyAsk(Handle menu, MenuAction action,int param1,int param2) +int Menu_ProxyAsk(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -253,7 +409,7 @@ int Menu_ProxyAsk(Handle menu, MenuAction action,int param1,int param2) for (int client = 1; client <= MaxClients; client++) { - if (!IsClientInGame(client) || !g_PlayerEliminated[client]) + if (!IsValidClient(client) || !g_PlayerEliminated[client]) { continue; } @@ -350,14 +506,17 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP { return; } + if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) { return; } + if (GetClientTeam(client) != TFTeam_Blue) { return; } + if (g_PlayerProxy[client]) { return; @@ -376,21 +535,24 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP if (IsClientInKart(client)) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); } g_PlayerProxy[client] = true; + ChangeClientTeamNoSuicide(client, TFTeam_Blue); PvP_SetPlayerPvPState(client, false, true, false); + PvE_SetPlayerPvEState(client, false, false); TF2_RespawnPlayer(client); // Speed recalculation. Props to the creators of FF2/VSH for this snippet. TF2_AddCondition(client, TFCond_SpeedBuffAlly, 0.001); g_PlayerProxy[client] = true; + g_PlayerProxyMaster[client] = NPCGetUniqueID(bossIndex); g_PlayerProxyControl[client] = 100; g_PlayerProxyControlRate[client] = g_SlenderProxyControlDrainRate[bossIndex][difficulty]; @@ -422,7 +584,6 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP UTIL_ScreenFade(client, 200, 1, FFADE_IN, 255, 255, 255, 100); EmitSoundToClient(client, "weapons/teleporter_send.wav", _, SNDCHAN_STATIC); - ClientDisableConstantGlow(client); ClientActivateUltravision(client); TF2Attrib_SetByDefIndex(client, 28, 1.0); @@ -434,65 +595,6 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP CreateTimer(1.0, Timer_GiveWeaponAll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - if (g_SlenderInDeathcam[npcIndex]) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } - } - //SDKHook(client, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); SF2PlayerProxySpawnEntity spawnPoint = SF2PlayerProxySpawnEntity(spawnPointEnt); @@ -519,6 +621,11 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP Call_StartForward(g_OnClientSpawnedAsProxyFwd); Call_PushCell(client); Call_Finish(); + + Call_StartForward(g_OnPlayerChangeProxyStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(true); + Call_Finish(); } static Action Timer_GiveWeaponAll(Handle timer, any userid) @@ -588,9 +695,9 @@ static Action Timer_GiveWeaponAll(Handle timer, any userid) return Plugin_Stop; } -bool Hook_ClientProxyShouldCollide(int ent,int collisiongroup,int contentsmask, bool originalResult) +bool Hook_ClientProxyShouldCollide(int ent, int collisiongroup, int contentsmask, bool originalResult) { - if (!g_Enabled || !g_PlayerProxy[ent] || IsClientInPvP(ent)) + if (!g_Enabled || !g_PlayerProxy[ent] || IsClientInPvP(ent) || IsClientInPvE(ent)) { SDKUnhook(ent, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); return originalResult; @@ -641,7 +748,7 @@ Action Timer_ClientProxyControl(Handle timer, any userid) if (g_PlayerProxyControl[client] <= 0) { // ForcePlayerSuicide isn't really dependable, since the player doesn't suicide until several seconds after spawning has passed. - SDKHooks_TakeDamage(client, client, client, 9001.0, DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + KillClient(client); return Plugin_Stop; } @@ -660,8 +767,6 @@ Action Timer_ApplyCustomModel(Handle timer, any userid) SetEntProp(client, Prop_Send, "m_iAirDash", 99999); - TF2Attrib_SetByName(client, "crit mod disabled", 0.0); - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); if (g_PlayerProxy[client] && master != -1) @@ -687,7 +792,7 @@ Action Timer_ApplyCustomModel(Handle timer, any userid) { switch (difficulty) { - case Difficulty_Easy, Difficulty_Normal: + case Difficulty_Normal: { modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); if (modelsArray == null) @@ -854,25 +959,6 @@ Action Timer_ApplyCustomModel(Handle timer, any userid) } } - for (int i = 1; i <= MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } - } - if (IsPlayerAlive(client)) { g_PlayerProxyNextVoiceSound[client] = GetGameTime(); @@ -981,7 +1067,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) { case Difficulty_Normal: { - if (strcmp(model,g_ClientProxyModel[client]) != 0 && g_ClientProxyModel[client][0] != '\0') + if (strcmp(model, g_ClientProxyModel[client]) != 0 && g_ClientProxyModel[client][0] != '\0') { SetVariantString(g_ClientProxyModel[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -990,7 +1076,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Hard: { - if (strcmp(model,g_ClientProxyModelHard[client]) != 0 && g_ClientProxyModelHard[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelHard[client]) != 0 && g_ClientProxyModelHard[client][0] != '\0') { SetVariantString(g_ClientProxyModelHard[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -999,7 +1085,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Insane: { - if (strcmp(model,g_ClientProxyModelInsane[client]) != 0 && g_ClientProxyModelInsane[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelInsane[client]) != 0 && g_ClientProxyModelInsane[client][0] != '\0') { SetVariantString(g_ClientProxyModelInsane[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1008,7 +1094,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Nightmare: { - if (strcmp(model,g_ClientProxyModelNightmare[client]) != 0 && g_ClientProxyModelNightmare[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelNightmare[client]) != 0 && g_ClientProxyModelNightmare[client][0] != '\0') { SetVariantString(g_ClientProxyModelNightmare[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1017,7 +1103,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Apollyon: { - if (strcmp(model,g_ClientProxyModelApollyon[client]) != 0 && g_ClientProxyModelApollyon[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelApollyon[client]) != 0 && g_ClientProxyModelApollyon[client][0] != '\0') { SetVariantString(g_ClientProxyModelApollyon[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1030,15 +1116,16 @@ Action ClientCheckProxyModel(Handle timer, any userid) void SF2_RefreshRestrictions() { - for(int client=1;client <=MaxClients;client++) + for(int client = 1; client <= MaxClients; client++) { - if (IsValidClient(client) && (!g_PlayerEliminated[client] || !IsClientInPvP(client))) + if (IsValidClient(client) && (!g_PlayerEliminated[client] || (g_PlayerEliminated[client] && !IsClientInPvP(client) && !IsClientInPvE(client) && !IsClientInWeaponsTrigger(client)))) { ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - g_PlayerPostWeaponsTimer[client]=CreateTimer(1.0,Timer_ClientPostWeapons,GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + g_PlayerPostWeaponsTimer[client] = CreateTimer(1.0, Timer_ClientPostWeapons, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } } } + Action Timer_ClientPostWeapons(Handle timer, any userid) { int client = GetClientOfUserId(userid); @@ -1047,7 +1134,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) return Plugin_Stop; } - if (IsClientInGame(client) && !IsPlayerAlive(client)) + if (IsValidClient(client) && !IsPlayerAlive(client)) { return Plugin_Stop; } @@ -1075,7 +1162,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) for (int i = 0; i <= 5; i++) { - if (IsClientInGame(client) && IsValidClient(client)) + if (IsValidClient(client)) { int weaponEnt = GetPlayerWeaponSlot(client, i); if (!IsValidEdict(weaponEnt)) @@ -1101,7 +1188,6 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) { removeWeapons = false; restrictWeapons = false; - keepUtilityItems = false; } } @@ -1109,12 +1195,11 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) { removeWeapons = false; restrictWeapons = false; - keepUtilityItems = false; preventAttack = true; } // pvp - if (IsClientInPvP(client)) + if (IsClientInPvP(client) || IsClientInWeaponsTrigger(client)) { removeWeapons = false; restrictWeapons = false; @@ -1122,6 +1207,23 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) preventAttack = false; } + if (IsClientInPvE(client)) + { + if (IsPvEBoxing()) + { + restrictWeapons = false; + keepUtilityItems = true; + preventAttack = false; + } + else + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = false; + } + } + if (g_PlayerProxy[client]) { restrictWeapons = true; @@ -1449,7 +1551,9 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) else { if (!g_PlayerHasRegenerationItem[client]) + { g_PlayerHasRegenerationItem[client] = IsRegenWeapon(weaponEnt); + } } } delete itemHandle; @@ -1543,20 +1647,6 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) SetEntityHealth(client, 300); } } - case 1084: //Festive GRU - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fists", 1084, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - if (!IsRoundPlaying()) - { - SetEntityHealth(client, 300); - } - } case 1100: //Bread Bite { TF2_RemoveWeaponSlot(client, slot); @@ -1566,10 +1656,6 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) delete weaponHandle; weaponHandle = null; EquipPlayerWeapon(client, entity); - if (!IsRoundPlaying()) - { - SetEntityHealth(client, 300); - } } case 426: //Eviction Notice { @@ -1679,7 +1765,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) } weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + if (!IsValidEntity(weaponEnt)) { continue; } @@ -1701,7 +1787,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) } //Remove the teleport ability - if (IsClientInPvP(client) || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !g_PlayerEliminated[client])) //DidClientEscape(client) + if (IsClientInPvP(client) || IsClientInPvE(client) || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !g_PlayerEliminated[client])) //DidClientEscape(client) { int weaponEnt = INVALID_ENT_REFERENCE; Handle weaponHandle = null; @@ -1848,7 +1934,7 @@ public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int itemDef void Frame_ClientHealArrow(int client) { - if (IsClientInGame(client) && IsClientInPvP(client)) + if (IsValidClient(client) && IsClientInPvP(client)) { int entity = -1; while ((entity = FindEntityByClassname(entity, "tf_projectile_healing_bolt")) != -1) @@ -1919,7 +2005,7 @@ bool IsWeaponRestricted(TFClassType class,int itemDefInt) bool proxyBoss = false; for (int i = 0; i < MAX_BOSSES; i++) { - SF2NPC_BaseNPC Npc = view_as(i); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (!Npc.IsValid()) { continue; @@ -1932,7 +2018,7 @@ bool IsWeaponRestricted(TFClassType class,int itemDefInt) } if (g_RestrictedWeaponsConfig.JumpToKey("all")) { - //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef)); + //returnBool = (g_RestrictedWeaponsConfig.GetNum(itemDef)) != 0; //view_as bool value turn to 2 into a true value. if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) { @@ -1999,7 +2085,7 @@ bool IsWeaponRestricted(TFClassType class,int itemDefInt) if (bFoundSection) { - //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef, returnBool)); + //returnBool = (g_RestrictedWeaponsConfig.GetNum(itemDef, returnBool)) != 0; if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) { returnBool = true; diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp new file mode 100644 index 00000000..4b327ce8 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -0,0 +1,894 @@ +#pragma semicolon 1 + +static bool g_PlayerSprint[MAXTF2PLAYERS] = { false, ... }; +static bool g_WantsToSprint[MAXTF2PLAYERS] = { false, ... }; +static float g_Stamina[MAXTF2PLAYERS] = { 1.0, ... }; +static float g_StaminaRechargeTime[MAXTF2PLAYERS] = { 1.0, ... }; + +static ConVar g_PlayerScareSprintBoost; + +void SetupSprint() +{ + g_OnPlayerJumpPFwd.AddFunction(null, OnJump); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + + g_PlayerScareSprintBoost = CreateConVar("sf2_player_scare_boost", "0", "Determines if the player run speeds should be decreased slightly if they are not in danger.", _, true, 0.0, true, 1.0); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetSprint(client); + + SDKHook(client.index, SDKHook_PreThink, Hook_SpeedThink); + SDKHook(client.index, SDKHook_PreThink, Hook_SprintThink); +} + +static void OnJump(SF2_BasePlayer client) +{ + if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped || client.IsLatched || client.IsTrapped) + { + return; + } + + if (!IsInfiniteSprintEnabled() && client.Stamina > 0.0) + { + TFClassType classType = client.Class; + int classToInt = view_as(classType); + if (!IsClassConfigsValid()) + { + if (classType != TFClass_Soldier || client.Stamina <= 0.1 || client.IsSprinting) + { + client.SetStaminaRechargeTime(GetGameTime() + 1.5); + + client.Stamina -= 0.07; + } + } + else + { + float sprintPointsLoss = g_ClassSprintPointLossJumping[classToInt]; + if (client.Stamina <= 0.1 || client.IsSprinting) + { + sprintPointsLoss = 0.07; + } + + if (sprintPointsLoss > 0.0) + { + client.SetStaminaRechargeTime(GetGameTime() + 1.5); + } + + client.Stamina -= sprintPointsLoss; + } + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetSprint(client); + + if (client.IsAlive && !client.IsEliminated && !client.HasEscaped) + { + if (SF_IsBoxingMap() || SF_IsRaidMap() || SF_IsSlaughterRunMap()) + { + g_WantsToSprint[client.index] = true; + } + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetSprint(client); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetSprint(client); +} + +static void Hook_SprintThink(int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsAlive || player.IsEliminated || player.HasEscaped || player.IsInGhostMode) + { + return; + } + + bool wasSprinting = g_PlayerSprint[client]; + bool isSprinting = false; + + if (g_WantsToSprint[player.index] && !player.IsInDeathCam) + { + float velocity[3]; + player.GetAbsVelocity(velocity); + if (GetVectorLength(velocity, true) > Pow(30.0, 2.0)) + { + isSprinting = true; + } + + int fov = GetEntData(client, g_PlayerDefaultFOVOffset); + + int targetFov = g_PlayerDesiredFOV[client] + 10; + + if (fov < targetFov) + { + int diff = RoundFloat(FloatAbs(float(fov - targetFov))); + if (diff >= 1) + { + ClientSetFOV(client, fov + 1); + } + else + { + ClientSetFOV(client, targetFov); + } + } + else if (fov >= targetFov) + { + ClientSetFOV(client, targetFov); + } + } + else + { + int fov = GetEntData(client, g_PlayerDefaultFOVOffset); + if (fov > g_PlayerDesiredFOV[client]) + { + int diff = RoundFloat(FloatAbs(float(fov - g_PlayerDesiredFOV[client]))); + if (diff >= 1) + { + ClientSetFOV(client, fov - 1); + } + else + { + ClientSetFOV(client, g_PlayerDesiredFOV[client]); + } + } + else if (fov <= g_PlayerDesiredFOV[client]) + { + ClientSetFOV(client, g_PlayerDesiredFOV[client]); + } + } + + g_PlayerSprint[player.index] = isSprinting; + + if (wasSprinting != isSprinting) + { + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S)) + { + if (isSprinting) + { + Client90sMusicStart(client); + + Call_StartForward(g_OnClientStartSprintingFwd); + Call_PushCell(client); + Call_Finish(); + } + else + { + Client90sMusicStop(client); + + Call_StartForward(g_OnClientStopSprintingFwd); + Call_PushCell(client); + Call_Finish(); + } + } + } + + if (isSprinting) + { + if (!IsInfiniteSprintEnabled()) + { + player.SetStaminaRechargeTime(GetGameTime() + 1.5); + + player.Stamina -= GetStaminaDecreaseRate(player) * GetTickInterval(); + if (player.Stamina <= 0.0) + { + g_WantsToSprint[client] = false; + } + } + } + + if (player.Stamina < 1.0 && GetGameTime() > g_StaminaRechargeTime[player.index]) + { + player.Stamina += GetStaminaRechargeRate(player) * GetTickInterval(); + } +} + +float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknown) +{ + float returnFloat = 400.0; + float returnFloat2 = returnFloat; + Action action = Plugin_Continue; + if (IsValidClient(client)) + { + class = TF2_GetPlayerClass(client); + } + + switch (class) + { + case TFClass_Scout: + { + returnFloat = 400.0; + } + case TFClass_Sniper: + { + returnFloat = 370.0; + } + case TFClass_Soldier: + { + returnFloat = 350.0; + } + case TFClass_DemoMan: + { + returnFloat = 350.0; + } + case TFClass_Heavy: + { + returnFloat = 350.0; + } + case TFClass_Medic: + { + returnFloat = 370.0; + } + case TFClass_Pyro: + { + returnFloat = 370.0; + } + case TFClass_Spy: + { + returnFloat = 370.0; + } + case TFClass_Engineer: + { + returnFloat = 370.0; + } + } + + if (SF_IsSlaughterRunMap()) + { + returnFloat = g_SlaughterRunDefaultClassRunSpeedConVar.FloatValue; + } + + if (IsValidClient(client)) + { + // Call our forward. + Call_StartForward(g_OnClientGetDefaultSprintSpeedFwd); + Call_PushCell(client); + Call_PushCellRef(returnFloat2); + Call_Finish(action); + } + + if (action == Plugin_Changed) + { + returnFloat = returnFloat2; + } + + return returnFloat; +} + +bool IsClientSprinting(int client) +{ + return g_PlayerSprint[client]; +} + +float ClientGetStamina(int client) +{ + return g_Stamina[client]; +} + +void ClientSetStamina(int client, float value) +{ + g_Stamina[client] = value; + if (g_Stamina[client] > 1.0) + { + g_Stamina[client] = 1.0; + } + if (g_Stamina[client] < 0.0) + { + g_Stamina[client] = 0.0; + } +} + +void SetPlayerStaminaRechargeTime(SF2_BasePlayer client, float time, bool checkTime = true) +{ + if (checkTime && g_StaminaRechargeTime[client.index] >= time) + { + return; + } + + g_StaminaRechargeTime[client.index] = time; +} + +float GetStaminaDecreaseRate(SF2_BasePlayer client) +{ + float rate = 0.03; + + if (SF_SpecialRound(SPECIALROUND_COFFEE)) + { + rate *= 0.5; + } + + if (SF_IsProxyMap()) + { + rate *= 0.25; + } + + TFClassType class = client.Class; + int classToInt = view_as(class); + if (!IsClassConfigsValid()) + { + if (class == TFClass_DemoMan) + { + rate *= 0.9; // Demoman gets a 10% sprint duration increase + } + else if (class == TFClass_Scout) + { + rate *= 0.95; // Scout gets a 5% sprint duration increase + } + } + else + { + rate *= g_ClassSprintDurationMultipler[classToInt]; + } + + return rate; +} + +float GetStaminaRechargeRate(SF2_BasePlayer client) +{ + float rate = 0.02; + + if (client.Ducked) + { + float velocity[3]; + client.GetAbsVelocity(velocity); + if (GetVectorLength(velocity, true) == 0.0) + { + // Double the sprint rate if the player is crouching and holding still + rate = 0.04; + } + } + + if (SF_SpecialRound(SPECIALROUND_COFFEE)) + { + rate *= 0.35; + } + + if (SF_IsProxyMap()) + { + rate *= 0.25; + } + + return rate; +} + +static void ClientResetSprint(SF2_BasePlayer client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetSprint(%d)", client.index); + } + #endif + + bool wasSprinting = client.IsSprinting; + + client.SetStaminaRechargeTime(0.0, false); + g_PlayerSprint[client.index] = false; + g_WantsToSprint[client.index] = false; + client.Stamina = 1.0; + + if (client.IsValid) + { + ClientSetFOV(client.index, g_PlayerDesiredFOV[client.index]); + } + + if (wasSprinting) + { + Call_StartForward(g_OnClientStopSprintingFwd); + Call_PushCell(client.index); + Call_Finish(); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetSprint(%d)", client.index); + } + #endif +} + + /** + * Handles sprinting upon player input. + */ +void ClientHandleSprint(int client, bool sprint) +{ + if (IsRoundInWarmup() || IsRoundInIntro() || IsRoundEnding()) + { + return; + } + + if (!IsPlayerAlive(client) || + g_PlayerEliminated[client] || + DidClientEscape(client) || + g_PlayerProxy[client] || + IsClientInGhostMode(client)) + { + return; + } + + if (SF_IsRaidMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) // On these maps we always sprint + { + return; + } + + if (sprint) + { + if (g_Stamina[client] > 0) + { + g_WantsToSprint[client] = true; + } + else + { + EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); + } + } + else + { + g_WantsToSprint[client] = false; + } +} + +bool IsClientReallySprinting(int client) +{ + if (!IsClientSprinting(client)) + { + return false; + } + if (!(GetEntityFlags(client) & FL_ONGROUND)) + { + return false; + } + + float velocity[3]; + GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); + if (GetVectorLength(velocity, true) < SquareFloat(30.0)) + { + return false; + } + + return true; +} + +static void Hook_SpeedThink(int client) +{ + if (!g_Enabled) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + RoundState roundState = GameRules_GetRoundState(); + if (player.IsEliminated || roundState != RoundState_RoundRunning || + IsRoundEnding() || IsRoundInWarmup() || player.HasEscaped) + { + return; + } + + TFClassType class = player.Class; + int classToInt = view_as(class); + + bool inDanger = false; + bool inChase = false; + + if (!inDanger) + { + int state; + CBaseEntity bossTarget; + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + int ent = NPCGetEntIndex(i); + if (!ent || ent == INVALID_ENT_REFERENCE) + { + continue; + } + + if (NPCGetType(i) == SF2BossType_Chaser) + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(ent); + bossTarget = chaser.Target; + state = chaser.State; + + if ((state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) && + ((bossTarget.IsValid() && (bossTarget.index == client || ClientGetDistanceFromEntity(client, bossTarget.index) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) + { + inDanger = true; + if (bossTarget.index == client) + { + inChase = true; + } + ClientSetScareBoostEndTime(player.index, GetGameTime() + 5.0); + + // Induce client stress levels. + float unComfortZoneDist = 512.0; + float stressScalar = ((SquareFloat(unComfortZoneDist) / NPCGetDistanceFromEntity(i, client))); + ClientAddStress(player.index, 0.025 * stressScalar); + + break; + } + } + } + } + + if (g_PlayerStaticAmount[player.index] > 0.4) + { + inDanger = true; + } + if (GetGameTime() < ClientGetScareBoostEndTime(player.index)) + { + inDanger = true; + } + + if (!inDanger) + { + int state; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if (NPCGetType(i) == SF2BossType_Chaser) + { + if (state == STATE_ALERT) + { + if (player.CanSeeSlender(i)) + { + inDanger = true; + ClientSetScareBoostEndTime(player.index, GetGameTime() + 5.0); + } + } + } + } + } + + if (!inDanger) + { + float curTime = GetGameTime(); + float scareSprintDuration = 3.0; + if (!IsClassConfigsValid()) + { + if (class == TFClass_DemoMan) + { + scareSprintDuration *= 1.667; + } + } + else + { + scareSprintDuration *= g_ClassScareSprintDurationMultipler[classToInt]; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if ((curTime - g_PlayerScareLastTime[player.index][i]) <= scareSprintDuration) + { + inDanger = true; + break; + } + } + } + + if (player.IsTrapped || g_PlayerPeeking[player.index]) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 0.1); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 0.1); + return; + } + + float walkSpeed, sprintSpeed; + if (!IsClassConfigsValid() || SF_IsSlaughterRunMap()) + { + walkSpeed = ClientGetDefaultWalkSpeed(player.index); + sprintSpeed = ClientGetDefaultSprintSpeed(player.index); + } + else + { + walkSpeed = g_ClassWalkSpeed[classToInt]; + sprintSpeed = g_ClassRunSpeed[classToInt]; + } + + // Check for weapon speed changes. + int weaponEnt = INVALID_ENT_REFERENCE; + + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDefInt) + { + case 172: // Scotsman's Skullcutter + { + if (player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + sprintSpeed -= (sprintSpeed * 0.05); + walkSpeed -= (walkSpeed * 0.05); + } + } + case 214: // The Powerjack + { + if (player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + sprintSpeed += (sprintSpeed * 0.03); + } + } + case 239: // Gloves of Running Urgently + { + if (player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + sprintSpeed += (sprintSpeed * 0.075); + } + } + case 775: // Escape Plan + { + float health = float(player.GetProp(Prop_Send, "m_iHealth")); + float maxHealth = float(SDKCall(g_SDKGetMaxHealth, player.index)); + float percentage = health / maxHealth; + + if (percentage < 0.805 && percentage >= 0.605) + { + walkSpeed += (walkSpeed * 0.05); + sprintSpeed += (sprintSpeed * 0.05); + } + else if (percentage < 0.605 && percentage >= 0.405) + { + walkSpeed += (walkSpeed * 0.1); + sprintSpeed += (sprintSpeed * 0.1); + } + else if (percentage < 0.405 && percentage >= 0.205) + { + walkSpeed += (walkSpeed * 0.15); + sprintSpeed += (sprintSpeed * 0.15); + } + else if (percentage < 0.205) + { + walkSpeed += (walkSpeed * 0.2); + sprintSpeed += (sprintSpeed * 0.2); + } + } + } + } + + // Speed buff + if (!SF_IsSlaughterRunMap()) + { + if (player.InCondition(TFCond_SpeedBuffAlly)) + { + walkSpeed += (walkSpeed * 0.115); + sprintSpeed += (sprintSpeed * 0.125); + } + } + else + { + if (player.InCondition(TFCond_SpeedBuffAlly)) + { + walkSpeed += (walkSpeed * 0.11); + sprintSpeed += (sprintSpeed * 0.11); + } + } + + if (g_PlayerScareSprintBoost.BoolValue) + { + if (!inDanger) + { + // We're not going to account for sprint speed for the scare sprint mechanic + // It's a bit of a headache for me to do something like that + float newValue = (walkSpeed + sprintSpeed) / 1.85; + sprintSpeed = newValue; + } + else + { + if (!IsClassConfigsValid()) + { + if (class != TFClass_Spy && class != TFClass_Pyro) + { + walkSpeed *= 1.34; + } + else + { + if (class == TFClass_Spy) + { + walkSpeed *= 1.28; + } + else + { + weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + walkSpeed *= 1.32; + } + else + { + walkSpeed *= 1.34; + } + } + } + } + } + else + { + float multiplier = g_ClassDangerSpeedMultipler[classToInt]; + if (class == TFClass_Pyro) + { + weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + multiplier -= 0.02; + } + } + } + walkSpeed *= multiplier; + } + } + } + + if (g_PlayerPreferences[player.index].PlayerPreference_ShowHints && inDanger) + { + if (!inChase && !player.HasHint(PlayerHint_Crouch)) + { + player.ShowHint(PlayerHint_Crouch); + } + else if (inChase && !player.HasHint(PlayerHint_Sprint)) + { + player.ShowHint(PlayerHint_Sprint); + } + } + + if (player.Stamina <= 0.04) + { + float sprintSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.425); + float walkSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.3); + + sprintSpeedSubtract += 125; + sprintSpeed -= sprintSpeedSubtract; + walkSpeedSubtract += 25; + walkSpeed -= walkSpeedSubtract; + } + + if (player.IsSprinting) + { + if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) + { + if (!player.InCondition(TFCond_Charging)) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed); + } + else + { + if (SF_IsBoxingMap() || SF_IsRaidMap()) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed * 2.5); + } + else + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed / 2.5); + } + } + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 190.0); + } + else + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 520.0); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 190.0); + } + } + else + { + if (!player.InCondition(TFCond_Charging)) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed); + } + else + { + if (SF_IsBoxingMap() || SF_IsRaidMap()) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed * 2.5); + } + else + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 190.0); + } + } + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 190.0); + } +} + +void Sprint_SetupAPI() +{ + CreateNative("SF2_GetClientSprintPoints", Native_GetSprintPoints); + CreateNative("SF2_SetClientSprintPoints", Native_SetSprintPoints); + + CreateNative("SF2_IsClientSprinting", Native_IsSprinting); + CreateNative("SF2_IsClientReallySprinting", Native_IsReallySprinting); + CreateNative("SF2_SetClientSprintState", Native_SetSprintState); +} + +static any Native_GetSprintPoints(Handle plugin, int numParams) +{ + return RoundToNearest(SF2_BasePlayer(GetNativeCell(1)).Stamina * 100.0); +} + +static any Native_SetSprintPoints(Handle plugin, int numParams) +{ + float newValue = GetNativeCell(2) / 100.0; + SF2_BasePlayer(GetNativeCell(1)).Stamina = newValue; + return 0; +} + +static any Native_IsSprinting(Handle plugin, int numParams) +{ + return IsClientSprinting(GetNativeCell(1)); +} + +static any Native_IsReallySprinting(Handle plugin, int numParams) +{ + return IsClientReallySprinting(GetNativeCell(1)); +} + +static any Native_SetSprintState(Handle plugin, int numParams) +{ + ClientHandleSprint(GetNativeCell(1), GetNativeCell(2)); + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/client/static.sp b/addons/sourcemod/scripting/sf2/client/static.sp new file mode 100644 index 00000000..1bc02aac --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/static.sp @@ -0,0 +1,479 @@ +#pragma semicolon 1 + +// Player static data. +int g_PlayerStaticMode[MAXTF2PLAYERS][MAX_BOSSES]; +float g_PlayerStaticIncreaseRate[MAXTF2PLAYERS]; +float g_PlayerStaticDecreaseRate[MAXTF2PLAYERS]; +Handle g_PlayerStaticTimer[MAXTF2PLAYERS]; +int g_PlayerStaticMaster[MAXTF2PLAYERS] = { -1, ... }; +char g_PlayerStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +char g_PlayerLastStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +float g_PlayerLastStaticTime[MAXTF2PLAYERS]; +float g_PlayerLastStaticVolume[MAXTF2PLAYERS]; +Handle g_PlayerLastStaticTimer[MAXTF2PLAYERS]; + +// Static shake data. +int g_PlayerStaticShakeMaster[MAXTF2PLAYERS]; +bool g_PlayerInStaticShake[MAXTF2PLAYERS]; +char g_PlayerStaticShakeSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +float g_PlayerStaticShakeMinVolume[MAXTF2PLAYERS]; +float g_PlayerStaticShakeMaxVolume[MAXTF2PLAYERS]; + +void SetupStatic() +{ + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetStatic(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetStatic(client.index); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetStatic(client.index); + + SDKHook(client.index, SDKHook_PreThink, Hook_StaticThink); +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetStatic(client.index); +} + +static void OnBossRemoved(SF2NPC_BaseNPC npc) +{ + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + + if (npc.UniqueID == g_PlayerStaticMaster[i]) + { + g_PlayerStaticMaster[i] = -1; + + // No one is the static master. + g_PlayerStaticTimer[i] = CreateTimer(g_PlayerStaticDecreaseRate[i], + Timer_ClientDecreaseStatic, + GetClientUserId(i), + TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + + TriggerTimer(g_PlayerStaticTimer[i], true); + } + } +} + +void ClientResetStatic(int client) +{ + g_PlayerStaticMaster[client] = -1; + g_PlayerStaticTimer[client] = null; + g_PlayerStaticIncreaseRate[client] = 0.0; + g_PlayerStaticDecreaseRate[client] = 0.0; + g_PlayerLastStaticTimer[client] = null; + g_PlayerLastStaticTime[client] = 0.0; + g_PlayerLastStaticVolume[client] = 0.0; + g_PlayerInStaticShake[client] = false; + g_PlayerStaticShakeMaster[client] = -1; + g_PlayerStaticShakeMinVolume[client] = 0.0; + g_PlayerStaticShakeMaxVolume[client] = 0.0; + g_PlayerStaticAmount[client] = 0.0; + + if (IsValidClient(client)) + { + if (g_PlayerStaticSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticSound[client]); + } + if (g_PlayerLastStaticSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + } + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); + } + } + + g_PlayerStaticSound[client][0] = '\0'; + g_PlayerLastStaticSound[client][0] = '\0'; + g_PlayerStaticShakeSound[client][0] = '\0'; +} + +// Static shaking should only affect the x, y portion of the player's view, not roll. +// This is purely for cosmetic effect. + +void ClientProcessStaticShake(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + bool oldStaticShake = g_PlayerInStaticShake[client]; + int oldStaticShakeMaster = NPCGetFromUniqueID(g_PlayerStaticShakeMaster[client]); + int newStaticShakeMaster = -1; + + float oldPunchAng[3], oldPunchAngVel[3]; + GetEntDataVector(client, g_PlayerPunchAngleOffset, oldPunchAng); + GetEntDataVector(client, g_PlayerPunchAngleOffsetVel, oldPunchAngVel); + + float newPunchAng[3], newPunchAngVel[3]; + + for (int i = 0; i < 3; i++) + { + newPunchAng[i] = oldPunchAng[i]; + newPunchAngVel[i] = oldPunchAngVel[i]; + } + + int staticMaster = NPCGetFromUniqueID(g_PlayerStaticMaster[client]); + if (staticMaster != -1 && NPCGetFlags(staticMaster) & SFF_HASSTATICSHAKE) + { + if (g_PlayerStaticMode[client][staticMaster] == Static_Increase) + { + newStaticShakeMaster = staticMaster; + } + } + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if (NPCGetFlags(i) & SFF_HASSTATICSHAKE) + { + int master = NPCGetFromUniqueID(g_SlenderCopyMaster[i]); + if (master == -1) + { + master = i; + } + } + } + + if (newStaticShakeMaster != -1) + { + g_PlayerStaticShakeMaster[client] = NPCGetUniqueID(newStaticShakeMaster); + + if (newStaticShakeMaster != oldStaticShakeMaster) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(newStaticShakeMaster, profile, sizeof(profile)); + + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); + } + + g_PlayerStaticShakeMinVolume[client] = GetBossProfileStaticShakeLocalVolumeMin(profile); + g_PlayerStaticShakeMaxVolume[client] = GetBossProfileStaticShakeLocalVolumeMax(profile); + + char staticSound[PLATFORM_MAX_PATH]; + GetBossProfileStaticShakeSound(profile, staticSound, sizeof(staticSound)); + if (staticSound[0] != '\0') + { + strcopy(g_PlayerStaticShakeSound[client], sizeof(g_PlayerStaticShakeSound[]), staticSound); + } + else + { + g_PlayerStaticShakeSound[client][0] = '\0'; + } + } + } + + if (g_PlayerInStaticShake[client]) + { + if (g_PlayerStaticAmount[client] <= 0.0) + { + g_PlayerInStaticShake[client] = false; + } + } + else + { + if (newStaticShakeMaster != -1) + { + g_PlayerInStaticShake[client] = true; + } + } + + if (g_PlayerInStaticShake[client] && !oldStaticShake) + { + for (int i = 0; i < 2; i++) + { + newPunchAng[i] = 0.0; + newPunchAngVel[i] = 0.0; + } + + SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); + SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); + } + else if (!g_PlayerInStaticShake[client] && oldStaticShake) + { + for (int i = 0; i < 2; i++) + { + newPunchAng[i] = 0.0; + newPunchAngVel[i] = 0.0; + } + + g_PlayerStaticShakeMaster[client] = -1; + + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); + } + + g_PlayerStaticShakeSound[client][0] = '\0'; + + g_PlayerStaticShakeMinVolume[client] = 0.0; + g_PlayerStaticShakeMaxVolume[client] = 0.0; + + SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); + SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); + } + + if (g_PlayerInStaticShake[client]) + { + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + float volume = g_PlayerStaticAmount[client]; + if (GetRandomFloat(0.0, 1.0) <= 0.35) + { + volume = 0.0; + } + else + { + if (volume < g_PlayerStaticShakeMinVolume[client]) + { + volume = g_PlayerStaticShakeMinVolume[client]; + } + + if (volume > g_PlayerStaticShakeMaxVolume[client]) + { + volume = g_PlayerStaticShakeMaxVolume[client]; + } + } + + EmitSoundToClient(client, g_PlayerStaticShakeSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL | SND_STOP, volume); + } + + // Spazz our view all over the place. + for (int i = 0; i < 2; i++) + { + newPunchAng[i] = AngleNormalize(GetRandomFloat(0.0, 360.0)); + } + NormalizeVector(newPunchAng, newPunchAng); + + float angVelocityScalar = 5.0 * g_PlayerStaticAmount[client]; + if (angVelocityScalar < 0.0) + { + angVelocityScalar = 0.0; + } + ScaleVector(newPunchAng, angVelocityScalar); + + for (int i = 0; i < 2; i++) + { + newPunchAngVel[i] = 0.0; + } + + SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); + SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); + } +} + +static void Hook_StaticThink(int client) +{ + if (!g_Enabled) + { + return; + } + SF2_BasePlayer player = SF2_BasePlayer(client); + + if (g_IsPlayerShakeEnabled) + { + bool doShake = false; + + if (player.IsAlive) + { + int staticMaster = NPCGetFromUniqueID(g_PlayerStaticMaster[player.index]); + if (staticMaster != -1 && NPCGetFlags(staticMaster) & SFF_HASVIEWSHAKE) + { + doShake = true; + } + } + + if (doShake) + { + float percent = g_PlayerStaticAmount[player.index]; + + float amplitudeMax = g_PlayerShakeAmplitudeMaxConVar.FloatValue; + float amplitude = amplitudeMax * percent; + + float frequencyMax = g_PlayerShakeFrequencyMaxConVar.FloatValue; + float frequency = frequencyMax * percent; + + UTIL_ClientScreenShake(player.index, amplitude, 0.5, frequency); + } + } +} + +Action Timer_ClientIncreaseStatic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerStaticTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerStaticAmount[client] += 0.05; + if (g_PlayerStaticAmount[client] > 1.0) + { + g_PlayerStaticAmount[client] = 1.0; + } + + if (g_PlayerStaticSound[client][0] != '\0') + { + EmitSoundToClient(client, g_PlayerStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerStaticAmount[client]); + + if (g_PlayerStaticAmount[client] >= 0.5) + { + ClientAddStress(client, 0.03); + } + else + { + ClientAddStress(client, 0.02); + } + } + + return Plugin_Continue; +} + +Action Timer_ClientDecreaseStatic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerStaticTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerStaticAmount[client] -= 0.05; + if (g_PlayerStaticAmount[client] < 0.0) + { + g_PlayerStaticAmount[client] = 0.0; + } + + if (g_PlayerLastStaticSound[client][0] != '\0') + { + float volume = g_PlayerStaticAmount[client]; + if (volume > 0.0) + { + EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); + } + } + + if (g_PlayerStaticAmount[client] <= 0.0) + { + // I've done my job; no point to keep on doing it. + if (g_PlayerLastStaticSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + } + g_PlayerStaticTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_ClientFadeOutLastStaticSound(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerLastStaticTimer[client]) + { + return Plugin_Stop; + } + + if (strcmp(g_PlayerLastStaticSound[client], g_PlayerStaticSound[client], false) == 0) + { + // Wait, the player's current static sound is the same one we're stopping. Abort! + g_PlayerLastStaticTimer[client] = null; + return Plugin_Stop; + } + + if (g_PlayerLastStaticSound[client][0] != '\0') + { + float diff = (GetGameTime() - g_PlayerLastStaticTime[client]) / 1.0; + if (diff > 1.0) + { + diff = 1.0; + } + + float volume = g_PlayerLastStaticVolume[client] - diff; + if (volume < 0.0) + { + volume = 0.0; + } + + if (volume <= 0.0) + { + // I've done my job; no point to keep on doing it. + StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + g_PlayerLastStaticTimer[client] = null; + return Plugin_Stop; + } + else + { + EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); + } + } + else + { + // I've done my job; no point to keep on doing it. + g_PlayerLastStaticTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp new file mode 100644 index 00000000..93b6647d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -0,0 +1,638 @@ +#if defined _sf2_clients_think_included + #endinput +#endif +#define _sf2_clients_think_included + +#pragma semicolon 1 + +void Hook_ClientPreThink(int client) +{ + if (!g_Enabled) + { + return; + } + + ClientProcessFlashlightAngles(client); + ClientProcessStaticShake(client); + ClientProcessViewAngles(client); + + if ((g_PlayerTrapped[client] || g_PlayerLatchedByTongue[client]) && !IsClientInGhostMode(client)) + { + TF2Attrib_SetByName(client, "increased jump height", 0.0); + } + else + { + TF2Attrib_SetByName(client, "increased jump height", 1.0); + } + + if (IsClientInGhostMode(client)) + { + SetEntityFlags(client, GetEntityFlags(client) ^ FL_EDICT_ALWAYS); + SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 520.0); + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + if (IsClientInKart(client) || !TF2_IsPlayerInCondition(client, TFCond_Stealthed)) + { + TF2_RemoveCondition(client,TFCond_HalloweenKart); + TF2_RemoveCondition(client,TFCond_HalloweenKartDash); + TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_Taunting); + ClientHandleGhostMode(client, true); + } + TF2_RemoveCondition(client, TFCond_Taunting); + } + else if (!g_PlayerEliminated[client] || g_PlayerProxy[client]) + { + if (!IsRoundEnding() && !IsRoundInWarmup() && !DidClientEscape(client)) + { + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + + int roundState = view_as(GameRules_GetRoundState()); + TFClassType class = TF2_GetPlayerClass(client); + + if (!g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Red) + { + if (TF2_IsPlayerInCondition(client, TFCond_Disguised)) + { + TF2_RemoveCondition(client, TFCond_Disguised); + } + + if (TF2_IsPlayerInCondition(client, TFCond_Taunting) && (g_PlayerTrapped[client] || g_PlayerLatchedByTongue[client])) + { + TF2_RemoveCondition(client, TFCond_Taunting); + } + + if (TF2_IsPlayerInCondition(client, TFCond_Taunting) && class == TFClass_Soldier) + { + int weaponEnt = GetPlayerWeaponSlot(client, TFWeaponSlot_Melee); + if (weaponEnt && weaponEnt != INVALID_ENT_REFERENCE) + { + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if ((itemDefInt == 775 || itemDefInt == 128) && GetEntProp(client, Prop_Send, "m_iTauntIndex") == 0) + { + TF2_RemoveCondition(client, TFCond_Taunting); //Stop suiciding... + } + } + } + + if (roundState == 4) + { + + } + } + else if (g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Blue) + { + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); + + float maxSpeed; + bool override; + + if (master != -1) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(master, profile, sizeof(profile)); + + override = GetBossProfileProxyOverrideMaxSpeed(profile); + if (override) + { + int difficulty = GetLocalGlobalDifficulty(master); + + maxSpeed = GetBossProfileProxyMaxSpeed(profile, difficulty); + } + } + + bool speedup = TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly); + + if (override) + { + if (speedup || g_InProxySurvivalRageMode) + { + float rageSpeed = maxSpeed + 30.0; + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", maxSpeed); + } + } + else + { + switch (class) + { + case TFClass_Scout: + { + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 390.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } + } + case TFClass_Medic: + { + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } + } + case TFClass_Spy: + { + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } + } + default: + { + if (g_InProxySurvivalRageMode) + { + float rageSpeed = ClientGetDefaultSprintSpeed(client) + 30.0; + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); + } + } + } + } + } + } + } + if (g_PlayerEliminated[client] && (IsClientInPvP(client) || IsClientInPvE(client))) + { + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + if (IsClientInKart(client)) + { + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); + } + } + if (IsRoundInWarmup() || (IsRoundInIntro() && !g_PlayerEliminated[client]) || IsRoundEnding()) //I told you, stop breaking my plugin + { + if (IsClientInKart(client)) + { + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); + } + } + + // Calculate player stress levels. + if (!g_PlayerEliminated[client] && !DidClientEscape(client) && GetGameTime() >= g_PlayerStressNextUpdateTime[client]) + { + g_PlayerStressNextUpdateTime[client] = GetGameTime() + 0.33; + ClientAddStress(client, -0.01); + + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_PLAYER_STRESS, 1, "g_PlayerStressAmount[%d]: %0.1f", client, g_PlayerStressAmount[client]); + #endif + } + + if (g_LastVisibilityProcess[client] + 0.30 >= GetGameTime()) + { + return; + } + + /*if (!g_PlayerEliminated[client]) + { + CNavArea targetArea = SDK_GetLastKnownArea(client);//SDK_GetLastKnownArea(client) =/= g_lastNavArea[client], SDK_GetLastKnownArea() retrives the nav area stored by the server + if (targetArea != INVALID_NAV_AREA) + { + ClientNavAreaUpdate(client, g_lastNavArea[client], targetArea); + g_lastNavArea[client] = targetArea; + } + }*/ + + g_LastVisibilityProcess[client] = GetGameTime(); + + ClientProcessVisibility(client); +} + +void ClientOnButtonPress(SF2_BasePlayer client, int button) +{ + switch (button) + { + case IN_ATTACK2: + { + if (client.IsAlive) + { + if (!IsRoundInWarmup() && + !IsRoundInIntro() && + !IsRoundEnding() && + !client.HasEscaped) + { + if (GetGameTime() >= client.GetFlashlightNextInputTime()) + { + client.HandleFlashlight(); + } + } + } + } + case IN_ATTACK3: + { + client.HandleSprint(true); + } + case IN_RELOAD: + { + if (client.IsAlive) + { + if (!client.IsEliminated) + { + if (!IsRoundEnding() && + !IsRoundInWarmup() && + !IsRoundInIntro() && + !client.HasEscaped) + { + client.SetHoldingBlink(true); + client.Blink(); + } + } + } + } + case IN_JUMP: + { + if (client.IsAlive && !(client.GetFlags() & FL_FROZEN)) + { + if (!(client.GetProp(Prop_Send, "m_bDucked")) && + (client.GetFlags() & FL_ONGROUND) && + client.GetProp(Prop_Send, "m_nWaterLevel") < 2) + { + Call_StartForward(g_OnPlayerJumpPFwd); + Call_PushCell(client); + Call_Finish(); + } + } + if (client.IsInGhostMode) + { + client.SetGravity(0.0001); + } + else + { + client.SetGravity(1.0); + } + } + case IN_DUCK: + { + if (client.IsInGhostMode) + { + client.SetGravity(4.0); + } + else + { + client.SetGravity(1.0); + } + } + } +} + +void ClientOnButtonRelease(SF2_BasePlayer client, int button) +{ + switch (button) + { + case IN_ATTACK3: + { + client.HandleSprint(false); + } + case IN_DUCK: + { + client.EndPeeking(); + + if (client.IsInGhostMode) + { + client.SetGravity(0.5); + } + else + { + client.SetGravity(1.0); + } + } + case IN_JUMP: + { + if (client.IsInGhostMode) + { + client.SetGravity(0.5); + } + else + { + client.SetGravity(1.0); + } + } + case IN_RELOAD: + { + if (client.IsAlive) + { + if (!client.IsEliminated) + { + if (!IsRoundEnding() && + !IsRoundInWarmup() && + !IsRoundInIntro() && + !client.HasEscaped) + { + client.SetHoldingBlink(false); + } + } + } + } + } +} + +#define SF2_PLAYER_HUD_BLINK_SYMBOL_ON "O" +#define SF2_PLAYER_HUD_BLINK_SYMBOL_OFF "Ɵ" +#define SF2_PLAYER_HUD_BLINK_SYMBOL_OLD "B" +#define SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL "ϟ" +#define SF2_PLAYER_HUD_BAR_SYMBOL "█" +#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL "░" +#define SF2_PLAYER_HUD_BAR_SYMBOL_OLD "|" +#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD "" +#define SF2_PLAYER_HUD_INFINITY_SYMBOL "∞" +#define SF2_PLAYER_HUD_SPRINT_SYMBOL "»" +#define SF2_PLAYER_HUD_HEALTH_SYMBOL "♥" + +Action Timer_ClientAverageUpdate(Handle timer) +{ + if (timer != g_ClientAverageUpdateTimer) + { + return Plugin_Stop; + } + + if (!g_Enabled) + { + return Plugin_Stop; + } + + if (IsRoundInWarmup() || IsRoundEnding()) + { + return Plugin_Continue; + } + + // First, process through HUD stuff. + char buffer[256]; + + static int hudColorHealthy[3]; + static int hudColorCritical[3] = { 255, 10, 10 }; + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + if (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud) + { + hudColorHealthy = { 50, 255, 50 }; + } + else + { + hudColorHealthy = { 150, 255, 150 }; + } + + if (player.IsAlive && !player.IsInDeathCam) + { + if (!player.IsEliminated) + { + if (player.HasEscaped) + { + continue; + } + + int maxBars = 12; + int bars; + char buffer2[64]; + if (!SF_IsRaidMap() && !SF_IsBoxingMap()) + { + float percent = float(player.Health) / float(player.MaxHealth); + if (percent > 1.0) + { + percent = 1.0; + } + bars = RoundToCeil(float(maxBars) * percent); + if (bars > maxBars) + { + bars = maxBars; + } + FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_HEALTH_SYMBOL); + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + } + + if (!SF_SpecialRound(SPECIALROUND_LIGHTSOUT) && !SF_IsRaidMap() && !SF_IsBoxingMap()) + { + bars = RoundToCeil(float(maxBars) * ClientGetFlashlightBatteryLife(i)); + if (bars > maxBars) + { + bars = maxBars; + } + + FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL); + StrCat(buffer, sizeof(buffer), buffer2); + + if (IsInfiniteFlashlightEnabled()) + { + StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); + } + else + { + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + } + } + + bars = RoundToCeil(float(maxBars) * player.Stamina); + if (bars > maxBars) + { + bars = maxBars; + } + + FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_SPRINT_SYMBOL); + StrCat(buffer, sizeof(buffer), buffer2); + + if (IsInfiniteSprintEnabled()) + { + StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); + } + else + { + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + } + + float healthRatio = float(player.Health) / float(player.MaxHealth); + int color[3]; + for (int i2 = 0; i2 < 3; i2++) + { + color[i2] = RoundFloat(float(hudColorHealthy[i2]) + (float(hudColorCritical[i2] - hudColorHealthy[i2]) * (1.0 - healthRatio))); + } + if (!SF_IsRaidMap() && !SF_IsBoxingMap()) + { + SetHudTextParams(0.035, 0.83, + 0.3, + color[0], + color[1], + color[2], + 40, + _, + 1.0, + 0.07, + 0.5); + } + else if (SF_IsRaidMap() || SF_IsBoxingMap()) + { + SetHudTextParams(0.035, 0.43, + 0.3, + color[0], + color[1], + color[2], + 40, + _, + 1.0, + 0.07, + 0.5); + } + ShowSyncHudText(player.index, g_HudSync2, buffer); + if (!SF_IsRaidMap() && !SF_IsBoxingMap() && !IsInfiniteBlinkEnabled() && player.HasStartedBlinking) + { + bars = RoundToCeil(float(maxBars) * player.BlinkMeter); + if (bars > maxBars) + { + bars = maxBars; + } + + if (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud) + { + if (bars != 0) + { + FormatEx(buffer, sizeof(buffer), "\n%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_ON); + } + else + { + FormatEx(buffer, sizeof(buffer), "\n%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OFF); + } + } + else + { + FormatEx(buffer, sizeof(buffer), "\n%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OLD); + } + + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + + SetHudTextParams(0.8, 0.83, + 0.3, + color[0], + color[1], + color[2], + 40, + _, + 1.0, + 0.07, + 0.5); + ShowSyncHudText(player.index, g_HudSync4, buffer); + } + buffer[0] = '\0'; + } + else + { + if (player.IsProxy) + { + int maxBars = 12; + int bars = RoundToCeil(float(maxBars) * (float(player.ProxyControl) / 100.0)); + if (bars > maxBars) + { + bars = maxBars; + } + + strcopy(buffer, sizeof(buffer), "CONTROL\n"); + + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + + SetHudTextParams(-1.0, 0.83, + 0.3, + SF2_HUD_TEXT_COLOR_R, + SF2_HUD_TEXT_COLOR_G, + SF2_HUD_TEXT_COLOR_B, + 40, + _, + 1.0, + 0.07, + 0.5); + ShowSyncHudText(player.index, g_HudSync2, buffer); + } + } + } + player.UpdateListeningFlags(); + player.UpdateMusicSystem(); + + Call_StartForward(g_OnPlayerAverageUpdatePFwd); + Call_PushCell(player); + Call_Finish(); + } + + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/ultravision.sp b/addons/sourcemod/scripting/sf2/client/ultravision.sp new file mode 100644 index 00000000..53b1209e --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/ultravision.sp @@ -0,0 +1,302 @@ +#pragma semicolon 1 + +#define SF2_ULTRAVISION_CONE 180.0 +#define SF2_ULTRAVISION_WIDTH 800.0 +#define SF2_ULTRAVISION_LENGTH 800.0 +#define SF2_ULTRAVISION_BRIGHTNESS -4 // Intensity of Ultravision. + +// Ultravision data. +bool g_PlayerHasUltravision[MAXTF2PLAYERS] = { false, ... }; +int g_PlayerUltravisionEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; + +void SetupUltravision() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientDeactivateUltravision(client.index); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + ClientDeactivateUltravision(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientDeactivateUltravision(client.index); + if (!client.IsEliminated && !client.HasEscaped) + { + ClientActivateUltravision(client.index); + } +} + +static void OnPlayerDeath(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientDeactivateUltravision(client.index); +} + +bool IsClientUsingUltravision(int client) +{ + return g_PlayerHasUltravision[client]; +} + +void ClientActivateUltravision(int client, bool nightVision = false) +{ + if (!g_Enabled) + { + return; + } + + if (!IsValidClient(client) || IsClientUsingUltravision(client)) + { + return; + } + + if (!g_PlayerEliminated[client] && (SF_SpecialRound(SPECIALROUND_NOULTRAVISION) && !nightVision)) + { + return; + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientActivateUltravision(%d)", client); + } + #endif + + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + + g_PlayerHasUltravision[client] = true; + + int ent = CreateEntityByName("light_dynamic"); + if (ent != -1) + { + float eyePos[3]; + GetClientEyePosition(client, eyePos); + + TeleportEntity(ent, eyePos, { 90.0, 0.0, 0.0 }, NULL_VECTOR); + if (nightVision && !g_PlayerEliminated[client]) + { + switch (g_NightVisionType) + { + case 0: // Green + { + DispatchKeyValue(ent, "rendercolor", "50 255 50"); + } + case 1: // Red + { + DispatchKeyValue(ent, "rendercolor", "255 50 50"); + } + case 2: // Blue + { + DispatchKeyValue(ent, "rendercolor", "50 50 255"); + } + } + } + else + { + DispatchKeyValue(ent, "rendercolor", "100 200 255"); + } + + float radius = 0.0; + if (g_PlayerEliminated[client]) + { + radius = g_UltravisionRadiusBlueConVar.FloatValue; + } + else + { + radius = g_UltravisionRadiusRedConVar.FloatValue; + if (nightVision) + { + radius = g_NightvisionRadiusConVar.FloatValue; + if (!IsClassConfigsValid()) + { + radius = g_NightvisionRadiusConVar.FloatValue; + } + else + { + radius = g_NightvisionRadiusConVar.FloatValue * g_ClassNightvisionRadiusMultiplier[classToInt]; + } + } + if (!nightVision && class == TFClass_Sniper && !IsClassConfigsValid()) + { + radius *= 2.0; + } + else if (!nightVision && IsClassConfigsValid()) + { + radius *= g_ClassUltravisionRadiusMultiplier[classToInt]; + } + } + + SetVariantFloat(radius); + AcceptEntityInput(ent, "spotlight_radius"); + SetVariantFloat(radius); + AcceptEntityInput(ent, "distance"); + + SetVariantInt(-15); // Start dark, then fade in via the Timer_UltravisionFadeInEffect timer func. + AcceptEntityInput(ent, "brightness"); + if (nightVision && !g_PlayerEliminated[client]) + { + if (!IsClassConfigsValid()) + { + SetVariantInt(5); + AcceptEntityInput(ent, "brightness"); + } + else + { + int roundedBrightness = RoundToNearest(5.0 * g_ClassNightvisionBrightnessMultiplier[classToInt]); + SetVariantInt(roundedBrightness); + AcceptEntityInput(ent, "brightness"); + } + } + + // Convert WU to inches. + float cone = SF2_ULTRAVISION_CONE; + cone *= 0.75; + + SetVariantInt(RoundToFloor(cone)); + AcceptEntityInput(ent, "_inner_cone"); + SetVariantInt(0); + AcceptEntityInput(ent, "_cone"); + DispatchSpawn(ent); + ActivateEntity(ent); + SetVariantString("!activator"); + AcceptEntityInput(ent, "SetParent", client); + AcceptEntityInput(ent, "TurnOn"); + //SetEntityRenderFx(ent, RENDERFX_SOLID_FAST); + SetEntityRenderColor(ent, 150, 225, 255, 255); + if (nightVision && !g_PlayerEliminated[client]) + { + switch (g_NightVisionType) + { + case 0: // Green + { + DispatchKeyValue(ent, "rendercolor", "50 255 50"); + } + case 1: // Red + { + DispatchKeyValue(ent, "rendercolor", "255 50 50"); + } + case 2: // Blue + { + DispatchKeyValue(ent, "rendercolor", "50 50 255"); + } + } + } + + g_PlayerUltravisionEnt[client] = EntIndexToEntRef(ent); + + SDKHook(ent, SDKHook_SetTransmit, Hook_UltravisionSetTransmit); + + // Fade in effect. + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer || IsClientInGhostMode(client)) + { + CreateTimer(0.0, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + else if (class == TFClass_Engineer && !IsClientInGhostMode(client)) + { + CreateTimer(0.15, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + else + { + CreateTimer(g_ClassUltravisionFadeInTimer[classToInt], Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientActivateUltravision(%d)", client); + } + #endif +} + +static Action Timer_UltravisionFadeInEffect(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); + if (!ent || ent == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } + + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + + int brightness = GetEntProp(ent, Prop_Send, "m_Exponent"); + int maxBrightness = g_UltravisionBrightnessConVar.IntValue; + if (!IsClassConfigsValid()) + { + if (class == TFClass_Sniper) + { + maxBrightness = RoundToNearest(float(maxBrightness) * 0.75); + } + } + else + { + maxBrightness = RoundToNearest(float(maxBrightness) * g_ClassUltravisionBrightnessMultiplier[classToInt]); + } + if (brightness >= maxBrightness) + { + return Plugin_Stop; + } + + brightness++; + SetVariantInt(brightness); + AcceptEntityInput(ent, "brightness"); + + return Plugin_Continue; +} + +void ClientDeactivateUltravision(int client) +{ + if (!IsClientUsingUltravision(client)) + { + return; + } + + g_PlayerHasUltravision[client] = false; + + int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); + if (ent != INVALID_ENT_REFERENCE) + { + AcceptEntityInput(ent, "TurnOff"); + RemoveEntity(ent); + } + + g_PlayerUltravisionEnt[client] = INVALID_ENT_REFERENCE; +} + +static Action Hook_UltravisionSetTransmit(int ent,int other) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (!g_UltravisionEnabledConVar.BoolValue || EntRefToEntIndex(g_PlayerUltravisionEnt[other]) != ent || !IsPlayerAlive(other)) + { + return Plugin_Handled; + } + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/weapons.sp b/addons/sourcemod/scripting/sf2/client/weapons.sp new file mode 100644 index 00000000..6709c876 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/weapons.sp @@ -0,0 +1,818 @@ +#pragma newdecls required +#pragma semicolon 1 + +static KeyMap g_RestrictedWeapons = null; + +// Weapon Handles +static Handle g_WeaponHandles[10][10]; + +void SetupPlayerWeapons() +{ + ReloadRestrictedWeapons(); + SetupClassDefaultWeapons(); +} + +void SF2_RefreshRestrictions() +{ + for(int client = 1; client <= MaxClients; client++) + { + if (IsValidClient(client) && (!g_PlayerEliminated[client] || (g_PlayerEliminated[client] && !IsClientInPvP(client) && !IsClientInPvE(client)))) + { + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + g_PlayerPostWeaponsTimer[client] = CreateTimer(1.0, Timer_ClientPostWeapons, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + } +} + +static void SetupClassDefaultWeapons() +{ + // Scout + g_WeaponHandles[view_as(TFClass_Scout)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_scattergun", 13, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Scout)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_pistol", 23, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Scout)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_bat", 0, 0, 0, "", true); + + // Sniper + g_WeaponHandles[view_as(TFClass_Sniper)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_sniperrifle", 14, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Sniper)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_smg", 16, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Sniper)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_club", 3, 0, 0, "", true); + + // Soldier + g_WeaponHandles[view_as(TFClass_Soldier)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_rocketlauncher", 18, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Soldier)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_shotgun", 10, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Soldier)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_shovel", 6, 0, 0, "", true); + + // Demoman + g_WeaponHandles[view_as(TFClass_DemoMan)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_grenadelauncher", 19, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_DemoMan)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_pipebomblauncher", 20, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_DemoMan)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_bottle", 1, 0, 0, "", true); + + // Medic + g_WeaponHandles[view_as(TFClass_Medic)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_syringegun_medic", 17, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Medic)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_medigun", 29, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Medic)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_bonesaw", 8, 0, 0, "", true); + + // Heavy + g_WeaponHandles[view_as(TFClass_Heavy)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_minigun", 15, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Heavy)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_shotgun", 11, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Heavy)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_fists", 5, 0, 0, "", true); + + // Pyro + g_WeaponHandles[view_as(TFClass_Pyro)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_flamethrower", 21, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Pyro)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_shotgun", 12, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Pyro)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_fireaxe", 2, 0, 0, "", true); + + // Spy + g_WeaponHandles[view_as(TFClass_Spy)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_revolver", 24, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Spy)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_knife", 4, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Spy)][TFWeaponSlot_Building] = PrepareItemHandle("tf_weapon_invis", 297, 0, 0, "", true); + + // Engineer + g_WeaponHandles[view_as(TFClass_Engineer)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_shotgun", 9, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Engineer)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_pistol", 22, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Engineer)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_wrench", 7, 0, 0, "", true); +} + +Handle GetClassStockWeaponHandle(TFClassType tfClass, int slot) +{ + return g_WeaponHandles[tfClass][slot]; +} + +void ReloadRestrictedWeapons() +{ + if (g_RestrictedWeapons != null) + { + CleanupKeyMap(g_RestrictedWeapons); + } + + char buffer[PLATFORM_MAX_PATH]; + if (!g_UseAlternateConfigDirectoryConVar.BoolValue) + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_RESTRICTEDWEAPONS); + } + else + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_RESTRICTEDWEAPONS_DATA); + } + KeyValues kv = new KeyValues("root"); + if (!FileToKeyValues(kv, buffer)) + { + delete kv; + LogError("Failed to load restricted weapons list! File not found!"); + return; + } + + g_RestrictedWeapons = KeyValuesToKeyMap(kv); + delete kv; +} + +Action Timer_ClientPostWeapons(Handle timer, any userid) +{ + SF2_BasePlayer client = SF2_BasePlayer(GetClientOfUserId(userid)); + if (!client.IsValid || !client.IsAlive) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPostWeaponsTimer[client.index]) + { + return Plugin_Stop; + } + + g_PlayerHasRegenerationItem[client.index] = false; + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 0) + { + DebugMessage("START Timer_ClientPostWeapons(%d)", client.index); + } + + int oldWeaponItemIndexes[6] = { -1, ... }; + int newWeaponItemIndexes[6] = { -1, ... }; + + for (int i = 0; i <= 5; i++) + { + int weaponEnt = client.GetWeaponSlot(i); + if (!IsValidEdict(weaponEnt)) + { + continue; + } + + oldWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + } + #endif + + bool removeWeapons = true; + bool keepUtilityItems = false; + bool restrictWeapons = true; + bool useStock = false; + bool removeWearables = false; + bool preventAttack = false; + + if (IsRoundEnding()) + { + if (!client.IsEliminated) + { + removeWeapons = true; + restrictWeapons = true; + } + } + + if (client.IsEliminated) + { + removeWeapons = !g_PlayerKeepWeaponsConVar.BoolValue; + restrictWeapons = true; + preventAttack = true; + } + + // pvp + if (client.IsInPvP || (SF_IsRaidMap() && !client.IsEliminated) || IsClientInWeaponsTrigger(client.index)) + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = false; + preventAttack = false; + } + + if (IsRoundInWarmup()) + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = false; + } + + if (client.IsInPvE) + { + if (IsPvEBoxing()) + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = true; + preventAttack = false; + } + else + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = false; + preventAttack = false; + } + } + + if (client.IsProxy) + { + restrictWeapons = true; + removeWeapons = true; + useStock = true; + removeWearables = true; + keepUtilityItems = false; + preventAttack = false; + } + + if (client.IsInGhostMode) + { + removeWeapons = true; + } + + if (SF_IsBoxingMap() && !client.IsEliminated && !IsRoundEnding()) + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = true; + preventAttack = false; + } + + if (removeWeapons && !keepUtilityItems) + { + for (int i = 0; i <= 5; i++) + { + if (i == TFWeaponSlot_Melee && !client.IsInGhostMode) + { + continue; + } + TF2_RemoveWeaponSlotAndWearables(client.index, i); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_demoshield")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + client.SwitchToWeaponSlot(TFWeaponSlot_Melee); + } + + if (keepUtilityItems) + { + for (int i = 0; i <= 5; i++) + { + if ((i == TFWeaponSlot_Melee || i == TFWeaponSlot_Secondary) && !client.IsInGhostMode) + { + continue; + } + TF2_RemoveWeaponSlotAndWearables(client.index, i); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + int weaponEnt = INVALID_ENT_REFERENCE; + weaponEnt = client.GetWeaponSlot(TFWeaponSlot_Secondary); + + if (IsValidEdict(weaponEnt)) + { + int itemIndex = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemIndex) + { + case 163, 129, 226, 354, 1001, 131, 406, 1099, 42, 159, 311, 433, 863, 1002, 1190: + { + //Do nothing + } + default: + { + TF2_RemoveWeaponSlotAndWearables(client.index, TFWeaponSlot_Secondary); + } + } + } + } + + if (removeWearables) + { + TF2_StripWearables(client.index); + } + + TFClassType playerClass = client.Class; + int classToInt = view_as(playerClass); + + if (restrictWeapons) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsWeaponRestricted(client, itemIndex) && GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_razorback")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsWeaponRestricted(client, itemIndex) && GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_demoshield")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsWeaponRestricted(client, itemIndex) && GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + if (g_RestrictedWeapons != null) + { + int weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = TFWeaponSlot_Primary; slot <= 6; slot++) + { + weaponEnt = client.GetWeaponSlot(slot); + if (!IsValidEntity(weaponEnt)) + { + continue; + } + + if (useStock || IsWeaponRestricted(client, GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"))) + { + TF2_RemoveWeaponSlotAndWearables(client.index, slot); + + if (GetClassStockWeaponHandle(playerClass, slot) != null) + { + int newWeapon = TF2Items_GiveNamedItem(client.index, GetClassStockWeaponHandle(playerClass, slot)); + if (IsValidEntity(newWeapon)) + { + EquipPlayerWeapon(client.index, newWeapon); + } + } + } + else + { + if (!client.HasRegenItem) + { + client.HasRegenItem = IsRegenWeapon(weaponEnt); + } + } + } + } + } + + // Change stats on some weapons. + if (!client.IsEliminated) + { + int weaponEnt = INVALID_ENT_REFERENCE; + Handle weaponHandle = null; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = client.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + bool resetHealth = false; + switch (itemDef) + { + case 214: // Powerjack + { + weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 214, 0, 0, "180 ; 12.0 ; 412 ; 1.2"); + } + case 310: //The Warrior's Spirit + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 310, 0, 0, "2 ; 1.3 ; 412 ; 1.3"); + } + case 326: // The Back Scratcher + { + weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 326, 0, 0, "2 ; 1.25 ; 412 ; 1.25 ; 69 ; 0.25 ; 108 ; 1.25"); + } + case 304: // Amputator + { + if (!SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "200 ; 0.0 ; 57 ; 2 ; 1 ; 0.8"); + } + else + { + weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "1 ; 0.8"); + } + } + case 239: //GRU + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 239, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); + resetHealth = true; + } + case 1100: //Bread Bite + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 1100, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); + resetHealth = true; + } + case 426: //Eviction Notice + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 426, 0, 0, "6 ; 0.6 ; 107 ; 1.15 ; 737 ; 4.0 ; 1 ; 0.4 ; 412 ; 1.2"); + } + case 775: //The Escape Plan (Its like, real buggy on wearer) + { + weaponHandle = PrepareItemHandle("tf_weapon_shovel", 775, 0, 0, "414 ; 1 ; 734 ; 0.1"); + } + case 452: //Three Rune Blade + { + weaponHandle = PrepareItemHandle("tf_weapon_bat", 452, 0, 0, ""); + } + case 325: //Boston Basher + { + weaponHandle = PrepareItemHandle("tf_weapon_bat", 325, 0, 0, ""); + } + case 450: //Atomizer + { + weaponHandle = PrepareItemHandle("tf_weapon_bat", 450, 0, 0, ""); + } + case 225: //Your Eternal Reward + { + weaponHandle = PrepareItemHandle("tf_weapon_knife", 225, 0, 0, ""); + } + case 649: //Spy-cicle + { + weaponHandle = PrepareItemHandle("tf_weapon_knife", 649, 0, 0, ""); + } + case 574: //Spy-cicle + { + weaponHandle = PrepareItemHandle("tf_weapon_knife", 574, 0, 0, ""); + } + } + if (weaponHandle != null) + { + client.RemoveWeaponSlot(slot); + int entity = TF2Items_GiveNamedItem(client.index, weaponHandle); + EquipPlayerWeapon(client.index, entity); + if (resetHealth && !IsRoundPlaying()) + { + SetEntityHealth(client.index, client.MaxHealth); + } + delete weaponHandle; + } + } + } + + if (preventAttack) + { + int weaponEnt = INVALID_ENT_REFERENCE; + while ((weaponEnt = FindEntityByClassname(weaponEnt, "tf_wearable_demoshield")) != INVALID_ENT_REFERENCE) + { + if (GetEntPropEnt(weaponEnt, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(weaponEnt); + } + } + + for (int slot = 0; slot <= 5; slot++) + { + if (slot == TFWeaponSlot_Melee) + { + continue; + } + + weaponEnt = client.GetWeaponSlot(slot); + if (!IsValidEntity(weaponEnt)) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDef) + { + case 30, 212, 59, 60, 297, 947, 1101: // Invis Watch, Base Jumper + { + TF2_RemoveWeaponSlotAndWearables(client.index, slot); + } + + default: + { + SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextPrimaryAttack", 99999999.9); + SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextSecondaryAttack", 99999999.9); + } + } + } + } + + //Remove the teleport ability + if (client.IsInPvP || client.IsInPvE || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !client.IsEliminated) || IsClientInWeaponsTrigger(client.index)) + { + int weaponEnt = INVALID_ENT_REFERENCE; + Handle weaponHandle = null; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = client.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDef) + { + case 589: // Eureka Effect + { + client.RemoveWeaponSlot(slot); + + weaponHandle = PrepareItemHandle("tf_weapon_wrench", 589, 0, 0, "93 ; 0.5 ; 732 ; 0.5"); + int entity = TF2Items_GiveNamedItem(client.index, weaponHandle); + EquipPlayerWeapon(client.index, entity); + } + } + if (weaponHandle != null) + { + delete weaponHandle; + } + } + } + //Force them to take their melee wep, it prevents the civilian bug. + client.SwitchToWeaponSlot(TFWeaponSlot_Melee); + + // Remove all hats. + if (client.IsInGhostMode) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_campaign_item")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + } + + float healthFromPack = 1.0; + if (!IsClassConfigsValid()) + { + if (!client.IsEliminated && !SF_IsBoxingMap()) + { + if (client.HasRegenItem) + { + healthFromPack = 0.40; + } + if (client.Class == TFClass_Medic) + { + healthFromPack = 0.0; + } + } + } + else + { + if (!client.IsEliminated && !SF_IsBoxingMap()) + { + healthFromPack = g_ClassHealthPickupMultiplier[classToInt]; + if (client.HasRegenItem) + { + healthFromPack -= 0.6; + } + if (healthFromPack <= 0.0) + { + healthFromPack = 0.0; + } + } + } + + TF2Attrib_SetByDefIndex(client.index, 109, healthFromPack); + + #if defined DEBUG + int weaponEnt = INVALID_ENT_REFERENCE; + + for (int i = 0; i <= 5; i++) + { + weaponEnt = client.GetWeaponSlot(i); + if (!IsValidEdict(weaponEnt)) + { + continue; + } + + newWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + } + + if (g_DebugDetailConVar.IntValue > 0) + { + for (int i = 0; i <= 5; i++) + { + DebugMessage("-> slot %d: %d (old: %d)", i, newWeaponItemIndexes[i], oldWeaponItemIndexes[i]); + } + + DebugMessage("END Timer_ClientPostWeapons(%d) -> remove = %d, restrict = %d", client.index, removeWeapons, restrictWeapons); + } + #endif + + Call_StartForward(g_OnPlayerPostWeaponsPFwd); + Call_PushCell(client); + Call_Finish(); + + return Plugin_Stop; +} + +public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int itemDefinitionIndex, Handle &itemHandle) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + switch (itemDefinitionIndex) + { + case 642: + { + Handle itemOverride = PrepareItemHandle("tf_wearable", 642, 0, 0, "376 ; 1.0 ; 377 ; 0.2 ; 57 ; 2 ; 412 ; 1.10"); + + if (itemOverride != null) + { + itemHandle = itemOverride; + + return Plugin_Changed; + } + delete itemOverride; + itemOverride = null; + } + } + + return Plugin_Continue; +} + +bool IsRegenWeapon(int weaponEnt) +{ + Address attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 1003); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 490); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 190); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 130); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 57); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 220); + if (attribRegen != Address_Null) + { + return true; + } + return false; +} + +bool IsWeaponRestricted(SF2_BasePlayer client, int itemDefInt) +{ + if (g_RestrictedWeapons == null) + { + return false; + } + + bool returnBool = false; + + char itemDef[32]; + FormatEx(itemDef, sizeof(itemDef), "%d", itemDefInt); + + bool proxyBoss = SF_IsProxyMap(); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); + if (!Npc.IsValid()) + { + continue; + } + if (Npc.GetProfileDataEx().GetProxies().IsEnabled(Npc.Difficulty)) + { + proxyBoss = true; + break; + } + } + + char className[32]; + TF2_GetClassName(client.Class, className, sizeof(className)); + KeyMap section = g_RestrictedWeapons.GetSection(className); + if (section != null) + { + char restriction[32]; + section.GetString(itemDef, restriction, sizeof(restriction), "0"); + if (StrContains(restriction, "1") != -1 && !client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "2") != -1 && !client.IsEliminated && proxyBoss) + { + returnBool = true; + } + + if (StrContains(restriction, "3") != -1 && client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "4") != -1 && client.IsInPvP) + { + returnBool = true; + } + + if (StrContains(restriction, "5") != -1) + { + returnBool = true; + } + + if (StrContains(restriction, "6") != -1 && client.IsInPvE) + { + returnBool = true; + } + + if (StrContains(restriction, "7") != -1 && !client.IsEliminated && SF_IsBoxingMap()) + { + returnBool = true; + } + + if (StrContains(restriction, "8") != -1 && !client.IsEliminated && SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + returnBool = true; + } + } + + section = g_RestrictedWeapons.GetSection("all"); + if (section != null) + { + char restriction[32]; + section.GetString(itemDef, restriction, sizeof(restriction), "0"); + if (StrContains(restriction, "1") != -1 && !client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "2") != -1 && !client.IsEliminated && proxyBoss) + { + returnBool = true; + } + + if (StrContains(restriction, "3") != -1 && client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "4") != -1 && client.IsInPvP) + { + returnBool = true; + } + + if (StrContains(restriction, "5") != -1) + { + returnBool = true; + } + + if (StrContains(restriction, "6") != -1 && client.IsInPvE) + { + returnBool = true; + } + + if (StrContains(restriction, "7") != -1 && !client.IsEliminated && SF_IsBoxingMap()) + { + returnBool = true; + } + + if (StrContains(restriction, "8") != -1 && !client.IsEliminated && SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + returnBool = true; + } + } + + return returnBool; +} diff --git a/addons/sourcemod/scripting/sf2/debug.sp b/addons/sourcemod/scripting/sf2/debug.sp index 021c9138..e08792a0 100644 --- a/addons/sourcemod/scripting/sf2/debug.sp +++ b/addons/sourcemod/scripting/sf2/debug.sp @@ -20,8 +20,9 @@ #define DEBUG_BOSS_ANIMATION (1 << 10) #define DEBUG_EVENT (1 << 11) #define DEBUG_KILLICONS (1 << 12) -#define DEBUG_ARRAYLIST (1 << 13) #define DEBUG_BOSS_IDLE (1 << 14) +#define DEBUG_BOSS_EYES (1 << 15) +#define DEBUG_BOSS_PATH (1 << 16) int g_PlayerDebugFlags[MAXTF2PLAYERS] = { 0, ... }; @@ -45,8 +46,9 @@ void InitializeDebug() RegAdminCmd("sm_sf2_debug_ghost_mode", Command_DebugGhostMode, ADMFLAG_CHEATS); RegAdminCmd("sm_sf2_debug_events", Command_DebugEvent, ADMFLAG_CHEATS); RegAdminCmd("sm_sf2_debug_kill_icons", Command_DebugKillIcons, ADMFLAG_CHEATS); - RegAdminCmd("sm_sf2_debug_arraylists", Command_DebugArrayLists, ADMFLAG_CHEATS); RegAdminCmd("sm_sf2_debug_boss_idle", Command_DebugBossIdle, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_debug_boss_eyes", Command_DebugBossEyes, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_debug_boss_path", Command_DebugBossPath, ADMFLAG_CHEATS); } void InitializeDebugLogging() @@ -71,9 +73,9 @@ void DebugMessage(const char[] message, any ...) LogToFile(g_DebugLogFilePath, debugMessage); } -void SendDebugMessageToPlayer(int client,int debugFlags,int type, const char[] message, any ...) +void SendDebugMessageToPlayer(int client, int debugFlags, int type, const char[] message, any ...) { - if (!IsClientInGame(client) || IsFakeClient(client)) + if (!IsValidClient(client) || IsFakeClient(client)) { return; } @@ -101,14 +103,14 @@ void SendDebugMessageToPlayer(int client,int debugFlags,int type, const char[] m } } -void SendDebugMessageToPlayers(int debugFlags,int type, const char[] message, any ...) +void SendDebugMessageToPlayers(int debugFlags, int type, const char[] message, any ...) { char msg[1024]; VFormat(msg, sizeof(msg), message, 4); for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i)) + if (!IsValidClient(i) || IsFakeClient(i)) { continue; } @@ -134,14 +136,14 @@ void SendDebugMessageToPlayers(int debugFlags,int type, const char[] message, an } } -static Action Command_DebugBossTeleport(int client,int args) +static Action Command_DebugBossTeleport(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_TELEPORTATION); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_TELEPORTATION) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_TELEPORTATION; @@ -156,14 +158,14 @@ static Action Command_DebugBossTeleport(int client,int args) return Plugin_Handled; } -static Action Command_DebugBossChase(int client,int args) +static Action Command_DebugBossChase(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_CHASE); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_CHASE) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_CHASE; @@ -178,14 +180,14 @@ static Action Command_DebugBossChase(int client,int args) return Plugin_Handled; } -static Action Command_DebugBossIdle(int client,int args) +static Action Command_DebugBossIdle(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_IDLE); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_IDLE) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_IDLE; @@ -200,14 +202,58 @@ static Action Command_DebugBossIdle(int client,int args) return Plugin_Handled; } -static Action Command_DebugBossAnimation(int client,int args) +static Action Command_DebugBossEyes(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_ANIMATION); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_EYES) != 0; + if (!inMode) + { + g_PlayerDebugFlags[client] |= DEBUG_BOSS_EYES; + PrintToChat(client, "Enabled debugging boss eyes."); + } + else + { + g_PlayerDebugFlags[client] &= ~DEBUG_BOSS_EYES; + PrintToChat(client, "Disabled debugging boss eyes."); + } + + return Plugin_Handled; +} + +static Action Command_DebugBossPath(int client, int args) +{ + if (client < 1 || client > MaxClients) + { + return Plugin_Handled; + } + + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_PATH) != 0; + if (!inMode) + { + g_PlayerDebugFlags[client] |= DEBUG_BOSS_PATH; + PrintToChat(client, "Enabled debugging boss pathing."); + } + else + { + g_PlayerDebugFlags[client] &= ~DEBUG_BOSS_PATH; + PrintToChat(client, "Disabled debugging boss pathing."); + } + + return Plugin_Handled; +} + +static Action Command_DebugBossAnimation(int client, int args) +{ + if (client < 1 || client > MaxClients) + { + return Plugin_Handled; + } + + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_ANIMATION) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_ANIMATION; @@ -222,14 +268,14 @@ static Action Command_DebugBossAnimation(int client,int args) return Plugin_Handled; } -static Action Command_DebugNextbot(int client,int args) +static Action Command_DebugNextbot(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_NEXTBOT); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_NEXTBOT) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_NEXTBOT; @@ -251,7 +297,7 @@ static Action Command_DebugPlayerStress(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_PLAYER_STRESS); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_PLAYER_STRESS) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_PLAYER_STRESS; @@ -273,7 +319,7 @@ static Action Command_DebugBossProxies(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_PROXIES); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_PROXIES) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_PROXIES; @@ -294,7 +340,7 @@ static Action Command_DebugHitbox(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_HITBOX); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_HITBOX) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_HITBOX; @@ -316,7 +362,7 @@ static Action Command_DebugStun(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_STUN); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_STUN) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_STUN; @@ -338,7 +384,7 @@ static Action Command_DebugGhostMode(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_GHOSTMODE); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_GHOSTMODE) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_GHOSTMODE; @@ -360,7 +406,7 @@ static Action Command_DebugEntity(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_ENTITIES); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_ENTITIES) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_ENTITIES; @@ -375,14 +421,14 @@ static Action Command_DebugEntity(int client, int args) return Plugin_Handled; } -static Action Command_DebugEvent(int client,int args) +static Action Command_DebugEvent(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_EVENT); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_EVENT) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_EVENT; @@ -397,14 +443,14 @@ static Action Command_DebugEvent(int client,int args) return Plugin_Handled; } -static Action Command_DebugKillIcons(int client,int args) +static Action Command_DebugKillIcons(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_KILLICONS); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_KILLICONS) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_KILLICONS; @@ -419,24 +465,49 @@ static Action Command_DebugKillIcons(int client,int args) return Plugin_Handled; } -static Action Command_DebugArrayLists(int client,int args) +void TE_DrawBox(int client, float origin[3], float endOrigin[3], float mins[3], float maxs[3], float duration = 0.1, int laserIndex, int color[4]) { - if (client < 1 || client > MaxClients) - { - return Plugin_Handled; - } - - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_ARRAYLIST); - if (!inMode) + if( mins[0] == maxs[0] && mins[1] == maxs[1] && mins[2] == maxs[2] ) { - g_PlayerDebugFlags[client] |= DEBUG_ARRAYLIST; - PrintToChat(client, "Enabled debugging array lists."); + mins = {-15.0, -15.0, -15.0}; + maxs = {15.0, 15.0, 15.0}; } else { - g_PlayerDebugFlags[client] &= ~DEBUG_ARRAYLIST; - PrintToChat(client, "Disabled debugging array lists."); - } + AddVectors(endOrigin, maxs, maxs); + AddVectors(origin, mins, mins); + } + + float pos1[3], pos2[3], pos3[3], pos4[3], pos5[3], pos6[3]; + pos1 = maxs; + pos1[0] = mins[0]; + pos2 = maxs; + pos2[1] = mins[1]; + pos3 = maxs; + pos3[2] = mins[2]; + pos4 = mins; + pos4[0] = maxs[0]; + pos5 = mins; + pos5[1] = maxs[1]; + pos6 = mins; + pos6[2] = maxs[2]; + + TE_SendBeam(client, maxs, pos1, duration, laserIndex, color); + TE_SendBeam(client, maxs, pos2, duration, laserIndex, color); + TE_SendBeam(client, maxs, pos3, duration, laserIndex, color); + TE_SendBeam(client, pos6, pos1, duration, laserIndex, color); + TE_SendBeam(client, pos6, pos2, duration, laserIndex, color); + TE_SendBeam(client, pos6, mins, duration, laserIndex, color); + TE_SendBeam(client, pos4, mins, duration, laserIndex, color); + TE_SendBeam(client, pos5, mins, duration, laserIndex, color); + TE_SendBeam(client, pos5, pos1, duration, laserIndex, color); + TE_SendBeam(client, pos5, pos3, duration, laserIndex, color); + TE_SendBeam(client, pos4, pos3, duration, laserIndex, color); + TE_SendBeam(client, pos4, pos2, duration, laserIndex, color); +} - return Plugin_Handled; +void TE_SendBeam(int client, float mins[3], float maxs[3], float duration = 0.1, int laserIndex, int color[4]) +{ + TE_SetupBeamPoints(mins, maxs, laserIndex, laserIndex, 0, 30, duration, 1.0, 1.0, 1, 0.0, color, 30); + TE_SendToClient(client); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index d9008fe5..1c036a0f 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -8,10 +8,30 @@ static EffectEvent g_EntityEffectEvent[2049]; static EffectType g_EntityEffectType[2049]; static ArrayList g_NpcEffectsArray[MAX_BOSSES]; -static float g_EffectSpotlightEndLength[2049]; -static int g_EffectSpotlightEndEntity[2049] = { INVALID_ENT_REFERENCE, ... }; -void SlenderSpawnEffects(int bossIndex) +void InitializeEffects() +{ + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_NpcEffectsArray[i] == null) + { + continue; + } + + int index = g_NpcEffectsArray[i].FindValue(ent.index); + if (index != -1) + { + g_NpcEffectsArray[i].Erase(index); + } + } +} + +void SlenderSpawnEffects(ArrayList effects, int bossIndex, bool nonEffects = true, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE, bool noParenting = false) { if (bossIndex < 0 || bossIndex >= MAX_BOSSES) { @@ -24,8 +44,6 @@ void SlenderSpawnEffects(int bossIndex) return; } - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int slenderEnt = NPCGetEntIndex(bossIndex); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -36,127 +54,126 @@ void SlenderSpawnEffects(int bossIndex) g_NpcEffectsArray[bossIndex] = new ArrayList(); } - if (NPCGetDiscoModeState(bossIndex)) + if (nonEffects) { - int light = CreateEntityByName("light_dynamic"); - int particle = CreateEntityByName("info_particle_system"); - if (light != -1) + if (NPCGetDiscoModeState(bossIndex)) { - float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); - effectPos[2] = 50.0; - VectorTransform(effectPos, basePosX, baseAngX, effectPos); - AddVectors(effectAng, baseAngX, effectAng); - TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); - SetVariantInt(5); - AcceptEntityInput(light, "Brightness"); - SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); - AcceptEntityInput(light, "Distance"); - SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); - AcceptEntityInput(light, "spotlight_radius"); - SetVariantInt(1); - AcceptEntityInput(light, "cone"); - DispatchKeyValue(light, "spawnflags", "0"); - int rChase = GetRandomInt(75, 250); - int gChase = GetRandomInt(75, 250); - int bChase = GetRandomInt(75, 250); - SetEntityRenderColor(light, rChase, gChase, bChase, 255); - DispatchSpawn(light); - ActivateEntity(light); - AcceptEntityInput(light, "TurnOn"); - SetVariantString("!activator"); - AcceptEntityInput(light, "SetParent", slenderEnt); - CreateTimer(0.1, Timer_DiscoLight, EntIndexToEntRef(light), TIMER_REPEAT); - SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); - g_EntityEffectType[light] = EffectType_DynamicLight; - g_NpcEffectsArray[bossIndex].Push(light); - } + int light = CreateEntityByName("light_dynamic"); + int particle = CreateEntityByName("info_particle_system"); + if (light != -1) + { + float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; + GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); + GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); + effectPos[2] = 50.0; + VectorTransform(effectPos, basePosX, baseAngX, effectPos); + AddVectors(effectAng, baseAngX, effectAng); + TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); + SetVariantInt(5); + AcceptEntityInput(light, "Brightness"); + SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); + AcceptEntityInput(light, "Distance"); + SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); + AcceptEntityInput(light, "spotlight_radius"); + SetVariantInt(1); + AcceptEntityInput(light, "cone"); + DispatchKeyValue(light, "spawnflags", "0"); + int rChase = GetRandomInt(75, 250); + int gChase = GetRandomInt(75, 250); + int bChase = GetRandomInt(75, 250); + SetEntityRenderColor(light, rChase, gChase, bChase, 255); + DispatchSpawn(light); + ActivateEntity(light); + AcceptEntityInput(light, "TurnOn"); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", slenderEnt); + CreateTimer(0.1, Timer_DiscoLight, EntIndexToEntRef(light), TIMER_REPEAT); + SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); + g_EntityEffectType[light] = EffectType_DynamicLight; + g_NpcEffectsArray[bossIndex].Push(light); + } - if (particle != -1) - { - float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); - CopyVector(NPCGetDiscoModePos(bossIndex), effectPos); - VectorTransform(effectPos, basePosX, baseAngX, effectPos); - AddVectors(effectAng, baseAngX, effectAng); - TeleportEntity(particle, effectPos, effectAng, NULL_VECTOR); - - DispatchKeyValue(particle, "effect_name", "utaunt_disco_party"); - DispatchSpawn(particle); - ActivateEntity(particle); - AcceptEntityInput(particle, "start"); - SetVariantString("!activator"); - AcceptEntityInput(particle, "SetParent", slenderEnt); - SDKHook(particle, SDKHook_SetTransmit, Hook_EffectTransmitX); - g_EntityEffectType[particle] = EffectType_Particle; - g_NpcEffectsArray[bossIndex].Push(particle); + if (particle != -1) + { + float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; + GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); + GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); + CopyVector(NPCGetDiscoModePos(bossIndex), effectPos); + VectorTransform(effectPos, basePosX, baseAngX, effectPos); + AddVectors(effectAng, baseAngX, effectAng); + TeleportEntity(particle, effectPos, effectAng, NULL_VECTOR); + + DispatchKeyValue(particle, "effect_name", "utaunt_disco_party"); + DispatchSpawn(particle); + ActivateEntity(particle); + AcceptEntityInput(particle, "start"); + SetVariantString("!activator"); + AcceptEntityInput(particle, "SetParent", slenderEnt); + SDKHook(particle, SDKHook_SetTransmit, Hook_EffectTransmitX); + g_EntityEffectType[particle] = EffectType_Particle; + g_NpcEffectsArray[bossIndex].Push(particle); + } } - } - if (NPCGetFestiveLightState(bossIndex)) - { - int light = CreateEntityByName("light_dynamic"); - if (light != -1) + if (NPCGetFestiveLightState(bossIndex)) { - float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); - CopyVector(NPCGetFestiveLightPosition(bossIndex), effectPos); - CopyVector(NPCGetFestiveLightAngle(bossIndex), effectAng); - VectorTransform(effectPos, basePosX, baseAngX, effectPos); - AddVectors(effectAng, baseAngX, effectAng); - TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); - SetVariantInt(NPCGetFestiveLightBrightness(bossIndex)); - AcceptEntityInput(light, "Brightness"); - SetVariantFloat(NPCGetFestiveLightDistance(bossIndex)); - AcceptEntityInput(light, "Distance"); - SetVariantFloat(NPCGetFestiveLightRadius(bossIndex)); - AcceptEntityInput(light, "spotlight_radius"); - SetVariantInt(1); - AcceptEntityInput(light, "cone"); - DispatchKeyValue(light, "spawnflags", "0"); - int funnyFestive = GetRandomInt(1, 3); - switch (funnyFestive) + int light = CreateEntityByName("light_dynamic"); + if (light != -1) { - case 1: - { - SetEntityRenderColor(light, 230, 37, 37, 255); - } - case 2: + float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; + GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); + GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); + CopyVector(NPCGetFestiveLightPosition(bossIndex), effectPos); + CopyVector(NPCGetFestiveLightAngle(bossIndex), effectAng); + VectorTransform(effectPos, basePosX, baseAngX, effectPos); + AddVectors(effectAng, baseAngX, effectAng); + TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); + SetVariantInt(NPCGetFestiveLightBrightness(bossIndex)); + AcceptEntityInput(light, "Brightness"); + SetVariantFloat(NPCGetFestiveLightDistance(bossIndex)); + AcceptEntityInput(light, "Distance"); + SetVariantFloat(NPCGetFestiveLightRadius(bossIndex)); + AcceptEntityInput(light, "spotlight_radius"); + SetVariantInt(1); + AcceptEntityInput(light, "cone"); + DispatchKeyValue(light, "spawnflags", "0"); + int funnyFestive = GetRandomInt(1, 3); + switch (funnyFestive) { - SetEntityRenderColor(light, 94, 227, 79, 255); - } - case 3: - { - SetEntityRenderColor(light, 235, 235, 235, 255); + case 1: + { + SetEntityRenderColor(light, 230, 37, 37, 255); + } + case 2: + { + SetEntityRenderColor(light, 79, 227, 79, 255); + } + case 3: + { + SetEntityRenderColor(light, 255, 255, 255, 255); + } } + DispatchSpawn(light); + ActivateEntity(light); + CreateTimer(0.5, Timer_FestiveLight, EntIndexToEntRef(light), TIMER_REPEAT); + AcceptEntityInput(light, "TurnOn"); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", slenderEnt); + SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); + g_EntityEffectType[light] = EffectType_Particle; + g_NpcEffectsArray[bossIndex].Push(light); } - DispatchSpawn(light); - ActivateEntity(light); - CreateTimer(0.5, Timer_FestiveLight, EntIndexToEntRef(light), TIMER_REPEAT); - AcceptEntityInput(light, "TurnOn"); - SetVariantString("!activator"); - AcceptEntityInput(light, "SetParent", slenderEnt); - SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); - g_EntityEffectType[light] = EffectType_Particle; - g_NpcEffectsArray[bossIndex].Push(light); } } - ArrayList array = GetBossProfileEffectsArray(profile); - char sectionName[64]; - - if (array == null) + if (effects == null) { return; } - float basePos[3], baseAng[3]; SF2BossProfileBaseEffectInfo effectsInfo; - for (int i = 0, size = array.Length; i < size; i++) + for (int i = 0, size = effects.Length; i < size; i++) { - array.GetArray(i, effectsInfo, sizeof(effectsInfo)); + effects.GetArray(i, effectsInfo, sizeof(effectsInfo)); // Validate effect event and type. Check to see if it matches with ours. if (effectsInfo.Event != EffectEvent_Invalid) @@ -166,387 +183,457 @@ void SlenderSpawnEffects(int bossIndex) // Check base position behavior. if (!slenderEnt || slenderEnt == INVALID_ENT_REFERENCE) { - LogError("Could not spawn effect %s for boss %d: unable to read position due to boss entity not in game!"); + LogError("Could not spawn effect %s for boss %d: unable to read position and angles due to boss entity not in game!"); continue; } - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePos); - - if (!slenderEnt || slenderEnt == INVALID_ENT_REFERENCE) + if (effectsInfo.Delay > 0.0) { - LogError("Could not spawn effect %s for boss %d: unable to read angles due to boss entity not in game!"); - continue; + DataPack pack; + CreateDataTimer(effectsInfo.Delay, Timer_SpawnEffect, pack, TIMER_FLAG_NO_MAPCHANGE); + pack.WriteCellArray(effectsInfo, sizeof(effectsInfo)); + pack.WriteCell(bossIndex); + pack.WriteCellArray(overridePos, sizeof(overridePos)); + pack.WriteCellArray(overrideAng, sizeof(overrideAng)); + pack.WriteCell(output); + pack.WriteCell(entityOverride); + pack.WriteCell(noParenting); + } + else + { + SpawnEffect(effectsInfo, bossIndex, overridePos, overrideAng, output, entityOverride, noParenting); } + } + else + { + LogError("Could not spawn effect %s for boss %d: invalid type!", effectsInfo.SectionName, bossIndex); + } + } + } +} - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAng); +static Action Timer_SpawnEffect(Handle timer, DataPack pack) +{ + pack.Reset(); + SF2BossProfileBaseEffectInfo effect; + pack.ReadCellArray(effect, sizeof(effect)); + int bossIndex = pack.ReadCell(); + float overridePos[3], overrideAng[3]; + pack.ReadCellArray(overridePos, sizeof(overridePos)); + pack.ReadCellArray(overrideAng, sizeof(overrideAng)); + ArrayList output = pack.ReadCell(); + int entityOverride = pack.ReadCell(); + bool noParenting = pack.ReadCell(); + SpawnEffect(effect, bossIndex, overridePos, overrideAng, output, entityOverride, noParenting); + return Plugin_Stop; +} - int difficultyIndex = effectsInfo.DifficultyIndexes; - char indexes[8], currentIndex[2]; - FormatEx(indexes, sizeof(indexes), "%d", difficultyIndex); - FormatEx(currentIndex, sizeof(currentIndex), "%d", g_DifficultyConVar.IntValue); - char number = currentIndex[0]; - int difficultyNumber = 0; - if (FindCharInString(indexes, number) != -1) +static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE, bool noParenting = false) +{ + int slenderEnt = NPCGetEntIndex(bossIndex); + int attacher = slenderEnt; + if (entityOverride != INVALID_ENT_REFERENCE && effectsInfo.AttachPlayer) + { + attacher = entityOverride; + } + int difficulty = GetLocalGlobalDifficulty(bossIndex); + float basePos[3], baseAng[3]; + if (!IsEmptyVector(overridePos)) + { + basePos = overridePos; + } + else + { + GetEntPropVector(attacher, Prop_Data, "m_vecAbsOrigin", basePos); + } + if (!IsEmptyVector(overrideAng)) + { + baseAng = overrideAng; + } + else + { + GetEntPropVector(attacher, Prop_Data, "m_angAbsRotation", baseAng); + } + + int difficultyIndex = effectsInfo.DifficultyIndexes; + char indexes[8], currentIndex[2]; + FormatEx(indexes, sizeof(indexes), "%d", difficultyIndex); + FormatEx(currentIndex, sizeof(currentIndex), "%d", g_DifficultyConVar.IntValue); + char number = currentIndex[0]; + int difficultyNumber = 0; + if (FindCharInString(indexes, number) != -1) + { + difficultyNumber += g_DifficultyConVar.IntValue; + } + if (indexes[0] != '\0' && currentIndex[0] != '\0' && difficultyNumber != -1) + { + int currentIntegerIndex = StringToInt(currentIndex); + if (difficultyNumber != currentIntegerIndex) + { + return; + } + } + + int entity = -1; + bool isEntity = true; + + switch (effectsInfo.Type) + { + case EffectType_Steam: + { + entity = CreateEntityByName("env_steam"); + } + case EffectType_DynamicLight: + { + entity = CreateEntityByName("light_dynamic"); + } + case EffectType_Particle: + { + entity = CreateEntityByName("info_particle_system"); + } + case EffectType_Trail: + { + entity = CreateEntityByName("env_spritetrail"); + } + case EffectType_PropDynamic: + { + entity = CreateEntityByName("prop_dynamic"); + } + case EffectType_PointSpotlight: + { + entity = CreateEntityByName("sf2_point_spotlight"); + } + case EffectType_Sprite: + { + entity = CreateEntityByName("env_sprite"); + } + case EffectType_TempEntBeamRing, EffectType_TempEntParticle, EffectType_Sound: + { + isEntity = false; + } + } + + float effectPos[3], effectAng[3]; + + effectPos = effectsInfo.Origin; + effectAng = effectsInfo.Angles; + + VectorTransform(effectPos, basePos, baseAng, effectPos); + AddVectors(effectAng, baseAng, effectAng); + + if (entity != -1 && isEntity) + { + TeleportEntity(entity, effectPos, effectAng, NULL_VECTOR); + char value[PLATFORM_MAX_PATH]; + DispatchKeyValueInt(entity, "renderamt", effectsInfo.FadeAlpha); + DispatchKeyValueInt(entity, "rendermode", view_as(effectsInfo.RenderModes)); + DispatchKeyValueInt(entity, "renderfx", view_as(effectsInfo.RenderEffects)); + DispatchKeyValueInt(entity, "spawnflags", effectsInfo.SpawnFlags); + + switch (effectsInfo.Type) + { + case EffectType_Steam: + { + DispatchKeyValueInt(entity, "SpreadSpeed", effectsInfo.SteamSpreadSpeed); + DispatchKeyValueInt(entity, "Speed", effectsInfo.SteamSpeed); + DispatchKeyValueInt(entity, "StartSize", effectsInfo.SteamStartSize); + DispatchKeyValueInt(entity, "EndSize", effectsInfo.SteamEndSize); + DispatchKeyValueInt(entity, "Rate", effectsInfo.SteamRate); + DispatchKeyValueInt(entity, "Jetlength", effectsInfo.SteamJetLength); + DispatchKeyValueFloat(entity, "RollSpeed", effectsInfo.SteamRollSpeed); + DispatchKeyValueInt(entity, "type", effectsInfo.SteamType); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_DynamicLight: + { + SetVariantInt(effectsInfo.LightBrightness); + AcceptEntityInput(entity, "Brightness"); + SetVariantFloat(effectsInfo.LightMaxDistance); + AcceptEntityInput(entity, "Distance"); + SetVariantFloat(effectsInfo.LightMaxDistance); + AcceptEntityInput(entity, "spotlight_radius"); + SetVariantInt(effectsInfo.LightCone); + AcceptEntityInput(entity, "cone"); + DispatchSpawn(entity); + ActivateEntity(entity); + + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); + + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + SetEntProp(entity, Prop_Data, "m_LightStyle", effectsInfo.LightStyle); + } + case EffectType_Particle: + { + DispatchKeyValue(entity, "effect_name", effectsInfo.ParticleName); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_Trail: + { + DispatchKeyValueFloat(entity, "lifetime", effectsInfo.TrailTime); + DispatchKeyValueFloat(entity, "startwidth", effectsInfo.TrailStartWidth); + DispatchKeyValueFloat(entity, "endwidth", effectsInfo.TrailEndWidth); + DispatchKeyValue(entity, "spritename", effectsInfo.TrailName); + SetEntPropFloat(entity, Prop_Send, "m_flTextureRes", 0.05); + + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); + + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_PropDynamic: + { + DispatchKeyValue(entity, "model", effectsInfo.ModelName); + float modelScale = effectsInfo.ModelScale; + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES) && modelScale != GetEntPropFloat(attacher, Prop_Send, "m_flModelScale") && !effectsInfo.AttachPlayer) { - difficultyNumber += g_DifficultyConVar.IntValue; + modelScale *= 0.5; } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && difficultyNumber != -1) + DispatchKeyValueFloat(entity, "modelscale", modelScale); + SetEntProp(entity, Prop_Send, "m_nSkin", effectsInfo.ModelSkin); + SetEntProp(entity, Prop_Send, "m_fEffects", EF_BONEMERGE|EF_PARENT_ANIMATES); + if (effectsInfo.ModelAnimation[0] != '\0') { - int currentIntegerIndex = StringToInt(currentIndex); - if (difficultyNumber != currentIntegerIndex) - { - continue; - } + SetVariantString(value); + AcceptEntityInput(entity, "SetAnimation"); } - int entity = -1; + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - switch (effectsInfo.Type) - { - case EffectType_Steam: - { - entity = CreateEntityByName("env_steam"); - } - case EffectType_DynamicLight: - { - entity = CreateEntityByName("light_dynamic"); - } - case EffectType_Particle: - { - entity = CreateEntityByName("info_particle_system"); - } - case EffectType_Trail: - { - entity = CreateEntityByName("env_spritetrail"); - } - case EffectType_PropDynamic: - { - entity = CreateEntityByName("prop_dynamic"); - } - case EffectType_PointSpotlight: - { - entity = CreateEntityByName("env_beam"); - } - case EffectType_Sprite: - { - entity = CreateEntityByName("env_sprite"); - } - } + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_PointSpotlight: + { + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - if (entity != -1) + SF2PointSpotlightEntity spotlight = SF2PointSpotlightEntity(entity); + if (effectsInfo.AttachmentName[0] != '\0') { - char value[PLATFORM_MAX_PATH]; - DispatchKeyValueInt(entity, "renderamt", effectsInfo.FadeAlpha); - DispatchKeyValueInt(entity, "rendermode", view_as(effectsInfo.RenderModes)); - DispatchKeyValueInt(entity, "renderfx", view_as(effectsInfo.RenderEffects)); - DispatchKeyValueInt(entity, "spawnflags", effectsInfo.SpawnFlags); - - float effectPos[3], effectAng[3]; + SetVariantString("!activator"); + spotlight.Start.AcceptInput("ClearParent"); + SetVariantString("!activator"); + spotlight.Start.AcceptInput("SetParent", attacher); + SetVariantString(effectsInfo.AttachmentName); + spotlight.Start.AcceptInput("SetParentAttachmentMaintainOffset"); + SetVariantString("!activator"); + spotlight.End.AcceptInput("ClearParent"); + SetVariantString("!activator"); + spotlight.End.AcceptInput("SetParent", attacher); + SetVariantString(effectsInfo.AttachmentName); + spotlight.End.AcceptInput("SetParentAttachmentMaintainOffset"); + } + spotlight.SetRenderColor(renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + spotlight.Length = effectsInfo.SpotlightLength; + spotlight.Width = effectsInfo.SpotlightWidth; + spotlight.EndWidth = spotlight.Width * 2.0; + spotlight.Brightness = effectsInfo.LightBrightness; + spotlight.Distance = effectsInfo.LightMaxDistance; + spotlight.SpotlightRadius = effectsInfo.LightMaxDistance; + spotlight.Cone = effectsInfo.LightCone; + spotlight.HaloScale = effectsInfo.SpotlightHaloScale; + + spotlight.Spawn(); + spotlight.Activate(); + + spotlight.TurnOn(); + + //SDKHook(spotlight.Spotlight.index, SDKHook_SetTransmit, Hook_FlashlightSpotlightEffectSetTransmit); + } + case EffectType_Sprite: + { + DispatchKeyValue(entity, "classname", "env_sprite"); + DispatchKeyValue(entity, "model", effectsInfo.SpriteName); + DispatchKeyValueFloat(entity, "scale", effectsInfo.SpriteScale); - effectPos = effectsInfo.Origin; - effectAng = effectsInfo.Angles; + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - VectorTransform(effectPos, basePos, baseAng, effectPos); - AddVectors(effectAng, baseAng, effectAng); - TeleportEntity(entity, effectPos, effectAng, NULL_VECTOR); + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - switch (effectsInfo.Type) - { - case EffectType_Steam: - { - DispatchKeyValueInt(entity, "SpreadSpeed", effectsInfo.SteamSpreadSpeed); - DispatchKeyValueInt(entity, "Speed", effectsInfo.SteamSpeed); - DispatchKeyValueInt(entity, "StartSize", effectsInfo.SteamStartSize); - DispatchKeyValueInt(entity, "EndSize", effectsInfo.SteamEndSize); - DispatchKeyValueInt(entity, "Rate", effectsInfo.SteamRate); - DispatchKeyValueInt(entity, "Jetlength", effectsInfo.SteamJetLength); - DispatchKeyValueFloat(entity, "RollSpeed", effectsInfo.SteamRollSpeed); - DispatchKeyValueInt(entity, "type", effectsInfo.SteamType); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_DynamicLight: - { - SetVariantInt(effectsInfo.LightBrightness); - AcceptEntityInput(entity, "Brightness"); - SetVariantFloat(effectsInfo.LightMaxDistance); - AcceptEntityInput(entity, "Distance"); - SetVariantFloat(effectsInfo.LightMaxDistance); - AcceptEntityInput(entity, "spotlight_radius"); - SetVariantInt(effectsInfo.LightCone); - AcceptEntityInput(entity, "cone"); - DispatchSpawn(entity); - ActivateEntity(entity); - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - SetEntProp(entity, Prop_Data, "m_LightStyle", effectsInfo.LightStyle); - } - case EffectType_Particle: - { - DispatchKeyValue(entity, "effect_name", effectsInfo.ParticleName); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_Trail: - { - DispatchKeyValueFloat(entity, "lifetime", effectsInfo.TrailTime); - DispatchKeyValueFloat(entity, "startwidth", effectsInfo.TrailStartWidth); - DispatchKeyValueFloat(entity, "endwidth", effectsInfo.TrailEndWidth); - DispatchKeyValue(entity, "spritename", effectsInfo.TrailName); - SetEntPropFloat(entity, Prop_Send, "m_flTextureRes", 0.05); - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_PropDynamic: - { - DispatchKeyValue(entity, "model", effectsInfo.ModelName); - float modelScale = effectsInfo.ModelScale; - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES) && modelScale != GetEntPropFloat(slenderEnt, Prop_Send, "m_flModelScale")) - { - modelScale *= 0.5; - } - DispatchKeyValueFloat(entity, "modelscale", modelScale); - SetEntProp(entity, Prop_Send, "m_nSkin", effectsInfo.ModelSkin); - SetEntProp(entity, Prop_Send, "m_fEffects", EF_BONEMERGE|EF_PARENT_ANIMATES); - if (effectsInfo.ModelAnimation[0] != '\0') - { - SetVariantString(value); - AcceptEntityInput(entity, "SetAnimation"); - } - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_PointSpotlight: - { - int startEnt = CreateEntityByName("info_target"); - int endEnt = CreateEntityByName("info_target"); - if (startEnt != -1) // Start - { - SetEntPropString(startEnt, Prop_Data, "m_iClassname", "sf2_boss_spotlight_start"); - SetEntProp(startEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(startEnt, effectPos, effectAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(startEnt, "SetParent", slenderEnt); - if (effectsInfo.Attachment && effectsInfo.AttachmentName[0] != '\0') - { - SetVariantString(effectsInfo.AttachmentName); - AcceptEntityInput(startEnt, "SetParentAttachmentMaintainOffset"); - } - - DispatchSpawn(startEnt); - SetEntityOwner(startEnt, slenderEnt); - - SetEntityTransmitState(startEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectShouldTransmit); - } - if (endEnt != -1) // End - { - SetEntPropString(endEnt, Prop_Data, "m_iClassname", "sf2_boss_spotlight_end"); - SetEntProp(endEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(endEnt, effectPos, effectAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(endEnt, "SetParent", slenderEnt); - if (effectsInfo.Attachment && effectsInfo.AttachmentName[0] != '\0') - { - SetVariantString(effectsInfo.AttachmentName); - AcceptEntityInput(endEnt, "SetParentAttachmentMaintainOffset"); - } - - DispatchSpawn(endEnt); - SetEntityOwner(endEnt, slenderEnt); - - SetEntityTransmitState(endEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectShouldTransmit); - } - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - SetEntityModel(entity, SF2_FLASHLIGHT_BEAM_MATERIAL); - SetEntityRenderMode(entity, effectsInfo.RenderModes); - SetEntPropFloat(entity, Prop_Data, "m_life", 0.0); - - DispatchSpawn(entity); - ActivateEntity(entity); - - SetEntPropEnt(entity, Prop_Send, "m_hAttachEntity", startEnt, 0); - SetEntPropEnt(entity, Prop_Send, "m_hAttachEntity", endEnt, 1); - SetEntProp(entity, Prop_Send, "m_nNumBeamEnts", 2); - SetEntProp(entity, Prop_Send, "m_nBeamType", 2); - - float width = effectsInfo.SpotlightWidth; - SetEntPropFloat(entity, Prop_Send, "m_fWidth", width); - - float endWidth = width * 2.0; - SetEntPropFloat(entity, Prop_Data, "m_fEndWidth", endWidth); - - SetEntPropFloat(entity, Prop_Send, "m_fFadeLength", 0.0); - SetEntProp(entity, Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); - SetEntPropFloat(entity, Prop_Send, "m_fHaloScale", 40.0); - SetEntProp(entity, Prop_Send, "m_nBeamFlags", 0x80 | 0x200); - SetEntProp(entity, Prop_Data, "m_spawnflags", 0x8000); - g_EffectSpotlightEndEntity[entity] = EntIndexToEntRef(endEnt); - - g_EffectSpotlightEndLength[entity] = effectsInfo.SpotlightLength; - - AcceptEntityInput(entity, "TurnOn"); - - SetEntityOwner(entity, slenderEnt); - - SetEntityTransmitState(entity, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, entity, Hook_SpotlightEffectShouldTransmit); - } - case EffectType_Sprite: - { - DispatchKeyValue(entity, "classname", "env_sprite"); - DispatchKeyValue(entity, "model", effectsInfo.SpriteName); - DispatchKeyValueFloat(entity, "scale", effectsInfo.SpriteScale); - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - - DispatchSpawn(entity); - ActivateEntity(entity); - } - } + DispatchSpawn(entity); + ActivateEntity(entity); + } + } - float lifeTime = effectsInfo.LifeTime; - if (lifeTime > 0.0) - { - CreateTimer(lifeTime, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); - } + float lifeTime = effectsInfo.LifeTime; + if (lifeTime > 0.0) + { + CreateTimer(lifeTime, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); + } - if (!slenderEnt || slenderEnt == INVALID_ENT_REFERENCE) - { - LogError("Could not parent effect %s of boss %d to itself: boss entity does not exist!", sectionName, bossIndex); - continue; - } + if (!noParenting) + { + if (!attacher || attacher == INVALID_ENT_REFERENCE) + { + LogError("Could not parent effect %s of boss %d to itself: boss entity does not exist!", effectsInfo.SectionName, bossIndex); + return; + } - SetVariantString("!activator"); - AcceptEntityInput(entity, "SetParent", slenderEnt); - if (effectsInfo.Attachment) + SetVariantString("!activator"); + AcceptEntityInput(entity, "SetParent", attacher); + if (effectsInfo.Attachment) + { + if (effectsInfo.AttachmentName[0] != '\0') + { + SetVariantString(effectsInfo.AttachmentName); + if (effectsInfo.Type != EffectType_PropDynamic && effectsInfo.Type != EffectType_PointSpotlight) { - if (effectsInfo.AttachmentName[0] != '\0') - { - SetVariantString(effectsInfo.AttachmentName); - if (effectsInfo.Type != EffectType_PropDynamic && effectsInfo.Type != EffectType_PointSpotlight) - { - AcceptEntityInput(entity, "SetParentAttachment"); - } - else - { - AcceptEntityInput(entity, "SetParentAttachmentMaintainOffset"); - } - } + AcceptEntityInput(entity, "SetParentAttachment"); } - - switch (effectsInfo.Type) + else { - case EffectType_Steam, - EffectType_DynamicLight: - { - AcceptEntityInput(entity, "TurnOn"); - } - case EffectType_Particle: - { - AcceptEntityInput(entity, "start"); - } - case EffectType_Trail: - { - AcceptEntityInput(entity, "showsprite"); - } - case EffectType_PointSpotlight: - { - AcceptEntityInput(entity, "TurnOn"); - } - case EffectType_Sprite: - { - AcceptEntityInput(entity, "ShowSprite"); - } + AcceptEntityInput(entity, "SetParentAttachmentMaintainOffset"); } - SDKHook(entity, SDKHook_SetTransmit, Hook_EffectTransmit); - g_EntityEffectType[entity] = effectsInfo.Type; - g_EntityEffectEvent[entity] = effectsInfo.Event; - g_NpcEffectsArray[bossIndex].Push(entity); } } - else + } + + switch (effectsInfo.Type) + { + case EffectType_Steam, + EffectType_DynamicLight: + { + AcceptEntityInput(entity, "TurnOn"); + } + case EffectType_Particle: + { + AcceptEntityInput(entity, "start"); + } + case EffectType_Trail: + { + AcceptEntityInput(entity, "showsprite"); + } + case EffectType_PointSpotlight: + { + AcceptEntityInput(entity, "TurnOn"); + } + case EffectType_Sprite: { - LogError("Could not spawn effect %s for boss %d: invalid type!", sectionName, bossIndex); + AcceptEntityInput(entity, "ShowSprite"); } } - } + SDKHook(entity, SDKHook_SetTransmit, Hook_EffectTransmit); + g_EntityEffectType[entity] = effectsInfo.Type; + g_EntityEffectEvent[entity] = effectsInfo.Event; + if (!noParenting) + { + g_NpcEffectsArray[bossIndex].Push(entity); + } - if (g_NpcEffectsArray[bossIndex] != null && g_NpcEffectsArray[bossIndex].Length > 0) + if (output != null) + { + output.Push(EntIndexToEntRef(entity)); + } + } + else { - for (int effects = 0; effects < g_NpcEffectsArray[bossIndex].Length; effects++) + switch (effectsInfo.Type) { - int ent = g_NpcEffectsArray[bossIndex].Get(effects); - if (!IsValidEntity(ent)) + case EffectType_TempEntBeamRing: { - continue; + char colorString[32]; + strcopy(colorString, sizeof(colorString), effectsInfo.BeamRingColor); + char keys[4][4]; + ExplodeString(colorString, " ", keys, sizeof(keys), sizeof(keys)); + int color[4]; + for (int i2 = 0; i2 < 4; i2++) + { + color[i2] = StringToInt(keys[i2]); + } + + TE_SetupBeamRingPoint(effectPos, effectsInfo.BeamRingStartRadius, effectsInfo.BeamRingEndRadius, effectsInfo.BeamRingBeamModel, effectsInfo.BeamRingHaloModel, effectsInfo.BeamRingStartFrame, effectsInfo.BeamRingFrameRate, effectsInfo.LifeTime, effectsInfo.BeamRingWidth, effectsInfo.BeamRingAmplitude, color, effectsInfo.BeamRingSpeed, effectsInfo.BeamRingFlags); + TE_SendToAll(); } - if (g_EntityEffectType[ent] == EffectType_PointSpotlight) + case EffectType_TempEntParticle: { - SDKHook(slenderEnt, SDKHook_ThinkPost, SlenderSpotlightThink); - break; + if (effectsInfo.ParticleName[0] == '\0') + { + return; + } + + int particle = PrecacheParticleSystem(effectsInfo.ParticleName); + if (particle == -1) + { + return; + } + + int attachment = LookupEntityAttachment(attacher, effectsInfo.AttachmentName); + + float start[3], pos[3], ang[3]; + start = effectsInfo.TEParticleStartPos; + if (effectsInfo.TEParticleAttachType == 2) + { + VectorTransform(start, basePos, baseAng, start); + pos = start; + ang = effectAng; + } + else + { + pos = effectPos; + ang = effectAng; + } + TE_Particle(particle, pos, start, ang, attacher, effectsInfo.TEParticleAttachType, attachment, effectsInfo.TEParticleReset, effectsInfo.TEParticleHasControlPoint, effectsInfo.TEParticleControlPointAttachType, effectsInfo.TEParticleControlPointOffset); + TE_SendToAll(); + } + case EffectType_Sound: + { + effectsInfo.SoundSounds.EmitSound(_, attacher); } } } } -static Action Hook_EffectTransmit(int ent,int other) +static Action Hook_EffectTransmit(int ent, int other) { if (!g_Enabled) { return Plugin_Continue; } - int slender = GetEntPropEnt(ent, Prop_Send, "moveparent"); - int bossIndex = NPCGetFromEntIndex(slender); + SF2_ChaserEntity slender = SF2_ChaserEntity(GetEntPropEnt(ent, Prop_Send, "moveparent")); - if (bossIndex != -1 && NPCChaserIsCloaked(bossIndex)) + if (!slender.IsValid()) { - return Plugin_Handled; + return Plugin_Continue; } - if (g_EntityEffectEvent[ent] == EffectEvent_PlayerSeesBoss && IsValidClient(other) && bossIndex != -1 && !g_PlayerEliminated[other] && !IsClientInGhostMode(other) && - !DidClientEscape(other) && !PlayerCanSeeSlender(other, bossIndex, true)) + + if (slender.IsValid() && slender.HasCloaked) { return Plugin_Handled; } + SF2NPC_Chaser controller = slender.Controller; + + SF2_BasePlayer player = SF2_BasePlayer(other); + if (player.IsValid && !player.IsEliminated && !player.IsInGhostMode && !player.HasEscaped) + { + if (g_EntityEffectEvent[ent] == EffectEvent_PlayerSeesBoss && !controller.PlayerCanSee(player.index)) + { + return Plugin_Handled; + } + } + return Plugin_Continue; } -static Action Hook_EffectTransmitX(int ent,int other) +static Action Hook_EffectTransmitX(int ent, int other) { if (!g_Enabled) { return Plugin_Continue; } - int slender = GetEntPropEnt(ent,Prop_Send,"moveparent"); - int bossIndex = NPCGetFromEntIndex(slender); + SF2_ChaserEntity slender = SF2_ChaserEntity(GetEntPropEnt(ent, Prop_Send, "moveparent")); - if (bossIndex != -1 && NPCChaserIsCloaked(bossIndex)) + if (slender.IsValid() && slender.HasCloaked) { return Plugin_Handled; } @@ -554,24 +641,12 @@ static Action Hook_EffectTransmitX(int ent,int other) return Plugin_Continue; } -MRESReturn Hook_SpotlightEffectUpdateTransmitState(int entity, DHookReturn returnHook) -{ - returnHook.Value = SetEntityTransmitState(entity, FL_EDICT_FULLCHECK); - return MRES_Supercede; -} - -MRESReturn Hook_SpotlightEffectShouldTransmit(int entity, DHookReturn returnHook, DHookParam params) -{ - returnHook.Value = FL_EDICT_ALWAYS; - return MRES_Supercede; -} - -void SlenderToggleParticleEffects(int slenderEnt,bool reverse=false) +void SlenderToggleParticleEffects(int slenderEnt, bool reverse = false) { int effect = -1; - while((effect = FindEntityByClassname(effect, "info_particle_system")) > MaxClients) + while ((effect = FindEntityByClassname(effect, "info_particle_system")) > MaxClients) { - if (GetEntPropEnt(effect,Prop_Send,"moveparent") == slenderEnt) + if (GetEntPropEnt(effect, Prop_Send, "moveparent") == slenderEnt) { if (!reverse) { @@ -587,25 +662,14 @@ void SlenderToggleParticleEffects(int slenderEnt,bool reverse=false) } } -void SlenderRemoveEffects(int slenderEnt,bool kill=false) +void SlenderRemoveEffects(int bossIndex, bool kill = false) { - int bossIndex = NPCGetFromEntIndex(slenderEnt); - if (bossIndex == -1) - { - return; - } - if (g_NpcEffectsArray[bossIndex] == null) { return; } - if (g_NpcEffectsArray[bossIndex] != null && g_NpcEffectsArray[bossIndex].Length <= 0) - { - return; - } - - for (int effect = 0; effect < g_NpcEffectsArray[bossIndex].Length; effect++) + for (int effect = 0; effect < g_NpcEffectsArray[bossIndex].Length;) { int ent = g_NpcEffectsArray[bossIndex].Get(effect); if (!IsValidEntity(ent)) @@ -647,47 +711,6 @@ void SlenderRemoveEffects(int slenderEnt,bool kill=false) delete g_NpcEffectsArray[bossIndex]; } -static void SlenderSpotlightThink(int slenderEnt) -{ - if (IsValidEntity(slenderEnt)) - { - int bossIndex = NPCGetFromEntIndex(slenderEnt); - if (bossIndex != -1 && g_NpcEffectsArray[bossIndex] != null && g_NpcEffectsArray[bossIndex].Length > 0) - { - for (int effect = 0; effect < g_NpcEffectsArray[bossIndex].Length; effect++) - { - int ent = g_NpcEffectsArray[bossIndex].Get(effect); - if (!IsValidEntity(ent)) - { - continue; - } - if (g_EntityEffectType[ent] != EffectType_PointSpotlight) - { - continue; - } - float entPos[3], entRot[3], endPos[3]; - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", entPos); - GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", entRot); - GetAngleVectors(entRot, entRot, NULL_VECTOR, NULL_VECTOR); - endPos = entRot; - ScaleVector(endPos, g_EffectSpotlightEndLength[ent]); - AddVectors(endPos, entPos, endPos); - - CBaseEntity spotlightEnd = CBaseEntity(EntRefToEntIndex(g_EffectSpotlightEndEntity[ent])); - if (spotlightEnd.IsValid()) - { - TR_TraceRayFilter(entPos, endPos, MASK_SOLID_BRUSHONLY, RayType_EndPoint, TraceRayDontHitEntity, ent); - - float hitPos[3]; - TR_GetEndPosition(hitPos); - - spotlightEnd.SetAbsOrigin(hitPos); - } - } - } - } -} - static Action Timer_DiscoLight(Handle timer, any effect) { if (effect == -1) @@ -696,12 +719,12 @@ static Action Timer_DiscoLight(Handle timer, any effect) } int ent = EntRefToEntIndex(effect); - if (!IsValidEntity(ent)) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } - int slender = GetEntPropEnt(ent,Prop_Send,"moveparent"); + int slender = GetEntPropEnt(ent, Prop_Send, "moveparent"); if (!slender || slender == INVALID_ENT_REFERENCE) { return Plugin_Stop; @@ -736,19 +759,7 @@ static Action Timer_FestiveLight(Handle timer, any effect) } int ent = EntRefToEntIndex(effect); - if (!IsValidEntity(ent)) - { - return Plugin_Stop; - } - - int slender = GetEntPropEnt(ent,Prop_Send,"moveparent"); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } @@ -758,15 +769,15 @@ static Action Timer_FestiveLight(Handle timer, any effect) { case 1: { - SetEntityRenderColor(effect, 230, 37, 37, 255); + SetEntityRenderColor(ent, 230, 37, 37, 255); } case 2: { - SetEntityRenderColor(effect, 94, 227, 79, 255); + SetEntityRenderColor(ent, 79, 227, 79, 255); } case 3: { - SetEntityRenderColor(effect, 235, 235, 235, 255); + SetEntityRenderColor(ent, 255, 255, 255, 255); } } diff --git a/addons/sourcemod/scripting/sf2/entities/initialize.sp b/addons/sourcemod/scripting/sf2/entities/initialize.sp new file mode 100644 index 00000000..87834773 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/initialize.sp @@ -0,0 +1,27 @@ +#include "sf2_base_projectile.sp" + +void InitializeCustomEntities() +{ + SF2_ProjectileBase.Initialize(); + SF2_ProjectileRocket.Initialize(); + SF2_ProjectileFireball.Initialize(); + SF2_ProjectileIceball.Initialize(); + SF2_ProjectileSentryRocket.Initialize(); + SF2_ProjectileCowMangler.Initialize(); + SF2_ProjectileArrow.Initialize(); + SF2_ProjectileGrenade.Initialize(); + SF2_ProjectileBaseball.Initialize(); +} + +void SetupCustomEntitiesAPI() +{ + SF2_ProjectileBase.SetupAPI(); + SF2_ProjectileRocket.SetupAPI(); + SF2_ProjectileFireball.SetupAPI(); + SF2_ProjectileIceball.SetupAPI(); + SF2_ProjectileSentryRocket.SetupAPI(); + SF2_ProjectileCowMangler.SetupAPI(); + SF2_ProjectileArrow.SetupAPI(); + SF2_ProjectileGrenade.SetupAPI(); + SF2_ProjectileBaseball.SetupAPI(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp new file mode 100644 index 00000000..87d2464b --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp @@ -0,0 +1,205 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_arrow"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileArrow < SF2_ProjectileBase +{ + public SF2_ProjectileArrow(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_Touched") + .EndDataMapDesc(); + g_Factory.Install(); + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Arrow.Create", Native_Create); + CreateNative("SF2_projectile_Arrow.IsValid.get", Native_IsValid); + } + + property bool Touched + { + public get() + { + return this.GetProp(Prop_Data, "m_Touched") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_Touched", value); + } + } + + public static SF2_ProjectileArrow Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] trail, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileArrow arrow = SF2_ProjectileArrow(CreateEntityByName(g_EntityClassname)); + if (!arrow.IsValid()) + { + return SF2_ProjectileArrow(-1); + } + + arrow.Type = SF2BossProjectileType_Arrow; + arrow.Speed = speed; + arrow.Damage = damage; + if (arrow.IsCrits) + { + CBaseEntity critParticle = arrow.CreateParticle("critical_rocket_blue"); + arrow.CritEntity = critParticle; + } + arrow.AttackWaiters = attackWaiters; + arrow.SetImpactSound(impactSound); + SetEntityOwner(arrow.index, owner.index); + arrow.SetModel(model); + arrow.KeyValue("solid", "2"); + + arrow.Spawn(); + arrow.Activate(); + arrow.SetMoveType(MOVETYPE_FLYGRAVITY); + arrow.SetProp(Prop_Send, "m_usSolidFlags", 12); + arrow.Teleport(pos, ang, NULL_VECTOR); + arrow.CreateTrail(true, "effects/arrowtrail_red.vmt", "255", "1"); + arrow.SetVelocity(); + + SDKHook(arrow.index, SDKHook_StartTouch, StartTouch); + } +} + +static void StartTouch(int entity, int other) +{ + SF2_ProjectileArrow projectile = SF2_ProjectileArrow(entity); + + if (other == 0) + { + RemoveEntity(projectile.index); + return; + } + + bool hit = false; + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + if (otherPlayer.IsValid) + { + if (otherPlayer.IsInGhostMode) + { + return; + } + + if (otherPlayer.IsEliminated && !projectile.AttackWaiters) + { + return; + } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == otherPlayer.Team) + { + return; + } + } + else + { + int hitIndex = NPCGetFromEntIndex(other); + if (hitIndex != -1) + { + hit = false; + } + } + + if (owner == other) + { + hit = false; + } + + char class[64]; + GetEntityClassname(other, class, sizeof(class)); + if (StrContains(class, "sf2_projectile", false) != -1) + { + hit = false; + } + + if (SF2_BasePlayer(other).IsValid) + { + hit = true; + } + + if (hit) + { + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(other)); + Call_Finish(); + int flags = DMG_BULLET; + float pos[3]; + projectile.GetAbsOrigin(pos); + if (projectile.IsCrits) + { + flags |= DMG_ACID; + } + SF2_BasePlayer player = SF2_BasePlayer(other); + if (player.IsValid) + { + if (!projectile.AttackWaiters && player.IsEliminated) + { + return; + } + + player.TakeDamage(_, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(player); + Call_PushCell(projectile); + Call_Finish(); + } + else + { + SDKHooks_TakeDamage(other, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + } + EmitSoundToAll(projectile.GetImpactSound(), projectile.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + RemoveEntity(projectile.index); + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char trail[64], impact[64], model[64]; + GetNativeString(7, trail, sizeof(trail)); + GetNativeString(8, impact, sizeof(impact)); + GetNativeString(9, model, sizeof(model)); + SF2_ProjectileArrow projectile = SF2_ProjectileArrow.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), trail, impact, model, GetNativeCell(10)); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileArrow(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp new file mode 100644 index 00000000..932580ad --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp @@ -0,0 +1,210 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_baseball"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileBaseball < SF2_ProjectileGrenade +{ + public SF2_ProjectileBaseball(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileGrenade.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_TravelTime") + .EndDataMapDesc(); + g_Factory.Install(); + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Baseball.Create", Native_Create); + CreateNative("SF2_Projectile_Baseball.IsValid.get", Native_IsValid); + } + + property float TravelTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_TravelTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_TravelTime", value); + } + } + + public static SF2_ProjectileBaseball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileBaseball ball = SF2_ProjectileBaseball(CreateEntityByName(g_EntityClassname)); + if (!ball.IsValid()) + { + return SF2_ProjectileBaseball(-1); + } + + ball.Type = SF2BossProjectileType_Baseball; + ball.Speed = speed; + ball.Damage = damage; + if (ball.IsCrits) + { + CBaseEntity critParticle = ball.CreateParticle("critical_rocket_blue"); + ball.CritEntity = critParticle; + } + ball.AttackWaiters = attackWaiters; + SetEntityOwner(ball.index, owner.index); + ball.SetModel(model); + ball.SetProp(Prop_Send, "m_nSkin", 1); + ball.SetProp(Prop_Send, "m_usSolidFlags", 12); + ball.KeyValue("solid", "2"); + ball.KeyValue("spawnflags", "4"); + SetEntityCollisionGroup(ball.index, 13); + ball.SetProp(Prop_Send, "m_usSolidFlags", 0); + + ball.Spawn(); + ball.Activate(); + ball.Teleport(pos, ang, NULL_VECTOR); + ball.CreateTrail(true, "effects/baseballtrail_blu.vmt", "255", "1"); + ball.SetVelocity(); + + ball.Timer = GetGameTime() + 12.0; + + SDKHook(ball.index, SDKHook_VPhysicsUpdate, Think); + } +} + +static void Think(int entity) +{ + SF2_ProjectileBaseball projectile = SF2_ProjectileBaseball(entity); + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + + if (projectile.Timer < GetGameTime()) + { + RemoveEdict(projectile.index); + } + + if (projectile.Touched) + { + return; + } + + projectile.TravelTime += GetGameFrameTime(); + + float pos[3], mins[3], maxs[3]; + projectile.GetAbsOrigin(pos); + projectile.GetPropVector(Prop_Send, "m_vecMins", mins); + projectile.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, projectile.index); + + int hitIndex = TR_GetEntityIndex(); + if (IsValidEntity(hitIndex)) + { + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(hitIndex)); + Call_Finish(); + + if (hitIndex == 0) + { + projectile.Touched = true; + return; + } + else + { + int flags = DMG_CLUB; + if (projectile.IsCrits) + { + flags |= DMG_ACID; + } + SF2_BasePlayer player = SF2_BasePlayer(hitIndex); + if (player.IsValid) + { + if (player.IsInGhostMode) + { + return; + } + + if (player.IsEliminated && !projectile.AttackWaiters) + { + projectile.Touched = true; + return; + } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) + { + projectile.Touched = true; + return; + } + player.TakeDamage(_, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + float ratio = projectile.TravelTime / 1.0; + if (ratio > 0.1) + { + float time = 6.0 * ratio; + float stun = 0.5; + if (projectile.IsCrits) + { + time += 2.0; + } + + if (ratio >= 1.0) + { + time += 1.0; + } + + if (player.GetProp(Prop_Send, "m_nWaterLevel") < 3) + { + player.Stun(time, stun, TF_STUNFLAG_SLOWDOWN, player.index); + } + } + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(player); + Call_PushCell(projectile); + Call_Finish(); + projectile.Touched = true; + } + else + { + SDKHooks_TakeDamage(hitIndex, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + } + } + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char model[64]; + GetNativeString(7, model, sizeof(model)); + SF2_ProjectileBaseball projectile = SF2_ProjectileBaseball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), model, GetNativeCell(8)); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileBaseball(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp new file mode 100644 index 00000000..44401841 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp @@ -0,0 +1,74 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_cowmangler"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileCowMangler < SF2_ProjectileRocket +{ + public SF2_ProjectileCowMangler(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileRocket.GetFactory()); + g_Factory.Install(); + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_CowMangler.Create", Native_Create); + CreateNative("SF2_Projectile_CowMangler.IsValid.get", Native_IsValid); + } + + public static SF2_ProjectileCowMangler Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool attackWaiters = false) + { + SF2_ProjectileCowMangler rocket = SF2_ProjectileCowMangler(CreateEntityByName(g_EntityClassname)); + if (!rocket.IsValid()) + { + return SF2_ProjectileCowMangler(-1); + } + + rocket.InitializeProjectile(SF2BossProjectileType_Mangler, owner, pos, ang, speed, damage, blastRadius, false, "drg_cow_rockettrail_normal_blue", "drg_cow_explosioncore_normal_blue", ROCKET_IMPACT, "models/roller.mdl", attackWaiters); + + return rocket; + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char impact[64], trail[64], freeze[64]; + GetNativeString(7, impact, sizeof(impact)); + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(9, freeze, sizeof(freeze)); + SF2_ProjectileCowMangler projectile = SF2_ProjectileCowMangler.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7)); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileCowMangler(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp new file mode 100644 index 00000000..3206cdbf --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp @@ -0,0 +1,90 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_fireball"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileFireball < SF2_ProjectileBase +{ + public SF2_ProjectileFireball(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.Install(); + g_OnPlayerDamagedByProjectilePFwd.AddFunction(null, OnPlayerDamagedByProjectile); + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Fireball.Create", Native_Create); + CreateNative("SF2_Projectile_Fireball.IsValid.get", Native_IsValid); + } + + public void OnPlayerDamaged(SF2_BasePlayer player) + { + player.Ignite(true); + } + + public static SF2_ProjectileFireball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const bool attackWaiters = false) + { + SF2_ProjectileFireball fireball = SF2_ProjectileFireball(CreateEntityByName(g_EntityClassname)); + if (!fireball.IsValid()) + { + return SF2_ProjectileFireball(-1); + } + + fireball.InitializeProjectile(SF2BossProjectileType_Fireball, owner, pos, ang, speed, damage, blastRadius, + false, "spell_fireball_small_red", "bombinomicon_burningdebris", impactSound, "models/roller.mdl", attackWaiters); + + return fireball; + } +} + +static void OnPlayerDamagedByProjectile(SF2_BasePlayer player, SF2_ProjectileBase projectile) +{ + if (projectile.Type == SF2BossProjectileType_Fireball) + { + player.Ignite(true); + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char impact[64], trail[64]; + GetNativeString(7, impact, sizeof(impact)); + GetNativeString(8, trail, sizeof(trail)); + SF2_ProjectileFireball projectile = SF2_ProjectileFireball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), impact, trail); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileFireball(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp new file mode 100644 index 00000000..65dcb688 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -0,0 +1,434 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_grenade"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileGrenade < SF2_ProjectileBase +{ + public SF2_ProjectileGrenade(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromClass("prop_physics_override"); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_TrailEntity") + .DefineEntityField("m_ParticleEntity") + .DefineEntityField("m_CritEntity") + .DefineIntField("m_Type") + .DefineFloatField("m_Speed") + .DefineFloatField("m_Damage") + .DefineBoolField("m_IsCrits") + .DefineFloatField("m_BlastRadius") + .DefineStringField("m_TrailName") + .DefineStringField("m_ExplosionParticle") + .DefineStringField("m_ImpactSound") + .DefineBoolField("m_AttackWaiters") + .DefineBoolField("m_Touched") + .DefineFloatField("m_Timer") + .EndDataMapDesc(); + g_Factory.Install(); + } + + public static CEntityFactory GetFactory() + { + return g_Factory; + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Grenade.Create", Native_Create); + CreateNative("SF2_Projectile_Grenade.IsValid.get", Native_IsValid); + } + + property CBaseEntity TrailEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_TrailEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_TrailEntity", entity.index); + } + } + + property CBaseEntity ParticleEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_ParticleEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_ParticleEntity", entity.index); + } + } + + property CBaseEntity CritEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_CritEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_CritEntity", entity.index); + } + } + + property int Type + { + public get() + { + return this.GetProp(Prop_Data, "m_Type"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Type", value); + } + } + + property float Speed + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Speed"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Speed", value); + } + } + + property float Damage + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Damage"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Damage", value); + } + } + + property bool IsCrits + { + public get() + { + return this.GetProp(Prop_Data, "m_IsCrits") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsCrits", value); + } + } + + property float BlastRadius + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_BlastRadius"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_BlastRadius", value); + } + } + + public char[] GetTrailName() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_TrailName", buffer, sizeof(buffer)); + return buffer; + } + + public void SetTrailName(const char[] value) + { + this.SetPropString(Prop_Data, "m_TrailName", value); + } + + public char[] GetExplosionParticle() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_ExplosionParticle", buffer, sizeof(buffer)); + return buffer; + } + + public void SetExplosionParticle(const char[] value) + { + this.SetPropString(Prop_Data, "m_ExplosionParticle", value); + } + + public char[] GetImpactSound() + { + char buffer[PLATFORM_MAX_PATH]; + this.GetPropString(Prop_Data, "m_ImpactSound", buffer, sizeof(buffer)); + return buffer; + } + + public void SetImpactSound(const char[] value) + { + this.SetPropString(Prop_Data, "m_ImpactSound", value); + } + + property bool AttackWaiters + { + public get() + { + return this.GetProp(Prop_Data, "m_AttackWaiters") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_AttackWaiters", value); + } + } + + property bool Touched + { + public get() + { + return this.GetProp(Prop_Data, "m_Touched") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_Touched", value); + } + } + + property float Timer + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Timer"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Timer", value); + } + } + + public static SF2_ProjectileGrenade Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileGrenade grenade = SF2_ProjectileGrenade(CreateEntityByName(g_EntityClassname)); + if (!grenade.IsValid()) + { + return SF2_ProjectileGrenade(-1); + } + + grenade.Type = SF2BossProjectileType_Grenade; + grenade.Speed = speed; + grenade.Damage = damage; + grenade.BlastRadius = blastRadius; + if (grenade.IsCrits) + { + CBaseEntity critParticle = grenade.CreateParticle("critical_grenade_blue"); + grenade.CritEntity = critParticle; + } + grenade.AttackWaiters = attackWaiters; + grenade.SetTrailName(trail); + grenade.SetImpactSound(impactSound); + grenade.SetExplosionParticle(explosion); + SetEntityOwner(grenade.index, owner.index); + grenade.SetModel(model); + grenade.SetProp(Prop_Send, "m_nSkin", 1); + grenade.KeyValue("solid", "2"); + grenade.KeyValue("spawnflags", "4"); + SetEntityCollisionGroup(grenade.index, COLLISION_GROUP_DEBRIS_TRIGGER); + grenade.SetProp(Prop_Send, "m_usSolidFlags", 0); + + CBaseEntity particle = grenade.CreateParticle(grenade.GetTrailName()); + grenade.ParticleEntity = particle; + + grenade.Spawn(); + grenade.Activate(); + grenade.CreateTrail(); + grenade.Teleport(pos, ang, NULL_VECTOR); + grenade.SetVelocity(); + + grenade.Timer = GetGameTime() + 2.0; + + CreateTimer(0.1, Timer_Think, EntIndexToEntRef(grenade.index), TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); + } +} + +static Action Timer_Think(Handle timer, any ref) +{ + int entity = EntRefToEntIndex(ref); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } + SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade(entity); + + if (projectile.Timer < GetGameTime()) + { + projectile.DoExplosion(); + if (projectile.IsValid()) + { + RemoveEdict(projectile.index); + } + } + + if (projectile.Touched) + { + return Plugin_Continue; + } + + float pos[3], mins[3], maxs[3]; + projectile.GetAbsOrigin(pos); + projectile.GetPropVector(Prop_Send, "m_vecMins", mins); + projectile.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, projectile.index); + + int hitIndex = TR_GetEntityIndex(); + if (IsValidEntity(hitIndex)) + { + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(hitIndex)); + Call_Finish(); + + if (hitIndex == 0) + { + projectile.Touched = true; + return Plugin_Continue; + } + else + { + if (SF2_BasePlayer(hitIndex).IsValid) + { + if (!SF2_BasePlayer(hitIndex).IsEliminated || projectile.AttackWaiters) + { + projectile.DoExplosion(); + } + + if (g_Buildings.FindValue(EntIndexToEntRef(hitIndex)) != -1) + { + projectile.DoExplosion(); + } + + if (g_WhitelistedEntities.FindValue(EntIndexToEntRef(hitIndex)) != -1) + { + projectile.DoExplosion(); + } + + if (g_BreakableProps.FindValue(EntIndexToEntRef(hitIndex)) != -1) + { + projectile.DoExplosion(); + } + } + } + } + + return Plugin_Continue; +} + +bool TraceRayGrenade(int entity, int mask, any data) +{ + SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade(data); + if (entity == data) + { + return false; + } + + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + + SF2_BasePlayer player = SF2_BasePlayer(entity); + if (player.IsValid) + { + if (player.IsInGhostMode) + { + return false; + } + + if (player.IsEliminated && !projectile.AttackWaiters) + { + return false; + } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) + { + return false; + } + } + + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) + { + return false; + } + + if (g_WhitelistedEntities.FindValue(EntIndexToEntRef(entity)) != -1) + { + return true; + } + + if (g_Buildings.FindValue(EntIndexToEntRef(entity)) != -1) + { + return true; + } + + if (g_BreakableProps.FindValue(EntIndexToEntRef(entity)) != -1) + { + return true; + } + + return true; +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char trail[64], explosion[64], impact[64], model[64]; + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(9, explosion, sizeof(explosion)); + GetNativeString(10, impact, sizeof(impact)); + GetNativeString(11, model, sizeof(model)); + SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), trail, explosion, impact, model, GetNativeCell(12)); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileGrenade(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp new file mode 100644 index 00000000..664e40b4 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp @@ -0,0 +1,142 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_iceball"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileIceball < SF2_ProjectileBase +{ + public SF2_ProjectileIceball(int entIndex) + { + return view_as(entIndex); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_SlowDuration") + .DefineFloatField("m_SlowMultiplier") + .DefineStringField("m_FreezeSound") + .EndDataMapDesc(); + g_Factory.Install(); + g_OnPlayerDamagedByProjectilePFwd.AddFunction(null, OnPlayerDamagedByProjectile); + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Iceball.Create", Native_Create); + CreateNative("SF2_Projectile_Iceball.IsValid.get", Native_IsValid); + } + + property float SlowDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SlowDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SlowDuration", value); + } + } + + property float SlowMultiplier + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SlowMultiplier"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SlowMultiplier", value); + } + } + + public char[] GetFreezeSound() + { + char buffer[PLATFORM_MAX_PATH]; + this.GetPropString(Prop_Data, "m_FreezeSound", buffer, sizeof(buffer)); + return buffer; + } + + public void SetFreezeSound(const char[] value) + { + this.SetPropString(Prop_Data, "m_FreezeSound", value); + } + + public void OnPlayerDamaged(SF2_BasePlayer player) + { + EmitSoundToClient(player.index, this.GetFreezeSound(), _, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + player.Stun(this.SlowDuration, this.SlowMultiplier, TF_STUNFLAG_SLOWDOWN, player.index); + } + + public static SF2_ProjectileIceball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const float slowDuration, + const float slowMultiplier, + const char[] freezeSound, + const bool attackWaiters = false) + { + SF2_ProjectileIceball iceball = SF2_ProjectileIceball(CreateEntityByName(g_EntityClassname)); + if (!iceball.IsValid()) + { + return SF2_ProjectileIceball(-1); + } + + iceball.SlowDuration = slowDuration; + iceball.SlowMultiplier = slowMultiplier; + iceball.SetFreezeSound(freezeSound); + iceball.InitializeProjectile(SF2BossProjectileType_Iceball, owner, pos, ang, speed, damage, blastRadius, + false, "spell_fireball_small_blue", "spell_batball_impact_blue", impactSound, "models/roller.mdl", attackWaiters); + return iceball; + } +} + +static void OnPlayerDamagedByProjectile(SF2_BasePlayer player, SF2_ProjectileBase projectile) +{ + if (projectile.Type == SF2BossProjectileType_Iceball) + { + SF2_ProjectileIceball iceball = SF2_ProjectileIceball(projectile.index); + EmitSoundToClient(player.index, iceball.GetFreezeSound(), _, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + player.Stun(iceball.SlowDuration, iceball.SlowMultiplier, TF_STUNFLAG_SLOWDOWN, player.index); + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char impact[64], trail[64], freeze[64]; + GetNativeString(7, impact, sizeof(impact)); + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(11, freeze, sizeof(freeze)); + SF2_ProjectileIceball projectile = SF2_ProjectileIceball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), impact, trail, GetNativeCell(9), GetNativeCell(10), freeze, GetNativeCell(12)); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileIceball(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp new file mode 100644 index 00000000..21ddebee --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp @@ -0,0 +1,80 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_rocket"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileRocket < SF2_ProjectileBase +{ + public SF2_ProjectileRocket(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.Install(); + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Rocket.Create", Native_Create); + CreateNative("SF2_Projectile_Rocket.IsValid.get", Native_IsValid); + } + + public static SF2_ProjectileRocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileRocket rocket = SF2_ProjectileRocket(CreateEntityByName(g_EntityClassname)); + if (!rocket.IsValid()) + { + return SF2_ProjectileRocket(-1); + } + + rocket.InitializeProjectile(SF2BossProjectileType_Rocket, owner, pos, ang, speed, damage, blastRadius, isCrits, trail, explosion, impactSound, model, attackWaiters); + + return rocket; + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char trail[64], explosion[64], impact[64], model[64]; + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(9, explosion, sizeof(explosion)); + GetNativeString(10, impact, sizeof(impact)); + GetNativeString(11, model, sizeof(model)); + SF2_ProjectileRocket projectile = SF2_ProjectileRocket.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), trail, explosion, impact, model, GetNativeCell(12)); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileRocket(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp new file mode 100644 index 00000000..400c5ec4 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp @@ -0,0 +1,73 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_sentryrocket"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileSentryRocket < SF2_ProjectileRocket +{ + public SF2_ProjectileSentryRocket(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileRocket.GetFactory()); + g_Factory.Install(); + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_SentryRocket.Create", Native_Create); + CreateNative("SF2_Projectile_SentryRocket.IsValid.get", Native_IsValid); + } + + public static SF2_ProjectileSentryRocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const bool attackWaiters = false) + { + SF2_ProjectileSentryRocket rocket = SF2_ProjectileSentryRocket(CreateEntityByName(g_EntityClassname)); + if (!rocket.IsValid()) + { + return SF2_ProjectileSentryRocket(-1); + } + + rocket.InitializeProjectile(SF2BossProjectileType_SentryRocket, owner, pos, ang, speed, damage, blastRadius, isCrits, ROCKET_TRAIL, ROCKET_EXPLODE_PARTICLE, ROCKET_IMPACT, "models/buildables/sentry3_rockets.mdl", attackWaiters); + rocket.ResetSequence(rocket.LookupSequence("idle")); + rocket.SetProp(Prop_Data, "m_bSequenceLoops", true); + + return rocket; + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + SF2_ProjectileSentryRocket projectile = SF2_ProjectileSentryRocket.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), GetNativeCell(8)); + return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileSentryRocket(GetNativeCell(1)).IsValid(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp new file mode 100644 index 00000000..f6b6efde --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -0,0 +1,640 @@ +#pragma semicolon 1 + +static CEntityFactory g_Factory; + +PrivateForward g_OnPlayerDamagedByProjectilePFwd; + +methodmap SF2_ProjectileBase < CBaseAnimating +{ + public SF2_ProjectileBase(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_projectile_base", _, OnRemove); + g_Factory.DeriveFromClass("prop_dynamic_override"); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_TrailEntity") + .DefineEntityField("m_ParticleEntity") + .DefineEntityField("m_CritEntity") + .DefineIntField("m_Type") + .DefineFloatField("m_Speed") + .DefineFloatField("m_Damage") + .DefineBoolField("m_IsCrits") + .DefineFloatField("m_BlastRadius") + .DefineStringField("m_TrailName") + .DefineStringField("m_ExplosionParticle") + .DefineStringField("m_ImpactSound") + .DefineBoolField("m_AttackWaiters") + .EndDataMapDesc(); + g_Factory.Install(); + + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnPlayerDamagedByProjectilePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + } + + public static CEntityFactory GetFactory() + { + return g_Factory; + } + + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Base.Initialize", Native_Initialize); + CreateNative("SF2_Projectile_Base.DoExplosion", Native_DoExplosion); + } + + property CBaseEntity TrailEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_TrailEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_TrailEntity", entity.index); + } + } + + property CBaseEntity ParticleEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_ParticleEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_ParticleEntity", entity.index); + } + } + + property CBaseEntity CritEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_CritEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_CritEntity", entity.index); + } + } + + property int Type + { + public get() + { + return this.GetProp(Prop_Data, "m_Type"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Type", value); + } + } + + property float Speed + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Speed"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Speed", value); + } + } + + property float Damage + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Damage"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Damage", value); + } + } + + property bool IsCrits + { + public get() + { + return this.GetProp(Prop_Data, "m_IsCrits") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsCrits", value); + } + } + + property float BlastRadius + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_BlastRadius"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_BlastRadius", value); + } + } + + public char[] GetTrailName() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_TrailName", buffer, sizeof(buffer)); + return buffer; + } + + public void SetTrailName(const char[] value) + { + this.SetPropString(Prop_Data, "m_TrailName", value); + } + + public char[] GetExplosionParticle() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_ExplosionParticle", buffer, sizeof(buffer)); + return buffer; + } + + public void SetExplosionParticle(const char[] value) + { + this.SetPropString(Prop_Data, "m_ExplosionParticle", value); + } + + public char[] GetImpactSound() + { + char buffer[PLATFORM_MAX_PATH]; + this.GetPropString(Prop_Data, "m_ImpactSound", buffer, sizeof(buffer)); + return buffer; + } + + public void SetImpactSound(const char[] value) + { + this.SetPropString(Prop_Data, "m_ImpactSound", value); + } + + property bool AttackWaiters + { + public get() + { + return this.GetProp(Prop_Data, "m_AttackWaiters") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_AttackWaiters", value); + } + } + + public void InitializeProjectile( + const int type, + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + this.Type = type; + this.Speed = speed; + this.Damage = damage; + this.BlastRadius = blastRadius; + this.IsCrits = isCrits; + if (this.IsCrits) + { + CBaseEntity critParticle = this.CreateParticle("critical_rocket_blue"); + this.CritEntity = critParticle; + } + this.AttackWaiters = attackWaiters; + this.SetTrailName(trail); + this.SetImpactSound(impactSound); + this.SetExplosionParticle(explosion); + SetEntityOwner(this.index, owner.index); + this.SetModel(model); + if (type == SF2BossProjectileType_Fireball || type == SF2BossProjectileType_Iceball || type == SF2BossProjectileType_Mangler) + { + this.SetRenderMode(RENDER_TRANSCOLOR); + this.SetRenderColor(0, 0, 0, 0); + } + this.KeyValue("solid", "2"); + + CBaseEntity particle = this.CreateParticle(this.GetTrailName()); + this.ParticleEntity = particle; + + this.Spawn(); + this.Activate(); + this.SetMoveType(MOVETYPE_FLY); + this.SetProp(Prop_Send, "m_usSolidFlags", 12); + this.CreateTrail(); + this.Teleport(pos, ang, NULL_VECTOR); + this.SetVelocity(); + + SDKHook(this.index, SDKHook_StartTouch, StartTouch); + + CreateTimer(15.0, Timer_KillEntity, EntIndexToEntRef(this.index), TIMER_FLAG_NO_MAPCHANGE); + } + + public void CreateTrail(bool teleport = false, const char[] override = "sprites/laserbeam.vmt", const char[] alpha = "0", const char[] mode = "10") + { + CBaseEntity trail = CBaseEntity(CreateEntityByName("env_spritetrail")); + if (trail.IsValid()) + { + if (teleport) + { + float pos[3], ang[3]; + this.GetAbsOrigin(pos); + this.GetAbsAngles(ang); + trail.Teleport(pos, ang, NULL_VECTOR); + } + trail.KeyValue("spritetrail", override); + trail.KeyValue("renderamt", alpha); + trail.KeyValue("rendermode", mode); + trail.KeyValueFloat("lifetime", 0.5); + trail.KeyValueFloat("startwidth", 1.0); + trail.KeyValueFloat("endwidth", 1.0); + trail.Spawn(); + trail.Activate(); + SetVariantString("!activator"); + trail.AcceptInput("SetParent", this.index); + this.TrailEntity = trail; + } + } + + public CBaseEntity CreateParticle(const char[] particleName) + { + CBaseEntity particle = CBaseEntity(CreateEntityByName("info_particle_system")); + if (!particle.IsValid()) + { + return CBaseEntity(-1); + } + + particle.KeyValue("effect_name", particleName); + particle.Spawn(); + particle.Activate(); + SetVariantString("!activator"); + particle.AcceptInput("SetParent", this.index); + particle.AcceptInput("Start"); + return particle; + } + + public void SetVelocity() + { + float angle[3], velocity[3], fwd[3]; + this.GetAbsAngles(angle); + GetAngleVectors(angle, fwd, NULL_VECTOR, NULL_VECTOR); + + velocity[0] = fwd[0] * this.Speed; + velocity[1] = fwd[1] * this.Speed; + velocity[2] = fwd[2] * this.Speed; + + this.Teleport(NULL_VECTOR, NULL_VECTOR, velocity); + } + + public void GetDamageForce(CBaseEntity target, float buffer[3]) + { + target.GetAbsVelocity(buffer); + NormalizeVector(buffer, buffer); + ScaleVector(buffer, 2.0); + } + + public void OnPlayerDamaged(SF2_BasePlayer player) + { + // Do nothing + } + + public void DoExplosion() + { + int owner = this.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + float pos[3], otherPos[3]; + this.GetAbsOrigin(pos); + float adjustedDamage, falloff; + float subtracted[3]; + if (this.BlastRadius) + { + falloff = this.Damage / this.BlastRadius; + } + else + { + falloff = 1.0; + } + + // Search all valid entities + ArrayList hitList = new ArrayList(); + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + + player.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; + } + + if (g_Enabled) + { + if (!this.AttackWaiters && player.IsEliminated) + { + continue; + } + } + else + { + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) + { + continue; + } + } + + hitList.Push(EntIndexToEntRef(player.index)); + } + + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!building.IsValid()) + { + continue; + } + + building.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; + } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == building.GetProp(Prop_Data, "m_iTeamNum")) + { + continue; + } + + hitList.Push(EntIndexToEntRef(building.index)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + if (!building.IsValid()) + { + continue; + } + + building.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; + } + + hitList.Push(EntIndexToEntRef(building.index)); + } + + for (int i = 0; i < g_BreakableProps.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_BreakableProps.Get(i))); + if (!building.IsValid()) + { + continue; + } + + building.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; + } + + hitList.Push(EntIndexToEntRef(building.index)); + } + + for (int i = 0; i < hitList.Length; i++) + { + CBaseEntity valid = CBaseEntity(EntRefToEntIndex(hitList.Get(i))); + if (!valid.IsValid()) + { + continue; + } + + float targetPos[3]; + valid.WorldSpaceCenter(targetPos); + TR_TraceRayFilter(pos, targetPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_GRATE, + RayType_EndPoint, TraceRayDontHitAnyEntity, this.index); + + if (TR_DidHit() && TR_GetEntityIndex() != valid.index) + { + continue; + } + + TR_GetEndPosition(subtracted); + + SubtractVectors(pos, subtracted, subtracted); + adjustedDamage = GetVectorLength(subtracted) * falloff; + adjustedDamage = this.Damage - adjustedDamage; + + if (adjustedDamage <= 0.0) + { + continue; + } + + int flags = DMG_BLAST; + if (this.IsCrits) + { + flags |= DMG_ACID; + } + float force[3]; + this.GetDamageForce(valid, force); + SDKHooks_TakeDamage(valid.index, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, adjustedDamage, flags, _, force, pos); + if (SF2_BasePlayer(valid.index).IsValid) + { + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(SF2_BasePlayer(valid.index)); + Call_PushCell(this); + Call_Finish(); + } + } + + delete hitList; + if (this.Type != SF2BossProjectileType_Mangler) + { + EmitSoundToAll(this.GetImpactSound(), this.index, SNDCHAN_ITEM); + } + else + { + int random = GetRandomInt(0, 2); + char sound[PLATFORM_MAX_PATH]; + switch (random) + { + case 0: + { + strcopy(sound, sizeof(sound), MANGLER_EXPLODE1); + } + + case 1: + { + strcopy(sound, sizeof(sound), MANGLER_EXPLODE2); + } + + case 2: + { + strcopy(sound, sizeof(sound), MANGLER_EXPLODE3); + } + } + EmitSoundToAll(sound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + CBaseEntity particle = CBaseEntity(CreateEntityByName("info_particle_system")); + if (particle.IsValid()) + { + particle.KeyValue("effect_name", this.GetExplosionParticle()); + particle.Teleport(pos, NULL_VECTOR, NULL_VECTOR); + particle.Spawn(); + particle.Activate(); + particle.AcceptInput("Start"); + CreateTimer(0.1, Timer_KillEntity, EntIndexToEntRef(particle.index), TIMER_FLAG_NO_MAPCHANGE); + } + RemoveEntity(this.index); + } +} + +static void OnRemove(SF2_ProjectileBase projectile) +{ + if (projectile.TrailEntity.IsValid()) + { + RemoveEntity(projectile.TrailEntity.index); + } + if (projectile.ParticleEntity.IsValid()) + { + RemoveEntity(projectile.ParticleEntity.index); + } + if (projectile.CritEntity.IsValid()) + { + RemoveEntity(projectile.CritEntity.index); + } +} + +static void MapStart() +{ + PrecacheModel("models/roller.mdl"); + PrecacheModel("models/buildables/sentry3_rockets.mdl"); + PrecacheModel("models/weapons/w_models/w_grenade_grenadelauncher.mdl"); + PrecacheModel("models/weapons/w_models/w_arrow.mdl"); + PrecacheSound("weapons/fx/rics/arrow_impact_flesh2.wav"); +} + +static void StartTouch(int entity, int other) +{ + SF2_ProjectileBase projectile = SF2_ProjectileBase(entity); + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + + bool hit = true; + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + if (otherPlayer.IsValid) + { + if (otherPlayer.IsInGhostMode) + { + return; + } + + if (otherPlayer.IsEliminated && !projectile.AttackWaiters) + { + return; + } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == otherPlayer.Team) + { + return; + } + } + else + { + int hitIndex = NPCGetFromEntIndex(other); + if (hitIndex != -1) + { + return; + } + } + + if (owner == other) + { + return; + } + + char class[64]; + GetEntityClassname(other, class, sizeof(class)); + if (StrContains(class, "sf2_projectile", false) != -1) + { + return; + } + + if (hit) + { + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(other)); + Call_Finish(); + projectile.DoExplosion(); + } +} + +static any Native_Initialize(Handle plugin, int numParams) +{ + SF2_ProjectileBase projectile = SF2_ProjectileBase(GetNativeCell(1)); + if (!projectile.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid projectile index %d", projectile.index); + } + + float pos[3], ang[3]; + GetNativeArray(3, pos, 3); + GetNativeArray(4, ang, 3); + char trail[64], explosion[64], impact[64], model[64]; + GetNativeString(9, trail, sizeof(trail)); + GetNativeString(10, explosion, sizeof(explosion)); + GetNativeString(11, impact, sizeof(impact)); + GetNativeString(12, model, sizeof(model)); + projectile.InitializeProjectile(GetNativeCell(1), GetNativeCell(2), pos, ang, GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), GetNativeCell(8), trail, explosion, impact, model, GetNativeCell(13)); + + return 0; +} + +static any Native_DoExplosion(Handle plugin, int numParams) +{ + SF2_ProjectileBase projectile = SF2_ProjectileBase(GetNativeCell(1)); + if (!projectile.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid projectile index %d", projectile.index); + } + + projectile.DoExplosion(); + + return 0; +} + +#include "projectiles/sf2_fireball_projectile.sp" +#include "projectiles/sf2_iceball_projectile.sp" +#include "projectiles/sf2_rocket_projectile.sp" +#include "projectiles/sf2_sentry_rocket_projectile.sp" +#include "projectiles/sf2_cow_mangler_projectile.sp" +#include "projectiles/sf2_grenade_projectile.sp" +#include "projectiles/sf2_arrow_projectile.sp" +#include "projectiles/sf2_baseball_projectile.sp" \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/extras/bosspreview.sp b/addons/sourcemod/scripting/sf2/extras/bosspreview.sp new file mode 100644 index 00000000..106deb41 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/extras/bosspreview.sp @@ -0,0 +1,650 @@ +enum struct BossData +{ + char Name[64]; + char Profile[64]; + char Pack[64]; + char Type[32]; + char Description[128]; + char WalkSpeed[32]; + char RunSpeed[32]; + char Filepath[PLATFORM_MAX_PATH]; +} + +enum struct PackData +{ + char Name[64]; + char Path[64]; + bool AutoLoad; +} + +enum struct EquipData +{ + char Profile[64]; + int Flags; +} + +static ArrayList g_BossPacks; +static ArrayList g_BossList; + +static int g_MainMenuPage[MAXPLAYERS + 1]; +static int g_PackMenuPage[MAXPLAYERS + 1]; +static char g_ViewingPack[MAXPLAYERS + 1][64]; +static char g_ViewingBoss[MAXPLAYERS + 1][64]; + +static int g_PreviewRef[MAXPLAYERS + 1] = {INVALID_ENT_REFERENCE, ...}; + +void BossPreview_ReloadPack() +{ + if (g_BossPacks) + { + delete g_BossPacks; + } + + if (g_BossList) + { + delete g_BossList; + } + + g_BossPacks = new ArrayList(sizeof(PackData)); + g_BossList = new ArrayList(sizeof(BossData)); + + BossData data; + char pack[64], buffer[PLATFORM_MAX_PATH]; + + if (!g_UseAlternateConfigDirectoryConVar.BoolValue) + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_DIR); + } + else + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_DIR_DATA); + } + strcopy(data.Pack, sizeof(data.Pack), "Core Pack"); + AddSlenderPack(data, "", buffer, true); + + KeyValues kv = new KeyValues("root"); + + if (!g_UseAlternateConfigDirectoryConVar.BoolValue) + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_PACKS); + } + else + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_PACKS_DATA); + } + if (kv.ImportFromFile(buffer)) + { + kv.JumpToKey("packs"); + kv.GotoFirstSubKey(); + + do + { + kv.GetString("file", pack, sizeof(pack)); + BuildPath(Path_SM, buffer, sizeof(buffer), "%s/%s", !g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_PROFILES_PACKS_DIR : FILE_PROFILES_PACKS_DIR_DATA, pack); + kv.GetString("name", data.Pack, sizeof(data.Pack)); + + AddSlenderPack(data, pack, buffer, kv.GetNum("autoload", false) != 0); + } + while (kv.GotoNextKey()); + } + + delete kv; +} + +Action BossPreview_MainMenu(int client, int args) +{ + if (client) + { + if (args) + { + char buffer[64]; + GetCmdArgString(buffer, sizeof(buffer)); + StripQuotes(buffer); + + BossData boss; + int i; + int length = g_BossList.Length; + for (;i < length; i++) + { + g_BossList.GetArray(i, boss); + if (StrContains(boss.Name, buffer, false) != -1) + { + strcopy(g_ViewingPack[client], sizeof(g_ViewingPack[]), boss.Pack); + BossMenu(client, boss.Profile); + break; + } + } + + if (i == length) + { + ReplyToCommand(client, "Could not find boss matching \"%s\"", buffer); + } + } + else + { + MainMenu(client); + } + } + return Plugin_Handled; +} + +static void MainMenu(int client) +{ + BossPreview_Remove(client); + + g_PackMenuPage[client] = 0; + + Menu menu = new Menu(MainMenuH); + + menu.SetTitle("Boss List:\n "); + + char buffer[64], current[64]; + GetCurrentBossPack(current, sizeof(current)); + + PackData pack; + int length = g_BossPacks.Length; + for (int i; i < length; i++) + { + g_BossPacks.GetArray(i, pack); + if (!pack.AutoLoad && strcmp(pack.Name, current) == 0) + { + Format(buffer, sizeof(buffer), "Current: %s\n ", pack.Name); + menu.InsertItem(0, pack.Name, buffer); + } + else if (pack.AutoLoad) + { + Format(buffer, sizeof(buffer), "%s\n ", pack.Name); + menu.AddItem(pack.Name, buffer); + } + else + { + menu.AddItem(pack.Name, pack.Name); + } + } + + menu.DisplayAt(client, g_MainMenuPage[client], MENU_TIME_FOREVER); +} + +static int MainMenuH(Menu menu, MenuAction action, int client, int choice) +{ + switch(action) + { + case MenuAction_End: + { + delete menu; + } + case MenuAction_Select: + { + g_MainMenuPage[client] = choice / 7 * 7; + + char buffer[64]; + menu.GetItem(choice, buffer, sizeof(buffer)); + PackMenu(client, buffer); + } + } + return 0; +} + +static void PackMenu(int client, const char[] pack) +{ + BossPreview_Remove(client); + + strcopy(g_ViewingPack[client], sizeof(g_ViewingPack[]), pack); + + Menu menu = new Menu(PackMenuH); + menu.SetTitle("%s\n ", pack); + + PackData packData; + for (int i = 0; i < g_BossPacks.Length; i++) + { + g_BossPacks.GetArray(i, packData); + if (strcmp(packData.Name, pack) == 0) + { + break; + } + } + + BossData boss; + int length = g_BossList.Length; + for (int i = 0; i < length; i++) + { + g_BossList.GetArray(i, boss); + if (strcmp(boss.Pack, pack) == 0) + { + menu.AddItem(boss.Profile, boss.Name); + } + } + + if (menu.ItemCount == 0) + { + PrintToChat(client, "Boss pack %s is not available to view right now.", pack); + MainMenu(client); + return; + } + + menu.ExitBackButton = true; + menu.DisplayAt(client, g_PackMenuPage[client], MENU_TIME_FOREVER); +} + +static int PackMenuH(Menu menu, MenuAction action, int client, int choice) +{ + switch (action) + { + case MenuAction_End: + { + delete menu; + } + case MenuAction_Cancel: + { + if(choice == MenuCancel_ExitBack) + { + MainMenu(client); + } + } + case MenuAction_Select: + { + g_PackMenuPage[client] = choice / 7 * 7; + + char buffer[64]; + menu.GetItem(choice, buffer, sizeof(buffer)); + BossMenu(client, buffer); + } + } + return 0; +} + +static void BossMenu(int client, const char[] profile) +{ + BossData boss; + int flags = g_BossList.Length; + for (int i; i < flags; i++) + { + g_BossList.GetArray(i, boss); + if (strcmp(boss.Profile, profile) == 0) + { + break; + } + } + + Menu menu = new Menu(BossMenuH); + char buffer[256], buffer2[128]; + FormatEx(buffer, sizeof(buffer), "%s\n \n%s\n", g_ViewingPack[client], boss.Name); + FormatEx(buffer2, sizeof(buffer2), "Type: %s\n \n", boss.Type); + StrCat(buffer, sizeof(buffer), buffer2); + FormatEx(buffer2, sizeof(buffer2), "Walk speed: %s\n", boss.WalkSpeed); + StrCat(buffer, sizeof(buffer), buffer2); + FormatEx(buffer2, sizeof(buffer2), "Run speed: %s\n", boss.RunSpeed); + StrCat(buffer, sizeof(buffer), buffer2); + FormatEx(buffer2, sizeof(buffer2), "%s", boss.Description); + ReplaceString(buffer2, sizeof(buffer2), "\\n", "\n"); + StrCat(buffer, sizeof(buffer), buffer2); + FormatEx(buffer2, sizeof(buffer2), "\n "); + StrCat(buffer, sizeof(buffer), buffer2); + + menu.SetTitle(buffer); + + menu.AddItem(boss.Profile, "Preview Boss", IsProfileValid(boss.Profile) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + + g_BossPreviewWikiConVar.GetString(boss.Name, sizeof(boss.Name)); + if (boss.Name[0] != '\0') + { + menu.AddItem(boss.Profile, "Open Wiki Page"); + } + + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); +} + +static int BossMenuH(Menu menu, MenuAction action, int client, int option) +{ + switch (action) + { + case MenuAction_End: + { + delete menu; + } + case MenuAction_Cancel: + { + if(option == MenuCancel_ExitBack) + { + PackMenu(client, g_ViewingPack[client]); + } + } + case MenuAction_Select: + { + char profile[64]; + menu.GetItem(option, profile, sizeof(profile)); + + BossData boss; + int length = g_BossList.Length; + for (int i; i < length; i++) + { + g_BossList.GetArray(i, boss); + if (strcmp(boss.Profile, profile) == 0) + { + break; + } + } + + switch (option) + { + case 0: + { + CreatePreview(client, boss.Profile); + } + case 1: + { + char buffer[PLATFORM_MAX_PATH]; + g_BossPreviewWikiConVar.GetString(buffer, sizeof(buffer)); + Format(buffer, sizeof(buffer), buffer, boss.Name); + PrintToChat(client, "%s", buffer); + + KeyValues kv = new KeyValues("data"); + + kv.SetString("title", "Wiki Page (cl_disablehtmlmotd)"); + kv.SetNum("type", MOTDPANEL_TYPE_URL); + kv.SetString("msg", buffer); + + ShowVGUIPanel(client, "info", kv, true); + delete kv; + } + } + + BossMenu(client, profile); + } + } + return 0; +} + +static void AddSlenderPack(BossData data, const char[] pack, const char[] path, bool autoLoad) +{ + DirectoryListing dir = OpenDirectory(path); + if (dir != null) + { + FileType type; + while (dir.GetNext(data.Profile, sizeof(data.Profile), type)) + { + if (type == FileType_File) + { + KeyValues kv = new KeyValues("root"); + + FormatEx(data.Filepath, sizeof(data.Filepath), "%s/%s", path, data.Profile); + if (kv.ImportFromFile(data.Filepath)) + { + kv.GetSectionName(data.Profile, sizeof(data.Profile)); + kv.GetString("name", data.Name, sizeof(data.Name)); + data.Description = "No description provided."; + int bossType = kv.GetNum("type", SF2BossType_Chaser); + switch (bossType) + { + case SF2BossType_Chaser: + { + data.Type = "Chaser"; + } + + case SF2BossType_Statue: + { + data.Type = "Statue"; + } + } + ConvertWalkSpeedToDescription(kv.GetFloat("walkspeed", 100.0), data.WalkSpeed, sizeof(data.WalkSpeed)); + ConvertRunSpeedToDescription(kv.GetFloat("speed", 300.0), data.RunSpeed, sizeof(data.RunSpeed)); + + if (kv.JumpToKey("description")) + { + if (kv.GetNum("hidden", false) != 0) + { + delete kv; + continue; + } + kv.GetString("type", data.Type, sizeof(data.Type), data.Type); + kv.GetString("description", data.Description, sizeof(data.Description), data.Description); + } + + g_BossList.PushArray(data); + } + + delete kv; + } + } + + delete dir; + + PackData data3; + strcopy(data3.Path, sizeof(data3.Path), pack); + strcopy(data3.Name, sizeof(data3.Name), data.Pack); + data3.AutoLoad = autoLoad; + g_BossPacks.PushArray(data3); + } +} + +static void CreatePreview(int client, const char[] profile) +{ + BossPreview_Remove(client); + + if (!IsValidClient(client) || !IsPlayerAlive(client) || !IsProfileValid(profile)) + { + return; + } + + if (IsClientInGhostMode(client) || g_PlayerProxy[client]) + { + return; + } + + ArrayList modelsArray = GetBossProfileModels(profile); + + char model[PLATFORM_MAX_PATH]; + modelsArray.GetString(1, model, sizeof(model)); + + CBaseAnimating entity = CBaseAnimating(CreateEntityByName("prop_dynamic")); + if (entity.IsValid()) + { + entity.SetModel(model); + entity.KeyValue("solid", "0"); + entity.SetProp(Prop_Send, "m_nBody", GetBossProfileBodyGroups(profile)); + entity.SetProp(Prop_Send, "m_nSkin", GetBossProfileSkin(profile)); + entity.SetPropFloat(Prop_Send, "m_flModelScale", GetBossProfileModelScale(profile)); + + entity.SetPropEnt(Prop_Data, "m_hEffectEntity", client); + SDKHook(entity.index, SDKHook_SetTransmit, PreviewTransmit); + + g_PreviewRef[client] = EntIndexToEntRef(entity.index); + strcopy(g_ViewingBoss[client], sizeof(g_ViewingBoss[]), profile); + SetDefaultAnimation(entity.index, profile); + + static float pos[3], ang[3]; + GetClientAbsOrigin(client, pos); + GetClientEyeAngles(client, ang); + ang[0] = 0.0; + entity.Teleport(pos, ang, NULL_VECTOR); + + entity.Spawn(); + + int move = entity.LookupPoseParameter("move_x"); + if (move > 0) + { + entity.SetPoseParameter(move, 1.0); + } + move = entity.LookupPoseParameter("move_scale"); + if (move > 0) + { + entity.SetPoseParameter(move, 1.0); + } + } +} + +void BossPreview_Remove(int client) +{ + if (IsValidEntity(g_PreviewRef[client])) + { + RemoveEntity(g_PreviewRef[client]); + g_PreviewRef[client] = INVALID_ENT_REFERENCE; + } +} + +static Action PreviewTransmit(int entity, int client) +{ + SetEdictFlags(entity, GetEdictFlags(entity) &~ FL_EDICT_ALWAYS); + + int owner = GetEntPropEnt(entity, Prop_Data, "m_hEffectEntity"); + if (owner == -1) + { + RemoveEntity(entity); + return Plugin_Continue; + } + + if(owner == client) + { + if (GetClientMenu(client) == MenuSource_None) + { + RemoveEntity(entity); + } + + return Plugin_Continue; + } + + return Plugin_Stop; +} + +static void SetDefaultAnimation(int entity, const char[] profile) +{ + SF2BossProfileMasterAnimationsData masterData; + GetBossProfileAnimationsData(profile, masterData); + char animation[64]; + masterData.GetAnimation("idle", Difficulty_Normal, animation, sizeof(animation)); + + ArrayList animations, validAnimations; + validAnimations = new ArrayList(); + masterData.Animations.GetValue("idle", animations); + for (int i = 0; i < animations.Length; i++) + { + validAnimations.Push(i); + } + if (validAnimations.Length <= 0) + { + return; + } + + float playback; + int randomIndex = validAnimations.Get(GetRandomInt(0, validAnimations.Length - 1)); + masterData.GetAnimation("idle", Difficulty_Normal, animation, sizeof(animation), playback, _, _, _, _, randomIndex); + + CBaseAnimating animator = CBaseAnimating(entity); + + SetVariantString(animation); + animator.AcceptInput("SetDefaultAnimation"); + + SetVariantString(animation); + animator.AcceptInput("SetAnimation"); + + animator.SetPropFloat(Prop_Data, "m_flCycle", 0.0); + animator.SetPropFloat(Prop_Send, "m_flPlaybackRate", playback); + + delete validAnimations; +} + +void BossPreview_OnClientAttack(int client) +{ + if (!IsValidEntity(g_PreviewRef[client])) + { + return; + } + + int entity = EntRefToEntIndex(g_PreviewRef[client]); + + SF2BossProfileMasterAnimationsData masterData; + GetBossProfileAnimationsData(g_ViewingBoss[client], masterData); + char animation[64]; + masterData.GetAnimation("attack", Difficulty_Normal, animation, sizeof(animation)); + + ArrayList animations, validAnimations; + validAnimations = new ArrayList(); + masterData.Animations.GetValue("attack", animations); + for (int i = 0; i < animations.Length; i++) + { + validAnimations.Push(i); + } + if (validAnimations.Length <= 0) + { + return; + } + + float playback; + int randomIndex = validAnimations.Get(GetRandomInt(0, validAnimations.Length - 1)); + masterData.GetAnimation("attack", Difficulty_Normal, animation, sizeof(animation), playback, _, _, _, _, randomIndex); + + CBaseAnimating animator = CBaseAnimating(entity); + + SetVariantString(animation); + animator.AcceptInput("SetAnimation"); + + animator.SetPropFloat(Prop_Data, "m_flCycle", 0.0); + animator.SetPropFloat(Prop_Send, "m_flPlaybackRate", playback); + + delete validAnimations; +} + +static void ConvertWalkSpeedToDescription(float speed, char[] buffer, int bufferLen) +{ + char val[128]; + val = "None"; + if (speed > 0.0 && speed <= 45.0) + { + val = "Very slow"; + } + else if (speed > 45.0 && speed <= 65.0) + { + val = "Slow"; + } + else if (speed > 65.0 && speed <= 85.0) + { + val = "Moderate"; + } + else if (speed > 85.0 && speed <= 120.0) + { + val = "Average"; + } + else if (speed > 120.0 && speed <= 150.0) + { + val = "Fast"; + } + else + { + val = "Very fast"; + } + + strcopy(buffer, bufferLen, val); +} + +static void ConvertRunSpeedToDescription(float speed, char[] buffer, int bufferLen) +{ + char val[128]; + val = "None"; + if (speed > 0.0 && speed <= 75.0) + { + val = "Very slow"; + } + else if (speed > 75.0 && speed <= 150.0) + { + val = "Slow"; + } + else if (speed > 150.0 && speed <= 275.0) + { + val = "Moderate"; + } + else if (speed > 275.0 && speed <= 325.0) + { + val = "Average"; + } + else if (speed > 325.0 && speed <= 375.0) + { + val = "Fast"; + } + else + { + val = "Very fast"; + } + + strcopy(buffer, bufferLen, val); +} diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 64c9626c..8eb2fc5b 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -72,6 +72,9 @@ public void OnPluginStart() g_Pages = new ArrayList(sizeof(SF2PageEntityData)); g_PageMusicRanges = new ArrayList(3); + g_EmptySpawnPagePoints = new ArrayList(); + g_PageLocations = new ArrayList(); + g_PageLocationsGlow = new ArrayList(); char valueToString[32]; @@ -101,8 +104,6 @@ public void OnPluginStart() g_PlayerViewbobHurtEnabledConVar.AddChangeHook(OnConVarChanged); g_PlayerViewbobSprintEnabledConVar = CreateConVar("sf2_player_viewbob_sprint_enabled", "0", "Enable/Disable player step viewbobbing when sprinting.", _, true, 0.0, true, 1.0); g_PlayerViewbobSprintEnabledConVar.AddChangeHook(OnConVarChanged); - g_GravityConVar = FindConVar("sv_gravity"); - g_GravityConVar.AddChangeHook(OnConVarChanged); g_ForcedHolidayConVar = FindConVar("tf_forced_holiday"); @@ -110,15 +111,15 @@ public void OnPluginStart() g_PhysicsPushScaleConVar = FindConVar("phys_pushscale"); - g_DragonsFuryBurningBonus = FindConVar("tf_fireball_burning_bonus"); - g_DragonsFuryBurnDuration = FindConVar("tf_fireball_burn_duration"); + g_DragonsFuryBurningBonusConVar = FindConVar("tf_fireball_burning_bonus"); + g_DragonsFuryBurnDurationConVar = FindConVar("tf_fireball_burn_duration"); g_PlayerShakeEnabledConVar = CreateConVar("sf2_player_shake_enabled", "1", "Enable/Disable player view shake during boss encounters.", _, true, 0.0, true, 1.0); g_PlayerShakeEnabledConVar.AddChangeHook(OnConVarChanged); g_PlayerShakeFrequencyMaxConVar = CreateConVar("sf2_player_shake_frequency_max", "255", "Maximum frequency value of the shake. Should be a value between 1-255.", _, true, 1.0, true, 255.0); g_PlayerShakeAmplitudeMaxConVar = CreateConVar("sf2_player_shake_amplitude_max", "5", "Maximum amplitude value of the shake. Should be a value between 1-16.", _, true, 1.0, true, 16.0); - g_PlayerBlinkRateConVar = CreateConVar("sf2_player_blink_rate", "0.33", "How long (in seconds) each bar on the player's Blink meter lasts.", _, true, 0.0); + g_PlayerBlinkRateConVar = CreateConVar("sf2_player_blink_rate", "1.0", "How long (in seconds) each bar on the player's Blink meter lasts.", _, true, 0.0); g_PlayerBlinkHoldTimeConVar = CreateConVar("sf2_player_blink_holdtime", "0.15", "How long (in seconds) a player will stay in Blink mode when he or she blinks.", _, true, 0.0); g_UltravisionEnabledConVar = CreateConVar("sf2_player_ultravision_enabled", "1", "Enable/Disable player Ultravision. This helps players see in the dark when their Flashlight is off or unavailable.", _, true, 0.0, true, 1.0); @@ -127,6 +128,7 @@ public void OnPluginStart() g_NightvisionRadiusConVar = CreateConVar("sf2_player_nightvision_radius", "900.0"); g_UltravisionBrightnessConVar = CreateConVar("sf2_player_ultravision_brightness", "-2"); g_NightvisionEnabledConVar = CreateConVar("sf2_player_flashlight_isnightvision", "0", "Enable/Disable flashlight replacement with nightvision",_, true, 0.0, true, 1.0); + g_NightvisionEnabledConVar.AddChangeHook(OnConVarChanged); g_GhostModeConnectionConVar = CreateConVar("sf2_ghostmode_no_tolerance", "0", "If set on 1, it will instant kick out the client of the Ghost mode if the client has timed out."); g_GhostModeConnectionCheckConVar = CreateConVar("sf2_ghostmode_check_connection", "1", "Checks a player's connection while in Ghost Mode. If the check fails, the client is booted out of Ghost Mode and the action and client's SteamID is logged in the main SF2 log."); @@ -138,13 +140,6 @@ public void OnPluginStart() g_MaxPlayersOverrideConVar = CreateConVar("sf2_maxplayers_override", "-1", "Overrides the maximum amount of players that can be in one round.", _, true, -1.0); g_MaxPlayersOverrideConVar.AddChangeHook(OnConVarChanged); - g_CampingEnabledConVar = CreateConVar("sf2_anticamping_enabled", "1", "Enable/Disable anti-camping system for RED.", _, true, 0.0, true, 1.0); - g_CampingMaxStrikesConVar = CreateConVar("sf2_anticamping_maxstrikes", "4", "How many 5-second intervals players are allowed to stay in one spot before he/she is forced to suicide.", _, true, 0.0); - g_CampingStrikesWarnConVar = CreateConVar("sf2_anticamping_strikeswarn", "2", "The amount of strikes left where the player will be warned of camping."); - g_ExitCampingTimeAllowedConVar = CreateConVar("sf2_exitcamping_allowedtime", "25.0", "The amount of time a player can stay near the exit before being flagged as camper."); - g_CampingMinDistanceConVar = CreateConVar("sf2_anticamping_mindistance", "128.0", "Every 5 seconds the player has to be at least this far away from his last position 5 seconds ago or else he'll get a strike."); - g_CampingNoStrikeSanityConVar = CreateConVar("sf2_anticamping_no_strike_sanity", "0.1", "The camping system will NOT give any strikes under any circumstances if the players's Sanity is missing at least this much of his maximum Sanity (max is 1.0)."); - g_CampingNoStrikeBossDistanceConVar = CreateConVar("sf2_anticamping_no_strike_boss_distance", "512.0", "The camping system will NOT give any strikes under any circumstances if the player is this close to a boss (ignoring LOS)."); g_BossMainConVar = CreateConVar("sf2_boss_main", "slenderman", "The name of the main boss (its profile name, not its display name)"); g_BossProfileOverrideConVar = CreateConVar("sf2_boss_profile_override", "", "Overrides which boss will be chosen next. Only applies to the first boss being chosen."); g_DifficultyConVar = CreateConVar("sf2_difficulty", "1", "Difficulty of the game. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon.", _, true, 1.0, true, 5.0); @@ -169,9 +164,8 @@ public void OnPluginStart() g_IgnoreRedPlayerDeathSwapConVar = CreateConVar("sf2_ignore_red_player_death_team_switch", "0", "If set to 1, RED players will not switch back to the BLU team.", FCVAR_NOTIFY, true, 0.0, true, 1.0); g_IgnoreRedPlayerDeathSwapConVar.AddChangeHook(OnConVarChanged); - g_DefaultBossVisibilityStateConVar = CreateConVar("sf2_default_boss_visibility", "0", "Sets the default visibility state for bosses before the CBaseNPC update to allow for easier/harder peeking, this does not affect bosses seeing through transparent surfaces like glass however. Keep in mind creators can override this value for specific bosses only.", _, true, 0.0, true, 1.0); - g_EnableWallHaxConVar = CreateConVar("sf2_enable_wall_hax", "0", "Enables/disables the Wall Hax special round without needing to turn on Wall Hax. This will not force the difficulty to Insane and will show player + boss outlines.", _, true, 0.0, true, 1.0); + g_EnableWallHaxConVar.AddChangeHook(OnConVarChanged); g_TimeLimitConVar = CreateConVar("sf2_timelimit_default", "300", "The time limit of the round. Maps can change the time limit.", _, true, 0.0); g_TimeLimitEscapeConVar = CreateConVar("sf2_timelimit_escape_default", "90", "The time limit to escape. Maps can change the time limit.", _, true, 0.0); @@ -204,8 +198,9 @@ public void OnPluginStart() g_RenevantMaxWaves = CreateConVar("sf2_renevant_maxwaves", valueToString, "Determines the maximum number of waves the Revenant gamemode can use.", _, true, 0.0); g_SlaughterRunMapConVar = CreateConVar("sf2_isslaughterrunmap", "0", "Set to 1 if the map is a slaughter run map.", _, true, 0.0, true, 1.0); - g_SlaughterRunDivisibleTimeConVar = CreateConVar("sf2_slaughterrun_divide_time", "125.0", "Determines how much the average time should be divided by in Slaughter Run, the lower the number, the longer the bosses spawn.", _, true, 0.0); + g_SlaughterRunDefaultClassRunSpeedConVar = CreateConVar("sf2_slaughterrun_class_run_speed", "450.0", "How fast every class will run if Slaughter Run is enabled.", _, true); + g_SlaughterRunMinimumBossRunSpeedConVar = CreateConVar("sf2_slaughterrun_min_boss_run_speed", "600.0", "The speed bosses will run at if their original run speed is less than this value.", _, true); g_UseAlternateConfigDirectoryConVar = CreateConVar("sf2_alternateconfigs", "0", "Set to 1 if the server should pick up the configs from data/.", _, true, 0.0, true, 1.0); @@ -226,26 +221,37 @@ public void OnPluginStart() g_DefaultLegacyHudConVar = CreateConVar("sf2_default_legacy_hud", "0", "Set to 1 if the server should enable the legacy hud by default in their settings.", _, true, 0.0, true, 1.0); g_DifficultyVoteOptionsConVar = CreateConVar("sf2_difficulty_vote_options", "1,2,3", "What vote options will appear on the Difficulty vote. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon, 6 = Random"); + g_DifficultyVoteRevoteConVar = CreateConVar("sf2_difficulty_vote_runoff", "0.0", "If the winning vote has less precentage of player votes, do a run-off vote.", _, true, 0.0, true, 1.0); g_DifficultyVoteRandomConVar = CreateConVar("sf2_difficulty_random_vote", "1", "If random vote will use a random vote option instead of a random difficulty.", _, true, 0.0, true, 1.0); - g_DifficultyNoGracePageConVar = CreateConVar("sf2_difficulty_no_grace_pages", "", "On what difficulties will players be unable to collect pages while grace period is active. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon"); + g_HighDifficultyPercentConVar = CreateConVar("sf2_high_difficulty_vote_percentage", "0.0", "The required percentage needed for Nightmare and Apollyon to vote whenever they show up in the vote pool."); + g_FileCheckConVar = CreateConVar("sf2_debug_file_checks", "0", "Determines if the gamemode should look for missing files when loading all the bosses. Note that turning this on leads to longer boss loading times.", _, true, 0.0, true, 1.0); + g_LoadOutsideMapsConVar = CreateConVar("sf2_load_outside_maps", "0", "Allow bosses to be loaded outside of Slender Fortress maps.", _, true, 0.0, true, 1.0); + g_DefaultBossTeamConVar = CreateConVar("sf2_default_boss_team", "1", "If bosses are loaded outside of SF2, determine what default team bosses should be with.", _, true, 1.0, true, 5.0); + + g_EngineerBuildInBLUConVar = CreateConVar("sf2_engineer_build_in_blue", "0", "Allows BLU engineers to build outside of the PvP and PvE arena.", _, true, 0.0, true, 1.0); + g_DisableTauntLoopsConVar = CreateConVar("sf2_disable_taunt_loop_sounds", "0", "Enables/Disables the ability for proxies and REDs to hear taunt sounds.", _, true, 0.0, true, 1.0); + + g_BossPreviewWikiConVar = CreateConVar("sf2_bosspreview_wikiurl", "", "The url format to show the wiki (%s for the boss name)"); + g_MaxRoundsConVar = FindConVar("mp_maxrounds"); g_HudSync = CreateHudSynchronizer(); g_HudSync2 = CreateHudSynchronizer(); g_HudSync3 = CreateHudSynchronizer(); + g_HudSync4 = CreateHudSynchronizer(); g_RoundTimerSync = CreateHudSynchronizer(); g_Cookie = RegClientCookie("sf2_newcookies", "", CookieAccess_Private); + g_Buildings = new ArrayList(); + g_WhitelistedEntities = new ArrayList(); + g_BreakableProps = new ArrayList(); + switch (g_DifficultyConVar.IntValue) { - case Difficulty_Easy: - { - g_RoundDifficultyModifier = DIFFICULTYMODIFIER_NORMAL; - } case Difficulty_Hard: { g_RoundDifficultyModifier = DIFFICULTYMODIFIER_HARD; @@ -283,9 +289,8 @@ public void OnPluginStart() RegConsoleCmd("sm_sltuto", Command_Tutorial); RegConsoleCmd("sm_sf2tutorial", Command_Tutorial); RegConsoleCmd("sm_sf2tuto", Command_Tutorial); - RegConsoleCmd("sm_slupdate", Command_Update); - RegConsoleCmd("sm_slpack", Command_Pack); - RegConsoleCmd("sm_sf2pack", Command_Pack); + RegConsoleCmd("sm_slpack", BossPreview_MainMenu); + RegConsoleCmd("sm_sf2pack", BossPreview_MainMenu); RegConsoleCmd("sm_slnextpack", Command_NextPack); RegConsoleCmd("sm_sf2nextpack", Command_NextPack); RegConsoleCmd("sm_slnext", Command_Next); @@ -294,8 +299,15 @@ public void OnPluginStart() RegConsoleCmd("sm_slhelp", Command_Help); RegConsoleCmd("sm_slsettings", Command_Settings); RegConsoleCmd("sm_slcredits", Command_Credits); - RegConsoleCmd("sm_slviewbosslist", Command_BossList); - RegConsoleCmd("sm_slbosslist", Command_BossList); + RegConsoleCmd("sm_slviewbosslist", BossPreview_MainMenu); + RegConsoleCmd("sm_slbosslist", BossPreview_MainMenu); + RegConsoleCmd("sm_slpacks", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_sf2packs", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_sf2viewbosslist", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_sf2bosslist", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_bosslist", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_listboss", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_bosspack", BossPreview_MainMenu, "", FCVAR_HIDDEN); RegConsoleCmd("sm_slafk", Command_NoPoints); RegConsoleCmd("sm_flashlight", Command_ToggleFlashlight); RegConsoleCmd("sm_slhud", Command_MenuSwitchHud); @@ -336,6 +348,19 @@ public void OnPluginStart() RegAdminCmd("sm_sf2_endless_chasing", Command_EndlessChasing, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_red_player_death_switch", Command_RedDeathTeamSwitch, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_set_queue", Command_SetQueuePoints, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_toggle_intro", Command_ToggleIntro, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_global_alltalk", Command_ToggleGlobalAllTalk, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_block_suicide", Command_ToggleBlockSuicide, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_maxplayers", Command_MaxPlayers, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_maxplayers_override", Command_MaxPlayersOverride, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_specialround_mode", Command_SpecialRoundMode, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_player_infinite_sprint_override", Command_InfiniteSprint, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_player_infinite_flashlight_override", Command_InfiniteFlashlight, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_player_infinite_blink_override", Command_InfiniteBlink, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_wall_hax", Command_WallHax, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_keep_weapons", Command_KeepWeapons, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_reveal_page_locations", Command_RevealPageLocations, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_set_modboss", Command_BossOverride, ADMFLAG_SLAY); RegAdminCmd("+alltalk", Command_AllTalkOn, ADMFLAG_SLAY); RegAdminCmd("-alltalk", Command_AllTalkOff, ADMFLAG_SLAY); RegAdminCmd("+slalltalk", Command_AllTalkOn, ADMFLAG_SLAY, _, _, FCVAR_HIDDEN); @@ -373,6 +398,8 @@ public void OnPluginStart() HookEvent("player_death", Event_PlayerDeathPre, EventHookMode_Pre); HookEvent("player_death", Event_PlayerDeath); HookEvent("player_changeclass", Event_PlayerClass); + HookEvent("player_healed", Event_PlayerHealed, EventHookMode_Pre); + HookEvent("player_healonhit", Event_HealOnHit, EventHookMode_Pre); // Hook entities. HookEntityOutput("info_npc_spawn_destination", "OnUser1", NPCSpawn); @@ -387,8 +414,50 @@ public void OnPluginStart() HookUserMessage(GetUserMessageId("SayText2"), Hook_BlockUserMessageEx, true); //HookUserMessage(GetUserMessageId("TextMsg"), Hook_BlockUserMessage, true); + g_OnGamemodeStartPFwd = new PrivateForward(ET_Ignore); + g_OnGamemodeEndPFwd = new PrivateForward(ET_Ignore); + g_OnMapStartPFwd = new PrivateForward(ET_Ignore); + g_OnMapEndPFwd = new PrivateForward(ET_Ignore); + g_OnGameFramePFwd = new PrivateForward(ET_Ignore); + g_OnRoundStartPFwd = new PrivateForward(ET_Ignore); + g_OnRoundEndPFwd = new PrivateForward(ET_Ignore); + g_OnEntityCreatedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_String); + g_OnEntityDestroyedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_String); + g_OnEntityTeleportedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerJumpPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerTakeDamagePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_CellByRef, Param_CellByRef, Param_FloatByRef, Param_CellByRef); + g_OnPlayerTakeDamagePostPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Cell); + g_OnPlayerDeathPrePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + g_OnPlayerDeathPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + g_OnPlayerPutInServerPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerDisconnectedPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerEscapePFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerTeamPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerClassPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerLookAtBossPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerChangePlayStatePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell); + g_OnPlayerChangeGhostStatePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerChangeProxyStatePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerConditionAddedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerConditionRemovedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerTurnOnFlashlightPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerTurnOffFlashlightPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerFlashlightBreakPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerAverageUpdatePFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnSpecialRoundStartPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnBossSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnBossRemovedPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnChaserGetAttackActionPFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_CellByRef); + g_OnChaserGetCustomAttackPossibleStatePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); + g_OnChaserUpdatePosturePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); + g_OnDifficultyChangePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnDifficultyVoteFinishedPFwd = new PrivateForward(ET_Hook, Param_Cell, Param_CellByRef); + g_OnRenevantTriggerWavePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnWallHaxDebugPFwd = new PrivateForward(ET_Ignore); + // Hook sounds. - AddNormalSoundHook(view_as(Hook_NormalSound)); + AddNormalSoundHook(Hook_NormalSound); AddTempEntHook("Fire Bullets", Hook_TEFireBullets); @@ -400,26 +469,71 @@ public void OnPluginStart() NPCInitialize(); + SetupTraps(); + SetupMenus(); + InitializeChangelog(); + + InitializeEffects(); + + SetupClients(); + + SetupAntiCamping(); + SetupBlink(); + SetupBreathing(); + SetupDeathCams(); + SetupGhost(); + SetupProxy(); + SetupHints(); + SetupStatic(); + SetupFlashlight(); + SetupMusic(); + SetupSprint(); + SetupUltravision(); + SetupPlayerGlows(); + SetupInteractables(); + SetupAdminMenu(); SetupPlayerGroups(); PvP_Initialize(); + PvE_Initialize(); SetupCustomMapEntities(); + SetupEntityActions(); + + InitializeCustomEntities(); + + SetupSpecialRounds(); + + SetupRenevantMode(); + + SetupGlows(); + g_FuncNavPrefer = new ArrayList(); + CreateTimer(0.1, Timer_OnEverythingLoaded, _, TIMER_FLAG_NO_MAPCHANGE); + // @TODO: When cvars are finalized, set this to true. AutoExecConfig(false); #if defined DEBUG InitializeDebug(); #endif +} +// You have to do this otherwise SF2_OnEverythingLoaded won't call +static Action Timer_OnEverythingLoaded(Handle timer) +{ + if (!g_Enabled) + { + return Plugin_Stop; + } Call_StartForward(g_OnEverythingLoadedFwd); Call_Finish(); + return Plugin_Stop; } static Action Command_BlockCommand(int args) @@ -446,35 +560,40 @@ static void OnDifficultyConVarChangedForward(ConVar cvar, const char[] oldValue, Call_PushCell(difficulty); Call_PushCell(oldDifficulty); Call_Finish(); + + Call_StartForward(g_OnDifficultyChangePFwd); + Call_PushCell(difficulty); + Call_PushCell(oldDifficulty); + Call_Finish(); } // ========================================================== // COMMANDS AND COMMAND HOOK FUNCTIONS // ========================================================== -static Action Command_Help(int client,int args) +static Action Command_Help(int client, int args) { if (!g_Enabled) { return Plugin_Continue; } - DisplayMenu(g_MenuHelp, client, 30); + g_MenuHelp.Display(client, 30); return Plugin_Handled; } -static Action Command_Settings(int client,int args) +static Action Command_Settings(int client, int args) { if (!g_Enabled) { return Plugin_Continue; } - DisplayMenu(g_MenuSettings, client, 30); + g_MenuSettings.Display(client, 30); return Plugin_Handled; } -static Action Command_MenuSwitchHud(int client,int args) +static Action Command_MenuSwitchHud(int client, int args) { if (!g_Enabled) { @@ -484,18 +603,18 @@ static Action Command_MenuSwitchHud(int client,int args) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Hud Version Title", client); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Use the new HUD"); - DrawPanelItem(panel, "Use the legacy HUD"); + panel.DrawItem("Use the new HUD"); + panel.DrawItem("Use the legacy HUD"); - SendPanelToClient(panel, client, Panel_SettingsHudVersion, 30); + panel.Send(client, Panel_SettingsHudVersion, 30); delete panel; return Plugin_Handled; } -static Action Command_MenuViewBob(int client,int args) +static Action Command_MenuViewBob(int client, int args) { if (!g_Enabled) { @@ -503,38 +622,27 @@ static Action Command_MenuViewBob(int client,int args) } char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings View Bobbing Toggle Title", client); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); - DrawPanelItem(panel, "Enable View Bobbing"); - DrawPanelItem(panel, "Disable View Bobbing"); - SendPanelToClient(panel, client, Panel_SettingsViewBobbing, 30); + Panel panel = new Panel(); + panel.SetTitle(buffer); + panel.DrawItem("Enable View Bobbing"); + panel.DrawItem("Disable View Bobbing"); + panel.Send(client, Panel_SettingsViewBobbing, 30); delete panel; return Plugin_Handled; } -static Action Command_Credits(int client,int args) +static Action Command_Credits(int client, int args) { if (!g_Enabled) { return Plugin_Continue; } - DisplayMenu(g_MenuCredits, client, MENU_TIME_FOREVER); + g_MenuCredits.Display(client, MENU_TIME_FOREVER); return Plugin_Handled; } -static Action Command_BossList(int client,int args) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - DisplayBossList(client); - return Plugin_Handled; -} - -static Action Command_NoPoints(int client,int args) +static Action Command_NoPoints(int client, int args) { if (!g_Enabled) { @@ -555,14 +663,14 @@ static Action Command_NoPoints(int client,int args) return Plugin_Handled; } -static Action Command_ToggleFlashlight(int client,int args) +static Action Command_ToggleFlashlight(int client, int args) { if (!g_Enabled) { return Plugin_Continue; } - if (!IsClientInGame(client) || !IsPlayerAlive(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client)) { return Plugin_Handled; } @@ -578,7 +686,7 @@ static Action Command_ToggleFlashlight(int client,int args) return Plugin_Handled; } -static Action Command_SprintOn(int client,int args) +static Action Command_SprintOn(int client, int args) { if (!g_Enabled) { @@ -593,7 +701,7 @@ static Action Command_SprintOn(int client,int args) return Plugin_Handled; } -static Action Command_SprintOff(int client,int args) +static Action Command_SprintOff(int client, int args) { if (!g_Enabled) { @@ -645,7 +753,7 @@ static Action Command_BlinkOff(int client, int args) return Plugin_Handled; } -static Action DevCommand_BossPackVote(int client,int args) +static Action DevCommand_BossPackVote(int client, int args) { if (!g_Enabled) { @@ -655,7 +763,7 @@ static Action DevCommand_BossPackVote(int client,int args) return Plugin_Handled; } -static Action Command_NoPointsAdmin(int client,int args) +static Action Command_NoPointsAdmin(int client, int args) { if (!g_Enabled) { @@ -694,7 +802,7 @@ static Action Command_NoPointsAdmin(int client,int args) { char arg2[32]; GetCmdArg(2, arg2, sizeof(arg2)); - mode = !!StringToInt(arg2); + mode = StringToInt(arg2) != 0; } for (int i = 0; i < target_count; i++) @@ -702,7 +810,7 @@ static Action Command_NoPointsAdmin(int client,int args) int target = target_list[i]; if (IsClientSourceTV(target)) { - continue;//Exclude the sourcetv bot + continue; // Exclude the sourcetv bot } g_AdminNoPoints[client] = args > 1 ? mode : !g_AdminNoPoints[client]; @@ -721,17 +829,17 @@ static Action Command_NoPointsAdmin(int client,int args) return Plugin_Handled; } -static Action Command_MainMenu(int client,int args) +static Action Command_MainMenu(int client, int args) { if (!g_Enabled) { return Plugin_Continue; } - DisplayMenu(g_MenuMain, client, 30); + g_MenuMain.Display(client, 30); return Plugin_Handled; } -static Action Command_Tutorial(int client,int args) +static Action Command_Tutorial(int client, int args) { if (!g_Enabled) { @@ -741,17 +849,7 @@ static Action Command_Tutorial(int client,int args) return Plugin_Handled; } -static Action Command_Update(int client, int args) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - DisplayMenu(g_MenuUpdate, client, 30); - return Plugin_Handled; -} - -static Action Command_Next(int client,int args) +static Action Command_Next(int client, int args) { if (!g_Enabled) { @@ -762,7 +860,7 @@ static Action Command_Next(int client,int args) return Plugin_Handled; } -static Action Command_Group(int client,int args) +static Action Command_Group(int client, int args) { if (!g_Enabled) { @@ -773,7 +871,7 @@ static Action Command_Group(int client,int args) return Plugin_Handled; } -static Action Command_GroupName(int client,int args) +static Action Command_GroupName(int client, int args) { if (!g_Enabled) { @@ -826,14 +924,15 @@ static Action Command_GroupName(int client,int args) return Plugin_Handled; } -static Action Command_GhostMode(int client,int args) + +static Action Command_GhostMode(int client, int args) { if (!g_Enabled) { return Plugin_Continue; } - if (IsRoundEnding() || IsRoundInWarmup() || !g_PlayerEliminated[client] || !IsClientParticipating(client) || g_PlayerProxy[client] || IsClientInPvP(client) || IsClientInKart(client) || TF2_IsPlayerInCondition(client,TFCond_Taunting)|| TF2_IsPlayerInCondition(client,TFCond_Charging) || g_LastCommandTime[client] > GetEngineTime()) + if (IsRoundEnding() || IsRoundInWarmup() || !g_PlayerEliminated[client] || !IsClientParticipating(client) || g_PlayerProxy[client] || IsClientInPvP(client) || IsClientInPvE(client) || IsClientInKart(client) || TF2_IsPlayerInCondition(client,TFCond_Taunting)|| TF2_IsPlayerInCondition(client,TFCond_Charging) || g_LastCommandTime[client] > GetEngineTime()) { CPrintToChat(client, "{red}%T", "SF2 Ghost Mode Not Allowed", client); return Plugin_Handled; @@ -855,7 +954,7 @@ static Action Command_GhostMode(int client,int args) CPrintToChat(client, "{dodgerblue}%T", "SF2 Ghost Mode Disabled", client); } - g_LastCommandTime[client] = GetEngineTime()+0.5; + g_LastCommandTime[client] = GetEngineTime() + 0.5; return Plugin_Handled; } @@ -882,7 +981,7 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] if (g_PlayerEliminated[client]) { - if (IsClientInGame(client) && !IsPlayerAlive(client) && GetClientTeam(client) == TFTeam_Red) + if (IsValidClient(client) && !IsPlayerAlive(client) && GetClientTeam(client) == TFTeam_Red) { return Plugin_Stop; // Plugin_Stop in this case stops message AND post hook so bot won't see message in OnClientSayCommand_Post() } @@ -930,6 +1029,10 @@ static Action Hook_CommandSuicideAttempt(int client, const char[] command,int ar { return Plugin_Continue; } + if (!IsValidClient(client)) + { + return Plugin_Continue; + } if (GetClientTeam(client) == TFTeam_Spectator) { return Plugin_Continue; @@ -1046,7 +1149,7 @@ static Action Hook_CommandVoiceMenu(int client, const char[] command,int argc) return Plugin_Continue; } -static Action Command_ClientKillDeathcam(int client,int args) +static Action Command_ClientKillDeathcam(int client, int args) { if (!g_Enabled) { @@ -1084,7 +1187,7 @@ static Action Command_ClientKillDeathcam(int client,int args) for (int i = 0; i < target_count; i++) { int target = target_list[i]; - if (!IsClientInGame(target) || !IsPlayerAlive(target) || g_PlayerEliminated[target] || IsClientInGhostMode(target)) + if (!IsValidClient(target) || !IsPlayerAlive(target) || g_PlayerEliminated[target] || IsClientInGhostMode(target)) { continue; } @@ -1100,7 +1203,7 @@ static Action Command_ClientKillDeathcam(int client,int args) return Plugin_Handled; } -static Action Command_ClientPerformScare(int client,int args) +static Action Command_ClientPerformScare(int client, int args) { if (!g_Enabled) { @@ -1144,9 +1247,9 @@ static Action Command_ClientPerformScare(int client,int args) return Plugin_Handled; } -static Action Command_SpawnSlender(int client,int args) +static Action Command_SpawnSlender(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1160,9 +1263,17 @@ static Action Command_SpawnSlender(int client,int args) char arg1[32]; GetCmdArg(1, arg1, sizeof(arg1)); - SF2NPC_BaseNPC Npc = view_as(StringToInt(arg1)); - if (NPCGetUniqueID(Npc.Index) == -1) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(StringToInt(arg1)); + if (!npc.IsValid()) + { + return Plugin_Handled; + } + + SF2BossProfileData data; + data = npc.GetProfileData(); + if (data.IsPvEBoss) { + ReplyToCommand(client, "You may not spawn PvE bosses!"); return Plugin_Handled; } @@ -1174,12 +1285,15 @@ static Action Command_SpawnSlender(int client,int args) TR_GetEndPosition(endPos, trace); delete trace; - SpawnSlender(Npc, endPos); + npc.Spawn(endPos); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.GetProfile(profile, sizeof(profile)); + npc.GetProfile(profile, sizeof(profile)); - CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Spawned Boss", client); + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Spawned Boss", client); + } return Plugin_Handled; } @@ -1187,9 +1301,9 @@ static Action Command_SpawnSlender(int client,int args) static Handle g_SpawnAllSlendersTimer[MAXTF2PLAYERS] = { null, ... }; static int g_SpawnAllBossesCount = 0; -static Action Command_SpawnAllSlenders(int client,int args) +static Action Command_SpawnAllSlenders(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1202,7 +1316,7 @@ static Action Command_SpawnAllSlenders(int client,int args) char arg1[32]; GetCmdArg(1, arg1, sizeof(arg1)); - bool doTimer = !!StringToInt(arg1); + bool doTimer = StringToInt(arg1) != 0; if (!doTimer) { @@ -1215,13 +1329,19 @@ static Action Command_SpawnAllSlenders(int client,int args) TR_GetEndPosition(endPos, trace); delete trace; - SF2NPC_BaseNPC Npc; - for(int npcIndex;npcIndex<=MAX_BOSSES;npcIndex++) + SF2NPC_BaseNPC npc; + for (int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - Npc = view_as(npcIndex); - if (Npc.IsValid()) + npc = SF2NPC_BaseNPC(npcIndex); + if (npc.IsValid()) { - SpawnSlender(Npc, endPos); + SF2BossProfileData data; + data = npc.GetProfileData(); + if (data.IsPvEBoss) + { + continue; + } + npc.Spawn(endPos); } } } @@ -1254,7 +1374,7 @@ static Action Timer_SpawnAllSlenders(Handle timer, any userid) { return Plugin_Stop; } - if (g_SpawnAllBossesCount >= 64) + if (g_SpawnAllBossesCount >= MAX_BOSSES) { CPrintToChat(client.index, "{royalblue}%t {default}Spawned all bosses at your locations.", "SF2 Prefix"); g_SpawnAllBossesCount = 0; @@ -1269,19 +1389,23 @@ static Action Timer_SpawnAllSlenders(Handle timer, any userid) TR_GetEndPosition(endPos, trace); delete trace; - SF2NPC_BaseNPC Npc; - Npc = view_as(g_SpawnAllBossesCount); - if (Npc.IsValid()) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(g_SpawnAllBossesCount); + if (npc.IsValid()) { - SpawnSlender(Npc, endPos); + SF2BossProfileData data; + data = npc.GetProfileData(); + if (!data.IsPvEBoss) + { + npc.Spawn(endPos); + } } g_SpawnAllBossesCount++; return Plugin_Continue; } -static Action Command_RemoveSlender(int client,int args) +static Action Command_RemoveSlender(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1304,26 +1428,27 @@ static Action Command_RemoveSlender(int client,int args) char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(bossIndex)) - { - g_SlenderBoxingBossCount -= 1; - } - - if (MusicActive() && BossHasMusic(profile) && BossMatchesCurrentMusic(profile)) + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) { - NPCStopMusic(); + ReplyToCommand(client, "You may not remove PvE bosses!"); + return Plugin_Handled; } NPCRemove(bossIndex); - CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Removed Boss", client); + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Removed Boss", client); + } return Plugin_Handled; } -static Action Command_RemoveAllSlenders(int client,int args) +static Action Command_RemoveAllSlenders(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1332,17 +1457,31 @@ static Action Command_RemoveAllSlenders(int client,int args) { for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { - if (NPCGetUniqueID(npcIndex) == -1) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(npcIndex); + if (!npc.IsValid()) + { + continue; + } + + SF2BossProfileData data; + data = npc.GetProfileData(); + if (data.IsPvEBoss) { continue; } - NPCRemove(npcIndex); + npc.Remove(); + } + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}Removed all bosses.", "SF2 Prefix", client); } - CPrintToChat(client, "{royalblue}%t {default}Removed all bosses.", "SF2 Prefix", client); } else { - CPrintToChat(client, "{royalblue}%t {default}Cannot use this command in Boxing maps.", "SF2 Prefix", client); + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}Cannot use this command in Boxing maps.", "SF2 Prefix", client); + } } if (MusicActive()) @@ -1353,7 +1492,7 @@ static Action Command_RemoveAllSlenders(int client,int args) return Plugin_Handled; } -static Action Command_GetBossIndexes(int client,int args) +static Action Command_GetBossIndexes(int client, int args) { if (!g_Enabled) { @@ -1422,7 +1561,7 @@ static Action Command_GetBossIndexes(int client,int args) return Plugin_Handled; } -static Action Command_SlenderAttackWaiters(int client,int args) +static Action Command_SlenderAttackWaiters(int client, int args) { if (!g_Enabled) { @@ -1449,8 +1588,8 @@ static Action Command_SlenderAttackWaiters(int client,int args) int bossFlags = NPCGetFlags(bossIndex); - bool state = !!StringToInt(arg2); - bool oldState = !!(bossFlags & SFF_ATTACKWAITERS); + bool state = StringToInt(arg2) != 0; + bool oldState = (bossFlags & SFF_ATTACKWAITERS) != 0; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -1461,6 +1600,11 @@ static Action Command_SlenderAttackWaiters(int client,int args) { NPCSetFlags(bossIndex, bossFlags | SFF_ATTACKWAITERS); CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Attack Waiters", client); + CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); + if (boss.IsValid()) + { + boss.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Spectator); + } } } else @@ -1469,13 +1613,18 @@ static Action Command_SlenderAttackWaiters(int client,int args) { NPCSetFlags(bossIndex, bossFlags & ~SFF_ATTACKWAITERS); CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Do Not Attack Waiters", client); + CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); + if (boss.IsValid()) + { + boss.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + } } } return Plugin_Handled; } -static Action Command_SlenderNoTeleport(int client,int args) +static Action Command_SlenderNoTeleport(int client, int args) { if (!g_Enabled) { @@ -1502,8 +1651,8 @@ static Action Command_SlenderNoTeleport(int client,int args) int bossFlags = NPCGetFlags(bossIndex); - bool state = !!StringToInt(arg2); - bool oldState = !!(bossFlags & SFF_NOTELEPORT); + bool state = StringToInt(arg2) != 0; + bool oldState = (bossFlags & SFF_NOTELEPORT) != 0; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -1528,9 +1677,9 @@ static Action Command_SlenderNoTeleport(int client,int args) return Plugin_Handled; } -static Action Command_ToggleAllBossTeleports(int client,int args) +static Action Command_ToggleAllBossTeleports(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1574,7 +1723,7 @@ static Action Command_ToggleAllBossTeleports(int client,int args) return Plugin_Handled; } -static Action Command_DebugLogicEscape(int client,int args) +static Action Command_DebugLogicEscape(int client, int args) { if (!g_Enabled) { @@ -1586,21 +1735,10 @@ static Action Command_DebugLogicEscape(int client,int args) while ((ent = FindEntityByClassname(ent, "info_target")) != -1) { GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); - if (!SF_IsBoxingMap()) - { - if (strcmp(name, "sf2_logic_escape", false) == 0) - { - AcceptEntityInput(ent, "FireUser1"); - break; - } - } - else + if (strcmp(name, "sf2_logic_escape", false) == 0) { - if (strcmp(name, "sf2_logic_escape", false) == 0) - { - AcceptEntityInput(ent, "FireUser1"); - break; - } + AcceptEntityInput(ent, "FireUser1"); + break; } } CPrintToChat(client, "{royalblue}%t {default}Triggered sf2_logic_escape.", "SF2 Prefix", client); @@ -1608,7 +1746,7 @@ static Action Command_DebugLogicEscape(int client,int args) return Plugin_Handled; } -static Action Command_ForceEndGrace(int client,int args) +static Action Command_ForceEndGrace(int client, int args) { if (!g_Enabled) { @@ -1625,21 +1763,54 @@ static Action Command_ForceEndGrace(int client,int args) static Action Command_ReloadProfiles(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } ReloadBossProfiles(); - ReloadRestrictedWeapons(); - ReloadSpecialRounds(); - ReloadClassConfigs(); + if (g_Enabled) + { + ReloadRestrictedWeapons(); + ReloadSpecialRounds(); + ReloadClassConfigs(); + BossPreview_ReloadPack(); + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + + if (!npc.IsValid()) + { + continue; + } + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + npc.GetProfile(profile, sizeof(profile)); + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + NPCSetProfileData(npc.Index, data); + + if (npc.Type == SF2BossType_Chaser) + { + SF2ChaserBossProfileData chaserData; + g_ChaserBossProfileData.GetArray(profile, chaserData, sizeof(chaserData)); + NPCChaserSetProfileData(npc.Index, chaserData); + } + else if (npc.Type == SF2BossType_Statue) + { + SF2StatueBossProfileData statueData; + g_StatueBossProfileData.GetArray(profile, statueData, sizeof(statueData)); + NPCStatueSetProfileData(npc.Index, statueData); + } + } CPrintToChatAll("{royalblue}%t {default} Reloaded all profiles successfully.", "SF2 Prefix"); return Plugin_Handled; } -static Action Command_ToggleAllAttackWaiters(int client,int args) +static Action Command_ToggleAllAttackWaiters(int client, int args) { if (!g_Enabled) { @@ -1685,7 +1856,7 @@ static Action Command_ToggleAllAttackWaiters(int client,int args) return Plugin_Handled; } -static Action Command_ForceProxy(int client,int args) +static Action Command_ForceProxy(int client, int args) { if (!g_Enabled) { @@ -1747,7 +1918,7 @@ static Action Command_ForceProxy(int client,int args) char name[MAX_NAME_LENGTH]; if (IsClientSourceTV(target)) { - continue;//Exclude the sourcetv bot + continue; // Exclude the sourcetv bot } FormatEx(name, sizeof(name), "%N", target); @@ -1759,7 +1930,7 @@ static Action Command_ForceProxy(int client,int args) if (g_PlayerProxy[target]) { - continue; //Exclude any active proxies + continue; // Exclude any active proxies } float intPos[3]; @@ -1778,7 +1949,7 @@ static Action Command_ForceProxy(int client,int args) return Plugin_Handled; } -static Action Command_ForceEscape(int client,int args) +static Action Command_ForceEscape(int client, int args) { if (!g_Enabled) { @@ -1824,9 +1995,10 @@ static Action Command_ForceEscape(int client,int args) return Plugin_Handled; } -static Action Command_ForceDifficulty(int client,int args) + +static Action Command_ForceDifficulty(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1848,17 +2020,17 @@ static Action Command_ForceDifficulty(int client,int args) int newDifficulty = StringToInt(arg1); - if (newDifficulty < 1) + if (newDifficulty < Difficulty_Normal) { - newDifficulty = 1; + newDifficulty = Difficulty_Normal; } - else if (newDifficulty > 5) + else if (newDifficulty > Difficulty_Apollyon) { - newDifficulty = 5; + newDifficulty = Difficulty_Apollyon; } - else if (newDifficulty > 0 && newDifficulty < 6) + else if (newDifficulty > Difficulty_Easy && newDifficulty < Difficulty_Max) { - g_DifficultyConVar.SetInt(newDifficulty); + SetDifficulty(newDifficulty); } switch (newDifficulty) @@ -1894,7 +2066,7 @@ static Action Command_ForceDifficulty(int client,int args) return Plugin_Handled; } -static Action Command_ForceSpecialRound(int client,int args) +static Action Command_ForceSpecialRound(int client, int args) { if (!g_Enabled) { @@ -1920,7 +2092,8 @@ static Action Command_ForceSpecialRound(int client,int args) { specialRound = 38; } - else if (specialRound > 0 && specialRound < 39) + + if (specialRound > 0 && specialRound < 39) { g_SpecialRoundOverrideConVar.SetInt(specialRound); } @@ -1947,9 +2120,9 @@ static Action Command_ForceSpecialRound(int client,int args) { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Bacon Spray.", "SF2 Prefix", client); } - case SPECIALROUND_DOOMBOX: + case SPECIALROUND_SILENTSLENDER: { - CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Stealth Box of Doom.", "SF2 Prefix", client); + CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Silent Slender {default}(Here you die).", "SF2 Prefix", client); } case SPECIALROUND_NOGRACE: { @@ -2039,10 +2212,6 @@ static Action Command_ForceSpecialRound(int client,int args) { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Class Scramble.", "SF2 Prefix", client); } - case SPECIALROUND_2DOOM: - { - CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Silent Slender.", "SF2 Prefix", client); - } case SPECIALROUND_PAGEREWARDS: { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Page Rewards.", "SF2 Prefix", client); @@ -2079,14 +2248,18 @@ static Action Command_ForceSpecialRound(int client,int args) { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Single Player.", "SF2 Prefix", client); } + case SPECIALROUND_BEATBOX: + { + CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Beatbox.", "SF2 Prefix", client); + } } return Plugin_Handled; } -static Action Command_AddSlender(int client,int args) +static Action Command_AddSlender(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -2106,35 +2279,33 @@ static Action Command_AddSlender(int client,int args) return Plugin_Handled; } - SF2NPC_BaseNPC Npc = AddProfile(profile); - if (Npc.IsValid()) + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) { - float eyePos[3], eyeAng[3], pos[3]; - GetClientEyePosition(client, eyePos); - GetClientEyeAngles(client, eyeAng); - - Handle trace = TR_TraceRayFilterEx(eyePos, eyeAng, MASK_NPCSOLID, RayType_Infinite, TraceRayDontHitEntity, client); - TR_GetEndPosition(pos, trace); - delete trace; - - SpawnSlender(Npc, pos); + ReplyToCommand(client, "You may not spawn PvE bosses!"); + return Plugin_Handled; + } - if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(Npc.Index)) + SF2NPC_BaseNPC npc = AddProfile(profile); + if (npc.IsValid()) + { + if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(npc.Index)) { - g_SlenderBoxingBossCount += 1; + g_SlenderBoxingBossCount++; } } return Plugin_Handled; } -static void NPCSpawn(const char[] output,int ent,int activator, float delay) + +static void NPCSpawn(const char[] output, int ent, int activator, float delay) { if (!g_Enabled) { return; } char targetName[255]; - float vecPos[3]; GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); if (targetName[0] != '\0') { @@ -2150,19 +2321,18 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) return; } char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - SF2NPC_BaseNPC Npc; - for(int npcIndex;npcIndex<=MAX_BOSSES;npcIndex++) + SF2NPC_BaseNPC npc; + for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - Npc = view_as(npcIndex); - if (Npc.IsValid()) + npc = SF2NPC_BaseNPC(npcIndex); + if (npc.IsValid()) { - Npc.GetProfile(profile,sizeof(profile)); - if (strcmp(profile,targetName) == 0) + npc.GetProfile(profile, sizeof(profile)); + if (strcmp(profile, targetName) == 0) { - Npc.UnSpawn(); float pos[3]; - GetEntPropVector(ent, Prop_Data, "m_vecOrigin", pos); - SpawnSlender(Npc, pos); + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); + npc.Spawn(pos); break; } } @@ -2176,24 +2346,27 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) spawnPoint.Push(ent); } ent = -1; - if (spawnPoint.Length > 0) ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); + if (spawnPoint.Length > 0) + { + ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); + } delete spawnPoint; - if (ent > MaxClients) + if (IsValidEntity(ent)) { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", vecPos); + float pos[3]; + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - SF2NPC_BaseNPC Npc = view_as(npcIndex); - if (!Npc.IsValid()) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(npcIndex); + if (!npc.IsValid()) { continue; } - if (Npc.Flags & SFF_NOTELEPORT) + if (npc.Flags & SFF_NOTELEPORT) { continue; } - Npc.UnSpawn(); - SpawnSlender(Npc, vecPos); + npc.Spawn(pos); break; } } @@ -2202,9 +2375,9 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) return; } -static Action Command_AddSlenderFake(int client,int args) +static Action Command_AddSlenderFake(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -2224,8 +2397,16 @@ static Action Command_AddSlenderFake(int client,int args) return Plugin_Handled; } - SF2NPC_BaseNPC Npc = AddProfile(profile, SFF_FAKE); - if (Npc.IsValid()) + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + ReplyToCommand(client, "You may not spawn PvE bosses!"); + return Plugin_Handled; + } + + SF2NPC_BaseNPC npc = AddProfile(profile, SFF_FAKE); + if (npc.IsValid()) { float eyePos[3], eyeAng[3], pos[3]; GetClientEyePosition(client, eyePos); @@ -2235,13 +2416,13 @@ static Action Command_AddSlenderFake(int client,int args) TR_GetEndPosition(pos, trace); delete trace; - SpawnSlender(Npc, pos); + npc.Spawn(pos); } return Plugin_Handled; } -static Action Command_ForceState(int client,int args) +static Action Command_ForceState(int client, int args) { if (!g_Enabled) { @@ -2294,14 +2475,14 @@ static Action Command_ForceState(int client,int args) if (IsClientSourceTV(target)) { - continue;//Exclude the sourcetv bot + continue; //Exclude the sourcetv bot } FormatEx(name, sizeof(name), "%N", target); if (g_PlayerProxy[target]) { - continue;//Can't force proxies + continue; //Can't force proxies } if (state && g_PlayerEliminated[target]) @@ -2312,7 +2493,7 @@ static Action Command_ForceState(int client,int args) ClientSetGhostModeState(target, false); TF2_RespawnPlayer(target); TF2_RemoveCondition(target, TFCond_StealthedUserBuffFade); - g_LastCommandTime[target] = GetEngineTime()+0.5; + g_LastCommandTime[target] = GetEngineTime() + 0.5; CreateTimer(0.25, Timer_ForcePlayer, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE); } else @@ -2459,5 +2640,359 @@ static Action Command_SetQueuePoints(int client, int args) CPrintToChatAll("{royalblue}%t {collectors}%N: {default}%t", "SF2 Prefix", client, "SF2 Set Queue Points", name); } + return Plugin_Handled; +} + +static Action Command_ToggleIntro(int client, int args) +{ + g_IntroEnabledConVar.BoolValue = !g_IntroEnabledConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}Intro is now %s.", "SF2 Prefix", g_IntroEnabledConVar.BoolValue ? "enabled" : "disabled"); + return Plugin_Handled; +} + +static Action Command_ToggleGlobalAllTalk(int client, int args) +{ + g_AllChatConVar.BoolValue = !g_AllChatConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}All talk is now %s.", "SF2 Prefix", g_AllChatConVar.BoolValue ? "enabled" : "disabled"); + return Plugin_Handled; +} + +static Action Command_ToggleBlockSuicide(int client, int args) +{ + g_BlockSuicideDuringRoundConVar.BoolValue = !g_BlockSuicideDuringRoundConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}RED players can %s suicide during rounds.", "SF2 Prefix", g_BlockSuicideDuringRoundConVar.BoolValue ? "now" : "no longer"); + return Plugin_Handled; +} + +static Action Command_MaxPlayers(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_maxplayers "); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int amount = StringToInt(arg1); + + if (amount < 1) + { + amount = 1; + } + else if (amount > 32) + { + amount = 32; + } + + g_MaxPlayersConVar.IntValue = amount; + CPrintToChat(client, "{royalblue}%t {default}Set the max players to %i.", "SF2 Prefix", amount); + return Plugin_Handled; +} + +static Action Command_MaxPlayersOverride(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_maxplayers_override "); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int amount = StringToInt(arg1); + + if (amount < 1) + { + amount = 1; + } + else if (amount > 32) + { + amount = 32; + } + + g_MaxPlayersOverrideConVar.IntValue = amount; + CPrintToChat(client, "{royalblue}%t {default}Set the overrided max players to %i.", "SF2 Prefix", amount); + return Plugin_Handled; +} + +static Action Command_SpecialRoundMode(int client, int args) +{ + g_SpecialRoundBehaviorConVar.BoolValue = !g_SpecialRoundBehaviorConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}Set the special rounds to %s.", "SF2 Prefix", g_BlockSuicideDuringRoundConVar.BoolValue ? "always reset upon the next round" : "keep going until all players have played a special round"); + return Plugin_Handled; +} + +static Action Command_InfiniteSprint(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_player_infinite_sprint_override <-1 - 1>"); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int state = StringToInt(arg1); + + if (state < -1) + { + state = 1; + } + else if (state > 1) + { + state = 1; + } + + g_PlayerInfiniteSprintOverrideConVar.IntValue = state; + switch (state) + { + case -1: + { + CPrintToChat(client, "{royalblue}%t {default}The game will now determine whether infinite sprint is allowed or not.", "SF2 Prefix"); + } + case 0: + { + CPrintToChat(client, "{royalblue}%t {default}Fully disabled infinite sprint across all maps.", "SF2 Prefix"); + } + case 1: + { + CPrintToChat(client, "{royalblue}%t {default}Fully enabled infinite sprint across all maps.", "SF2 Prefix"); + } + } + return Plugin_Handled; +} + +static Action Command_InfiniteFlashlight(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_player_infinite_flashlight_override <-1 - 1>"); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int state = StringToInt(arg1); + + if (state < -1) + { + state = 1; + } + else if (state > 1) + { + state = 1; + } + + g_PlayerInfiniteFlashlightOverrideConVar.IntValue = state; + switch (state) + { + case -1: + { + CPrintToChat(client, "{royalblue}%t {default}The game will now determine whether infinite flashlight is allowed or not.", "SF2 Prefix"); + } + case 0: + { + CPrintToChat(client, "{royalblue}%t {default}Fully disabled infinite flashlight across all maps.", "SF2 Prefix"); + } + case 1: + { + CPrintToChat(client, "{royalblue}%t {default}Fully enabled infinite flashlight across all maps.", "SF2 Prefix"); + } + } + return Plugin_Handled; +} + +static Action Command_InfiniteBlink(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_player_infinite_blink_override <-1 - 1>"); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int state = StringToInt(arg1); + + if (state < -1) + { + state = 1; + } + else if (state > 1) + { + state = 1; + } + + g_PlayerInfiniteBlinkOverrideConVar.IntValue = state; + switch (state) + { + case -1: + { + CPrintToChat(client, "{royalblue}%t {default}The game will now determine whether infinite blink is allowed or not.", "SF2 Prefix"); + } + case 0: + { + CPrintToChat(client, "{royalblue}%t {default}Fully disabled infinite blink across all maps.", "SF2 Prefix"); + } + case 1: + { + CPrintToChat(client, "{royalblue}%t {default}Fully enabled infinite blink across all maps.", "SF2 Prefix"); + } + } + return Plugin_Handled; +} + +static Action Command_WallHax(int client, int args) +{ + g_EnableWallHaxConVar.BoolValue = !g_EnableWallHaxConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}%s Wall Hax.", "SF2 Prefix", g_EnableWallHaxConVar.BoolValue ? "enabled" : "disabled"); + return Plugin_Handled; +} + +static Action Command_KeepWeapons(int client, int args) +{ + g_PlayerKeepWeaponsConVar.BoolValue = !g_PlayerKeepWeaponsConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}Lobby players can %s keep their weapons.", "SF2 Prefix", g_PlayerKeepWeaponsConVar.BoolValue ? "now" : "no longer"); + return Plugin_Handled; +} + +static Action Command_RevealPageLocations(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + + if (!g_PagesRevealed) + { + int count = 0; + EmitSoundToClient(client, DEBUG_PAGEREVEALSOUND); + for (int i = 0; i < g_EmptySpawnPagePoints.Length; i++) + { + float pos[3], ang[3]; + CBaseEntity location = CBaseEntity(g_EmptySpawnPagePoints.Get(i)); + location.GetAbsOrigin(pos); + location.GetAbsAngles(ang); + CBaseEntity page = CBaseEntity(CreateEntityByName("prop_dynamic_override")); + page.KeyValue("solid", "0"); + page.SetModel(PAGE_MODEL); + page.Teleport(pos, ang); + page.Spawn(); + page.Activate(); + page.AcceptInput("DisableCollision"); + page.SetPropFloat(Prop_Send, "m_flModelScale", PAGE_MODELSCALE); + SetEntityTransmitState(page.index, FL_EDICT_ALWAYS); + + g_PageLocations.Push(EntIndexToEntRef(page.index)); + CBaseEntity glow = CBaseEntity(CreateGlowEntityDataless(page.index)); + glow.SetPropEnt(Prop_Data, "m_hOwnerEntity", page.index); + g_PageLocationsGlow.Push(EntIndexToEntRef(glow.index)); + count++; + } + + for (int i = 0; i < g_Pages.Length; i++) + { + SF2PageEntityData pageData; + g_Pages.GetArray(i, pageData, sizeof(pageData)); + + CBaseEntity page = CBaseEntity(EntRefToEntIndex(pageData.EntRef)); + if (!page.IsValid()) + { + page = CBaseEntity(CreateEntityByName("prop_dynamic_override")); + page.KeyValue("solid", "0"); + page.SetModel(PAGE_MODEL); + page.Teleport(pageData.Pos, pageData.Ang); + page.Spawn(); + page.Activate(); + page.AcceptInput("DisableCollision"); + page.SetPropFloat(Prop_Send, "m_flModelScale", PAGE_MODELSCALE); + SetEntityTransmitState(page.index, FL_EDICT_ALWAYS); + g_PageLocations.Push(EntIndexToEntRef(page.index)); + } + else + { + page = CBaseEntity(page.GetPropEnt(Prop_Send, "m_hOwnerEntity")); + SetEntityTransmitState(page.index, FL_EDICT_ALWAYS); + } + + CBaseEntity glow = CBaseEntity(CreateGlowEntityDataless(page.index, { 255, 0, 0, 255 })); + glow.SetPropEnt(Prop_Data, "m_hOwnerEntity", page.index); + g_PageLocationsGlow.Push(EntIndexToEntRef(glow.index)); + count++; + } + CPrintToChat(client, "{royalblue}%t {default}Revealed %i page locations.", "SF2 Prefix", count); + g_PagesRevealed = true; + } + else + { + for (int i = 0; i < g_PageLocations.Length; i++) + { + int entity = EntRefToEntIndex(g_PageLocations.Get(i)); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + RemoveEntity(entity); + } + + for (int i = 0; i < g_PageLocationsGlow.Length; i++) + { + int entity = EntRefToEntIndex(g_PageLocationsGlow.Get(i)); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + RemoveEntity(entity); + } + + for (int i = 0; i < g_Pages.Length; i++) + { + SF2PageEntityData pageData; + g_Pages.GetArray(i, pageData, sizeof(pageData)); + + CBaseEntity page = CBaseEntity(EntRefToEntIndex(pageData.EntRef)); + if (page.IsValid()) + { + page = CBaseEntity(page.GetPropEnt(Prop_Send, "m_hOwnerEntity")); + page.DispatchUpdateTransmitState(); + } + } + g_PageLocations.Clear(); + g_PageLocationsGlow.Clear(); + g_PagesRevealed = false; + } return Plugin_Handled; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 9a6d2617..d5e54ce2 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -9,6 +9,10 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue) + { + NPCRemoveAll(); + } return Plugin_Continue; } @@ -89,7 +93,8 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) InitializeNewGame(); } - PvP_OnRoundStart(); + Call_StartForward(g_OnRoundStartPFwd); + Call_Finish(); #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) { @@ -101,23 +106,6 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) delete profiler; #endif - //Nextbot doesn't trigger the triggers with npc flags, for map backward compatibility we are going to change the trigger filter and force a custom one. - /*int iEnt = -1; - while ((iEnt = FindEntityByClassname(iEnt, "trigger_*")) != -1) - { - if (IsValidEntity(iEnt)) - { - int flags = GetEntProp(iEnt, Prop_Data, "m_spawnflags"); - if ((flags & TRIGGER_NPCS) && !(flags & TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS)) - { - //Set the trigger to allow every entity, our custom filter will discard the unwanted entities. - SetEntProp(iEnt, Prop_Data, "m_spawnflags", flags|TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS); - SDKHook(iEnt, SDKHook_StartTouch, Hook_TriggerNPCTouch); - SDKHook(iEnt, SDKHook_Touch, Hook_TriggerNPCTouch); - SDKHook(iEnt, SDKHook_EndTouch, Hook_TriggerNPCTouch); - } - } - }*/ return Plugin_Continue; } @@ -131,7 +119,7 @@ Action Event_WinPanel(Event event, const char[] name, bool dontBroadcast) char cappers[7]; int i = 0; - for (int client; client <= MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && DidClientEscape(client) && i < 7) { @@ -146,10 +134,10 @@ Action Event_WinPanel(Event event, const char[] name, bool dontBroadcast) Action Event_Audio(Event event, const char[] name, bool dB) { - char strAudio[PLATFORM_MAX_PATH]; + char audio[PLATFORM_MAX_PATH]; - GetEventString(event, "sound", strAudio, sizeof(strAudio)); - if (strncmp(strAudio, "Game.Your", 9) == 0 || strcmp(strAudio, "Game.Stalemate") == 0) + GetEventString(event, "sound", audio, sizeof(audio)); + if (strncmp(audio, "Game.Your", 9) == 0 || strcmp(audio, "Game.Stalemate") == 0) { for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) { @@ -184,8 +172,6 @@ Action Event_RoundEnd(Handle event, const char[] name, bool dB) SF_FailEnd(); - if (SF_IsRenevantMap() && g_RenevantWaveTimer != null) KillTimer(g_RenevantWaveTimer); - for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -234,8 +220,6 @@ Action Event_RoundEnd(Handle event, const char[] name, bool dB) delete randomBosses; - SpecialRound_RoundEnd(); - SetRoundState(SF2RoundState_Outro); DistributeQueuePointsToPlayers(); @@ -243,6 +227,9 @@ Action Event_RoundEnd(Handle event, const char[] name, bool dB) g_RoundEndCount++; CheckRoundLimitForBossPackVote(g_RoundEndCount); + Call_StartForward(g_OnRoundEndPFwd); + Call_Finish(); + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) { @@ -292,8 +279,6 @@ Action Event_PlayerTeam(Handle event, const char[] name, bool dB) g_PlayerEliminated[client] = true; g_PlayerEscaped[client] = false; - ClientSetGhostModeState(client, false); - if (!GetEntProp(client, Prop_Send, "m_bIsCoaching")) { // This is to prevent player spawn spam when someone is coaching. Who coaches in SF2, anyway? @@ -321,7 +306,6 @@ Action Event_PlayerTeam(Handle event, const char[] name, bool dB) { if (!g_PlayerChoseTeam[client]) { - AFK_SetAFK(client); g_PlayerChoseTeam[client] = true; if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) @@ -336,118 +320,11 @@ Action Event_PlayerTeam(Handle event, const char[] name, bool dB) CreateTimer(5.0, Timer_WelcomeMessage, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !g_PlayerEliminated[client] && teamNum == TFTeam_Red && - !DidClientEscape(client)) - { - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - ShowVGUIPanel(client, "class_red"); - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - TFClassType newClass; - int random = GetRandomInt(1, 8); - switch (random) - { - case 1: - { - newClass = TFClass_Scout; - } - case 2: - { - newClass = TFClass_Soldier; - } - case 3: - { - newClass = TFClass_Pyro; - } - case 4: - { - newClass = TFClass_DemoMan; - } - case 5: - { - newClass = TFClass_Heavy; - } - case 6: - { - newClass = TFClass_Engineer; - } - case 7: - { - newClass = TFClass_Sniper; - } - case 8: - { - newClass = TFClass_Spy; - } - } - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - } - } - else - { - if (g_ClassBlockedOnThanatophobia[classToInt]) - { - ShowVGUIPanel(client, "class_red"); - switch (class) - { - case TFClass_Scout: - { - EmitSoundToClient(client, THANATOPHOBIA_SCOUTNO); - } - case TFClass_Soldier: - { - EmitSoundToClient(client, THANATOPHOBIA_SOLDIERNO); - } - case TFClass_Pyro: - { - EmitSoundToClient(client, THANATOPHOBIA_PYRONO); - } - case TFClass_DemoMan: - { - EmitSoundToClient(client, THANATOPHOBIA_DEMOMANNO); - } - case TFClass_Heavy: - { - EmitSoundToClient(client, THANATOPHOBIA_HEAVYNO); - } - case TFClass_Engineer: - { - EmitSoundToClient(client, THANATOPHOBIA_ENGINEERNO); - } - case TFClass_Medic: - { - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - } - case TFClass_Sniper: - { - EmitSoundToClient(client, THANATOPHOBIA_SNIPERNO); - } - case TFClass_Spy: - { - EmitSoundToClient(client, THANATOPHOBIA_SPYNO); - } - } - ArrayList classArrays = new ArrayList(); - for (int i = 1; i < MAX_CLASSES + 1; i++) - { - if (!g_ClassBlockedOnThanatophobia[i]) - { - classArrays.Push(view_as(i)); - } - } - TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - delete classArrays; - } - } - } } + Call_StartForward(g_OnPlayerTeamPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(teamNum); + Call_Finish(); } // Check groups. @@ -478,6 +355,12 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue && GetClientOfUserId(GetEventInt(event, "userid")) > 0) + { + Call_StartForward(g_OnPlayerSpawnPFwd); + Call_PushCell(SF2_BasePlayer(GetClientOfUserId(GetEventInt(event, "userid")))); + Call_Finish(); + } return Plugin_Continue; } @@ -498,53 +381,29 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } #endif + for (int i = 0; i < view_as(TFCond_PowerupModeDominant); i++) + { + g_ClientInCondition[client][i] = false; + } + if (GetClientTeam(client) > 1) { g_LastVisibilityProcess[client] = GetGameTime(); - ClientResetStatic(client); - if (!g_SeeUpdateMenu[client]) - { - g_SeeUpdateMenu[client] = true; - DisplayMenu(g_MenuUpdate, client, 30); - } } if (!IsClientParticipating(client)) { TF2Attrib_SetByName(client, "increased jump height", 1.0); TF2Attrib_RemoveByDefIndex(client, 10); - ClientSetGhostModeState(client, false); SetEntityGravity(client, 1.0); g_PlayerPageCount[client] = 0; - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); - ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - - ClientHandleGhostMode(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -569,12 +428,9 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } g_PlayerPostWeaponsTimer[client] = null; - g_PlayerIgniteTimer[client] = null; g_PlayerPageRewardCycleTimer[client] = null; g_PlayerFireworkTimer[client] = null; - g_PlayerBossKillSubject[client] = INVALID_ENT_REFERENCE; - g_PlayerGettingPageReward[client] = false; g_PlayerHitsToCrits[client] = 0; g_PlayerHitsToHeads[client] = 0; @@ -582,6 +438,10 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) g_PlayerTrapped[client] = false; g_PlayerTrapCount[client] = 0; + g_PlayerLatchedByTongue[client] = false; + g_PlayerLatchCount[client] = 0; + g_PlayerLatcher[client] = -1; + g_PlayerRandomClassNumber[client] = 1; if (IsPlayerAlive(client) && IsClientParticipating(client)) @@ -615,34 +475,12 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) { g_PlayerPageCount[client] = 0; - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); - ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - - ClientHandleGhostMode(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -664,6 +502,10 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) if (!g_PlayerEliminated[client]) { + if (IsFakeClient(client)) + { + TF2_SetPlayerClass(client, TFClass_Sniper); + } if ((SF_IsRaidMap() || SF_IsBoxingMap()) && !IsRoundPlaying()) { TF2Attrib_SetByDefIndex(client, 10, 7.0); @@ -679,16 +521,8 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) CreateTimer(0.1, Timer_StopAirDash, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) - { - TF2_StripContrackerOnly(client); - } - - ClientStartDrainingBlinkMeter(client); ClientSetScareBoostEndTime(client, -1.0); - ClientStartCampingTimer(client); - HandlePlayerIntroState(client); if (IsFakeClient(client)) @@ -706,140 +540,6 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) { CreateTimer(0.1, Timer_TeleportPlayerToEscapePoint, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - else - { - int red[4] = { 184, 56, 59, 255 }; - ClientEnableConstantGlow(client, red); - ClientActivateUltravision(client); - } - - if (SF_IsRenevantMap() && g_RenevantMarkForDeath && !DidClientEscape(client)) - { - TF2_AddCondition(client, TFCond_MarkedForDeathSilent, -1.0); - } - - if (SF_SpecialRound(SPECIALROUND_1UP) && !g_PlayerIn1UpCondition[client] && !g_PlayerDied1Up[client]) - { - g_PlayerDied1Up[client] = false; - g_PlayerIn1UpCondition[client] = true; - g_PlayerFullyDied1Up[client] = false; - } - - if (SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) - { - ClientSetSpecialRoundTimer(client, 0.0, Timer_ClientPageDetector, GetClientUserId(client)); - } - - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !DidClientEscape(client)) - { - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - ShowVGUIPanel(client, "class_red"); - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - TFClassType newClass; - int random = GetRandomInt(1, 8); - switch (random) - { - case 1: - { - newClass = TFClass_Scout; - } - case 2: - { - newClass = TFClass_Soldier; - } - case 3: - { - newClass = TFClass_Pyro; - } - case 4: - { - newClass = TFClass_DemoMan; - } - case 5: - { - newClass = TFClass_Heavy; - } - case 6: - { - newClass = TFClass_Engineer; - } - case 7: - { - newClass = TFClass_Sniper; - } - case 8: - { - newClass = TFClass_Spy; - } - } - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - } - } - else - { - if (g_ClassBlockedOnThanatophobia[classToInt]) - { - ShowVGUIPanel(client, "class_red"); - switch (class) - { - case TFClass_Scout: - { - EmitSoundToClient(client, THANATOPHOBIA_SCOUTNO); - } - case TFClass_Soldier: - { - EmitSoundToClient(client, THANATOPHOBIA_SOLDIERNO); - } - case TFClass_Pyro: - { - EmitSoundToClient(client, THANATOPHOBIA_PYRONO); - } - case TFClass_DemoMan: - { - EmitSoundToClient(client, THANATOPHOBIA_DEMOMANNO); - } - case TFClass_Heavy: - { - EmitSoundToClient(client, THANATOPHOBIA_HEAVYNO); - } - case TFClass_Engineer: - { - EmitSoundToClient(client, THANATOPHOBIA_ENGINEERNO); - } - case TFClass_Medic: - { - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - } - case TFClass_Sniper: - { - EmitSoundToClient(client, THANATOPHOBIA_SNIPERNO); - } - case TFClass_Spy: - { - EmitSoundToClient(client, THANATOPHOBIA_SPYNO); - } - } - ArrayList classArrays = new ArrayList(); - for (int i = 1; i < MAX_CLASSES + 1; i++) - { - if (!g_ClassBlockedOnThanatophobia[i]) - { - classArrays.Push(view_as(i)); - } - } - TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - delete classArrays; - } - } - } } else { @@ -855,7 +555,11 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } } - PvP_OnPlayerSpawn(client); + BossPreview_Remove(client); + + Call_StartForward(g_OnPlayerSpawnPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) @@ -881,119 +585,9 @@ void Event_PlayerClass(Event event, const char[] name, bool dontBroadcast) return; } - int teamNum = GetClientTeam(client); - - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !g_PlayerEliminated[client] && - teamNum == TFTeam_Red && TF2_GetPlayerClass(client) == TFClass_Medic && !DidClientEscape(client)) - { - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - ShowVGUIPanel(client, "class_red"); - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - TFClassType newClass; - int random = GetRandomInt(1, 8); - switch (random) - { - case 1: - { - newClass = TFClass_Scout; - } - case 2: - { - newClass = TFClass_Soldier; - } - case 3: - { - newClass = TFClass_Pyro; - } - case 4: - { - newClass = TFClass_DemoMan; - } - case 5: - { - newClass = TFClass_Heavy; - } - case 6: - { - newClass = TFClass_Engineer; - } - case 7: - { - newClass = TFClass_Sniper; - } - case 8: - { - newClass = TFClass_Spy; - } - } - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - } - } - else - { - if (g_ClassBlockedOnThanatophobia[classToInt]) - { - ShowVGUIPanel(client, "class_red"); - switch (class) - { - case TFClass_Scout: - { - EmitSoundToClient(client, THANATOPHOBIA_SCOUTNO); - } - case TFClass_Soldier: - { - EmitSoundToClient(client, THANATOPHOBIA_SOLDIERNO); - } - case TFClass_Pyro: - { - EmitSoundToClient(client, THANATOPHOBIA_PYRONO); - } - case TFClass_DemoMan: - { - EmitSoundToClient(client, THANATOPHOBIA_DEMOMANNO); - } - case TFClass_Heavy: - { - EmitSoundToClient(client, THANATOPHOBIA_HEAVYNO); - } - case TFClass_Engineer: - { - EmitSoundToClient(client, THANATOPHOBIA_ENGINEERNO); - } - case TFClass_Medic: - { - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - } - case TFClass_Sniper: - { - EmitSoundToClient(client, THANATOPHOBIA_SNIPERNO); - } - case TFClass_Spy: - { - EmitSoundToClient(client, THANATOPHOBIA_SPYNO); - } - } - ArrayList classArrays = new ArrayList(); - for (int i = 1; i < MAX_CLASSES + 1; i++) - { - if (!g_ClassBlockedOnThanatophobia[i]) - { - classArrays.Push(view_as(i)); - } - } - TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - delete classArrays; - } - } - } + Call_StartForward(g_OnPlayerClassPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } Action Event_PostInventoryApplication(Handle event, const char[] name, bool dB) @@ -1010,11 +604,11 @@ Action Event_PostInventoryApplication(Handle event, const char[] name, bool dB) } #endif - int client = GetClientOfUserId(GetEventInt(event, "userid")); - if (client > 0) + SF2_BasePlayer client = SF2_BasePlayer(GetClientOfUserId(GetEventInt(event, "userid"))); + if (client.IsValid) { - ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - g_PlayerPostWeaponsTimer[client] = CreateTimer(0.1, Timer_ClientPostWeapons, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + client.SwitchToWeaponSlot(TFWeaponSlot_Melee); + g_PlayerPostWeaponsTimer[client.index] = CreateTimer(0.1, Timer_ClientPostWeapons, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } #if defined DEBUG @@ -1048,6 +642,15 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue && GetClientOfUserId(event.GetInt("userid")) > 0) + { + Call_StartForward(g_OnPlayerDeathPrePFwd); + Call_PushCell(SF2_BasePlayer(GetClientOfUserId(event.GetInt("userid")))); + Call_PushCell(event.GetInt("attacker")); + Call_PushCell(event.GetInt("inflictor_entindex")); + Call_PushCell((event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0); + Call_Finish(); + } return Plugin_Continue; } @@ -1059,20 +662,31 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) #endif int client = GetClientOfUserId(event.GetInt("userid")); - if (g_PlayerProxy[client]) + for (int i = 0; i < view_as(TFCond_PowerupModeDominant); i++) { - TF2Attrib_RemoveByName(client, "crit mod disabled"); + g_ClientInCondition[client][i] = false; } int inflictor = event.GetInt("inflictor_entindex"); + int owner = inflictor; - // If this player was killed by a boss, play a sound. - int npcIndex = NPCGetFromEntIndex(inflictor); - if (npcIndex != -1 && !IsEntityAProjectile(inflictor)) + if (IsValidEntity(inflictor) && !SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) + { + owner = GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity"); + } + + Call_StartForward(g_OnPlayerDeathPrePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(event.GetInt("attacker")); + Call_PushCell(inflictor); + Call_PushCell((event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0); + Call_Finish(); + + int npcIndex = NPCGetFromEntIndex(owner); + if (npcIndex != -1) { int target = GetClientOfUserId(g_SourceTVUserID); - int attackIndex = NPCGetCurrentAttackIndex(npcIndex); - if (MaxClients < target || target < 1 || !IsClientInGame(target) || !IsClientSourceTV(target)) //If the server has a source TV bot uses to print boss' name in kill feed. + if (!IsValidClient(target) || !IsClientSourceTV(target)) // If the server has a source TV bot uses to print boss' name in kill feed. { target = GetClientForDeath(client); } @@ -1098,14 +712,16 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) event.SetString("assister_fallback", ""); if ((NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) || (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS)) { - if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) + if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLS && SF2_ChaserEntity(owner).IsValid()) { - char weaponType[PLATFORM_MAX_PATH]; - int weaponNum = NPCChaserGetAttackWeaponTypeInt(npcIndex, attackIndex); - GetChaserProfileAttackWeaponTypeString(profile, attackIndex, weaponType, sizeof(weaponType)); - event.SetString("weapon_logclassname", weaponType); - event.SetString("weapon", weaponType); - event.SetInt("customkill", weaponNum); + SF2_ChaserEntity chaser = SF2_ChaserEntity(owner); + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(npcIndex); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(chaser.GetAttackName(), attackData); + event.SetString("weapon_logclassname", attackData.WeaponString); + event.SetString("weapon", attackData.WeaponString); + event.SetInt("customkill", attackData.WeaponInt); } else if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS) { @@ -1126,64 +742,6 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) int userid = GetClientUserId(target); event.SetInt("attacker", userid); g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - if (IsValidClient(target)) - { - event.SetInt("ignore", target); - - //Show a different attacker to the user were taking - target = GetClientForDeath(client, target); - if (target != -1) - { - if (g_TimerChangeClientName[target] != null) - { - KillTimer(g_TimerChangeClientName[target]); - } - else - { - GetEntPropString(target, Prop_Data, "m_szNetname", g_OldClientName[target], sizeof(g_OldClientName[])); - } - - Format(bossName, sizeof(bossName), " %s", bossName); - - SetClientName(target, bossName); - SetEntPropString(target, Prop_Data, "m_szNetname", bossName); - - g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - char string[64]; - Event event2 = CreateEvent("player_death", true); - event2.SetInt("userid", event.GetInt("userid")); - event2.SetInt("victim_entindex", event.GetInt("victim_entindex")); - event2.SetInt("inflictor_entindex", event.GetInt("inflictor_entindex")); - event2.SetInt("attacker", GetClientUserId(target)); - event2.SetInt("weaponid", event.GetInt("weaponid")); - event2.SetInt("damagebits", event.GetInt("damagebits")); - event2.SetInt("customkill", event.GetInt("customkill")); - event2.SetInt("assister", event.GetInt("assister")); - event2.SetInt("stun_flags", event.GetInt("stun_flags")); - event2.SetInt("death_flags", event.GetInt("death_flags")); - event2.SetBool("silent_kill", event.GetBool("silent_kill")); - event2.SetInt("playerpenetratecount", event.GetInt("playerpenetratecount")); - event2.SetInt("kill_streak_total", event.GetInt("kill_streak_total")); - event2.SetInt("kill_streak_wep", event.GetInt("kill_streak_wep")); - event2.SetInt("kill_streak_assist", event.GetInt("kill_streak_assist")); - event2.SetInt("kill_streak_victim", event.GetInt("kill_streak_victim")); - event2.SetInt("ducks_streaked", event.GetInt("ducks_streaked")); - event2.SetInt("duck_streak_total", event.GetInt("duck_streak_total")); - event2.SetInt("duck_streak_assist", event.GetInt("duck_streak_assist")); - event2.SetInt("duck_streak_victim", event.GetInt("duck_streak_victim")); - event2.SetBool("rocket_jump", event.GetBool("rocket_jump")); - event2.SetInt("weapon_def_index", event.GetInt("weapon_def_index")); - event.GetString("weapon_logclassname", string, sizeof(string)); - event2.SetString("weapon_logclassname", string); - event.GetString("weapon", string, sizeof(string)); - event2.SetString("weapon", string); - event2.SetInt("send", userid); - - CreateTimer(0.2, Timer_SendDeathToSpecific, event2); - } - } } } @@ -1193,131 +751,6 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) SendDebugMessageToPlayers(DEBUG_KILLICONS, 0, "String kill icon is %s, integer kill icon is %i.", stringName, event.GetInt("customkill")); #endif - if (IsEntityAProjectile(inflictor)) - { - int npcIndex2 = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); - if (npcIndex2 != -1) - { - int target = GetClientOfUserId(g_SourceTVUserID); - if (MaxClients < target || target < 1 || !IsClientInGame(target) || !IsClientSourceTV(target)) //If the server has a source TV bot uses to print boss' name in kill feed. - { - target = GetClientForDeath(client); - } - - if (target != -1) - { - if (g_TimerChangeClientName[target] != null) - { - KillTimer(g_TimerChangeClientName[target]); - } - else //No timer running that means the SourceTV bot's current name is the correct one, we can safely update our last known SourceTV bot's name. - { - GetEntPropString(target, Prop_Data, "m_szNetname", g_OldClientName[target], sizeof(g_OldClientName[])); - } - - char bossName[SF2_MAX_NAME_LENGTH], profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(npcIndex2, profile, sizeof(profile)); - NPCGetBossName(npcIndex2, bossName, sizeof(bossName)); - - SetClientName(target, bossName); - SetEntPropString(target, Prop_Data, "m_szNetname", bossName); - - event.SetString("assister_fallback", ""); - - switch (ProjectileGetFlags(inflictor)) - { - case PROJ_ROCKET: - { - event.SetString("weapon_logclassname", "tf_projectile_rocket"); - event.SetString("weapon", "tf_projectile_rocket"); - } - case PROJ_MANGLER: - { - event.SetString("weapon_logclassname", "cow_mangler"); - event.SetString("weapon", "cow_mangler"); - } - case PROJ_GRENADE: - { - event.SetString("weapon_logclassname", "tf_projectile_pipe"); - event.SetString("weapon", "tf_projectile_pipe"); - } - case PROJ_FIREBALL, PROJ_ICEBALL, PROJ_FIREBALL_ATTACK, PROJ_ICEBALL_ATTACK: - { - event.SetString("weapon_logclassname", "spellbook_fireball"); - event.SetString("weapon", "spellbook_fireball"); - } - case PROJ_SENTRYROCKET: - { - event.SetString("weapon_logclassname", "obj_sentrygun3"); - event.SetString("weapon", "obj_sentrygun3"); - } - } - - int userid = GetClientUserId(target); - event.SetInt("attacker", userid); - g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - if (IsValidClient(target)) - { - event.SetInt("ignore", target); - - //Show a different attacker to the user were taking - target = GetClientForDeath(client, target); - if (target != -1) - { - if (g_TimerChangeClientName[target] != null) - { - KillTimer(g_TimerChangeClientName[target]); - } - else - { - GetEntPropString(target, Prop_Data, "m_szNetname", g_OldClientName[target], sizeof(g_OldClientName[])); - } - - Format(bossName, sizeof(bossName), " %s", bossName); - - SetClientName(target, bossName); - SetEntPropString(target, Prop_Data, "m_szNetname", bossName); - - g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - char string[64]; - Event event2 = CreateEvent("player_death", true); - event2.SetInt("userid", event.GetInt("userid")); - event2.SetInt("victim_entindex", event.GetInt("victim_entindex")); - event2.SetInt("inflictor_entindex", event.GetInt("inflictor_entindex")); - event2.SetInt("attacker", GetClientUserId(target)); - event2.SetInt("weaponid", event.GetInt("weaponid")); - event2.SetInt("damagebits", event.GetInt("damagebits")); - event2.SetInt("customkill", event.GetInt("customkill")); - event2.SetInt("assister", event.GetInt("assister")); - event2.SetInt("stun_flags", event.GetInt("stun_flags")); - event2.SetInt("death_flags", event.GetInt("death_flags")); - event2.SetBool("silent_kill", event.GetBool("silent_kill")); - event2.SetInt("playerpenetratecount", event.GetInt("playerpenetratecount")); - event2.SetInt("kill_streak_total", event.GetInt("kill_streak_total")); - event2.SetInt("kill_streak_wep", event.GetInt("kill_streak_wep")); - event2.SetInt("kill_streak_assist", event.GetInt("kill_streak_assist")); - event2.SetInt("kill_streak_victim", event.GetInt("kill_streak_victim")); - event2.SetInt("ducks_streaked", event.GetInt("ducks_streaked")); - event2.SetInt("duck_streak_total", event.GetInt("duck_streak_total")); - event2.SetInt("duck_streak_assist", event.GetInt("duck_streak_assist")); - event2.SetInt("duck_streak_victim", event.GetInt("duck_streak_victim")); - event2.SetBool("rocket_jump", event.GetBool("rocket_jump")); - event2.SetInt("weapon_def_index", event.GetInt("weapon_def_index")); - event.GetString("weapon_logclassname", string, sizeof(string)); - event2.SetString("weapon_logclassname", string); - event.GetString("weapon", string, sizeof(string)); - event2.SetString("weapon", string); - event2.SetInt("send", userid); - - CreateTimer(0.2, Timer_SendDeathToSpecific, event2); - } - } - } - } - } - if (!IsRoundInWarmup()) { if (client > 0) @@ -1329,53 +762,50 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) } } } - if (MAX_BOSSES > npcIndex >= 0 && (g_SlenderHasAshKillEffect[npcIndex] || g_SlenderHasCloakKillEffect[npcIndex] - || g_SlenderHasDecapKillEffect[npcIndex] || g_SlenderHasDeleteKillEffect[npcIndex] - || g_SlenderHasDissolveRagdollOnKill[npcIndex] - || g_SlenderHasElectrocuteKillEffect[npcIndex] || g_SlenderHasGoldKillEffect[npcIndex] - || g_SlenderHasIceKillEffect[npcIndex] || g_SlenderHasPlasmaRagdollOnKill[npcIndex] - || g_SlenderHasPushRagdollOnKill[npcIndex] || g_SlenderHasResizeRagdollOnKill[npcIndex] - || g_SlenderHasBurnKillEffect[npcIndex] || g_SlenderHasGibKillEffect[npcIndex])) - { - CreateTimer(0.01, Timer_ModifyRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - if (MAX_BOSSES > npcIndex >= 0 && g_SlenderPlayerCustomDeathFlag[npcIndex]) - { - event.SetInt("death_flags", g_SlenderPlayerSetDeathFlag[npcIndex]); - } - if (MAX_BOSSES > npcIndex >= 0 && g_SlenderHasDecapOrGibKillEffect[npcIndex]) + + bool modify = false; + + if (!modify && SF2_ProjectileIceball(inflictor).IsValid()) { - CreateTimer(0.01, Timer_DeGibRagdoll, GetClientUserId(client)); + modify = true; + CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (MAX_BOSSES > npcIndex >= 0 && g_SlenderHasMultiKillEffect[npcIndex]) + if (!modify && SF2_ProjectileCowMangler(inflictor).IsValid()) { - CreateTimer(0.01, Timer_MultiRagdoll, GetClientUserId(client)); + modify = true; + CreateTimer(0.01, Timer_ManglerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (IsEntityAProjectile(inflictor)) + + if (npcIndex != -1) { - switch (ProjectileGetFlags(inflictor)) + SF2BossProfileData data; + data = NPCGetProfileData(npcIndex); + g_PlayerBossKillSubject[client] = npcIndex; + + if (!modify && (data.AshRagdoll || data.CloakRagdoll || data.DecapRagdoll || data.DeleteRagdoll || data.DissolveRagdoll || + data.ElectrocuteRagdoll || data.GoldRagdoll || data.IceRagdoll || data.PlasmaRagdoll || data.PushRagdoll || + data.ResizeRagdoll || data.BurnRagdoll || data.GibRagdoll)) { - case PROJ_MANGLER: - { - CreateTimer(0.01, Timer_ManglerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - case PROJ_ICEBALL: - { - CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - case PROJ_ICEBALL_ATTACK: - { - CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } + modify = true; + CreateTimer(0.01, Timer_ModifyRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - } - if (MAX_BOSSES > npcIndex >= 0) - { - float value = NPCGetAttributeValue(npcIndex, SF2Attribute_IgnitePlayerOnDeath); - if (MAX_BOSSES > npcIndex >= 0 && value > 0.0) + + if (data.CustomDeathFlag) + { + event.SetInt("death_flags", data.CustomDeathFlagType); + } + + if (!modify && data.DecapOrGibRagdoll) + { + modify = true; + CreateTimer(0.01, Timer_DeGibRagdoll, GetClientUserId(client)); + } + + if (!modify && data.MultiEffectRagdoll) { - TF2_IgnitePlayer(client, client); + modify = true; + CreateTimer(0.01, Timer_MultiRagdoll, GetClientUserId(client)); } } @@ -1385,6 +815,7 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) DebugMessage("EVENT END: Event_PlayerDeathPre"); } #endif + event.BroadcastDisabled = true; return Plugin_Changed; } @@ -1449,6 +880,15 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue && GetClientOfUserId(event.GetInt("userid")) > 0) + { + Call_StartForward(g_OnPlayerDeathPFwd); + Call_PushCell(SF2_BasePlayer(GetClientOfUserId(event.GetInt("userid")))); + Call_PushCell(event.GetInt("attacker")); + Call_PushCell(event.GetInt("inflictor_entindex")); + Call_PushCell((event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0); + Call_Finish(); + } return Plugin_Continue; } @@ -1458,6 +898,8 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) return Plugin_Continue; } + int attacker = event.GetInt("attacker"); + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) { @@ -1465,7 +907,7 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } #endif - bool fake = !!(event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER); + bool fake = (event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0; int inflictor = event.GetInt("inflictor_entindex"); #if defined DEBUG @@ -1477,29 +919,10 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) if (!fake) { - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); - ClientResetInteractiveGlow(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); - ClientResetDeathCam(client); - - ClientUpdateMusicSystem(client); - - ClientDisableConstantGlow(client); + BossPreview_Remove(client); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { @@ -1516,20 +939,8 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { g_NpcChaseOnLookTarget[npcIndex].Erase(foundClient); } - switch (NPCGetType(npcIndex)) - { - case SF2BossType_Chaser: - { - if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderGiveUp[npcIndex] = true; - } - } - } } - PvP_SetPlayerPvPState(client, false, false, false); - if (IsRoundInWarmup()) { CreateTimer(0.3, Timer_RespawnPlayer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); @@ -1538,14 +949,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { if (!g_PlayerEliminated[client]) { - if (IsFakeClient(client)) - { - TF2_SetPlayerClass(client, TFClass_Sniper); - } - if (SF_SpecialRound(SPECIALROUND_MULTIEFFECT) || g_RenevantMultiEffect) - { - CreateTimer(0.1, Timer_ReplacePlayerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } if (IsRoundInIntro() || (!IsRoundPlaying() && !IsRoundEnding()) || DidClientEscape(client) || (SF_SpecialRound(SPECIALROUND_1UP) && g_PlayerIn1UpCondition[client] && !g_PlayerDied1Up[client])) { CreateTimer(0.3, Timer_RespawnPlayer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); @@ -1557,166 +960,13 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) g_PlayerFullyDied1Up[client] = true; g_PlayerSwitchBlueTimer[client] = CreateTimer(0.5, Timer_PlayerSwitchToBlue, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (g_PlayerPreferences[client].PlayerPreference_GhostModeToggleState == 2) - { - CreateTimer(0.25, Timer_ToggleGhostModeCommand, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !DidClientEscape(client)) - { - for (int reds = 1; reds <= MaxClients; reds++) - { - if (!IsValidClient(reds) || - g_PlayerEliminated[reds] || - DidClientEscape(reds) || - GetClientTeam(reds) != TFTeam_Red || - !IsPlayerAlive(reds)) - { - continue; - } - int randomNegative = GetRandomInt(1, 5); - switch (randomNegative) - { - case 1: - { - TF2_MakeBleed(reds, reds, 4.0); - EmitSoundToClient(reds, BLEED_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(reds, TFCond_Jarated, 5.0); - EmitSoundToClient(reds, JARATE_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(reds, TFCond_Gas, 5.0); - EmitSoundToClient(reds, GAS_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 4: - { - int maxHealth = SDKCall(g_SDKGetMaxHealth, reds); - float damageToTake = float(maxHealth) / 10.0; - SDKHooks_TakeDamage(reds, reds, reds, damageToTake, 128, _, { 0.0, 0.0, 0.0 } ); - } - case 5: - { - TF2_AddCondition(reds, TFCond_MarkedForDeath, 5.0); - } - } - } - } } else { } - // If this player was killed by a boss, play a sound, or print a message. - int npcIndex = NPCGetFromEntIndex(inflictor); - if (npcIndex != -1) - { - int slender = NPCGetEntIndex(npcIndex); - if (slender && slender != INVALID_ENT_REFERENCE) - { - g_PlayerBossKillSubject[client] = EntIndexToEntRef(slender); - } - - char npcProfile[SF2_MAX_PROFILE_NAME_LENGTH], buffer[PLATFORM_MAX_PATH], bossName[SF2_MAX_NAME_LENGTH]; - NPCGetProfile(npcIndex, npcProfile, sizeof(npcProfile)); - NPCGetBossName(npcIndex, bossName, sizeof(bossName)); - - #if defined _store_included - int difficulty = GetLocalGlobalDifficulty(npcIndex); - if (NPCGetDrainCreditState(npcIndex)) - { - Store_SetClientCredits(client, Store_GetClientCredits(client) - NPCGetDrainCreditAmount(npcIndex, difficulty)); - CPrintToChat(client, "{valve}%s{default} has stolen {green}%i credits{default} from you.", bossName, NPCGetDrainCreditAmount(npcIndex, difficulty)); - } - #endif - - ArrayList soundList; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileAttackKilledClientSounds(npcProfile, soundInfo); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0' && g_PlayerEliminated[client]) - { - EmitSoundToClient(client, buffer, _, SNDCHAN_STATIC, SNDLEVEL_HELICOPTER); - } - } - - GetChaserProfileAttackKilledAllSounds(npcProfile, soundInfo); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0' && g_PlayerEliminated[client]) - { - EmitSoundToAll(buffer, _, SNDCHAN_STATIC, SNDLEVEL_HELICOPTER); - } - } - soundList = null; - - SlenderPrintChatMessage(npcIndex, client); - - SlenderPerformVoice(npcIndex, _, SF2BossSound_AttackKilled); - } - - if (IsEntityAProjectile(inflictor)) - { - int npcIndex2 = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); - if (npcIndex2 != -1) - { - int slender = NPCGetEntIndex(npcIndex2); - if (slender && slender != INVALID_ENT_REFERENCE) - { - g_PlayerBossKillSubject[client] = EntIndexToEntRef(slender); - } - - char npcProfile[SF2_MAX_PROFILE_NAME_LENGTH], bossName[SF2_MAX_NAME_LENGTH]; - NPCGetProfile(npcIndex2, npcProfile, sizeof(npcProfile)); - NPCGetBossName(npcIndex2, bossName, sizeof(bossName)); - - #if defined _store_included - int difficulty = GetLocalGlobalDifficulty(npcIndex2); - if (NPCGetDrainCreditState(npcIndex2)) - { - Store_SetClientCredits(client, Store_GetClientCredits(client) - NPCGetDrainCreditAmount(npcIndex2, difficulty)); - CPrintToChat(client, "{valve}%s{default} has stolen {green}%i credits{default} from you.", bossName, NPCGetDrainCreditAmount(npcIndex2, difficulty)); - } - #endif - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileAttackKilledClientSounds(npcProfile, soundInfo); - soundInfo.EmitSound(true, client); - - GetChaserProfileAttackKilledAllSounds(npcProfile, soundInfo); - soundInfo.EmitSound(); - - SlenderPrintChatMessage(npcIndex2, client); - - SlenderPerformVoice(npcIndex2, _, SF2BossSound_AttackKilled); - } - } - CreateTimer(0.2, Timer_CheckRoundWinConditions, _, TIMER_FLAG_NO_MAPCHANGE); - // Notify to other bosses that this player has died. - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (EntRefToEntIndex(g_SlenderTarget[i]) == client) - { - g_SlenderInterruptConditions[i] |= COND_CHASETARGETINVALIDATED; - GetClientAbsOrigin(client, g_SlenderChaseDeathPosition[i]); - g_BossPathFollower[i].Invalidate(); - } - } - if (g_IgnoreRedPlayerDeathSwapConVar.BoolValue && GetClientTeam(client) == TFTeam_Red) { g_PlayerEliminated[client] = false; @@ -1724,42 +974,25 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } } - if (g_PlayerProxy[client]) - { - // We're a proxy, so play some sounds. - - int proxyMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); - if (proxyMaster != -1) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(proxyMaster, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetBossProfileProxyDeathSounds(profile, soundInfo); - soundInfo.EmitSound(_, client); - } - } - - ClientResetProxy(client, true); ClientUpdateListeningFlags(client); // Half-Zatoichi nerf code. int katanaHealthGain = 5; if (katanaHealthGain >= 0) { - int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (attacker > 0) + SF2_BasePlayer attackerClient = SF2_BasePlayer(GetClientOfUserId(attacker)); + if (attackerClient.IsValid) { - if (!IsClientInPvP(attacker) && (!g_PlayerEliminated[attacker] || g_PlayerProxy[attacker])) + if (!attackerClient.IsInPvP && (!attackerClient.IsEliminated || attackerClient.IsProxy)) { char weaponClass[64]; event.GetString("weapon", weaponClass, sizeof(weaponClass)); if (strcmp(weaponClass, "demokatana") == 0) { - int attackerPreHealth = GetEntProp(attacker, Prop_Send, "m_iHealth"); + int attackerPreHealth = attackerClient.GetProp(Prop_Send, "m_iHealth"); Handle pack = CreateDataPack(); - WritePackCell(pack, GetClientUserId(attacker)); + WritePackCell(pack, attackerClient.UserID); WritePackCell(pack, attackerPreHealth + katanaHealthGain); CreateTimer(0.0, Timer_SetPlayerHealth, pack, TIMER_FLAG_NO_MAPCHANGE); @@ -1769,7 +1002,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } g_PlayerPostWeaponsTimer[client] = null; - g_PlayerIgniteTimer[client] = null; g_PlayerPageRewardCycleTimer[client] = null; g_PlayerFireworkTimer[client] = null; @@ -1780,12 +1012,16 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) g_PlayerTrapped[client] = false; g_PlayerTrapCount[client] = 0; + g_PlayerLatchedByTongue[client] = false; + g_PlayerLatchCount[client] = 0; + g_PlayerLatcher[client] = -1; + g_PlayerRandomClassNumber[client] = 1; } if (!IsRoundEnding() && !g_RoundWaitingForPlayers) { - int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (IsRoundPlaying() && client != attacker) + SF2_BasePlayer attackerClient = SF2_BasePlayer(GetClientOfUserId(attacker)); + if (IsRoundPlaying() && client != attackerClient.index) { //Copy the data char string[64]; @@ -1819,7 +1055,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) event.GetString("weapon", string, sizeof(string)); event2.SetString("weapon", string); - event2.SetInt("ignore", event.GetInt("ignore")); CreateTimer(0.2, Timer_SendDeath, event2, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); } } @@ -1827,7 +1062,13 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { CreateTimer(0.2, Timer_CheckAlivePlayers, _, TIMER_FLAG_NO_MAPCHANGE); } - PvP_OnPlayerDeath(client, fake); + + Call_StartForward(g_OnPlayerDeathPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(attacker); + Call_PushCell(inflictor); + Call_PushCell(fake); + Call_Finish(); #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) @@ -1839,3 +1080,45 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) return Plugin_Continue; } + +static bool g_MuteHealOnHit = false; + +Action Event_PlayerHealed(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + int patient = GetClientOfUserId(event.GetInt("patient")); + int healer = GetClientOfUserId(event.GetInt("healer")); + if (!IsValidClient(patient) || !IsValidClient(healer)) + { + return Plugin_Continue; + } + + if ((IsClientInPvP(patient) && IsClientInPvP(healer)) || IsRoundInWarmup()) // Both in PvP + { + event.BroadcastDisabled = true; + g_MuteHealOnHit = true; + return Plugin_Handled; + } + + return Plugin_Continue; +} + +Action Event_HealOnHit(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (g_MuteHealOnHit) + { + event.BroadcastDisabled = true; + g_MuteHealOnHit = false; + return Plugin_Handled; + } + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 7f2671c3..78c61389 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -9,32 +9,30 @@ // GENERAL PLUGIN HOOK FUNCTIONS // ========================================================== -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error,int err_max) +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { RegPluginLibrary("sf2"); - g_OnBossAddedFwd = new GlobalForward("SF2_OnBossAdded", ET_Ignore, Param_Cell); - g_OnBossSpawnFwd = new GlobalForward("SF2_OnBossSpawn", ET_Ignore, Param_Cell); - g_OnBossDespawnFwd = new GlobalForward("SF2_OnBossDespawn", ET_Ignore, Param_Cell); g_OnBossChangeStateFwd = new GlobalForward("SF2_OnBossChangeState", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); - g_OnBossAnimationUpdateFwd = new GlobalForward("SF2_OnBossAnimationUpdate", ET_Hook, Param_Cell); g_OnBossGetSpeedFwd = new GlobalForward("SF2_OnBossGetSpeed", ET_Hook, Param_Cell, Param_FloatByRef); g_OnBossGetWalkSpeedFwd = new GlobalForward("SF2_OnBossGetWalkSpeed", ET_Hook, Param_Cell, Param_FloatByRef); g_OnBossHearEntityFwd = new GlobalForward("SF2_OnBossHearEntity", ET_Hook, Param_Cell, Param_Cell, Param_Cell); g_OnBossSeeEntityFwd = new GlobalForward("SF2_OnBossSeeEntity", ET_Hook, Param_Cell, Param_Cell); - g_OnBossRemovedFwd = new GlobalForward("SF2_OnBossRemoved", ET_Ignore, Param_Cell); g_OnBossStunnedFwd = new GlobalForward("SF2_OnBossStunned", ET_Ignore, Param_Cell, Param_Cell); + g_OnBossKilledFwd = new GlobalForward("SF2_OnBossKilled", ET_Ignore, Param_Cell, Param_Cell); g_OnBossCloakedFwd = new GlobalForward("SF2_OnBossCloaked", ET_Ignore, Param_Cell); g_OnBossDecloakedFwd = new GlobalForward("SF2_OnBossDecloaked", ET_Ignore, Param_Cell); - g_OnBossFinishSpawningFwd = new GlobalForward("SF2_OnBossFinishSpawning", ET_Ignore, Param_Cell); g_OnBossPreAttackFwd = new GlobalForward("SF2_OnBossPreAttack", ET_Ignore, Param_Cell, Param_Cell); g_OnBossAttackedFwd = new GlobalForward("SF2_OnBossAttacked", ET_Ignore, Param_Cell, Param_Cell); + g_OnBossPreTakeDamageFwd = new GlobalForward("SF2_OnBossPreTakeDamage", ET_Hook, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + g_OnBossPreFlashlightDamageFwd = new GlobalForward("SF2_OnBossPreFlashlightDamage", ET_Hook, Param_Cell, Param_Cell); + g_OnBossAnimationUpdateFwd = new GlobalForward("SF2_OnBossAnimationUpdate", ET_Hook, Param_Cell, Param_String); + g_OnChaserBossGetSuspendActionFwd = new GlobalForward("SF2_OnChaserBossGetSuspendAction", ET_Hook, Param_Cell, Param_CellByRef); g_OnPagesSpawnedFwd = new GlobalForward("SF2_OnPagesSpawned", ET_Ignore); g_OnRoundStateChangeFwd = new GlobalForward("SF2_OnRoundStateChange", ET_Ignore, Param_Cell, Param_Cell); g_OnClientCollectPageFwd = new GlobalForward("SF2_OnClientCollectPage", ET_Ignore, Param_Cell, Param_Cell); g_OnClientBlinkFwd = new GlobalForward("SF2_OnClientBlink", ET_Ignore, Param_Cell); g_OnClientScareFwd = new GlobalForward("SF2_OnClientScare", ET_Ignore, Param_Cell, Param_Cell); - g_OnClientCaughtByBossFwd = new GlobalForward("SF2_OnClientCaughtByBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnClientGiveQueuePointsFwd = new GlobalForward("SF2_OnClientGiveQueuePoints", ET_Hook, Param_Cell, Param_CellByRef); g_OnClientActivateFlashlightFwd = new GlobalForward("SF2_OnClientActivateFlashlight", ET_Ignore, Param_Cell); g_OnClientDeactivateFlashlightFwd = new GlobalForward("SF2_OnClientDeactivateFlashlight", ET_Ignore, Param_Cell); @@ -45,32 +43,46 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error,int err_max) g_OnClientLooksAtBossFwd = new GlobalForward("SF2_OnClientLooksAtBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnClientLooksAwayFromBossFwd = new GlobalForward("SF2_OnClientLooksAwayFromBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnClientStartDeathCamFwd = new GlobalForward("SF2_OnClientStartDeathCam", ET_Ignore, Param_Cell, Param_Cell); - g_OnClientEndDeathCamFwd = new GlobalForward("SF2_OnClientEndDeathCam", ET_Ignore, Param_Cell, Param_Cell); + g_OnClientPreKillDeathCamFwd = new GlobalForward("SF2_OnClientPreKillDeathCam", ET_Ignore, Param_Cell, Param_Cell); + g_OnClientEndDeathCamFwd = new GlobalForward("SF2_OnClientEndDeathCam", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); g_OnClientGetDefaultWalkSpeedFwd = new GlobalForward("SF2_OnClientGetDefaultWalkSpeed", ET_Hook, Param_Cell, Param_CellByRef); g_OnClientGetDefaultSprintSpeedFwd = new GlobalForward("SF2_OnClientGetDefaultSprintSpeed", ET_Hook, Param_Cell, Param_CellByRef); g_OnClientTakeDamageFwd = new GlobalForward("SF2_OnClientTakeDamage", ET_Hook, Param_Cell, Param_CellByRef, Param_CellByRef, Param_FloatByRef); g_OnClientSpawnedAsProxyFwd = new GlobalForward("SF2_OnClientSpawnedAsProxy", ET_Ignore, Param_Cell); g_OnClientDamagedByBossFwd = new GlobalForward("SF2_OnClientDamagedByBoss", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Cell); g_OnGroupGiveQueuePointsFwd = new GlobalForward("SF2_OnGroupGiveQueuePoints", ET_Hook, Param_Cell, Param_CellByRef); - g_OnRenevantTriggerWaveFwd = new GlobalForward("SF2_OnRenevantWaveTrigger", ET_Ignore, Param_Cell); g_OnBossPackVoteStartFwd = new GlobalForward("SF2_OnBossPackVoteStart", ET_Ignore); g_OnDifficultyChangeFwd = new GlobalForward("SF2_OnDifficultyChanged", ET_Ignore, Param_Cell, Param_Cell); g_OnClientEnterGameFwd = new GlobalForward("SF2_OnClientEnterGame", ET_Hook, Param_Cell); g_OnGroupEnterGameFwd = new GlobalForward("SF2_OnGroupEnterGame", ET_Hook, Param_Cell); g_OnEverythingLoadedFwd = new GlobalForward("SF2_OnEverythingLoaded", ET_Ignore); + g_OnDifficultyVoteFinishedFwd = new GlobalForward("SF2_OnDifficultyVoteFinished", ET_Ignore, Param_Cell, Param_Cell); + g_OnIsBossCustomAttackPossibleFwd = new GlobalForward("SF2_OnIsBossCustomAttackPossible", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell); + g_OnBossGetCustomAttackActionFwd = new GlobalForward("SF2_OnBossGetCustomAttackAction", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell, Param_CellByRef); + g_OnProjectileTouchFwd = new GlobalForward("SF2_OnProjectileTouch", ET_Ignore, Param_Cell, Param_Cell); + g_OnPreSelectStartingBosses = new GlobalForward("SF2_OnSelectStartingBosses", ET_Ignore); + g_OnPostSelectStartingBosses = new GlobalForward("SF2_OnFinishStartingBosses", ET_Ignore, Param_String); CreateNative("SF2_GetConfig", Native_GetConfig); CreateNative("SF2_IsRunning", Native_IsRunning); CreateNative("SF2_GetRoundState", Native_GetRoundState); CreateNative("SF2_IsRoundInGracePeriod", Native_IsRoundInGracePeriod); CreateNative("SF2_GetCurrentDifficulty", Native_GetCurrentDifficulty); + CreateNative("SF2_GetBossDifficulty", Native_GetBossDifficulty); CreateNative("SF2_GetDifficultyModifier", Native_GetDifficultyModifier); CreateNative("SF2_IsInSpecialRound", Native_IsInSpecialRound); + CreateNative("SF2_GetCurrentBossPack", Native_GetCurrentBossPack); CreateNative("SF2_GetClientGroup", Native_GetClientGroup); CreateNative("SF2_GetClientQueuePoints", Native_GetClientQueuePoints); CreateNative("SF2_SetClientQueuePoints", Native_SetClientQueuePoints); + CreateNative("SF2_IsValidClient", Native_IsValidClient); + CreateNative("SF2_IsClientCritBoosted", Native_IsClientCritBoosted); + CreateNative("SF2_IsClientMiniCritBoosted", Native_IsClientMiniCritBoosted); + CreateNative("SF2_IsClientUbercharged", Native_IsClientUbercharged); + CreateNative("SF2_IsClientInKart", Native_IsClientInKart); + CreateNative("SF2_IsClientInCondition", Native_IsClientInCondition); CreateNative("SF2_IsClientEliminated", Native_IsClientEliminated); CreateNative("SF2_IsClientInGhostMode", Native_IsClientInGhostMode); CreateNative("SF2_IsClientProxy", Native_IsClientProxy); @@ -95,12 +107,6 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error,int err_max) CreateNative("SF2_SetClientFlashlightBatteryLife", Native_SetClientFlashlightBatteryLife); CreateNative("SF2_IsClientUsingFlashlight", Native_IsClientUsingFlashlight); - CreateNative("SF2_GetClientSprintPoints", Native_GetClientSprintPoints); - CreateNative("SF2_SetClientSprintPoints", Native_SetClientSprintPoints); - CreateNative("SF2_IsClientSprinting", Native_IsClientSprinting); - CreateNative("SF2_IsClientReallySprinting", Native_IsClientReallySprinting); - CreateNative("SF2_SetClientSprintState", Native_SetClientSprintState); - CreateNative("SF2_IsClientTrapped", Native_IsClientTrapped); CreateNative("SF2_IsClientInDeathCam", Native_IsClientInDeathCam); CreateNative("SF2_ClientStartDeathCam", Native_ClientStartDeathCam); @@ -109,105 +115,48 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error,int err_max) CreateNative("SF2_ClientForceProxy", Native_ClientForceProxy); CreateNative("SF2_CollectAsPage", Native_CollectAsPage); - - CreateNative("SF2_GetMaxBossCount", Native_GetMaxBosses); - CreateNative("SF2_EntIndexToBossIndex", Native_EntIndexToBossIndex); - CreateNative("SF2_BossIndexToEntIndex", Native_BossIndexToEntIndex); - CreateNative("SF2_BossIndexToEntIndexEx", Native_BossIndexToEntIndexEx); - CreateNative("SF2_BossIDToBossIndex", Native_BossIDToBossIndex); - CreateNative("SF2_BossIndexToBossID", Native_BossIndexToBossID); - - CreateNative("SF2_UpdateBossAnimation", Native_UpdateBossAnimation); + CreateNative("SF2_GetEmptyPageSpawnPoints", Native_GetEmptyPageSpawnPoints); CreateNative("SF2_ForceBossJump", Native_ForceBossJump); - CreateNative("SF2_AddBoss", Native_AddBoss); - CreateNative("SF2_RemoveBoss", Native_RemoveBoss); - - CreateNative("SF2_GetBossName", Native_GetBossName); - CreateNative("SF2_GetBossType", Native_GetBossType); - - CreateNative("SF2_GetBossFlags", Native_GetBossFlags); - CreateNative("SF2_SetBossFlags", Native_SetBossFlags); - - CreateNative("SF2_SpawnBoss", Native_SpawnBoss); - CreateNative("SF2_IsBossSpawning", Native_IsBossSpawning); - CreateNative("SF2_DespawnBoss", Native_DespawnBoss); - - CreateNative("SF2_PerformBossVoice", Native_PerformBossVoice); - CreateNative("SF2_GetBossModelEntity", Native_GetBossModelEntity); CreateNative("SF2_GetBossTarget", Native_GetBossTarget); CreateNative("SF2_SetBossTarget", Native_SetBossTarget); - CreateNative("SF2_GetBossPathFollower", Native_GetBossPathFollower); - CreateNative("SF2_GetBossMaster", Native_GetBossMaster); - CreateNative("SF2_GetBossIdleLifetime", Native_GetBossIdleLifetime); - CreateNative("SF2_GetBossState", Native_GetBossState); - CreateNative("SF2_SetBossState", Native_SetBossState); - - CreateNative("SF2_GetBossEyePosition", Native_GetBossEyePosition); - CreateNative("SF2_GetBossEyePositionOffset", Native_GetBossEyePositionOffset); - CreateNative("SF2_GetBossFOV", Native_GetBossFOV); - - CreateNative("SF2_GetBossTimeUntilAlert", Native_GetBossTimeUntilAlert); - CreateNative("SF2_GetBossTimeUntilNoPersistence", Native_GetBossTimeUntilNoPersistence); - CreateNative("SF2_GetBossTimeUntilIdle", Native_GetBossTimeUntilIdle); - CreateNative("SF2_SetBossTimeUntilAlert", Native_SetBossTimeUntilAlert); - CreateNative("SF2_SetBossTimeUntilNoPersistence", Native_SetBossTimeUntilNoPersistence); - CreateNative("SF2_SetBossTimeUntilIdle", Native_SetBossTimeUntilIdle); - CreateNative("SF2_IsBossStunnable", Native_IsBossStunnable); CreateNative("SF2_IsBossStunnableByFlashlight", Native_IsBossStunnableByFlashlight); CreateNative("SF2_IsBossCloaked", Native_IsBossCloaked); CreateNative("SF2_GetBossStunHealth", Native_GetBossStunHealth); CreateNative("SF2_SetBossStunHealth", Native_SetBossStunHealth); - CreateNative("SF2_GetBossNextStunTime", Native_GetBossNextStunTime); - CreateNative("SF2_SetBossNextStunTime", Native_SetBossNextStunTime); - CreateNative("SF2_ForceBossGiveUp", Native_ForceBossGiveUp); - CreateNative("SF2_GetBossGoalPosition", Native_GetBossGoalPosition); - CreateNative("SF2_CanBossHearClient", Native_CanBossHearClient); - CreateNative("SF2_CreateBossSoundHint", Native_CreateBossSoundHint); - - CreateNative("SF2_GetBossProjectileType", Native_GetBossProjectileType); - - CreateNative("SF2_GetBossCurrentAttackIndex", Native_GetBossCurrentAttackIndex); - CreateNative("SF2_SetBossCurrentAttackIndex", Native_SetBossCurrentAttackIndex); - CreateNative("SF2_GetBossMaxAttackIndexes", Native_GetBossMaxAttackIndexes); - CreateNative("SF2_GetBossAttackRunDurationTime", Native_GetBossAttackRunDurationTime); - CreateNative("SF2_SetBossAttackRunDurationTime", Native_SetBossAttackRunDurationTime); - CreateNative("SF2_GetBossAttackRunDelayTime", Native_GetBossAttackRunDelayTime); - CreateNative("SF2_SetBossAttackRunDelayTime", Native_SetBossAttackRunDelayTime); - CreateNative("SF2_GetBossAttackIndexDamageType", Native_GetBossAttackIndexDamageType); - CreateNative("SF2_GetBossAttackIndexDamage", Native_GetBossAttackIndexDamage); - CreateNative("SF2_GetBossAttackIndexSpread", Native_GetBossAttackIndexSpread); - CreateNative("SF2_GetBossAttackIndexRange", Native_GetBossAttackIndexRange); - CreateNative("SF2_GetBossAttackIndexType", Native_GetBossAttackIndexType); - - CreateNative("SF2_GetBossTeleportThinkTimer", Native_GetBossTeleportThinkTimer); - CreateNative("SF2_SetBossTeleportThinkTimer", Native_SetBossTeleportThinkTimer); - CreateNative("SF2_GetBossTeleportTarget", Native_GetBossTeleportTarget); - - CreateNative("SF2_GetBossThinkTimer", Native_GetBossThinkTimer); - CreateNative("SF2_SetBossThinkTimer", Native_SetBossThinkTimer); - CreateNative("SF2_UnhookBossThinkHook", Native_UnhookBossThinkHook); CreateNative("SF2_GetVectorSquareMagnitude", Native_GetVectorSquareMagnitude); CreateNative("SF2_InitiateBossPackVote", Native_InitiateBossPackVote); - CreateNative("SF2_GetProjectileFlags", Native_GetProjectileFlags); - CreateNative("SF2_SetProjectileFlags", Native_SetProjectileFlags); CreateNative("SF2_IsSurvivalMap", Native_IsSurvivalMap); CreateNative("SF2_IsBoxingMap", Native_IsBoxingMap); CreateNative("SF2_IsRaidMap", Native_IsRaidMap); CreateNative("SF2_IsProxyMap", Native_IsProxyMap); - CreateNative("SF2_IsRenevantMap", Native_IsRenevantMap); CreateNative("SF2_IsSlaughterRunMap", Native_IsSlaughterRunMap); + SF2_BaseBoss.SetupAPI(); + SF2_PlaySequenceAndWait.SetupAPI(); + NPC_InitializeAPI(); + NPCChaser_InitializeAPI(); + NPCStatue_InitializeAPI(); + + Client_SetupAPI(); + PvP_InitializeAPI(); + PvE_InitializeAPI(); + + Renevant_InitializeAPI(); SpecialRoundInitializeAPI(); + SetupMethodmapAPI(); + + SetupCustomEntitiesAPI(); + SetupBossProfileNatives(); #if defined _steamtools_included @@ -284,23 +233,23 @@ void SDK_Init() BuildPath(Path_SM, strFilePath, sizeof(strFilePath), "gamedata/tf2items.randomizer.txt"); if (FileExists(strFilePath)) { - Handle hGameConf = LoadGameConfigFile("tf2items.randomizer"); - if (hGameConf != null) + GameData gameConf = LoadGameConfigFile("tf2items.randomizer"); + if (gameConf != null) { StartPrepSDKCall(SDKCall_Player); - PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "CTFPlayer::EquipWearable"); + PrepSDKCall_SetFromConf(gameConf, SDKConf_Virtual, "CTFPlayer::EquipWearable"); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer ); g_SDKEquipWearable = EndPrepSDKCall(); if (g_SDKEquipWearable == null) { // Old gamedata StartPrepSDKCall(SDKCall_Player); - PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "EquipWearable"); + PrepSDKCall_SetFromConf(gameConf, SDKConf_Virtual, "EquipWearable"); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); g_SDKEquipWearable = EndPrepSDKCall(); } } - delete hGameConf; + delete gameConf; } } if (g_SDKEquipWearable == null) @@ -333,7 +282,7 @@ void SDK_Init() PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CBaseTrigger::PassesTriggerFilters"); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); - if ((g_SDKPassesTriggerFilters = EndPrepSDKCall()) == INVALID_HANDLE) + if ((g_SDKPassesTriggerFilters = EndPrepSDKCall()) == null) { SetFailState("Failed to setup CBaseTrigger::PassesTriggerFilters call from gamedata!"); } @@ -346,6 +295,39 @@ void SDK_Init() SetFailState("Couldn't find CBaseEntity::GetSmoothedVelocity offset from SF2 gamedata!"); }*/ + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CBaseAnimating::LookupBone"); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + if ((g_SDKLookupBone = EndPrepSDKCall()) == null) + { + LogError("Failed to setup CBaseAnimating::LookupBone call from gamedata!"); + } + + // void CBaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CBaseAnimating::GetBonePosition"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef, .encflags = VENCODE_FLAG_COPYBACK); + PrepSDKCall_AddParameter(SDKType_QAngle, SDKPass_ByRef, .encflags = VENCODE_FLAG_COPYBACK); + if ((g_SDKGetBonePosition = EndPrepSDKCall()) == null) + { + LogError("Failed to setup CBaseAnimating::GetBonePosition call from gamedata"); + } + + StartPrepSDKCall(SDKCall_Static); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "Studio_SeqVelocity"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef, .encflags = VENCODE_FLAG_COPYBACK); + PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); + if ((g_SDKSequenceVelocity = EndPrepSDKCall()) == null) + { + LogError("Failed to setup Studio_SeqVelocity call from gamedata"); + } + //Hook_ClientWantsLagCompensationOnEntity int offset = gameData.GetOffset("CTFPlayer::WantsLagCompensationOnEntity"); g_DHookWantsLagCompensationOnEntity = new DynamicHook(offset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity); @@ -397,17 +379,17 @@ void SDK_Init() // API // ========================================================== -static any Native_GetConfig(Handle plugin,int numParams) +static any Native_GetConfig(Handle plugin, int numParams) { return g_Config; } -static any Native_IsRunning(Handle plugin,int numParams) +static any Native_IsRunning(Handle plugin, int numParams) { return g_Enabled; } -static any Native_GetRoundState(Handle plugin,int numParams) +static any Native_GetRoundState(Handle plugin, int numParams) { return g_RoundState; } @@ -417,26 +399,27 @@ static any Native_IsRoundInGracePeriod(Handle plugin, int numParams) return GetRoundState() == SF2RoundState_Grace; } -static any Native_GetCurrentDifficulty(Handle plugin,int numParams) +static any Native_GetCurrentDifficulty(Handle plugin, int numParams) { return g_DifficultyConVar.IntValue; } -static any Native_GetDifficultyModifier(Handle plugin,int numParams) +static any Native_GetBossDifficulty(Handle plugin, int numParams) +{ + return GetLocalGlobalDifficulty(GetNativeCell(1)); +} + +static any Native_GetDifficultyModifier(Handle plugin, int numParams) { int difficulty = GetNativeCell(1); - if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + if (difficulty < Difficulty_Normal || difficulty >= Difficulty_Max) { - LogError("Difficulty parameter can only be from %d to %d!", Difficulty_Easy, Difficulty_Max - 1); + LogError("Difficulty parameter can only be from %d to %d!", Difficulty_Normal, Difficulty_Max - 1); return 1; } switch (difficulty) { - case Difficulty_Easy: - { - return DIFFICULTYMODIFIER_NORMAL; - } case Difficulty_Hard: { return DIFFICULTYMODIFIER_HARD; @@ -458,107 +441,146 @@ static any Native_GetDifficultyModifier(Handle plugin,int numParams) return DIFFICULTYMODIFIER_NORMAL; } -static any Native_IsInSpecialRound(Handle plugin,int numParams) +static any Native_IsInSpecialRound(Handle plugin, int numParams) { return SF_SpecialRound(GetNativeCell(1)); } -static any Native_GetClientGroup(Handle plugin,int numParams) +static any Native_GetCurrentBossPack(Handle plugin, int numParams) +{ + int length = GetNativeCell(2); + char[] bossPackName = new char[++length]; + GetCurrentBossPack(bossPackName, length); + SetNativeString(1, bossPackName, length, _, length); + return length; +} + +static any Native_GetClientGroup(Handle plugin, int numParams) { return ClientGetPlayerGroup(GetNativeCell(1)); } -static any Native_GetClientQueuePoints(Handle plugin,int numParams) +static any Native_GetClientQueuePoints(Handle plugin, int numParams) { return g_PlayerQueuePoints[GetNativeCell(1)]; } -static any Native_SetClientQueuePoints(Handle plugin,int numParams) +static any Native_SetClientQueuePoints(Handle plugin, int numParams) { g_PlayerQueuePoints[GetNativeCell(1)] = GetNativeCell(2); return 0; } -static any Native_IsClientEliminated(Handle plugin,int numParams) +static any Native_IsValidClient(Handle plugin, int numParams) +{ + return IsValidClient(GetNativeCell(1)); +} + +static any Native_IsClientCritBoosted(Handle plugin, int numParams) +{ + return IsClientCritBoosted(GetNativeCell(1)); +} + +static any Native_IsClientMiniCritBoosted(Handle plugin, int numParams) +{ + return TF2_IsMiniCritBuffed(GetNativeCell(1)); +} + +static any Native_IsClientUbercharged(Handle plugin, int numParams) +{ + return IsClientCritUbercharged(GetNativeCell(1)); +} + +static any Native_IsClientInKart(Handle plugin, int numParams) +{ + return IsClientInKart(GetNativeCell(1)); +} + +static any Native_IsClientInCondition(Handle plugin, int numParams) +{ + return TF2_IsPlayerInCondition(GetNativeCell(1), GetNativeCell(2)); +} + +static any Native_IsClientEliminated(Handle plugin, int numParams) { return g_PlayerEliminated[GetNativeCell(1)]; } -static any Native_IsClientInGhostMode(Handle plugin,int numParams) +static any Native_IsClientInGhostMode(Handle plugin, int numParams) { return IsClientInGhostMode(GetNativeCell(1)); } -static any Native_IsClientProxy(Handle plugin,int numParams) +static any Native_IsClientProxy(Handle plugin, int numParams) { return g_PlayerProxy[GetNativeCell(1)]; } -static any Native_GetClientBlinkCount(Handle plugin,int numParams) +static any Native_GetClientBlinkCount(Handle plugin, int numParams) { return ClientGetBlinkCount(GetNativeCell(1)); } -static any Native_IsClientBlinking(Handle plugin,int numParams) +static any Native_IsClientBlinking(Handle plugin, int numParams) { return IsClientBlinking(GetNativeCell(1)); } -static any Native_GetClientBlinkMeter(Handle plugin,int numParams) +static any Native_GetClientBlinkMeter(Handle plugin, int numParams) { return ClientGetBlinkMeter(GetNativeCell(1)); } -static any Native_SetClientBlinkMeter(Handle plugin,int numParams) +static any Native_SetClientBlinkMeter(Handle plugin, int numParams) { ClientSetBlinkMeter(GetNativeCell(1), GetNativeCell(2)); return 0; } -static any Native_GetClientProxyMaster(Handle plugin,int numParams) +static any Native_GetClientProxyMaster(Handle plugin, int numParams) { return NPCGetFromUniqueID(g_PlayerProxyMaster[GetNativeCell(1)]); } -static any Native_GetClientProxyControlAmount(Handle plugin,int numParams) +static any Native_GetClientProxyControlAmount(Handle plugin, int numParams) { return g_PlayerProxyControl[GetNativeCell(1)]; } -static any Native_GetClientProxyControlRate(Handle plugin,int numParams) +static any Native_GetClientProxyControlRate(Handle plugin, int numParams) { return g_PlayerProxyControlRate[GetNativeCell(1)]; } -static any Native_SetClientProxyMaster(Handle plugin,int numParams) +static any Native_SetClientProxyMaster(Handle plugin, int numParams) { g_PlayerProxyMaster[GetNativeCell(1)] = NPCGetUniqueID(GetNativeCell(2)); return 0; } -static any Native_SetClientProxyControlAmount(Handle plugin,int numParams) +static any Native_SetClientProxyControlAmount(Handle plugin, int numParams) { g_PlayerProxyControl[GetNativeCell(1)] = GetNativeCell(2); return 0; } -static any Native_SetClientProxyControlRate(Handle plugin,int numParams) +static any Native_SetClientProxyControlRate(Handle plugin, int numParams) { g_PlayerProxyControlRate[GetNativeCell(1)] = GetNativeCell(2); return 0; } -static any Native_IsClientLookingAtBoss(Handle plugin,int numParams) +static any Native_IsClientLookingAtBoss(Handle plugin, int numParams) { return g_PlayerSeesSlender[GetNativeCell(1)][GetNativeCell(2)]; } -static any Native_DidClientEscape(Handle plugin,int numParams) +static any Native_DidClientEscape(Handle plugin, int numParams) { return DidClientEscape(GetNativeCell(1)); } -static any Native_ForceClientEscape(Handle plugin,int numParams) +static any Native_ForceClientEscape(Handle plugin, int numParams) { int client = GetNativeCell(1); @@ -583,33 +605,6 @@ static any Native_IsClientUsingFlashlight(Handle plugin, int numParams) return IsClientUsingFlashlight(GetNativeCell(1)); } -static any Native_GetClientSprintPoints(Handle plugin, int numParams) -{ - return ClientGetSprintPoints(GetNativeCell(1)); -} - -static any Native_SetClientSprintPoints(Handle plugin, int numParams) -{ - g_PlayerSprintPoints[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_IsClientSprinting(Handle plugin, int numParams) -{ - return IsClientSprinting(GetNativeCell(1)); -} - -static any Native_IsClientReallySprinting(Handle plugin, int numParams) -{ - return IsClientReallySprinting(GetNativeCell(1)); -} - -static any Native_SetClientSprintState(Handle plugin, int numParams) -{ - ClientHandleSprint(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - static any Native_IsClientTrapped(Handle plugin, int numParams) { return g_PlayerTrapped[GetNativeCell(1)]; @@ -624,7 +619,7 @@ static any Native_ClientStartDeathCam(Handle plugin, int numParams) { float pos[3]; GetNativeArray(3, pos, sizeof(pos)); - ClientStartDeathCam(GetNativeCell(1), GetNativeCell(2), pos, GetNativeCell(4)); + ClientStartDeathCam(GetNativeCell(1), GetNativeCell(2), pos, GetNativeCell(4), GetNativeCell(5)); return 0; } @@ -644,485 +639,79 @@ static any Native_ClientForceProxy(Handle plugin, int numParams) return 0; } -static any Native_CollectAsPage(Handle plugin,int numParams) +static any Native_CollectAsPage(Handle plugin, int numParams) { CollectPage(GetNativeCell(1), GetNativeCell(2)); return 0; } -static any Native_GetMaxBosses(Handle plugin,int numParams) -{ - return MAX_BOSSES; -} - -static any Native_EntIndexToBossIndex(Handle plugin,int numParams) +static any Native_GetEmptyPageSpawnPoints(Handle plugin, int numParams) { - return NPCGetFromEntIndex(GetNativeCell(1)); + return g_EmptySpawnPagePoints; } -static any Native_BossIndexToEntIndex(Handle plugin,int numParams) -{ - return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); -} - -static any Native_BossIndexToEntIndexEx(Handle plugin,int numParams) -{ - return NPCGetEntIndex(GetNativeCell(1)); -} - -static any Native_BossIDToBossIndex(Handle plugin,int numParams) -{ - return NPCGetFromUniqueID(GetNativeCell(1)); -} - -static any Native_BossIndexToBossID(Handle plugin,int numParams) -{ - return NPCGetUniqueID(GetNativeCell(1)); -} - -static any Native_UpdateBossAnimation(Handle plugin,int numParams) -{ - NPCChaserUpdateBossAnimation(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); - return 0; -} - -static any Native_ForceBossJump(Handle plugin,int numParams) +static any Native_ForceBossJump(Handle plugin, int numParams) { float startPos[3], endPos[3]; GetNativeArray(2, startPos, 3); GetNativeArray(3, endPos, 3); - CBaseNPC_Jump(GetNativeCell(1), startPos, endPos); - return 0; -} - -static any Native_AddBoss(Handle plugin, int numParams) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - GetNativeString(1, profile, sizeof(profile)); - - int flags = GetNativeCell(2); - bool spawnCompanions = GetNativeCell(3); - bool playSpawnSound = GetNativeCell(4); - - return AddProfile(profile, flags, _, spawnCompanions, playSpawnSound); -} - -static any Native_RemoveBoss(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - RemoveProfile(boss.Index); - return 0; -} - -static any Native_DespawnBoss(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - RemoveSlender(boss.Index); - return 0; -} - -static any Native_PerformBossVoice(Handle plugin, int numParams) -{ - SlenderPerformVoice(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3)); - return 0; -} - -static any Native_GetBossName(Handle plugin,int numParams) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(GetNativeCell(1), profile, sizeof(profile)); - - SetNativeString(2, profile, GetNativeCell(3)); + //CBaseNPC_Jump(GetNativeCell(1), startPos, endPos); return 0; } -static any Native_GetBossType(Handle plugin, int numParams) -{ - return NPCGetType(GetNativeCell(1)); -} - -static any Native_GetBossFlags(Handle plugin, int numParams) -{ - return NPCGetFlags(GetNativeCell(1)); -} - -static any Native_SetBossFlags(Handle plugin, int numParams) -{ - NPCSetFlags(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - -static any Native_SpawnBoss(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - float position[3]; - GetNativeArray(2, position, 3); - - SpawnSlender(boss, position); - return 0; -} - -static any Native_IsBossSpawning(Handle plugin, int numParams) -{ - return g_SlenderSpawning[GetNativeCell(1)]; -} - -static any Native_GetBossModelEntity(Handle plugin,int numParams) +static any Native_GetBossModelEntity(Handle plugin, int numParams) { return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); } -static any Native_GetBossTarget(Handle plugin,int numParams) +static any Native_GetBossTarget(Handle plugin, int numParams) { - return EntRefToEntIndex(g_SlenderTarget[GetNativeCell(1)]); + SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + return boss.Target.index; } static any Native_SetBossTarget(Handle plugin, int numParams) { - g_SlenderTarget[GetNativeCell(1)] = EntIndexToEntRef(GetNativeCell(2)); - return 0; -} - -static any Native_GetBossPathFollower(Handle plugin, int numParams) -{ - return g_BossPathFollower[GetNativeCell(1)]; -} - -static any Native_GetBossMaster(Handle plugin,int numParams) -{ - return g_SlenderCopyMaster[GetNativeCell(1)]; -} - -static any Native_GetBossIdleLifetime(Handle plugin,int numParams) -{ - return NPCGetIdleLifetime(GetNativeCell(1), GetNativeCell(2)); -} - -static any Native_GetBossState(Handle plugin,int numParams) -{ - return g_SlenderState[GetNativeCell(1)]; -} - -static any Native_SetBossState(Handle plugin,int numParams) -{ - g_SlenderState[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossEyePosition(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid() || !IsValidEntity(boss.EntIndex)) - { - return 0; - } - - float eyePos[3]; - boss.GetEyePosition(eyePos); - - SetNativeArray(2, eyePos, 3); - return 0; -} - -static any Native_GetBossEyePositionOffset(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - float eyePos[3]; - boss.GetEyePositionOffset(eyePos); - - SetNativeArray(2, eyePos, 3); - return 0; -} - -static any Native_GetBossFOV(Handle plugin, int numParams) -{ - return NPCGetFOV(GetNativeCell(1)); -} - -static any Native_GetBossTimeUntilNoPersistence(Handle plugin, int numParams) -{ - return g_SlenderTimeUntilNoPersistence[GetNativeCell(1)]; -} - -static any Native_SetBossTimeUntilNoPersistence(Handle plugin, int numParams) -{ - g_SlenderTimeUntilNoPersistence[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossTimeUntilIdle(Handle plugin, int numParams) -{ - return g_SlenderTimeUntilIdle[GetNativeCell(1)]; -} - -static any Native_SetBossTimeUntilIdle(Handle plugin, int numParams) -{ - g_SlenderTimeUntilIdle[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossTimeUntilAlert(Handle plugin, int numParams) -{ - return g_SlenderTimeUntilAlert[GetNativeCell(1)]; -} - -static any Native_SetBossTimeUntilAlert(Handle plugin, int numParams) -{ - g_SlenderTimeUntilAlert[GetNativeCell(1)] = GetNativeCell(2); + SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + boss.Target = CBaseEntity(GetNativeCell(2)); return 0; } static any Native_IsBossStunnable(Handle plugin, int numParams) { - return SF2NPC_Chaser(GetNativeCell(1)).StunEnabled; + return SF2NPC_Chaser(GetNativeCell(1)).GetProfileData().StunData.Enabled[1]; } static any Native_IsBossStunnableByFlashlight(Handle plugin, int numParams) { - return SF2NPC_Chaser(GetNativeCell(1)).StunByFlashlightEnabled; + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(GetNativeCell(1)); + return data.StunData.FlashlightStun[1]; } static any Native_IsBossCloaked(Handle plugin, int numParams) { - return g_NpcHasCloaked[GetNativeCell(1)]; + return SF2_ChaserEntity(NPCGetEntIndex(GetNativeCell(1))).HasCloaked; } static any Native_GetBossStunHealth(Handle plugin, int numParams) { - return SF2NPC_Chaser(GetNativeCell(1)).StunHealth; -} - -static any Native_SetBossStunHealth(Handle plugin, int numParams) -{ - SF2NPC_Chaser(GetNativeCell(1)).StunHealth = GetNativeCell(2); - return 0; -} - -static any Native_GetBossNextStunTime(Handle plugin, int numParams) -{ - return g_SlenderNextStunTime[GetNativeCell(1)]; -} - -static any Native_SetBossNextStunTime(Handle plugin, int numParams) -{ - g_SlenderNextStunTime[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_ForceBossGiveUp(Handle plugin, int numParams) -{ - g_SlenderGiveUp[GetNativeCell(1)] = false; - return 0; -} - -static any Native_GetBossGoalPosition(Handle plugin, int numParams) -{ - SetNativeArray(2, g_SlenderGoalPos[GetNativeCell(1)], 3); - return 0; -} - -static any Native_CanBossHearClient(Handle plugin, int numParams) -{ - return SlenderCanHearPlayer(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3)); -} - -static any Native_CreateBossSoundHint(Handle plugin, int numParams) -{ - SF2NPC_Chaser boss = SF2NPC_Chaser(GetNativeCell(1)); - if (!boss.IsValid() || boss.Type != SF2BossType_Chaser || !IsValidEntity(boss.EntIndex)) + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(GetNativeCell(1))); + if (!chaser.IsValid()) { return 0; } - - SoundType soundType = GetNativeCell(2); - - float position[3]; - GetNativeArray(3, position, 3); - - int difficulty = GetNativeCell(4); - - switch (soundType) - { - case SoundType_Footstep, SoundType_LoudFootstep, SoundType_QuietFootstep: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDFOOTSTEP); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddFootstep(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - case SoundType_Voice: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDVOICE); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddVoice(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - case SoundType_Weapon: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDWEAPON); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddWeapon(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - case SoundType_Flashlight: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDFLASHLIGHT); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddWeapon(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - } - return 0; -} - -static any Native_GetBossProjectileType(Handle plugin,int numParams) -{ - return NPCChaserGetProjectileType(GetNativeCell(1)); -} - -static any Native_SetBossCurrentAttackIndex(Handle plugin,int numParams) -{ - NPCSetCurrentAttackIndex(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - -static any Native_GetBossCurrentAttackIndex(Handle plugin,int numParams) -{ - return NPCGetCurrentAttackIndex(GetNativeCell(1)); -} - -static any Native_GetBossMaxAttackIndexes(Handle plugin,int numParams) -{ - return NPCChaserGetAttackCount(GetNativeCell(1)); -} - -static any Native_GetBossAttackRunDurationTime(Handle plugin,int numParams) -{ - return g_NpcBaseAttackRunDurationTime[GetNativeCell(1)][GetNativeCell(2)]; -} - -static any Native_SetBossAttackRunDurationTime(Handle plugin,int numParams) -{ - g_NpcBaseAttackRunDurationTime[GetNativeCell(1)][GetNativeCell(2)] = GetNativeCell(3); - return 0; -} - -static any Native_GetBossAttackRunDelayTime(Handle plugin,int numParams) -{ - return g_NpcBaseAttackRunDelayTime[GetNativeCell(1)][GetNativeCell(2)]; -} - -static any Native_SetBossAttackRunDelayTime(Handle plugin,int numParams) -{ - g_NpcBaseAttackRunDelayTime[GetNativeCell(1)][GetNativeCell(2)] = GetNativeCell(3); - return 0; -} - -static any Native_GetBossAttackIndexDamageType(Handle plugin,int numParams) -{ - return NPCChaserGetAttackDamageType(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); -} - -static any Native_GetBossAttackIndexDamage(Handle plugin,int numParams) -{ - return NPCChaserGetAttackDamage(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3)); -} - -static any Native_GetBossAttackIndexSpread(Handle plugin,int numParams) -{ - return NPCChaserGetAttackSpread(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); + return chaser.StunHealth; } -static any Native_GetBossAttackIndexRange(Handle plugin,int numParams) -{ - return NPCChaserGetAttackRange(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); -} - -static any Native_GetBossAttackIndexType(Handle plugin,int numParams) -{ - return NPCChaserGetAttackType(GetNativeCell(1), GetNativeCell(2)); -} - -static any Native_GetBossTeleportThinkTimer(Handle plugin,int numParams) -{ - return g_SlenderThink[GetNativeCell(1)]; -} - -static any Native_SetBossTeleportThinkTimer(Handle plugin,int numParams) -{ - int bossIndex = GetNativeCell(1); - g_SlenderThink[bossIndex] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossTeleportTarget(Handle plugin,int numParams) -{ - return EntRefToEntIndex(g_SlenderTeleportTarget[GetNativeCell(1)]); -} - -static any Native_GetBossThinkTimer(Handle plugin,int numParams) -{ - return g_SlenderEntityThink[GetNativeCell(1)]; -} - -static any Native_SetBossThinkTimer(Handle plugin,int numParams) -{ - int bossIndex = GetNativeCell(1); - g_SlenderEntityThink[bossIndex] = GetNativeCell(2); - return 0; -} - -static any Native_UnhookBossThinkHook(Handle plugin,int numParams) +static any Native_SetBossStunHealth(Handle plugin, int numParams) { - int bossIndex = GetNativeCell(1); - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(GetNativeCell(1))); + if (!chaser.IsValid()) { return 0; } - switch (NPCGetType(bossIndex)) - { - case SF2BossType_Statue: - { - SDKUnhook(slender, SDKHook_Think, SlenderStatueBossProcessMovement); - } - case SF2BossType_Chaser: - { - SDKUnhook(slender, SDKHook_Think, SlenderChaseBossProcessMovement); - } - } + chaser.StunHealth = GetNativeCell(2); return 0; } @@ -1140,17 +729,6 @@ static any Native_InitiateBossPackVote(Handle plugin, int numParams) return 0; } -static any Native_GetProjectileFlags(Handle plugin, int numParams) -{ - return ProjectileGetFlags(GetNativeCell(1)); -} - -static any Native_SetProjectileFlags(Handle plugin, int numParams) -{ - ProjectileSetFlags(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - static any Native_IsSurvivalMap(Handle plugin, int numParams) { return SF_IsSurvivalMap(); diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_hints.sp b/addons/sourcemod/scripting/sf2/functionclients/client_hints.sp deleted file mode 100644 index d571f8a7..00000000 --- a/addons/sourcemod/scripting/sf2/functionclients/client_hints.sp +++ /dev/null @@ -1,69 +0,0 @@ -#if defined _sf2_client_hints_included - #endinput -#endif -#define _sf2_client_hints_included - -bool g_PlayerHints[MAXTF2PLAYERS][PlayerHint_MaxNum]; - -// Hint data. -enum -{ - PlayerHint_Sprint = 0, - PlayerHint_Flashlight, - PlayerHint_MainMenu, - PlayerHint_Blink, - PlayerHint_Trap, - PlayerHint_MaxNum -}; - - -void ClientResetHints(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetHints(%d)", client); - } - #endif - - for (int i = 0; i < PlayerHint_MaxNum; i++) - { - g_PlayerHints[client][i] = false; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetHints(%d)", client); - } - #endif -} - -void ClientShowHint(int client,int hint) -{ - g_PlayerHints[client][hint] = true; - - switch (hint) - { - case PlayerHint_Sprint: - { - PrintHintText(client, "%T", "SF2 Hint Sprint", client); - } - case PlayerHint_Flashlight: - { - PrintHintText(client, "%T", "SF2 Hint Flashlight", client); - } - case PlayerHint_Blink: - { - PrintHintText(client, "%T", "SF2 Hint Blink", client); - } - case PlayerHint_MainMenu: - { - PrintHintText(client, "%T", "SF2 Hint Main Menu", client); - } - case PlayerHint_Trap: - { - PrintHintText(client, "%T", "SF2 Hint Trap", client); - } - } -} diff --git a/addons/sourcemod/scripting/sf2/functionclients/clients_think.sp b/addons/sourcemod/scripting/sf2/functionclients/clients_think.sp deleted file mode 100644 index 430e1777..00000000 --- a/addons/sourcemod/scripting/sf2/functionclients/clients_think.sp +++ /dev/null @@ -1,1718 +0,0 @@ -#if defined _sf2_clients_think_included - #endinput -#endif -#define _sf2_clients_think_included - -#pragma semicolon 1 - -void Hook_ClientPreThink(int client) -{ - if (!g_Enabled) - { - return; - } - - ClientProcessFlashlightAngles(client); - ClientProcessInteractiveGlow(client); - ClientProcessStaticShake(client); - ClientProcessViewAngles(client); - - if (IsClientInDeathCam(client) && !IsClientInGhostMode(client)) - { - int ent = EntRefToEntIndex(g_PlayerDeathCamEnt[client]); - if (ent && ent != INVALID_ENT_REFERENCE && g_CameraInDeathCamAdvanced[ent]) - { - float camPos[3], camAngs[3]; - GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", camAngs); - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", camPos); - - camPos[0] -= g_CameraPlayerOffsetBackward[ent]; - camPos[2] -= g_CameraPlayerOffsetDownward[ent]; - - CBaseEntity player = CBaseEntity(client); - - player.SetLocalOrigin(camPos); - player.SetLocalAngles(camAngs); - } - } - - if (IsClientInGhostMode(client)) - { - SetEntityFlags(client,GetEntityFlags(client)^FL_EDICT_ALWAYS); - SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 520.0); - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - if (IsClientInKart(client) || !TF2_IsPlayerInCondition(client, TFCond_Stealthed)) - { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); - TF2_RemoveCondition(client, TFCond_Taunting); - ClientHandleGhostMode(client, true); - } - TF2_RemoveCondition(client, TFCond_Taunting); - } - else if (!g_PlayerEliminated[client] || g_PlayerProxy[client]) - { - if (!IsRoundEnding() && !IsRoundInWarmup() && !DidClientEscape(client)) - { - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - - int roundState = view_as(GameRules_GetRoundState()); - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - if (!g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Red) - { - if (TF2_IsPlayerInCondition(client,TFCond_Disguised)) - { - TF2_RemoveCondition(client,TFCond_Disguised); - } - - if (TF2_IsPlayerInCondition(client,TFCond_Taunting) && g_PlayerTrapped[client]) - { - TF2_RemoveCondition(client,TFCond_Taunting); - } - - if (TF2_IsPlayerInCondition(client,TFCond_Taunting) && class == TFClass_Soldier) - { - int weaponEnt = GetPlayerWeaponSlot(client, TFWeaponSlot_Melee); - if (weaponEnt && weaponEnt != INVALID_ENT_REFERENCE) - { - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 775 || itemDefInt == 128) - { - TF2_RemoveCondition(client,TFCond_Taunting); //Stop suiciding... - } - } - } - - if (roundState == 4) - { - bool inDanger = false; - - if (!inDanger) - { - int state; - int bossTarget; - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (NPCGetType(i) == SF2BossType_Chaser) - { - bossTarget = EntRefToEntIndex(g_SlenderTarget[i]); - state = g_SlenderState[i]; - - if ((state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) && - ((bossTarget && bossTarget != INVALID_ENT_REFERENCE && (bossTarget == client || ClientGetDistanceFromEntity(client, bossTarget) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) - { - inDanger = true; - ClientSetScareBoostEndTime(client, GetGameTime() + 5.0); - - // Induce client stress levels. - float unComfortZoneDist = 512.0; - float stressScalar = ((SquareFloat(unComfortZoneDist) / NPCGetDistanceFromEntity(i, client))); - ClientAddStress(client, 0.025 * stressScalar); - - break; - } - } - } - } - - if (g_PlayerStaticAmount[client] > 0.4) - { - inDanger = true; - } - if (GetGameTime() < ClientGetScareBoostEndTime(client)) - { - inDanger = true; - } - - if (!inDanger) - { - int state; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (NPCGetType(i) == SF2BossType_Chaser) - { - if (state == STATE_ALERT) - { - if (PlayerCanSeeSlender(client, i)) - { - inDanger = true; - ClientSetScareBoostEndTime(client, GetGameTime() + 5.0); - } - } - } - } - } - - if (!inDanger) - { - float curTime = GetGameTime(); - float scareSprintDuration = 3.0; - if (!IsClassConfigsValid()) - { - if (class == TFClass_DemoMan) - { - scareSprintDuration *= 1.667; - } - } - else - { - scareSprintDuration *= g_ClassScareSprintDurationMultipler[classToInt]; - } - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if ((curTime - g_PlayerScareLastTime[client][i]) <= scareSprintDuration) - { - inDanger = true; - break; - } - } - } - - float walkSpeed, sprintSpeed; - if (!IsClassConfigsValid()) - { - walkSpeed = ClientGetDefaultWalkSpeed(client); - sprintSpeed = ClientGetDefaultSprintSpeed(client); - } - else - { - walkSpeed = g_ClassWalkSpeed[classToInt]; - sprintSpeed = g_ClassRunSpeed[classToInt]; - } - - // Check for weapon speed changes. - int weaponEnt = INVALID_ENT_REFERENCE; - - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - switch (itemDefInt) - { - case 172: // Scotsman's Skullcutter - { - if (GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - sprintSpeed -= (sprintSpeed * 0.05); - walkSpeed -= (walkSpeed * 0.05); - } - } - case 214: // The Powerjack - { - if (GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - sprintSpeed += (sprintSpeed * 0.03); - } - } - case 239: // Gloves of Running Urgently - { - if (GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - sprintSpeed += (sprintSpeed * 0.075); - } - } - case 775: // Escape Plan - { - float health = float(GetEntProp(client, Prop_Send, "m_iHealth")); - float maxHealth = float(SDKCall(g_SDKGetMaxHealth, client)); - float percentage = health / maxHealth; - - if (percentage < 0.805 && percentage >= 0.605) - { - walkSpeed += (walkSpeed * 0.05); - sprintSpeed += (sprintSpeed * 0.05); - } - else if (percentage < 0.605 && percentage >= 0.405) - { - walkSpeed += (walkSpeed * 0.1); - sprintSpeed += (sprintSpeed * 0.1); - } - else if (percentage < 0.405 && percentage >= 0.205) - { - walkSpeed += (walkSpeed * 0.15); - sprintSpeed += (sprintSpeed * 0.15); - } - else if (percentage < 0.205) - { - walkSpeed += (walkSpeed * 0.2); - sprintSpeed += (sprintSpeed * 0.2); - } - } - } - } - - // Speed buff - if (!SF_IsSlaughterRunMap()) - { - if (TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly)) - { - walkSpeed += (walkSpeed * 0.115); - sprintSpeed += (sprintSpeed * 0.165); - } - } - else - { - if (TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly)) - { - walkSpeed += (walkSpeed * 0.105); - sprintSpeed += (sprintSpeed * 0.14); - } - } - - if (inDanger) - { - if (!IsClassConfigsValid()) - { - if (class != TFClass_Spy && class != TFClass_Pyro) - { - walkSpeed *= 1.34; - sprintSpeed *= 1.34; - } - else - { - if (class == TFClass_Spy) - { - walkSpeed *= 1.28; - sprintSpeed *= 1.28; - } - else - { - weaponEnt = INVALID_ENT_REFERENCE; - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - walkSpeed *= 1.32; - sprintSpeed *= 1.32; - } - else - { - walkSpeed *= 1.34; - sprintSpeed *= 1.34; - } - } - } - } - } - else - { - float multiplier = g_ClassDangerSpeedMultipler[classToInt]; - if (class == TFClass_Pyro) - { - weaponEnt = INVALID_ENT_REFERENCE; - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - multiplier -= 0.02; - } - } - } - walkSpeed *= multiplier; - sprintSpeed *= multiplier; - } - - if (!g_PlayerHints[client][PlayerHint_Sprint]) - { - ClientShowHint(client, PlayerHint_Sprint); - } - } - - float sprintSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.425); - float walkSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.3); - if (g_PlayerSprintPoints[client] > 8) - { - sprintSpeedSubtract -= sprintSpeedSubtract * (g_PlayerSprintPoints[client] != 0 ? (float(g_PlayerSprintPoints[client]) / 100.0) : 0.0); - sprintSpeed -= sprintSpeedSubtract; - } - else - { - sprintSpeedSubtract += 125; - sprintSpeed -= sprintSpeedSubtract; - walkSpeedSubtract += 25; - walkSpeed -= walkSpeedSubtract; - } - - if (IsClientSprinting(client)) - { - if (!g_PlayerTrapped[client]) - { - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - if (!TF2_IsPlayerInCondition(client, TFCond_Charging)) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", sprintSpeed); - } - else - { - if (SF_IsBoxingMap() || SF_IsRaidMap()) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", sprintSpeed*2.5); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", sprintSpeed/2.5); - } - } - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 520.0); - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); - } - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 0.1); - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", 0.1); - } - } - else - { - if (!g_PlayerTrapped[client]) - { - if (!TF2_IsPlayerInCondition(client, TFCond_Charging)) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", walkSpeed); - } - else - { - if (SF_IsBoxingMap() || SF_IsRaidMap()) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", walkSpeed*2.5); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", walkSpeed/2.5); - } - } - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", walkSpeed-20.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 0.01); - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", 0.01); - } - } - - if (ClientCanBreath(client) && !g_PlayerBreath[client]) - { - ClientStartBreathing(client); - } - - if (g_PlayerTrapped[client]) - { - TF2Attrib_SetByName(client, "increased jump height", 0.0); - } - else - { - TF2Attrib_SetByName(client, "increased jump height", 1.0); - } - } - } - else if (g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Blue) - { - bool speedup = TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly); - - switch (class) - { - case TFClass_Scout: - { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 390.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } - } - case TFClass_Medic: - { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } - } - case TFClass_Spy: - { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } - } - default: - { - if (g_InProxySurvivalRageMode) - { - float rageSpeed = ClientGetDefaultSprintSpeed(client)+30.0; - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); - } - } - } - } - } - } - if (g_PlayerEliminated[client] && IsClientInPvP(client)) - { - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - if (IsClientInKart(client)) - { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); - } - } - if (IsRoundInWarmup() || (IsRoundInIntro() && !g_PlayerEliminated[client]) || IsRoundEnding()) //I told you, stop breaking my plugin - { - if (IsClientInKart(client)) - { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); - } - } - - // Calculate player stress levels. - if (GetGameTime() >= g_PlayerStressNextUpdateTime[client]) - { - //float flPagePercent = g_PageMax != 0 ? float(g_PageCount) / float(g_PageMax) : 0.0; - //float flPageCountPercent = g_PageMax != 0? float(g_PlayerPageCount[client]) / float(g_PageMax) : 0.0; - - g_PlayerStressNextUpdateTime[client] = GetGameTime() + 0.33; - ClientAddStress(client, -0.01); - - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_PLAYER_STRESS, 1, "g_PlayerStressAmount[%d]: %0.1f", client, g_PlayerStressAmount[client]); - #endif - } - - // Process screen shake, if enabled. - if (g_IsPlayerShakeEnabled) - { - bool doShake = false; - - if (IsPlayerAlive(client)) - { - int staticMaster = NPCGetFromUniqueID(g_PlayerStaticMaster[client]); - if (staticMaster != -1 && NPCGetFlags(staticMaster) & SFF_HASVIEWSHAKE) - { - doShake = true; - } - } - - if (doShake) - { - float percent = g_PlayerStaticAmount[client]; - - float amplitudeMax = g_PlayerShakeAmplitudeMaxConVar.FloatValue; - float amplitude = amplitudeMax * percent; - - float frequencyMax = g_PlayerShakeFrequencyMaxConVar.FloatValue; - float frequency = frequencyMax * percent; - - UTIL_ClientScreenShake(client, amplitude, 0.5, frequency); - } - } - - if (g_LastVisibilityProcess[client] + 0.30 >= GetGameTime()) - { - return; - } - - /*if (!g_PlayerEliminated[client]) - { - CNavArea targetArea = SDK_GetLastKnownArea(client);//SDK_GetLastKnownArea(client) =/= g_lastNavArea[client], SDK_GetLastKnownArea() retrives the nav area stored by the server - if (targetArea != INVALID_NAV_AREA) - { - ClientNavAreaUpdate(client, g_lastNavArea[client], targetArea); - g_lastNavArea[client] = targetArea; - } - }*/ - - g_LastVisibilityProcess[client] = GetGameTime(); - - ClientProcessVisibility(client); -} - -Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &damage,int &damagetype,int &weapon, float damageForce[3], float damagePosition[3],int damagecustom) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - Action action = Plugin_Continue; - - float damage2 = damage; - Call_StartForward(g_OnClientTakeDamageFwd); - Call_PushCell(victim); - Call_PushCellRef(attacker); - Call_PushCellRef(inflictor); - Call_PushFloatRef(damage2); - Call_Finish(action); - - if (action == Plugin_Changed) - { - damage = damage2; - return Plugin_Changed; - } - - TFClassType class = TF2_GetPlayerClass(victim); - int classToInt = view_as(class); - - if (IsRoundInWarmup() && IsValidClient(attacker)) - { - float modelScale = GetEntPropFloat(attacker, Prop_Send, "m_flModelScale"); - float headScale = GetEntPropFloat(attacker, Prop_Send, "m_flHeadScale"); - float torsoScale = GetEntPropFloat(attacker, Prop_Send, "m_flTorsoScale"); - float handScale = GetEntPropFloat(attacker, Prop_Send, "m_flHandScale"); - if (modelScale < 1.0 || modelScale > 1.0 || headScale < 1.0 || headScale > 1.0 || torsoScale < 1.0 || torsoScale > 1.0 || handScale < 1.0 || handScale > 1.0) - { - damage = 0.0; //So how does it feel? - return Plugin_Changed; - } - } - - if (IsClientInKart(victim) && (attacker == -1 || inflictor == -1)) - { - damage = 0.0; - return Plugin_Changed; - } - - char inflictorClass[32]; - if (inflictor >= 0) - { - GetEdictClassname(inflictor, inflictorClass, sizeof(inflictorClass)); - } - - if (IsValidClient(attacker) && IsValidClient(victim) && g_PlayerProxy[attacker] && GetClientTeam(victim) == TFTeam_Red && TF2_IsPlayerInCondition(victim, TFCond_Gas)) - { - TF2_IgnitePlayer(victim, victim); - TF2_RemoveCondition(victim, TFCond_Gas); - } - - if (IsValidClient(attacker) && IsValidClient(victim) && IsClientInPvP(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) - { - damage = 0.0; - return Plugin_Changed; - } - - if (IsValidClient(attacker) && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && g_PlayerTrapped[victim]) - { - if (!g_PlayerEliminated[attacker] && !g_PlayerEliminated[victim]) - { - if (damagetype & 0x80) // 0x80 == melee damage - { - g_PlayerTrapped[victim] = false; - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); - } - } - } - - if (IsValidClient(attacker) && !g_PlayerEliminated[attacker] && !DidClientEscape(attacker) && class == TFClass_Soldier && !(GetEntityFlags(attacker) & FL_ONGROUND)) - { - int weaponEnt = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(weaponEnt)) - { - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - float zVelocity[3]; - GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); - if (itemDefInt == 416 && zVelocity[2] < 0.0 && weaponEnt == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) //A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. - { - damagetype |= DMG_ACID; - } - } - } - - if (IsEntityAProjectile(inflictor)) - { - int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); - if (npcIndex != -1) - { - bool attackEliminated = !!(NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS); - if (!attackEliminated && (GetClientTeam(victim) == TFTeam_Blue) && IsValidClient(victim) ) - { - damage = 0.0; - return Plugin_Changed; - } - } - } - - // Prevent telefrags - if ((damagetype & DMG_CRUSH) && damage > 500.0) - { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(attacker)); - if (Npc != SF2_INVALID_NPC && IsValidClient(victim)) - { - damage = 0.0; - Npc.UnSpawn(); - return Plugin_Changed; - } - } - - if (attacker != victim && IsValidClient(attacker)) - { - if (!IsRoundEnding()) - { - if (IsRoundInWarmup() || (IsClientInPvP(victim) && IsClientInPvP(attacker))) - { - if (attacker == inflictor) - { - if (IsValidEdict(weapon)) - { - char weaponClass[64]; - GetEdictClassname(weapon, weaponClass, sizeof(weaponClass)); - - // Backstab check! - if ((strcmp(weaponClass, "tf_weapon_knife") == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy") == 0)) && - (damagecustom != TF_CUSTOM_TAUNT_FENCING)) - { - float myPos[3], hisPos[3], myDirection[3]; - GetClientAbsOrigin(victim, myPos); - GetClientAbsOrigin(attacker, hisPos); - GetClientEyeAngles(victim, myDirection); - GetAngleVectors(myDirection, myDirection, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(myDirection, myDirection); - ScaleVector(myDirection, 32.0); - AddVectors(myDirection, myPos, myDirection); - - float p[3], s[3]; - MakeVectorFromPoints(myPos, hisPos, p); - MakeVectorFromPoints(myPos, myDirection, s); - if (GetVectorDotProduct(p, s) <= 0.0)//We can backstab him m8 - { - if (GetClientTeam(victim) == GetClientTeam(attacker) && class == TFClass_Sniper) - { - //look if the player has a razorback - int wearableEnt = INVALID_ENT_REFERENCE; - while ((wearableEnt = FindEntityByClassname(wearableEnt, "tf_wearable")) != -1) - { - if (GetEntPropEnt(wearableEnt, Prop_Send, "m_hOwnerEntity") == victim && GetEntProp(wearableEnt, Prop_Send, "m_iItemDefinitionIndex") == 57) - { - RemoveEntity(wearableEnt); - damage = 0.0; - EmitSoundToClient(victim, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - - SetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); - int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); - if (vm > MaxClients) - { - int meleeIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); - int anim = 41; - switch (meleeIndex) - { - case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: - { - anim = 15; - } - case 638: - { - anim = 31; - } - } - SetEntProp(vm, Prop_Send, "m_nSequence", anim); - } - return Plugin_Changed; - } - } - } - if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. - { - damage = 120.0; - if (damagetype & DMG_ACID) - { - damage = 120.0; - } - } - - if (g_WeaponCriticalsConVar != null && g_WeaponCriticalsConVar.BoolValue) - { - damagetype |= DMG_ACID; - } - - if (!IsClientCritUbercharged(victim)) - { - if (GetClientTeam(victim) == GetClientTeam(attacker)) - { - int pistol = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); - if (pistol > MaxClients && GetEntProp(pistol, Prop_Send, "m_iItemDefinitionIndex") == 525) //Give one crit fort the backstab - { - int crits = GetEntProp(attacker, Prop_Send, "m_iRevengeCrits"); - crits++; - SetEntProp(attacker, Prop_Send, "m_iRevengeCrits", crits); - } - } - if (GetEntProp(victim, Prop_Send, "m_iHealth") <= 120) - { - g_PlayerBackStabbed[victim] = true; - } - else - { - g_PlayerBackStabbed[victim] = false; - } - } - return Plugin_Changed; - } - } - } - } - } - else if (g_PlayerProxy[victim] || g_PlayerProxy[attacker]) - { - if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) - { - damage = 0.0; - return Plugin_Changed; - } - - if (attacker == victim)//Don't allow proxy to self regenerate control. - { - return Plugin_Continue; - } - - if (g_PlayerProxy[attacker]) - { - int maxHealth = SDKCall(g_SDKGetMaxHealth, victim); - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[attacker]); - if (master != -1) - { - int difficulty = GetLocalGlobalDifficulty(master); - if (damagecustom == TF_CUSTOM_TAUNT_GRAND_SLAM || - damagecustom == TF_CUSTOM_TAUNT_FENCING || - damagecustom == TF_CUSTOM_TAUNT_ARROW_STAB || - damagecustom == TF_CUSTOM_TAUNT_GRENADE || - damagecustom == TF_CUSTOM_TAUNT_BARBARIAN_SWING || - damagecustom == TF_CUSTOM_TAUNT_ENGINEER_ARM || - damagecustom == TF_CUSTOM_TAUNT_ARMAGEDDON) - { - if (damage >= float(maxHealth)) - { - damage = float(maxHealth) * 0.5; - } - else - { - damage = 0.0; - } - } - else if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. - { - damage = float(maxHealth) * g_SlenderProxyDamageVsBackstab[master][difficulty]; - if (damagetype & DMG_ACID) - { - damage /= 2.0; - } - } - - g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitEnemy[master][difficulty]; - if (g_PlayerProxyControl[attacker] > 100) - { - g_PlayerProxyControl[attacker] = 100; - } - - float originalPercentage = g_SlenderProxyDamageVsEnemy[master][difficulty]; - float additionPercentage = 0.15; - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - damage *= (originalPercentage + additionPercentage); - } - else - { - damage *= originalPercentage; - } - } - else - { - damage *= originalPercentage + g_ClassProxyDamageVulnerability[classToInt]; - } - } - return Plugin_Changed; - } - else if (g_PlayerProxy[victim]) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[victim]); - if (master != -1) - { - NPCGetProfile(master, profile, sizeof(profile)); - int difficulty = GetLocalGlobalDifficulty(master); - g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitByEnemy[master][difficulty]; - if (g_PlayerProxyControl[attacker] > 100) - { - g_PlayerProxyControl[attacker] = 100; - } - - damage *= g_SlenderProxyDamageVsSelf[master][difficulty]; - } - if (TF2_IsPlayerInCondition(victim, view_as(87))) - { - damage = 0.0; - } - if (damage * (damagetype & DMG_CRIT ? 3.0 : 1.0) >= float(GetClientHealth(victim)) && !TF2_IsPlayerInCondition(victim, view_as(87)))//The proxy is about to die - { - char buffer[PLATFORM_MAX_PATH]; - int classIndex = view_as(TF2_GetPlayerClass(victim)); - ArrayList deathAnims = GetBossProfileProxyDeathAnimations(profile); - if (deathAnims != null) - { - deathAnims.GetString(classIndex, buffer, sizeof(buffer)); - if (buffer[0] == '\0') - { - deathAnims.GetString(0, buffer, sizeof(buffer)); - } - if (buffer[0] != '\0') - { - g_ClientMaxFrameDeathAnim[victim]=GetBossProfileProxyDeathAnimFrames(profile, classIndex); - if (g_ClientMaxFrameDeathAnim[victim] == 0) - { - g_ClientMaxFrameDeathAnim[victim] = GetBossProfileProxyDeathAnimFrames(profile, 0); - } - if (g_ClientMaxFrameDeathAnim[victim] > 0) - { - // Cancel out any other taunts. - if (TF2_IsPlayerInCondition(victim, TFCond_Taunting)) - { - TF2_RemoveCondition(victim, TFCond_Taunting); - } - //The model has a death anim play it. - SDK_PlaySpecificSequence(victim,buffer); - g_ClientFrame[victim] = 0; - RequestFrame(ProxyDeathAnimation,victim); - TF2_AddCondition(victim, view_as(87), 5.0); - //Prevent death, and show the damage to the attacker. - TF2_AddCondition(victim, view_as(70), 0.5); - return Plugin_Changed; - } - } - } - - //the player has no death anim leave him die. - } - return Plugin_Changed; - } - } - else - { - damage = 0.0; - return Plugin_Changed; - } - } - else - { - if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) - { - damage = 0.0; - return Plugin_Changed; - } - } - - if (IsClientInGhostMode(victim)) - { - damage = 0.0; - return Plugin_Changed; - } - } - - return Plugin_Continue; -} - -Action Timer_ClientSprinting(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerSprintTimer[client]) - { - return Plugin_Stop; - } - - if (!IsClientSprinting(client)) - { - return Plugin_Stop; - } - - if (g_PlayerSprintPoints[client] <= 0) - { - ClientStopSprint(client); - g_PlayerSprintPoints[client] = 0; - return Plugin_Stop; - } - - if (IsClientReallySprinting(client)) - { - int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; - if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0) - { - g_PlayerSprintPoints[client]--; - } - } - - ClientSprintTimer(client); - - return Plugin_Stop; -} - -void Hook_ClientSprintingPreThink(int client) -{ - if (!IsClientReallySprinting(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - return; - } - - int fov = GetEntData(client, g_PlayerDefaultFOVOffset); - - int targetFov = g_PlayerDesiredFOV[client] + 10; - - if (fov < targetFov) - { - int diff = RoundFloat(FloatAbs(float(fov - targetFov))); - if (diff >= 1) - { - ClientSetFOV(client, fov + 1); - } - else - { - ClientSetFOV(client, targetFov); - } - } - else if (fov >= targetFov) - { - ClientSetFOV(client, targetFov); - //SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - } -} - -void Hook_ClientRechargeSprintPreThink(int client) -{ - if (IsClientReallySprinting(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - return; - } - - int fov = GetEntData(client, g_PlayerDefaultFOVOffset); - if (fov > g_PlayerDesiredFOV[client]) - { - int diff = RoundFloat(FloatAbs(float(fov - g_PlayerDesiredFOV[client]))); - if (diff >= 1) - { - ClientSetFOV(client, fov - 1); - } - else - { - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } - } - else if (fov <= g_PlayerDesiredFOV[client]) - { - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } -} - -Action Timer_ClientRechargeSprint(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - float velSpeed[3]; - GetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", velSpeed); - float speed = GetVectorLength(velSpeed, true); - - if (timer != g_PlayerSprintTimer[client]) - { - return Plugin_Stop; - } - - if (IsClientSprinting(client)) - { - g_PlayerSprintTimer[client] = null; - return Plugin_Stop; - } - - if (g_PlayerSprintPoints[client] >= 100) - { - g_PlayerSprintPoints[client] = 100; - g_PlayerSprintTimer[client] = null; - return Plugin_Stop; - } - if ((!GetEntProp(client, Prop_Send, "m_bDucking") && !GetEntProp(client, Prop_Send, "m_bDucked")) || (GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked") && IsClientReallySprinting(client) || speed > 0.0)) - { - g_PlayerSprintPoints[client]++; - } - else if ((GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked")) && !IsClientReallySprinting(client) && speed == 0.0) - { - if (!SF_SpecialRound(SPECIALROUND_COFFEE)) - { - g_PlayerSprintPoints[client] += 2; - } - else - { - g_PlayerSprintPoints[client] += 1; - } - } - ClientSprintTimer(client, true); - return Plugin_Stop; -} - -void ClientOnButtonPress(int client,int button) -{ - switch (button) - { - case IN_ATTACK2: - { - if (IsPlayerAlive(client)) - { - if (!IsRoundInWarmup() && - !IsRoundInIntro() && - !IsRoundEnding() && - !DidClientEscape(client)) - { - if (GetGameTime() >= ClientGetFlashlightNextInputTime(client)) - { - ClientHandleFlashlight(client); - } - } - } - } - case IN_ATTACK3: - { - ClientHandleSprint(client, true); - } - case IN_RELOAD: - { - if (IsPlayerAlive(client)) - { - if (!g_PlayerEliminated[client]) - { - if (!IsRoundEnding() && - !IsRoundInWarmup() && - !IsRoundInIntro() && - !DidClientEscape(client)) - { - g_PlayerHoldingBlink[client] = true; - ClientBlink(client); - } - } - } - } - case IN_JUMP: - { - if (IsPlayerAlive(client) && !(GetEntityFlags(client) & FL_FROZEN)) - { - if (!(GetEntProp(client, Prop_Send, "m_bDucked")) && - (GetEntityFlags(client) & FL_ONGROUND) && - GetEntProp(client, Prop_Send, "m_nWaterLevel") < 2) - { - ClientOnJump(client); - } - } - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 0.0001); - } - else - { - SetEntityGravity(client, 1.0); - } - } - case IN_DUCK: - { - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 4.0); - } - else - { - SetEntityGravity(client, 1.0); - } - } - } -} - -void ClientOnButtonRelease(int client,int button) -{ - switch (button) - { - case IN_ATTACK3: - { - ClientHandleSprint(client, false); - } - case IN_DUCK: - { - ClientEndPeeking(client); - - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 0.5); - } - else - { - SetEntityGravity(client, 1.0); - } - } - case IN_JUMP: - { - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 0.5); - } - else - { - SetEntityGravity(client, 1.0); - } - } - case IN_RELOAD: - { - if (IsPlayerAlive(client)) - { - if (!g_PlayerEliminated[client]) - { - if (!IsRoundEnding() && - !IsRoundInWarmup() && - !IsRoundInIntro() && - !DidClientEscape(client)) - { - g_PlayerHoldingBlink[client] = false; - } - } - } - } - } -} - -void ClientOnJump(int client) -{ - if (!g_PlayerEliminated[client]) - { - if (!IsRoundEnding() && !IsRoundInWarmup() && !DidClientEscape(client)) - { - int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; - if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0 && !g_PlayerTrapped[client]) - { - if (g_PlayerSprintPoints[client] >= 2) - { - TFClassType classType = TF2_GetPlayerClass(client); - int classToInt = view_as(classType); - if (!IsClassConfigsValid()) - { - if (classType != TFClass_Soldier || g_PlayerSprintPoints[client] <= 10 || IsClientSprinting(client)) - { - g_PlayerSprintPoints[client] -= 7; - } - } - else - { - int sprintPointsLoss = g_ClassSprintPointLossJumping[classToInt]; - if (g_PlayerSprintPoints[client] <= 10 || IsClientSprinting(client)) - { - sprintPointsLoss = 7; - } - g_PlayerSprintPoints[client] -= sprintPointsLoss; - } - if (g_PlayerSprintPoints[client] <= 0) - { - g_PlayerSprintPoints[client] = 0; - } - } - } - - if (!IsClientSprinting(client)) - { - if (g_PlayerSprintTimer[client] == null) - { - // If the player hasn't sprinted recently, force us to regenerate the stamina. - ClientSprintTimer(client, true); - } - } - if (g_PlayerTrapped[client]) - { - g_PlayerTrapCount[client] -= 1; - } - if (g_PlayerTrapped[client] && g_PlayerTrapCount[client] <= 1) - { - g_PlayerTrapped[client] = false; - g_PlayerTrapCount[client] = 0; - } - } - } -} - -Action Timer_GhostModeConnectionCheck(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerGhostModeConnectionCheckTimer[client]) - { - return Plugin_Stop; - } - - if (!IsFakeClient(client) && IsClientTimingOut(client)) - { - float bootTime = g_PlayerGhostModeConnectionBootTime[client]; - bool checkBool = g_GhostModeConnectionConVar.BoolValue; - if (bootTime < 0.0 && !checkBool) - { - bootTime = GetGameTime() + g_GhostModeConnectionToleranceConVar.FloatValue; - g_PlayerGhostModeConnectionBootTime[client] = bootTime; - g_PlayerGhostModeConnectionTimeOutTime[client] = GetGameTime(); - } - - if (GetGameTime() >= bootTime || checkBool) - { - ClientSetGhostModeState(client, false); - TF2_RespawnPlayer(client); - - char authString[128]; - GetClientAuthId(client,AuthId_Engine, authString, sizeof(authString)); - - LogSF2Message("Removed %N (%s) from ghost mode due to timing out for %f seconds", client, authString, g_GhostModeConnectionToleranceConVar.FloatValue); - - float timeOutTime = g_PlayerGhostModeConnectionTimeOutTime[client]; - CPrintToChat(client, "\x08FF4040FF%T", "SF2 Ghost Mode Bad Connection", client, RoundFloat(bootTime - timeOutTime)); - - return Plugin_Stop; - } - } - else - { - // Player regained connection; reset. - g_PlayerGhostModeConnectionBootTime[client] = -1.0; - } - - return Plugin_Continue; -} - -Action Timer_ClientCheckCamp(Handle timer, any userid) -{ - if (IsRoundInWarmup()) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerCampingTimer[client]) - { - return Plugin_Stop; - } - - if (IsRoundEnding() || !IsPlayerAlive(client) || g_PlayerEliminated[client] || DidClientEscape(client)) - { - return Plugin_Stop; - } - - if (!g_IsPlayerCampingFirstTime[client]) - { - bool isCamping = false; - float pos[3], maxs[3], mins[3]; - GetClientAbsOrigin(client, pos); - GetEntPropVector(client, Prop_Send, "m_vecMins", mins); - GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); - - // Only do something if the player is NOT stuck. - float distFromLastPosition = GetVectorSquareMagnitude(g_PlayerCampingLastPosition[client], pos); - float distFromClosestBoss = 9999999.0; - int closestBoss = -1; - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - int slender = NPCGetEntIndex(i); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - continue; - } - - float slenderPos[3]; - SlenderGetAbsOrigin(i, slenderPos); - - float flDist = GetVectorSquareMagnitude(slenderPos, pos); - if (flDist < distFromClosestBoss) - { - closestBoss = i; - distFromClosestBoss = flDist; - } - } - /*if (IsSpaceOccupiedIgnorePlayers(pos, mins, maxs, client)) - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is stuck, no actions taken", client, client);*/ - if (!SF_IsBoxingMap() && g_CampingEnabledConVar.BoolValue && - IsRoundPlaying() && - g_PlayerStaticAmount[client] <= g_CampingNoStrikeSanityConVar.FloatValue && - (closestBoss == -1 || distFromClosestBoss >= g_CampingNoStrikeBossDistanceConVar.FloatValue) && - distFromLastPosition <= SquareFloat(g_CampingMinDistanceConVar.FloatValue)) - { - isCamping = true; - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk, or camping", client, client); - } - if (isCamping) - { - g_PlayerCampingStrikes[client]++; - if (g_PlayerCampingStrikes[client] < g_CampingMaxStrikesConVar.IntValue) - { - if (g_PlayerCampingStrikes[client] >= g_CampingStrikesWarnConVar.IntValue) - { - CPrintToChat(client, "{red}%T", "SF2 Camping System Warning", client, (g_CampingMaxStrikesConVar.IntValue - g_PlayerCampingStrikes[client]) * 5); - } - } - else - { - g_PlayerCampingStrikes[client] = 0; - ClientStartDeathCam(client, 0, pos, true); - } - } - else - { - // Forgiveness. - if (g_PlayerCampingStrikes[client] > 0) - { - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is forgiven of one strike.", client, client); - g_PlayerCampingStrikes[client]--; - } - } - - g_PlayerCampingLastPosition[client][0] = pos[0]; - g_PlayerCampingLastPosition[client][1] = pos[1]; - g_PlayerCampingLastPosition[client][2] = pos[2]; - } - else - { - g_IsPlayerCampingFirstTime[client] = false; - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk/camping for the 1st time since the reset, don't take any actions for now.....", client, client); - } - - return Plugin_Continue; -} - -#define SF2_PLAYER_HUD_BLINK_SYMBOL_ON "O" -#define SF2_PLAYER_HUD_BLINK_SYMBOL_OFF "Ɵ" -#define SF2_PLAYER_HUD_BLINK_SYMBOL_OLD "B" -#define SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL "ϟ" -#define SF2_PLAYER_HUD_BAR_SYMBOL "█" -#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL "░" -#define SF2_PLAYER_HUD_BAR_SYMBOL_OLD "|" -#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD "" -#define SF2_PLAYER_HUD_INFINITY_SYMBOL "∞" -#define SF2_PLAYER_HUD_SPRINT_SYMBOL "»" - -Action Timer_ClientAverageUpdate(Handle timer) -{ - if (timer != g_ClientAverageUpdateTimer) - { - return Plugin_Stop; - } - - if (!g_Enabled) - { - return Plugin_Stop; - } - - if (IsRoundInWarmup() || IsRoundEnding()) - { - return Plugin_Continue; - } - - // First, process through HUD stuff. - char buffer[256]; - - static int hudColorHealthy[3]; - static int hudColorCritical[3] = { 255, 10, 10 }; - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i)) - { - continue; - } - if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) - { - hudColorHealthy = { 50, 255, 50 }; - } - else - { - hudColorHealthy = { 150, 255, 150 }; - } - - if (IsPlayerAlive(i) && !IsClientInDeathCam(i)) - { - if (!g_PlayerEliminated[i]) - { - if (DidClientEscape(i)) - { - continue; - } - - int maxBars = 12; - int bars; - if (!SF_IsRaidMap() && !SF_IsBoxingMap()) - { - bars = RoundToCeil(float(maxBars) * ClientGetBlinkMeter(i)); - if (bars > maxBars) - { - bars = maxBars; - } - - if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) - { - if (bars != 0) - { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_ON); - } - else - { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OFF); - } - } - else - { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OLD); - } - - if (IsInfiniteBlinkEnabled()) - { - StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); - } - else - { - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - } - } - if (!SF_SpecialRound(SPECIALROUND_LIGHTSOUT) && !SF_IsRaidMap() && !SF_IsBoxingMap()) - { - bars = RoundToCeil(float(maxBars) * ClientGetFlashlightBatteryLife(i)); - if (bars > maxBars) - { - bars = maxBars; - } - - char buffer2[64]; - FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL); - StrCat(buffer, sizeof(buffer), buffer2); - - if (IsInfiniteFlashlightEnabled()) - { - StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); - } - else - { - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - } - } - - bars = RoundToCeil(float(maxBars) * (float(ClientGetSprintPoints(i)) / 100.0)); - if (bars > maxBars) - { - bars = maxBars; - } - - char buffer2[64]; - FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_SPRINT_SYMBOL); - StrCat(buffer, sizeof(buffer), buffer2); - - if (IsInfiniteSprintEnabled()) - { - StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); - } - else - { - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - } - - float healthRatio = float(GetEntProp(i, Prop_Send, "m_iHealth")) / float(SDKCall(g_SDKGetMaxHealth, i)); - - int color[3]; - for (int i2 = 0; i2 < 3; i2++) - { - color[i2] = RoundFloat(float(hudColorHealthy[i2]) + (float(hudColorCritical[i2] - hudColorHealthy[i2]) * (1.0 - healthRatio))); - } - if (!SF_IsRaidMap() && !SF_IsBoxingMap()) - { - SetHudTextParams(0.035, 0.83, - 0.3, - color[0], - color[1], - color[2], - 40, - _, - 1.0, - 0.07, - 0.5); - } - else if (SF_IsRaidMap() || SF_IsBoxingMap()) - { - SetHudTextParams(0.035, 0.43, - 0.3, - color[0], - color[1], - color[2], - 40, - _, - 1.0, - 0.07, - 0.5); - } - ShowSyncHudText(i, g_HudSync2, buffer); - buffer[0] = '\0'; - } - else - { - if (g_PlayerProxy[i]) - { - int maxBars = 12; - int bars = RoundToCeil(float(maxBars) * (float(g_PlayerProxyControl[i]) / 100.0)); - if (bars > maxBars) - { - bars = maxBars; - } - - strcopy(buffer, sizeof(buffer), "CONTROL\n"); - - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - - SetHudTextParams(-1.0, 0.83, - 0.3, - SF2_HUD_TEXT_COLOR_R, - SF2_HUD_TEXT_COLOR_G, - SF2_HUD_TEXT_COLOR_B, - 40, - _, - 1.0, - 0.07, - 0.5); - ShowSyncHudText(i, g_HudSync2, buffer); - } - } - } - ClientUpdateListeningFlags(i); - ClientUpdateMusicSystem(i); - } - - return Plugin_Continue; -} diff --git a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp similarity index 70% rename from addons/sourcemod/scripting/sf2/extras/renevant_mode.sp rename to addons/sourcemod/scripting/sf2/gamemodes/renevant.sp index 260133d7..e0571c45 100644 --- a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp +++ b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp @@ -5,31 +5,73 @@ #pragma semicolon 1 -bool SF_IsRenevantMap() +static GlobalForward g_OnRenevantTriggerWaveFwd; + +void SetupRenevantMode() { - return !!(g_IsRenevantMap || (g_RenevantMapConVar.IntValue == 1)); + g_OnRoundEndPFwd.AddFunction(null, OnRoundEnd); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); } -static bool Renevant_TryAddBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], int profileLen, char[] name, int nameLen, bool playSpawnSound=true, bool invincible = false) +void Renevant_InitializeAPI() { - if (!GetRandomRenevantBossProfile(profile, profileLen)) + g_OnRenevantTriggerWaveFwd = new GlobalForward("SF2_OnRenevantWaveTrigger", ET_Ignore, Param_Cell, Param_Cell); + + CreateNative("SF2_IsRenevantMap", Native_IsRenevantMap); +} + +static void OnRoundEnd() +{ + if (SF_IsRenevantMap() && g_RenevantWaveTimer != null) { - return false; + KillTimer(g_RenevantWaveTimer); } +} - NPCGetBossName(_, name, nameLen, profile); - if (name[0] == '\0') +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) { - strcopy(name, nameLen, profile); + return; } - AddProfile(profile, _, _, _, playSpawnSound, invincible); - return true; + if (fake) + { + return; + } + + if (g_RenevantMultiEffect) + { + CreateTimer(0.1, Timer_ReplacePlayerRagdoll, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (SF_IsRenevantMap() && g_RenevantMarkForDeath && !client.HasEscaped) + { + client.ChangeCondition(TFCond_MarkedForDeathSilent, _, -1.0); + } } -static bool Renevant_TryAddSingleBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], char[] name, int nameLen, bool playSpawnSound=true, bool invincible = false) +static void OnPlayerEscape(SF2_BasePlayer client) { - if (!IsProfileValid(profile)) + if (SF_IsRenevantMap() && g_RenevantMarkForDeath) + { + client.ChangeCondition(TFCond_MarkedForDeathSilent, true); + } +} + +bool SF_IsRenevantMap() +{ + return (g_IsRenevantMap || (g_RenevantMapConVar.IntValue == 1)); +} + +static bool Renevant_TryAddBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], int profileLen, char[] name, int nameLen, bool playSpawnSound = true) +{ + if (!GetRandomRenevantBossProfile(profile, profileLen)) { return false; } @@ -39,11 +81,12 @@ static bool Renevant_TryAddSingleBossProfile(char profile[SF2_MAX_PROFILE_NAME_L { strcopy(name, nameLen, profile); } - AddProfile(profile, _, _, _, playSpawnSound, invincible); + AddProfile(profile, _, _, _, playSpawnSound); return true; } + static void ShowRenevantMessageToClient(int client, const char[] message, int params, any ...) { char messageDisplay[512]; @@ -69,7 +112,7 @@ static void Renevant_BroadcastMessage(const char[] message, int params, ...) for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i) || !IsClientInGame(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i)) || !IsPlayerAlive(i)) + if (!IsValidClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i)) || !IsPlayerAlive(i)) { continue; } @@ -78,22 +121,6 @@ static void Renevant_BroadcastMessage(const char[] message, int params, ...) } } -enum RenevantWave -{ - RenevantWave_Normal = 0, - RenevantWave_IncreaseDifficulty, - RenevantWave_MultiEffect, - RenevantWave_BaconSpray, - RenevantWave_DoubleTrouble, - RenevantWave_DoomBox, - RenevantWave_90s, - RenevantWave_MarkForDeath, - RenevantWave_SingleBoss, - RenevantWave_AdminBoss, - RenevantWave_WallHax, - RenevantWave_Max -} - static void Renevant_DoWaveAction(RenevantWave action) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH], buffer2[SF2_MAX_PROFILE_NAME_LENGTH], buffer3[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -112,7 +139,7 @@ static void Renevant_DoWaveAction(RenevantWave action) if (addedBossCount == 1 && action != RenevantWave_DoubleTrouble && action != RenevantWave_DoomBox && action != RenevantWave_SingleBoss && action != RenevantWave_AdminBoss) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s","SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } @@ -120,6 +147,7 @@ static void Renevant_DoWaveAction(RenevantWave action) { case RenevantWave_Normal: { + // Do nothing } case RenevantWave_IncreaseDifficulty: { @@ -127,30 +155,30 @@ static void Renevant_DoWaveAction(RenevantWave action) { case Difficulty_Normal: { - g_DifficultyConVar.IntValue = Difficulty_Hard; + SetDifficulty(Difficulty_Hard); - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nDifficulty set to: %t", "SF2 Hard Difficulty"); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Hard Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Hard: { - g_DifficultyConVar.IntValue = Difficulty_Insane; + SetDifficulty(Difficulty_Insane); - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nDifficulty set to: %t", "SF2 Insane Difficulty"); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Insane Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Insane: { - g_DifficultyConVar.IntValue = Difficulty_Nightmare; + SetDifficulty(Difficulty_Nightmare); - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nDifficulty set to: %t", "SF2 Nightmare Difficulty"); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Nightmare Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Nightmare: { - g_DifficultyConVar.IntValue = Difficulty_Apollyon; + SetDifficulty(Difficulty_Apollyon); - g_BossesChaseEndlessly = true; + g_RenevantBossesChaseEndlessly = true; g_IsRoundInfiniteSprint = true; char nightmareDisplay[256]; @@ -159,10 +187,7 @@ static void Renevant_DoWaveAction(RenevantWave action) CPrintToChatAll("The difficulty has been set to: {darkgray}%t{default}!", "SF2 Apollyon Difficulty"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); @@ -218,13 +243,20 @@ static void Renevant_DoWaveAction(RenevantWave action) { g_RenevantWaveList.Erase(eraseWave); } + + eraseWave = g_RenevantWaveList.FindValue(RenevantWave_IncreaseDifficulty); + if (eraseWave != -1) + { + g_RenevantWaveList.Erase(eraseWave); + } } } } case RenevantWave_MultiEffect: { g_RenevantMultiEffect = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nBosses can now inflict Multieffect."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Multieffects"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_MultiEffect); if (eraseWave != -1) { @@ -234,7 +266,8 @@ static void Renevant_DoWaveAction(RenevantWave action) case RenevantWave_BaconSpray: { g_RenevantBeaconEffect = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nBosses are now alerted on spawn."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Bacon"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_BaconSpray); if (eraseWave != -1) { @@ -250,12 +283,12 @@ static void Renevant_DoWaveAction(RenevantWave action) if (addedBossCount == 1) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s","SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } else if (addedBossCount == 2) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBosses: %s and %s", name, name2); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s and %s","SF2 Bosses Added", name, name2); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } } @@ -272,17 +305,17 @@ static void Renevant_DoWaveAction(RenevantWave action) if (addedBossCount == 1) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s","SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } else if (addedBossCount == 2) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBosses: %s and %s", name, name2); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s and %s","SF2 Bosses Added", name, name2); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } else if (addedBossCount == 3) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBosses: %s, %s, and %s", name, name2, name3); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s, %s, and %s","SF2 Bosses Added", name, name2, name3); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_DoomBox); @@ -294,7 +327,8 @@ static void Renevant_DoWaveAction(RenevantWave action) case RenevantWave_90s: { g_Renevant90sEffect = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nYou feel very nervous."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant 90s"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_90s); if (eraseWave != -1) { @@ -310,7 +344,7 @@ static void Renevant_DoWaveAction(RenevantWave action) { continue; } - if (!IsClientInGame(client) || + if (!IsValidClient(client) || !IsPlayerAlive(client) || g_PlayerEliminated[client] || IsClientInGhostMode(client) || @@ -320,7 +354,8 @@ static void Renevant_DoWaveAction(RenevantWave action) } TF2_AddCondition(client, TFCond_MarkedForDeathSilent, -1.0); } - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nEveryone is marked for death permanently."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Marked"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_MarkForDeath); if (eraseWave != -1) { @@ -331,7 +366,7 @@ static void Renevant_DoWaveAction(RenevantWave action) { char bufferSingle[SF2_MAX_PROFILE_NAME_LENGTH], singleBossName[SF2_MAX_NAME_LENGTH]; g_DefaultRenevantBossConVar.GetString(bufferSingle, sizeof(bufferSingle)); - if (Renevant_TryAddSingleBossProfile(bufferSingle, singleBossName, sizeof(singleBossName), _, true)) + if (Renevant_TryAddBossProfile(bufferSingle, sizeof(bufferSingle), singleBossName, sizeof(singleBossName))) { addedBossCount++; } @@ -345,7 +380,7 @@ static void Renevant_DoWaveAction(RenevantWave action) } case RenevantWave_AdminBoss: { - ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList().Clone(); + ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -357,15 +392,25 @@ static void Renevant_DoWaveAction(RenevantWave action) { strcopy(name, sizeof(name), buffer); } - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s", "SF2 Admin Boss", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); + char nightmareDisplay[256]; + + for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) + { + EmitSoundToAll(g_SoundNightmareMode[i]); + } + + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%s", name); + + SpecialRoundGameText(nightmareDisplay, "d_purgatory"); } } else { if (Renevant_TryAddBossProfile(buffer, sizeof(buffer), name, sizeof(name), false)) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s", "SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } } @@ -374,12 +419,12 @@ static void Renevant_DoWaveAction(RenevantWave action) { g_RenevantWaveList.Erase(eraseWave); } - delete selectableBosses; } case RenevantWave_WallHax: { g_RenevantWallHax = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nYou can now see players and bosses through walls."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Wall Hax"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_WallHax); if (eraseWave != -1) { @@ -411,11 +456,18 @@ void Renevant_SetWave(int wave, bool resetTimer = false) g_RenevantWaveTimer = null; // At zero/max wave so stop it. } + if (wave == 0) + { + return; + } + + RenevantWave randomWave = RenevantWave_Normal; + switch (g_RenevantWaveNumber) { case 1: //Wave 1 { - g_DifficultyConVar.IntValue = Difficulty_Normal; + SetDifficulty(Difficulty_Normal); Renevant_DoWaveAction(RenevantWave_Normal); @@ -435,7 +487,7 @@ void Renevant_SetWave(int wave, bool resetTimer = false) g_RenevantWaveList.Erase(eraseWave); } } - ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList().Clone(); + ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList(); if (selectableBosses.Length <= 0) { int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_AdminBoss); @@ -444,12 +496,15 @@ void Renevant_SetWave(int wave, bool resetTimer = false) g_RenevantWaveList.Erase(eraseWave); } } - delete selectableBosses; + } + case 0: + { + // Do nothing } default: //Waves except 1 { - int randomWave = g_RenevantWaveList.Get(GetRandomInt(0, g_RenevantWaveList.Length - 1)); - Renevant_DoWaveAction(view_as(randomWave)); + randomWave = view_as(g_RenevantWaveList.Get(GetRandomInt(0, g_RenevantWaveList.Length - 1))); + Renevant_DoWaveAction(randomWave); } } @@ -457,12 +512,23 @@ void Renevant_SetWave(int wave, bool resetTimer = false) Call_StartForward(g_OnRenevantTriggerWaveFwd); Call_PushCell(g_RenevantWaveNumber); + Call_PushCell(randomWave); + Call_Finish(); + + Call_StartForward(g_OnRenevantTriggerWavePFwd); + Call_PushCell(g_RenevantWaveNumber); + Call_PushCell(randomWave); Call_Finish(); } static Action Timer_RenevantWave(Handle timer, any data) { - if (timer != g_RenevantWaveTimer || IsRoundEnding()) + if (timer != g_RenevantWaveTimer) + { + return Plugin_Stop; + } + + if (!IsRoundInEscapeObjective()) { return Plugin_Stop; } @@ -494,17 +560,17 @@ static void Renevant_SpawnApollyon() ent = -1; if (spawnPoint.Length > 0) { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); + ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); } delete spawnPoint; - if (ent > MaxClients) + if (IsValidEntity(ent)) { GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - SF2NPC_BaseNPC Npc = view_as(npcIndex); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); if (!Npc.IsValid()) { continue; @@ -513,13 +579,13 @@ static void Renevant_SpawnApollyon() { continue; } - Npc.UnSpawn(); - SpawnSlender(Npc, teleportPos); + Npc.UnSpawn(true); + Npc.Spawn(teleportPos); } } } -int Native_IsRenevantMap(Handle plugin, int numParams) +static int Native_IsRenevantMap(Handle plugin, int numParams) { return view_as(SF_IsRenevantMap()); } diff --git a/addons/sourcemod/scripting/sf2/glow.sp b/addons/sourcemod/scripting/sf2/glow.sp new file mode 100644 index 00000000..311d9290 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/glow.sp @@ -0,0 +1,391 @@ +enum struct SF2GlowData +{ + int Ref; + int Color[4]; + bool VisibleTo[MAXTF2PLAYERS]; + + void Init() + { + this.Ref = INVALID_ENT_REFERENCE; + for (int i = 0; i < 4; i++) + { + this.Color[i] = 255; + } + + for (int i = 1; i <= MaxClients; i++) + { + this.VisibleTo[i] = false; + } + } + + bool IsVisibleTo(int client) + { + return this.VisibleTo[client]; + } + + void SetVisibleTo(int client, bool state) + { + this.VisibleTo[client] = state; + } +} + +static StringMap g_GlowMap; + +void SetupGlows() +{ + g_GlowMap = new StringMap(); + + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); + g_OnMapEndPFwd.AddFunction(null, MapEnd); + + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); +} + +static void OnGamemodeEnd() +{ + StringMapSnapshot snapshot = g_GlowMap.Snapshot(); + + SF2GlowData data; + char value[32]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, value, sizeof(value)); + + if (!g_GlowMap.GetArray(value, data, sizeof(data))) + { + continue; + } + + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (entity.IsValid()) + { + RemoveEntity(entity.index); + } + } + + delete snapshot; +} + +static void MapEnd() +{ + g_GlowMap.Clear(); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + StringMapSnapshot snapshot = g_GlowMap.Snapshot(); + + SF2GlowData data; + char value[32]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, value, sizeof(value)); + + if (!g_GlowMap.GetArray(value, data, sizeof(data))) + { + continue; + } + + data.SetVisibleTo(client.index, false); + + g_GlowMap.SetArray(value, data, sizeof(data)); + } + + delete snapshot; +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + DestroyGlowEntity(ent.index); +} + +bool DoesEntityHaveGlow(int ent) +{ + SF2GlowData data; + return GetGlowData(ent, data); +} + +static bool GetGlowData(int ent, SF2GlowData data, bool create = false) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + + if (g_GlowMap.GetArray(key, data, sizeof(data))) + { + return true; + } + + if (create) + { + data.Init(); + + if (g_GlowMap.SetArray(key, data, sizeof(data))) + { + return true; + } + } + + return false; +} + +static bool SetGlowData(int ent, const SF2GlowData data) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + + return g_GlowMap.SetArray(key, data, sizeof(data)); +} + +static bool RemoveGlowData(int ent) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + + return g_GlowMap.Remove(key); +} + +void CreateGlowEntity(int ent, const int color[4] = { 255, 255, 255, 255 }, bool preserveColor = false) +{ + if (!g_Enabled) + { + return; + } + + if (!IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data, true)) + { + return; + } + + if (!preserveColor) + { + for (int i = 0; i < 4; i++) + { + data.Color[i] = color[i]; + } + } + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + RemoveEntity(glow.index); + } + + glow = CBaseEntity(TF2_CreateGlow(ent)); + SDKHook(glow.index, SDKHook_SetTransmit, SetTransmit); + data.Ref = EnsureEntRef(glow.index); + SetEntityTransmitState(glow.index, FL_EDICT_FULLCHECK); + + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow.index, UpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, glow.index, ShouldTransmit); + + SetVariantColor(data.Color); + glow.AcceptInput("SetGlowColor"); + glow.AcceptInput("Enable"); + + SetGlowData(ent, data); +} + +int CreateGlowEntityDataless(int ent, const int color[4] = { 255, 255, 255, 255 }) +{ + CBaseEntity glow = CBaseEntity(TF2_CreateGlow(ent)); + SetEntityTransmitState(glow.index, FL_EDICT_FULLCHECK); + + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow.index, UpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, glow.index, ShouldTransmit); + + SetVariantColor(color); + glow.AcceptInput("SetGlowColor"); + glow.AcceptInput("Enable"); + + return glow.index; +} + +void DestroyGlowEntity(int ent) +{ + if (!IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return; + } + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + RemoveEntity(glow.index); + } + + RemoveGlowData(ent); +} + +void SetGlowVisibility(int ent, const int[] clients, int numClients, const bool[] visibleStates) +{ + if (numClients == 0 || !IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return; + } + + bool update = false; + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + + if (!glow.IsValid()) + { + update = true; + } + + for (int i = 0; i < numClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(clients[i]); + + bool oldVisible = data.IsVisibleTo(player.index); + bool visible = visibleStates[i]; + if (oldVisible == visible) + { + continue; + } + + data.SetVisibleTo(player.index, visible); + + if (!update) + { + if (!visible && player.IsValid && !player.IsBot) + { + update = true; + } + } + } + + SetGlowData(ent, data); + + if (update) + { + CreateGlowEntity(ent, .preserveColor = true); + } +} + +void SetGlowVisibilityForClient(int ent, int client, bool visible) +{ + int clients[1]; + clients[0] = client; + bool visibleStates[1]; + visibleStates[0] = visible; + + SetGlowVisibility(ent, clients, 1, visibleStates); +} + +bool SetGlowColor(int ent, const int color[4]) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return false; + } + + for (int i = 0; i < 4; i++) + { + data.Color[i] = color[i]; + } + + SetGlowData(ent, data); + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + SetVariantColor(color); + glow.AcceptInput("SetGlowColor"); + } + + return true; +} + +void SetGlowModel(int ent, const char[] model) +{ + if (!IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return; + } + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + glow.SetModel(model); + } +} + +static Action SetTransmit(int ent, int other) +{ + CBaseEntity glow = CBaseEntity(ent); + CBaseEntity target = CBaseEntity(glow.GetPropEnt(Prop_Send, "m_hTarget")); + if (!target.IsValid()) + { + return Plugin_Handled; + } + + SF2GlowData data; + if (!GetGlowData(target.index, data)) + { + return Plugin_Handled; + } + + if (glow.GetProp(Prop_Send, "m_bDisabled")) + { + return Plugin_Continue; + } + + return data.IsVisibleTo(other) ? Plugin_Continue : Plugin_Handled; +} + +static MRESReturn UpdateTransmitState(int glow, DHookReturn returnHook) +{ + returnHook.Value = SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn ShouldTransmit(int glow, DHookReturn returnHook, DHookParam param) +{ + returnHook.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} diff --git a/addons/sourcemod/scripting/sf2/mapentities.sp b/addons/sourcemod/scripting/sf2/mapentities.sp index 203f354e..dd6edeee 100644 --- a/addons/sourcemod/scripting/sf2/mapentities.sp +++ b/addons/sourcemod/scripting/sf2/mapentities.sp @@ -7,10 +7,10 @@ //#define DEBUG_MAPENTITIES -static PrivateForward g_CustomEntityOnMapStart; -static PrivateForward g_CustomEntityOnRoundStateChanged; -static PrivateForward g_CustomEntityOnDifficultyChanged; -static PrivateForward g_CustomEntityOnPageCountChanged; +static PrivateForward g_CustomEntityOnMapStartPFwd; +static PrivateForward g_CustomEntityOnRoundStateChangedPFwd; +static PrivateForward g_CustomEntityOnDifficultyChangedPFwd; +static PrivateForward g_CustomEntityOnPageCountChangedPFwd; static PrivateForward g_CustomEntityOnRenevantWaveTriggered; @@ -34,13 +34,20 @@ methodmap SF2TriggerMapEntity < CBaseEntity public bool PassesTriggerFilters(int entity) { - return !!SDKCall(g_SDKPassesTriggerFilters, this.index, entity); + return SDKCall(g_SDKPassesTriggerFilters, this.index, entity) != 0; } property bool IsDisabled { - public get() { return !!this.GetProp(Prop_Data, "m_bDisabled"); } - public set(bool value) { this.SetProp(Prop_Data, "m_bDisabled", value); } + public get() + { + return this.GetProp(Prop_Data, "m_bDisabled") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_bDisabled", value); + } } } @@ -60,19 +67,19 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) { case SF2MapEntityHook_OnMapStart: { - g_CustomEntityOnMapStart.AddFunction(null, hookFunc); + g_CustomEntityOnMapStartPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnRoundStateChanged: { - g_CustomEntityOnRoundStateChanged.AddFunction(null, hookFunc); + g_CustomEntityOnRoundStateChangedPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnDifficultyChanged: { - g_CustomEntityOnDifficultyChanged.AddFunction(null, hookFunc); + g_CustomEntityOnDifficultyChangedPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnPageCountChanged: { - g_CustomEntityOnPageCountChanged.AddFunction(null, hookFunc); + g_CustomEntityOnPageCountChangedPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnRenevantWaveTriggered: { @@ -85,36 +92,41 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) } } -#include "sf2/mapentities/base_spawnpoint.sp" -#include "sf2/mapentities/sf2_game_text.sp" -#include "sf2/mapentities/sf2_gamerules.sp" -#include "sf2/mapentities/sf2_trigger_escape.sp" -#include "sf2/mapentities/sf2_info_player_escapespawn.sp" -#include "sf2/mapentities/sf2_trigger_pvp.sp" -#include "sf2/mapentities/sf2_info_player_pvpspawn.sp" -#include "sf2/mapentities/sf2_info_player_proxyspawn.sp" -#include "sf2/mapentities/sf2_info_boss_spawn.sp" -#include "sf2/mapentities/sf2_info_page_spawn.sp" -#include "sf2/mapentities/sf2_info_page_music.sp" -#include "sf2/mapentities/sf2_logic_proxy.sp" -#include "sf2/mapentities/sf2_logic_raid.sp" -#include "sf2/mapentities/sf2_boss_maker.sp" -#include "sf2/mapentities/sf2_trigger_boss_despawn.sp" +#include "mapentities/base_spawnpoint.sp" +#include "mapentities/sf2_game_text.sp" +#include "mapentities/sf2_gamerules.sp" +#include "mapentities/sf2_trigger_escape.sp" +#include "mapentities/sf2_info_player_escapespawn.sp" +#include "mapentities/sf2_trigger_pvp.sp" +#include "mapentities/sf2_trigger_pve.sp" +#include "mapentities/sf2_trigger_weapons.sp" +#include "mapentities/sf2_info_player_pvpspawn.sp" +#include "mapentities/sf2_info_player_proxyspawn.sp" +#include "mapentities/sf2_info_boss_spawn.sp" +#include "mapentities/sf2_info_page_spawn.sp" +#include "mapentities/sf2_info_page_music.sp" +#include "mapentities/sf2_logic_proxy.sp" +#include "mapentities/sf2_logic_raid.sp" +#include "mapentities/sf2_boss_maker.sp" +#include "mapentities/sf2_trigger_boss_despawn.sp" // Modified only -#include "sf2/mapentities/sf2_logic_arena.sp" -#include "sf2/mapentities/sf2_logic_boxing.sp" -#include "sf2/mapentities/sf2_logic_slaughter.sp" +#include "mapentities/sf2_logic_arena.sp" +#include "mapentities/sf2_logic_boxing.sp" +#include "mapentities/sf2_logic_slaughter.sp" +#include "mapentities/sf2_point_spotlight.sp" void SetupCustomMapEntities() { - g_CustomEntityOnMapStart = new PrivateForward(ET_Ignore); - g_CustomEntityOnRoundStateChanged = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - g_CustomEntityOnDifficultyChanged = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - g_CustomEntityOnPageCountChanged = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_CustomEntityOnMapStartPFwd = new PrivateForward(ET_Ignore); + g_CustomEntityOnRoundStateChangedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_CustomEntityOnDifficultyChangedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_CustomEntityOnPageCountChangedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_CustomEntityOnRenevantWaveTriggered = new PrivateForward(ET_Ignore, Param_Cell); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_DifficultyConVar.AddChangeHook(SF2MapEntity_OnDifficultyChanged); // Initialize static entity data. @@ -137,6 +149,12 @@ void SetupCustomMapEntities() // sf2_trigger_pvp SF2TriggerPvPEntity.Initialize(); + // sf2_trigger_pve + SF2TriggerPvEEntity.Initialize(); + + // sf2_trigger_weapons + SF2TriggerWeaponsEntity.Initialize(); + // sf2_info_player_pvpspawn SF2PlayerPvPSpawnEntity.Initialize(); @@ -174,11 +192,14 @@ void SetupCustomMapEntities() // sf2_logic_slaughter SF2LogicSlaughterEntity.Initialize(); + + // sf2_point_spotlight + SF2PointSpotlightEntity.Initialize(); } -void SF2MapEntity_OnMapStart() +static void MapStart() { - Call_StartForward(g_CustomEntityOnMapStart); + Call_StartForward(g_CustomEntityOnMapStartPFwd); Call_Finish(); } @@ -257,7 +278,7 @@ int SF2MapEntity_FindEntityByTargetname(int startEnt, const char[] name, int sea void SF2MapEntity_OnRoundStateChanged(SF2RoundState roundState, SF2RoundState oldRoundState) { - Call_StartForward(g_CustomEntityOnRoundStateChanged); + Call_StartForward(g_CustomEntityOnRoundStateChangedPFwd); Call_PushCell(roundState); Call_PushCell(oldRoundState); Call_Finish(); @@ -268,7 +289,7 @@ static void SF2MapEntity_OnDifficultyChanged(ConVar cvar, const char[] oldValue, int oldDifficulty = StringToInt(oldValue); int difficulty = StringToInt(newValue); - Call_StartForward(g_CustomEntityOnDifficultyChanged); + Call_StartForward(g_CustomEntityOnDifficultyChangedPFwd); Call_PushCell(difficulty); Call_PushCell(oldDifficulty); Call_Finish(); @@ -276,7 +297,7 @@ static void SF2MapEntity_OnDifficultyChanged(ConVar cvar, const char[] oldValue, void SF2MapEntity_OnPageCountChanged(int pageCount, int oldPageCount) { - Call_StartForward(g_CustomEntityOnPageCountChanged); + Call_StartForward(g_CustomEntityOnPageCountChangedPFwd); Call_PushCell(pageCount); Call_PushCell(oldPageCount); Call_Finish(); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp index eac9e17e..6ceb00b2 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp @@ -56,6 +56,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return view_as(this.GetProp(Prop_Data, "sf2_hBosses")); } + public set(ArrayList value) { this.SetProp(Prop_Data, "sf2_hBosses", value); @@ -68,6 +69,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetProp(Prop_Data, "sf2_iSpawnCount"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iSpawnCount", value); @@ -80,6 +82,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flSpawnRadius"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flSpawnRadius", value); @@ -92,6 +95,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetProp(Prop_Data, "sf2_nMaxLiveChildren"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_nMaxLiveChildren", value); @@ -104,6 +108,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropEnt(Prop_Data, "sf2_iDestinationEntity"); } + public set(int entity) { this.SetPropEnt(Prop_Data, "sf2_iDestinationEntity", IsValidEntity(entity) ? entity : INVALID_ENT_REFERENCE); @@ -136,6 +141,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flSpawnAnimRate"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flSpawnAnimRate", value); @@ -148,6 +154,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flSpawnAnimDuration"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flSpawnAnimDuration", value); @@ -209,9 +216,9 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity pos[1] += vec[1]; } - SpawnSlender(view_as(bossIndex), pos); + SpawnSlender(SF2NPC_BaseNPC(bossIndex), pos); - CBaseAnimating bossEntity = CBaseAnimating(NPCGetEntIndex(bossIndex)); + CBaseCombatCharacter bossEntity = CBaseCombatCharacter(NPCGetEntIndex(bossIndex)); if (bossEntity.IsValid()) { if (!(spawnFlags & SF_SF2_BOSS_MAKER_NODROP)) @@ -245,15 +252,8 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity if (spawnAnim[0] != '\0') { - float playbackRate = this.SpawnAnimationPlaybackRate; - float duration = this.SpawnAnimationDuration; - - EntitySetAnimation(bossEntity.index, spawnAnim, playbackRate); - EntitySetAnimation(bossEntity.index, spawnAnim, playbackRate); //Fix an issue where an anim could start on the wrong frame. - - g_SlenderSpawning[bossIndex] = true; - g_SlenderSpawnTimer[bossIndex] = CreateTimer(duration, Timer_SlenderSpawnTimer, EntIndexToEntRef(bossEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_SlenderEntityThink[bossIndex] = null; + SF2_ChaserEntity chaser = SF2_ChaserEntity(bossEntity.index); + chaser.SetOverrideSpawnAnimation(spawnAnim, this.SpawnAnimationDuration, this.SpawnAnimationPlaybackRate); } } @@ -289,7 +289,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity continue; } - boss.UnSpawn(); + boss.UnSpawn(true); } } @@ -400,7 +400,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity if ((spawnFlags & SF_SF2_BOSS_MAKER_NOCOPIES)) { - NPCSetFlags(bossIndex, NPCGetFlags(bossIndex) & ~SFF_COPIES); + NPCSetFlags(bossIndex, NPCGetFlags(bossIndex) | SFF_NOCOPIES); } if (!(spawnFlags & SF_SF2_BOSS_MAKER_ADDONLY)) diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp index 105fd748..7bc10f88 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp @@ -45,12 +45,12 @@ methodmap SF2GameTextEntity < CBaseEntity { if (StrContains(buffer, "%d") != -1) { - char sName[64]; - this.GetPropString(Prop_Data, "m_iName", sName, sizeof(sName)); + char name[64]; + this.GetPropString(Prop_Data, "m_iName", name, sizeof(name)); char[] message = new char[bufferSize]; strcopy(message, bufferSize, buffer); ReplaceString(message, bufferSize, "%d", "%%d"); - LogError("sf2_game_text (%s): %%d formatting parameters are NOT ALLOWED! Use the and variables! Please report this to the map creator.\nOffending message: %s", sName, message); + LogError("sf2_game_text (%s): %%d formatting parameters are NOT ALLOWED! Use the and variables! Please report this to the map creator.\nOffending message: %s", name, message); return false; } @@ -68,19 +68,22 @@ methodmap SF2GameTextEntity < CBaseEntity if (StrContains(buffer, "") != -1) { - char arg[16]; IntToString(g_PageMax, arg, sizeof(arg)); + char arg[16]; + IntToString(g_PageMax, arg, sizeof(arg)); ReplaceString(buffer, bufferSize, "", arg); } if (StrContains(buffer, "") != -1) { - char arg[16]; IntToString(g_PageCount, arg, sizeof(arg)); + char arg[16]; + IntToString(g_PageCount, arg, sizeof(arg)); ReplaceString(buffer, bufferSize, "", arg); } if (StrContains(buffer, "") != -1) { - char arg[16]; IntToString(g_PageMax - g_PageCount, arg, sizeof(arg)); + char arg[16]; + IntToString(g_PageMax - g_PageCount, arg, sizeof(arg)); ReplaceString(buffer, bufferSize, "", arg); } } @@ -115,6 +118,7 @@ methodmap SF2GameTextEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flNextIntroTextDelay"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flNextIntroTextDelay", value); @@ -193,7 +197,7 @@ static void InputDisplay(int entity, int activator, int caller, const char[] val // If a targetname is specified, try to show text to the matched entity. int target = -1; - while ((target = SF2MapEntity_FindEntityByTargetname(target, value, caller, activator, caller)) != -1 && target > 0 && target <= MaxClients) + while ((target = SF2MapEntity_FindEntityByTargetname(target, value, caller, activator, caller)) != -1 && IsValidClient(target)) { clients[clientsNum++] = target; } @@ -204,7 +208,7 @@ static void InputDisplay(int entity, int activator, int caller, const char[] val for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp index 1294effb..6501631b 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp @@ -34,6 +34,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iMaxPlayers"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iMaxPlayers", value); @@ -46,6 +47,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iMaxPages"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iMaxPages", value); @@ -84,6 +86,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iInitialTimeLimit"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iInitialTimeLimit", value); @@ -96,6 +99,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iPageCollectAddTime"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iPageCollectAddTime", value); @@ -118,6 +122,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iPageCollectSoundPitch"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iPageCollectSoundPitch", value); @@ -128,8 +133,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bInfiniteFlashlight"); + return this.GetProp(Prop_Data, "sf2_bInfiniteFlashlight") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bInfiniteFlashlight", value); @@ -140,8 +146,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bInfiniteSprint"); + return this.GetProp(Prop_Data, "sf2_bInfiniteSprint") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bInfiniteSprint", value); @@ -152,8 +159,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bInfiniteBlink"); + return this.GetProp(Prop_Data, "sf2_bInfiniteBlink") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bInfiniteBlink", value); @@ -164,8 +172,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bBossesChaseEndlessly"); + return this.GetProp(Prop_Data, "sf2_bBossesChaseEndlessly") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bBossesChaseEndlessly", value); @@ -176,8 +185,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bEscapeToWin"); + return this.GetProp(Prop_Data, "sf2_bEscapeToWin") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bEscapeToWin", value); @@ -190,6 +200,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iEscapeTimeLimit"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iEscapeTimeLimit", value); @@ -226,8 +237,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bStopPageMusicOnEscape"); + return this.GetProp(Prop_Data, "sf2_bStopPageMusicOnEscape") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bStopPageMusicOnEscape", value); @@ -238,8 +250,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bSurvive"); + return this.GetProp(Prop_Data, "sf2_bSurvive") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bSurvive", value); @@ -252,6 +265,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iSurviveUntilTime"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iSurviveUntilTime", value); @@ -294,6 +308,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flIntroFadeHoldTime"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flIntroFadeHoldTime", value); @@ -306,6 +321,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flIntroFadeTime"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flIntroFadeTime", value); @@ -344,6 +360,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flIntroTextDelay"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flIntroTextDelay", value); @@ -803,7 +820,7 @@ static void InputSetDifficulty(int entity, int activator, int caller, int value) value = Difficulty_Max - 1; } - g_DifficultyConVar.SetInt(value); + SetDifficulty(value); } static void InputEndGracePeriod(int entity, int activator, int caller) diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp index a9f54cda..f181526b 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp @@ -42,6 +42,7 @@ methodmap SF2BossSpawnEntity < SF2SpawnPointBaseEntity { return this.GetProp(Prop_Data, "sf2_iMaxBosses"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iMaxBosses", value); @@ -77,7 +78,7 @@ methodmap SF2BossSpawnEntity < SF2SpawnPointBaseEntity NPCGetProfile(bossIndex, profile, sizeof(profile)); if (strcmp(profile, targetProfile) == 0) { - SpawnSlender(view_as(bossIndex), pos); + SpawnSlender(SF2NPC_BaseNPC(bossIndex), pos); count++; int bossEntIndex = NPCGetEntIndex(bossIndex); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp index ebf69dbf..f52702fc 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp @@ -39,8 +39,9 @@ methodmap SF2PageMusicEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bLayered"); + return this.GetProp(Prop_Data, "sf2_bLayered") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bLayered", value); @@ -53,6 +54,7 @@ methodmap SF2PageMusicEntity < CBaseEntity { return view_as(this.GetProp(Prop_Data, "sf2_hRanges")); } + public set(ArrayList value) { this.SetProp(Prop_Data, "sf2_hRanges", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp index f3381611..d63e840c 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp @@ -40,6 +40,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iSkin"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iSkin", value); @@ -52,6 +53,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flModelScale"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flModelScale", value); @@ -64,6 +66,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iBodyGroup"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iBodyGroup", value); @@ -106,6 +109,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { this.GetProp(Prop_Data, "sf2_iCollectSoundPitch"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iCollectSoundPitch", value); @@ -160,13 +164,13 @@ static void OnSpawn(int entity) char buffer[PLATFORM_MAX_PATH]; thisEnt.GetPageModel(buffer, sizeof(buffer)); - if (buffer[0]) + if (buffer[0] != '\0') { PrecacheModel(buffer); } thisEnt.GetPageCollectSound(buffer, sizeof(buffer)); - if (buffer[0]) + if (buffer[0] != '\0') { PrecacheSound(buffer); } diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp index 79446c5d..4f9fe452 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp @@ -27,6 +27,7 @@ methodmap SF2PlayerEscapeSpawnEntity < SF2SpawnPointBaseEntity { return !this.GetProp(Prop_Data, "sf2_bDisabled"); } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bDisabled", !value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp index 9c890c09..2a1fbcbc 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp @@ -27,6 +27,7 @@ methodmap SF2PlayerProxySpawnEntity < SF2SpawnPointBaseEntity { return !this.GetProp(Prop_Data, "sf2_bDisabled"); } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bDisabled", !value); @@ -37,8 +38,9 @@ methodmap SF2PlayerProxySpawnEntity < SF2SpawnPointBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bIgnoreVisibility"); + return this.GetProp(Prop_Data, "sf2_bIgnoreVisibility") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bIgnoreVisibility", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp index a4cc3168..3a39cf87 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp @@ -27,6 +27,7 @@ methodmap SF2PlayerPvPSpawnEntity < SF2SpawnPointBaseEntity { return !this.GetProp(Prop_Data, "sf2_bDisabled"); } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bDisabled", !value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp index 164f1e13..2d3f7434 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp @@ -32,6 +32,7 @@ methodmap SF2LogicRenevantEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iFinaleTime"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iFinaleTime", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_point_spotlight.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_point_spotlight.sp new file mode 100644 index 00000000..3bff8c78 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_point_spotlight.sp @@ -0,0 +1,398 @@ + +static const char g_Classname[] = "sf2_point_spotlight"; + +static CEntityFactory g_Factory; + +methodmap SF2PointSpotlightEntity < CBaseEntity +{ + public SF2PointSpotlightEntity(int entIndex) + { + return view_as(CBaseEntity(entIndex)); + } + + public bool IsValid() + { + if (!CBaseEntity(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_Classname, OnCreate, OnRemove); + g_Factory.DeriveFromClass("env_beam"); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_Start") + .DefineEntityField("m_End") + .DefineFloatField("m_HaloScale") + .DefineIntField("m_Brightness") + .DefineFloatField("m_Distance") + .DefineFloatField("m_SpotlightRadius") + .DefineIntField("m_Cone") + .DefineBoolField("m_IsOn") + .DefineFloatField("m_Length") + .DefineInputFunc("LightOn", InputFuncValueType_Void, InputLightOn) + .DefineInputFunc("LightOff", InputFuncValueType_Void, InputLightOff) + .EndDataMapDesc(); + g_Factory.Install(); + } + + property CBaseEntity Start + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_Start")); + } + + public set(CBaseEntity value) + { + this.SetPropEnt(Prop_Data, "m_Start", value.index); + } + } + + property CBaseEntity End + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_End")); + } + + public set(CBaseEntity value) + { + this.SetPropEnt(Prop_Data, "m_End", value.index); + } + } + + property float HaloScale + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_HaloScale"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_HaloScale", value); + } + } + + property int Brightness + { + public get() + { + return this.GetProp(Prop_Data, "m_Brightness"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Brightness", value); + } + } + + property float Distance + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Distance"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Distance", value); + } + } + + property float SpotlightRadius + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SpotlightRadius"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SpotlightRadius", value); + } + } + + property int Cone + { + public get() + { + return this.GetProp(Prop_Data, "m_Cone"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Cone", value); + } + } + + property bool IsOn + { + public get() + { + return this.GetProp(Prop_Data, "m_IsOn") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsOn", value); + } + } + + property float Length + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Length"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Length", value); + } + } + + property float Width + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_fWidth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_fWidth", value); + } + } + + property float EndWidth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_fEndWidth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_fEndWidth", value); + } + } + + public void TurnOn() + { + if (this.IsOn) + { + return; + } + + this.IsOn = true; + + this.AcceptInput("TurnOn"); + this.End.AcceptInput("TurnOn"); + } + + public void TurnOff() + { + if (!this.IsOn) + { + return; + } + + this.IsOn = false; + + this.AcceptInput("TurnOff"); + this.End.AcceptInput("TurnOff"); + } + + public void SetLightSettings() + { + SetVariantInt(this.Brightness); + this.End.AcceptInput("Brightness"); + SetVariantFloat(this.Distance); + this.End.AcceptInput("Distance"); + SetVariantFloat(this.SpotlightRadius); + this.End.AcceptInput("spotlight_radius"); + SetVariantInt(this.Cone); + this.End.AcceptInput("cone"); + } +} + +static void OnCreate(SF2PointSpotlightEntity ent) +{ + ent.SetModel(SF2_FLASHLIGHT_BEAM_MATERIAL); + ent.SetPropFloat(Prop_Data, "m_life", 0.0); + + float pos[3], ang[3]; + ent.GetAbsOrigin(pos); + ent.GetAbsAngles(ang); + + CBaseEntity start = ent.Start = CBaseEntity(CreateEntityByName("info_target")); + CBaseEntity end = ent.End = CBaseEntity(CreateEntityByName("light_dynamic")); + + start.SetPropString(Prop_Data, "m_iClassname", "sf2_point_spotlight_start"); + end.SetPropString(Prop_Data, "m_iClassname", "sf2_point_spotlight_end"); + start.Teleport(pos, ang); + end.Teleport(pos, ang); + SetVariantString("!activator"); + start.AcceptInput("SetParent", ent.index); + SetVariantString("!activator"); + end.AcceptInput("SetParent", ent.index); + start.Spawn(); + end.Spawn(); + end.Activate(); + + SDKHook(ent.index, SDKHook_SpawnPost, OnSpawn); +} + +static void OnSpawn(int entIndex) +{ + SF2PointSpotlightEntity entity = SF2PointSpotlightEntity(entIndex); + + entity.SetPropEnt(Prop_Send, "m_hAttachEntity", entity.Start.index, 0); + entity.SetPropEnt(Prop_Send, "m_hAttachEntity", entity.End.index, 1); + entity.SetProp(Prop_Send, "m_nNumBeamEnts", 2); + entity.SetProp(Prop_Send, "m_nBeamType", 2); + + entity.SetPropFloat(Prop_Send, "m_fFadeLength", 12.0); + entity.SetPropFloat(Prop_Data, "m_fEndWidth", entity.EndWidth); + entity.SetProp(Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); + entity.SetPropFloat(Prop_Send, "m_fHaloScale", entity.HaloScale); + entity.SetProp(Prop_Send, "m_nBeamFlags", 0x80 | 0x200); + entity.SetProp(Prop_Data, "m_spawnflags", 0x8000); + + entity.SetLightSettings(); + + SetEntityTransmitState(entity.index, FL_EDICT_FULLCHECK); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity.index, SpotlightEffectUpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, entity.index, SpotlightEffectShouldTransmit); + SetEntityTransmitState(entity.End.index, FL_EDICT_FULLCHECK); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity.End.index, SpotlightEffectUpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, entity.End.index, SpotlightEffectShouldTransmit); + + RequestFrame(SpotlightThink, EnsureEntRef(entIndex)); +} + +static void UpdateSpotlight(SF2PointSpotlightEntity entity) +{ + CBaseEntity spotlightEnd = entity.End; + if (spotlightEnd.IsValid()) + { + float pos[3], dir[3]; + entity.GetAbsOrigin(pos); + entity.GetAbsAngles(dir); + float endPos[3]; + endPos[0] = entity.Length; + VectorTransform(endPos, pos, dir, endPos); + + TR_TraceRayFilter(pos, endPos, MASK_SOLID_BRUSHONLY, RayType_EndPoint, Trace, entity.index); + + float hitPos[3]; + TR_GetEndPosition(hitPos); +/* + int color[4] = { 255, 0, 0, 255 }; + TE_SetupBeamPoints(pos, + endPos, + g_ShockwaveBeam, + g_ShockwaveHalo, + 0, + 30, + 0.1, + 5.0, + 5.0, + 5, + 0.0, + color, + 1); + TE_SendToAll(); +*/ + spotlightEnd.SetAbsOrigin(hitPos); + } + +} + +static void SpotlightThink(SF2PointSpotlightEntity entity) +{ + if (!entity.IsValid()) + { + return; + } + + if (entity.IsOn) + { + UpdateSpotlight(entity); + } + + RequestFrame(SpotlightThink, entity); +} + +static bool Trace(int entity, int mask, any data) +{ + if (entity == data) + { + return false; + } + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) + { + return false; + } + if (SF2_BasePlayer(entity).IsValid) + { + return false; + } + return true; +} + +static void OnRemove(SF2PointSpotlightEntity entity) +{ + if (entity.Start.IsValid()) + { + RemoveEntity(entity.Start.index); + } + + if (entity.End.IsValid()) + { + RemoveEntity(entity.End.index); + } +} + +static MRESReturn SpotlightEffectUpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = SetEntityTransmitState(entIndex, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn SpotlightEffectShouldTransmit(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + // We can return FL_EDICT_ALWAYS here because SetTransmit does get called if ShouldTransmit() + // returns this. + // https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/gameinterface.cpp#L2481 + ret.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} + +static void InputLightOn(int entIndex, int activator, int caller) +{ + SF2PointSpotlightEntity entity = SF2PointSpotlightEntity(entIndex); + entity.TurnOn(); +} + +static void InputLightOff(int entIndex, int activator, int caller) +{ + SF2PointSpotlightEntity entity = SF2PointSpotlightEntity(entIndex); + entity.TurnOff(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp new file mode 100644 index 00000000..b29ee97e --- /dev/null +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp @@ -0,0 +1,78 @@ +// sf2_trigger_pve + +#pragma semicolon 1 + +static CEntityFactory g_EntityFactory; + +/** + * Interface that exposes public methods for interacting with the entity. + */ +methodmap SF2TriggerPvEEntity < SF2TriggerMapEntity +{ + public SF2TriggerPvEEntity(int entIndex) + { + return view_as(SF2TriggerMapEntity(entIndex)); + } + + public bool IsValid() + { + if (!CBaseEntity(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_EntityFactory; + } + + property bool IsBossPvE + { + public get() + { + return this.GetProp(Prop_Data, "m_IsBossPvE") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsBossPvE", value); + } + } + + public static void Initialize() + { + Initialize(); + } +} + +static void Initialize() +{ + g_EntityFactory = new CEntityFactory("sf2_trigger_pve", OnCreate); + g_EntityFactory.DeriveFromClass("trigger_multiple"); + g_EntityFactory.BeginDataMapDesc() + .DefineBoolField("m_IsBossPvE", _, "bosspve") + .EndDataMapDesc(); + + g_EntityFactory.Install(); +} + +static void OnCreate(int entity) +{ + SDKHook(entity, SDKHook_SpawnPost, OnSpawn); + SDKHook(entity, SDKHook_StartTouchPost, OnStartTouchPost); + SDKHook(entity, SDKHook_EndTouchPost, OnEndTouchPost); +} + +static void OnSpawn(int entity) +{ + int spawnFlags = GetEntProp(entity, Prop_Data, "m_spawnflags"); + SetEntProp(entity, Prop_Data, "m_spawnflags", spawnFlags | TRIGGER_CLIENTS); +} + +static void OnStartTouchPost(int entity, int toucher) +{ + PvE_OnTriggerStartTouch(entity, toucher); +} + +static void OnEndTouchPost(int entity, int toucher) +{ + PvE_OnTriggerEndTouch(entity, toucher); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_weapons.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_weapons.sp new file mode 100644 index 00000000..ec81c29c --- /dev/null +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_weapons.sp @@ -0,0 +1,189 @@ +// sf2_trigger_weapons + +#pragma semicolon 1 +#pragma newdecls required + +static CEntityFactory g_EntityFactory; + +static bool g_PlayerInWeaponsTrigger[MAXTF2PLAYERS]; +static ArrayList g_PlayerEnteredWeaponTriggers[MAXTF2PLAYERS] = { null, ... }; + +/** + * Interface that exposes public methods for interacting with the entity. + */ +methodmap SF2TriggerWeaponsEntity < SF2TriggerMapEntity +{ + public SF2TriggerWeaponsEntity(int entIndex) + { + return view_as(SF2TriggerMapEntity(entIndex)); + } + + public bool IsValid() + { + if (!CBaseEntity(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_EntityFactory; + } + + public static void Initialize() + { + Initialize(); + } +} + +static void Initialize() +{ + g_EntityFactory = new CEntityFactory("sf2_trigger_weapons", OnCreate); + g_EntityFactory.DeriveFromClass("trigger_multiple"); + + g_EntityFactory.Install(); + + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + g_PlayerEnteredWeaponTriggers[client.index] = new ArrayList(); + g_PlayerInWeaponsTrigger[client.index] = false; +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + SetWeaponsState(client.index, false, false); + + if (g_PlayerEnteredWeaponTriggers[client.index] != null) + { + delete g_PlayerEnteredWeaponTriggers[client.index]; + g_PlayerEnteredWeaponTriggers[client.index] = null; + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + SetWeaponsState(client.index, false, false); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + SetWeaponsState(client.index, false, false); + } +} + +static void OnCreate(int entity) +{ + SDKHook(entity, SDKHook_SpawnPost, OnSpawn); + SDKHook(entity, SDKHook_StartTouchPost, OnStartTouchPost); + SDKHook(entity, SDKHook_EndTouchPost, OnEndTouchPost); +} + +static void OnSpawn(int entity) +{ + int spawnFlags = GetEntProp(entity, Prop_Data, "m_spawnflags"); + SetEntProp(entity, Prop_Data, "m_spawnflags", spawnFlags | TRIGGER_CLIENTS); +} + +static void OnStartTouchPost(int entity, int toucher) +{ + if (!IsValidClient(toucher) || !IsPlayerAlive(toucher) || IsClientInGhostMode(toucher)) + { + return; + } + + if (!g_PlayerEliminated[toucher] && !DidClientEscape(toucher)) + { + return; + } + + int entRef = EnsureEntRef(entity); + if (g_PlayerEnteredWeaponTriggers[toucher].FindValue(entRef) == -1) + { + g_PlayerEnteredWeaponTriggers[toucher].Push(entRef); + } + + SetWeaponsState(toucher, true); +} + +static void OnEndTouchPost(int entity, int toucher) +{ + if (!IsValidClient(toucher)) + { + return; + } + + if (g_PlayerEnteredWeaponTriggers[toucher] != null) + { + int triggerEntRef = EnsureEntRef(entity); + for (int i = g_PlayerEnteredWeaponTriggers[toucher].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredWeaponTriggers[toucher].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredWeaponTriggers[toucher].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredWeaponTriggers[toucher].Erase(i); + } + } + } + + if (IsClientInWeaponsTrigger(toucher) && g_PlayerEnteredWeaponTriggers[toucher].Length == 0) + { + SetWeaponsState(toucher, false); + } +} + +static void SetWeaponsState(int client, bool status, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool old = g_PlayerInWeaponsTrigger[player.index]; + if (status == old) + { + return; + } + + g_PlayerInWeaponsTrigger[player.index] = status; + + if (regenerate) + { + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +bool IsClientInWeaponsTrigger(int client) +{ + return g_PlayerInWeaponsTrigger[client]; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 9816f6d0..9fd02d5f 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -6,170 +6,169 @@ #pragma semicolon 1 -Handle g_MenuMain; -Handle g_MenuVoteDifficulty; -Handle g_MenuHelp; -Handle g_MenuHelpObjective; -Handle g_MenuHelpObjective2; -Handle g_MenuHelpCommands; -Handle g_MenuHelpSprinting; -Handle g_MenuHelpControls; -Handle g_MenuHelpClasinfo; -Handle g_MenuHelpGhostMode; -Handle g_MenuSettings; -Handle g_MenuSettingsFlashlightTemp1; -Handle g_MenuCredits; -Handle g_MenuCredits1; -Handle g_MenuCredits2; -Handle g_MenuCredits3; -Handle g_MenuCredits4; -Handle g_MenuCredits5; -Handle g_MenuUpdate; - -#include "sf2/playergroups/menus.sp" -#include "sf2/pvp/menus.sp" +Menu g_MenuMain; +Menu g_MenuVoteDifficulty; +Menu g_MenuHelp; +Menu g_MenuHelpObjective; +Menu g_MenuHelpObjective2; +Menu g_MenuHelpCommands; +Menu g_MenuHelpSprinting; +Menu g_MenuHelpControls; +Menu g_MenuHelpClassInfo; +Menu g_MenuHelpGhostMode; +Menu g_MenuSettings; +Menu g_MenuSettingsFlashlightTemp1; +Menu g_MenuCredits; +Menu g_MenuCredits1; +Menu g_MenuCredits2; +Menu g_MenuCredits3; +Menu g_MenuCredits4; +Menu g_MenuCredits5; + +static ArrayList g_Voters; +static bool g_IsRunOff; + +#include "playergroups/menus.sp" +#include "pvp/menus.sp" +#include "changelog.sp" void SetupMenus() { char buffer[512]; // Create menus. - g_MenuMain = CreateMenu(Menu_Main); - SetMenuTitle(g_MenuMain, "%t%t\n \n", "SF2 Prefix", "SF2 Main Menu Title"); + g_MenuMain = new Menu(Menu_Main); + g_MenuMain.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Main Menu Title"); FormatEx(buffer, sizeof(buffer), "%t (!slhelp)", "SF2 Help Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slnext)", "SF2 Queue Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slgroup)", "SF2 Group Main Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slghost)", "SF2 Ghost Mode Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slpack)", "SF2 Boss Pack Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slnextpack)", "SF2 Boss Next Pack Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slsettings)", "SF2 Settings Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); strcopy(buffer, sizeof(buffer), "Credits (!slcredits)"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slbosslist)", "SF2 Boss View On List Title"); - AddMenuItem(g_MenuMain, "0", buffer); - - g_MenuVoteDifficulty = CreateMenu(Menu_VoteDifficulty); - SetMenuTitle(g_MenuVoteDifficulty, "%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - - g_MenuHelp = CreateMenu(Menu_Help); - SetMenuTitle(g_MenuHelp, "%t%t\n \n", "SF2 Prefix", "SF2 Help Menu Title"); + g_MenuMain.AddItem("0", buffer); + + g_MenuHelp = new Menu(Menu_Help); + g_MenuHelp.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Help Menu Title"); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Objective Menu Title"); - AddMenuItem(g_MenuHelp, "0", buffer); + g_MenuHelp.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Commands Menu Title"); - AddMenuItem(g_MenuHelp, "1", buffer); + g_MenuHelp.AddItem("1", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Class Info Menu Title"); - AddMenuItem(g_MenuHelp, "2", buffer); + g_MenuHelp.AddItem("2", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Ghost Mode Menu Title"); - AddMenuItem(g_MenuHelp, "3", buffer); + g_MenuHelp.AddItem("3", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Sprinting And Stamina Menu Title"); - AddMenuItem(g_MenuHelp, "4", buffer); + g_MenuHelp.AddItem("4", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Controls Menu Title"); - AddMenuItem(g_MenuHelp, "5", buffer); - SetMenuExitBackButton(g_MenuHelp, true); + g_MenuHelp.AddItem("5", buffer); + g_MenuHelp.ExitBackButton = true; - g_MenuHelpObjective = CreateMenu(Menu_HelpObjective); - SetMenuTitle(g_MenuHelpObjective, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description"); - AddMenuItem(g_MenuHelpObjective, "0", "Next"); - AddMenuItem(g_MenuHelpObjective, "1", "Back"); + g_MenuHelpObjective = new Menu(Menu_HelpObjective); + g_MenuHelpObjective.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description"); + g_MenuHelpObjective.AddItem("0", "Next"); + g_MenuHelpObjective.AddItem("1", "Back"); - g_MenuHelpObjective2 = CreateMenu(Menu_HelpObjective2); - SetMenuTitle(g_MenuHelpObjective2, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description 2"); - AddMenuItem(g_MenuHelpObjective2, "0", "Back"); + g_MenuHelpObjective2 = new Menu(Menu_HelpObjective2); + g_MenuHelpObjective2.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description 2"); + g_MenuHelpObjective2.AddItem("0", "Back"); - g_MenuHelpCommands = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpCommands, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Commands Menu Title", "SF2 Help Commands Description"); - AddMenuItem(g_MenuHelpCommands, "0", "Back"); + g_MenuHelpCommands = new Menu(Menu_BackButtonOnly); + g_MenuHelpCommands.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Commands Menu Title", "SF2 Help Commands Description"); + g_MenuHelpCommands.AddItem("0", "Back"); - g_MenuHelpGhostMode = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpGhostMode, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Ghost Mode Menu Title", "SF2 Help Ghost Mode Description"); - AddMenuItem(g_MenuHelpGhostMode, "0", "Back"); + g_MenuHelpGhostMode = new Menu(Menu_BackButtonOnly); + g_MenuHelpGhostMode.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Ghost Mode Menu Title", "SF2 Help Ghost Mode Description"); + g_MenuHelpGhostMode.AddItem("0", "Back"); - g_MenuHelpSprinting = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpSprinting, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Sprinting And Stamina Menu Title", "SF2 Help Sprinting And Stamina Description"); - AddMenuItem(g_MenuHelpSprinting, "0", "Back"); + g_MenuHelpSprinting = new Menu(Menu_BackButtonOnly); + g_MenuHelpSprinting.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Sprinting And Stamina Menu Title", "SF2 Help Sprinting And Stamina Description"); + g_MenuHelpSprinting.AddItem("0", "Back"); - g_MenuHelpControls = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpControls, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Controls Menu Title", "SF2 Help Controls Description"); - AddMenuItem(g_MenuHelpControls, "0", "Back"); + g_MenuHelpControls = new Menu(Menu_BackButtonOnly); + g_MenuHelpControls.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Controls Menu Title", "SF2 Help Controls Description"); + g_MenuHelpControls.AddItem("0", "Back"); - g_MenuHelpClasinfo = CreateMenu(Menu_ClassInfo); - SetMenuTitle(g_MenuHelpClasinfo, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Class Info Menu Title", "SF2 Help Class Info Description"); + g_MenuHelpClassInfo = new Menu(Menu_ClassInfo); + g_MenuHelpClassInfo.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Class Info Menu Title", "SF2 Help Class Info Description"); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Scout Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Scout", buffer); + g_MenuHelpClassInfo.AddItem("Scout", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Sniper Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Sniper", buffer); + g_MenuHelpClassInfo.AddItem("Sniper", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Soldier Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Soldier", buffer); + g_MenuHelpClassInfo.AddItem("Soldier", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Demoman Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Demoman", buffer); + g_MenuHelpClassInfo.AddItem("Demoman", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Heavy Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Heavy", buffer); + g_MenuHelpClassInfo.AddItem("Heavy", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Medic Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Medic", buffer); + g_MenuHelpClassInfo.AddItem("Medic", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Pyro Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Pyro", buffer); + g_MenuHelpClassInfo.AddItem("Pyro", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Spy Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Spy", buffer); + g_MenuHelpClassInfo.AddItem("Spy", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Engineer Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Engineer", buffer); - SetMenuExitBackButton(g_MenuHelpClasinfo, true); + g_MenuHelpClassInfo.AddItem("Engineer", buffer); + g_MenuHelpClassInfo.ExitBackButton = true; - g_MenuSettings = CreateMenu(Menu_Settings); - SetMenuTitle(g_MenuSettings, "%t%t\n \n", "SF2 Prefix", "SF2 Settings Menu Title"); + g_MenuSettings = new Menu(Menu_Settings); + g_MenuSettings.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Settings Menu Title"); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings PvP Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Hints Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Mute Mode Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Film Grain Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); - FormatEx(buffer, sizeof(buffer), "Toggle camera view bobbing"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings View Bobbing Title"); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Hud Version Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Proxy Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Music Volume Title"); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Flashlight Temperature Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Ghost Mode Teleport Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Ghost Mode Toggle State Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Proxy Ask Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); - SetMenuExitBackButton(g_MenuSettings, true); - - g_MenuSettingsFlashlightTemp1 = CreateMenu(Menu_Settings_Flashlighttemp1); - SetMenuTitle(g_MenuSettingsFlashlightTemp1, buffer); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "0", "1000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "1", "2000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "2", "3000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "3", "4000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "4", "5000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "5", "6000 Kelvin (Default)"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "6", "7000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "7", "8000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "8", "9000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "9", "10000 Kelvin"); - SetMenuExitBackButton(g_MenuSettingsFlashlightTemp1, true); - - g_MenuCredits = CreateMenu(Menu_Credits); - - FormatEx(buffer, sizeof(buffer), "Credits\n"); + g_MenuSettings.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings PvE Menu Title"); + g_MenuSettings.AddItem("0", buffer); + g_MenuSettings.ExitBackButton = true; + + g_MenuSettingsFlashlightTemp1 = new Menu(Menu_Settings_Flashlighttemp1); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Flashlight Temperature Title"); + g_MenuSettingsFlashlightTemp1.SetTitle(buffer); + g_MenuSettingsFlashlightTemp1.AddItem("0", "1k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("1", "2k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("2", "3k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("3", "4k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("4", "5k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("5", "6k Kelvin (Default)"); + g_MenuSettingsFlashlightTemp1.AddItem("6", "7k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("7", "8k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("8", "9k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("9", "10k Kelvin"); + g_MenuSettingsFlashlightTemp1.ExitBackButton = true; + + g_MenuCredits = new Menu(Menu_Credits); + + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Coders: KitRifty, Kenzzer, Mentrillum, The Gaben\n"); StrCat(buffer, sizeof(buffer), "Mark J. Hadley - The creator of the Slender game\n"); StrCat(buffer, sizeof(buffer), "Mark Steen - Compositing the intro music\n"); @@ -177,28 +176,28 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Mammoth Mogul - For being a GREAT test subject\n"); StrCat(buffer, sizeof(buffer), "Egosins - For offering to host this publicly\n"); - SetMenuTitle(g_MenuCredits, buffer); - AddMenuItem(g_MenuCredits, "0", "Next"); - AddMenuItem(g_MenuCredits, "1", "Back"); + g_MenuCredits.SetTitle(buffer); + g_MenuCredits.AddItem("0", "Next"); + g_MenuCredits.AddItem("1", "Back"); - g_MenuCredits1 = CreateMenu(Menu_Credits1); + g_MenuCredits1 = new Menu(Menu_Credits1); - FormatEx(buffer, sizeof(buffer), "Credits\n"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Glubbable - For working on a ton of maps\n"); StrCat(buffer, sizeof(buffer), "Somberguy - Suggestions and support\n"); StrCat(buffer, sizeof(buffer), "Omi-Box - Materials, maps, current Slender Man model, and more\n"); StrCat(buffer, sizeof(buffer), "Narry Gewman - Imported first Slender Man model\n"); StrCat(buffer, sizeof(buffer), "Simply Delicious - For the awesome camera overlay\n"); - StrCat(buffer, sizeof(buffer), "Jason278 -Page models"); + StrCat(buffer, sizeof(buffer), "Jason278 - Page models"); StrCat(buffer, sizeof(buffer), "Dj-Rec0il - Running In the 90s Remix composer\n"); - SetMenuTitle(g_MenuCredits1, buffer); - AddMenuItem(g_MenuCredits1, "0", "Next"); - AddMenuItem(g_MenuCredits1, "1", "Back"); + g_MenuCredits1.SetTitle(buffer); + g_MenuCredits1.AddItem("0", "Next"); + g_MenuCredits1.AddItem("1", "Back"); - g_MenuCredits2 = CreateMenu(Menu_Credits2); + g_MenuCredits2 = new Menu(Menu_Credits2); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "To all the peeps who alpha-tested this thing!\n \n"); StrCat(buffer, sizeof(buffer), "Tofu\n"); StrCat(buffer, sizeof(buffer), "Ace-Dashie\n"); @@ -211,13 +210,13 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Pinkle D Lies\n"); StrCat(buffer, sizeof(buffer), "Ultimatefry\n \n"); - SetMenuTitle(g_MenuCredits2, buffer); - AddMenuItem(g_MenuCredits2, "0", "Next"); - AddMenuItem(g_MenuCredits2, "1", "Back"); + g_MenuCredits2.SetTitle(buffer); + g_MenuCredits2.AddItem("0", "Next"); + g_MenuCredits2.AddItem("1", "Back"); - g_MenuCredits3 = CreateMenu(Menu_Credits3); + g_MenuCredits3 = new Menu(Menu_Credits3); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Credits to all peeps who gave special round suggestions!\n \n"); StrCat(buffer, sizeof(buffer), "TehPlayer14\n"); StrCat(buffer, sizeof(buffer), "SirAnthony\n"); @@ -230,58 +229,44 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Spooky Pyro\n"); StrCat(buffer, sizeof(buffer), "Firedudeet\n \n"); - SetMenuTitle(g_MenuCredits3, buffer); - AddMenuItem(g_MenuCredits3, "0", "Next"); - AddMenuItem(g_MenuCredits3, "1", "Back"); + g_MenuCredits3.SetTitle(buffer); + g_MenuCredits3.AddItem("0", "Next"); + g_MenuCredits3.AddItem("1", "Back"); - g_MenuCredits4 = CreateMenu(Menu_Credits4); + g_MenuCredits4 = new Menu(Menu_Credits4); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Major special thanks to all official Modified server owners!\n \n"); StrCat(buffer, sizeof(buffer), "Demon Hamster Eating My Wafflez\n"); StrCat(buffer, sizeof(buffer), "Munt\n"); StrCat(buffer, sizeof(buffer), "KanP\n"); StrCat(buffer, sizeof(buffer), "SAXY GIBUS MAN\n"); StrCat(buffer, sizeof(buffer), "Fire\n"); - StrCat(buffer, sizeof(buffer), "[NxN]Nameless\n"); - SetMenuTitle(g_MenuCredits4, buffer); - AddMenuItem(g_MenuCredits4, "0", "Next"); - AddMenuItem(g_MenuCredits4, "1", "Back"); + g_MenuCredits4.SetTitle(buffer); + g_MenuCredits4.AddItem("0", "Next"); + g_MenuCredits4.AddItem("1", "Back"); - g_MenuCredits5 = CreateMenu(Menu_Credits5); + g_MenuCredits5 = new Menu(Menu_Credits5); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "And finally to all of these people that helped out this version one way or another!\n \n"); - StrCat(buffer, sizeof(buffer), "Dookster\n"); + StrCat(buffer, sizeof(buffer), "KitRifty\n"); StrCat(buffer, sizeof(buffer), "Spook\n"); StrCat(buffer, sizeof(buffer), "Rorek\n"); StrCat(buffer, sizeof(buffer), "Chillax\n"); - StrCat(buffer, sizeof(buffer), "Staff from Disc-FF (EllieDear, Arrow Skye, tocks, and Pasta Stalin)\n"); + StrCat(buffer, sizeof(buffer), "Staff from Disc-FF (EllieDear, Arrow Skye, tocks, Pasta Stalin, etc)\n"); StrCat(buffer, sizeof(buffer), "Basically everyone at Disc-FF\n"); StrCat(buffer, sizeof(buffer), "And you for playing this new way of SF2!\n \n"); - SetMenuTitle(g_MenuCredits5, buffer); - AddMenuItem(g_MenuCredits5, "0", "Back"); - - g_MenuUpdate = CreateMenu(Menu_Update); - FormatEx(buffer, sizeof(buffer), "%tSlender Fortress\n \n", "SF2 Prefix"); - StrCat(buffer, sizeof(buffer), "Coders: KitRifty, Kenzzer, Mentrillum, The Gaben\n"); - StrCat(buffer, sizeof(buffer), "Version: "); - StrCat(buffer, sizeof(buffer), PLUGIN_VERSION); - StrCat(buffer, sizeof(buffer), "\n \n"); - Format(buffer, sizeof(buffer), "%s%t\n",buffer,"SF2 Recent Changes"); - Format(buffer, sizeof(buffer), "%s%t\n",buffer,"SF2 Change Log"); - StrCat(buffer, sizeof(buffer), "\n \n"); - - SetMenuTitle(g_MenuUpdate, buffer); - - AddMenuItem(g_MenuUpdate, "0", "Display main menu"); + g_MenuCredits5.SetTitle(buffer); + g_MenuCredits5.AddItem("0", "Back"); PvP_SetupMenus(); + PvE_SetupMenus(); } -void RandomizeVoteMenu() +void RandomizeVoteMenu(bool excludeHigh = false) { char buffer[512]; @@ -290,8 +275,16 @@ void RandomizeVoteMenu() delete g_MenuVoteDifficulty; } - g_MenuVoteDifficulty = CreateMenu(Menu_VoteDifficulty); - SetMenuTitle(g_MenuVoteDifficulty, "%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); + if (g_Voters != null) + { + delete g_Voters; + } + + g_Voters = new ArrayList(2); + g_IsRunOff = false; + + g_MenuVoteDifficulty = new Menu((g_DifficultyVoteRevoteConVar.FloatValue > 0.0) ? Menu_VoteNoneDifficulty : Menu_VoteDifficulty); + g_MenuVoteDifficulty.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); g_DifficultyVoteOptionsConVar.GetString(buffer, sizeof(buffer)); @@ -302,136 +295,152 @@ void RandomizeVoteMenu() bool apollyon = StrContains(buffer, "5") != -1; bool random = StrContains(buffer, "6") != -1; - switch (GetRandomInt(1,6))//There's probably a better way to do this but I was tired. + if (excludeHigh) { - case 1: - { - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - } - case 2: - { - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - } - case 3: - { - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - } - case 4: + nightmare = false; + apollyon = false; + } + + // WHOA there is a better way, and I'm not tired + ArrayList options = new ArrayList(ByteCountToCells(64)); + ArrayList indicies = new ArrayList(); + int index = 0; + if (normal) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); + options.PushString(buffer); + indicies.Push(1); + index++; + } + + if (hard) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); + options.PushString(buffer); + indicies.Push(2); + index++; + } + + if (insane) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); + options.PushString(buffer); + indicies.Push(3); + index++; + } + + if (nightmare) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); + options.PushString(buffer); + indicies.Push(4); + index++; + } + + if (apollyon) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); + options.PushString(buffer); + indicies.Push(5); + index++; + } + + int participating = 0; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || !IsClientParticipating(i) || IsFakeClient(i)) { - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } + continue; } - case 5: + + participating++; + } + + if (!excludeHigh) + { + for (int i = 1; i <= MaxClients; i++) { - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - if (normal) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); + continue; } - if (hard) + + int group = ClientGetPlayerGroup(i); + if (!IsPlayerGroupValid(group) || !IsPlayerGroupOptInHarder(group)) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); + continue; } - } - case 6: - { - if (insane) + + if (GetPlayerGroupMemberCount(group) < participating) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); + if (GetPlayerGroupMemberCount(group) < GetMaxPlayersForRound()) + { + continue; + } } - if (hard) + + if (!nightmare) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); + options.PushString(buffer); + indicies.Push(4); + index++; } - if (normal) + + if (!apollyon) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); + options.PushString(buffer); + indicies.Push(5); + index++; } + break; } } - if (nightmare) + + if (random) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "4", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Random Difficulty"); + options.PushString(buffer); + indicies.Push(6); + index++; } - if(apollyon) + + // I'm going to say that the random shuffle for the array list sucks, so I'm doing my own + ArrayList actualOptions = new ArrayList(ByteCountToCells(64)); + ArrayList actualIndicies = new ArrayList(); + for (int i = 0; i < options.Length;) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "5", buffer); + int arrayIndex = GetRandomInt(0, options.Length - 1); + char choice[64]; + options.GetString(arrayIndex, choice, sizeof(choice)); + actualOptions.PushString(choice); + actualIndicies.Push(indicies.Get(arrayIndex)); + options.Erase(arrayIndex); + indicies.Erase(arrayIndex); } - if (random) + for (int i = 0; i < actualOptions.Length; i++) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Random Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "", buffer); + int indexToAdd = actualIndicies.Get(i); + char choice[64], value[2]; + actualOptions.GetString(i, choice, sizeof(choice)); + FormatEx(value, sizeof(value), "%d", indexToAdd); + g_MenuVoteDifficulty.AddItem(value, choice); + } + + delete options; + delete indicies; + delete actualOptions; + delete actualIndicies; + + if (g_DifficultyVoteRevoteConVar.FloatValue > 0.0) + { + SetVoteResultCallback(g_MenuVoteDifficulty, Menu_VoteRunoffDifficulty); } } -static int Menu_Main(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Main(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -439,7 +448,7 @@ static int Menu_Main(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } case 1: { @@ -463,29 +472,217 @@ static int Menu_Main(Handle menu, MenuAction action,int param1,int param2) } case 6: { - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } case 7: { - DisplayMenu(g_MenuCredits, param1, MENU_TIME_FOREVER); + g_MenuCredits.Display(param1, MENU_TIME_FOREVER); } case 8: { - DisplayBossList(param1); + BossPreview_MainMenu(param1, 0); } } } return 0; } -static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int param2) +static void Menu_VoteRunoffDifficulty(Menu oldmenu, int votes, int clients, const int[][] clientInfo, int items, const int[][] itemInfo) +{ + if (items > 1) + { + float runoff = g_DifficultyVoteRevoteConVar.FloatValue; + if (runoff) + { + if (float(itemInfo[0][VOTEINFO_ITEM_VOTES]) <= (votes * runoff)) + { + ArrayList list = new ArrayList(); + for(int i = 0; i < items; i++) + { + if (itemInfo[i][VOTEINFO_ITEM_VOTES] >= itemInfo[1][VOTEINFO_ITEM_VOTES]) + { + list.Push(itemInfo[i][VOTEINFO_ITEM_INDEX]); + } + } + + char data[64]; + if (g_HighDifficultyPercentConVar.FloatValue > 0.0) + { + bool hard, easy; + int length = list.Length; + for (int i = 0; i < length; i++) + { + int index = list.Get(i); + oldmenu.GetItem(index, data, sizeof(data)); + + if ((strcmp(data, "4") == 0 || strcmp(data, "5") == 0)) + { + hard = true; + } + else + { + easy = true; + } + + if(easy && hard) + { + delete list; + + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(false); + Call_Finish(); + + CPrintToChatAll("%t", "SF2 Difficulty Vote Finished Unsuccessful Runoff", RoundToFloor(g_HighDifficultyPercentConVar.FloatValue * 100.0)); + g_VoteTimer = CreateTimer(1.0, Timer_HighVoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + return; + } + } + } + + g_IsRunOff = true; + Menu newmenu = new Menu(Menu_VoteDifficulty); + newmenu.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); + + char display[64]; + int length = list.Length; + for (int i = 0; i < length; i++) + { + int index = list.Get(i); + oldmenu.GetItem(index, data, sizeof(data), _, display, sizeof(display)); + newmenu.AddItem(data, display); + } + + delete list; + + g_VoteTimer = CreateTimer(1.0, Timer_ReVoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + + if (GetMenuItemCount(g_MenuVoteDifficulty) > 1) + { + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(false); + Call_Finish(); + } + g_Voters.Clear(); + + delete g_MenuVoteDifficulty; + g_MenuVoteDifficulty = newmenu; + + return; + } + } + } + + Menu_VoteDifficulty(oldmenu, MenuAction_VoteEnd, itemInfo[0][VOTEINFO_ITEM_INDEX], 0); +} + +static Action Timer_ReVoteDifficulty(Handle timer) +{ + if (timer != g_VoteTimer || IsRoundEnding()) + { + g_VoteTimer = null; + return Plugin_Stop; + } + + if (IsVoteInProgress() || NativeVotes_IsVoteInProgress()) + { + return Plugin_Continue; // There's another vote in progess. Wait. + } + + int clients[MAXTF2PLAYERS] = { -1, ... }; + int clientsNum; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsBot || player.IsEliminated) + { + continue; + } + + clients[clientsNum] = player.index; + clientsNum++; + } + + if (clientsNum == 0) + { + g_VoteTimer = null; + return Plugin_Stop; + } + + VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, 7); + g_VoteTimer = null; + return Plugin_Stop; +} + +static int Menu_VoteNoneDifficulty(Menu menu, MenuAction action,int param1,int param2) +{ + if (action == MenuAction_Select) + { + char option[64]; + GetMenuItem(menu, param2, option, sizeof(option)); + int index = g_Voters.Push(param1); + int value = StringToInt(option); + g_Voters.Set(index, value, 1); + } + return 0; +} + +static Action Timer_HighVoteDifficulty(Handle timer) +{ + if (timer != g_VoteTimer || IsRoundEnding()) + { + return Plugin_Stop; + } + + if (NativeVotes_IsVoteInProgress() || IsVoteInProgress()) + { + return Plugin_Continue; // There's another vote in progess. Wait. + } + + int clients[MAXTF2PLAYERS] = { -1, ... }; + int clientsNum; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || g_PlayerEliminated[i]) + { + continue; + } + + clients[clientsNum] = i; + clientsNum++; + } + + RandomizeVoteMenu(true); + VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, 10); + if (GetMenuItemCount(g_MenuVoteDifficulty) == 1) + { + for (int i = 0; i < clientsNum; i++) + { + FakeClientCommand(clients[i], "menuselect 1"); + } + } + + return Plugin_Stop; +} + +static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int param2) { + if (action == MenuAction_Select) + { + char option[64]; + menu.GetItem(param2, option, sizeof(option)); + int index = g_Voters.Push(param1); + int value = StringToInt(option); + g_Voters.Set(index, value, 1); + } + if (action == MenuAction_VoteEnd && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && !g_RestartSessionConVar.BoolValue) { int clientInGame = 0, clientCallingForNightmare = 0; for (int client = 1; client <= MaxClients; client++) { - if (IsClientInGame(client) && !g_PlayerEliminated[client]) + if (IsValidClient(client) && !g_PlayerEliminated[client]) { clientInGame++; if (g_PlayerCalledForNightmare[client]) @@ -497,37 +694,74 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par bool playersCalledForNightmare = (clientInGame == clientCallingForNightmare); char info[64], display[256], color[32], nightmareDisplay[256]; - GetMenuItem(menu, param1, info, sizeof(info), _, display, sizeof(display)); + menu.GetItem(param1, info, sizeof(info), _, display, sizeof(display)); - bool rng = false; + int difficulty = StringToInt(info); - if (IsSpecialRoundRunning() && (SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) || SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_WALLHAX) || SF_SpecialRound(SPECIALROUND_ESCAPETICKETS))) - { - g_DifficultyConVar.SetInt(Difficulty_Insane); - } - else if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && !SF_SpecialRound(SPECIALROUND_2DOOM) && - !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_ESCAPETICKETS) && !SF_SpecialRound(SPECIALROUND_NOGRACE) && - !SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) && !SF_SpecialRound(SPECIALROUND_WALLHAX) && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && - (GetRandomInt(1, 200) <= 2 || playersCalledForNightmare)) + bool rng = false, change = false; + + if (info[0] != '\0') { - if (GetRandomInt(1, 20) <= 1) + if (g_HighDifficultyPercentConVar.FloatValue > 0.0 && (strcmp(info, "4") == 0 || strcmp(info, "5") == 0)) { - rng = true; - g_DifficultyConVar.SetInt(Difficulty_Apollyon); - } - else - { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + float nrm, hrd, ins, ngt, apl; + for (int i = 0; i < g_Voters.Length; i++) + { + switch (g_Voters.Get(i, 1)) + { + case Difficulty_Normal: + { + nrm += 1.0; + } + case Difficulty_Hard: + { + hrd += 1.0; + } + case Difficulty_Insane: + { + ins += 1.0; + } + case Difficulty_Nightmare: + { + ngt += 1.0; + } + case Difficulty_Apollyon: + { + apl += 1.0; + } + } + } + float values = (ngt + apl) / g_Voters.Length; + if (values < g_HighDifficultyPercentConVar.FloatValue) + { + if (values > 0.5) + { + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(g_IsRunOff); + Call_Finish(); + + CPrintToChatAll("%t", "SF2 Difficulty Vote Finished Unsuccessful Runoff", RoundToFloor(g_HighDifficultyPercentConVar.FloatValue * 100.0)); + g_VoteTimer = CreateTimer(1.0, Timer_HighVoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + return 0; + } + else + { + int winner = Difficulty_Normal; + if (hrd > ins && hrd > nrm) + { + winner = Difficulty_Hard; + } + else if (ins > hrd && ins > nrm) + { + winner = Difficulty_Insane; + } + difficulty = winner; + change = true; + } + } } } - else if (IsSpecialRoundRunning() && (SF_SpecialRound(SPECIALROUND_NOGRACE) || SF_SpecialRound(SPECIALROUND_2DOOM))) - { - g_DifficultyConVar.SetInt(Difficulty_Hard); - } - else if (info[0] != '\0') - { - g_DifficultyConVar.SetString(info); - } else { g_DifficultyVoteOptionsConVar.GetString(info, sizeof(info)); @@ -547,92 +781,123 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par { case 5: { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } case 4: { if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } case 3: { if (insane) { - g_DifficultyConVar.SetInt(Difficulty_Insane); + difficulty = Difficulty_Insane; } else if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } case 2: { if (hard) { - g_DifficultyConVar.SetInt(Difficulty_Hard); + difficulty = Difficulty_Hard; } else if (insane) { - g_DifficultyConVar.SetInt(Difficulty_Insane); + difficulty = Difficulty_Insane; } else if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } case 1: { if (normal) { - g_DifficultyConVar.SetInt(Difficulty_Normal); + difficulty = Difficulty_Normal; } else if (hard) { - g_DifficultyConVar.SetInt(Difficulty_Hard); + difficulty = Difficulty_Hard; } else if (insane) { - g_DifficultyConVar.SetInt(Difficulty_Insane); + difficulty = Difficulty_Insane; } else if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } } } else { - g_DifficultyConVar.SetInt(GetRandomInt(Difficulty_Normal, Difficulty_Apollyon)); + difficulty = GetRandomInt(Difficulty_Normal, Difficulty_Insane); } } - int difficulty = g_DifficultyConVar.IntValue; - switch (difficulty) + Action fwdAction = Plugin_Continue; + int difficulty2 = difficulty; + + // This cell ref shit does not work, why? + Call_StartForward(g_OnDifficultyVoteFinishedPFwd); + Call_PushCell(difficulty); + Call_PushCellRef(difficulty2); + Call_Finish(fwdAction); + + if (fwdAction == Plugin_Changed) + { + difficulty = difficulty2; + } + + if ((SF_SpecialRound(SPECIALROUND_SILENTSLENDER) || SF_SpecialRound(SPECIALROUND_NOGRACE)) && difficulty < Difficulty_Hard) + { + difficulty = Difficulty_Hard; + } + + if ((SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) || SF_SpecialRound(SPECIALROUND_ESCAPETICKETS) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) || SF_SpecialRound(SPECIALROUND_WALLHAX)) && difficulty < Difficulty_Insane) + { + difficulty = Difficulty_Insane; + } + + if (GetRandomInt(1, 200) <= 2 || playersCalledForNightmare) { - case Difficulty_Easy: + if (GetRandomInt(1, 20) <= 1) + { + rng = true; + difficulty = Difficulty_Apollyon; + } + else { - FormatEx(display, sizeof(display), "%t", "SF2 Easy Difficulty"); - strcopy(color, sizeof(color), "{green}"); + difficulty = Difficulty_Nightmare; } + } + + switch (difficulty) + { case Difficulty_Hard: { FormatEx(display, sizeof(display), "%t", "SF2 Hard Difficulty"); @@ -648,10 +913,7 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par FormatEx(display, sizeof(display), "%t!", "SF2 Nightmare Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); strcopy(color, sizeof(color), "{valve}"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); } case Difficulty_Apollyon: @@ -659,10 +921,7 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par FormatEx(display, sizeof(display), "%t!", "SF2 Apollyon Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); strcopy(color, sizeof(color), "{darkgray}"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); if (rng) { @@ -719,12 +978,30 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par } } - CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished", color, display); + SetDifficulty(difficulty); + + if (!change) + { + CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished", color, display); + } + else + { + CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished Unsuccessful", RoundToFloor(g_HighDifficultyPercentConVar.FloatValue * 100.0), color, display); + } + char checker[64]; + g_DifficultyVoteRandomConVar.GetString(checker, sizeof(checker)); + if (g_MenuVoteDifficulty.ItemCount > 1) + { + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(g_IsRunOff); + Call_Finish(); + } } return 0; } -static int Menu_Help(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Help(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -732,27 +1009,27 @@ static int Menu_Help(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuHelpObjective, param1, 30); + g_MenuHelpObjective.Display(param1, 30); } case 1: { - DisplayMenu(g_MenuHelpCommands, param1, 30); + g_MenuHelpCommands.Display(param1, 30); } case 2: { - DisplayMenu(g_MenuHelpClasinfo, param1, 30); + g_MenuHelpClassInfo.Display(param1, 30); } case 3: { - DisplayMenu(g_MenuHelpGhostMode, param1, 30); + g_MenuHelpGhostMode.Display(param1, 30); } case 4: { - DisplayMenu(g_MenuHelpSprinting, param1, 30); + g_MenuHelpSprinting.Display(param1, 30); } case 5: { - DisplayMenu(g_MenuHelpControls, param1, 30); + g_MenuHelpControls.Display(param1, 30); } } } @@ -760,13 +1037,13 @@ static int Menu_Help(Handle menu, MenuAction action,int param1,int param2) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } return 0; } -static int Menu_HelpObjective(Handle menu, MenuAction action,int param1,int param2) +static int Menu_HelpObjective(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -774,18 +1051,18 @@ static int Menu_HelpObjective(Handle menu, MenuAction action,int param1,int para { case 0: { - DisplayMenu(g_MenuHelpObjective2, param1, 30); + g_MenuHelpObjective2.Display(param1, 30); } case 1: { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } } } return 0; } -static int Menu_HelpObjective2(Handle menu, MenuAction action,int param1,int param2) +static int Menu_HelpObjective2(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -793,14 +1070,14 @@ static int Menu_HelpObjective2(Handle menu, MenuAction action,int param1,int par { case 0: { - DisplayMenu(g_MenuHelpObjective, param1, 30); + g_MenuHelpObjective.Display(param1, 30); } } } return 0; } -static int Menu_BackButtonOnly(Handle menu, MenuAction action,int param1,int param2) +static int Menu_BackButtonOnly(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -808,14 +1085,14 @@ static int Menu_BackButtonOnly(Handle menu, MenuAction action,int param1,int par { case 0: { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } } } return 0; } -static int Menu_Credits(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -823,18 +1100,18 @@ static int Menu_Credits(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuCredits1, param1, MENU_TIME_FOREVER); + g_MenuCredits1.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } } return 0; } -static int Menu_Credits1(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits1(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -842,45 +1119,45 @@ static int Menu_Credits1(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuCredits2, param1, MENU_TIME_FOREVER); + g_MenuCredits2.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits, param1, MENU_TIME_FOREVER); + g_MenuCredits.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_ClassInfo(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ClassInfo(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } } else if (action == MenuAction_Select) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); - Handle menuHandle = CreateMenu(Menu_ClassInfoBackOnly); + Menu menuHandle = new Menu(Menu_ClassInfoBackOnly); char title[64], description[64]; FormatEx(title, sizeof(title), "SF2 Help %s Class Info Menu Title", info); FormatEx(description, sizeof(description), "SF2 Help %s Class Info Description", info); - SetMenuTitle(menuHandle, "%t%t\n \n%t\n \n", "SF2 Prefix", title, description); - AddMenuItem(menuHandle, "0", "Back"); - DisplayMenu(menuHandle, param1, 30); + menuHandle.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", title, description); + menuHandle.AddItem("0", "Back"); + menuHandle.Display(param1, 30); } return 0; } -static int Menu_ClassInfoBackOnly(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ClassInfoBackOnly(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -888,12 +1165,12 @@ static int Menu_ClassInfoBackOnly(Handle menu, MenuAction action,int param1,int } else if (action == MenuAction_Select) { - DisplayMenu(g_MenuHelpClasinfo, param1, 30); + g_MenuHelpClassInfo.Display(param1, 30); } return 0; } -static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Settings(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -901,22 +1178,22 @@ static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuSettingsPvP, param1, 30); + g_MenuSettingsPvP.Display(param1, 30); } case 1: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Hints Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsHints, 30); + panel.Send(param1, Panel_SettingsHints, 30); delete panel; } case 2: @@ -924,14 +1201,14 @@ static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Mute Mode Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Normal"); - DrawPanelItem(panel, "Mute opposing team"); - DrawPanelItem(panel, "Mute opposing team except when I'm a proxy"); + panel.DrawItem("Normal"); + panel.DrawItem("Mute opposing team"); + panel.DrawItem("Mute opposing team except when I'm a proxy"); - SendPanelToClient(panel, param1, Panel_SettingsMuteMode, 30); + panel.Send(param1, Panel_SettingsMuteMode, 30); delete panel; } case 3: @@ -939,15 +1216,15 @@ static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Film Grain Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsFilmGrain, 30); + panel.Send(param1, Panel_SettingsFilmGrain, 30); delete panel; } case 4: @@ -959,13 +1236,13 @@ static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Hud Version Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Use the new HUD"); - DrawPanelItem(panel, "Use the legacy HUD"); + panel.DrawItem("Use the new HUD"); + panel.DrawItem("Use the legacy HUD"); - SendPanelToClient(panel, param1, Panel_SettingsHudVersion, 30); + panel.Send(param1, Panel_SettingsHudVersion, 30); delete panel; } case 6: @@ -973,77 +1250,98 @@ static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Proxy Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsProxy, 30); + panel.Send(param1, Panel_SettingsProxy, 30); delete panel; } case 7: { - DisplayMenu(g_MenuSettingsFlashlightTemp1, param1, 30); + char buffer[512]; + Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Music Volume Title", param1); + + Panel panel = new Panel(); + panel.SetTitle(buffer); + + panel.DrawItem("0%"); + panel.DrawItem("25%"); + panel.DrawItem("50%"); + panel.DrawItem("75%"); + panel.DrawItem("100% (Default)"); + + panel.Send(param1, Panel_SettingsMusicVolume, 30); + delete panel; } case 8: + { + g_MenuSettingsFlashlightTemp1.Display(param1, 30); + } + case 9: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Ghost Mode Teleport Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Teleport to only players"); - DrawPanelItem(panel, "Teleport to only bosses"); + panel.DrawItem("Teleport to only players"); + panel.DrawItem("Teleport to only bosses"); - SendPanelToClient(panel, param1, Panel_SettingsGhostModeTeleport, 30); + panel.Send(param1, Panel_SettingsGhostModeTeleport, 30); delete panel; } - case 9: + case 10: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Ghost Mode Toggle State Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Default state"); - DrawPanelItem(panel, "Enable ghost mode upon grace period ends"); - DrawPanelItem(panel, "Enable ghost mode upon death on RED"); + panel.DrawItem("Default state"); + panel.DrawItem("Enable ghost mode upon grace period ends"); + panel.DrawItem("Enable ghost mode upon death on RED"); - SendPanelToClient(panel, param1, Panel_SettingsGhostModeToggleState, 30); + panel.Send(param1, Panel_SettingsGhostModeToggleState, 30); delete panel; } - case 10: + case 11: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Proxy Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Enable Ask Message"); - DrawPanelItem(panel, "Disable Ask Message"); + panel.DrawItem("Enable Ask Message"); + panel.DrawItem("Disable Ask Message"); - SendPanelToClient(panel, param1, Panel_SettingsProxyAskMenu, 30); + panel.Send(param1, Panel_SettingsProxyAskMenu, 30); delete panel; } + case 12: + { + GetPvEMenu().Display(param1, 30); + } } } else if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } return 0; } -static int Menu_Settings_Flashlighttemp1(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Settings_Flashlighttemp1(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1100,18 +1398,19 @@ static int Menu_Settings_Flashlighttemp1(Handle menu, MenuAction action,int para ClientSaveCookies(param1); } } + CPrintToChat(param1, "%t", "SF2 Flashlight Temperature Changed", g_PlayerPreferences[param1].PlayerPreference_FlashlightTemperature); } else if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } return 0; } -static int Panel_SettingsFilmGrain(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsFilmGrain(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1131,12 +1430,12 @@ static int Panel_SettingsFilmGrain(Handle menu, MenuAction action,int param1,int } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsHints(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsHints(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1156,12 +1455,12 @@ static int Panel_SettingsHints(Handle menu, MenuAction action,int param1,int par } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsProxy(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsProxy(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1181,12 +1480,12 @@ static int Panel_SettingsProxy(Handle menu, MenuAction action,int param1,int par } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsProxyAskMenu(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsProxyAskMenu(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1206,12 +1505,12 @@ static int Panel_SettingsProxyAskMenu(Handle menu, MenuAction action,int param1, } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsMuteMode(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsMuteMode(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1240,12 +1539,12 @@ static int Panel_SettingsMuteMode(Handle menu, MenuAction action,int param1,int } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsGhostModeTeleport(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsGhostModeTeleport(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1267,12 +1566,12 @@ static int Panel_SettingsGhostModeTeleport(Handle menu, MenuAction action,int pa } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsGhostModeToggleState(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsGhostModeToggleState(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1301,12 +1600,30 @@ static int Panel_SettingsGhostModeToggleState(Handle menu, MenuAction action,int } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); + } + return 0; +} + +static int Panel_SettingsMusicVolume(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select) + { + float volume; + for (int i = 1; i < param2; i++) + { + volume += 0.25; + } + g_PlayerPreferences[param1].PlayerPreference_MusicVolume = volume; + CPrintToChat(param1, "%t", "SF2 Music Volum Changed", RoundToNearest(volume * 100.0)); + ClientSaveCookies(param1); + + g_MenuSettings.Display(param1, 30); } return 0; } -int Panel_SettingsHudVersion(Handle menu, MenuAction action,int param1,int param2) +int Panel_SettingsHudVersion(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1326,12 +1643,12 @@ int Panel_SettingsHudVersion(Handle menu, MenuAction action,int param1,int param } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -int Panel_SettingsViewBobbing(Handle menu, MenuAction action,int param1,int param2) +int Panel_SettingsViewBobbing(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1351,12 +1668,12 @@ int Panel_SettingsViewBobbing(Handle menu, MenuAction action,int param1,int para } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Menu_Credits2(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits2(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1364,17 +1681,18 @@ static int Menu_Credits2(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuCredits3, param1, MENU_TIME_FOREVER); + g_MenuCredits3.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits1, param1, MENU_TIME_FOREVER); + g_MenuCredits1.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Credits3(Handle menu, MenuAction action,int param1,int param2) + +static int Menu_Credits3(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1382,17 +1700,18 @@ static int Menu_Credits3(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuCredits4, param1, MENU_TIME_FOREVER); + g_MenuCredits4.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits2, param1, MENU_TIME_FOREVER); + g_MenuCredits2.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Credits4(Handle menu, MenuAction action,int param1,int param2) + +static int Menu_Credits4(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1400,17 +1719,18 @@ static int Menu_Credits4(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuCredits5, param1, MENU_TIME_FOREVER); + g_MenuCredits5.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits3, param1, MENU_TIME_FOREVER); + g_MenuCredits3.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Credits5(Handle menu, MenuAction action,int param1,int param2) + +static int Menu_Credits5(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1418,26 +1738,16 @@ static int Menu_Credits5(Handle menu, MenuAction action,int param1,int param2) { case 0: { - DisplayMenu(g_MenuCredits4, param1, MENU_TIME_FOREVER); + g_MenuCredits4.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Update(Handle menu, MenuAction action,int param1,int param2) -{ - if (action == MenuAction_Select) - { - if (param2 == 0) - { - DisplayMenu(g_MenuMain, param1, 30); - } - } - return 0; -} + void DisplayQueuePointsMenu(int client) { - Handle menu = CreateMenu(Menu_QueuePoints); + Menu menu = new Menu(Menu_QueuePoints); ArrayList queueList = GetQueueList(); char buffer[256]; @@ -1445,7 +1755,7 @@ void DisplayQueuePointsMenu(int client) if (queueList.Length) { FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Reset Queue Points Option", client, g_PlayerQueuePoints[client]); - AddMenuItem(menu, "ponyponypony", buffer); + menu.AddItem("deathcorridor", buffer); int index; char groupName[SF2_MAX_PLAYER_GROUP_NAME_LENGTH]; @@ -1459,7 +1769,7 @@ void DisplayQueuePointsMenu(int client) FormatEx(buffer, sizeof(buffer), "%N - %d", index, g_PlayerQueuePoints[index]); FormatEx(info, sizeof(info), "player_%d", GetClientUserId(index)); - AddMenuItem(menu, info, buffer, g_PlayerPlaying[index] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem(info, buffer, g_PlayerPlaying[index] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); } else { @@ -1470,7 +1780,7 @@ void DisplayQueuePointsMenu(int client) FormatEx(buffer, sizeof(buffer), "[GROUP] %s - %d", groupName, GetPlayerGroupQueuePoints(index)); FormatEx(info, sizeof(info), "group_%d", index); - AddMenuItem(menu, info, buffer, IsPlayerGroupPlaying(index) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem(info, buffer, IsPlayerGroupPlaying(index) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); } else { @@ -1484,7 +1794,7 @@ void DisplayQueuePointsMenu(int client) { FormatEx(buffer, sizeof(buffer), "%N - %d", i2, g_PlayerQueuePoints[i2]); FormatEx(info, sizeof(info), "player_%d", GetClientUserId(i2)); - AddMenuItem(menu, "player", buffer, g_PlayerPlaying[i2] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem("player", buffer, g_PlayerPlaying[i2] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); break; } } @@ -1495,9 +1805,9 @@ void DisplayQueuePointsMenu(int client) delete queueList; - SetMenuTitle(menu, "%t%T\n \n", "SF2 Prefix", "SF2 Queue Menu Title", client); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Queue Menu Title", client); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) @@ -1511,9 +1821,6 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) } ArrayList playersArray = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersArray in DisplayViewGroupMembersQueueMenu.", playersArray); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -1521,8 +1828,8 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) continue; } - int iTempGroup = ClientGetPlayerGroup(i); - if (!IsPlayerGroupActive(iTempGroup) || iTempGroup != groupIndex) + int tempGroup = ClientGetPlayerGroup(i); + if (!IsPlayerGroupActive(tempGroup) || tempGroup != groupIndex) { continue; } @@ -1536,8 +1843,8 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) char groupName[SF2_MAX_PLAYER_GROUP_NAME_LENGTH]; GetPlayerGroupName(groupIndex, groupName, sizeof(groupName)); - Handle menuHandle = CreateMenu(Menu_ViewGroupMembersQueue); - SetMenuTitle(menuHandle, "%t%T (%s)\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, groupName); + Menu menuHandle = new Menu(Menu_ViewGroupMembersQueue); + menuHandle.SetTitle("%t %T (%s)\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, groupName); char userId[32]; char name[MAX_NAME_LENGTH * 2]; @@ -1552,11 +1859,11 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) StrCat(name, sizeof(name), " (LEADER)"); } - AddMenuItem(menuHandle, userId, name); + menuHandle.AddItem(userId, name); } - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } else { @@ -1565,12 +1872,9 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) } delete playersArray; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersArray in DisplayViewGroupMembersQueueMenu.", playersArray); - #endif } -static int Menu_ViewGroupMembersQueue(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ViewGroupMembersQueue(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1597,44 +1901,45 @@ void DisplayResetQueuePointsMenu(int client) { char buffer[256]; - Handle menu = CreateMenu(Menu_ResetQueuePoints); + Menu menu = new Menu(Menu_ResetQueuePoints); FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "1", buffer); - SetMenuTitle(menu, "%T\n \n", "SF2 Should Reset Queue Points", client); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.AddItem("1", buffer); + menu.SetTitle("%T\n \n", "SF2 Should Reset Queue Points", client); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_QueuePoints(Handle menu, MenuAction action,int param1,int param2) +static int Menu_QueuePoints(Menu menu, MenuAction action, int param1, int param2) { switch (action) { case MenuAction_Select: { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); - if (strcmp(info, "ponyponypony", false) == 0) + if (strcmp(info, "deathcorridor", false) == 0) { DisplayResetQueuePointsMenu(param1); } else if (!StrContains(info, "player_")) { + // Do nothing } else if (!StrContains(info, "group_")) { - char sIndex[64]; - strcopy(sIndex, sizeof(sIndex), info); - ReplaceString(sIndex, sizeof(sIndex), "group_", ""); - DisplayViewGroupMembersQueueMenu(param1, StringToInt(sIndex)); + char index[64]; + strcopy(index, sizeof(index), info); + ReplaceString(index, sizeof(index), "group_", ""); + DisplayViewGroupMembersQueueMenu(param1, StringToInt(index)); } } case MenuAction_Cancel: { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } case MenuAction_End: @@ -1645,7 +1950,7 @@ static int Menu_QueuePoints(Handle menu, MenuAction action,int param1,int param2 return 0; } -static int Menu_ResetQueuePoints(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ResetQueuePoints(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1686,7 +1991,7 @@ static int Menu_ResetQueuePoints(Handle menu, MenuAction action,int param1,int p void DisplayBossList(int client) { - Handle menu = CreateMenu(Menu_BossList); + Menu menu = new Menu(Menu_BossList); ArrayList bossList = GetBossProfileList(); @@ -1702,15 +2007,25 @@ void DisplayBossList(int client) { strcopy(displayName, sizeof(displayName), profile); } - AddMenuItem(menu, profile, displayName); + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.Description.Hidden) + { + continue; + } + if (data.IsPvEBoss) + { + StrCat(displayName, sizeof(displayName), " (PvE Boss)"); + } + menu.AddItem(profile, displayName); } } - SetMenuTitle(menu, "%t%T\n \n", "SF2 Prefix", "SF2 Boss List Menu Title", client); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Boss List Menu Title", client); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_BossList(Handle menu, MenuAction action,int param1,int param2) +static int Menu_BossList(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1718,9 +2033,134 @@ static int Menu_BossList(Handle menu, MenuAction action,int param1,int param2) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } + + case MenuAction_Select: + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + menu.GetItem(param2, profile, sizeof(profile)); + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + Menu newMenu = new Menu(Menu_BossDisplay); + char buffer[256], buffer2[128]; + data.Names.GetString(Difficulty_Normal, buffer2, sizeof(buffer2)); + FormatEx(buffer, sizeof(buffer), "%t %s\n \n", "SF2 Prefix", buffer2); + FormatEx(buffer2, sizeof(buffer2), "Type: %s\n \n", data.Description.Type); + StrCat(buffer, sizeof(buffer), buffer2); + SF2ChaserBossProfileData chaserData; + if (g_ChaserBossProfileData.GetArray(profile, chaserData, sizeof(chaserData))) + { + FormatEx(buffer2, sizeof(buffer2), "Walk speed: %s\n", ConvertWalkSpeedToDescription(chaserData.WalkSpeed[Difficulty_Normal])); + } + StrCat(buffer, sizeof(buffer), buffer2); + float speed = data.RunSpeed[Difficulty_Normal]; + SF2StatueBossProfileData statueData; + if (g_StatueBossProfileData.GetArray(profile, statueData, sizeof(statueData))) + { + speed *= 10.0; + } + FormatEx(buffer2, sizeof(buffer2), "Run speed: %s\n \n", ConvertRunSpeedToDescription(speed)); + StrCat(buffer, sizeof(buffer), buffer2); + FormatEx(buffer2, sizeof(buffer2), "%s", data.Description.Information); + ReplaceString(buffer2, sizeof(buffer2), "\\n", "\n"); + StrCat(buffer, sizeof(buffer), buffer2); + newMenu.SetTitle(buffer); + newMenu.AddItem("", "", ITEMDRAW_SPACER); + newMenu.ExitBackButton = true; + newMenu.Display(param1, MENU_TIME_FOREVER); + } + + case MenuAction_End: + { + delete menu; + } + } + return 0; +} + +static char[] ConvertWalkSpeedToDescription(float speed) +{ + char buffer[128]; + buffer = "None"; + if (speed > 0.0 && speed <= 45.0) + { + buffer = "Very slow"; + } + else if (speed > 45.0 && speed <= 65.0) + { + buffer = "Slow"; + } + else if (speed > 65.0 && speed <= 85.0) + { + buffer = "Moderate"; + } + else if (speed > 85.0 && speed <= 120.0) + { + buffer = "Average"; + } + else if (speed > 120.0 && speed <= 150.0) + { + buffer = "Fast"; + } + else + { + buffer = "Very fast"; + } + + return buffer; +} + +static char[] ConvertRunSpeedToDescription(float speed) +{ + char buffer[128]; + buffer = "None"; + if (speed > 0.0 && speed <= 50.0) + { + buffer = "Very slow"; + } + else if (speed > 50.0 && speed <= 150.0) + { + buffer = "Slow"; + } + else if (speed > 150.0 && speed <= 250.0) + { + buffer = "Moderate"; + } + else if (speed > 250.0 && speed <= 300.0) + { + buffer = "Average"; + } + else if (speed > 300.0 && speed <= 350.0) + { + buffer = "Fast"; + } + else + { + buffer = "Very fast"; + } + + return buffer; +} + +static int Menu_BossDisplay(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossList(param1); + } + } + + case MenuAction_Select: + { + DisplayBossList(param1); + } + case MenuAction_End: { delete menu; diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index a48e3406..9aeb0a15 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -29,6 +29,14 @@ methodmap SF2NPC_BaseNPC } } + property PathFollower Path + { + public get() + { + return g_BossPathFollower[this.Index]; + } + } + property int ProfileIndex { public get() @@ -53,6 +61,16 @@ methodmap SF2NPC_BaseNPC } } + public static SF2NPC_BaseNPC FromUniqueId(int uniqueID) + { + return SF2NPC_BaseNPC(NPCGetFromUniqueID(uniqueID)); + } + + public static SF2NPC_BaseNPC FromEntity(int entity) + { + return SF2NPC_BaseNPC(NPCGetFromEntIndex(entity)); + } + property int EntRef { public get() @@ -69,12 +87,31 @@ methodmap SF2NPC_BaseNPC } } + public SF2BossProfileData GetProfileData() + { + return NPCGetProfileData(this.Index); + } + + property int Difficulty + { + public get() + { + return GetLocalGlobalDifficulty(this.Index); + } + + public set(int value) + { + NPCChaserSetBoxingDifficulty(this.Index, value); + } + } + property int Flags { public get() { return NPCGetFlags(this.Index); } + public set(int flags) { NPCSetFlags(this.Index, flags); @@ -97,7 +134,17 @@ methodmap SF2NPC_BaseNPC } } - property int RaidHitbox + public bool GetModel(int modelState = 0, char[] buffer, int bufferLen) + { + return GetSlenderModel(this.Index, modelState, buffer, bufferLen); + } + + public int GetMaxCopies(int difficulty) + { + return g_SlenderMaxCopies[this.Index][difficulty]; + } + + property bool RaidHitbox { public get() { @@ -105,43 +152,53 @@ methodmap SF2NPC_BaseNPC } } - property float TurnRate + property float ScareRadius { public get() { - return NPCGetTurnRate(this.Index); + return NPCGetScareRadius(this.Index); } } - property float FOV + property float ScareCooldown { public get() { - return NPCGetFOV(this.Index); + return NPCGetScareCooldown(this.Index); } } - property float ScareRadius + property SF2NPC_BaseNPC CopyMaster { public get() { - return NPCGetScareRadius(this.Index); + return NPCGetCopyMaster(this); + } + + public set(SF2NPC_BaseNPC value) + { + g_SlenderCopyMaster[this.Index] = value.Index; } } - property float ScareCooldown + property bool IsCopy { public get() { - return NPCGetScareCooldown(this.Index); + return this.CopyMaster.IsValid(); } } - property float InstantKillRadius + property SF2NPC_BaseNPC CompanionMaster { public get() { - return NPCGetInstantKillRadius(this.Index); + return NPCGetCompanionMaster(this); + } + + public set(SF2NPC_BaseNPC value) + { + g_SlenderCompanionMaster[this.Index] = value.Index; } } @@ -189,6 +246,7 @@ methodmap SF2NPC_BaseNPC { return NPCHasDeathCamEnabled(this.Index); } + public set(bool state) { NPCSetDeathCamEnabled(this.Index, state); @@ -205,9 +263,20 @@ methodmap SF2NPC_BaseNPC SpawnSlender(this, pos); } - public void UnSpawn() + public void UnSpawn(bool instant = false) { - RemoveSlender(this.Index); + if (IsValidEntity(this.EntIndex)) + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(this.EntIndex); + if (chaser.IsValid() && !instant) + { + chaser.ShouldDespawn = true; + } + else + { + RemoveEntity(this.EntIndex); + } + } } public void Remove() @@ -219,7 +288,7 @@ methodmap SF2NPC_BaseNPC { public get() { - SlenderCanRemove(this.Index); + return SlenderCanRemove(this.Index); } } @@ -243,6 +312,11 @@ methodmap SF2NPC_BaseNPC NPCSetProfile(this.Index, profileName); } + public void GetName(char[] buffer, int bufferLen) + { + NPCGetBossName(this.Index, buffer, bufferLen); + } + public void RemoveFromGame() { RemoveProfile(this.Index); @@ -253,11 +327,6 @@ methodmap SF2NPC_BaseNPC NPCGetDistanceFromEntity(this.Index, entity); } - public float GetSpeed(int difficulty) - { - return NPCGetSpeed(this.Index, difficulty); - } - public float GetAddSpeed() { return NPCGetAddSpeed(this.Index); @@ -268,31 +337,6 @@ methodmap SF2NPC_BaseNPC NPCSetAddSpeed(this.Index, value); } - public float GetMaxSpeed(int difficulty) - { - return NPCGetMaxSpeed(this.Index, difficulty); - } - - public float GetAddMaxSpeed() - { - return NPCGetAddMaxSpeed(this.Index); - } - - public void SetAddMaxSpeed(float value) - { - NPCSetAddMaxSpeed(this.Index, value); - } - - public bool GetAbsOrigin(float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) - { - return SlenderGetAbsOrigin(this.Index, buffer, defaultValue); - } - - public float GetAcceleration(int difficulty) - { - return NPCGetAcceleration(this.Index, difficulty); - } - public float GetAddAcceleration() { return NPCGetAddAcceleration(this.Index); @@ -309,6 +353,7 @@ methodmap SF2NPC_BaseNPC { NPCGetAddAcceleration(this.Index); } + public set(float amount) { NPCSetAddAcceleration(this.Index, amount); @@ -356,7 +401,7 @@ methodmap SF2NPC_BaseNPC return NPCGetAttributeValue(this.Index, attributeIndex); } - public bool CanBeSeen(bool fov = true, bool blink = false) + public bool CanBeSeen(bool fov = true, bool blink = false, bool checkEliminated = true) { return PeopleCanSeeSlender(this.Index, fov, blink); } @@ -370,13 +415,49 @@ methodmap SF2NPC_BaseNPC { NPCAddCompanions(this); } + + public bool IsAffectedBySight() + { + return NPCGetAffectedBySightState(this.Index); + } + + public void SetAffectedBySight(bool state) + { + NPCSetAffectedBySightState(this.Index, state); + } + + property int DefaultTeam + { + public get() + { + return NPCGetDefaultTeam(this.Index); + } + + public set(int value) + { + NPCSetDefaultTeam(this.Index, value); + } + } + + property bool WasKilled + { + public get() + { + return NPCGetWasKilled(this.Index); + } + + public set(bool state) + { + NPCSetWasKilled(this.Index, state); + } + } } methodmap SF2_BasePlayer < CBaseCombatCharacter { public SF2_BasePlayer(int client) { - if (!IsValidClient(client)) + if (client <= 0) { return view_as(SF2_INVALID_PLAYER); } @@ -411,7 +492,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { public get() { - return IsClientInGame(this.index); + return IsClientInGameEx(this.index); } } @@ -443,7 +524,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { public get() { - return !!GetEntProp(this.index, Prop_Send, "m_nForceTauntCam"); + return GetEntProp(this.index, Prop_Send, "m_nForceTauntCam") != 0; } } @@ -455,6 +536,14 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public bool IsMoving() + { + int buttons = this.Buttons; + return buttons & IN_FORWARD || buttons & IN_BACK || buttons & IN_RIGHT || + buttons & IN_LEFT || buttons & IN_MOVERIGHT || buttons & IN_MOVELEFT || + buttons & IN_JUMP; + } + property int Health { public get() @@ -476,6 +565,14 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + property int ModifiedMaxHealth + { + public get() + { + return ClientGetModifiedMaxHealth(this.index); + } + } + property bool Ducking { public get() @@ -519,6 +616,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerHasRegenerationItem[this.index]; } + public set(bool state) { g_PlayerHasRegenerationItem[this.index] = state; @@ -542,6 +640,16 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public bool IsCritBoosted() + { + return IsClientCritBoosted(this.index); + } + + public bool IsMiniCritBoosted() + { + return TF2_IsMiniCritBuffed(this.index); + } + public void Ignite(bool self = false, int attacker = 0, float duration = 10.0) { TF2_IgnitePlayer(this.index, !self && attacker > 0 ? attacker : this.index, duration); @@ -552,7 +660,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter TF2_MakeBleed(this.index, !self && attacker > 0 ? attacker : this.index, duration); } - public void Stun(float duration, float slowdown, int stunflags, int attacker = 0) + public void Stun(float duration, float slowdown = 0.0, int stunflags, int attacker = 0) { TF2_StunPlayer(this.index, duration, slowdown, stunflags, attacker); } @@ -582,7 +690,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter GetEntDataVector(this.index, offset, buffer); } - public void SetDataVector(int offset, const float buffer[3], bool state=false) + public void SetDataVector(int offset, const float buffer[3], bool state = false) { SetEntDataVector(this.index, offset, buffer, state); } @@ -597,19 +705,19 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter return GetPlayerWeaponSlot(this.index, slot); } - public void ScreenShake(float amp, float duration, float freq) + public void SwitchToWeaponSlot(int slot) { - UTIL_ClientScreenShake(this.index, amp, duration, freq); + ClientSwitchToWeaponSlot(this.index, slot); } - public int ShowHudText(Handle hudSync, const char[] buffer) + public void RemoveWeaponSlot(int slot) { - return ShowSyncHudText(this.index, hudSync, buffer); + TF2_RemoveWeaponSlot(this.index, slot); } - public any SDK_Call(Handle handle) + public void ScreenShake(float amp, float duration, float freq) { - return SDKCall(handle, this.index); + UTIL_ClientScreenShake(this.index, amp, duration, freq); } public void ViewPunch(const float punchVel[3]) @@ -634,12 +742,44 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter ClientUpdateListeningFlags(this.index, false); } + property bool IsParticipating + { + public get() + { + return IsClientParticipating(this.index); + } + } + + public bool GetName(char[] name, int length) + { + return GetClientName(this.index, name, length); + } + + property int LastButtons + { + public get() + { + return g_PlayerLastButtons[this.index]; + } + + public set(int value) + { + g_PlayerLastButtons[this.index] = value; + } + } + + public void ScreenFade(int duration, int time, int flags, int r, int g, int b, int a) + { + UTIL_ScreenFade(this.index, duration, time, flags, r, g, b, a); + } + property bool IsEliminated { public get() { return g_PlayerEliminated[this.index]; } + public set(bool state) { g_PlayerEliminated[this.index] = state; @@ -665,6 +805,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerProxy[this.index]; } + public set(bool state) { g_PlayerProxy[this.index] = state; @@ -677,6 +818,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerProxyControl[this.index]; } + public set(int value) { if (value < 0) @@ -697,6 +839,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerProxyMaster[this.index]; } + public set(int value) { g_PlayerProxyMaster[this.index] = value; @@ -711,6 +854,22 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + property bool IsInPvE + { + public get() + { + return IsClientInPvE(this.index); + } + } + + property bool IsInWeaponsTriggers + { + public get() + { + return IsClientInWeaponsTrigger(this.index); + } + } + property bool IsInDeathCam { public get() @@ -719,9 +878,9 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } - public void StartDeathCam(int bossIndex, const float vecLookPos[3], bool antiCamp = false) + public void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false) { - ClientStartDeathCam(this.index, bossIndex, vecLookPos, antiCamp); + ClientStartDeathCam(this.index, bossIndex, lookPos, antiCamp, staticDeath); } property bool HasEscaped @@ -761,6 +920,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return ClientGetFlashlightBatteryLife(this.index); } + public set(float value) { ClientSetFlashlightBatteryLife(this.index, value); @@ -772,6 +932,11 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter ClientResetFlashlight(this.index); } + public float GetFlashlightNextInputTime() + { + return ClientGetFlashlightNextInputTime(this.index); + } + property bool IsSprinting { public get() @@ -788,6 +953,37 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public void HandleSprint(bool sprint) + { + ClientHandleSprint(this.index, sprint); + } + + property float Stamina + { + public get() + { + return ClientGetStamina(this.index); + } + + public set(float value) + { + ClientSetStamina(this.index, value); + } + } + + public void SetStaminaRechargeTime(float time, bool checkTime = true) + { + SetPlayerStaminaRechargeTime(this, time, checkTime); + } + + property bool HasStartedBlinking + { + public get() + { + return ClientHasStartedBlinking(this.index); + } + } + property bool IsBlinking { public get() @@ -796,12 +992,23 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public bool IsHoldingBlink() + { + return IsClientHoldingBlink(this.index); + } + + public void SetHoldingBlink(bool value) + { + ClientSetHoldingBlink(this.index, value); + } + property float BlinkMeter { public get() { return ClientGetBlinkMeter(this.index); } + public set(float amount) { ClientSetBlinkMeter(this.index, amount); @@ -816,9 +1023,9 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } - public void ResetBlink() + public void Blink() { - ClientResetBlink(this.index); + ClientBlink(this.index); } public bool StartPeeking() @@ -826,21 +1033,27 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter return ClientStartPeeking(this.index); } + public void EndPeeking() + { + ClientEndPeeking(this.index); + } + property int PageCount { public get() { return g_PlayerPageCount[this.index]; } + public set(int amount) { g_PlayerPageCount[this.index] = amount; } } - public void ResetHints() + public bool HasHint(int hint) { - ClientResetHints(this.index); + return ClientHasHint(this.index, hint); } public void ShowHint(int hint) @@ -854,6 +1067,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerTrapped[this.index]; } + public set(bool state) { g_PlayerTrapped[this.index] = state; @@ -866,478 +1080,1649 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerTrapCount[this.index]; } + public set(int amount) { g_PlayerTrapCount[this.index] = amount; } } - public void UpdateMusicSystem(bool initialize = false) - { - ClientUpdateMusicSystem(this.index, initialize); - } - - property bool HasConstantGlow + property bool IsLatched { public get() { - return DoesClientHaveConstantGlow(this.index); + return g_PlayerLatchedByTongue[this.index]; } - } - - public void SetPlayState(bool state, bool enablePlay = true) - { - SetClientPlayState(this.index, state, enablePlay); - } - - public bool CanSeeSlender(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) - { - return PlayerCanSeeSlender(this.index, bossIndex, checkFOV, checkBlink, checkEliminated); - } -} -methodmap SF2NPC_Chaser < SF2NPC_BaseNPC -{ - property float WakeRadius - { - public get() + public set(bool state) { - return NPCChaserGetWakeRadius(this.Index); + g_PlayerLatchedByTongue[this.index] = state; } } - property bool StunEnabled + property int LatchCount { public get() { - return NPCChaserIsStunEnabled(this.Index); + return g_PlayerLatchCount[this.index]; } - } - property bool StunByFlashlightEnabled - { - public get() + public set(int amount) { - return NPCChaserIsStunByFlashlightEnabled(this.Index); + g_PlayerLatchCount[this.index] = amount; } } - property float StunFlashlightDamage + property int Latcher { public get() { - return NPCChaserGetStunFlashlightDamage(this.Index); + return g_PlayerLatcher[this.index]; } - } - property float StunCooldown - { - public get() + public set(int value) { - return NPCChaserGetStunCooldown(this.Index); + g_PlayerLatcher[this.index] = value; } } - property float StunHealth + public void UpdateMusicSystem(bool initialize = false) { - public get() - { - return NPCChaserGetStunHealth(this.Index); - } - public set(float amount) - { - NPCChaserSetStunHealth(this.Index, amount); - } + ClientUpdateMusicSystem(this.index, initialize); } - property float InitialStunHealth + public void SetPlayState(bool state, bool enablePlay = true) { - public get() - { - return NPCChaserGetStunInitialHealth(this.Index); - } - public set(float amount) - { - NPCChaserSetStunInitialHealth(this.Index, amount); - } + SetClientPlayState(this.index, state, enablePlay); } - public void TriggerStun(int entity, char profile[SF2_MAX_PROFILE_NAME_LENGTH], float pos[3]) + public bool CanSeeSlender(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { - NPCBossTriggerStun(this.Index, entity, profile, pos); + return PlayerCanSeeSlender(this.index, bossIndex, checkFOV, checkBlink, checkEliminated); } - property bool HasDamageParticles + public void SetAFKTime(bool reset = true) { - public get() - { - return NPCChaserDamageParticlesEnabled(this.Index); - } + AFK_SetTime(this.index); } - property bool UseShootGesture + public void SetAFKState() { - public get() - { - return NPCChaserUseShootGesture(this.Index); - } + AFK_SetAFK(this.index); } - property bool CloakEnabled + public void CheckAFKTime() { - public get() - { - return NPCChaserIsCloakEnabled(this.Index); - } + AFK_CheckTime(this.index); } - public float GetCloakCooldown(int difficulty) + public bool ShouldBeForceChased(SF2NPC_BaseNPC controller) { - return NPCChaserGetCloakCooldown(this.Index, difficulty); + return ShouldClientBeForceChased(controller, this); } - public float GetCloakRange(int difficulty) + public void SetForceChaseState(SF2NPC_BaseNPC controller, bool value) { - return NPCChaserGetCloakRange(this.Index, difficulty); + SetClientForceChaseState(controller, this, value); } - public float GetDecloakRange(int difficulty) + public bool IsLookingAtBoss(SF2NPC_BaseNPC controller) { - return NPCChaserGetDecloakRange(this.Index, difficulty); + return g_PlayerSeesSlender[this.index][controller.Index]; } +} - public float GetCloakSpeedMultiplier(int difficulty) +methodmap SF2NPC_Chaser < SF2NPC_BaseNPC +{ + public SF2ChaserBossProfileData GetProfileData() { - return NPCChaserGetCloakSpeedMultiplier(this.Index, difficulty); + return NPCChaserGetProfileData(this.Index); } - public float GetCrawlSpeedMultiplier(int difficulty) + public float GetInitialDeathHealth(int difficulty) { - return NPCChaserGetCrawlSpeedMultiplier(this.Index, difficulty); + return NPCChaserGetDeathInitialHealth(this.Index, difficulty); } - property bool HasKeyDrop + public float GetDeathHealth(int difficulty) { - public get() - { - return NPCChaseHasKeyDrop(this.Index); - } + return NPCChaserGetDeathHealth(this.Index, difficulty); } - property bool ProjectileEnabled + public void SetDeathHealth(int difficulty, float amount) { - public get() - { - return NPCChaserIsProjectileEnabled(this.Index); - } + NPCChaserSetDeathHealth(this.Index, difficulty, amount); } - property bool ProjectileUsesAmmo + property float StunHealthAdd { public get() { - return NPCChaserUseProjectileAmmo(this.Index); + return NPCChaserGetAddStunHealth(this.Index); + } + + public set(float value) + { + NPCChaserSetAddStunHealth(this.Index, value); } } - property int ProjectileType + property ArrayList ChaseOnLookTargets { public get() { - return NPCChaserGetProjectileType(this.Index); + return NPCChaserGetAutoChaseTargets(this.Index); } } - public float GetProjectileCooldownMin(int difficulty) + public SF2NPC_Chaser(int index) { - return NPCChaserGetProjectileCooldownMin(this.Index, difficulty); + return view_as(SF2NPC_BaseNPC(index)); } +} - public float GetProjectileCooldownMax(int difficulty) +methodmap SF2NPC_Statue < SF2NPC_BaseNPC +{ + public SF2NPC_Statue(int index) { - return NPCChaserGetProjectileCooldownMax(this.Index, difficulty); + return view_as(SF2NPC_BaseNPC(index)); } - public float GetProjectileSpeed(int difficulty) + public SF2StatueBossProfileData GetProfileData() { - return NPCChaserGetProjectileSpeed(this.Index, difficulty); + return NPCStatueGetProfileData(this.Index); } - public float GetProjectileDamage(int difficulty) + public float GetIdleLifetime(int difficulty) { - return NPCChaserGetProjectileDamage(this.Index, difficulty); + return NPCStatueGetIdleLifetime(this.Index, difficulty); } +} + +void SetupMethodmapAPI() +{ + CreateNative("SF2_Player.UserID.get", Native_GetClientUserID); + CreateNative("SF2_Player.IsValid.get", Native_GetIsValidClient); + CreateNative("SF2_Player.IsAlive.get", Native_GetIsClientAlive); + CreateNative("SF2_Player.IsInGame.get", Native_GetIsClientInGame); + CreateNative("SF2_Player.IsBot.get", Native_GetIsClientBot); + CreateNative("SF2_Player.IsSourceTV.get", Native_GetIsClientSourceTV); + CreateNative("SF2_Player.IsReplay.get", Native_GetIsClientReplay); + CreateNative("SF2_Player.InThirdPerson.get", Native_GetIsClientInThirdPerson); + CreateNative("SF2_Player.Buttons.get", Native_GetClientButtons); + CreateNative("SF2_Player.IsMoving", Native_GetIsClientMoving); + CreateNative("SF2_Player.Health.get", Native_GetClientHealth); + CreateNative("SF2_Player.MaxHealth.get", Native_GetClientMaxHealth); + CreateNative("SF2_Player.Ducking.get", Native_GetIsClientDucking); + CreateNative("SF2_Player.Ducked.get", Native_GetIsClientDucked); + CreateNative("SF2_Player.GetDataEnt", Native_GetClientDataEnt); + CreateNative("SF2_Player.Class.get", Native_GetClientClass); + CreateNative("SF2_Player.Team.get", Native_GetClientTeam); + CreateNative("SF2_Player.HasRegenItem.get", Native_GetClientHasRegenItem); + CreateNative("SF2_Player.HasRegenItem.set", Native_SetClientHasRegenItem); + CreateNative("SF2_Player.InCondition", Native_GetClientInCondition); + CreateNative("SF2_Player.ChangeCondition", Native_ClientChangeCondition); + CreateNative("SF2_Player.IsCritBoosted", Native_GetClientIsCritBoosted); + CreateNative("SF2_Player.IsMiniCritBoosted", Native_GetClientIsMiniCritBoosted); + CreateNative("SF2_Player.Ignite", Native_ClientIgnite); + CreateNative("SF2_Player.Bleed", Native_ClientBleed); + CreateNative("SF2_Player.Stun", Native_ClientStun); + CreateNative("SF2_Player.Regenerate", Native_ClientRegenerate); + CreateNative("SF2_Player.SetClass", Native_ClientSetClass); + CreateNative("SF2_Player.GetEyePosition", Native_GetClientEyePosition); + CreateNative("SF2_Player.GetEyeAngles", Native_GetClientEyeAngles); + CreateNative("SF2_Player.GetDataVector", Native_GetClientDataVector); + CreateNative("SF2_Player.SetDataVector", Native_SetClientDataVector); + CreateNative("SF2_Player.GetDistanceFromEntity", Native_GetClientDistanceFromEntity); + CreateNative("SF2_Player.GetWeaponSlot", Native_GetClientWeaponSlot); + CreateNative("SF2_Player.SwitchToWeaponSlot", Native_ClientSwitchToWeaponSlot); + CreateNative("SF2_Player.RemoveWeaponSlot", Native_ClientRemoveWeaponSlot); + CreateNative("SF2_Player.ScreenShake", Native_ClientScreenShake); + CreateNative("SF2_Player.ViewPunch", Native_ClientViewPunch); + CreateNative("SF2_Player.TakeDamage", Native_ClientTakeDamage); + CreateNative("SF2_Player.Respawn", Native_ClientRespawn); + CreateNative("SF2_Player.UpdateListeningFlags", Native_ClientUpdateListeningFlags); + CreateNative("SF2_Player.IsParticipating.get", Native_GetClientIsParticipating); + CreateNative("SF2_Player.GetName", Native_GetClientName); + CreateNative("SF2_Player.LastButtons.get", Native_GetClientLastButtons); + CreateNative("SF2_Player.LastButtons.set", Native_SetClientLastButtons); + CreateNative("SF2_Player.ScreenFade", Native_ClientScreenFade); + CreateNative("SF2_Player.IsEliminated.get", Native_GetClientIsEliminated); + CreateNative("SF2_Player.IsEliminated.set", Native_SetClientIsEliminated); + CreateNative("SF2_Player.IsInGhostMode.get", Native_GetClientIsInGhostMode); + CreateNative("SF2_Player.SetGhostState", Native_SetClientGhostState); + CreateNative("SF2_Player.IsProxy.get", Native_GetClientIsProxy); + CreateNative("SF2_Player.IsProxy.set", Native_SetClientIsProxy); + CreateNative("SF2_Player.ProxyControl.get", Native_GetClientProxyControl); + CreateNative("SF2_Player.ProxyControl.set", Native_SetClientProxyControl); + CreateNative("SF2_Player.ProxyMaster.get", Native_GetClientProxyMaster); + CreateNative("SF2_Player.ProxyMaster.set", Native_SetClientProxyMaster); + CreateNative("SF2_Player.IsInPvP.get", Native_GetClientIsInPvP); + CreateNative("SF2_Player.IsInPvE.get", Native_GetClientIsInPvE); + CreateNative("SF2_Player.IsInDeathCam.get", Native_GetClientIsInDeathCam); + CreateNative("SF2_Player.StartDeathCam", Native_ClientStartDeathCam); + CreateNative("SF2_Player.HasEscaped.get", Native_GetClientHasEscaped); + CreateNative("SF2_Player.Escape", Native_ClientEscape); + CreateNative("SF2_Player.TeleportToEscapePoint", Native_ClientTeleportToEscapePoint); + CreateNative("SF2_Player.ForceEscape", Native_ClientForceEscape); + CreateNative("SF2_Player.UsingFlashlight.get", Native_GetClientUsingFlashlight); + CreateNative("SF2_Player.HandleFlashlight", Native_ClientHandleFlashlight); + CreateNative("SF2_Player.FlashlightBatteryLife.get", Native_GetClientFlashlightBatteryLife); + CreateNative("SF2_Player.FlashlightBatteryLife.set", Native_SetClientFlashlightBatteryLife); + CreateNative("SF2_Player.ResetFlashlight", Native_ClientResetFlashlight); + CreateNative("SF2_Player.GetFlashlightNextInputTime", Native_GetClientFlashlightNextInputTime); + CreateNative("SF2_Player.IsSprinting.get", Native_GetClientIsSprinting); + CreateNative("SF2_Player.IsReallySprinting.get", Native_GetClientIsReallySprinting); + CreateNative("SF2_Player.HandleSprint", Native_ClientHandleSprint); + CreateNative("SF2_Player.Stamina.get", Native_GetClientSprintPoints); + CreateNative("SF2_Player.Stamina.set", Native_SetClientSprintPoints); + CreateNative("SF2_Player.SetStaminaRechargeTime", Native_SetClientStaminaRechargeTime); + CreateNative("SF2_Player.HasStartedBlinking.get", Native_GetClientHasStartedBlinking); + CreateNative("SF2_Player.IsBlinking.get", Native_GetClientIsBlinking); + CreateNative("SF2_Player.IsHoldingBlink", Native_GetClientIsHoldingBlink); + CreateNative("SF2_Player.SetHoldingBlink", Native_SetClientIsHoldingBlink); + CreateNative("SF2_Player.BlinkMeter.get", Native_GetClientBlinkMeter); + CreateNative("SF2_Player.BlinkMeter.set", Native_SetClientBlinkMeter); + CreateNative("SF2_Player.BlinkCount.get", Native_GetClientBlinkCount); + CreateNative("SF2_Player.Blink", Native_ClientBlink); + CreateNative("SF2_Player.StartPeeking", Native_ClientStartPeeking); + CreateNative("SF2_Player.EndPeeking", Native_ClientEndPeeking); + CreateNative("SF2_Player.PageCount.get", Native_GetClientPageCount); + CreateNative("SF2_Player.PageCount.set", Native_SetClientPageCount); + CreateNative("SF2_Player.ShowHint", Native_ClientShowHint); + CreateNative("SF2_Player.IsTrapped.get", Native_GetClientIsTrapped); + CreateNative("SF2_Player.IsTrapped.set", Native_SetClientIsTrapped); + CreateNative("SF2_Player.TrapCount.get", Native_GetClientTrapCount); + CreateNative("SF2_Player.TrapCount.set", Native_SetClientTrapCount); + CreateNative("SF2_Player.IsLatched.get", Native_GetClientIsLatched); + CreateNative("SF2_Player.IsLatched.set", Native_SetClientIsLatched); + CreateNative("SF2_Player.LatchCount.get", Native_GetClientLatchCount); + CreateNative("SF2_Player.LatchCount.set", Native_SetClientLatchCount); + CreateNative("SF2_Player.Latcher.get", Native_GetClientLatcher); + CreateNative("SF2_Player.Latcher.set", Native_SetClientLatcher); + CreateNative("SF2_Player.UpdateMusicSystem", Native_ClientUpdateMusicSystem); + CreateNative("SF2_Player.HasConstantGlow.get", Native_GetClientHasConstantGlow); + CreateNative("SF2_Player.SetPlayState", Native_SetClientPlayState); + CreateNative("SF2_Player.CanSeeSlender", Native_GetClientCanSeeSlender); + CreateNative("SF2_Player.SetAFKTime", Native_SetClientAFKTime); + CreateNative("SF2_Player.SetAFKState", Native_SetClientAFKState); + CreateNative("SF2_Player.CheckAFKTime", Native_CheckClientAFKTime); + CreateNative("SF2_Player.ShouldBeForceChased", Native_GetClientShouldBeForceChased); + CreateNative("SF2_Player.SetForceChaseState", Native_SetClientForceChaseState); + CreateNative("SF2_Player.IsLookingAtBoss", Native_GetClientIsLookingAtBoss); +} - public float GetProjectileRadius(int difficulty) +static any Native_GetClientUserID(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileRadius(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetProjectileReloadTime(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.UserID; +} + +static any Native_GetIsValidClient(Handle plugin, int numParams) +{ + // WOW, the one instance where I don't check if the client is valid but we return it + SF2_BasePlayer player = SF2_BasePlayer(GetNativeCell(1)); + return player.IsValid; +} + +static any Native_GetIsClientAlive(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileReloadTime(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool AdvancedDamageEffectsEnabled + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsAlive; +} + +static any Native_GetIsClientInGame(Handle plugin, int numParams) +{ + SF2_BasePlayer player = SF2_BasePlayer(GetNativeCell(1)); + return player.IsInGame; +} + +static any Native_GetIsClientBot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserUseAdvancedDamageEffects(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool AttachDamageEffectsParticle + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsBot; +} + +static any Native_GetIsClientSourceTV(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserAttachDamageParticle(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool JaratePlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsSourceTV; +} + +static any Native_GetIsClientReplay(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserJaratePlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetJarateDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsReplay; +} + +static any Native_GetIsClientInThirdPerson(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetJarateDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool MilkPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.InThirdPerson; +} + +static any Native_GetClientButtons(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserMilkPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Buttons; +} + +static any Native_GetIsClientMoving(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetMilkDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsMoving(); +} + +static any Native_GetClientHealth(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetMilkDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool GasPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Health; +} + +static any Native_GetClientMaxHealth(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGasPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetGasDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.MaxHealth; +} + +static any Native_GetIsClientDucking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetGasDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool MarkPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Ducking; +} + +static any Native_GetIsClientDucked(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserMarkPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetMarkDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Ducked; +} + +static any Native_GetClientDataEnt(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetMarkDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool IgnitePlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetDataEnt(GetNativeCell(2)); +} + +static any Native_GetClientClass(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserIgnitePlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetIgniteDelay(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Class; +} + +static any Native_GetClientTeam(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetIgniteDelay(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool StunPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Team; +} + +static any Native_GetClientHasRegenItem(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserStunPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetStunAttackDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.HasRegenItem; +} + +static any Native_SetClientHasRegenItem(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetStunAttackDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetStunAttackSlowdown(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.HasRegenItem = GetNativeCell(2); + return 0; +} + +static any Native_GetClientInCondition(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetStunAttackSlowdown(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool BleedPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.InCondition(GetNativeCell(2)); +} + +static any Native_ClientChangeCondition(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserBleedPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetBleedDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ChangeCondition(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5)); + return 0; +} + +static any Native_GetClientIsCritBoosted(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetBleedDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool ElectricPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsCritBoosted(); +} + +static any Native_GetClientIsMiniCritBoosted(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserElectricPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsMiniCritBoosted(); +} + +static any Native_ClientIgnite(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetElectricDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Ignite(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_ClientBleed(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetElectricDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetElectricSlowdown(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Bleed(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_ClientStun(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetElectricSlowdown(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool SmitePlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Stun(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5)); + return 0; +} + +static any Native_ClientRegenerate(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserSmitePlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property int State + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Regenerate(); + return 0; +} + +static any Native_ClientSetClass(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetState(this.Index); - } - public set(int state) - { - NPCChaserSetState(this.Index, state); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool HasTraps + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetClass(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_GetClientEyePosition(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetTrapState(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property int TrapType + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + player.GetEyePosition(buffer); + SetNativeArray(2, buffer, 3); + return 0; +} + +static any Native_GetClientEyeAngles(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetTrapType(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetTrapCooldown(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + player.GetEyeAngles(buffer); + SetNativeArray(2, buffer, 3); + return 0; +} + +static any Native_GetClientDataVector(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetTrapSpawnTime(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool HasCriticalRockets + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + player.GetDataVector(GetNativeCell(2), buffer); + SetNativeArray(3, buffer, 3); + return 0; +} + +static any Native_SetClientDataVector(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserHasCriticalRockets(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetWalkSpeed(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + GetNativeArray(3, buffer, 3); + player.SetDataVector(GetNativeCell(2), buffer); + return 0; +} + +static any Native_GetClientDistanceFromEntity(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetWalkSpeed(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetMaxWalkSpeed(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetDistanceFromEntity(GetNativeCell(2)); +} + +static any Native_GetClientWeaponSlot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetMaxWalkSpeed(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property float StunHealthAdd + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetWeaponSlot(GetNativeCell(2)); +} + +static any Native_ClientSwitchToWeaponSlot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetAddStunHealth(this.Index); - } - public set(float value) - { - NPCChaserSetAddStunHealth(this.Index, value); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SwitchToWeaponSlot(GetNativeCell(2)); + return 0; +} + +static any Native_ClientRemoveWeaponSlot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void AddStunHealth(float amount) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.RemoveWeaponSlot(GetNativeCell(2)); + return 0; +} + +static any Native_ClientScreenShake(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - NPCChaserAddStunHealth(this.Index, amount); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public bool CanDisappearOnStun() + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ScreenShake(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_ClientViewPunch(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserCanDisappearOnStun(this.Index); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool AutoChaseEnabled + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + GetNativeArray(2, buffer, 3); + player.ViewPunch(buffer); + return 0; +} + +static any Native_ClientTakeDamage(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return g_SlenderHasAutoChaseEnabled[this.Index]; - } - public set(bool autoChase) - { - g_SlenderHasAutoChaseEnabled[this.Index] = autoChase; - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool ChasesEndlessly + float force[3], position[3]; + GetNativeArray(8, force, 3); + GetNativeArray(9, position, 3); + SF2_BasePlayer player = SF2_BasePlayer(client); + player.TakeDamage(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), + GetNativeCell(7), force, position, GetNativeCell(10)); + return 0; +} + +static any Native_ClientRespawn(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return g_SlenderChasesEndlessly[this.Index]; - } - public set(bool chaseEndlessly) - { - g_SlenderChasesEndlessly[this.Index] = chaseEndlessly; - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Respawn(); + return 0; +} + +static any Native_ClientUpdateListeningFlags(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void SetGoalPos(float newPos[3]) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.UpdateListeningFlags(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientIsParticipating(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - CopyVector(newPos, g_SlenderGoalPos[this.Index]); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void GetGoalPos(float dest[3]) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsParticipating; +} + +static any Native_GetClientName(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - CopyVector(g_SlenderGoalPos[this.Index], dest); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void UpdateAnimation(int entity, int state) - { - NPCChaserUpdateBossAnimation(this.Index, entity, state); - } + int size; + GetNativeStringLength(2, size); + size++; + char[] buffer = new char[size]; + GetNativeString(2, buffer, size); + SF2_BasePlayer player = SF2_BasePlayer(client); + player.GetName(buffer, size); + SetNativeString(2, buffer, size); + return player.IsParticipating; +} - public SF2NPC_Chaser(int index) +static any Native_GetClientLastButtons(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return view_as(SF2NPC_BaseNPC(index)); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.LastButtons; } -methodmap SF2NPC_Statue < SF2NPC_BaseNPC +static any Native_SetClientLastButtons(Handle plugin, int numParams) { - public SF2NPC_Statue(int index) + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return view_as(SF2NPC_BaseNPC(index)); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.LastButtons = GetNativeCell(2); + return 0; +} + +static any Native_ClientScreenFade(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ScreenFade(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), GetNativeCell(8)); + return 0; +} + +static any Native_GetClientIsEliminated(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsEliminated; +} + +static any Native_SetClientIsEliminated(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsEliminated = GetNativeCell(2); + return 0; +} + +static any Native_GetClientIsInGhostMode(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInGhostMode; +} + +static any Native_SetClientGhostState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetGhostState(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientIsProxy(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsProxy; +} + +static any Native_SetClientIsProxy(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsProxy = GetNativeCell(2); + return 0; +} + +static any Native_GetClientProxyControl(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.ProxyControl; +} + +static any Native_SetClientProxyControl(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ProxyControl = GetNativeCell(2); + return 0; +} + +static any Native_GetClientProxyMaster(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.ProxyMaster; +} + +static any Native_SetClientProxyMaster(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ProxyMaster = GetNativeCell(2); + return 0; +} + +static any Native_GetClientIsInPvP(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInPvP; +} + +static any Native_GetClientIsInPvE(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInPvE; +} + +static any Native_GetClientIsInDeathCam(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInDeathCam; +} + +static any Native_ClientStartDeathCam(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + GetNativeArray(3, buffer, 3); + player.StartDeathCam(GetNativeCell(2), buffer, GetNativeCell(4), GetNativeCell(5)); + return 0; +} + +static any Native_GetClientHasEscaped(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.HasEscaped; +} + +static any Native_ClientEscape(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Escape(); + return 0; +} + +static any Native_ClientTeleportToEscapePoint(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.TeleportToEscapePoint(); + return 0; +} + +static any Native_ClientForceEscape(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Escape(); + player.TeleportToEscapePoint(); + return 0; +} + +static any Native_GetClientUsingFlashlight(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.UsingFlashlight; +} + +static any Native_ClientHandleFlashlight(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.HandleFlashlight(); + return 0; +} + +static any Native_GetClientFlashlightBatteryLife(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.FlashlightBatteryLife; +} + +static any Native_SetClientFlashlightBatteryLife(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.FlashlightBatteryLife = GetNativeCell(2); + return 0; +} + +static any Native_ClientResetFlashlight(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ResetFlashlight(); + return 0; +} + +static any Native_GetClientFlashlightNextInputTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetFlashlightNextInputTime(); +} + +static any Native_GetClientIsSprinting(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsSprinting; +} + +static any Native_GetClientIsReallySprinting(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsReallySprinting; +} + +static any Native_ClientHandleSprint(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.HandleSprint(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientSprintPoints(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Stamina; +} + +static any Native_SetClientSprintPoints(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Stamina = GetNativeCell(2); + return 0; +} + +static any Native_SetClientStaminaRechargeTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetStaminaRechargeTime(GetNativeCell(2), GetNativeCell(3)); + return 0; +} + +static any Native_GetClientHasStartedBlinking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.HasStartedBlinking; +} + +static any Native_GetClientIsBlinking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsBlinking; +} + +static any Native_GetClientIsHoldingBlink(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsHoldingBlink(); +} + +static any Native_SetClientIsHoldingBlink(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetHoldingBlink(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientBlinkMeter(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.BlinkMeter; +} + +static any Native_SetClientBlinkMeter(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.BlinkMeter = GetNativeCell(2); + return 0; +} + +static any Native_GetClientBlinkCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.BlinkCount; +} + +static any Native_ClientBlink(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Blink(); + return 0; +} + +static any Native_ClientStartPeeking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.StartPeeking(); + return 0; +} + +static any Native_ClientEndPeeking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.EndPeeking(); + return 0; +} + +static any Native_GetClientPageCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.PageCount; +} + +static any Native_SetClientPageCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.PageCount = GetNativeCell(2); + return 0; +} + +static any Native_ClientShowHint(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.EndPeeking(); + return 0; +} + +static any Native_GetClientIsTrapped(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsTrapped; +} + +static any Native_SetClientIsTrapped(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsTrapped = GetNativeCell(2); + return 0; +} + +static any Native_GetClientTrapCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.TrapCount; +} + +static any Native_SetClientTrapCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.TrapCount = GetNativeCell(2); + return 0; +} + +static any Native_GetClientIsLatched(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsLatched; +} + +static any Native_SetClientIsLatched(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsLatched = GetNativeCell(2); + return 0; +} + +static any Native_GetClientLatchCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.LatchCount; +} + +static any Native_SetClientLatchCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.LatchCount = GetNativeCell(2); + return 0; +} + +static any Native_GetClientLatcher(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Latcher; +} + +static any Native_SetClientLatcher(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Latcher = GetNativeCell(2); + return 0; +} + +static any Native_ClientUpdateMusicSystem(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.UpdateMusicSystem(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientHasConstantGlow(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + return DoesEntityHaveGlow(client); +} + +static any Native_SetClientPlayState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetPlayState(GetNativeCell(2), GetNativeCell(3)); + return 0; +} + +static any Native_GetClientCanSeeSlender(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.CanSeeSlender(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5)); +} + +static any Native_SetClientAFKTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetPlayState(GetNativeCell(2)); + return 0; +} + +static any Native_SetClientAFKState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetPlayState(GetNativeCell(2), GetNativeCell(3)); + return 0; +} + +static any Native_CheckClientAFKTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.CheckAFKTime(); +} + +static any Native_GetClientShouldBeForceChased(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(GetNativeCell(2)); + return player.ShouldBeForceChased(npc); +} + +static any Native_SetClientForceChaseState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(GetNativeCell(2)); + player.SetForceChaseState(npc, GetNativeCell(3)); + return 0; +} + +static any Native_GetClientIsLookingAtBoss(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(GetNativeCell(2)); + return player.IsLookingAtBoss(npc); } diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 1bdc9b61..f6b5f452 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -5,13 +5,15 @@ #pragma semicolon 1 -#define SF2_BOSS_PAGE_CALCULATION 0.3 +#define SF2_BOSS_PAGE_CALCULATION 512.0 #define SF2_BOSS_COPY_SPAWN_MIN_DISTANCE 800.0 // The default minimum distance boss copies can spawn from each other. #define SF2_BOSS_ATTACK_MELEE 0 static int g_NpcGlobalUniqueID = 0; +static SF2BossProfileData g_NpcProfileData[MAX_BOSSES]; + static int g_NpcUniqueID[MAX_BOSSES] = { -1, ... }; static char g_SlenderProfile[MAX_BOSSES][SF2_MAX_PROFILE_NAME_LENGTH]; static int g_NpcProfileIndex[MAX_BOSSES] = { -1, ... }; @@ -25,12 +27,11 @@ static int g_NpcModelSkinMax[MAX_BOSSES] = { 0, ... }; static int g_NpcModelBodyGroups[MAX_BOSSES] = { 0, ... }; static int g_NpcModelBodyGroupsDifficulty[MAX_BOSSES][Difficulty_Max]; static int g_NpcModelBodyGroupsMax[MAX_BOSSES] = { 0, ... }; -int g_NpcRaidHitbox[MAX_BOSSES] = { 0, ... }; -int g_NpcGlowEntity[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; +bool g_NpcRaidHitbox[MAX_BOSSES] = { false, ... }; static float g_NpcSoundMusicLoop[MAX_BOSSES][Difficulty_Max]; static int g_NpcAllowMusicOnDifficulty[MAX_BOSSES]; static char g_NpcName[MAX_BOSSES][Difficulty_Max][SF2_MAX_PROFILE_NAME_LENGTH]; -static bool g_NpcHasFakeCopiesEnabled[MAX_BOSSES]; +static bool g_NpcHasFakeCopiesEnabled[MAX_BOSSES][Difficulty_Max]; static float g_NpcBlinkLookRate[MAX_BOSSES]; static float g_NpcBlinkStaticRate[MAX_BOSSES]; static float g_NpcStepSize[MAX_BOSSES]; @@ -49,8 +50,6 @@ static float g_NpcFestiveLightPos[MAX_BOSSES][3]; static float g_NpcFestiveLightAng[MAX_BOSSES][3]; static float g_NpcFieldOfView[MAX_BOSSES] = { 0.0, ... }; -static float g_NpcTurnRate[MAX_BOSSES] = { 0.0, ... }; -static float g_NpcBackstabFOV[MAX_BOSSES] = { 0.0, ... }; static bool g_NpcHasTeleportAllowed[MAX_BOSSES][Difficulty_Max]; static float g_NpcTeleportTimeMin[MAX_BOSSES][Difficulty_Max]; @@ -64,14 +63,10 @@ static float g_NpcJumpscareDistance[MAX_BOSSES][Difficulty_Max]; static float g_NpcJumpscareDuration[MAX_BOSSES][Difficulty_Max]; static float g_NpcJumpscareCooldown[MAX_BOSSES][Difficulty_Max]; static bool g_NpcHasJumpscareOnScare[MAX_BOSSES]; -bool g_NpcIgnoreNonMarkedForChase[MAX_BOSSES]; -g_SlenderEnt[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; +int g_SlenderEnt[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -static float g_NpcSpeed[MAX_BOSSES][Difficulty_Max]; -static float g_NpcMaxSpeed[MAX_BOSSES][Difficulty_Max]; static float g_NpcAddSpeed[MAX_BOSSES]; -static float g_NpcAddMaxSpeed[MAX_BOSSES]; static float g_NpcAddAcceleration[MAX_BOSSES]; static float g_NpcIdleLifetime[MAX_BOSSES][Difficulty_Max]; @@ -85,24 +80,14 @@ static bool g_NpcHasPlayerScareReaction[MAX_BOSSES]; static int g_NpcPlayerScareReactionType[MAX_BOSSES]; static bool g_NpcHasPlayerScareReplenishSprint[MAX_BOSSES]; -static int g_NpcPlayerScareReplenishSprintAmount[MAX_BOSSES]; +static float g_NpcPlayerScareReplenishSprintAmount[MAX_BOSSES]; static int g_NpcTeleportType[MAX_BOSSES] = { -1, ... }; -static float g_NpcSearchRadius[MAX_BOSSES][Difficulty_Max]; -static float g_NpcHearingRadius[MAX_BOSSES][Difficulty_Max]; -static float g_NpcTauntAlertRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcInstantKillRadius[MAX_BOSSES] = { 0.0, ... }; -static float g_NpcInstantKillCooldown[MAX_BOSSES][Difficulty_Max]; - static bool g_NpcHasDeathCamEnabled[MAX_BOSSES] = { false, ... }; static bool g_NpcHasProxyWeaponsEnabled[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasHealthbarEnabled[MAX_BOSSES] = { false, ... }; - -static int g_NpcEnemy[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; - static float g_NpcProxySpawnChanceMin[MAX_BOSSES][Difficulty_Max]; static float g_NpcProxySpawnChanceMax[MAX_BOSSES][Difficulty_Max]; static float g_NpcProxySpawnChanceThreshold[MAX_BOSSES][Difficulty_Max]; @@ -121,16 +106,15 @@ static int g_NpcDrainCreditAmount[MAX_BOSSES][Difficulty_Max]; static bool g_NpcHasProxySpawnEffectEnabled[MAX_BOSSES]; static float g_NpcProxySpawnEffectZOffset[MAX_BOSSES]; -Handle timerMusic = null;//Planning to add a bosses array on. +static bool g_NpcShouldBeAffectedBySight[MAX_BOSSES]; -float g_LastStuckTime[MAX_BOSSES] = {-1.0, ...}; -bool g_NpcHasCloaked[MAX_BOSSES] = { false, ... }; -bool g_NpcUsedRage1[MAX_BOSSES] = { false, ... }; -bool g_NpcUsedRage2[MAX_BOSSES] = { false, ... }; -bool g_NpcHasUsedRage3[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesRageAnimation[MAX_BOSSES][3]; +static int g_NpcDefaultTeam[MAX_BOSSES]; -bool NPCGetBossName(int npcIndex = -1, char[] buffer,int bufferLen, char profile[SF2_MAX_PROFILE_NAME_LENGTH] = "") +static bool g_NpcWasKilled[MAX_BOSSES]; + +Handle timerMusic = null; //Planning to add a bosses array on. + +bool NPCGetBossName(int npcIndex = -1, char[] buffer, int bufferLen, char profile[SF2_MAX_PROFILE_NAME_LENGTH] = "") { if (npcIndex == -1 && profile[0] == '\0') { @@ -212,7 +196,271 @@ void NPCSetDeathCamEnabled(int npcIndex, bool state) void NPCInitialize() { + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + g_OnEntityTeleportedPFwd.AddFunction(null, EntityTeleported); + g_OnPlayerLookAtBossPFwd.AddFunction(null, OnPlayerLookAtBoss); + NPCChaserInitialize(); + SetupNPCGlows(); +} + +void NPC_InitializeAPI() +{ + g_OnBossAddedFwd = new GlobalForward("SF2_OnBossAdded", ET_Ignore, Param_Cell); + g_OnBossSpawnFwd = new GlobalForward("SF2_OnBossSpawn", ET_Ignore, Param_Cell); + g_OnBossDespawnFwd = new GlobalForward("SF2_OnBossDespawn", ET_Ignore, Param_Cell); + g_OnBossRemovedFwd = new GlobalForward("SF2_OnBossRemoved", ET_Ignore, Param_Cell); + g_OnBossFinishSpawningFwd = new GlobalForward("SF2_OnBossFinishSpawning", ET_Ignore, Param_Cell); + g_OnClientCaughtByBossFwd = new GlobalForward("SF2_OnClientCaughtByBoss", ET_Ignore, Param_Cell, Param_Cell); + + CreateNative("SF2_GetMaxBossCount", Native_GetMaxBosses); + CreateNative("SF2_EntIndexToBossIndex", Native_EntIndexToBossIndex); + CreateNative("SF2_BossIndexToEntIndex", Native_BossIndexToEntIndex); + CreateNative("SF2_BossIndexToEntIndexEx", Native_BossIndexToEntIndexEx); + CreateNative("SF2_BossIDToBossIndex", Native_BossIDToBossIndex); + CreateNative("SF2_BossIndexToBossID", Native_BossIndexToBossID); + + CreateNative("SF2_AddBoss", Native_AddBoss); + CreateNative("SF2_RemoveBoss", Native_RemoveBoss); + + CreateNative("SF2_GetBossName", Native_GetBossName); + CreateNative("SF2_GetBossType", Native_GetBossType); + + CreateNative("SF2_GetBossFlags", Native_GetBossFlags); + CreateNative("SF2_SetBossFlags", Native_SetBossFlags); + + CreateNative("SF2_SpawnBoss", Native_SpawnBoss); + CreateNative("SF2_IsBossSpawning", Native_IsBossSpawning); + CreateNative("SF2_DespawnBoss", Native_DespawnBoss); + + CreateNative("SF2_GetBossPathFollower", Native_GetBossPathFollower); + CreateNative("SF2_GetBossMaster", Native_GetBossMaster); + CreateNative("SF2_GetBossIdleLifetime", Native_GetBossIdleLifetime); + CreateNative("SF2_GetBossState", Native_GetBossState); + CreateNative("SF2_SetBossState", Native_SetBossState); + + CreateNative("SF2_GetBossEyePosition", Native_GetBossEyePosition); + CreateNative("SF2_GetBossEyePositionOffset", Native_GetBossEyePositionOffset); + + CreateNative("SF2_GetBossTeleportThinkTimer", Native_GetBossTeleportThinkTimer); + CreateNative("SF2_SetBossTeleportThinkTimer", Native_SetBossTeleportThinkTimer); + CreateNative("SF2_GetBossTeleportTarget", Native_GetBossTeleportTarget); + + CreateNative("SF2_GetBossGoalPosition", Native_GetBossGoalPosition); + + CreateNative("SF2_GetBossTimeUntilNoPersistence", Native_Nothing); + CreateNative("SF2_SetBossTimeUntilNoPersistence", Native_Nothing); + CreateNative("SF2_GetBossTimeUntilAlert", Native_GetBossCurrentChaseDuration); + CreateNative("SF2_SetBossTimeUntilAlert", Native_SetBossCurrentChaseDuration); + + CreateNative("SF2_GetProfileFromBossIndex", Native_GetProfileData); + CreateNative("SF2_GetProfileFromName", Native_GetProfileDataEx); + + CreateNative("SF2_SpawnBossEffects", Native_SpawnBossEffects); + + CreateNative("SF2_CanBossBeSeen", Native_CanBossBeSeen); +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC.FromEntity(ent.index); + if (controller.IsValid()) + { + NPCOnDespawn(controller, ent); + } +} + +static void EntityTeleported(CBaseEntity teleporter, CBaseEntity activator) +{ + int flags = teleporter.GetProp(Prop_Data, "m_spawnflags"); + if (((flags & TRIGGER_CLIENTS) != 0 && (flags & TRIGGER_NPCS)) || (flags & TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS) != 0) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(NPCGetFromEntIndex(activator.index)); + if (controller.IsValid()) + { + // A boss took a teleporter, remove it from our list if possible + SF2_BaseBoss boss = SF2_BaseBoss(controller.EntIndex); + ArrayList teleporters = boss.Teleporters; + int index = teleporters.FindValue(teleporter.index); + if (index != -1) + { + teleporters.Erase(index); + } + } + else + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + SF2_BaseBoss boss = SF2_BaseBoss(npc.EntIndex); + if (!SF2_ChaserEntity(npc.EntIndex).IsValid() && !SF2_StatueEntity(npc.EntIndex).IsValid()) + { + continue; + } + + if (boss.Target == activator) + { + // The boss target took a teleporter and the boss can follow, add the teleporter to the list of teleporters + boss.Teleporters.Push(teleporter.index); + } + } + } + } + else + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i); + if (!controller.IsValid()) + { + continue; + } + + SF2_BaseBoss boss = SF2_BaseBoss(controller.EntIndex); + if (!SF2_ChaserEntity(controller.EntIndex).IsValid() && !SF2_StatueEntity(controller.EntIndex).IsValid()) + { + continue; + } + + // Currently a boss is chasing this entity but the boss cannot follow, abort the chase state + if (boss.Target == activator) + { + boss.Target = CBaseEntity(-1); + boss.OldTarget = CBaseEntity(-1); + + // OR if we're supposed to endlessly chase, despawn us otherwise that'd lead to some bad moments + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) + { + controller.UnSpawn(); + } + + if (controller.Type == SF2BossType_Chaser) + { + if (view_as(controller).GetProfileData().ChasesEndlessly) + { + controller.UnSpawn(); + } + } + } + } + } +} + +static void OnPlayerLookAtBoss(SF2_BasePlayer client, SF2NPC_BaseNPC boss) +{ + switch (boss.Type) + { + case SF2BossType_Statue: + { + if (g_PlayerPreferences[client.index].PlayerPreference_ShowHints && !client.HasHint(PlayerHint_Blink)) + { + client.ShowHint(PlayerHint_Blink); + } + } + } +} + +void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity, bool killed = false) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + controller.GetProfile(profile, sizeof(profile)); + int bossIndex = controller.Index; + SF2BossProfileData data; + data = controller.GetProfileData(); + Call_StartForward(g_OnBossDespawnFwd); + Call_PushCell(bossIndex); + Call_Finish(); + + //Turn off all slender's effects in order to prevent some bugs. + SlenderRemoveEffects(bossIndex, true); + + if (controller.Type == SF2BossType_Statue) + { + Despawn_Statue(SF2NPC_Statue(bossIndex), entity); + } + else + { + Despawn_Chaser(bossIndex); + } + + if (g_BossPathFollower[bossIndex] != view_as(0) && g_BossPathFollower[bossIndex].IsValid()) + { + g_BossPathFollower[bossIndex].Invalidate(); + } + + if (data.DespawnEffects != null) + { + if (killed && data.HideDespawnEffectsOnDeath) + { + // Do nothing + } + else + { + StringMapSnapshot snapshot = data.DespawnEffects.Snapshot(); + char key[64]; + snapshot.GetKey(GetRandomInt(0, snapshot.Length - 1), key, sizeof(key)); + ArrayList effects; + data.DespawnEffects.GetValue(key, effects); + float pos[3], ang[3]; + CBaseEntity(NPCGetEntIndex(bossIndex)).GetAbsOrigin(pos); + CBaseEntity(NPCGetEntIndex(bossIndex)).GetAbsAngles(ang); + SlenderSpawnEffects(effects, bossIndex, false, pos, ang, _, _, true); + delete snapshot; + } + } + + if (data.EngineSound[0] != '\0') + { + StopSound(entity.index, SNDCHAN_STATIC, data.EngineSound); + } + + if (NPCGetFlags(bossIndex) & SFF_HASSTATICLOOPLOCALSOUND) + { + char loopSound[PLATFORM_MAX_PATH]; + GetBossProfileStaticLocalSound(profile, loopSound, sizeof(loopSound)); + + if (loopSound[0] != '\0') + { + StopSound(entity.index, SNDCHAN_STATIC, loopSound); + } + } + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || data.Healthbar) + { + controller.Flags = controller.Flags & ~SFF_NOTELEPORT; + } + + g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; + if (data.Healthbar && g_HealthBar != -1) + { + int npcIndex = 0; + for (npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + { + if (NPCGetUniqueID(npcIndex) == -1) + { + continue; + } + data = NPCGetProfileData(npcIndex); + if (data.Healthbar) + { + int tempSlender = NPCGetEntIndex(npcIndex); + + if (tempSlender && tempSlender != INVALID_ENT_REFERENCE) + { + UpdateHealthBar(npcIndex); + break; + } + } + } + if (npcIndex == MAX_BOSSES) + { + UpdateHealthBar(bossIndex, 0); + } + } } void NPCOnConfigsExecuted() @@ -230,16 +478,16 @@ int NPCGetUniqueID(int npcIndex) return g_NpcUniqueID[npcIndex]; } -int NPCGetFromUniqueID(int iNPCUniqueID) +int NPCGetFromUniqueID(int NPCUniqueID) { - if (iNPCUniqueID == -1) + if (NPCUniqueID == -1) { return -1; } for (int i = 0; i < MAX_BOSSES; i++) { - if (NPCGetUniqueID(i) == iNPCUniqueID) + if (NPCGetUniqueID(i) == NPCUniqueID) { return i; } @@ -306,12 +554,22 @@ int NPCGetUniqueProfileIndex(int npcIndex) return g_NpcUniqueProfileIndex[npcIndex]; } -bool NPCGetProfile(int npcIndex, char[] buffer,int bufferLen) +bool NPCGetProfile(int npcIndex, char[] buffer, int bufferLen) { strcopy(buffer, bufferLen, g_SlenderProfile[npcIndex]); return true; } +SF2BossProfileData NPCGetProfileData(int npcIndex) +{ + return g_NpcProfileData[npcIndex]; +} + +void NPCSetProfileData(int npcIndex, SF2BossProfileData value) +{ + g_NpcProfileData[npcIndex] = value; +} + void NPCSetProfile(int npcIndex, const char[] profile) { strcopy(g_SlenderProfile[npcIndex], sizeof(g_SlenderProfile[]), profile); @@ -326,6 +584,7 @@ void NPCRemove(int npcIndex) RemoveProfile(npcIndex); } + void NPCStopMusic() { //Stop the music timer @@ -334,7 +593,7 @@ void NPCStopMusic() delete timerMusic; } //Stop the music for all players. - for(int i = 1;i<=MaxClients;i++) + for (int i = 1; i <= MaxClients; i++) { if (IsValidClient(i)) { @@ -346,6 +605,7 @@ void NPCStopMusic() } } } + void CheckIfMusicValid() { int difficulty = g_DifficultyConVar.IntValue; @@ -355,6 +615,12 @@ void CheckIfMusicValid() { continue; } + SF2BossProfileData data; + data = NPCGetProfileData(i); + if (data.IsPvEBoss) + { + continue; + } if (g_NpcAllowMusicOnDifficulty[i] & difficulty) { currentMusicTrackNormal[0] = '\0'; @@ -364,7 +630,7 @@ void CheckIfMusicValid() currentMusicTrackApollyon[0] = '\0'; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(i, profile, sizeof(profile)); - for(int client = 1;client <=MaxClients;client ++) + for(int client = 1;client <= MaxClients; client++) { if (IsValidClient(client) && (!g_PlayerEliminated[client] || IsClientInGhostMode(client))) { @@ -501,30 +767,30 @@ void CheckIfMusicValid() switch (g_DifficultyConVar.IntValue) { - case Difficulty_Normal, Difficulty_Easy: + case Difficulty_Normal: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNormal); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNormal); } case Difficulty_Hard: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackHard); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackHard); } case Difficulty_Insane: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackInsane); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackInsane); } case Difficulty_Nightmare: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNightmare); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNightmare); } case Difficulty_Apollyon: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackApollyon); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackApollyon); } } if (currentMusicTrack[0] != '\0') { - timerMusic = CreateTimer(NPCGetSoundMusicLoop(i, difficulty),BossMusic,i,TIMER_FLAG_NO_MAPCHANGE); + timerMusic = CreateTimer(NPCGetSoundMusicLoop(i, difficulty), BossMusic, i, TIMER_FLAG_NO_MAPCHANGE); StopSound(client, MUSIC_CHAN, currentMusicTrack); ClientChaseMusicReset(client); ClientChaseMusicSeeReset(client); @@ -546,24 +812,16 @@ void CheckIfMusicValid() ClientUpdateMusicSystem(client); break; } - else - { - - } } } break; } - else - { - - } } } bool MusicActive() { - if (timerMusic!=null) + if (timerMusic != null) { return true; } @@ -776,6 +1034,7 @@ bool BossHasMusic(char[] profile) } return false; } + bool BossMatchesCurrentMusic(char[] profile) { if (!IsProfileValid(profile)) @@ -888,6 +1147,7 @@ bool BossMatchesCurrentMusic(char[] profile) return false; } + void GetBossMusic(char[] buffer,int bufferLen) { int difficulty = g_DifficultyConVar.IntValue; @@ -895,49 +1155,52 @@ void GetBossMusic(char[] buffer,int bufferLen) { case Difficulty_Normal: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNormal); - strcopy(buffer,bufferLen,currentMusicTrackNormal); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNormal); + strcopy(buffer, bufferLen, currentMusicTrackNormal); } case Difficulty_Hard: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackHard); - strcopy(buffer,bufferLen,currentMusicTrackHard); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackHard); + strcopy(buffer, bufferLen, currentMusicTrackHard); } case Difficulty_Insane: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackInsane); - strcopy(buffer,bufferLen,currentMusicTrackInsane); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackInsane); + strcopy(buffer, bufferLen, currentMusicTrackInsane); } case Difficulty_Nightmare: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNightmare); - strcopy(buffer,bufferLen,currentMusicTrackNightmare); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNightmare); + strcopy(buffer, bufferLen, currentMusicTrackNightmare); } case Difficulty_Apollyon: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackApollyon); - strcopy(buffer,bufferLen,currentMusicTrackApollyon); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackApollyon); + strcopy(buffer, bufferLen, currentMusicTrackApollyon); } } } -static Action BossMusic(Handle timer,any bossIndex) + +static Action BossMusic(Handle timer, any bossIndex) { int difficulty = g_DifficultyConVar.IntValue; float time = NPCGetSoundMusicLoop(bossIndex, difficulty); - if (time > 0.0 && (g_NpcAllowMusicOnDifficulty[bossIndex] & difficulty)) + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); + if (!data.IsPvEBoss && time > 0.0 && (g_NpcAllowMusicOnDifficulty[bossIndex] & difficulty)) { if (bossIndex > -1) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - for(int i = 1;i<=MaxClients;i++) + for (int i = 1; i <= MaxClients; i++) { if (IsValidClient(i) && currentMusicTrack[0] != '\0') { StopSound(i, MUSIC_CHAN, currentMusicTrack); } } - timerMusic = CreateTimer(time,BossMusic,bossIndex,TIMER_FLAG_NO_MAPCHANGE); + timerMusic = CreateTimer(time, BossMusic, bossIndex, TIMER_FLAG_NO_MAPCHANGE); return Plugin_Continue; } NPCStopMusic(); @@ -945,6 +1208,7 @@ static Action BossMusic(Handle timer,any bossIndex) timerMusic = null; return Plugin_Continue; } + void NPCRemoveAll() { for (int i = 0; i < MAX_BOSSES; i++) @@ -1008,7 +1272,7 @@ int NPCGetModelBodyGroupsMax(int npcIndex) return g_NpcModelBodyGroupsMax[npcIndex]; } -int NPCGetRaidHitbox(int npcIndex) +bool NPCGetRaidHitbox(int npcIndex) { return g_NpcRaidHitbox[npcIndex]; } @@ -1078,61 +1342,16 @@ bool NPCGetCustomOutlinesState(int npcIndex) return g_SlenderUseCustomOutlines[npcIndex]; } -int NPCGetOutlineColorR(int npcIndex) -{ - return g_SlenderOutlineColorR[npcIndex]; -} - -int NPCGetOutlineColorG(int npcIndex) -{ - return g_SlenderOutlineColorG[npcIndex]; -} - -int NPCGetOutlineColorB(int npcIndex) -{ - return g_SlenderOutlineColorB[npcIndex]; -} - -int NPCGetOutlineTransparency(int npcIndex) -{ - return g_SlenderOutlineTransparency[npcIndex]; -} - bool NPCGetRainbowOutlineState(int npcIndex) { return g_SlenderUseRainbowOutline[npcIndex]; } -float NPCGetRainbowOutlineCycleRate(int npcIndex) -{ - return g_SlenderRainbowCycleRate[npcIndex]; -} - -float NPCGetSpeed(int npcIndex,int difficulty) -{ - return g_NpcSpeed[npcIndex][difficulty]; -} - -float NPCGetMaxSpeed(int npcIndex,int difficulty) -{ - return g_NpcMaxSpeed[npcIndex][difficulty]; -} - -float NPCGetAcceleration(int npcIndex,int difficulty) -{ - return g_SlenderAcceleration[npcIndex][difficulty]; -} - void NPCSetAddSpeed(int npcIndex, float amount) { g_NpcAddSpeed[npcIndex] += amount; } -void NPCSetAddMaxSpeed(int npcIndex, float amount) -{ - g_NpcAddMaxSpeed[npcIndex] += amount; -} - void NPCSetAddAcceleration(int npcIndex, float amount) { g_NpcAddAcceleration[npcIndex] += amount; @@ -1143,11 +1362,6 @@ float NPCGetAddSpeed(int npcIndex) return g_NpcAddSpeed[npcIndex]; } -float NPCGetAddMaxSpeed(int npcIndex) -{ - return g_NpcAddMaxSpeed[npcIndex]; -} - float NPCGetAddAcceleration(int npcIndex) { return g_NpcAddAcceleration[npcIndex]; @@ -1198,6 +1412,16 @@ int NPCGetTeleporter(int bossIndex, int teleporterNumber) return g_NpcTeleporters[bossIndex][teleporterNumber]; } +SF2NPC_BaseNPC NPCGetCopyMaster(SF2NPC_BaseNPC controller) +{ + return SF2NPC_BaseNPC(g_SlenderCopyMaster[controller.Index]); +} + +SF2NPC_BaseNPC NPCGetCompanionMaster(SF2NPC_BaseNPC controller) +{ + return SF2NPC_BaseNPC(g_SlenderCompanionMaster[controller.Index]); +} + float NPCGetJumpscareDistance(int npcIndex, int difficulty) { return g_NpcJumpscareDistance[npcIndex][difficulty]; @@ -1218,21 +1442,6 @@ bool NPCGetJumpscareOnScare(int npcIndex) return g_NpcHasJumpscareOnScare[npcIndex]; } -float NPCGetTurnRate(int npcIndex) -{ - return g_NpcTurnRate[npcIndex]; -} - -float NPCGetFOV(int npcIndex) -{ - return g_NpcFieldOfView[npcIndex]; -} - -float NPCGetBackstabFOV(int npcIndex) -{ - return g_NpcBackstabFOV[npcIndex]; -} - float NPCGetIdleLifetime(int npcIndex,int difficulty) { return g_NpcIdleLifetime[npcIndex][difficulty]; @@ -1245,21 +1454,6 @@ void NPCGetEyePositionOffset(int npcIndex, float buffer[3]) buffer[2] = g_SlenderEyePosOffset[npcIndex][2]; } -float NPCGetSearchRadius(int npcIndex, int difficulty) -{ - return g_NpcSearchRadius[npcIndex][difficulty]; -} - -float NPCGetHearingRadius(int npcIndex, int difficulty) -{ - return g_NpcHearingRadius[npcIndex][difficulty]; -} - -float NPCGetTauntAlertRange(int npcIndex, int difficulty) -{ - return g_NpcTauntAlertRange[npcIndex][difficulty]; -} - float NPCGetScareRadius(int npcIndex) { return g_NpcScareRadius[npcIndex]; @@ -1295,34 +1489,19 @@ bool NPCGetScareReplenishSprintState(int npcIndex) return g_NpcHasPlayerScareReplenishSprint[npcIndex]; } -int NPCGetScareReplenishSprintAmount(int npcIndex) +float NPCGetScareReplenishSprintAmount(int npcIndex) { return g_NpcPlayerScareReplenishSprintAmount[npcIndex]; } -float NPCGetInstantKillRadius(int npcIndex) -{ - return g_NpcInstantKillRadius[npcIndex]; -} - -float NPCGetInstantKillCooldown(int npcIndex, int difficulty) -{ - return g_NpcInstantKillCooldown[npcIndex][difficulty]; -} - int NPCGetTeleportType(int npcIndex) { return g_NpcTeleportType[npcIndex]; } -bool NPCGetHealthbarState(int npcIndex) -{ - return g_NpcHasHealthbarEnabled[npcIndex]; -} - -bool NPCGetFakeCopyState(int npcIndex) +bool NPCGetFakeCopyState(int npcIndex, int difficulty) { - return g_NpcHasFakeCopiesEnabled[npcIndex]; + return g_NpcHasFakeCopiesEnabled[npcIndex][difficulty]; } #if defined _store_included @@ -1382,16 +1561,34 @@ float NPCGetProxySpawnCooldownMax(int npcIndex, int difficulty) return g_NpcProxySpawnCooldownMax[npcIndex][difficulty]; } -bool NPCIsRaging(int npcIndex) +bool NPCGetAffectedBySightState(int npcIndex) { - for (int i = 0; i < 3; i++) - { - if (g_NpcUsesRageAnimation[npcIndex][i]) - { - return true; - } - } - return false; + return g_NpcShouldBeAffectedBySight[npcIndex]; +} + +void NPCSetAffectedBySightState(int npcIndex, bool state) +{ + g_NpcShouldBeAffectedBySight[npcIndex] = state; +} + +int NPCGetDefaultTeam(int npcIndex) +{ + return g_NpcDefaultTeam[npcIndex]; +} + +void NPCSetDefaultTeam(int npcIndex, int team) +{ + g_NpcDefaultTeam[npcIndex] = team; +} + +bool NPCGetWasKilled(int npcIndex) +{ + return g_NpcWasKilled[npcIndex]; +} + +void NPCSetWasKilled(int npcIndex, bool state) +{ + g_NpcWasKilled[npcIndex] = state; } bool NPCShouldSeeEntity(int npcIndex, int entity) @@ -1422,6 +1619,11 @@ bool NPCShouldHearEntity(int npcIndex, int entity, SoundType soundType) return false; } + if (NPCGetType(npcIndex) == SF2BossType_Statue) + { + return false; + } + Action result = Plugin_Continue; Call_StartForward(g_OnBossHearEntityFwd); Call_PushCell(npcIndex); @@ -1453,7 +1655,7 @@ bool NPCAreAvailablePlayersAlive() /** * Returns the boss's eye position (eye pos offset + absorigin). */ -bool NPCGetEyePosition(int npcIndex, float buffer[3], const float defaultValue[3]={ 0.0, 0.0, 0.0 }) +void NPCGetEyePosition(int npcIndex, float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) { buffer[0] = defaultValue[0]; buffer[1] = defaultValue[1]; @@ -1461,22 +1663,20 @@ bool NPCGetEyePosition(int npcIndex, float buffer[3], const float defaultValue[3 if (!NPCIsValid(npcIndex)) { - return false; + return; } int npcEnt = NPCGetEntIndex(npcIndex); if (!npcEnt || npcEnt == INVALID_ENT_REFERENCE) { - return false; + return; } - // @TODO: Replace SlenderGetAbsOrigin with GetEntPropVector float pos[3], eyePosOffset[3]; - SlenderGetAbsOrigin(npcIndex, pos); + CBaseEntity(npcEnt).GetAbsOrigin(pos); NPCGetEyePositionOffset(npcIndex, eyePosOffset); AddVectors(pos, eyePosOffset, buffer); - return true; } bool NPCHasAttribute(int npcIndex, int attribute) @@ -1536,18 +1736,21 @@ bool SlenderCanRemove(int bossIndex) int difficulty = GetLocalGlobalDifficulty(bossIndex); + int slender = NPCGetEntIndex(bossIndex); + switch (teleportType) { case 0: { - if (NPCGetFlags(bossIndex) & SFF_STATICONRADIUS) + if (NPCGetFlags(bossIndex) & SFF_STATICONRADIUS && slender != INVALID_ENT_REFERENCE && slender) { float slenderPos[3], buffer[3]; - SlenderGetAbsOrigin(bossIndex, slenderPos); + CBaseEntity boss = CBaseEntity(slender); + boss.GetAbsOrigin(slenderPos); for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i) || @@ -1578,17 +1781,32 @@ bool SlenderCanRemove(int bossIndex) } case 2: { - int state = g_SlenderState[bossIndex]; - if (state != STATE_ALERT && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) + SF2_ChaserEntity chaser = SF2_ChaserEntity(slender); + if (chaser.IsValid()) { + if (chaser.State == STATE_ALERT || chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN || chaser.State == STATE_DEATH) + { + return false; + } + if (GetGameTime() < g_SlenderTimeUntilKill[bossIndex]) { return false; } } - else + + SF2_StatueEntity statue = SF2_StatueEntity(slender); + if (statue.IsValid()) { - return false; + if (statue.IsMoving) + { + return false; + } + + if (GetGameTime() < g_SlenderStatueIdleLifeTime[bossIndex]) + { + return false; + } } } } @@ -1596,366 +1814,291 @@ bool SlenderCanRemove(int bossIndex) return true; } -bool SlenderGetAbsOrigin(int bossIndex, float buffer[3], const float defaultValue[3]={ 0.0, 0.0, 0.0 }) -{ - for (int i = 0; i < 3; i++) - { - buffer[i] = defaultValue[i]; - } - - if (bossIndex < 0 || NPCGetUniqueID(bossIndex) == -1) - { - return false; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return false; - } - - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", buffer); - - return true; -} - -bool SlenderGetEyePosition(int bossIndex, float buffer[3], const float defaultValue[3]={ 0.0, 0.0, 0.0 }) -{ - return NPCGetEyePosition(bossIndex, buffer, defaultValue); -} - -bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFlags=0,SF2NPC_BaseNPC NpcCopyMaster=view_as(SF2_INVALID_NPC), bool spawnCompanions=true, bool playSpawnSound=true, bool invincible = false) +bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true, bool showPvEMessage = true) { if (!IsProfileValid(profile)) { - if (!NpcCopyMaster.IsValid()) + if (!npcCopyMaster.IsValid()) { - LogSF2Message("Could not select profile for boss %d: profile %s is invalid!", Npc.Index, profile); + LogSF2Message("Could not select profile for boss %d: profile %s is invalid!", npc.Index, profile); return false; } - /*else//Wait my copy master is valid but not my profil wut???? - { - char sNewProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - NpcCopyMaster.GetProfile(sNewProfile,sizeof(sNewProfile)); - //Add me again - SelectProfile(Npc, sNewProfile, additionalBossFlags, NpcCopyMaster, spawnCompanions, playSpawnSound); - }*/ } - Npc.Remove(); + npc.Remove(); - Npc.SetProfile(profile); + npc.SetProfile(profile); - int bossType = GetBossProfileType(profile); + SF2BossProfileData profileData; + g_BossProfileData.GetArray(profile, profileData, sizeof(profileData)); - g_NpcUniqueID[Npc.Index] = g_NpcGlobalUniqueID++; - g_NpcType[Npc.Index] = bossType; + int bossType = GetBossProfileType(profile); - g_NpcModelScale[Npc.Index] = GetBossProfileModelScale(profile); + g_NpcUniqueID[npc.Index] = g_NpcGlobalUniqueID++; + g_NpcProfileData[npc.Index] = profileData; + g_NpcType[npc.Index] = bossType; - g_NpcModelSkin[Npc.Index] = GetBossProfileSkin(profile); + g_NpcModelScale[npc.Index] = GetBossProfileModelScale(profile); - g_NpcModelSkinMax[Npc.Index] = GetBossProfileSkinMax(profile); + g_NpcModelSkin[npc.Index] = GetBossProfileSkin(profile); - g_NpcModelBodyGroups[Npc.Index] = GetBossProfileBodyGroups(profile); + g_NpcModelSkinMax[npc.Index] = GetBossProfileSkinMax(profile); - g_NpcModelBodyGroupsMax[Npc.Index] = GetBossProfileBodyGroupsMax(profile); + g_NpcModelBodyGroups[npc.Index] = GetBossProfileBodyGroups(profile); - g_NpcRaidHitbox[Npc.Index] = GetBossProfileRaidHitbox(profile); + g_NpcModelBodyGroupsMax[npc.Index] = GetBossProfileBodyGroupsMax(profile); - g_NpcHasIgnoreNavPrefer[Npc.Index] = GetBossProfileIgnoreNavPrefer(profile); + g_NpcRaidHitbox[npc.Index] = GetBossProfileRaidHitbox(profile); - g_NpcHasFakeCopiesEnabled[Npc.Index] = GetBossProfileFakeCopies(profile); + g_NpcHasIgnoreNavPrefer[npc.Index] = GetBossProfileIgnoreNavPrefer(profile); - g_NpcBlinkLookRate[Npc.Index] = GetBossProfileBlinkLookRate(profile); - g_NpcBlinkStaticRate[Npc.Index] = GetBossProfileBlinkStaticRate(profile); + g_NpcBlinkLookRate[npc.Index] = GetBossProfileBlinkLookRate(profile); + g_NpcBlinkStaticRate[npc.Index] = GetBossProfileBlinkStaticRate(profile); - g_NpcStepSize[Npc.Index] = GetBossProfileStepSize(profile); + g_NpcStepSize[npc.Index] = GetBossProfileStepSize(profile); - g_NpcHasDiscoMode[Npc.Index] = GetBossProfileDiscoModeState(profile); - g_NpcDiscoRadiusMin[Npc.Index] = GetBossProfileDiscoRadiusMin(profile); - g_NpcDiscoRadiusMax[Npc.Index] = GetBossProfileDiscoRadiusMax(profile); + g_NpcHasDiscoMode[npc.Index] = GetBossProfileDiscoModeState(profile); + g_NpcDiscoRadiusMin[npc.Index] = GetBossProfileDiscoRadiusMin(profile); + g_NpcDiscoRadiusMax[npc.Index] = GetBossProfileDiscoRadiusMax(profile); - g_NpcHasFestiveLights[Npc.Index] = GetBossProfileFestiveLightState(profile); - g_NpcFestiveLightBrightness[Npc.Index] = GetBossProfileFestiveLightBrightness(profile); - g_NpcFestiveLightDistance[Npc.Index] = GetBossProfileFestiveLightDistance(profile); - g_NpcFestiveLightRadius[Npc.Index] = GetBossProfileFestiveLightRadius(profile); - GetBossProfileDiscoPosition(profile, g_NpcDiscoModePos[Npc.Index]); - GetBossProfileFestiveLightPosition(profile, g_NpcFestiveLightPos[Npc.Index]); - GetBossProfileFestiveLightAngles(profile, g_NpcFestiveLightAng[Npc.Index]); + g_NpcHasFestiveLights[npc.Index] = GetBossProfileFestiveLightState(profile); + g_NpcFestiveLightBrightness[npc.Index] = GetBossProfileFestiveLightBrightness(profile); + g_NpcFestiveLightDistance[npc.Index] = GetBossProfileFestiveLightDistance(profile); + g_NpcFestiveLightRadius[npc.Index] = GetBossProfileFestiveLightRadius(profile); + GetBossProfileDiscoPosition(profile, g_NpcDiscoModePos[npc.Index]); + GetBossProfileFestiveLightPosition(profile, g_NpcFestiveLightPos[npc.Index]); + GetBossProfileFestiveLightAngles(profile, g_NpcFestiveLightAng[npc.Index]); - g_SlenderUseCustomOutlines[Npc.Index] = GetBossProfileCustomOutlinesState(profile); - g_SlenderOutlineColorR[Npc.Index] = GetBossProfileOutlineColorR(profile); - g_SlenderOutlineColorG[Npc.Index] = GetBossProfileOutlineColorG(profile); - g_SlenderOutlineColorB[Npc.Index] = GetBossProfileOutlineColorB(profile); - g_SlenderOutlineTransparency[Npc.Index] = GetBossProfileOutlineTransparency(profile); - g_SlenderUseRainbowOutline[Npc.Index] = GetBossProfileRainbowOutlineState(profile); - g_SlenderRainbowCycleRate[Npc.Index] = GetBossProfileRainbowCycleRate(profile); + g_SlenderUseCustomOutlines[npc.Index] = GetBossProfileCustomOutlinesState(profile); + g_SlenderUseRainbowOutline[npc.Index] = GetBossProfileRainbowOutlineState(profile); - g_NpcHasProxyWeaponsEnabled[Npc.Index] = GetBossProfileProxyWeapons(profile); + g_NpcHasProxyWeaponsEnabled[npc.Index] = GetBossProfileProxyWeapons(profile); - g_NpcHasDrainCreditsState[Npc.Index] = GetBossProfileDrainCreditState(profile); - g_NpcHasProxySpawnEffectEnabled[Npc.Index] = GetBossProfileProxySpawnEffectState(profile); - g_NpcProxySpawnEffectZOffset[Npc.Index] = GetBossProfileProxySpawnEffectZOffset(profile); + g_NpcHasDrainCreditsState[npc.Index] = GetBossProfileDrainCreditState(profile); + g_NpcHasProxySpawnEffectEnabled[npc.Index] = GetBossProfileProxySpawnEffectState(profile); + g_NpcProxySpawnEffectZOffset[npc.Index] = GetBossProfileProxySpawnEffectZOffset(profile); if (SF_IsSlaughterRunMap()) { - NPCSetFlags(Npc.Index, GetBossProfileFlags(profile) | additionalBossFlags | SFF_NOTELEPORT); + NPCSetFlags(npc.Index, GetBossProfileFlags(profile) | additionalBossFlags | SFF_NOTELEPORT); } else { - NPCSetFlags(Npc.Index, GetBossProfileFlags(profile) | additionalBossFlags); + NPCSetFlags(npc.Index, GetBossProfileFlags(profile) | additionalBossFlags); } - GetBossProfileEyePositionOffset(profile, g_SlenderEyePosOffset[Npc.Index]); - GetBossProfileEyeAngleOffset(profile, g_SlenderEyeAngOffset[Npc.Index]); + GetBossProfileEyePositionOffset(profile, g_SlenderEyePosOffset[npc.Index]); + GetBossProfileEyeAngleOffset(profile, g_SlenderEyeAngOffset[npc.Index]); + + GetBossProfileHullMins(profile, g_SlenderDetectMins[npc.Index]); + GetBossProfileHullMaxs(profile, g_SlenderDetectMaxs[npc.Index]); + + GetBossProfileRenderColor(profile, g_SlenderRenderColor[npc.Index]); - GetBossProfileHullMins(profile, g_SlenderDetectMins[Npc.Index]); - GetBossProfileHullMaxs(profile, g_SlenderDetectMaxs[Npc.Index]); + g_SlenderRenderFX[npc.Index] = GetBossProfileRenderFX(profile); + g_SlenderRenderMode[npc.Index] = GetBossProfileRenderMode(profile); - GetBossProfileRenderColor(profile, g_SlenderRenderColor[Npc.Index]); + npc.CopyMaster = SF2_INVALID_NPC; + npc.CompanionMaster = SF2_INVALID_NPC; - g_SlenderRenderFX[Npc.Index] = GetBossProfileRenderFX(profile); - g_SlenderRenderMode[Npc.Index] = GetBossProfileRenderMode(profile); + g_NpcShouldBeAffectedBySight[npc.Index] = false; - g_SlenderCopyMaster[Npc.Index] = -1; - g_SlenderCompanionMaster[Npc.Index] = -1; + g_NpcDefaultTeam[npc.Index] = g_DefaultBossTeamConVar.IntValue; for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - g_NpcSoundMusicLoop[Npc.Index][difficulty] = GetBossProfileSoundMusicLoop(profile, difficulty); - g_NpcSpeed[Npc.Index][difficulty] = GetBossProfileSpeed(profile, difficulty); - g_NpcMaxSpeed[Npc.Index][difficulty] = GetBossProfileMaxSpeed(profile, difficulty); - g_SlenderAcceleration[Npc.Index][difficulty] = GetBossProfileAcceleration(profile, difficulty); - g_NpcIdleLifetime[Npc.Index][difficulty] = GetBossProfileIdleLifetime(profile, difficulty); - g_SlenderStaticRadius[Npc.Index][difficulty] = GetBossProfileStaticRadius(profile, difficulty); - g_SlenderStaticRate[Npc.Index][difficulty] = GetBossProfileStaticRate(profile, difficulty); - g_SlenderStaticRateDecay[Npc.Index][difficulty] = GetBossProfileStaticRateDecay(profile, difficulty); - g_SlenderStaticGraceTime[Npc.Index][difficulty] = GetBossProfileStaticGraceTime(profile, difficulty); - g_NpcSearchRadius[Npc.Index][difficulty] = GetBossProfileSearchRadius(profile, difficulty); - g_NpcHearingRadius[Npc.Index][difficulty] = GetBossProfileHearRadius(profile, difficulty); - g_NpcTauntAlertRange[Npc.Index][difficulty] = GetBossProfileTauntAlertRange(profile, difficulty); - g_SlenderTeleportMinRange[Npc.Index][difficulty] = GetBossProfileTeleportRangeMin(profile, difficulty); - g_SlenderTeleportMaxRange[Npc.Index][difficulty] = GetBossProfileTeleportRangeMax(profile, difficulty); - g_NpcHasTeleportAllowed[Npc.Index][difficulty] = GetBossProfileTeleportAllowed(profile, difficulty); - g_NpcTeleportTimeMin[Npc.Index][difficulty] = GetBossProfileTeleportTimeMin(profile, difficulty); - g_NpcTeleportTimeMax[Npc.Index][difficulty] = GetBossProfileTeleportTimeMax(profile, difficulty); - g_NpcTeleportRestPeriod[Npc.Index][difficulty] = GetBossProfileTeleportTargetRestPeriod(profile, difficulty); - g_NpcTeleportStressMin[Npc.Index][difficulty] = GetBossProfileTeleportTargetStressMin(profile, difficulty); - g_NpcTeleportStressMax[Npc.Index][difficulty] = GetBossProfileTeleportTargetStressMax(profile, difficulty); - g_NpcTeleportPersistencyPeriod[Npc.Index][difficulty] = GetBossProfileTeleportTargetPersistencyPeriod(profile, difficulty); - g_NpcJumpscareDistance[Npc.Index][difficulty] = GetBossProfileJumpscareDistance(profile, difficulty); - g_NpcJumpscareDuration[Npc.Index][difficulty] = GetBossProfileJumpscareDuration(profile, difficulty); - g_NpcJumpscareCooldown[Npc.Index][difficulty] = GetBossProfileJumpscareCooldown(profile, difficulty); - g_NpcModelSkinDifficulty[Npc.Index][difficulty] = GetBossProfileSkinDifficulty(profile, difficulty); - g_NpcModelBodyGroupsDifficulty[Npc.Index][difficulty] = GetBossProfileBodyGroupsDifficulty(profile, difficulty); - g_SlenderMaxCopies[Npc.Index][difficulty] = GetBossProfileMaxCopies(profile, difficulty); - g_NpcInstantKillCooldown[Npc.Index][difficulty] = GetBossProfileInstantKillCooldown(profile, difficulty); - - g_SlenderProxyDamageVsEnemy[Npc.Index][difficulty] = GetBossProfileProxyDamageVsEnemy(profile, difficulty); - g_SlenderProxyDamageVsBackstab[Npc.Index][difficulty] = GetBossProfileProxyDamageVsBackstab(profile, difficulty); - g_SlenderProxyDamageVsSelf[Npc.Index][difficulty] = GetBossProfileProxyDamageVsSelf(profile, difficulty); - g_SlenderProxyControlGainHitEnemy[Npc.Index][difficulty] = GetBossProfileProxyControlGainHitEnemy(profile, difficulty); - g_SlenderProxyControlGainHitByEnemy[Npc.Index][difficulty] = GetBossProfileProxyControlGainHitByEnemy(profile, difficulty); - g_SlenderProxyControlDrainRate[Npc.Index][difficulty] = GetBossProfileProxyControlDrainRate(profile, difficulty); - g_SlenderMaxProxies[Npc.Index][difficulty] = GetBossProfileMaxProxies(profile, difficulty); - g_NpcProxySpawnChanceMin[Npc.Index][difficulty] = GetBossProfileProxySpawnChanceMin(profile, difficulty); - g_NpcProxySpawnChanceMax[Npc.Index][difficulty] = GetBossProfileProxySpawnChanceMax(profile, difficulty); - g_NpcProxySpawnChanceThreshold[Npc.Index][difficulty] = GetBossProfileProxySpawnChanceThreshold(profile, difficulty); - g_NpcProxySpawnNumMin[Npc.Index][difficulty] = GetBossProfileProxySpawnNumberMin(profile, difficulty); - g_NpcProxySpawnNumMax[Npc.Index][difficulty] = GetBossProfileProxySpawnNumberMax(profile, difficulty); - g_NpcProxySpawnCooldownMin[Npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMin(profile, difficulty); - g_NpcProxySpawnCooldownMax[Npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMax(profile, difficulty); - g_SlenderProxyTeleportMinRange[Npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMin(profile, difficulty); - g_SlenderProxyTeleportMaxRange[Npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMax(profile, difficulty); - g_NpcDrainCreditAmount[Npc.Index][difficulty] = GetBossProfileDrainCreditAmount(profile, difficulty); + g_NpcHasFakeCopiesEnabled[npc.Index][difficulty] = GetBossProfileFakeCopies(profile, difficulty); + g_NpcSoundMusicLoop[npc.Index][difficulty] = GetBossProfileSoundMusicLoop(profile, difficulty); + g_NpcIdleLifetime[npc.Index][difficulty] = GetBossProfileIdleLifetime(profile, difficulty); + g_SlenderStaticRadius[npc.Index][difficulty] = GetBossProfileStaticRadius(profile, difficulty); + g_SlenderStaticRate[npc.Index][difficulty] = GetBossProfileStaticRate(profile, difficulty); + g_SlenderStaticRateDecay[npc.Index][difficulty] = GetBossProfileStaticRateDecay(profile, difficulty); + g_SlenderStaticGraceTime[npc.Index][difficulty] = GetBossProfileStaticGraceTime(profile, difficulty); + g_SlenderTeleportMinRange[npc.Index][difficulty] = GetBossProfileTeleportRangeMin(profile, difficulty); + g_SlenderTeleportMaxRange[npc.Index][difficulty] = GetBossProfileTeleportRangeMax(profile, difficulty); + g_NpcHasTeleportAllowed[npc.Index][difficulty] = GetBossProfileTeleportAllowed(profile, difficulty); + g_NpcTeleportTimeMin[npc.Index][difficulty] = GetBossProfileTeleportTimeMin(profile, difficulty); + g_NpcTeleportTimeMax[npc.Index][difficulty] = GetBossProfileTeleportTimeMax(profile, difficulty); + g_NpcTeleportRestPeriod[npc.Index][difficulty] = GetBossProfileTeleportTargetRestPeriod(profile, difficulty); + g_NpcTeleportStressMin[npc.Index][difficulty] = GetBossProfileTeleportTargetStressMin(profile, difficulty); + g_NpcTeleportStressMax[npc.Index][difficulty] = GetBossProfileTeleportTargetStressMax(profile, difficulty); + g_NpcTeleportPersistencyPeriod[npc.Index][difficulty] = GetBossProfileTeleportTargetPersistencyPeriod(profile, difficulty); + g_NpcJumpscareDistance[npc.Index][difficulty] = GetBossProfileJumpscareDistance(profile, difficulty); + g_NpcJumpscareDuration[npc.Index][difficulty] = GetBossProfileJumpscareDuration(profile, difficulty); + g_NpcJumpscareCooldown[npc.Index][difficulty] = GetBossProfileJumpscareCooldown(profile, difficulty); + g_NpcModelSkinDifficulty[npc.Index][difficulty] = GetBossProfileSkinDifficulty(profile, difficulty); + g_NpcModelBodyGroupsDifficulty[npc.Index][difficulty] = GetBossProfileBodyGroupsDifficulty(profile, difficulty); + g_SlenderMaxCopies[npc.Index][difficulty] = GetBossProfileMaxCopies(profile, difficulty); + + g_SlenderProxyDamageVsEnemy[npc.Index][difficulty] = GetBossProfileProxyDamageVsEnemy(profile, difficulty); + g_SlenderProxyDamageVsBackstab[npc.Index][difficulty] = GetBossProfileProxyDamageVsBackstab(profile, difficulty); + g_SlenderProxyDamageVsSelf[npc.Index][difficulty] = GetBossProfileProxyDamageVsSelf(profile, difficulty); + g_SlenderProxyControlGainHitEnemy[npc.Index][difficulty] = GetBossProfileProxyControlGainHitEnemy(profile, difficulty); + g_SlenderProxyControlGainHitByEnemy[npc.Index][difficulty] = GetBossProfileProxyControlGainHitByEnemy(profile, difficulty); + g_SlenderProxyControlDrainRate[npc.Index][difficulty] = GetBossProfileProxyControlDrainRate(profile, difficulty); + g_SlenderMaxProxies[npc.Index][difficulty] = GetBossProfileMaxProxies(profile, difficulty); + g_NpcProxySpawnChanceMin[npc.Index][difficulty] = GetBossProfileProxySpawnChanceMin(profile, difficulty); + g_NpcProxySpawnChanceMax[npc.Index][difficulty] = GetBossProfileProxySpawnChanceMax(profile, difficulty); + g_NpcProxySpawnChanceThreshold[npc.Index][difficulty] = GetBossProfileProxySpawnChanceThreshold(profile, difficulty); + g_NpcProxySpawnNumMin[npc.Index][difficulty] = GetBossProfileProxySpawnNumberMin(profile, difficulty); + g_NpcProxySpawnNumMax[npc.Index][difficulty] = GetBossProfileProxySpawnNumberMax(profile, difficulty); + g_NpcProxySpawnCooldownMin[npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMin(profile, difficulty); + g_NpcProxySpawnCooldownMax[npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMax(profile, difficulty); + g_SlenderProxyTeleportMinRange[npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMin(profile, difficulty); + g_SlenderProxyTeleportMaxRange[npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMax(profile, difficulty); + g_NpcDrainCreditAmount[npc.Index][difficulty] = GetBossProfileDrainCreditAmount(profile, difficulty); } ArrayList arrayNames; arrayNames = GetBossProfileNames(profile); - arrayNames.GetString(Difficulty_Normal, g_NpcName[Npc.Index][1], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Normal, g_NpcName[npc.Index][1], sizeof(g_NpcName[][])); - strcopy(g_NpcName[Npc.Index][0], sizeof(g_NpcName[][]), g_NpcName[Npc.Index][1]); + strcopy(g_NpcName[npc.Index][0], sizeof(g_NpcName[][]), g_NpcName[npc.Index][1]); - arrayNames.GetString(Difficulty_Hard, g_NpcName[Npc.Index][2], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Hard, g_NpcName[npc.Index][2], sizeof(g_NpcName[][])); - arrayNames.GetString(Difficulty_Insane, g_NpcName[Npc.Index][3], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Insane, g_NpcName[npc.Index][3], sizeof(g_NpcName[][])); - arrayNames.GetString(Difficulty_Nightmare, g_NpcName[Npc.Index][4], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Nightmare, g_NpcName[npc.Index][4], sizeof(g_NpcName[][])); - arrayNames.GetString(Difficulty_Apollyon, g_NpcName[Npc.Index][5], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Apollyon, g_NpcName[npc.Index][5], sizeof(g_NpcName[][])); arrayNames = null; - g_NpcHasJumpscareOnScare[Npc.Index] = GetBossProfileJumpscareOnScare(profile); - - g_NpcTurnRate[Npc.Index] = GetBossProfileTurnRate(profile); - if (!SF_IsSlaughterRunMap()) - { - g_NpcFieldOfView[Npc.Index] = GetBossProfileFOV(profile); - } - else - { - g_NpcFieldOfView[Npc.Index] = 360.0; - } - g_NpcBackstabFOV[Npc.Index] = 180.0; - - g_NpcScareRadius[Npc.Index] = GetBossProfileScareRadius(profile); - g_NpcScareCooldown[Npc.Index] = GetBossProfileScareCooldown(profile); - - g_NpcHasPlayerScareSpeedBoost[Npc.Index] = GetBossProfileSpeedBoostOnScare(profile); - g_NpcPlayerSpeedBoostDuration[Npc.Index] = GetBossProfileScareSpeedBoostDuration(profile); + g_NpcHasJumpscareOnScare[npc.Index] = GetBossProfileJumpscareOnScare(profile); - g_NpcHasPlayerScareReaction[Npc.Index] = GetBossProfileScareReactionState(profile); - g_NpcPlayerScareReactionType[Npc.Index] = GetBossProfileScareReactionType(profile); + g_NpcScareRadius[npc.Index] = GetBossProfileScareRadius(profile); + g_NpcScareCooldown[npc.Index] = GetBossProfileScareCooldown(profile); - g_NpcHasPlayerScareReplenishSprint[Npc.Index] = GetBossProfileScareReplenishState(profile); - g_NpcPlayerScareReplenishSprintAmount[Npc.Index] = GetBossProfileScareReplenishAmount(profile); + g_NpcHasPlayerScareSpeedBoost[npc.Index] = GetBossProfileSpeedBoostOnScare(profile); + g_NpcPlayerSpeedBoostDuration[npc.Index] = GetBossProfileScareSpeedBoostDuration(profile); - g_NpcInstantKillRadius[Npc.Index] = GetBossProfileInstantKillRadius(profile); + g_NpcHasPlayerScareReaction[npc.Index] = GetBossProfileScareReactionState(profile); + g_NpcPlayerScareReactionType[npc.Index] = GetBossProfileScareReactionType(profile); - g_NpcTeleportType[Npc.Index] = GetBossProfileTeleportType(profile); + g_NpcHasPlayerScareReplenishSprint[npc.Index] = GetBossProfileScareReplenishState(profile); + g_NpcPlayerScareReplenishSprintAmount[npc.Index] = GetBossProfileScareReplenishAmount(profile); - g_SlenderTeleportIgnoreChases[Npc.Index] = GetBossProfileTeleportIgnoreChases(profile); + g_NpcTeleportType[npc.Index] = GetBossProfileTeleportType(profile); - g_SlenderTeleportIgnoreVis[Npc.Index] = GetBossProfileTeleportIgnoreVis(profile); + g_SlenderTeleportIgnoreChases[npc.Index] = GetBossProfileTeleportIgnoreChases(profile); - g_NpcHasHealthbarEnabled[Npc.Index] = GetBossProfileHealthbarState(profile); + g_SlenderTeleportIgnoreVis[npc.Index] = GetBossProfileTeleportIgnoreVis(profile); - g_SlenderProxiesAllowNormalVoices[Npc.Index] = GetBossProfileProxyAllowNormalVoices(profile); + g_SlenderProxiesAllowNormalVoices[npc.Index] = GetBossProfileProxyAllowNormalVoices(profile); - g_NpcDeathMessageDifficultyIndexes[Npc.Index] = GetBossProfileChatDeathMessageDifficultyIndexes(profile); + g_NpcDeathMessageDifficultyIndexes[npc.Index] = GetBossProfileChatDeathMessageDifficultyIndexes(profile); - g_NpcAddSpeed[Npc.Index] = 0.0; - g_NpcAddMaxSpeed[Npc.Index] = 0.0; - g_NpcAddAcceleration[Npc.Index] = 0.0; - - g_NpcEnemy[Npc.Index] = INVALID_ENT_REFERENCE; - - g_NpcPlayerScareVictin[Npc.Index] = INVALID_ENT_REFERENCE; - g_NpcChasingScareVictin[Npc.Index] = false; - g_NpcLostChasingScareVictim[Npc.Index] = false; + g_NpcAddSpeed[npc.Index] = 0.0; + g_NpcAddAcceleration[npc.Index] = 0.0; // Deathcam values. - Npc.DeathCamEnabled = GetBossProfileDeathCamState(profile); - g_SlenderDeathCamScareSound[Npc.Index] = GetBossProfileDeathCamScareSound(profile); - g_SlenderPublicDeathCam[Npc.Index] = GetBossProfilePublicDeathCamState(profile); - g_SlenderPublicDeathCamSpeed[Npc.Index] = GetBossProfilePublicDeathCamSpeed(profile); - g_SlenderPublicDeathCamAcceleration[Npc.Index] = GetBossProfilePublicDeathCamAcceleration(profile); - g_SlenderPublicDeathCamDeceleration[Npc.Index] = GetBossProfilePublicDeathCamDeceleration(profile); - g_SlenderPublicDeathCamBackwardOffset[Npc.Index] = GetBossProfilePublicDeathCamBackwardOffset(profile); - g_SlenderPublicDeathCamDownwardOffset[Npc.Index] = GetBossProfilePublicDeathCamDownwardOffset(profile); - g_SlenderDeathCamOverlay[Npc.Index] = GetBossProfileDeathCamOverlayState(profile); - g_SlenderDeathCamOverlayTimeStart[Npc.Index] = GetBossProfileDeathCamOverlayStartTime(profile); - g_SlenderDeathCamTime[Npc.Index] = GetBossProfileDeathCamTime(profile); - - g_SlenderFakeTimer[Npc.Index] = null; - g_SlenderEntityThink[Npc.Index] = null; - g_SlenderAttackTimer[Npc.Index] = null; - g_SlenderLaserTimer[Npc.Index] = null; - g_SlenderBackupAtkTimer[Npc.Index] = null; - g_SlenderChaseInitialTimer[Npc.Index] = null; - g_SlenderRage1Timer[Npc.Index] = null; - g_SlenderRage2Timer[Npc.Index] = null; - g_SlenderRage3Timer[Npc.Index] = null; - g_SlenderHealTimer[Npc.Index] = null; - g_SlenderHealDelayTimer[Npc.Index] = null; - g_SlenderHealEventTimer[Npc.Index] = null; - g_SlenderStartFleeTimer[Npc.Index] = null; - g_SlenderSpawnTimer[Npc.Index] = null; - g_SlenderDeathCamTimer[Npc.Index] = null; - g_SlenderDeathCamTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderNextTeleportTime[Npc.Index] = 0.0; - g_SlenderLastKill[Npc.Index] = 0.0; - g_SlenderTimeUntilKill[Npc.Index] = -1.0; - g_SlenderNextJumpScare[Npc.Index] = -1.0; - g_SlenderNextStunTime[Npc.Index] = -1.0; - g_SlenderNextCloakTime[Npc.Index] = -1.0; - g_NpcHasCloaked[Npc.Index] = false; - g_NpcUsedRage1[Npc.Index] = false; - g_NpcUsedRage2[Npc.Index] = false; - g_NpcHasUsedRage3[Npc.Index] = false; - g_SlenderTimeUntilNextProxy[Npc.Index] = -1.0; - g_NpcVelocityCancel[Npc.Index] = false; - g_SlenderBurnTimer[Npc.Index] = null; - g_SlenderBleedTimer[Npc.Index] = null; - g_SlenderMarkedTimer[Npc.Index] = null; - g_SlenderStopBurningTimer[Npc.Index] = 0.0; - g_SlenderStopBleedingTimer[Npc.Index] = 0.0; - g_SlenderIsBurning[Npc.Index] = false; - g_SlenderIsMarked[Npc.Index] = false; - g_SlenderSoundTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderSeeTarget[Npc.Index] = INVALID_ENT_REFERENCE; - - g_SlenderHasBurnKillEffect[Npc.Index] = GetBossProfileBurnRagdoll(profile); - g_SlenderHasCloakKillEffect[Npc.Index] = GetBossProfileCloakRagdoll(profile); - g_SlenderHasDecapKillEffect[Npc.Index] = GetBossProfileDecapRagdoll(profile); - g_SlenderHasGibKillEffect[Npc.Index] = GetBossProfileGibRagdoll(profile); - g_SlenderHasGoldKillEffect[Npc.Index] = GetBossProfileGoldRagdoll(profile); - g_SlenderHasIceKillEffect[Npc.Index] = GetBossProfileIceRagdoll(profile); - g_SlenderHasElectrocuteKillEffect[Npc.Index] = GetBossProfileElectrocuteRagdoll(profile); - g_SlenderHasAshKillEffect[Npc.Index] = GetBossProfileAshRagdoll(profile); - g_SlenderHasDeleteKillEffect[Npc.Index] = GetBossProfileDeleteRagdoll(profile); - g_SlenderHasPushRagdollOnKill[Npc.Index] = GetBossProfilePushRagdoll(profile); - - g_SlenderHasDissolveRagdollOnKill[Npc.Index] = GetBossProfileDissolveRagdoll(profile); - g_SlenderDissolveRagdollType[Npc.Index] = GetBossProfileDissolveRagdollType(profile); - - g_SlenderHasPlasmaRagdollOnKill[Npc.Index] = GetBossProfilePlasmaRagdoll(profile); - - g_SlenderHasResizeRagdollOnKill[Npc.Index] = GetBossProfileResizeRagdoll(profile); - g_SlenderResizeRagdollHands[Npc.Index] = GetBossProfileResizeRagdollHands(profile); - g_SlenderResizeRagdollHead[Npc.Index] = GetBossProfileResizeRagdollHead(profile); - g_SlenderResizeRagdollTorso[Npc.Index] = GetBossProfileResizeRagdollTorso(profile); - - g_SlenderHasDecapOrGibKillEffect[Npc.Index] = GetBossProfileDecapOrGibRagdoll(profile); - g_SlenderHasMultiKillEffect[Npc.Index] = GetBossProfileMultieffectRagdoll(profile); - - g_SlenderHasSilentKill[Npc.Index] = GetBossProfileSilentKill(profile); - - g_SlenderPlayerCustomDeathFlag[Npc.Index] = GetBossProfileCustomDeathFlag(profile); - g_SlenderPlayerSetDeathFlag[Npc.Index] = GetBossProfileCustomDeathFlagType(profile); - - g_SlenderCustomOutroSong[Npc.Index] = GetBossProfileOutroMusicState(profile); + npc.DeathCamEnabled = GetBossProfileDeathCamState(profile); + g_SlenderDeathCamScareSound[npc.Index] = GetBossProfileDeathCamScareSound(profile); + g_SlenderPublicDeathCam[npc.Index] = GetBossProfilePublicDeathCamState(profile); + g_SlenderPublicDeathCamSpeed[npc.Index] = GetBossProfilePublicDeathCamSpeed(profile); + g_SlenderPublicDeathCamAcceleration[npc.Index] = GetBossProfilePublicDeathCamAcceleration(profile); + g_SlenderPublicDeathCamDeceleration[npc.Index] = GetBossProfilePublicDeathCamDeceleration(profile); + g_SlenderPublicDeathCamBackwardOffset[npc.Index] = GetBossProfilePublicDeathCamBackwardOffset(profile); + g_SlenderPublicDeathCamDownwardOffset[npc.Index] = GetBossProfilePublicDeathCamDownwardOffset(profile); + g_SlenderDeathCamOverlay[npc.Index] = GetBossProfileDeathCamOverlayState(profile); + g_SlenderDeathCamOverlayTimeStart[npc.Index] = GetBossProfileDeathCamOverlayStartTime(profile); + g_SlenderDeathCamTime[npc.Index] = GetBossProfileDeathCamTime(profile); + + g_SlenderFakeTimer[npc.Index] = null; + g_SlenderEntityThink[npc.Index] = null; + g_SlenderDeathCamTimer[npc.Index] = null; + g_SlenderDeathCamTarget[npc.Index] = INVALID_ENT_REFERENCE; + g_SlenderNextTeleportTime[npc.Index] = 0.0; + g_SlenderTimeUntilKill[npc.Index] = -1.0; + g_SlenderNextJumpScare[npc.Index] = -1.0; + g_SlenderTimeUntilNextProxy[npc.Index] = -1.0; + + g_SlenderCustomOutroSong[npc.Index] = profileData.OutroMusic; for (int i = 1; i <= MaxClients; i++) { - g_PlayerLastChaseBossEncounterTime[i][Npc.Index] = -1.0; - g_SlenderTeleportPlayersRestTime[Npc.Index][i] = -1.0; + g_SlenderTeleportPlayersRestTime[npc.Index][i] = -1.0; } - g_SlenderTeleportTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderProxyTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderTeleportMaxTargetStress[Npc.Index] = 9999.0; - g_SlenderTeleportMaxTargetTime[Npc.Index] = -1.0; - g_SlenderNextTeleportTime[Npc.Index] = -1.0; - g_SlenderTeleportTargetTime[Npc.Index] = -1.0; + g_SlenderTeleportTarget[npc.Index] = INVALID_ENT_REFERENCE; + g_SlenderProxyTarget[npc.Index] = INVALID_ENT_REFERENCE; + g_SlenderTeleportMaxTargetStress[npc.Index] = 9999.0; + g_SlenderTeleportMaxTargetTime[npc.Index] = -1.0; + g_SlenderNextTeleportTime[npc.Index] = -1.0; + g_SlenderTeleportTargetTime[npc.Index] = -1.0; - g_SlenderAddCompanionsOnDifficulty[Npc.Index] = false; + g_SlenderAddCompanionsOnDifficulty[npc.Index] = false; - GetBossProfileEngineSound(profile, g_SlenderEngineSound[Npc.Index], sizeof(g_SlenderEngineSound[])); - - g_SlenderThink[Npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, Npc, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + if (!profileData.IsPvEBoss) + { + if (g_Enabled) + { + g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + else + { + if (g_NpcDefaultTeam[npc.Index] == TFTeam_Blue || g_NpcDefaultTeam[npc.Index] == TFTeam_Red) + { + g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderRespawnThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + else + { + g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + } switch (bossType) { case SF2BossType_Chaser: { - NPCChaserOnSelectProfile(Npc.Index, invincible); + NPCChaserOnSelectProfile(npc.Index); } case SF2BossType_Statue: { - NPCStatueOnSelectProfile(profile, Npc.Index); + NPCStatueOnSelectProfile(npc.Index); } } - if (NpcCopyMaster.IsValid()) + if (npcCopyMaster.IsValid()) { - g_SlenderCopyMaster[Npc.Index] = NpcCopyMaster.Index; - g_SlenderNextJumpScare[Npc.Index] = g_SlenderNextJumpScare[NpcCopyMaster.Index]; + npc.CopyMaster = npcCopyMaster; + g_SlenderNextJumpScare[npc.Index] = g_SlenderNextJumpScare[npcCopyMaster.Index]; } else { + if (profileData.IsPvEBoss && showPvEMessage && profileData.PvESpawnMessagesArray != null && profileData.PvESpawnMessagesArray.Length > 0) + { + char prefix[PLATFORM_MAX_PATH], message[PLATFORM_MAX_PATH]; + strcopy(prefix, sizeof(prefix), profileData.PvESpawnMessagePrefix); + if (prefix[0] == '\0') + { + prefix = "[SF2]"; + } + int messageIndex = GetRandomInt(0, profileData.PvESpawnMessagesArray.Length - 1); + profileData.PvESpawnMessagesArray.GetString(messageIndex, message, sizeof(message)); + if (StrContains(message, "[BOSS]", true) != -1) + { + ReplaceString(message, sizeof(message), "[BOSS]", g_NpcName[npc.Index][1]); + } + int chatLength = strlen(prefix) + strlen(message); + if (chatLength > 255) + { + LogSF2Message("WARNING! PvE spawn message %i has greater than 255 characters on boss index %i, shorten the length of this message.", messageIndex + 1, npc.Index); + } + else + { + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || !g_PlayerEliminated[i]) + { + continue; + } + CPrintToChat(i, "{royalblue}%s {default}%s", prefix, message); + } + } + } if (playSpawnSound) { SF2BossProfileSoundInfo soundInfo; @@ -1966,24 +2109,31 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl { continue; } + if (g_Enabled) + { + if (profileData.IsPvEBoss && !g_PlayerEliminated[i]) + { + continue; + } + } soundInfo.EmitSound(true, i); } } - if (timerMusic == null) + if (g_Enabled && !profileData.IsPvEBoss && timerMusic == null) { bool allowMusic = false; float time; for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - if (g_NpcSoundMusicLoop[Npc.Index][difficulty] > 0.0) + if (g_NpcSoundMusicLoop[npc.Index][difficulty] > 0.0) { allowMusic = true; - g_NpcAllowMusicOnDifficulty[Npc.Index] |= difficulty; + g_NpcAllowMusicOnDifficulty[npc.Index] |= difficulty; } } if (allowMusic) { - time = g_NpcSoundMusicLoop[Npc.Index][g_DifficultyConVar.IntValue]; + time = g_NpcSoundMusicLoop[npc.Index][g_DifficultyConVar.IntValue]; currentMusicTrackNormal[0] = '\0'; currentMusicTrackHard[0] = '\0'; currentMusicTrackInsane[0] = '\0'; @@ -2121,9 +2271,9 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl } soundList = null; - if ((g_NpcAllowMusicOnDifficulty[Npc.Index] & g_DifficultyConVar.IntValue) && time > 0.0) + if ((g_NpcAllowMusicOnDifficulty[npc.Index] & g_DifficultyConVar.IntValue) && time > 0.0) { - timerMusic = CreateTimer(time, BossMusic, Npc.Index, TIMER_FLAG_NO_MAPCHANGE); + timerMusic = CreateTimer(time, BossMusic, npc.Index, TIMER_FLAG_NO_MAPCHANGE); for(int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && (!g_PlayerEliminated[client] || IsClientInGhostMode(client))) @@ -2146,7 +2296,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl } switch (g_DifficultyConVar.IntValue) { - case Difficulty_Normal, Difficulty_Easy: + case Difficulty_Normal: { strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNormal); } @@ -2197,10 +2347,10 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl companionInfo.Bosses.GetString(i, compProfile, sizeof(compProfile)); if (IsProfileValid(compProfile)) { - SF2NPC_BaseNPC NpcCompanion = AddProfile(compProfile, _, _, false, false); - if (NpcCompanion.IsValid()) + SF2NPC_BaseNPC npcCompanion = AddProfile(compProfile, _, _, false, false, false); + if (npcCompanion.IsValid()) { - g_SlenderCompanionMaster[NpcCompanion.Index] = Npc.Index; + npcCompanion.CompanionMaster = npc; } } else @@ -2214,34 +2364,30 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl { if (StrContains(spawnType, "on_difficulty_change", false) != -1) { - g_SlenderAddCompanionsOnDifficulty[Npc.Index] = true; + g_SlenderAddCompanionsOnDifficulty[npc.Index] = true; } if (StrContains(spawnType, "on_spawn", false) != -1) { - Npc.AddCompanions(); + npc.AddCompanions(); } } } - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for companions..", companions); - #endif } } Call_StartForward(g_OnBossAddedFwd); - Call_PushCell(Npc.Index); + Call_PushCell(npc.Index); Call_Finish(); - if (g_SlenderCopyMaster[Npc.Index] == -1) + if (!npc.IsCopy) { LogSF2Message("Boss profile %s has been added to the game.", profile); } return true; } -//SF2NPC_BaseNPC NpcCopyMaster=view_as(SF2_INVALID_NPC) <= Bug? -SF2NPC_BaseNPC AddProfile(const char[] strName,int additionalBossFlags=0,SF2NPC_BaseNPC NpcCopyMaster=view_as(SF2_INVALID_NPC), bool spawnCompanions=true, bool playSpawnSound=true, bool invincible = false) + +SF2NPC_BaseNPC AddProfile(const char[] name, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true, bool showPvEMessage = true) { if (SF_IsRenevantMap() && GetRoundState() != SF2RoundState_Escape) { @@ -2249,12 +2395,12 @@ SF2NPC_BaseNPC AddProfile(const char[] strName,int additionalBossFlags=0,SF2NPC_ } for (int i = 0; i < MAX_BOSSES; i++) { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); - if (!Npc.IsValid()) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) { - if (SelectProfile(Npc, strName, additionalBossFlags, NpcCopyMaster, spawnCompanions, playSpawnSound, invincible)) + if (SelectProfile(npc, name, additionalBossFlags, npcCopyMaster, spawnCompanions, playSpawnSound, showPvEMessage)) { - return Npc; + return npc; } break; @@ -2264,10 +2410,10 @@ SF2NPC_BaseNPC AddProfile(const char[] strName,int additionalBossFlags=0,SF2NPC_ return SF2_INVALID_NPC; } -void NPCAddCompanions(SF2NPC_BaseNPC Npc) +void NPCAddCompanions(SF2NPC_BaseNPC npc) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.GetProfile(profile, sizeof(profile)); + npc.GetProfile(profile, sizeof(profile)); ArrayList companions = GetBossProfileCompanions(profile); if (companions == null) { @@ -2281,7 +2427,7 @@ void NPCAddCompanions(SF2NPC_BaseNPC Npc) { SF2BossProfileCompanionsInfo companionInfo; companions.GetArray(i, companionInfo, sizeof(companionInfo)); - maxWeight += companionInfo.Weight[GetLocalGlobalDifficulty(Npc.Index)]; + maxWeight += companionInfo.Weight[npc.Difficulty]; } float randomWeight = GetRandomFloat(0.0, maxWeight); @@ -2294,7 +2440,7 @@ void NPCAddCompanions(SF2NPC_BaseNPC Npc) continue; } - float weight = companionInfo.Weight[GetLocalGlobalDifficulty(Npc.Index)]; + float weight = companionInfo.Weight[npc.Difficulty]; if (weight <= 0.0) { continue; @@ -2319,10 +2465,10 @@ void NPCAddCompanions(SF2NPC_BaseNPC Npc) companionsToAdd.GetString(i, compProfile, sizeof(compProfile)); if (IsProfileValid(compProfile)) { - SF2NPC_BaseNPC NpcCompanion = AddProfile(compProfile, _, _, false, false); - if (NpcCompanion.IsValid()) + SF2NPC_BaseNPC npcCompanion = AddProfile(compProfile, _, _, false, false); + if (npcCompanion.IsValid()) { - g_SlenderCompanionMaster[NpcCompanion.Index] = Npc.Index; + npcCompanion.CompanionMaster = npc; } } else @@ -2375,62 +2521,166 @@ bool GetSlenderModel(int bossIndex, int modelState = 0, char[] buffer, int buffe } } } - case 1: - { - modelsArray = GetStatueProfileModelsAverageDist(profile); - switch (difficulty) + } + return true; +} + +bool NPCFindUnstuckPosition(SF2_BaseBoss boss, float lastPos[3], float destination[3]) +{ + SF2NPC_BaseNPC controller = boss.Controller; + PathFollower path = controller.Path; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(boss.index); + CNavArea area = TheNavMesh.GetNearestNavArea(lastPos, _, _, _, false); + area.GetCenter(destination); + float tempMaxs[3]; + npc.GetBodyMaxs(tempMaxs); + float traceMins[3]; + traceMins[0] = g_SlenderDetectMins[controller.Index][0] - 5.0; + traceMins[1] = g_SlenderDetectMins[controller.Index][1] - 5.0; + traceMins[2] = 0.0; + + float traceMaxs[3]; + traceMaxs[0] = g_SlenderDetectMaxs[controller.Index][0] + 5.0; + traceMaxs[1] = g_SlenderDetectMaxs[controller.Index][1] + 5.0; + traceMaxs[2] = tempMaxs[2]; + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (GetVectorSquareMagnitude(destination, lastPos) <= SquareFloat(16.0) || TR_DidHit()) + { + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 400.0); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + int randomArea = 0, randomCell = 0; + areaArray.Resize(validAreaCount); + area = NULL_AREA; + while (validAreaCount > 1) + { + randomCell = GetRandomInt(0, validAreaCount - 1); + randomArea = areaArray.Get(randomCell); + area = collector.Get(randomArea); + area.GetCenter(destination); + + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (TR_DidHit()) { - case Difficulty_Normal: - { - modelsArray.GetString(Difficulty_Normal, buffer, bufferLen); - } - case Difficulty_Hard: - { - modelsArray.GetString(Difficulty_Hard, buffer, bufferLen); - } - case Difficulty_Insane: - { - modelsArray.GetString(Difficulty_Insane, buffer, bufferLen); - } - case Difficulty_Nightmare: - { - modelsArray.GetString(Difficulty_Nightmare, buffer, bufferLen); - } - case Difficulty_Apollyon: + area = NULL_AREA; + validAreaCount--; + int findValue = areaArray.FindValue(randomCell); + if (findValue != -1) { - modelsArray.GetString(Difficulty_Apollyon, buffer, bufferLen); + areaArray.Erase(findValue); } } + else + { + break; + } } - case 2: + + delete collector; + delete areaArray; + } + path.GetClosestPosition(destination, destination, path.FirstSegment(), 400.0); + if (GetVectorSquareMagnitude(destination, lastPos) > SquareFloat(8.0)) + { + return true; + } + + Segment first = path.FirstSegment(); + if (first != NULL_PATH_SEGMENT) + { + int attempts = 0; + Segment next = NULL_PATH_SEGMENT; + while (attempts <= 2) { - modelsArray = GetStatueProfileModelsCloseDist(profile); - switch (difficulty) + next = path.NextSegment(first); + if (next == NULL_PATH_SEGMENT) { - case Difficulty_Normal: - { - modelsArray.GetString(Difficulty_Normal, buffer, bufferLen); - } - case Difficulty_Hard: - { - modelsArray.GetString(Difficulty_Hard, buffer, bufferLen); - } - case Difficulty_Insane: - { - modelsArray.GetString(Difficulty_Insane, buffer, bufferLen); - } - case Difficulty_Nightmare: - { - modelsArray.GetString(Difficulty_Nightmare, buffer, bufferLen); - } - case Difficulty_Apollyon: - { - modelsArray.GetString(Difficulty_Apollyon, buffer, bufferLen); - } + break; + } + float segmentPos[3], temp[3]; + next.GetPos(segmentPos); + path.GetClosestPosition(segmentPos, temp, next, 800.0); + if (GetVectorSquareMagnitude(temp, lastPos) > SquareFloat(64.0)) + { + destination = temp; + return true; } + first = next; + attempts++; } } - return true; + + SF2BossProfileData data; + data = controller.GetProfileData(); + + int ent = -1; + char targetName[64]; + if (data.IsPvEBoss) + { + ArrayList spawnPointList = new ArrayList(); + + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (!StrContains(targetName, "minigames_boss_spawnpoint", false)) + { + spawnPointList.Push(ent); + } + } + + if (spawnPointList.Length > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, spawnPointList.Length - 1)); + } + + delete spawnPointList; + + if (!IsValidEntity(ent)) + { + return false; + } + + CBaseEntity(ent).GetAbsOrigin(destination); + return true; + } + + if (SF_IsBoxingMap()) + { + ArrayList spawnPointList = new ArrayList(); + + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (!StrContains(targetName, "sf2_boss_respawnpoint", false)) + { + spawnPointList.Push(ent); + } + } + + if (spawnPointList.Length > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, spawnPointList.Length - 1)); + } + + delete spawnPointList; + + if (!IsValidEntity(ent)) + { + return false; + } + + CBaseEntity(ent).GetAbsOrigin(destination); + return true; + } + + return false; } void ChangeAllSlenderModels() @@ -2452,7 +2702,12 @@ void ChangeAllSlenderModels() NPCGetProfile(npcIndex, profile, sizeof(profile)); GetSlenderModel(npcIndex, _, buffer, sizeof(buffer)); SetEntityModel(slender, buffer); - SetEntityModel(EntRefToEntIndex(g_NpcGlowEntity[npcIndex]), buffer); + + if (SF2_ChaserEntity(slender).IsValid()) + { + SF2_ChaserEntity(slender).UpdateMovementAnimation(); + } + SetGlowModel(slender, buffer); if (NPCGetModelSkinMax(npcIndex) > 0) { int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(npcIndex)); @@ -2488,8 +2743,7 @@ void ChangeAllSlenderModels() if (NPCGetType(npcIndex) == SF2BossType_Chaser) { float tempHitboxMins[3]; - NPCChaserUpdateBossAnimation(npcIndex, slender, g_SlenderState[npcIndex]); - if (NPCGetRaidHitbox(npcIndex) == 1) + if (NPCGetRaidHitbox(npcIndex)) { CopyVector(g_SlenderDetectMins[npcIndex], tempHitboxMins); tempHitboxMins[2] = 10.0; @@ -2499,7 +2753,7 @@ void ChangeAllSlenderModels() SetEntPropVector(slender, Prop_Send, "m_vecMinsPreScaled", tempHitboxMins); SetEntPropVector(slender, Prop_Send, "m_vecMaxsPreScaled", g_SlenderDetectMaxs[npcIndex]); } - else if (NPCGetRaidHitbox(npcIndex) == 0) + else { CopyVector(HULL_HUMAN_MINS, tempHitboxMins); tempHitboxMins[2] = 10.0; @@ -2515,81 +2769,40 @@ void ChangeAllSlenderModels() void RemoveProfile(int bossIndex) { - RemoveSlender(bossIndex); + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(bossIndex); + if (IsValidEntity(controller.EntIndex)) + { + KillPvEBoss(controller.EntIndex); + } - // Call our forward. - Call_StartForward(g_OnBossRemovedFwd); - Call_PushCell(bossIndex); - Call_Finish(); + if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(bossIndex)) + { + g_SlenderBoxingBossCount -= 1; + } char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - NPCChaserOnRemoveProfile(bossIndex); + controller.GetProfile(profile, sizeof(profile)); - // Remove all possible sounds, for emergencies. - if (!MusicActive()) + if (!controller.IsCopy && MusicActive() && BossHasMusic(profile) && BossMatchesCurrentMusic(profile)) { - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i)) - { - continue; - } - - // Remove chase music. - if (g_PlayerChaseMusicMaster[i] == bossIndex) - { - ClientChaseMusicReset(i); - } - - // Don't forget search theme - if (g_PlayerAlertMusicMaster[i] == bossIndex) - { - ClientAlertMusicReset(i); - } + NPCStopMusic(); + } - if (g_PlayerChaseMusicSeeMaster[i] == bossIndex) - { - ClientChaseMusicSeeReset(i); - } + controller.UnSpawn(true); - if (g_PlayerIdleMusicMaster[i] == bossIndex) - { - ClientIdleMusicReset(i); - } + // Call our forward. + Call_StartForward(g_OnBossRemovedFwd); + Call_PushCell(controller.Index); + Call_Finish(); - ClientUpdateMusicSystem(i); - } - } + Call_StartForward(g_OnBossRemovedPFwd); + Call_PushCell(controller); + Call_Finish(); // Clean up on the clients. for (int i = 1; i <= MaxClients; i++) { - g_SlenderLastFoundPlayer[bossIndex][i] = -1.0; - g_PlayerLastChaseBossEncounterTime[i][bossIndex] = -1.0; g_SlenderTeleportPlayersRestTime[bossIndex][i] = -1.0; - - for (int i2 = 0; i2 < 3; i2++) - { - g_SlenderLastFoundPlayerPos[bossIndex][i][i2] = 0.0; - } - - if (IsClientInGame(i)) - { - if (NPCGetUniqueID(bossIndex) == g_PlayerStaticMaster[i]) - { - g_PlayerStaticMaster[i] = -1; - - // No one is the static master. - g_PlayerStaticTimer[i] = CreateTimer(g_PlayerStaticDecreaseRate[i], - Timer_ClientDecreaseStatic, - GetClientUserId(i), - TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - - TriggerTimer(g_PlayerStaticTimer[i], true); - } - } } g_NpcTeleportType[bossIndex] = -1; @@ -2600,46 +2813,39 @@ void RemoveProfile(int bossIndex) g_SlenderNextTeleportTime[bossIndex] = -1.0; g_SlenderTeleportTargetTime[bossIndex] = -1.0; g_SlenderTimeUntilKill[bossIndex] = -1.0; - g_NpcHasHealthbarEnabled[bossIndex] = false; // Remove all copies associated with me. for (int i = 0; i < MAX_BOSSES; i++) { - if (i == bossIndex || NPCGetUniqueID(i) == -1) + SF2NPC_BaseNPC check = SF2NPC_BaseNPC(i); + if (check == controller || !check.IsValid()) { continue; } - if (g_SlenderCopyMaster[i] == bossIndex) + if (check.CopyMaster == controller) { - LogMessage("Removed boss index %d because it is a copy of boss index %d", i, bossIndex); - NPCRemove(i); + LogMessage("Removed boss index %d because it is a copy of boss index %d", check.Index, controller.Index); + check.Remove(); } } for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - g_NpcSearchRadius[bossIndex][difficulty] = 0.0; - g_NpcHearingRadius[bossIndex][difficulty] = 0.0; - g_NpcTauntAlertRange[bossIndex][difficulty] = 0.0; - g_NpcInstantKillCooldown[bossIndex][difficulty] = 0.0; g_SlenderProxyTeleportMinRange[bossIndex][difficulty] = 0.0; g_SlenderProxyTeleportMaxRange[bossIndex][difficulty] = 0.0; } - NPCSetProfile(bossIndex, ""); g_NpcType[bossIndex] = -1; g_NpcProfileIndex[bossIndex] = -1; g_NpcUniqueProfileIndex[bossIndex] = -1; - NPCSetFlags(bossIndex, 0); + controller.Flags = 0; g_NpcFieldOfView[bossIndex] = 0.0; - g_NpcBackstabFOV[bossIndex] = 0.0; - g_NpcAddSpeed[bossIndex] = 0.0; - g_NpcAddMaxSpeed[bossIndex] = 0.0; - g_NpcAddAcceleration[bossIndex] = 0.0; + controller.SetAddSpeed(0.0); + controller.SetAddAcceleration(0.0); g_NpcStepSize[bossIndex] = 0.0; g_NpcHasDiscoMode[bossIndex] = false; @@ -2651,61 +2857,28 @@ void RemoveProfile(int bossIndex) g_NpcFestiveLightDistance[bossIndex] = 0.0; g_NpcFestiveLightRadius[bossIndex] = 0.0; - g_NpcEnemy[bossIndex] = INVALID_ENT_REFERENCE; - NPCSetDeathCamEnabled(bossIndex, false); - g_SlenderCopyMaster[bossIndex] = -1; - g_SlenderCompanionMaster[bossIndex] = -1; + controller.CopyMaster = SF2_INVALID_NPC; + controller.CompanionMaster = SF2_INVALID_NPC; g_NpcUniqueID[bossIndex] = -1; - g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; - g_SlenderAttackTimer[bossIndex] = null; - g_SlenderLaserTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_SlenderChaseInitialTimer[bossIndex] = null; - g_SlenderRage1Timer[bossIndex] = null; - g_SlenderRage2Timer[bossIndex] = null; - g_SlenderRage3Timer[bossIndex] = null; - g_SlenderHealTimer[bossIndex] = null; - g_SlenderHealDelayTimer[bossIndex] = null; - g_SlenderHealEventTimer[bossIndex] = null; - g_SlenderStartFleeTimer[bossIndex] = null; - g_SlenderSpawnTimer[bossIndex] = null; g_SlenderDeathCamTimer[bossIndex] = null; g_SlenderDeathCamTarget[bossIndex] = INVALID_ENT_REFERENCE; g_SlenderThink[bossIndex] = null; g_SlenderEntityThink[bossIndex] = null; - g_SlenderBurnTimer[bossIndex] = null; - g_SlenderBleedTimer[bossIndex] = null; - g_SlenderMarkedTimer[bossIndex] = null; - g_SlenderStopBurningTimer[bossIndex] = 0.0; - g_SlenderStopBleedingTimer[bossIndex] = 0.0; - g_SlenderIsBurning[bossIndex] = false; - g_SlenderIsMarked[bossIndex] = false; - g_SlenderSoundTarget[bossIndex] = INVALID_ENT_REFERENCE; - g_SlenderSeeTarget[bossIndex] = INVALID_ENT_REFERENCE; g_SlenderCustomOutroSong[bossIndex] = false; g_SlenderFakeTimer[bossIndex] = null; - g_SlenderLastKill[bossIndex] = -1.0; - g_SlenderState[bossIndex] = STATE_IDLE; - g_SlenderTarget[bossIndex] = INVALID_ENT_REFERENCE; - g_SlenderTargetIsVisible[bossIndex] = false; g_SlenderModel[bossIndex] = INVALID_ENT_REFERENCE; g_SlenderBoxingBossIsKilled[bossIndex] = false; g_SlenderTimeUntilNextProxy[bossIndex] = -1.0; - g_NpcInstantKillRadius[bossIndex] = 0.0; g_NpcScareRadius[bossIndex] = 0.0; g_NpcHasPlayerScareSpeedBoost[bossIndex] = false; g_NpcPlayerSpeedBoostDuration[bossIndex] = 0.0; g_NpcHasPlayerScareReaction[bossIndex] = false; g_NpcPlayerScareReactionType[bossIndex] = 0; g_NpcHasPlayerScareReplenishSprint[bossIndex] = false; - g_NpcPlayerScareReplenishSprintAmount[bossIndex] = 0; - g_NpcPlayerScareVictin[bossIndex] = INVALID_ENT_REFERENCE; - g_NpcChasingScareVictin[bossIndex] = false; - g_NpcLostChasingScareVictim[bossIndex] = false; - g_NpcVelocityCancel[bossIndex] = false; + g_NpcPlayerScareReplenishSprintAmount[bossIndex] = 0.0; g_SlenderRenderFX[bossIndex] = 0; g_SlenderRenderMode[bossIndex] = 0; @@ -2723,13 +2896,15 @@ void RemoveProfile(int bossIndex) { g_SlenderRenderColor[bossIndex][i] = 0; } + + controller.SetProfile(""); } -void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) +void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) { if (!IsRoundPlaying()) { - return; + //return; } if (SF_IsRenevantMap() && GetRoundState() != SF2RoundState_Escape) @@ -2738,134 +2913,61 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) } char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.UnSpawn(); - Npc.GetProfile(profile,sizeof(profile)); + npc.UnSpawn(true); + npc.GetProfile(profile,sizeof(profile)); + npc.WasKilled = false; float truePos[3], trueAng[3]; trueAng[1] = GetRandomFloat(0.0, 360.0); AddVectors(truePos, pos, truePos); - int bossIndex = Npc.Index; - int difficulty = GetLocalGlobalDifficulty(bossIndex); + int bossIndex = npc.Index; char buffer[PLATFORM_MAX_PATH]; GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); - CBaseNPC npcBoss = CBaseNPC(); - CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npcBoss.GetEntity()); - CBaseNPC_Locomotion locomotion = npcBoss.GetLocomotion(); - npcEntity.Hook_HandleAnimEvent(CBaseAnimating_HandleAnimEvent); - - npcEntity.Teleport(truePos, trueAng); - npcEntity.SetModel(buffer); - npcEntity.SetRenderMode(view_as(g_SlenderRenderMode[bossIndex])); - npcEntity.SetRenderFx(view_as(g_SlenderRenderFX[bossIndex])); - npcEntity.SetRenderColor(g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], g_SlenderRenderColor[bossIndex][3]); - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - float scaleModel = NPCGetModelScale(bossIndex) * 0.5; - npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); - } - else - { - npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", NPCGetModelScale(bossIndex)); - } - npcEntity.Spawn(); - npcEntity.Activate(); - - npcBoss.flStepSize = 18.0; - npcBoss.flGravity = g_Gravity; - npcBoss.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; - npcBoss.flDeathDropHeight = 99999.0; - npcBoss.flJumpHeight = 512.0; - npcBoss.flWalkSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; - npcBoss.flRunSpeed = g_SlenderCalculatedSpeed[bossIndex]; - - g_BossPathFollower[bossIndex].SetMinLookAheadDistance(GetBossProfileNodeDistanceLookAhead(profile)); - - if (NPCGetModelSkinMax(bossIndex) > 0) - { - int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(bossIndex)); - npcEntity.SetProp(Prop_Send, "m_nSkin", randomSkin); - } - else - { - if (GetBossProfileSkinDifficultyState(profile)) - { - npcEntity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkinDifficulty(bossIndex, difficulty)); - } - else - { - npcEntity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkin(bossIndex)); - } - } - if (NPCGetModelBodyGroupsMax(bossIndex) > 0) - { - int randomBody = GetRandomInt(0, NPCGetModelBodyGroupsMax(bossIndex)); - npcEntity.SetProp(Prop_Send, "m_nBody", randomBody); - } - else - { - if (GetBossProfileBodyGroupsDifficultyState(profile)) - { - npcEntity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroupsDifficulty(bossIndex, difficulty)); - } - else - { - npcEntity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroups(bossIndex)); - } - } - - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, npcEntity.iEnt, Hook_BossUpdateTransmitState); - SetEntityFlags(npcEntity.iEnt, FL_NPC); - SetEntityTransmitState(npcEntity.iEnt, FL_EDICT_ALWAYS); - - g_SlenderEnt[bossIndex] = EntIndexToEntRef(npcEntity.iEnt); - - if (g_SlenderEngineSound[bossIndex][0] != '\0') - { - EmitSoundToAll(g_SlenderEngineSound[bossIndex], npcEntity.iEnt, SNDCHAN_STATIC, GetBossProfileEngineSoundLevel(profile), - _, GetBossProfileEngineSoundVolume(profile)); - } - - if (GetBossProfileSpawnParticleState(profile)) - { - SlenderCreateParticleSpawnEffect(bossIndex); - } + CBaseCombatCharacter entity; switch (NPCGetType(bossIndex)) { case SF2BossType_Statue: { - npcBoss.flMaxYawRate = 0.0; - - locomotion.SetCallback(LocomotionCallback_IsAbleToJumpAcrossGaps, CanJumpAcrossGaps); - locomotion.SetCallback(LocomotionCallback_IsAbleToClimb, CanJumpAcrossGaps); - locomotion.SetCallback(LocomotionCallback_JumpAcrossGap, JumpAcrossGapsCBase); - locomotion.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); - - npcEntity.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); - npcEntity.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); - - npcEntity.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); - npcEntity.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); - - npcEntity.SetProp(Prop_Data, "m_nSolidType", SOLID_BBOX); - npcEntity.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); - - SDKHook(npcEntity.iEnt, SDKHook_ThinkPost, SlenderStatueSetNextThink); - SDKHook(npcEntity.iEnt, SDKHook_Think, SlenderStatueBossProcessMovement); - - g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderBlinkBossThink, g_SlenderEnt[bossIndex], TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - - Spawn_Statue(bossIndex); + entity = view_as(Spawn_Statue(npc, truePos, trueAng)); } case SF2BossType_Chaser: { - npcBoss.flMaxYawRate = NPCGetTurnRate(bossIndex); + entity = view_as(Spawn_Chaser(npc, truePos, trueAng)); + /*CBaseNPC npcBoss = CBaseNPC(); + CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npcBoss.GetEntity()); + CBaseNPC_Locomotion locomotion = npcBoss.GetLocomotion(); + npcEntity.Hook_HandleAnimEvent(CBaseAnimating_HandleAnimEvent); + + npcEntity.Teleport(truePos, trueAng); + npcEntity.SetModel(buffer); + npcEntity.SetRenderMode(view_as(g_SlenderRenderMode[bossIndex])); + npcEntity.SetRenderFx(view_as(g_SlenderRenderFX[bossIndex])); + npcEntity.SetRenderColor(g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], g_SlenderRenderColor[bossIndex][3]); + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + float scaleModel = NPCGetModelScale(bossIndex) * 0.5; + npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); + } + else + { + npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", NPCGetModelScale(bossIndex)); + } + npcEntity.Spawn(); + npcEntity.Activate(); + + npcBoss.flStepSize = 18.0; + npcBoss.flGravity = g_Gravity; + npcBoss.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; + npcBoss.flDeathDropHeight = 99999.0; + npcBoss.flJumpHeight = 512.0; + npcBoss.flWalkSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; + npcBoss.flRunSpeed = g_SlenderCalculatedSpeed[bossIndex]; - locomotion.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); if (!SF_IsBoxingMap()) { locomotion.SetCallback(LocomotionCallback_IsAbleToJumpAcrossGaps, CanJumpAcrossGaps); @@ -2874,7 +2976,7 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) locomotion.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); } - if (NPCGetRaidHitbox(bossIndex) == 1) + if (NPCGetRaidHitbox(bossIndex)) { npcBoss.SetBodyMins(g_SlenderDetectMins[bossIndex]); npcBoss.SetBodyMaxs(g_SlenderDetectMaxs[bossIndex]); @@ -2885,7 +2987,7 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) npcEntity.SetPropVector(Prop_Send, "m_vecMinsPreScaled", g_SlenderDetectMins[bossIndex]); npcEntity.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", g_SlenderDetectMaxs[bossIndex]); } - else if (NPCGetRaidHitbox(bossIndex) == 0) + else { npcBoss.SetBodyMins(HULL_HUMAN_MINS); npcBoss.SetBodyMaxs(HULL_HUMAN_MAXS); @@ -2927,7 +3029,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) g_SlenderTargetSoundType[bossIndex] = SoundType_None; g_SlenderInvestigatingSound[bossIndex] = false; g_SlenderNextStunTime[bossIndex] = -1.0; - g_SlenderNextCloakTime[bossIndex] = -1.0; g_NpcHasCloaked[bossIndex] = false; g_SlenderLastHeardFootstep[bossIndex] = 0.0; g_SlenderLastHeardVoice[bossIndex] = 0.0; @@ -2967,7 +3068,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) g_SlenderIsBurning[bossIndex] = false; g_SlenderIsMarked[bossIndex] = false; g_NpcAddSpeed[bossIndex] = 0.0; - g_NpcAddMaxSpeed[bossIndex] = 0.0; g_NpcAddAcceleration[bossIndex] = 0.0; g_SlenderAutoChaseCount[bossIndex] = 0; g_SlenderAutoChaseCooldown[bossIndex] = 0.0; @@ -3022,10 +3122,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) NPCSetFlags(bossIndex,NPCGetFlags(bossIndex)|SFF_NOTELEPORT); UpdateHealthBar(bossIndex); } - /*if (SF_BossesChaseEndlessly() || SF_IsRaidMap()) - { - NPCSetFlags(bossIndex,NPCGetFlags(bossIndex)|SFF_NOTELEPORT); - }*/ //Stun Health float maxHealth = NPCChaserGetStunInitialHealth(bossIndex); @@ -3048,76 +3144,116 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) if (SF_IsBoxingMap() && TF2_GetPlayerClass(client) == TFClass_Scout) { NPCSetAddSpeed(bossIndex, 10.0); - NPCSetAddMaxSpeed(bossIndex, 15.0); } } } NPCChaserSetStunInitialHealth(bossIndex, maxHealth); - NPCChaserSetStunHealth(bossIndex, NPCChaserGetStunInitialHealth(bossIndex)); int entHealth = RoundToCeil(maxHealth + 1500000000.0); npcEntity.SetProp(Prop_Data, "m_iHealth", entHealth); npcEntity.SetProp(Prop_Data, "m_iMaxHealth", entHealth); + + entity = npcEntity;*/ } } - if (NPCGetCustomOutlinesState(bossIndex)) + SF2BossProfileData data; + data = npc.GetProfileData(); + + if (g_Enabled) { - if (!NPCGetRainbowOutlineState(bossIndex)) + if (!data.IsPvEBoss && (npc.Flags & SFF_ATTACKWAITERS) == 0) { - int color[4]; - color[0] = NPCGetOutlineColorR(bossIndex); - color[1] = NPCGetOutlineColorG(bossIndex); - color[2] = NPCGetOutlineColorB(bossIndex); - color[3] = NPCGetOutlineTransparency(bossIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(bossIndex, color); + entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + } + else + { + entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Spectator); + } + } + else + { + entity.SetProp(Prop_Data, "m_iTeamNum", NPCGetDefaultTeam(bossIndex)); + } + + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + if (g_BossPathFollower[bossIndex] == view_as(0)) + { + g_BossPathFollower[bossIndex] = PathFollower(_, TraceRayDontHitAnyEntity_Pathing, Path_FilterOnlyActors); + } + + g_BossPathFollower[bossIndex].SetMinLookAheadDistance(GetBossProfileNodeDistanceLookAhead(profile)); + + if (NPCGetModelSkinMax(bossIndex) > 0) + { + int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(bossIndex)); + entity.SetProp(Prop_Send, "m_nSkin", randomSkin); + } + else + { + if (GetBossProfileSkinDifficultyState(profile)) + { + entity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkinDifficulty(bossIndex, difficulty)); + } + else + { + entity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkin(bossIndex)); + } + } + if (NPCGetModelBodyGroupsMax(bossIndex) > 0) + { + int randomBody = GetRandomInt(0, NPCGetModelBodyGroupsMax(bossIndex)); + entity.SetProp(Prop_Send, "m_nBody", randomBody); + } + else + { + if (GetBossProfileBodyGroupsDifficultyState(profile)) + { + entity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroupsDifficulty(bossIndex, difficulty)); } else { - SlenderAddGlow(bossIndex, view_as({0, 0, 0, 0})); + entity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroups(bossIndex)); + } + } + + entity.AddFlag(FL_NPC); + if (g_Enabled) + { + if (!data.IsPvEBoss && !SF_IsRaidMap()) + { + entity.AddFlag(FL_NOTARGET); } } else { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(bossIndex, purple); + entity.AddFlag(FL_CLIENT); + } + //SetEntityTransmitState(entity.index, FL_EDICT_ALWAYS); + + g_SlenderEnt[bossIndex] = EntIndexToEntRef(entity.index); + + if (data.EngineSound[0] != '\0') + { + EmitSoundToAll(data.EngineSound, entity.index, SNDCHAN_STATIC, data.EngineSoundLevel, + _, data.EngineSoundVolume); + } + + if (data.SpawnEffects != null) + { + StringMapSnapshot snapshot = data.SpawnEffects.Snapshot(); + char key[64]; + snapshot.GetKey(GetRandomInt(0, snapshot.Length - 1), key, sizeof(key)); + ArrayList effects; + data.SpawnEffects.GetValue(key, effects); + SlenderSpawnEffects(effects, bossIndex, false); + delete snapshot; } int master = g_SlenderCopyMaster[bossIndex]; int flags = NPCGetFlags(bossIndex); - if (MAX_BOSSES > master >= 0 && NPCGetFakeCopyState(bossIndex)) + if (MAX_BOSSES > master >= 0 && NPCGetFakeCopyState(bossIndex, difficulty)) { if (!SF_SpecialRound(SPECIALROUND_DREAMFAKEBOSSES)) { @@ -3125,69 +3261,38 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) } } - SlenderSpawnEffects(bossIndex); + ArrayList effects = data.EffectsArray; + SlenderSpawnEffects(effects, bossIndex); - if (IsValidEntity(g_SlenderEnt[bossIndex])) + if (entity.IsValid()) { SF2BossProfileSoundInfo soundInfo; GetBossProfileSpawnLocalSounds(profile, soundInfo); - soundInfo.EmitSound(_, EntRefToEntIndex(g_SlenderEnt[bossIndex])); + soundInfo.EmitSound(_, entity.index); } // Call our forward. Call_StartForward(g_OnBossSpawnFwd); Call_PushCell(bossIndex); Call_Finish(); -} -static MRESReturn Hook_BossUpdateTransmitState(int bossEntity, DHookReturn hookReturn) -{ - if (!g_Enabled || !IsValidEntity(bossEntity) || NPCGetFromEntIndex(bossEntity) == -1) - { - return MRES_Ignored; - } - - hookReturn.Value = SetEntityTransmitState(bossEntity, FL_EDICT_ALWAYS); - return MRES_Supercede; + Call_StartForward(g_OnBossSpawnPFwd); + Call_PushCell(npc); + Call_Finish(); } -static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) +bool ClimbUpCBase(CBaseNPC_Locomotion loco, const float goal[3], const float fwd[3], int entity) { - if (IsValidEntity(other)) + INextBot bot = loco.GetBot(); + SF2_BaseBoss boss = SF2_BaseBoss(bot.GetEntity()); + if (boss.IsValid()) { - char class[32]; - GetEdictClassname(other, class, sizeof(class)); - if (strcmp(class, "player") == 0) - { - if (!SF_IsBoxingMap() && !g_PlayerProxy[other] && !IsClientInGhostMode(other) && GetClientTeam(other) != TFTeam_Blue && !IsClientInDeathCam(other)) - { - return true; - } - } - if (IsEntityAProjectile(other)) - { - return false; - } + boss.IsJumping = true; } - return loco.CallBaseFunction(other); -} - -static bool CanJumpAcrossGaps(CBaseNPC_Locomotion loco) -{ - return false; -} - -static void JumpAcrossGapsCBase(CBaseNPC_Locomotion loco, const float landingGoal[3], const float landingForward[3]) -{ - return; -} - -static bool ClimbUpCBase(CBaseNPC_Locomotion loco, const float vecGoal[3], const float vecForward[3], int entity) -{ - return false; + return loco.CallBaseFunction(goal, fwd, entity); } -static MRESReturn CBaseAnimating_HandleAnimEvent(int thisInt, DHookParam params) +MRESReturn CBaseAnimating_HandleAnimEvent(int thisInt, DHookParam params) { int bossIndex = NPCGetFromEntIndex(thisInt); if (bossIndex == -1) @@ -3204,111 +3309,52 @@ static MRESReturn CBaseAnimating_HandleAnimEvent(int thisInt, DHookParam params) return MRES_Ignored; } -void RemoveSlender(int bossIndex) +void UpdateHealthBar(int bossIndex, int optionalSetPercent = -1) { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int bossEnt = NPCGetEntIndex(bossIndex); - - if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(bossIndex)); + if (!chaser.IsValid()) { - Call_StartForward(g_OnBossDespawnFwd); - Call_PushCell(bossIndex); - Call_Finish(); - - //Turn off all slender's effects in order to prevent some bugs. - SlenderRemoveEffects(bossEnt, true); - - if (NPCGetType(bossIndex) == SF2BossType_Statue) - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderStatueBossProcessMovement); - // Stop all possible looping sounds. - SF2BossProfileSoundInfo soundInfo; - GetStatueProfileMoveSounds(profile, soundInfo); - soundInfo.StopAllSounds(bossEnt); - } - else - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderChaseBossProcessMovement); - SDKUnhook(bossEnt, SDKHook_OnTakeDamageAlive, Hook_SlenderOnTakeDamage); - Despawn_Chaser(bossIndex); - } - - if (g_BossPathFollower[bossIndex].IsValid()) - { - g_BossPathFollower[bossIndex].Invalidate(); - } - - int glowEnt = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - if (glowEnt != INVALID_ENT_REFERENCE) - { - RemoveEntity(glowEnt); - g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; - } - - if (GetBossProfileDespawnParticleState(profile)) - { - SlenderCreateParticleSpawnEffect(bossIndex, true); - } - - if (g_SlenderEngineSound[bossIndex][0] != '\0') - { - StopSound(bossEnt, SNDCHAN_STATIC, g_SlenderEngineSound[bossIndex]); - } - - if (NPCGetFlags(bossIndex) & SFF_HASSTATICLOOPLOCALSOUND) - { - char loopSound[PLATFORM_MAX_PATH]; - GetBossProfileStaticLocalSound(profile, loopSound, sizeof(loopSound)); - - if (loopSound[0] != '\0') - { - StopSound(bossEnt, SNDCHAN_STATIC, loopSound); - } - } - int bossFlags = NPCGetFlags(bossIndex); - if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || !NPCGetHealthbarState(bossIndex)) - { - NPCSetFlags(bossIndex, bossFlags & ~SFF_NOTELEPORT); - } - - g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; - RemoveEntity(bossEnt); + return; } - else + float maxHealth = chaser.MaxHealth; + float health = float(chaser.GetProp(Prop_Data, "m_iHealth")); + if (chaser.GetProp(Prop_Data, "m_takedamage") == DAMAGE_EVENTS_ONLY) { - g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; - g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; + health = chaser.StunHealth; + maxHealth = chaser.MaxStunHealth; } - if (NPCGetHealthbarState(bossIndex) && g_HealthBar != -1) + if (g_HealthBar == -1) { - int npcIndex = 0; - for (npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - if (NPCGetHealthbarState(npcIndex)) - { - int tempSlender = NPCGetEntIndex(npcIndex); + return; + } + int healthPercent; + SetEntProp(g_HealthBar, Prop_Send, "m_iBossState", 0); + healthPercent = RoundToCeil((health / maxHealth) * float(255)); + if (healthPercent > 255) + { + healthPercent = 255; + } + else if (healthPercent <= 0) + { + healthPercent = 0; + } + if (optionalSetPercent > -1) + { + healthPercent = optionalSetPercent; + } + SetEntProp(g_HealthBar, Prop_Send, "m_iBossHealthPercentageByte", healthPercent); +} - if (tempSlender && tempSlender != INVALID_ENT_REFERENCE) - { - UpdateHealthBar(npcIndex); - break; - } - } - } - if (npcIndex == MAX_BOSSES) - { - UpdateHealthBar(bossIndex, 0); - } +void SetHealthBarColor(bool green) +{ + if (g_HealthBar == -1) + { + return; } + SetEntProp(g_HealthBar, Prop_Send, "m_iBossState", view_as(green)); } -Action Timer_BossBurn(Handle timer, any entref) +/*Action Timer_BossBurn(Handle timer, any entref) { if (!g_Enabled) { @@ -3338,7 +3384,7 @@ Action Timer_BossBurn(Handle timer, any entref) g_SlenderIsBurning[bossIndex] = false; return Plugin_Stop; } - int state = g_SlenderState[bossIndex]; + //int state = g_SlenderState[bossIndex]; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -3357,7 +3403,7 @@ Action Timer_BossBurn(Handle timer, any entref) { float myPos[3]; GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - NPCBossTriggerStun(bossIndex, slender, profile, myPos); + //NPCBossTriggerStun(bossIndex, slender, profile, myPos); Call_StartForward(g_OnBossStunnedFwd); Call_PushCell(bossIndex); Call_PushCell(-1); @@ -3401,7 +3447,7 @@ Action Timer_BossBleed(Handle timer, any entref) g_SlenderBleedTimer[bossIndex] = null; return Plugin_Stop; } - int state = g_SlenderState[bossIndex]; + //int state = g_SlenderState[bossIndex]; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -3419,7 +3465,7 @@ Action Timer_BossBleed(Handle timer, any entref) { float myPos[3]; GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - NPCBossTriggerStun(bossIndex, slender, profile, myPos); + //NPCBossTriggerStun(bossIndex, slender, profile, myPos); Call_StartForward(g_OnBossStunnedFwd); Call_PushCell(bossIndex); Call_PushCell(-1); @@ -3467,247 +3513,40 @@ Action Timer_BossMarked(Handle timer, any entref) g_SlenderIsMarked[bossIndex] = false; return Plugin_Stop; -} +}*/ -static Action Hook_SlenderGlowSetTransmit(int entity,int other) +bool SlenderUsesBlink(int bossIndex) { - if (!g_Enabled) - { - return Plugin_Continue; - } - if (g_PlayerProxy[other]) - { - return Plugin_Continue; - } - if (IsClientInGhostMode(other)) - { - return Plugin_Continue; - } - if ((SF_SpecialRound(SPECIALROUND_WALLHAX) || g_EnableWallHaxConVar.BoolValue || g_RenevantWallHax) && GetClientTeam(other) == TFTeam_Red && !DidClientEscape(other) && !g_PlayerEliminated[other]) - { - return Plugin_Continue; - } - if (g_RestartSessionEnabled) + if (NPCGetType(bossIndex) == SF2BossType_Statue) { - return Plugin_Continue; + return true; } - return Plugin_Handled; + return false; } -bool SlenderCanHearPlayer(int bossIndex,int client, SoundType soundType) +void SlenderPrintChatMessage(int bossIndex, int player) { - if (!IsValidClient(client) || !IsPlayerAlive(client)) + if (g_Enabled && GetClientTeam(player) != TFTeam_Red) { - return false; + return; } - int difficulty = GetLocalGlobalDifficulty(bossIndex); + if (bossIndex == -1) + { + return; + } int slender = NPCGetEntIndex(bossIndex); if (!slender || slender == INVALID_ENT_REFERENCE) { - return false; + return; } - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - float hisPos[3], myPos[3]; - GetClientAbsOrigin(client, hisPos); - SlenderGetAbsOrigin(bossIndex, myPos); - - float hearRadius = NPCGetHearingRadius(bossIndex, difficulty); - if (hearRadius <= 0.0) - { - return false; - } - - float distance = GetVectorSquareMagnitude(hisPos, myPos); - - // Trace check. - Handle trace = null; - bool traceHit = false; - - float myEyePos[3]; - SlenderGetEyePosition(bossIndex, myEyePos); - - float cooldown = 0.0; - - switch (soundType) - { - case SoundType_Footstep, SoundType_LoudFootstep, SoundType_QuietFootstep: - { - if (!(GetEntityFlags(client) & FL_ONGROUND)) - { - return false; - } - - if (soundType == SoundType_QuietFootstep) - { - if (GetChaserProfileQuietFootstepAddThreshold(profile, difficulty) <= 0) - { - return false; - } - cooldown = GetChaserProfileHearQuietFootstepCooldown(profile, difficulty); - distance *= 1.85; - } - else if (soundType == SoundType_LoudFootstep) - { - if (GetChaserProfileLoudFootstepAddThreshold(profile, difficulty) <= 0) - { - return false; - } - cooldown = GetChaserProfileHearLoudFootstepCooldown(profile, difficulty); - distance *= 0.66; - } - else - { - if (GetChaserProfileFootstepAddThreshold(profile, difficulty) <= 0) - { - return false; - } - cooldown = GetChaserProfileHearFootstepCooldown(profile, difficulty); - } - - trace = TR_TraceRayFilterEx(myPos, hisPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, slender); - traceHit = TR_DidHit(trace); - delete trace; - } - case SoundType_Voice, SoundType_Flashlight: - { - float hisEyePos[3]; - GetClientEyePosition(client, hisEyePos); - - trace = TR_TraceRayFilterEx(myEyePos, hisEyePos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, slender); - traceHit = TR_DidHit(trace); - delete trace; - - if (!IsClassConfigsValid() || soundType == SoundType_Voice) - { - distance *= 0.5; - } - else if (IsClassConfigsValid() && soundType == SoundType_Flashlight) - { - distance *= g_ClassFlashlightSoundRadius[classToInt]; - } - - if (soundType == SoundType_Voice) - { - if (GetChaserProfileVoiceAddThreshold(profile, difficulty) <= 0) - { - return false; - } - cooldown = GetChaserProfileHearVoiceCooldown(profile, difficulty); - } - else if (soundType == SoundType_Flashlight) - { - if (GetChaserProfileFlashlightAddThreshold(profile, difficulty) <= 0) - { - return false; - } - cooldown = GetChaserProfileHearFlashlightCooldown(profile, difficulty); - } - } - case SoundType_Weapon: - { - if (GetChaserProfileWeaponAddThreshold(profile, difficulty) <= 0) - { - return false; - } - - float hisMins[3], hisMaxs[3]; - GetEntPropVector(client, Prop_Send, "m_vecMins", hisMins); - GetEntPropVector(client, Prop_Send, "m_vecMaxs", hisMaxs); - - float middle[3]; - for (int i = 0; i < 2; i++) - { - middle[i] = (hisMins[i] + hisMaxs[i]) / 2.0; - } - - float endPos[3]; - GetClientAbsOrigin(client, endPos); - AddVectors(hisPos, middle, endPos); - - trace = TR_TraceRayFilterEx(myEyePos, endPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, slender); - traceHit = TR_DidHit(trace); - delete trace; - - distance *= 0.66; - - cooldown = GetChaserProfileHearWeaponCooldown(profile, difficulty); - } - } - - delete trace; - - if (traceHit) - { - distance *= 1.66; - } - - if (!IsClassConfigsValid()) - { - if (TF2_GetPlayerClass(client) == TFClass_Spy) - { - distance *= 1.3; - } - - if (TF2_GetPlayerClass(client) == TFClass_Scout) - { - distance *= 0.8; - } - } - else - { - distance *= g_ClassBossHearingSensitivity[classToInt]; - } - - if (distance > SquareFloat(hearRadius)) - { - return false; - } - - if (g_SlenderSoundPositionSetCooldown[bossIndex] > GetGameTime() && cooldown > 0.0) - { - return false; - } - - g_SlenderSoundPositionSetCooldown[bossIndex] = GetGameTime() + cooldown; - - return true; -} - -bool SlenderUsesBlink(int bossIndex) -{ - if (NPCGetType(bossIndex) == SF2BossType_Statue) - { - return true; - } - return false; -} - -void SlenderPrintChatMessage(int bossIndex, int player) -{ - if (bossIndex == -1) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - ArrayList deathMessages = GetBossProfileChatDeathMessages(profile); - if (deathMessages == null) + ArrayList deathMessages = GetBossProfileChatDeathMessages(profile); + if (deathMessages == null) { return; } @@ -3741,11 +3580,23 @@ void SlenderPrintChatMessage(int bossIndex, int player) { prefix = "[SF2]"; } - if (buffer[0] != '\0' && GetClientTeam(player) == 2) + if (buffer[0] != '\0') { if (StrContains(buffer, "[PLAYER]", true) != -1) { - FormatEx(replacePlayer, sizeof(replacePlayer), "{red}%s{default}", playerName); + switch (GetClientTeam(player)) + { + case TFTeam_Red: + { + FormatEx(replacePlayer, sizeof(replacePlayer), "{red}%s{default}", playerName); + } + + case TFTeam_Blue: + { + FormatEx(replacePlayer, sizeof(replacePlayer), "{blue}%s{default}", playerName); + } + } + ReplaceString(buffer, sizeof(buffer), "[PLAYER]", replacePlayer); } if (StrContains(buffer, "[BOSS]", true) != -1) @@ -3770,157 +3621,6 @@ void SlenderPrintChatMessage(int bossIndex, int player) } } -void SlenderPerformVoice(int bossIndex, const int attackIndex=-1, int soundType = -1) -{ - if (bossIndex == -1 || soundType == -1) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - ArrayList soundList; - switch (soundType) - { - case SF2BossSound_Idle: - { - GetChaserProfileIdleSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Alert: - { - GetChaserProfileAlertSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Chasing: - { - GetChaserProfileChasingSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_ChaseInitial: - { - GetChaserProfileChaseInitialSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Stun: - { - GetChaserProfileStunSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Attack: - { - ArrayList attackSounds = GetChaserProfileAttackSounds(profile); - if (attackSounds != null && attackSounds.Length > 0) - { - int length = attackSounds.Length; - attackSounds.GetArray(length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - SlenderPerformVoiceCooldown(bossIndex, slender, soundInfo, soundList); - } - } - return; - } - case SF2BossSound_AttackKilled: - { - GetChaserProfileAttackKilledSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_RageAll: - { - GetChaserProfileRageAllSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_RageTwo: - { - GetChaserProfileRageTwoSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_RageThree: - { - GetChaserProfileRageThreeSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_SelfHeal: - { - GetChaserProfileSelfHealSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - } - if (soundList != null && soundList.Length > 0) - { - SlenderPerformVoiceCooldown(bossIndex, slender, soundInfo, soundList); - } -} - -void SlenderPerformVoiceCooldown(int bossIndex, int slender, SF2BossProfileSoundInfo soundInfo, ArrayList soundList) -{ - char buffer[512]; - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0') - { - float threshold = GetRandomFloat(0.0, 1.0); - float cooldown = GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); - if (threshold > soundInfo.Chance) - { - g_SlenderNextVoiceSound[bossIndex] = GetGameTime() + cooldown; - return; - } - soundInfo.EmitSound(_, slender, _, _, SF_SpecialRound(SPECIALROUND_TINYBOSSES) ? 25 : 0); - g_SlenderNextVoiceSound[bossIndex] = GetGameTime() + cooldown; - } -} - -void SlenderCastFootstep(int bossIndex) -{ - if (bossIndex == -1) - { - return; - } - - if (g_SlenderFootstepTime[bossIndex] <= 0.0) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileFootstepSounds(profile, soundInfo); - if (soundInfo.Paths == null || soundInfo.Paths.Length <= 0) - { - return; - } - - float myPos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - g_SlenderNextFootstepSound[bossIndex] = GetGameTime() + g_SlenderFootstepTime[bossIndex]; - soundInfo.EmitSound(_, slender); - if (NPCChaserGetEarthquakeFootstepsState(bossIndex)) - { - UTIL_ScreenShake(myPos, NPCChaserGetEarthquakeFootstepsAmplitude(bossIndex), - NPCChaserGetEarthquakeFootstepsFrequency(bossIndex), NPCChaserGetEarthquakeFootstepsDuration(bossIndex), - NPCChaserGetEarthquakeFootstepsRadius(bossIndex), 0, NPCChaserGetEarthquakeFootstepsAirShakeState(bossIndex)); - } -} - void SlenderCastFootstepAnimEvent(int bossIndex, int event, int slender) { if (bossIndex == -1) @@ -3963,11 +3663,13 @@ void SlenderCastFootstepAnimEvent(int bossIndex, int event, int slender) GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); soundInfo.EmitSound(_, slender); - if (NPCChaserGetEarthquakeFootstepsState(bossIndex)) + SF2ChaserBossProfileData data; + data = SF2NPC_Chaser(bossIndex).GetProfileData(); + if (data.EarthquakeFootsteps) { - UTIL_ScreenShake(myPos, NPCChaserGetEarthquakeFootstepsAmplitude(bossIndex), - NPCChaserGetEarthquakeFootstepsFrequency(bossIndex), NPCChaserGetEarthquakeFootstepsDuration(bossIndex), - NPCChaserGetEarthquakeFootstepsRadius(bossIndex), 0, NPCChaserGetEarthquakeFootstepsAirShakeState(bossIndex)); + UTIL_ScreenShake(myPos, data.EarthquakeFootstepAmplitude, + data.EarthquakeFootstepFrequency, data.EarthquakeFootstepDuration, + data.EarthquakeFootstepRadius, 0, data.EarthquakeFootstepAirShake); } } @@ -4011,208 +3713,26 @@ void SlenderCastAnimEvent(int bossIndex, int event, int slender) soundInfo.EmitSound(_, slender); } -void SlenderCreateParticle(int bossIndex, const char[] sectionName, float particleZPos) -{ - if (bossIndex == -1) - { - return; - } - - if (g_RestartSessionEnabled) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - float slenderPosition[3], slenderAngles[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", slenderAngles); - slenderPosition[2] += particleZPos; - - if (!DispatchParticleEffect(slender, sectionName, slenderPosition, slenderAngles, slenderPosition)) - { - int particleEnt = CreateEntityByName("info_particle_system"); - - if (IsValidEntity(particleEnt)) - { - TeleportEntity(particleEnt, slenderPosition, slenderAngles, NULL_VECTOR); - - DispatchKeyValue(particleEnt, "targetname", "tf2particle"); - DispatchKeyValue(particleEnt, "effect_name", sectionName); - DispatchSpawn(particleEnt); - ActivateEntity(particleEnt); - AcceptEntityInput(particleEnt, "start"); - CreateTimer(0.1, Timer_KillEntity, particleEnt, TIMER_FLAG_NO_MAPCHANGE); - } - } -} - -void SlenderCreateParticleAttach(int bossIndex, const char[] sectionName, float particleZPos, SF2_BasePlayer client) -{ - if (bossIndex == -1) - { - return; - } - - if (g_RestartSessionEnabled) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - float playerPosition[3], playerAngles[3]; - if (client.IsValid) - { - client.GetAbsOrigin(playerPosition); - client.GetEyeAngles(playerAngles); - } - else - { - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", playerPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", playerAngles); - } - playerPosition[2] += particleZPos; - - if (!DispatchParticleEffect(slender, sectionName, playerPosition, playerAngles, playerPosition)) - { - int particleEnt = CreateEntityByName("info_particle_system"); - - if (IsValidEntity(particleEnt)) - { - TeleportEntity(particleEnt, playerPosition, playerAngles, NULL_VECTOR); - - DispatchKeyValue(particleEnt, "targetname", "tf2particle"); - DispatchKeyValue(particleEnt, "effect_name", sectionName); - DispatchSpawn(particleEnt); - ActivateEntity(particleEnt); - AcceptEntityInput(particleEnt, "start"); - CreateTimer(0.1, Timer_KillEntity, particleEnt, TIMER_FLAG_NO_MAPCHANGE); - } - } -} - -void SlenderCreateParticleBeamClient(int bossIndex, const char[] sectionName, float particleZPos, SF2_BasePlayer client) -{ - if (bossIndex == -1) - { - return; - } - - if (g_RestartSessionEnabled) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - float slenderPosition[3], slenderAngles[3], playerPosition[3]; - if (client.IsValid) - { - client.GetAbsOrigin(playerPosition); - } - else - { - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", playerPosition); - } - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", slenderAngles); - slenderPosition[2] += particleZPos; - playerPosition[2] += particleZPos; - - DispatchParticleEffectBeam(slender, sectionName, slenderPosition, slenderAngles, playerPosition); -} - -void SlenderCreateParticleSpawnEffect(int bossIndex, bool despawn = false) -{ - if (bossIndex == -1) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char particle[PLATFORM_MAX_PATH], soundEffect[PLATFORM_MAX_PATH]; - float volume; - int pitch; - if (!despawn) - { - GetBossProfileSpawnParticleName(profile, particle, sizeof(particle)); - GetBossProfileSpawnParticleSound(profile, soundEffect, sizeof(soundEffect)); - volume = GetBossProfileSpawnParticleSoundVolume(profile); - pitch = GetBossProfileSpawnParticleSoundPitch(profile); - } - else - { - GetBossProfileDespawnParticleName(profile, particle, sizeof(particle)); - GetBossProfileDespawnParticleSound(profile, soundEffect, sizeof(soundEffect)); - volume = GetBossProfileDespawnParticleSoundVolume(profile); - pitch = GetBossProfileDespawnParticleSoundPitch(profile); - } - - float slenderPosition[3], slenderAngles[3], offsetPos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", slenderAngles); - GetBossProfileSpawnParticleOrigin(profile, offsetPos); - AddVectors(offsetPos, slenderPosition, slenderPosition); - - if (!DispatchParticleEffect(slender, particle, slenderPosition, slenderAngles, slenderPosition)) - { - int particleEnt = CreateEntityByName("info_particle_system"); - - if (IsValidEntity(particleEnt)) - { - TeleportEntity(particleEnt, slenderPosition, slenderAngles, NULL_VECTOR); - - DispatchKeyValue(particleEnt, "targetname", "tf2particle"); - DispatchKeyValue(particleEnt, "effect_name", particle); - DispatchSpawn(particleEnt); - ActivateEntity(particleEnt); - AcceptEntityInput(particleEnt, "start"); - - CreateTimer(0.1, Timer_KillEntity, particleEnt, TIMER_FLAG_NO_MAPCHANGE); - } - } - - if (soundEffect[0] != '\0') - { - EmitSoundToAll(soundEffect, slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, volume, pitch); - } -} - // As time passes on, we have to get more aggressive in order to successfully peak the target's // stress level in the allotted duration we're given. Otherwise we'll be forced to place him // in a rest period. // Teleport progressively closer as time passes in attempt to increase the target's stress level. -// Maximum minimum range is capped by the boss's anger level. -static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) +static Action Timer_SlenderTeleportThink(Handle timer, any id) { - if (bossIndex == -1) + if (!g_Enabled) { return Plugin_Stop; } + + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC.FromUniqueId(id); + if (!controller.IsValid()) + { + return Plugin_Stop; + } + + int bossIndex = controller.Index; if (timer != g_SlenderThink[bossIndex]) { return Plugin_Stop; @@ -4228,51 +3748,63 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) return Plugin_Continue; } - if (NPCGetFlags(bossIndex) & SFF_NOTELEPORT) + if (controller.Flags & SFF_NOTELEPORT) { return Plugin_Continue; } + float gameTime = GetGameTime(); + int difficulty = GetLocalGlobalDifficulty(bossIndex); - if (!NPCIsTeleportAllowed(bossIndex, difficulty)) + // Check to see if anyone's looking at me before doing anything. + if (PeopleCanSeeSlender(bossIndex, _, false)) { return Plugin_Continue; } + int bossEnt = controller.EntIndex; + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) + { + SF2_BaseBoss boss = SF2_BaseBoss(bossEnt); + if (boss.IsKillingSomeone) + { + return Plugin_Continue; + } + } - // Check to see if anyone's looking at me before doing anything. - if (PeopleCanSeeSlender(bossIndex, _, false)) + if (!NPCIsTeleportAllowed(bossIndex, difficulty) && (!bossEnt || bossEnt == INVALID_ENT_REFERENCE)) { return Plugin_Continue; } - if (NPCGetTeleportType(bossIndex) == 2) + + if (controller.TeleportType == 2) { - int bossEnt = NPCGetEntIndex(bossIndex); if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) { if (NPCGetType(bossIndex) == SF2BossType_Chaser) { + SF2_ChaserEntity chaser = SF2_ChaserEntity(bossEnt); // Check to see if it's a good time to teleport away. - int state = g_SlenderState[bossIndex]; - if (state == STATE_IDLE || state == STATE_WANDER) + int state = chaser.State; + if (state == STATE_IDLE && chaser.IsAllowedToDespawn) { - if (GetGameTime() < g_SlenderTimeUntilKill[bossIndex]) + if (gameTime < g_SlenderTimeUntilKill[bossIndex]) { return Plugin_Continue; } } else { - if (GetGameTime() >= g_SlenderTimeUntilKill[bossIndex]) + if (gameTime >= g_SlenderTimeUntilKill[bossIndex]) { - g_SlenderTimeUntilKill[bossIndex] = GetGameTime() + NPCGetIdleLifetime(bossIndex, difficulty); + g_SlenderTimeUntilKill[bossIndex] = gameTime + NPCGetIdleLifetime(bossIndex, difficulty); } return Plugin_Continue; } } else if (NPCGetType(bossIndex) == SF2BossType_Statue) { - if (g_SlenderStatueIdleLifeTime[bossIndex] > GetGameTime()) + if (g_SlenderStatueIdleLifeTime[bossIndex] > gameTime) { return Plugin_Continue; } @@ -4285,28 +3817,47 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) if (IsRoundPlaying()) { - if (GetGameTime() >= g_SlenderNextTeleportTime[bossIndex]) + if (gameTime >= g_SlenderNextTeleportTime[bossIndex]) { + if (!NPCIsTeleportAllowed(bossIndex, difficulty) && bossEnt && bossEnt != INVALID_ENT_REFERENCE) + { + controller.UnSpawn(); + return Plugin_Continue; + } float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); - g_SlenderNextTeleportTime[bossIndex] = GetGameTime() + teleportTime; + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; bool ignoreFuncNavPrefer = g_NpcHasIgnoreNavPrefer[bossIndex]; int teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[bossIndex]); - if (!teleportTarget || teleportTarget == INVALID_ENT_REFERENCE) + if (!IsTargetValidForSlenderEx(CBaseEntity(teleportTarget), bossIndex)) { // We don't have any good targets. Remove myself for now. if (SlenderCanRemove(bossIndex)) { - RemoveSlender(bossIndex); + controller.UnSpawn(); } + g_SlenderTeleportTarget[bossIndex] = INVALID_ENT_REFERENCE; + #if defined DEBUG SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Teleport for boss %d: no good target, removing...", bossIndex); #endif } else { + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) + { + controller.UnSpawn(); + return Plugin_Continue; + } + + // Let's start the persistency timer here, so that way we won't have infinite looping impossible to spawn bosses + float targetDuration = controller.GetTeleportPersistencyPeriod(difficulty); + float deviation = GetRandomFloat(0.92, 1.08); + targetDuration = Pow(deviation * targetDuration, ((g_RoundDifficultyModifier) / 2.0)) + ((deviation * targetDuration) - 1.0); + g_SlenderTeleportMaxTargetTime[controller.Index] = gameTime + targetDuration; + float teleportMinRange = g_SlenderTeleportMinRange[bossIndex][difficulty]; bool shouldBeBehindObstruction = false; if (NPCGetTeleportType(bossIndex) == 2) @@ -4334,6 +3885,12 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) { continue; } + + if (view_as(collector.Get(i)).HasAttributeTF(NO_SPAWNING)) + { + continue; + } + if (collector.Get(i).GetCostSoFar() >= teleportMinRange) { areaArray.Set(validAreaCount, i); @@ -4353,7 +3910,42 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) randomArea = areaArray.Get(randomCell); area = collector.Get(randomArea); area.GetCenter(spawnPos); - spawnPos[2] += 15.0; + /*float cornerPosition[3]; // Will revisit later + ArrayList corners = new ArrayList(); + for (int i = 0; i < 4; i++) + { + corners.Push(view_as(i)); + } + NavCornerType cornerA = view_as(GetRandomInt(0, 3)); + NavCornerType invert = cornerA; + switch (cornerA) + { + case NORTH_WEST: + { + invert = SOUTH_EAST; + } + case NORTH_EAST: + { + invert = SOUTH_WEST; + } + case SOUTH_EAST: + { + invert = NORTH_WEST; + } + case SOUTH_WEST: + { + invert = NORTH_EAST; + } + } + corners.Erase(corners.FindValue(cornerA)); + corners.Erase(corners.FindValue(invert)); + NavCornerType cornerB = corners.Get(GetRandomInt(0, corners.Length - 1)); + area.GetCorner(cornerA, spawnPos); + area.GetCorner(cornerB, cornerPosition); + LerpVectors(spawnPos, cornerPosition, spawnPos, GetRandomFloat(0.3, 0.7)); + area.GetCorner(invert, cornerPosition); + LerpVectors(spawnPos, cornerPosition, spawnPos, GetRandomFloat(0.3, 0.7)); + delete corners;*/ float traceMins[3]; traceMins[0] = g_SlenderDetectMins[bossIndex][0]; @@ -4363,7 +3955,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) float traceMaxs[3]; traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0]; traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1]; - traceMaxs[2] = 0.0; + traceMaxs[2] = g_SlenderDetectMaxs[bossIndex][2]; TR_TraceHullFilter(spawnPos, spawnPos, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitEntity); if (TR_DidHit()) @@ -4374,6 +3966,10 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) if (findValue != -1) { areaArray.Erase(findValue); + + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: boss index %d collides with something", findValue, bossIndex); + #endif } } else @@ -4400,13 +3996,17 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) if (findValue != -1 && findValue < validAreaCount) { areaArray.Erase(findValue); + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: too close to %N", findValue, i); + #endif } canSpawn = false; + break; } } // Check visibility. - if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false)) + if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false, _, true)) { area = NULL_AREA; validAreaCount--; @@ -4414,13 +4014,16 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) if (findValue != -1) { areaArray.Erase(findValue); + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: is visible to someone", findValue); + #endif } canSpawn = false; } AddVectors(spawnPos, g_SlenderEyePosOffset[bossIndex], spawnPos); - if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false)) + if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false, _, true)) { area = NULL_AREA; validAreaCount--; @@ -4435,27 +4038,32 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) SubtractVectors(spawnPos, g_SlenderEyePosOffset[bossIndex], spawnPos); // Look for copies - if (NPCGetFlags(bossIndex) & SFF_COPIES && canSpawn) + if (controller.GetProfileData().CopiesInfo.Enabled[difficulty] && canSpawn) { - float minDistBetweenBosses = GetBossProfileTeleportCopyDistance(profile); + float minDistBetweenBosses = GetBossProfileTeleportCopyDistance(profile, difficulty); for (int bossCheck = 0; bossCheck < MAX_BOSSES; bossCheck++) { + SF2NPC_BaseNPC check = SF2NPC_BaseNPC(bossCheck); if (bossCheck == bossIndex || - NPCGetUniqueID(bossCheck) == -1 || - (g_SlenderCopyMaster[bossIndex] != bossCheck && g_SlenderCopyMaster[bossIndex] != g_SlenderCopyMaster[bossCheck])) + !check.IsValid()) + { + continue; + } + + if (check.CopyMaster != controller && controller.CopyMaster != check && controller.CopyMaster != check.CopyMaster) { continue; } - int bossEntCheck = NPCGetEntIndex(bossCheck); + int bossEntCheck = check.EntIndex; if (!bossEntCheck || bossEntCheck == INVALID_ENT_REFERENCE) { continue; } float tempPos[3]; - SlenderGetAbsOrigin(bossCheck, tempPos); + CBaseEntity(bossEntCheck).GetAbsOrigin(tempPos); if (GetVectorSquareMagnitude(spawnPos, tempPos) <= SquareFloat(minDistBetweenBosses)) { @@ -4465,6 +4073,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) if (findValue != -1) { areaArray.Erase(findValue); + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: boss index %d is too close to copy %d", findValue, bossIndex, bossCheck); + #endif } canSpawn = false; break; @@ -4474,7 +4085,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) if (canSpawn) { - SpawnSlender(bossIndex, spawnPos); + controller.Spawn(spawnPos); if (g_PlayerIsExitCamping[teleportTarget]) { @@ -4491,7 +4102,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) { continue; } @@ -4516,12 +4127,18 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) } break; } + else + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Teleport for boss %d: can't spawn due to no available areas", bossIndex); + #endif + } } } if (validAreaCount <= 0) { - RemoveSlender(bossIndex); + controller.UnSpawn(); } delete collector; @@ -4529,11 +4146,100 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) } } } - else + else + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Teleport for boss %d: failed because of teleport time (curtime: %f, teletime: %f)", bossIndex, GetGameTime(), g_SlenderNextTeleportTime[bossIndex]); + #endif + } + } + else + { + float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; + } + + return Plugin_Continue; +} + +static Action Timer_SlenderRespawnThink(Handle timer, any id) +{ + if (g_Enabled) + { + return Plugin_Stop; + } + + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC.FromUniqueId(id); + if (!controller.IsValid()) + { + return Plugin_Stop; + } + + int bossIndex = controller.Index; + if (timer != g_SlenderThink[bossIndex]) + { + return Plugin_Stop; + } + + float gameTime = GetGameTime(); + + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + bool cont = false; + + int bossEnt = controller.EntIndex; + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE && controller.TeleportType == 2) + { + cont = true; + } + + if (!NPCIsTeleportAllowed(bossIndex, difficulty) && (!bossEnt || bossEnt == INVALID_ENT_REFERENCE)) + { + cont = true; + } + + if (cont) + { + float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; + return Plugin_Continue; + } + + if (gameTime >= g_SlenderNextTeleportTime[bossIndex]) + { + float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; + + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) // For teleport type 0 + { + controller.UnSpawn(); + return Plugin_Continue; + } + + ArrayList spawnPoint = new ArrayList(); + float teleportPos[3]; + int ent = -1, spawnTeam = 0; + while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) + { + spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); + if (spawnTeam == NPCGetDefaultTeam(bossIndex)) + { + spawnPoint.Push(ent); + } + } + + ent = -1; + if (spawnPoint.Length > 0) + { + ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); + } + + delete spawnPoint; + + if (IsValidEntity(ent)) { - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Teleport for boss %d: failed because of teleport time (curtime: %f, teletime: %f)", bossIndex, GetGameTime(), g_SlenderNextTeleportTime[bossIndex]); - #endif + CBaseEntity(ent).GetAbsOrigin(teleportPos); + controller.Spawn(teleportPos); } } @@ -4543,7 +4249,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) bool SlenderMarkAsFake(int bossIndex) { int bossFlags = NPCGetFlags(bossIndex); - if (bossFlags & SFF_MARKEDASFAKE) + if ((bossFlags & SFF_MARKEDASFAKE) != 0) { return false; } @@ -4554,8 +4260,8 @@ bool SlenderMarkAsFake(int bossIndex) NPCSetFlags(bossIndex, bossFlags | SFF_MARKEDASFAKE); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); g_SlenderFakeTimer[bossIndex] = CreateTimer(3.0, Timer_SlenderMarkedAsFake, bossIndex, TIMER_FLAG_NO_MAPCHANGE); @@ -4574,13 +4280,13 @@ bool SlenderMarkAsFake(int bossIndex) } SetEntProp(slender, Prop_Send, "m_usSolidFlags", flags); - if (g_SlenderEngineSound[bossIndex][0] != '\0') + if (data.EngineSound[0] != '\0') { - StopSound(slender, SNDCHAN_STATIC, g_SlenderEngineSound[bossIndex]); + StopSound(slender, SNDCHAN_STATIC, data.EngineSound); } SetEntPropFloat(slender, Prop_Send, "m_flPlaybackRate", 0.0); - SetEntityRenderFx(slender, RENDERFX_FADE_FAST); + SetEntityRenderFx(slender, RENDERFX_FADE_SLOW); } return true; @@ -4609,7 +4315,7 @@ int SpawnSlenderModel(int bossIndex, const float pos[3], bool deathCam = false) char buffer[PLATFORM_MAX_PATH], profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - float playbackRate, tempFootsteps, cycle; + float playbackRate, cycle; int difficulty = GetLocalGlobalDifficulty(bossIndex); GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); @@ -4641,18 +4347,16 @@ int SpawnSlenderModel(int bossIndex, const float pos[3], bool deathCam = false) slenderModel.Activate(); SF2BossProfileMasterAnimationsData animData; GetBossProfileAnimationsData(profile, animData); - float tempDuration; - int tempIndex; if (!deathCam) { - animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, tempDuration, cycle, g_SlenderFootstepTime[bossIndex], tempIndex); + animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, _, cycle); } else { - bool animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, buffer, sizeof(buffer), playbackRate, tempDuration, cycle, tempFootsteps, tempIndex); + bool animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, buffer, sizeof(buffer), playbackRate, _, cycle); if (!animationFound || strcmp(buffer,"") <= 0) { - animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, tempDuration, cycle, g_SlenderFootstepTime[bossIndex], tempIndex); + animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, _, cycle); } } if (buffer[0] != '\0') @@ -4724,69 +4428,17 @@ int SpawnSlenderModel(int bossIndex, const float pos[3], bool deathCam = false) return slenderModel.index; } -bool PlayerCanSeeSlender(int client,int bossIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) +bool PlayerCanSeeSlender(int client, int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { return IsNPCVisibleToPlayer(bossIndex, client, checkFOV, checkBlink, checkEliminated); } -bool PeopleCanSeeSlender(int bossIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated = true) +bool PeopleCanSeeSlender(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { return IsNPCVisibleToAPlayer(bossIndex, checkFOV, checkBlink, checkEliminated); } -bool SlenderAddGlow(int bossIndex, int color[4] = {255, 255, 255, 255}) -{ - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return false; - } - - char buffer[PLATFORM_MAX_PATH]; - GetEntPropString(slender, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - - if (buffer[0] == '\0') - { - return false; - } - - // This is so much simpler, I don't know why I didn't make this earlier. - int glow = TF2_CreateGlow(slender); - if (IsValidEntity(glow)) - { - SDKHook(glow, SDKHook_SetTransmit, Hook_SlenderGlowSetTransmit); - - g_NpcGlowEntity[bossIndex] = EntIndexToEntRef(glow); - //Set our desired glow color - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - g_DHookShouldTransmit.HookEntity(Hook_Pre, glow, Hook_EntityShouldTransmit); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow, Hook_GlowUpdateTransmitState); - SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); - - return true; - } - - return false; -} - -void SlenderRemoveGlow(int bossIndex) -{ - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - int glow = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - if (glow != INVALID_ENT_REFERENCE) - { - RemoveEntity(glow); - g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; - } -} - -bool IsNPCVisibleToPlayer(int npcIndex,int client, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) +bool IsNPCVisibleToPlayer(int npcIndex, int client, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { if (!NPCIsValid(npcIndex)) { @@ -4804,7 +4456,7 @@ bool IsNPCVisibleToPlayer(int npcIndex,int client, bool checkFOV=true, bool chec return false; } -bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) +bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { for (int client = 1; client <= MaxClients; client++) { @@ -4817,53 +4469,13 @@ bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV=true, bool checkBlink=fal return false; } -bool CanNPCSeePlayerNonTransparent(int npcIndex, SF2_BasePlayer client) -{ - if (!NPCIsValid(npcIndex)) - { - return false; - } - - if (!client.IsValid) - { - return false; - } - - bool attackEliminated = !!(NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS); - - if (!IsTargetValidForSlender(client, attackEliminated)) - { - return false; - } - - int npcEnt = NPCGetEntIndex(npcIndex); - if (npcEnt && npcEnt != INVALID_ENT_REFERENCE) - { - float eyePos[3], clientPos[3]; - NPCGetEyePosition(npcIndex, eyePos); - client.GetEyePosition(clientPos); - Handle trace = TR_TraceRayFilterEx(eyePos, - clientPos, - MASK_NPCSOLID, - RayType_EndPoint, - TraceRayBossVisibility, - npcEnt); - - bool isVisible = !TR_DidHit(trace); - delete trace; - - return isVisible; - } - return false; -} - float NPCGetDistanceFromPoint(int npcIndex, const float point[3]) { int npcEnt = NPCGetEntIndex(npcIndex); if (npcEnt && npcEnt != INVALID_ENT_REFERENCE) { float pos[3]; - SlenderGetAbsOrigin(npcIndex, pos); + CBaseEntity(npcEnt).GetAbsOrigin(pos); return GetVectorSquareMagnitude(pos, point); } @@ -4871,7 +4483,7 @@ float NPCGetDistanceFromPoint(int npcIndex, const float point[3]) return -1.0; } -float NPCGetDistanceFromEntity(int npcIndex,int ent) +float NPCGetDistanceFromEntity(int npcIndex, int ent) { if (!IsValidEntity(ent)) { @@ -4884,138 +4496,90 @@ float NPCGetDistanceFromEntity(int npcIndex,int ent) return NPCGetDistanceFromPoint(npcIndex, pos); } -bool TraceRayBossVisibility(int entity,int mask, any data) +bool TraceRayBossVisibility(int entity, int mask, any data) { - if (entity == data || IsValidClient(entity)) + if (entity == data) { return false; } - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { if (g_PlayerProxy[entity] || IsClientInGhostMode(entity)) { return false; } } - int bossIndex = NPCGetFromEntIndex(entity); - if (entity <= MAX_BOSSES) - { - return false; - } - if (bossIndex != -1) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } -bool TraceRayDontHitCharacters(int entity,int mask, any data) +bool TraceRayDontHitCharacters(int entity, int mask, any data) { - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { return false; } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } -bool TraceRayDontHitAnyEntity(int entity,int mask,any data) +bool TraceRayDontHitAnyEntity(int entity, int mask, any data) { if (entity == data) { return false; } - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { if (g_PlayerProxy[entity] || IsClientInGhostMode(entity)) { return false; } } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } bool TraceRayDontHitAnyEntity_Pathing(int entity, int contentsMask, int desiredcollisiongroup) { - if ((entity > 0 && entity <= MaxClients)) + if (IsValidClient(entity)) { if (g_PlayerProxy[entity] || IsClientInGhostMode(entity)) { return false; } } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } -bool TraceRayDontHitCharactersOrEntity(int entity,int mask, any data) +bool TraceRayDontHitCharactersOrEntity(int entity, int mask, any data) { if (entity == data) { return false; } - if (entity > 0 && entity <= MaxClients) - { - return false; - } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) + if (IsValidClient(entity)) { return false; } - if (IsValidEntity(entity) && CBaseEntity(entity).IsCombatCharacter()) + + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } @@ -5023,7 +4587,7 @@ bool TraceRayDontHitCharactersOrEntity(int entity,int mask, any data) return true; } -bool TraceRayDontHitAnything(int entity,int mask, any data) +bool TraceRayDontHitAnything(int entity, int mask, any data) { return false; } @@ -5211,7 +4775,7 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint } if (IsSpaceOccupiedPlayer(traceHitPos, HULL_TF2PLAYER_MINS, HULL_TF2PLAYER_MAXS, client)) { - traceHitPos[2] +=5.0; + traceHitPos[2] += 5.0; if (IsSpaceOccupiedPlayer(traceHitPos, HULL_TF2PLAYER_MINS, HULL_TF2PLAYER_MAXS, client)) { #if defined DEBUG @@ -5249,7 +4813,7 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint // Check minimum range with players. for (int teleportClient = 1; teleportClient <= MaxClients; teleportClient++) { - if (!IsClientInGame(teleportClient) || + if (!IsValidClient(teleportClient) || !IsPlayerAlive(teleportClient) || g_PlayerEliminated[teleportClient] || IsClientInGhostMode(teleportClient) || @@ -5277,7 +4841,7 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint } // Check travel distance and put in the appropriate arrays. - float dist = view_as(areaArray.Get(i2, 1)); + float dist = areaArray.Get(i2, 1); if (dist > rangeSectionMin && dist < rangeSectionMax) { int index = -1; @@ -5320,25 +4884,25 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint { arrayIndex = GetRandomInt(0, areaArrayClose.Length - 1); teleportAreaIndex = areaArrayClose.Get(arrayIndex); - teleportPos[0] = view_as(areaArrayClose.Get(arrayIndex, 1)); - teleportPos[1] = view_as(areaArrayClose.Get(arrayIndex, 2)); - teleportPos[2] = view_as(areaArrayClose.Get(arrayIndex, 3)); + teleportPos[0] = areaArrayClose.Get(arrayIndex, 1); + teleportPos[1] = areaArrayClose.Get(arrayIndex, 2); + teleportPos[2] = areaArrayClose.Get(arrayIndex, 3); } else if (areaArrayAverage.Length) { arrayIndex = GetRandomInt(0, areaArrayAverage.Length - 1); teleportAreaIndex = areaArrayAverage.Get(arrayIndex); - teleportPos[0] = view_as(areaArrayAverage.Get(arrayIndex, 1)); - teleportPos[1] = view_as(areaArrayAverage.Get(arrayIndex, 2)); - teleportPos[2] = view_as(areaArrayAverage.Get(arrayIndex, 3)); + teleportPos[0] = areaArrayAverage.Get(arrayIndex, 1); + teleportPos[1] = areaArrayAverage.Get(arrayIndex, 2); + teleportPos[2] = areaArrayAverage.Get(arrayIndex, 3); } else if (areaArrayFar.Length) { arrayIndex = GetRandomInt(0, areaArrayFar.Length - 1); teleportAreaIndex = areaArrayFar.Get(arrayIndex); - teleportPos[0] = view_as(areaArrayFar.Get(arrayIndex, 1)); - teleportPos[1] = view_as(areaArrayFar.Get(arrayIndex, 2)); - teleportPos[2] = view_as(areaArrayFar.Get(arrayIndex, 3)); + teleportPos[0] = areaArrayFar.Get(arrayIndex, 1); + teleportPos[1] = areaArrayFar.Get(arrayIndex, 2); + teleportPos[2] = areaArrayFar.Get(arrayIndex, 3); } delete areaArrayClose; delete areaArrayAverage; @@ -5356,5 +4920,308 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint return true; } -#include "sf2/npc/npc_chaser.sp" -#include "sf2/npc/npc_chaser_takedamage.sp" \ No newline at end of file +#include "npc/glow.sp" +#include "npc/npc_chaser.sp" +#include "npc/entities/initialize.sp" + +static any Native_GetMaxBosses(Handle plugin, int numParams) +{ + return MAX_BOSSES; +} + +static any Native_EntIndexToBossIndex(Handle plugin, int numParams) +{ + return NPCGetFromEntIndex(GetNativeCell(1)); +} + +static any Native_BossIndexToEntIndex(Handle plugin, int numParams) +{ + return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); +} + +static any Native_BossIndexToEntIndexEx(Handle plugin, int numParams) +{ + return NPCGetEntIndex(GetNativeCell(1)); +} + +static any Native_BossIDToBossIndex(Handle plugin, int numParams) +{ + return NPCGetFromUniqueID(GetNativeCell(1)); +} + +static any Native_BossIndexToBossID(Handle plugin, int numParams) +{ + return NPCGetUniqueID(GetNativeCell(1)); +} + +static any Native_AddBoss(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetNativeString(1, profile, sizeof(profile)); + + int flags = GetNativeCell(2); + bool spawnCompanions = GetNativeCell(3); + bool playSpawnSound = GetNativeCell(4); + + return AddProfile(profile, flags, _, spawnCompanions, playSpawnSound); +} + +static any Native_RemoveBoss(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + RemoveProfile(boss.Index); + return 0; +} + +static any Native_GetBossName(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(GetNativeCell(1), profile, sizeof(profile)); + + SetNativeString(2, profile, GetNativeCell(3)); + return 0; +} + +static any Native_GetBossType(Handle plugin, int numParams) +{ + return NPCGetType(GetNativeCell(1)); +} + +static any Native_GetBossFlags(Handle plugin, int numParams) +{ + return NPCGetFlags(GetNativeCell(1)); +} + +static any Native_SetBossFlags(Handle plugin, int numParams) +{ + NPCSetFlags(GetNativeCell(1), GetNativeCell(2)); + return 0; +} + +static any Native_SpawnBoss(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + float position[3]; + GetNativeArray(2, position, 3); + + SpawnSlender(boss, position); + return 0; +} + +static any Native_IsBossSpawning(Handle plugin, int numParams) +{ + return false; +} + +static any Native_DespawnBoss(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + boss.UnSpawn(); + return 0; +} + +static any Native_GetBossPathFollower(Handle plugin, int numParams) +{ + return g_BossPathFollower[GetNativeCell(1)]; +} + +static any Native_GetBossMaster(Handle plugin, int numParams) +{ + return g_SlenderCopyMaster[GetNativeCell(1)]; +} + +static any Native_GetBossIdleLifetime(Handle plugin, int numParams) +{ + return NPCGetIdleLifetime(GetNativeCell(1), GetNativeCell(2)); +} + +static any Native_GetBossState(Handle plugin, int numParams) +{ + int entity = NPCGetEntIndex(GetNativeCell(1)); + if (!IsValidEntity(entity)) + { + return 0; + } + + SF2_BaseBoss boss = SF2_BaseBoss(entity); + return boss.State; +} + +static any Native_SetBossState(Handle plugin, int numParams) +{ + int entity = NPCGetEntIndex(GetNativeCell(1)); + if (!IsValidEntity(entity)) + { + return 0; + } + + SF2_BaseBoss boss = SF2_BaseBoss(entity); + boss.State = GetNativeCell(2); + return 0; +} + +static any Native_GetBossEyePosition(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid() || !IsValidEntity(boss.EntIndex)) + { + return 0; + } + + float eyePos[3]; + boss.GetEyePosition(eyePos); + + SetNativeArray(2, eyePos, 3); + return 0; +} + +static any Native_GetBossEyePositionOffset(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + float eyePos[3]; + boss.GetEyePositionOffset(eyePos); + + SetNativeArray(2, eyePos, 3); + return 0; +} + +static any Native_GetBossTeleportThinkTimer(Handle plugin, int numParams) +{ + return g_SlenderThink[GetNativeCell(1)]; +} + +static any Native_SetBossTeleportThinkTimer(Handle plugin, int numParams) +{ + int bossIndex = GetNativeCell(1); + g_SlenderThink[bossIndex] = GetNativeCell(2); + return 0; +} + +static any Native_GetBossTeleportTarget(Handle plugin, int numParams) +{ + return EntRefToEntIndex(g_SlenderTeleportTarget[GetNativeCell(1)]); +} + +static any Native_GetBossGoalPosition(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + if (!controller.Path.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid path for boss index %d", controller.Index); + } + + float ret[3]; + controller.Path.GetEndPosition(ret); + SetNativeArray(2, ret, 3); + return 0; +} + +static any Native_Nothing(Handle plugin, int numParams) +{ + return 0; +} + +static any Native_GetBossCurrentChaseDuration(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + int entity = controller.EntIndex; + if (!IsValidEntity(entity)) + { + return 0; + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.CurrentChaseDuration; +} + +static any Native_SetBossCurrentChaseDuration(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + int entity = controller.EntIndex; + if (!IsValidEntity(entity)) + { + return 0; + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + bossEntity.CurrentChaseDuration = GetNativeCell(2); + return 0; +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2BossProfileData data; + data = controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_GetProfileDataEx(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetNativeString(1, profile, sizeof(profile)); + SF2BossProfileData data; + if (!g_BossProfileData.GetArray(profile, data, sizeof(data))) + { + return false; + } + + SetNativeArray(2, data, sizeof(data)); + return true; +} + +static any Native_SpawnBossEffects(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(3, pos, 3); + GetNativeArray(4, ang, 3); + ArrayList output = GetNativeCellRef(5); + SlenderSpawnEffects(GetNativeCell(1), GetNativeCell(2), false, pos, ang, output, GetNativeCell(6)); + return 0; +} + +static any Native_CanBossBeSeen(Handle plugin, int numParams) +{ + return PeopleCanSeeSlender(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/deathcam.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/deathcam.sp new file mode 100644 index 00000000..8cbc11ee --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/deathcam.sp @@ -0,0 +1,71 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_DeathCamAction < NextBotAction +{ + public SF2_DeathCamAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("SF2_Deathcam"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnOtherKilled, OnOtherKilled); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_DeathCamAction action, SF2_BaseBoss actor) +{ + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + npc.flWalkSpeed = 0.0; + npc.flRunSpeed = 0.0; + actor.IsKillingSomeone = true; + float duration = 0.0, rate = 1.0, cycle = 0.0; + SF2NPC_BaseNPC controller = actor.Controller; + SF2BossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileSoundInfo info; + info = data.LocalDeathCamSounds; + info.EmitSound(_, actor.index); + + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + + return NULL_ACTION; +} + +static int Update(SF2_DeathCamAction action, SF2_BaseBoss actor, NextBotAction priorAction) +{ + if (!actor.KillTarget.IsValid()) + { + return action.Done(); + } + + SF2_BasePlayer player = SF2_BasePlayer(actor.KillTarget.index); + if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) + { + return action.Done(); + } + + return action.Continue(); +} + +static void OnEnd(SF2_DeathCamAction action, SF2_BaseBoss actor) +{ + actor.IsKillingSomeone = false; +} + +static void OnOtherKilled(SF2_DeathCamAction action, SF2_BaseBoss actor, CBaseCombatCharacter victim, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damagetype) +{ + if (actor.KillTarget.index == victim.index) + { + actor.KillTarget = CBaseEntity(-1); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp new file mode 100644 index 00000000..f4b96d1c --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp @@ -0,0 +1,165 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_PlaySequenceAndWait < NextBotAction +{ + public SF2_PlaySequenceAndWait(int sequence, float duration = 0.0, float rate = 1.0, float cycle = 0.0) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("SF2_PlaySequenceAndWait"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_Sequence") + .DefineFloatField("m_Duration") + .DefineFloatField("m_Rate") + .DefineFloatField("m_Cycle") + .DefineFloatField("m_EndTime") + .EndDataMapDesc(); + } + + SF2_PlaySequenceAndWait action = view_as(g_Factory.Create()); + + action.Sequence = sequence; + action.Duration = duration; + action.Rate = rate; + action.Cycle = cycle; + + return action; + } + + public static void SetupAPI() + { + CreateNative("SF2_PlaySequenceAndWaitAction.SF2_PlaySequenceAndWaitAction", Native_Create); + } + + property int Sequence + { + public get() + { + return this.GetData("m_Sequence"); + } + + public set(int value) + { + this.SetData("m_Sequence", value); + } + } + + property float Duration + { + public get() + { + return this.GetDataFloat("m_Duration"); + } + + public set(float value) + { + this.SetDataFloat("m_Duration", value); + } + } + + property float Rate + { + public get() + { + return this.GetDataFloat("m_Rate"); + } + + public set(float value) + { + this.SetDataFloat("m_Rate", value); + } + } + + property float Cycle + { + public get() + { + return this.GetDataFloat("m_Cycle"); + } + + public set(float value) + { + this.SetDataFloat("m_Cycle", value); + } + } + + property float EndTime + { + public get() + { + return this.GetDataFloat("m_EndTime"); + } + + public set(float value) + { + this.SetDataFloat("m_EndTime", value); + } + } +} + +static int OnStart(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAction priorAction) +{ + if (action.Sequence == -1) + { + return action.Done("Invalid sequence"); + } + + actor.ResetSequence(action.Sequence); + actor.SetPropFloat(Prop_Send, "m_flCycle", action.Cycle); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", action.Rate); + + float duration = action.Duration; + if (duration <= 0.0) + { + duration = actor.SequenceDuration(action.Sequence) / action.Rate; + duration *= (1.0 - action.Cycle); + } + + if (SF2_ChaserEntity(actor.index).IsValid()) + { + SF2_ChaserEntity(actor.index).GroundSpeedOverride = true; + } + + action.EndTime = GetGameTime() + duration; + + return action.Continue(); +} + +static int Update(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, float interval) +{ + if (GetGameTime() > action.EndTime) + { + return action.Done(); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAction interruptingAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_PlaySequenceAndWait action, SF2_ChaserEntity actor) +{ + if (SF2_ChaserEntity(actor.index).IsValid()) + { + SF2_ChaserEntity(actor.index).GroundSpeedOverride = false; + } +} + +static any Native_Create(Handle plugin, int numParams) +{ + int sequence = GetNativeCell(1); + float duration = GetNativeCell(2); + float rate = GetNativeCell(3); + float cycle = GetNativeCell(4); + + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp new file mode 100644 index 00000000..2d4dc6df --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -0,0 +1,1057 @@ +#pragma semicolon 1 + +#include "actions/playsequenceandwait.sp" +#include "actions/deathcam.sp" + +static CEntityFactory g_Factory; + +enum SF2NPCMoveParameterType +{ + SF2NPCMoveParameter_None = 0, + SF2NPCMoveParameter_XY, + SF2NPCMoveParameter_Yaw +} + +methodmap SF2_BaseBoss < CBaseCombatCharacter +{ + public SF2_BaseBoss(int ent) + { + return view_as(ent); + } + + public bool IsValid() + { + if (!CBaseCombatCharacter(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_npc_boss_base", OnCreate, OnRemove); + g_Factory.IsAbstract = true; + g_Factory.DeriveFromNPC(); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_Controller") + .DefineIntField("m_State") + .DefineIntField("m_PreviousState") + .DefineEntityField("m_Target") + .DefineEntityField("m_OldTarget") + .DefineIntField("m_InterruptConditions") + .DefineBoolField("m_IsJumping") + .DefineBoolField("m_IsEntityVisible", 2049) + .DefineBoolField("m_IsEntityInFOV", 2049) + .DefineBoolField("m_IsEntityNear", 2049) + .DefineFloatField("m_CurrentChaseDuration") + .DefineFloatField("m_InitialChaseDuration") + .DefineFloatField("m_LegacyFootstepInterval") + .DefineFloatField("m_LegacyFootstepTime") + .DefineIntField("m_MovementType") + .DefineIntField("m_MoveParameterType") + .DefineIntField("m_MoveXPoseParameter") + .DefineIntField("m_MoveYPoseParameter") + .DefineIntField("m_MoveScalePoseParameter") + .DefineIntField("m_MoveYawPoseParameter") + .DefineFloatField("m_AnimationPlaybackRate") + .DefineFloatField("m_EstimatedYaw") + .DefineFloatField("m_LastKillTime") + .DefineBoolField("m_DebugShouldGoToPos") + .DefineVectorField("m_ForceWanderPos") + .DefineBoolField("m_IsKillingSomeone") + .DefineEntityField("m_KillTarget") + .DefineBoolField("m_IsAttemptingToMove") + .DefineIntField("m_EyeBoneIndex") + .DefineBoolField("m_VelocityCancel") + .DefineIntField("m_Teleporters") + .EndDataMapDesc(); + g_Factory.Install(); + + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + } + + public static CEntityFactory GetFactory() + { + return g_Factory; + } + + property SF2NPC_BaseNPC Controller + { + public get() + { + return SF2NPC_BaseNPC.FromUniqueId(this.GetProp(Prop_Data, "m_Controller")); + } + + public set(SF2NPC_BaseNPC controller) + { + this.SetProp(Prop_Data, "m_Controller", controller.Index == -1 ? -1 : controller.UniqueID); + } + } + + property int State + { + public get() + { + return this.GetProp(Prop_Data, "m_State"); + } + + public set(int value) + { + if (this.Controller.IsValid()) + { + Call_StartForward(g_OnBossChangeStateFwd); + Call_PushCell(this.Controller.Index); + Call_PushCell(this.GetProp(Prop_Data, "m_State")); + Call_PushCell(value); + Call_Finish(); + } + + this.SetProp(Prop_Data, "m_State", value); + } + } + + property int PreviousState + { + public get() + { + return this.GetProp(Prop_Data, "m_PreviousState"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_PreviousState", value); + } + } + + public SF2BossProfileData GetProfileData() + { + return this.Controller.GetProfileData(); + } + + property CBaseEntity Target + { + public get() + { + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_Target"))); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_Target", EnsureEntRef(entity.index)); + } + } + + property CBaseEntity OldTarget + { + public get() + { + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_OldTarget"))); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_OldTarget", EnsureEntRef(entity.index)); + } + } + + property int InterruptConditions + { + public get() + { + return this.GetProp(Prop_Data, "m_InterruptConditions"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_InterruptConditions", value); + } + } + + property bool IsJumping + { + public get() + { + return this.GetProp(Prop_Data, "m_IsJumping") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsJumping", value); + } + } + + public bool GetIsVisible(CBaseEntity entity) + { + return this.GetProp(Prop_Data, "m_IsEntityVisible", _, entity.index) != 0; + } + + public void SetIsVisible(CBaseEntity entity, bool value) + { + this.SetProp(Prop_Data, "m_IsEntityVisible", value, _, entity.index); + } + + public bool GetInFOV(CBaseEntity entity) + { + return this.GetProp(Prop_Data, "m_IsEntityInFOV", _, entity.index) != 0; + } + + public void SetInFOV(CBaseEntity entity, bool value) + { + this.SetProp(Prop_Data, "m_IsEntityInFOV", value, _, entity.index); + } + + public bool GetIsNear(CBaseEntity entity) + { + return this.GetProp(Prop_Data, "m_IsEntityNear", _, entity.index) != 0; + } + + public void SetIsNear(CBaseEntity entity, bool value) + { + this.SetProp(Prop_Data, "m_IsEntityNear", value, _, entity.index); + } + + property float CurrentChaseDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_CurrentChaseDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_CurrentChaseDuration", value); + } + } + + property float InitialChaseDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_InitialChaseDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_InitialChaseDuration", value); + } + } + + property float LegacyFootstepInterval + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LegacyFootstepInterval"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LegacyFootstepInterval", value); + } + } + + property float LegacyFootstepTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LegacyFootstepTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LegacyFootstepTime", value); + } + } + + property SF2NPCMoveTypes MovementType + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_MovementType")); + } + + public set(SF2NPCMoveTypes value) + { + this.SetProp(Prop_Data, "m_MovementType", value); + } + } + + property SF2NPCMoveParameterType MoveParameterType + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_MoveParameterType")); + } + + public set(SF2NPCMoveParameterType value) + { + this.SetProp(Prop_Data, "m_MoveParameterType", value); + } + } + + property int MoveXParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveXPoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveXPoseParameter", value); + } + } + + property int MoveYParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveYPoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveYPoseParameter", value); + } + } + + property int MoveScaleParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveScalePoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveScalePoseParameter", value); + } + } + + property int MoveYawParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveYawPoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveYawPoseParameter", value); + } + } + + property float AnimationPlaybackRate + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AnimationPlaybackRate"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AnimationPlaybackRate", value); + } + } + + property float EstimatedYaw + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_EstimatedYaw"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_EstimatedYaw", value); + } + } + + property float LastKillTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LastKillTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LastKillTime", value); + } + } + + property bool DebugShouldGoToPos + { + public get() + { + return this.GetProp(Prop_Data, "m_DebugShouldGoToPos") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_DebugShouldGoToPos", value); + } + } + + public void GetForceWanderPosition(float buffer[3]) + { + this.GetPropVector(Prop_Data, "m_ForceWanderPos", buffer); + } + + public void SetForceWanderPosition(float buffer[3]) + { + this.SetPropVector(Prop_Data, "m_ForceWanderPos", buffer); + } + + property bool IsKillingSomeone + { + public get() + { + return this.GetProp(Prop_Data, "m_IsKillingSomeone") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsKillingSomeone", value); + } + } + + property CBaseEntity KillTarget + { + public get() + { + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_KillTarget"))); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_KillTarget", EnsureEntRef(entity.index)); + } + } + + property bool IsAttemptingToMove + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAttemptingToMove") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsAttemptingToMove", value); + } + } + + property int EyeBoneIndex + { + public get() + { + return this.GetProp(Prop_Data, "m_EyeBoneIndex"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_EyeBoneIndex", value); + } + } + + property bool VelocityCancel + { + public get() + { + return this.GetProp(Prop_Data, "m_VelocityCancel") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_VelocityCancel", value); + } + } + + property ArrayList Teleporters + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_Teleporters")); + } + + public set(ArrayList value) + { + this.SetProp(Prop_Data, "m_Teleporters", value); + } + } + + public void EyePosition(float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) + { + this.Controller.GetEyePosition(buffer, defaultValue); + } + + property int Team + { + public get() + { + return this.GetProp(Prop_Data, "m_iTeamNum"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_iTeamNum", value); + } + } + + public static void SetupAPI() + { + CreateNative("SF2_BaseBossEntity.IsValid.get", Native_GetIsValid); + CreateNative("SF2_BaseBossEntity.Controller.get", Native_GetController); + CreateNative("SF2_BaseBossEntity.Target.get", Native_GetTarget); + CreateNative("SF2_BaseBossEntity.State.get", Native_GetState); + CreateNative("SF2_BaseBossEntity.CurrentChaseDuration.get", Native_GetCurrentChaseDuration); + CreateNative("SF2_BaseBossEntity.CurrentChaseDuration.set", Native_SetCurrentChaseDuration); + CreateNative("SF2_BaseBossEntity.InitialChaseDuration.get", Native_GetInitialChaseDuration); + CreateNative("SF2_BaseBossEntity.InitialChaseDuration.set", Native_SetInitialChaseDuration); + CreateNative("SF2_BaseBossEntity.IsKillingSomeone.get", Native_GetIsKillingSomeone); + CreateNative("SF2_BaseBossEntity.EyePosition", Native_EyePosition); + CreateNative("SF2_BaseBossEntity.GetProfileName", Native_GetProfileName); + CreateNative("SF2_BaseBossEntity.GetName", Native_GetName); + CreateNative("SF2_BaseBossEntity.ProfileData", Native_GetProfileData); + CreateNative("SF2_BaseBossEntity.ResetProfileAnimation", Native_ResetProfileAnimation); + } + + public int SelectProfileAnimation(const char[] animType, float &rate = 1.0, float &duration = 0.0, float &cycle = 0.0, float &footstepInterval = 0.0, + int &index = 0, int preDefinedIndex = -1, const char[] preDefinedName = "", const char[] posture = NULL_STRING, bool &overrideLoop = false, bool &loop = false, char[] returnAnimation = "", int rtnAnimationLength = 0) + { + SF2NPC_BaseNPC controller = this.Controller; + int difficulty = controller.Difficulty; + + char animation[64]; + + bool found = false; + + if (controller.Type == SF2BossType_Chaser && !IsNullString(posture) && strcmp(posture, SF2_PROFILE_CHASER_DEFAULT_POSTURE) != 0) + { + SF2NPC_Chaser chaserController = view_as(controller); + SF2ChaserBossProfileData data; + data = chaserController.GetProfileData(); + SF2ChaserBossProfilePostureInfo postureInfo; + found = data.GetPosture(posture, postureInfo); + if (found) + { + found = postureInfo.Animations.GetAnimation(animType, difficulty, animation, sizeof(animation), + rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, overrideLoop, loop); + if (found) + { + this.AnimationPlaybackRate = rate; + + return LookupProfileAnimation(this.index, animation); + } + } + } + + found = controller.GetProfileData().AnimationData.GetAnimation(animType, difficulty, animation, sizeof(animation), + rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, overrideLoop, loop); + + if (!found) + { + return -1; + } + + int sequence = LookupProfileAnimation(this.index, animation); + + strcopy(returnAnimation, rtnAnimationLength, animation); + + this.AnimationPlaybackRate = rate; + + return sequence; + } + + public int SelectProfileGesture(int definedIndex = -1, const char[] definedName = "", const char[] animType, float &rate = 1.0, float &cycle = 0.0) + { + SF2NPC_BaseNPC controller = view_as(this.Controller); + int difficulty = GetLocalGlobalDifficulty(controller.Index); + + char gesture[64]; + + bool found = controller.GetProfileData().AnimationData.GetGesture(definedIndex, definedName, animType, difficulty, gesture, sizeof(gesture), + rate, cycle); + + if (!found) + { + return -1; + } + + int sequence = LookupProfileAnimation(this.index, gesture); + + return sequence; + } + + public bool ResetProfileAnimation(const char[] animType, int preDefinedIndex = -1, const char[] preDefinedName = "", float &duration = 0.0, const char[] posture = NULL_STRING) + { + if (this.Controller.IsValid() && (this.Controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return false; + } + float rate = 1.0, cycle = 0.0, footstepInterval = 0.0; + bool overrideLoop, loop; + int index = 0; + char animation[64]; + + int sequence = this.SelectProfileAnimation(animType, rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, posture, overrideLoop, loop, animation, sizeof(animation)); + if (sequence == -1) + { + return false; + } + + if (rate < 0.0) + { + rate = 0.0; + } + if (rate > 12.0) + { + rate = 12.0; + } + + Action result = Plugin_Continue; + Call_StartForward(g_OnBossAnimationUpdateFwd); + Call_PushCell(this.Controller); + Call_PushString(animation); + Call_Finish(result); + + if (result != Plugin_Handled) + { + bool isMovement = strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Walk]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Run]) == 0; + + bool shouldLoop = isMovement || strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Idle]) == 0; + this.ResetSequence(sequence); + this.SetPropFloat(Prop_Send, "m_flCycle", cycle); + if (strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Attack]) == 0 && this.MovementType == SF2NPCMoveType_Attack) + { + shouldLoop = true; + } + if (overrideLoop) + { + this.SetProp(Prop_Data, "m_bSequenceLoops", loop); + } + else + { + this.SetProp(Prop_Data, "m_bSequenceLoops", shouldLoop); + } + this.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + + this.LegacyFootstepInterval = footstepInterval; + } + + return true; + } + + public bool AddGesture(const char[] animType, int definedIndex = -1, const char[] definedName = "") + { + float rate = 1.0, duration = 0.0, cycle = 0.0; + int sequence = this.SelectProfileGesture(definedIndex, definedName, animType, rate, cycle); + if (sequence == -1) + { + return false; + } + + if (rate < 0.0) + { + rate = 0.0; + } + if (rate > 12.0) + { + rate = 12.0; + } + + duration = this.SequenceDuration(sequence); + int layer = this.AddLayeredSequence(sequence, 1); + this.SetLayerDuration(layer, duration); + this.SetLayerPlaybackRate(layer, rate); + this.SetLayerCycle(layer, cycle); + this.SetLayerAutokill(layer, true); + } + + public bool IsLOSClearFromTarget(CBaseEntity target, bool checkGlass = true) + { + if (!target.IsValid()) + { + return false; + } + int traceEnt; + float eyePos[3], targetPos[3]; + this.WorldSpaceCenter(eyePos); + target.WorldSpaceCenter(targetPos); + if (IsValidClient(target.index)) + { + GetClientEyePosition(target.index, targetPos); + } + int flags = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP; + if (checkGlass) + { + flags = flags | CONTENTS_GRATE | CONTENTS_WINDOW; + } + Handle trace = TR_TraceRayFilterEx(eyePos, targetPos, + flags, + RayType_EndPoint, TraceRayDontHitEntity, this.index); + bool visible = !TR_DidHit(trace); + traceEnt = TR_GetEntityIndex(trace); + delete trace; + if (!visible && traceEnt == target.index) + { + visible = true; + } + return visible; + } + + public void CreateParticle(char[] particleName, float pos[3]) + { + CBaseEntity particle = CBaseEntity(CreateEntityByName("info_particle_system")); + if (particle.IsValid()) + { + particle.KeyValue("effect_name", particleName); + particle.Teleport(pos, NULL_VECTOR, NULL_VECTOR); + particle.Spawn(); + particle.Activate(); + particle.AcceptInput("Start"); + CreateTimer(0.1, Timer_KillEntity, EntIndexToEntRef(particle.index), TIMER_FLAG_NO_MAPCHANGE); + } + } + + public void ProcessRainbowOutline() + { + SF2NPC_BaseNPC controller = this.Controller; + SF2BossProfileData data; + data = controller.GetProfileData(); + int color[4]; + color[0] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 0) * 127.5 + 127.5); + color[1] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 2) * 127.5 + 127.5); + color[2] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 4) * 127.5 + 127.5); + color[3] = 255; + SetGlowColor(this.index, color); + } + + public void CheckVelocityCancel() + { + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(this.index); + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + if (loco.IsClimbingOrJumping()) + { + this.VelocityCancel = false; + return; + } + float mins[3], maxs[3]; + npc.GetBodyMins(mins); + npc.GetBodyMaxs(maxs); + mins[0] -= 20.0; + mins[1] -= 20.0; + + maxs[0] += 20.0; + maxs[1] += 20.0; + + mins[2] += loco.GetStepHeight(); + maxs[2] += 5.0; + float myPos[3]; + this.GetAbsOrigin(myPos); + + if (!this.VelocityCancel && IsSpaceOccupiedIgnorePlayersAndEnts(myPos, mins, maxs, this.index)) + { + float origin[3]; + loco.SetVelocity(origin); + this.VelocityCancel = true; + } + } +} + +static void OnCreate(SF2_BaseBoss boss) +{ + boss.Controller = SF2_INVALID_NPC; + boss.Target = CBaseEntity(-1); + boss.LastKillTime = 0.0; + boss.Teleporters = new ArrayList(); + + SDKHook(boss.index, SDKHook_SpawnPost, SpawnPost); +} + +static void OnRemove(SF2_BaseBoss boss) +{ + if (boss.Teleporters != null) + { + delete boss.Teleporters; + } +} + +static void SpawnPost(int entIndex) +{ + SF2_BaseBoss boss = SF2_BaseBoss(entIndex); + + boss.MoveParameterType = SF2NPCMoveParameter_None; + boss.MoveXParameter = boss.LookupPoseParameter("move_x"); + boss.MoveYParameter = boss.LookupPoseParameter("move_y"); + boss.EstimatedYaw = 0.0; + if (boss.MoveXParameter != -1 && boss.MoveYParameter != -1) + { + boss.MoveParameterType = SF2NPCMoveParameter_XY; + } + + if (boss.MoveParameterType == SF2NPCMoveParameter_None) + { + boss.MoveYawParameter = boss.LookupPoseParameter("move_yaw"); + if (boss.MoveYawParameter != -1) + { + boss.MoveParameterType = SF2NPCMoveParameter_Yaw; + } + } + + // This one is independent of move_x, move_y, and move_yaw. + // Some models need move_scale in addition to move_yaw or move_x + move_y. + boss.MoveScaleParameter = boss.LookupPoseParameter("move_scale"); + + boss.SetForceWanderPosition(NULL_VECTOR); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) + { + return; + } + + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(NPCGetFromEntIndex(inflictor)); + if (!controller.IsValid()) + { + return; + } + + if (controller.HasAttribute(SF2Attribute_IgnitePlayerOnDeath)) + { + client.Ignite(true); + } + + #if defined _store_included + char bossName[SF2_MAX_NAME_LENGTH]; + controller.GetName(bossName, sizeof(bossName)); + int difficulty = controller.Difficulty; + if (NPCGetDrainCreditState(controller.Index)) + { + Store_SetClientCredits(client.index, Store_GetClientCredits(client.index) - NPCGetDrainCreditAmount(controller.Index, difficulty)); + CPrintToChat(client.index, "{valve}%s{default} has stolen {green}%i credits{default} from you.", bossName, NPCGetDrainCreditAmount(controller.Index, difficulty)); + } + #endif + + SlenderPrintChatMessage(controller.Index, client.index); +} + +static any Native_GetIsValid(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.IsValid(); +} + +static any Native_GetController(Handle plugin, int numParams) +{ + int ent = GetNativeCell(1); + if (!IsValidEntity(ent)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", ent); + } + + SF2_BaseBoss boss = SF2_BaseBoss(ent); + if (!boss.Controller.IsValid()) + { + return -1; + } + return boss.Controller; +} + +static any Native_GetTarget(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + CBaseEntity target = bossEntity.Target; + if (target.index == 0) + { + return CBaseEntity(-1); + } + return target; +} + +static any Native_GetState(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.State; +} + +static any Native_GetCurrentChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.CurrentChaseDuration; +} + +static any Native_SetCurrentChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + bossEntity.CurrentChaseDuration = GetNativeCell(2); + return 0; +} + +static any Native_GetInitialChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.InitialChaseDuration; +} + +static any Native_SetInitialChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + bossEntity.InitialChaseDuration = GetNativeCell(2); + return 0; +} + +static any Native_GetIsKillingSomeone(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.IsKillingSomeone; +} + +static any Native_EyePosition(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + float buffer[3]; + bossEntity.EyePosition(buffer); + SetNativeArray(2, buffer, 3); + + return 0; +} + +static any Native_GetProfileName(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + if (!bossEntity.Controller.IsValid()) + { + return 0; + } + + char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; + bossEntity.Controller.GetProfile(buffer, sizeof(buffer)); + SetNativeString(2, buffer, sizeof(buffer)); + + return 0; +} + +static any Native_GetName(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + SF2NPC_BaseNPC controller = bossEntity.Controller; + if (!controller.IsValid()) + { + return 0; + } + + char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; + controller.GetName(buffer, sizeof(buffer)); + SetNativeString(2, buffer, sizeof(buffer)); + + return 0; +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + if (!bossEntity.Controller.IsValid()) + { + return 0; + } + + SF2BossProfileData data; + data = bossEntity.Controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_ResetProfileAnimation(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + char animation[64], preDefinedName[64]; + GetNativeString(2, animation, sizeof(animation)); + GetNativeString(4, preDefinedName, sizeof(preDefinedName)); + float duration = GetNativeCellRef(5); + bossEntity.ResetProfileAnimation(animation, GetNativeCell(3), preDefinedName, duration); + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp new file mode 100644 index 00000000..c16faa3d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp @@ -0,0 +1,544 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAlertAction < NextBotAction +{ + public SF2_ChaserAlertAction(const float pos[3] = NULL_VECTOR, bool shouldRun = false) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Alert"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetEventCallback(EventResponderType_OnMoveToSuccess, OnMoveToSuccess); + g_Factory.SetEventCallback(EventResponderType_OnMoveToFailure, OnMoveToFailure); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_InitialState") + .DefineFloatField("m_EndTime") + .DefineBoolField("m_IsGoalPosSet") + .DefineVectorField("m_GoalPos") + .DefineFloatField("m_NextWanderTime") + .DefineFloatField("m_NextTurnTime") + .DefineVectorField("m_LookPos") + .DefineBoolField("m_HasReachedAlertPosition") + .DefineFloatField("m_TimeUntilChase") + .DefineBoolField("m_IsRunning") + .EndDataMapDesc(); + } + + SF2_ChaserAlertAction action = view_as(g_Factory.Create()); + + if (!IsNullVector(pos)) + { + action.SetGoalPosition(pos); + } + + action.HasReachedAlertPosition = false; + action.IsRunning = shouldRun; + + return action; + } + + property bool InitialState + { + public get() + { + return this.GetData("m_InitialState"); + } + + public set(bool value) + { + this.SetData("m_InitialState", value); + } + } + + property float EndTime + { + public get() + { + return this.GetDataFloat("m_EndTime"); + } + + public set(float value) + { + this.SetDataFloat("m_EndTime", value); + } + } + + property bool IsGoalPosSet + { + public get() + { + return this.GetData("m_IsGoalPosSet") != 0; + } + + public set(bool value) + { + this.SetData("m_IsGoalPosSet", value); + } + } + + public void GetGoalPosition(float pos[3]) + { + this.GetDataVector("m_GoalPos", pos); + } + + public void SetGoalPosition(const float pos[3]) + { + this.IsGoalPosSet = true; + this.SetDataVector("m_GoalPos", pos); + } + + property float NextWanderTime + { + public get() + { + return this.GetDataFloat("m_NextWanderTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextWanderTime", value); + } + } + + property float NextTurnTime + { + public get() + { + return this.GetDataFloat("m_NextTurnTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextTurnTime", value); + } + } + + public void GetLookPosition(float pos[3]) + { + this.GetDataVector("m_LookPos", pos); + } + + public void SetLookPosition(const float pos[3]) + { + this.SetDataVector("m_LookPos", pos); + } + + property bool HasReachedAlertPosition + { + public get() + { + return this.GetData("m_HasReachedAlertPosition") != 0; + } + + public set(bool value) + { + this.SetData("m_HasReachedAlertPosition", value); + } + } + + property float TimeUntilChase + { + public get() + { + return this.GetDataFloat("m_TimeUntilChase"); + } + + public set(float value) + { + this.SetDataFloat("m_TimeUntilChase", value); + } + } + + property bool IsRunning + { + public get() + { + return this.GetData("m_IsRunning") != 0; + } + + public set(bool value) + { + this.SetData("m_IsRunning", value); + } + } +} + +static int OnStart(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + if (!action.IsRunning) + { + actor.MovementType = SF2NPCMoveType_Walk; + } + else + { + actor.MovementType = SF2NPCMoveType_Run; + } + + action.EndTime = gameTime + data.AlertData.Duration[difficulty]; + action.TimeUntilChase = gameTime + data.AlertData.GraceTime[difficulty]; + action.InitialState = true; + + if (data.AlertOnAlertInfo.OnChangeState[difficulty]) + { + actor.ForceAlertOtherBosses(); + } + + actor.UpdateMovementAnimation(); + + float goalPos[3]; + if (action.IsGoalPosSet) + { + action.GetGoalPosition(goalPos); + } + else + { + actor.GetAbsOrigin(goalPos); + } + + UpdateAlertPosition(action, actor, goalPos, action.IsRunning); + + actor.AlertWithBoss = false; + + return action.Continue(); +} + +static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + if (!controller.IsValid()) + { + return action.Continue(); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + + if (!originalData.IsPvEBoss && IsBeatBoxBeating(2)) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + CBaseEntity target = actor.Target; + int interruptConditions = actor.InterruptConditions; + + PathFollower path = controller.Path; + + if (data.ChaseOnLookData.Enabled[difficulty] && controller.ChaseOnLookTargets.Length > 0) + { + SF2_BasePlayer lookTarget = controller.ChaseOnLookTargets.Get(0); + if (lookTarget.IsValid && !lookTarget.IsEliminated && lookTarget.IsAlive && !lookTarget.IsInGhostMode && !lookTarget.HasEscaped) + { + actor.Target = lookTarget; + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone has looked at me, GET THEM!"); + } + } + + if (gameTime >= action.EndTime) + { + actor.State = STATE_IDLE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + if (actor.FollowedCompanionAlert && actor.Controller.IsValid()) + { + actor.FollowCooldownAlert = GetGameTime() + data.AlertOnAlertInfo.FollowCooldown[difficulty]; + actor.FollowedCompanionAlert = false; + } + actor.AlertWithBoss = false; + return action.ChangeTo(SF2_ChaserIdleAction(), "Huh nothing around here, back to idling."); + } + + if (target.IsValid()) + { + if ((interruptConditions & COND_ENEMYRECHASE) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Wait hold up, someone needs rechasing. GET THEM!"); + } + else if ((interruptConditions & COND_ENEMYNEAR) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone is too close, get them!"); + } + } + + if (target.IsValid() && ((interruptConditions & COND_SAWENEMY) != 0)) + { + if (gameTime >= action.TimeUntilChase) + { + path.Invalidate(); + actor.State = STATE_CHASE; + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "I've got a target, get them!"); + } + } + else if ((interruptConditions & COND_ALERT_TRIGGER) != 0) + { + SF2_BasePlayer alertTarget = actor.AlertTriggerTarget; + if (alertTarget.IsValid && !alertTarget.IsEliminated && alertTarget.IsAlive && !alertTarget.HasEscaped) + { + float pos[3]; + actor.GetAlertTriggerPosition(alertTarget, pos); + + UpdateAlertPosition(action, actor, pos, data.AlertData.RunOnSuspect[difficulty]); + } + } + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0 || actor.QueueForAlertState) + { + float pos[3]; + actor.GetAlertTriggerPositionEx(pos); + + UpdateAlertPosition(action, actor, pos, data.AlertData.RunOnSuspect[difficulty]); + actor.QueueForAlertState = false; + } + + if ((interruptConditions & COND_DEBUG) != 0) + { + float pos[3]; + actor.GetForceWanderPosition(pos); + + UpdateAlertPosition(action, actor, pos, data.AlertData.RunOnWander[difficulty]); + } + + bool isAbleToWander = action.HasReachedAlertPosition && data.CanWander[difficulty]; + if (controller.HasAttribute(SF2Attribute_BlockWalkSpeedUnderDifficulty)) + { + int value = RoundToNearest(controller.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)); + if (difficulty < value) + { + isAbleToWander = false; + } + } + + if (isAbleToWander) + { + if (gameTime >= action.NextWanderTime) + { + float min = data.WanderTimeMin[difficulty]; + float max = data.WanderTimeMax[difficulty]; + + action.NextWanderTime = gameTime + GetRandomFloat(min, max); + + float rangeMin = data.WanderRangeMin[difficulty]; + float rangeMax = data.WanderRangeMax[difficulty]; + float range = GetRandomFloat(rangeMin, rangeMax); + + CNavArea area = actor.GetLastKnownArea(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, range); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) + { + continue; + } + if (collector.Get(i).GetCostSoFar() < rangeMin) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + if (validAreaCount > 0) + { + float wanderPos[3]; + CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); + + if (wanderArea != NULL_AREA) + { + wanderArea.GetCenter(wanderPos); + + UpdateAlertPosition(action, actor, wanderPos, data.AlertData.RunOnWander[difficulty]); + } + } + + delete collector; + delete areaArray; + } + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + if (actor.IsAttemptingToMove) + { + action.NextTurnTime = -1.0; + } + else + { + if (data.AlertData.TurnEnabled[difficulty]) + { + if (!action.InitialState) + { + float lookPos[3]; + action.GetLookPosition(lookPos); + loco.FaceTowards(lookPos); + } + if (action.NextTurnTime <= 0.0) + { + action.NextTurnTime = gameTime + GetRandomFloat(data.AlertData.TurnMinTime[difficulty], data.AlertData.TurnMaxTime[difficulty]); + } + } + } + + if (data.AlertData.TurnEnabled[difficulty] && action.NextTurnTime > 0.0 && action.NextTurnTime <= gameTime) + { + float myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + myAng[1] += GetRandomFloat(-data.AlertData.TurnAngle[difficulty], data.AlertData.TurnAngle[difficulty]); + float lookAt[3]; + lookAt[0] = 50.0; + VectorTransform(lookAt, myPos, myAng, lookAt); + action.SetLookPosition(lookAt); + action.NextTurnTime = gameTime + GetRandomFloat(data.AlertData.TurnMinTime[difficulty], data.AlertData.TurnMaxTime[difficulty]); + action.InitialState = false; + } + + if (gameTime >= actor.NextVoiceTime) + { + actor.PerformVoice(SF2BossSound_Alert); + } + + return action.Continue(); +} + +static void UpdateAlertPosition(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, const float newGoalPos[3], bool shouldRun) +{ + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + if (controller.HasAttribute(SF2Attribute_BlockWalkSpeedUnderDifficulty)) + { + int value = RoundToNearest(controller.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)); + if (difficulty < value) + { + return; + } + } + + INextBot bot = actor.MyNextBotPointer(); + PathFollower path = actor.Controller.Path; + + action.SetGoalPosition(newGoalPos); + + path.ComputeToPos(bot, newGoalPos); + action.HasReachedAlertPosition = false; + + if (!action.IsRunning) + { + if (shouldRun) + { + actor.MovementType = SF2NPCMoveType_Run; + action.IsRunning = true; + } + else + { + actor.MovementType = SF2NPCMoveType_Walk; + } + } +} + +static void OnSuspend(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + actor.IsAttemptingToMove = false; +} + +static void OnEnd(SF2_ChaserAlertAction action, SF2_ChaserEntity actor) +{ + actor.AlertTriggerTarget = SF2_INVALID_PLAYER; + for (int clientIndex = 1; clientIndex <= MaxClients; clientIndex++) + { + SF2_BasePlayer client = SF2_BasePlayer(clientIndex); + if (client.IsValid && !client.IsEliminated && !client.HasEscaped) + { + actor.SetAlertTriggerCount(client, 0); + actor.SetAlertSoundTriggerCooldown(client, 2.0); + } + } +} + +static void OnResume(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.UpdateMovementAnimation(); +} + +static void OnReachedAlertPosition(SF2_ChaserAlertAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + + action.NextTurnTime = gameTime + GetRandomFloat(data.AlertData.TurnMinTime[difficulty], data.AlertData.TurnMaxTime[difficulty]); + action.NextWanderTime = gameTime + GetRandomFloat(data.WanderEnterTimeMin[difficulty], data.WanderEnterTimeMax[difficulty]); +} + +static void OnMoveToSuccess(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, Path path) +{ + if (!action.HasReachedAlertPosition) + { + OnReachedAlertPosition(action, actor); + } + action.HasReachedAlertPosition = true; + action.IsRunning = false; +} + +static void OnMoveToFailure(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, Path path, MoveToFailureType reason) +{ + if (!action.HasReachedAlertPosition) + { + OnReachedAlertPosition(action, actor); + } + action.HasReachedAlertPosition = true; + action.IsRunning = false; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp new file mode 100644 index 00000000..0b5e620f --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -0,0 +1,489 @@ +#pragma semicolon 1 +// This manages the end time and getting a attack + +#include "attacks/melee.sp" +#include "attacks/bullet.sp" +#include "attacks/projectile.sp" +#include "attacks/laser_beam.sp" +#include "attacks/explosive_dance.sp" +#include "attacks/custom.sp" +#include "attacks/tongue.sp" +#include "attacks/forward_based.sp" + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction < NextBotAction +{ + public SF2_ChaserAttackAction(const char[] attackName, int attackIndex, float endTime) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackMain"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnOtherKilled, OnOtherKilled); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_OldState") + .DefineIntField("m_OldMovementType") + .DefineStringField("m_AttackName") + .DefineIntField("m_AttackIndex") + .DefineFloatField("m_EndTime") + .DefineBoolField("m_DidEndAnimation") + .DefineBoolField("m_PlayedBeginVoice") + .DefineBoolField("m_ShouldReplayAnimation") + .DefineStringField("m_LoopSound") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction action = view_as(g_Factory.Create()); + + action.SetAttackName(attackName); + action.EndTime = endTime; + + return action; + } + + public static void Initialize() + { + SF2_ChaserAttackAction_Melee.Initialize(); + SF2_ChaserAttackAction_ExplosiveDance.Initialize(); + SF2_ChaserAttackAction_Bullet.Initialize(); + SF2_ChaserAttackAction_Projectile.Initialize(); + SF2_ChaserAttackAction_Laser.Initialize(); + SF2_ChaserAttackAction_Custom.Initialize(); + SF2_ChaserAttackAction_Tongue.Initialize(); + SF2_ChaserAttackAction_ForwardBased.Initialize(); + } + + property int OldState + { + public get() + { + return this.GetData("m_OldState"); + } + + public set(int value) + { + this.SetData("m_OldState", value); + } + } + + property SF2NPCMoveTypes OldMovementType + { + public get() + { + return this.GetData("m_OldMovementType"); + } + + public set(SF2NPCMoveTypes value) + { + this.SetData("m_OldMovementType", value); + } + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + property float EndTime + { + public get() + { + return this.GetDataFloat("m_EndTime"); + } + + public set(float value) + { + this.SetDataFloat("m_EndTime", value); + } + } + + property bool DidEndAnimation + { + public get() + { + return this.GetData("m_DidEndAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_DidEndAnimation", value); + } + } + + public char[] GetLoopSound() + { + char name[PLATFORM_MAX_PATH]; + this.GetDataString("m_LoopSound", name, sizeof(name)); + return name; + } + + public void SetLoopSound(const char[] name) + { + this.SetDataString("m_LoopSound", name); + } + + property bool PlayedBeginVoice + { + public get() + { + return this.GetData("m_PlayedBeginVoice") != 0; + } + + public set(bool value) + { + this.SetData("m_PlayedBeginVoice", value); + } + } + + property bool ShouldReplayAnimation + { + public get() + { + return this.GetData("m_ShouldReplayAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_ShouldReplayAnimation", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserAttackAction action, SF2_ChaserEntity actor) +{ + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + actor.Controller.Path.Invalidate(); + + NextBotAction attackAction = NULL_ACTION; + + Action result = Plugin_Continue; + Call_StartForward(g_OnChaserGetAttackActionPFwd); + Call_PushCell(actor); + Call_PushString(action.GetAttackName()); + Call_PushCellRef(attackAction); + Call_Finish(result); + + if (result == Plugin_Changed) + { + return attackAction; + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + if (controller.Flags & SFF_FAKE) + { + controller.MarkAsFake(); + return action.Done("I'm a faker, bye"); + } + + if (action.ActiveChild == NULL_ACTION) + { + return action.Done("No attack action was given"); + } + + actor.SetAttackName(action.GetAttackName()); + actor.AttackIndex = action.AttackIndex; + actor.IsAttacking = true; + + actor.RemoveAllGestures(); + CBaseNPC_RemoveAllLayers(actor.index); + + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + SF2ChaserBossProfileData data; + SF2ChaserBossProfileAttackData attackData; + data = controller.GetProfileData(); + data.GetAttack(actor.GetAttackName(), attackData); + actor.AttackRunDelay = gameTime + attackData.RunDelay[difficulty]; + actor.AttackRunDuration = gameTime + attackData.RunDuration[difficulty]; + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + controller.Path.Invalidate(); + + if (actor.State == STATE_CHASE) + { + actor.CurrentChaseDuration += data.ChaseDurationAddOnAttack[difficulty]; + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + + action.OldState = actor.State; + action.OldMovementType = actor.MovementType; + actor.State = STATE_ATTACK; + + actor.InvokeOnStartAttack(action.GetAttackName()); + + if (attackData.StartEffects != null) + { + SlenderSpawnEffects(attackData.StartEffects, controller.Index, false); + } + + if (attackData.RunSpeed[difficulty] > 0.0) + { + actor.MovementType = SF2NPCMoveType_Attack; + } + + if (attackData.Type == SF2BossAttackType_Custom) + { + return action.Continue(); + } + + actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + actor.AddGesture(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + + SF2BossProfileSoundInfo info; + if (actor.SearchSoundsWithSectionName(data.AttackBeginSounds, action.GetAttackName(), info)) + { + action.PlayedBeginVoice = actor.PerformVoice(SF2BossSound_AttackBegin, action.GetAttackName()); + } + else + { + actor.PerformVoice(SF2BossSound_Attack, action.GetAttackName()); + action.PlayedBeginVoice = true; + } + + float duration = 0.0; + NextBotAction newAction = actor.IsAttackTransitionPossible(action.GetAttackName(), _, duration); + action.ShouldReplayAnimation = newAction != NULL_ACTION; + if (newAction != NULL_ACTION) + { + action.EndTime += duration; + } + return newAction != NULL_ACTION ? action.SuspendFor(newAction) : action.Continue(); +} + +static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + + if (!controller.IsValid()) + { + return action.Done(); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2ChaserBossProfileData data; + SF2ChaserBossProfileAttackData attackData; + int interrputConditions = actor.InterruptConditions; + data = controller.GetProfileData(); + data.GetAttack(actor.GetAttackName(), attackData); + bool end = false; + INextBot bot = actor.MyNextBotPointer(); + PathFollower path = controller.Path; + ILocomotion loco = bot.GetLocomotionInterface(); + + int difficulty = controller.Difficulty; + + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float myPos[3], targetPos[3]; + actor.GetAbsOrigin(myPos); + target.GetAbsOrigin(targetPos); + float distance = GetVectorSquareMagnitude(myPos, targetPos); + if (attackData.CancelDistance[difficulty] >= 0.0 && distance > Pow(attackData.CancelDistance[difficulty], 2.0)) + { + end = true; + } + + if (attackData.MinCancelDistance[difficulty] >= 0.0 && distance < Pow(attackData.MinCancelDistance[difficulty], 2.0)) + { + end = true; + } + + if (attackData.CancelLos[difficulty] && (actor.InterruptConditions & COND_ENEMYVISIBLE) == 0) + { + end = true; + } + + if (actor.MovementType == SF2NPCMoveType_Attack) + { + if (actor.Teleporters.Length > 0) + { + CBaseEntity(actor.Teleporters.Get(0)).GetAbsOrigin(targetPos); + } + + if (!bot.IsRangeLessThanEx(targetPos, 8.0)) + { + if ((interrputConditions & COND_NEWENEMY) != 0 || path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) + { + path.ComputeToPos(bot, targetPos); + } + } + } + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + if (attackData.Type == SF2BossAttackType_Custom) + { + if (action.ActiveChild == NULL_ACTION || end) + { + return action.Done("Done using a custom attack"); + } + return action.Continue(); + } + + SF2BossProfileSoundInfo info; + char value[PLATFORM_MAX_PATH]; + value = action.GetLoopSound(); + if (value[0] == '\0') + { + if (actor.SearchSoundsWithSectionName(data.AttackLoopSounds, action.GetAttackName(), info)) + { + char sound[PLATFORM_MAX_PATH]; + info.EmitSound(_, actor.index, _, _, _, sound); + action.SetLoopSound(sound); + } + else + { + action.SetLoopSound("-"); + } + } + + if (action.ActiveChild == NULL_ACTION || GetGameTime() > action.EndTime || end) + { + NextBotAction newAction = actor.IsAttackTransitionPossible(action.GetAttackName(), true); + if (!action.DidEndAnimation && newAction != NULL_ACTION) + { + actor.PerformVoice(SF2BossSound_AttackEnd, action.GetAttackName()); + action.DidEndAnimation = true; + if (newAction != NULL_ACTION) + { + return action.SuspendFor(newAction, "We're gonna do a attack end action"); + } + } + return action.Done("Attack finished"); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + char value[PLATFORM_MAX_PATH]; + value = action.GetLoopSound(); + if (value[0] != '\0' && strcmp(value, "-") != 0) + { + StopSound(actor.index, SNDCHAN_AUTO, value); + } + if (actor.IsStunned) + { + return action.Done(); + } + if (!action.ShouldReplayAnimation && !action.DidEndAnimation) + { + return action.Done("Suspended"); + } + return action.Continue(); +} + +static int OnResume(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + if (!action.PlayedBeginVoice) + { + actor.PerformVoice(SF2BossSound_Attack, action.GetAttackName()); + action.PlayedBeginVoice = true; + } + if (action.ShouldReplayAnimation) + { + actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + actor.AddGesture(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + action.ShouldReplayAnimation = false; + } + + if (action.DidEndAnimation) + { + return action.Done("Finished"); + } + return action.Continue(); +} + +static void OnEnd(SF2_ChaserAttackAction action, SF2_ChaserEntity actor) +{ + actor.IsAttacking = false; + actor.IsAttemptingToMove = false; + actor.CancelAttack = false; + actor.GroundSpeedOverride = false; + actor.MovementType = action.OldMovementType; + + char value[PLATFORM_MAX_PATH]; + value = action.GetLoopSound(); + if (value[0] != '\0' && strcmp(value, "-") != 0) + { + StopSound(actor.index, SNDCHAN_AUTO, value); + } + + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + float gameTime = GetGameTime(); + SF2ChaserBossProfileData data; + SF2ChaserBossProfileAttackData attackData; + data = controller.GetProfileData(); + data.GetAttack(actor.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + + actor.SetNextAttackTime(actor.GetAttackName(), gameTime + attackData.Cooldown[difficulty]); + + actor.InvokeOnEndAttack(actor.GetAttackName()); + } + + actor.State = action.OldState; + + actor.SetAttackName(""); + actor.AttackIndex = -1; +} + +static void OnOtherKilled(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseCombatCharacter victim, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damagetype) +{ + actor.CheckTauntKill(SF2_BasePlayer(victim.index)); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp new file mode 100644 index 00000000..d038b3d6 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp @@ -0,0 +1,40 @@ +#pragma semicolon 1 +// This is for detecting attacks + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackLayerAction < NextBotAction +{ + public SF2_ChaserAttackLayerAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackLayer"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.BeginDataMapDesc() + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserAttackLayerAction action, SF2_ChaserEntity actor) +{ + return SF2_ChaserChaseLayerAction(); +} + +static int Update(SF2_ChaserAttackLayerAction action, SF2_ChaserEntity actor) +{ + CBaseEntity target = actor.Target; + if (!actor.IsAttacking && target.IsValid()) + { + NextBotAction attackAction = actor.GetAttackAction(target); + if (attackAction != NULL_ACTION) + { + actor.EndCloak(); + return action.SuspendFor(attackAction, "Time to die!"); + } + } + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp new file mode 100644 index 00000000..b48d9ad4 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp @@ -0,0 +1,331 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Bullet < NextBotAction +{ + public SF2_ChaserAttackAction_Bullet(int attackIndex, const char[] attackName, float fireDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackBullet"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Bullet action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Ranged) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Bullet(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Bullet action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Bullet action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer firing bullets"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + float gameTime = GetGameTime(); + int difficulty = controller.Difficulty; + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + DoBulletAttack(actor, action.GetAttackName()); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextFireTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextFireTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextFireTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextFireTime = -1.0; + } + } + return action.Continue(); +} + +static void DoBulletAttack(SF2_ChaserEntity actor, const char[] attackName) +{ + SF2NPC_Chaser controller = actor.Controller; + CBaseEntity target = actor.Target; + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + float spread = attackData.BulletSpread[difficulty]; + ArrayList bulletSounds = data.BulletShootSounds; + SF2BossProfileSoundInfo soundInfo; + if (actor.SearchSoundsWithSectionName(bulletSounds, attackName, soundInfo)) + { + soundInfo.EmitSound(_, actor.index); + } + + float effectPos[3], targetPos[3], basePos[3], baseAng[3]; + float effectAng[3] = {0.0, 0.0, 0.0}; + actor.GetAbsOrigin(basePos); + actor.GetAbsAngles(baseAng); + if (target.IsValid()) + { + target.WorldSpaceCenter(targetPos); + } + else + { + float fwd[3], eyePos[3]; + GetAngleVectors(baseAng, fwd, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(fwd, fwd); + controller.GetEyePosition(eyePos); + targetPos[0] = eyePos[0] + fwd[0] * 9001.0; + targetPos[1] = eyePos[1] + fwd[1] * 9001.0; + targetPos[2] = eyePos[2] + fwd[2] * 9001.0; + } + effectPos = attackData.BulletOffset; + VectorTransform(effectPos, basePos, baseAng, effectPos); + AddVectors(effectAng, baseAng, effectAng); + + float direction[3], angle[3]; + if (!target.IsValid()) + { + angle = baseAng; + } + SubtractVectors(targetPos, effectPos, direction); + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + float fwd[3], right[3], up[3]; + GetAngleVectors(angle, fwd, right, up); + + float dir[3], end[3]; + float x, y; + + for (int i = 0; i < attackData.BulletCount[difficulty]; i++) + { + x = GetRandomFloat(-0.5, 0.5) + GetRandomFloat(-0.5, 0.5); + y = GetRandomFloat(-0.5, 0.5) + GetRandomFloat(-0.5, 0.5); + + dir[0] = fwd[0] + x * spread * right[0] + y * spread * up[0]; + dir[1] = fwd[1] + x * spread * right[1] + y * spread * up[1]; + dir[2] = fwd[2] + x * spread * right[2] + y * spread * up[2]; + NormalizeVector(dir, dir); + + end[0] = effectPos[0] + dir[0] * 9001.0; + end[1] = effectPos[1] + dir[1] * 9001.0; + end[2] = effectPos[2] + dir[2] * 9001.0; + + Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); + if (TR_GetFraction(trace) < 1.0) + { + int hitTarget = TR_GetEntityIndex(trace); + if (hitTarget == -1) + { + delete trace; + continue; + } + + float endPos[3]; + TR_GetEndPosition(endPos, trace); + + TE_SetupEffectDispatch(GetEffectDispatchStringTableIndex("Impact"), + .origin = endPos, + .start = effectPos, + .damageType = DMG_BULLET, + .surfaceProp = TR_GetSurfaceProps(trace), + .hitbox = TR_GetHitGroup(trace), + .entindex = TR_GetEntityIndex(trace)); + TE_SendToAll(); + + SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.BulletDamage[difficulty], DMG_BULLET, _, CalculateBulletDamageForce(dir, 1.0), endPos); + if (SF2_BasePlayer(hitTarget).IsValid) + { + attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); + } + + if (attackData.BulletTrace[0] == '\0') + { + delete trace; + continue; + } + + int table = FindStringTable("ParticleEffectNames"); + char tmp[256]; + int count = GetStringTableNumStrings(table); + int index = INVALID_STRING_INDEX; + for (int i2 = 0; i2 < count; i2++) + { + ReadStringTable(table, i2, tmp, sizeof(tmp)); + if (strcmp(tmp, attackData.BulletTrace, false) == 0) + { + index = i2; + break; + } + } + + if (index == INVALID_STRING_INDEX) + { + LogError("Could not find particle: %s", attackData.BulletTrace); + delete trace; + continue; + } + TE_Particle(index, effectPos, effectPos, dir, actor.index, view_as(PATTACH_CUSTOMORIGIN), _, _, true, view_as(PATTACH_CUSTOMORIGIN), endPos); + TE_SendToAll(); + } + delete trace; + } +} + +static float[] CalculateBulletDamageForce(const float bulletDir[3], float scale) +{ + float force[3]; + force = bulletDir; + NormalizeVector(force, force); + ScaleVector(force, g_PhysicsPushScaleConVar.FloatValue); + ScaleVector(force, scale); + return force; +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Bullet action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + DoBulletAttack(actor, action.GetAttackName()); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/custom.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/custom.sp new file mode 100644 index 00000000..4c5a0aba --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/custom.sp @@ -0,0 +1,41 @@ +#pragma semicolon 1 + +methodmap SF2_ChaserAttackAction_Custom < NextBotAction +{ + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + g_OnChaserGetCustomAttackPossibleStatePFwd.AddFunction(null, OnChaserGetCustomAttackPossibleState); + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + if (attackData.Type != SF2BossAttackType_Custom) + { + return Plugin_Continue; + } + + result = chaser.GetCustomAttack(attackName, chaser.Target); + return Plugin_Changed; +} + +static Action OnChaserGetCustomAttackPossibleState(SF2_ChaserEntity chaser, const char[] attackName, CBaseEntity target) +{ + if (!chaser.IsCustomAttackPossible(attackName, target)) + { + return Plugin_Continue; + } + + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/explosive_dance.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/explosive_dance.sp new file mode 100644 index 00000000..8bd5d9c5 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/explosive_dance.sp @@ -0,0 +1,215 @@ +#pragma semicolon 1 + +static char g_ExplosionSounds[][] = +{ + ")weapons/explode1.wav", + ")weapons/explode2.wav", + ")weapons/explode3.wav" +}; + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_ExplosiveDance < NextBotAction +{ + public SF2_ChaserAttackAction_ExplosiveDance(int attackIndex, const char[] attackName, float damageDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackExplosiveDance"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextDamageTime") + .DefineIntField("m_ExplosionCount") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_ExplosiveDance action = view_as(g_Factory.Create()); + + action.NextDamageTime = damageDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + action.ExplosionCount = 0; + + return action; + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextDamageTime + { + public get() + { + return this.GetDataFloat("m_NextDamageTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextDamageTime", value); + } + } + + property int ExplosionCount + { + public get() + { + return this.GetData("m_ExplosionCount"); + } + + public set(int value) + { + this.SetData("m_ExplosionCount", value); + } + } +} + +static void OnGamemodeStart() +{ + for (int i = 0; i < sizeof(g_ExplosionSounds); i++) + { + PrecacheSound(g_ExplosionSounds[i], true); + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_ExplosiveDance) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_ExplosiveDance(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer explosion dancing"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + float gameTime = GetGameTime(); + + if (action.NextDamageTime >= 0.0 && gameTime > action.NextDamageTime && attackData.EventNumber == -1) + { + DoExplosion(action, actor); + } + return action.Continue(); +} + +static void DoExplosion(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + float damage = attackData.Damage[difficulty]; + float radius = attackData.ExplosiveDanceRadius[difficulty]; + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + damage *= 0.5; + } + + float worldPos[3], myEyeAng[3]; + actor.WorldSpaceCenter(worldPos); + actor.GetAbsAngles(myEyeAng); + + action.ExplosionCount++; + if (action.ExplosionCount > 35) + { + action.NextDamageTime = -1.0; + return; + } + + float explosionPos[3]; + + explosionPos = worldPos; + explosionPos[0] = worldPos[0] + GetRandomFloat(-350.0, 350.0); + explosionPos[1] = worldPos[1] + GetRandomFloat(-350.0, 350.0); + Explode(explosionPos, damage, radius, actor.index); + EmitSoundToAll(g_ExplosionSounds[GetRandomInt(0, sizeof(g_ExplosionSounds) - 1)], actor.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + + action.NextDamageTime = GetGameTime() + 0.13; +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + DoExplosion(action, actor); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/forward_based.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/forward_based.sp new file mode 100644 index 00000000..cc78288e --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/forward_based.sp @@ -0,0 +1,190 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_ForwardBased < NextBotAction +{ + public SF2_ChaserAttackAction_ForwardBased(int attackIndex, const char[] attackName, float damageDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackForward"); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextDamageTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_ForwardBased action = view_as(g_Factory.Create()); + + action.NextDamageTime = damageDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextDamageTime + { + public get() + { + return this.GetDataFloat("m_NextDamageTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextDamageTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type >= SF2BossAttackType_Melee && attackData.Type <= SF2BossAttackType_Tongue) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_ForwardBased(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int Update(SF2_ChaserAttackAction_ForwardBased action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer forward attacking"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + if (action.NextDamageTime >= 0.0 && gameTime > action.NextDamageTime && attackData.EventNumber == -1) + { + RegisterForward(action, actor); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextDamageTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextDamageTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextDamageTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextDamageTime = -1.0; + } + } + return action.Continue(); +} + +static void RegisterForward(SF2_ChaserAttackAction_ForwardBased action, SF2_ChaserEntity actor) +{ + Call_StartForward(g_OnBossAttackedFwd); + Call_PushCell(actor.Controller.Index); + Call_PushCell(action.AttackIndex); + Call_Finish(); +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_ForwardBased action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + RegisterForward(action, actor); + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp new file mode 100644 index 00000000..8706c0cb --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp @@ -0,0 +1,281 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Laser < NextBotAction +{ + public SF2_ChaserAttackAction_Laser(int attackIndex, const char[] attackName, float fireDelay, float duration) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackLaser"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineFloatField("m_LaserDuration") + .DefineFloatField("m_LaserCooldown") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Laser action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.LaserDuration = duration; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property float LaserDuration + { + public get() + { + return this.GetDataFloat("m_LaserDuration"); + } + + public set(float value) + { + this.SetDataFloat("m_LaserDuration", value); + } + } + + property float LaserCooldown + { + public get() + { + return this.GetDataFloat("m_LaserCooldown"); + } + + public set(float value) + { + this.SetDataFloat("m_LaserCooldown", value); + } + } + +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_LaserBeam) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Laser(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime(), attackData.LaserDuration[difficulty] + GetGameTime() + attackData.DamageDelay[difficulty]); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer firing my super laser beam"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + if (action.LaserCooldown < gameTime) + { + action.LaserCooldown = gameTime + attackData.LaserTicks[difficulty]; + FireLaser(action, actor, attackData.LaserTicks[difficulty]); + } + + if (action.LaserDuration < gameTime) + { + action.NextFireTime = -1.0; + } + } + + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float lookAt[3]; + target.GetAbsOrigin(lookAt); + actor.MyNextBotPointer().GetLocomotionInterface().FaceTowards(lookAt); + } + + return action.Continue(); +} + +static void FireLaser(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + CBaseEntity target = actor.Target; + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (!target.IsValid()) + { + return; + } + + float targetPos[3]; + target.WorldSpaceCenter(targetPos); + + float basePos[3], baseAng[3], effectAng[3], effectPos[3]; + actor.GetAbsOrigin(basePos); + actor.GetAbsAngles(baseAng); + effectPos = attackData.LaserOffset; + VectorTransform(effectPos, basePos, baseAng, effectPos); + AddVectors(effectAng, baseAng, effectAng); + + float direction[3], angle[3]; + SubtractVectors(targetPos, effectPos, direction); + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + float fwd[3]; + GetAngleVectors(angle, fwd, NULL_VECTOR, NULL_VECTOR); + + NormalizeVector(fwd, fwd); + + float end[3]; + end[0] = effectPos[0] + fwd[0] * 9001.0; + end[1] = effectPos[1] + fwd[1] * 9001.0; + end[2] = effectPos[2] + fwd[2] * 9001.0; + + Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); + + if (TR_GetFraction(trace) < 1.0) + { + int hitTarget = TR_GetEntityIndex(trace); + if (hitTarget == -1) + { + delete trace; + return; + } + + float endPos[3]; + TR_GetEndPosition(endPos, trace); + + int color[3]; + color = attackData.LaserColor; + int actualColor[4]; + actualColor[0] = color[0]; + actualColor[1] = color[1]; + actualColor[2] = color[2]; + actualColor[3] = 255; + + if (attackData.LaserAttachment) + { + int attachmentIndex = actor.LookupAttachment(attackData.LaserAttachmentName); + float targetEntPos[3], tempAng[3]; + actor.GetAttachment(attachmentIndex, targetEntPos, tempAng); + TE_SetupBeamPoints(targetEntPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, interval, attackData.LaserSize, attackData.LaserSize, 5, attackData.LaserNoise, actualColor, 1); + TE_SendToAll(); + } + else + { + TE_SetupBeamPoints(effectPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, interval, attackData.LaserSize, attackData.LaserSize, 5, attackData.LaserNoise, actualColor, 1); + TE_SendToAll(); + } + + SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.LaserDamage[difficulty], DMG_SHOCK | DMG_ALWAYSGIB, _, _, endPos); + + attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); + } + + delete trace; +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + FireLaser(action, actor, 0.1); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp new file mode 100644 index 00000000..9c263f90 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -0,0 +1,521 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Melee < NextBotAction +{ + public SF2_ChaserAttackAction_Melee(int attackIndex, const char[] attackName, float damageDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackMelee"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextDamageTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Melee action = view_as(g_Factory.Create()); + + action.NextDamageTime = damageDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextDamageTime + { + public get() + { + return this.GetDataFloat("m_NextDamageTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextDamageTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Melee) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Melee(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer melee attacking"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + if (action.NextDamageTime >= 0.0 && gameTime > action.NextDamageTime && attackData.EventNumber == -1) + { + DoMeleeAttack(action, actor); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextDamageTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextDamageTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextDamageTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextDamageTime = -1.0; + } + } + return action.Continue(); +} + +static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + + int difficulty = controller.Difficulty; + + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (originalData.IsPvEBoss) + { + attackEliminated = true; + } + float damage = attackData.Damage[difficulty]; + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + damage *= 0.5; + } + int damageType = attackData.DamageType[difficulty]; + + float myPos[3], myEyePos[3], myEyeAng[3]; + actor.GetAbsOrigin(myPos); + controller.GetEyePosition(myEyePos); + actor.GetAbsAngles(myEyeAng); + + AddVectors(g_SlenderEyePosOffset[controller.Index], myEyeAng, myEyeAng); + + float viewPunch[3]; + viewPunch = attackData.PunchVelocity; + + float range = attackData.Range[difficulty]; + float spread = attackData.Spread[difficulty]; + float force = attackData.DamageForce[difficulty]; + + bool hit = false; + + ArrayList targets = new ArrayList(); + + // Sweep the props + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!building.IsValid()) + { + continue; + } + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, building, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(building)) + { + continue; + } + + if (!IsTargetInMeleeChecks(actor, attackData, building, range, spread)) + { + continue; + } + + targets.Push(g_Buildings.Get(i)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + if (!building.IsValid()) + { + continue; + } + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, building, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(building)) + { + continue; + } + + if (!IsTargetInMeleeChecks(actor, attackData, building, range, spread)) + { + continue; + } + + targets.Push(g_WhitelistedEntities.Get(i)); + } + + for (int i = 0; i < g_BreakableProps.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_BreakableProps.Get(i))); + if (!building.IsValid()) + { + continue; + } + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, building, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(building)) + { + continue; + } + + if (!IsTargetInMeleeChecks(actor, attackData, building, range, spread)) + { + continue; + } + + targets.Push(g_BreakableProps.Get(i)); + } + + for (int i = 0; i < targets.Length; i++) + { + CBaseEntity prop = CBaseEntity(EntRefToEntIndex(targets.Get(i))); + if (!prop.IsValid()) + { + continue; + } + + hit = true; + + char class[64]; + prop.GetClassname(class, sizeof(class)); + + float realDamage = damage; + if (attackData.DamagePercent[difficulty] > 0.0) + { + if (SF2_BasePlayer(targets.Get(i)).IsValid) + { + realDamage = float(SF2_BasePlayer(targets.Get(i)).ModifiedMaxHealth); + realDamage *= attackData.DamagePercent[difficulty]; + } + else + { + realDamage = (strcmp(class, "tank_boss", false) != 0 && strcmp(class, "func_breakable", false) != 0) ? float(prop.GetProp(Prop_Send, "m_iMaxHealth")) : float(prop.GetProp(Prop_Data, "m_iMaxHealth")); + realDamage *= attackData.DamagePercent[difficulty]; + } + } + SDKHooks_TakeDamage(prop.index, actor.index, actor.index, realDamage, 64, _, _, myEyePos, false); + } + delete targets; + + // Sweep the players + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, player, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(player)) + { + continue; + } + + if (!IsTargetInMeleeChecks(actor, attackData, player, range, spread)) + { + continue; + } + + hit = true; + + float direction[3], targetPos[3]; + player.WorldSpaceCenter(targetPos); + SubtractVectors(targetPos, myEyePos, direction); + GetVectorAngles(direction, direction); + GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(direction, direction); + ScaleVector(direction, force); + + float realDamage = damage; + if (attackData.DamagePercent[difficulty] > 0.0) + { + realDamage = float(player.GetProp(Prop_Data, "m_iMaxHealth")) * attackData.DamagePercent[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_DeathCamOnLowHealth) || attackData.DeathCamLowHealth) + { + float checkDamage = realDamage; + + if ((damageType & DMG_ACID) && !player.InCondition(TFCond_DefenseBuffed)) + { + checkDamage *= 3.0; + } + else if (((damageType & DMG_POISON) || player.InCondition(TFCond_Jarated) || player.InCondition(TFCond_MarkedForDeath) || player.InCondition(TFCond_MarkedForDeathSilent)) && !player.InCondition(TFCond_DefenseBuffed)) + { + checkDamage *= 1.35; + } + else if (player.InCondition(TFCond_DefenseBuffed)) + { + checkDamage *= 0.65; + } + + if (checkDamage > player.Health) + { + player.StartDeathCam(controller.Index, myPos); + } + } + + player.TakeDamage(_, actor.index, actor.index, realDamage, damageType, _, direction, myEyePos, false); + player.ViewPunch(viewPunch); + + if (player.InCondition(TFCond_UberchargedCanteen) && player.InCondition(TFCond_CritOnFirstBlood) && player.InCondition(TFCond_UberBulletResist) && player.InCondition(TFCond_UberBlastResist) && player.InCondition(TFCond_UberFireResist) && player.InCondition(TFCond_MegaHeal)) //Remove Powerplay + { + player.ChangeCondition(TFCond_UberchargedCanteen, true); + player.ChangeCondition(TFCond_CritOnFirstBlood, true); + player.ChangeCondition(TFCond_UberBulletResist, true); + player.ChangeCondition(TFCond_UberBlastResist, true); + player.ChangeCondition(TFCond_UberFireResist, true); + player.ChangeCondition(TFCond_MegaHeal, true); + TF2_SetPlayerPowerPlay(player.index, false); + } + + attackData.ApplyDamageEffects(player, difficulty, SF2_ChaserBossEntity(actor.index)); + + // Add stress + float stressScalar = damage / 125.0; + if (stressScalar > 1.0) + { + stressScalar = 1.0; + } + ClientAddStress(player.index, 0.33 * stressScalar); + } + + ArrayList hitSounds = hit ? data.HitSounds : data.MissSounds; + SF2BossProfileSoundInfo info; + if (actor.SearchSoundsWithSectionName(hitSounds, action.GetAttackName(), info)) + { + info.EmitSound(_, actor.index); + } + + actor.DoShockwave(action.GetAttackName()); + + if (hit) + { + CBaseEntity phys = CBaseEntity(CreateEntityByName("env_physexplosion")); + if (phys.IsValid()) + { + float worldSpace[3]; + actor.WorldSpaceCenter(worldSpace); + phys.Teleport(worldSpace, NULL_VECTOR, NULL_VECTOR); + phys.KeyValue("spawnflags", "1"); + phys.KeyValueFloat("radius", range); + phys.KeyValueFloat("magnitude", force); + phys.Spawn(); + phys.Activate(); + phys.AcceptInput("Explode"); + RemoveEntity(phys.index); + } + } + else + { + if (attackData.MissEffects != null) + { + SlenderSpawnEffects(attackData.MissEffects, controller.Index, false); + } + } + + if (!SF_IsSlaughterRunMap()) + { + if (attackData.Disappear[difficulty]) + { + controller.UnSpawn(true); + } + else + { + if (hit && attackData.DisappearOnHit[difficulty]) + { + controller.UnSpawn(true); + } + } + } +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + DoMeleeAttack(action, actor); + } +} + +static bool IsTargetInMeleeChecks(SF2_ChaserEntity actor, SF2ChaserBossProfileAttackData data, CBaseEntity target, float range, float fov) +{ + if (!target.IsValid()) + { + return false; + } + + float myPos[3], myEyePos[3], myEyeAng[3]; + actor.GetAbsOrigin(myPos); + actor.Controller.GetEyePosition(myEyePos); + actor.GetAbsAngles(myEyeAng); + + AddVectors(g_SlenderEyePosOffset[actor.Controller.Index], myEyeAng, myEyeAng); + + float targetPos[3]; + target.WorldSpaceCenter(targetPos); + + float distance = actor.MyNextBotPointer().GetRangeSquaredTo(target.index); + if (distance > Pow(range, 2.0)) + { + return false; + } + + float direction[3]; + SubtractVectors(targetPos, myEyePos, direction); + GetVectorAngles(direction, direction); + float difference = FloatAbs(AngleDiff(direction[1], myEyeAng[1])); + if (difference > fov) + { + return false; + } + + if (!data.HitThroughWalls[actor.Controller.Difficulty]) + { + TR_TraceRayFilter(myEyePos, targetPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); + + if (TR_DidHit() && TR_GetEntityIndex() != target.index) + { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp new file mode 100644 index 00000000..cf079605 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp @@ -0,0 +1,296 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Projectile < NextBotAction +{ + public SF2_ChaserAttackAction_Projectile(int attackIndex, const char[] attackName, float fireDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackProjectile"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Projectile action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Projectile) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Projectile(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Projectile action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Projectile action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer firing projectiles"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + FireProjectile(actor, action.GetAttackName()); + actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_ProjectileShoot], _, action.GetAttackName()); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextFireTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextFireTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextFireTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextFireTime = -1.0; + } + } + return action.Continue(); +} + +static void FireProjectile(SF2_ChaserEntity actor, const char[] attackName) +{ + SF2NPC_Chaser controller = actor.Controller; + float targetPos[3], myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + if (actor.Target.IsValid()) + { + actor.Target.WorldSpaceCenter(targetPos); + } + else + { + float fwd[3], eyePos[3]; + GetAngleVectors(myAng, fwd, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(fwd, fwd); + controller.GetEyePosition(eyePos); + targetPos[0] = eyePos[0] + fwd[0] * 9001.0; + targetPos[1] = eyePos[1] + fwd[1] * 9001.0; + targetPos[2] = eyePos[2] + fwd[2] * 9001.0; + } + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + + bool attackWaiters = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + attackWaiters = true; + } + + ArrayList projectileSounds = data.ProjectileShootSounds; + SF2BossProfileSoundInfo soundInfo; + if (actor.SearchSoundsWithSectionName(projectileSounds, attackName, soundInfo)) + { + soundInfo.EmitSound(_, actor.index); + } + + float effectPos[3]; + effectPos = attackData.ProjectileOffset; + VectorTransform(effectPos, myPos, myAng, effectPos); + + for (int i = 0; i < attackData.ProjectileCount[difficulty]; i++) + { + float direction[3], angle[3]; + SubtractVectors(targetPos, effectPos, direction); + float deviation = attackData.ProjectileDeviation[difficulty]; + + if (deviation != 0) + { + direction[0] += GetRandomFloat(-deviation, deviation); + direction[1] += GetRandomFloat(-deviation, deviation); + direction[2] += GetRandomFloat(-deviation, deviation); + } + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + switch (attackData.ProjectileType) + { + case SF2BossProjectileType_Fireball: + { + SF2_ProjectileFireball.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], data.FireballExplodeSound, attackData.FireballTrail, attackWaiters); + } + case SF2BossProjectileType_Iceball: + { + SF2_ProjectileIceball.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], data.FireballExplodeSound, attackData.IceballTrail, attackData.IceballSlowdownDuration[difficulty], attackData.IceballSlowdownPercent[difficulty], data.IceballSlowSound, attackWaiters); + } + case SF2BossProjectileType_Rocket: + { + SF2_ProjectileRocket.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackData.CritProjectiles[difficulty], data.RocketTrail, data.RocketExplodeParticle, data.RocketExplodeSound, attackData.RocketModel, attackWaiters); + } + case SF2BossProjectileType_SentryRocket: + { + SF2_ProjectileSentryRocket.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackData.CritProjectiles[difficulty], attackWaiters); + } + case SF2BossProjectileType_Mangler: + { + SF2_ProjectileCowMangler.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackWaiters); + } + case SF2BossProjectileType_Grenade: + { + SF2_ProjectileGrenade.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackData.CritProjectiles[difficulty], "pipebombtrail_blue", ROCKET_EXPLODE_PARTICLE, ROCKET_IMPACT, "models/weapons/w_models/w_grenade_grenadelauncher.mdl", attackWaiters); + } + case SF2BossProjectileType_Arrow: + { + SF2_ProjectileArrow.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.CritProjectiles[difficulty], "pipebombtrail_blue", "weapons/fx/rics/arrow_impact_flesh2.wav", "models/weapons/w_models/w_arrow.mdl", attackWaiters); + } + case SF2BossProjectileType_Baseball: + { + SF2_ProjectileBaseball.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.CritProjectiles[difficulty], "models/weapons/w_models/w_baseball.mdl", attackWaiters); + } + } + } +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Projectile action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + FireProjectile(actor, action.GetAttackName()); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp new file mode 100644 index 00000000..a4286682 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp @@ -0,0 +1,643 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Tongue < NextBotAction +{ + public SF2_ChaserAttackAction_Tongue(int attackIndex, const char[] attackName, float fireDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackTongue"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnCommandString, OnCommandString); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineEntityField("m_TongueEntity") + .DefineEntityField("m_TongueEntityEnd") + .DefineEntityField("m_TongueEntityTrail") + .DefineBoolField("m_CaughtTarget") + .DefineEntityField("m_LatchedTarget") + .DefineFloatField("m_TongueTimer") + .DefineBoolField("m_InPullAnimation") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Tongue action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnPlayerJumpPFwd.AddFunction(null, OnJump); + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property int TongueEntity + { + public get() + { + return this.GetDataEnt("m_TongueEntity"); + } + + public set(int value) + { + this.SetDataEnt("m_TongueEntity", value); + } + } + + property int TongueEntityEnd + { + public get() + { + return this.GetDataEnt("m_TongueEntityEnd"); + } + + public set(int value) + { + this.SetDataEnt("m_TongueEntityEnd", value); + } + } + + property int TongueEntityTrail + { + public get() + { + return this.GetDataEnt("m_TongueEntityTrail"); + } + + public set(int value) + { + this.SetDataEnt("m_TongueEntityTrail", value); + } + } + + property bool CaughtTarget + { + public get() + { + return this.GetData("m_CaughtTarget") != 0; + } + + public set(bool value) + { + this.SetData("m_CaughtTarget", value); + } + } + + property SF2_BasePlayer LatchedTarget + { + public get() + { + return SF2_BasePlayer(this.GetDataEnt("m_LatchedTarget")); + } + + public set(SF2_BasePlayer value) + { + this.SetDataEnt("m_LatchedTarget", value.index); + } + } + + property float TongueTime + { + public get() + { + return this.GetDataFloat("m_TongueTimer"); + } + + public set(float value) + { + this.SetDataFloat("m_TongueTimer", value); + } + } + + property bool InPullAnimation + { + public get() + { + return this.GetData("m_InPullAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_InPullAnimation", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Tongue) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Tongue(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static void OnJump(SF2_BasePlayer client) +{ + if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped) + { + return; + } + + if (client.IsLatched) + { + client.LatchCount--; + if (client.LatchCount <= 0) + { + client.IsLatched = false; + client.LatchCount = 0; + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + if (client.Latcher != npc.Index) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + + chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString("break tongue"); + } + } + } +} + +static int OnStart(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION || action.IsSuspended) + { + return action.Done("No longer firing a tongue"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + if (action.CaughtTarget) + { + SF2_ChaserAttackAction parent = view_as(action.Parent); + parent.EndTime += GetGameFrameTime(); + if (!action.LatchedTarget.IsValid) + { + return action.Done("Nobody is in my tongue"); + } + } + + SF2NPC_Chaser controller = actor.Controller; + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (originalData.IsPvEBoss) + { + attackEliminated = true; + } + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + FireTongue(actor, action); + + action.NextFireTime = -1.0; + } + + if (action.NextFireTime <= -1.0 && (!IsValidEntity(action.TongueEntity) || !IsValidEntity(action.TongueEntityEnd))) + { + return action.Done("Tongue down"); + } + + if (IsValidEntity(action.TongueEntity) && IsValidEntity(action.TongueEntityEnd) && IsValidEntity(action.TongueEntityTrail) && !action.CaughtTarget) + { + CBaseEntity tongueEnd = CBaseEntity(action.TongueEntityEnd); + float tonguePos[3]; + tongueEnd.GetAbsOrigin(tonguePos); + ArrayList players = new ArrayList(); + TR_EnumerateEntitiesSphere(tonguePos, 75.0, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, players); + + for (int i = 0; i < players.Length; i++) + { + SF2_BasePlayer player = players.Get(i); + + if (g_Enabled) + { + if (!attackEliminated && player.IsEliminated) + { + continue; + } + } + + float worldSpace[3]; + player.WorldSpaceCenter(worldSpace); + TR_TraceRayFilter(tonguePos, worldSpace, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, tongueEnd.index); + + if (TR_DidHit() && TR_GetEntityIndex() != player.index) + { + continue; + } + + if (!attackData.TongueCanEscape[difficulty]) + { + player.Stun(1.0, 1.0, TF_STUNFLAG_NOSOUNDOREFFECT | TF_STUNFLAG_BONKSTUCK); + } + else + { + player.Stun(2.0, 1.0, TF_STUNFLAG_SLOWDOWN); + } + player.TakeDamage(_, actor.index, actor.index, attackData.Damage[difficulty], DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE); + + tongueEnd.AcceptInput("ClearParent"); + tongueEnd.Teleport(worldSpace, NULL_VECTOR, NULL_VECTOR); + SetVariantString("!activator"); + tongueEnd.AcceptInput("SetParent", player.index); + RemoveEntity(action.TongueEntityTrail); + action.TongueTime = 0.5 + gameTime; + action.LatchedTarget = player; + action.CaughtTarget = true; + attackData.TongueTiedSound.EmitSound(_, tongueEnd.index); + attackData.TongueHitSound.EmitSound(_, tongueEnd.index); + player.IsLatched = true; + player.LatchCount = GetRandomInt(6, 10); + if (!attackData.TongueCanEscape[difficulty]) + { + player.LatchCount = 999999; // I'm lazy + } + player.Latcher = controller.Index; + if (!player.HasHint(PlayerHint_Trap)) + { + player.ShowHint(PlayerHint_Trap); + } + actor.MyNextBotPointer().GetIntentionInterface().OnCommandString("latch tongue"); + break; + } + + delete players; + } + + TongueThink(action, actor); + + return action.Continue(); +} + +static void TongueThink(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor) +{ + if (!IsValidEntity(action.TongueEntity) && !IsValidEntity(action.TongueEntityEnd)) + { + return; + } + + if (!action.CaughtTarget) + { + return; + } + + float gameTime = GetGameTime(); + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + SF2_BasePlayer player = action.LatchedTarget; + if (!player.IsValid || !player.IsAlive) + { + Unlatch(action); + return; + } + + CBaseEntity tongueStart = CBaseEntity(action.TongueEntity); + float tonguePos[3], worldSpace[3]; + tongueStart.GetAbsOrigin(tonguePos); + player.WorldSpaceCenter(worldSpace); + + TR_TraceRayFilter(tonguePos, worldSpace, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, tongueStart.index); + + if (TR_DidHit() && TR_GetEntityIndex() != player.index) + { + Unlatch(action, true); + return; + } + + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + float myPos[3], targetPos[3]; + actor.GetAbsOrigin(myPos); + player.GetAbsOrigin(targetPos); + + if (action.TongueTime > -1.0 && action.TongueTime <= gameTime) + { + SF2_BasePlayer latched = action.LatchedTarget; + if (!attackData.TongueCanEscape[difficulty]) + { + latched.Stun(1.0, 1.0, TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); + } + else + { + latched.Stun(2.0, 1.0, TF_STUNFLAG_SLOWDOWN); + } + action.TongueTime = gameTime + 0.5; + + float velocity[3]; + SubtractVectors(myPos, targetPos, velocity); + velocity[2] = 0.0; + + ScaleVector(velocity, attackData.TonguePullScale[difficulty]); + player.SetPropVector(Prop_Data, "m_vecBaseVelocity", velocity); + + player.TakeDamage(_, actor.index, actor.index, attackData.Damage[difficulty], DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE); + } + + loco.FaceTowards(targetPos); +} + +static void Unlatch(SF2_ChaserAttackAction_Tongue action, bool removeSlow = false) +{ + if (IsValidEntity(action.TongueEntity)) + { + RemoveEntity(action.TongueEntity); + } + + if (IsValidEntity(action.TongueEntityEnd)) + { + RemoveEntity(action.TongueEntityEnd); + } + + if (action.LatchedTarget.IsValid) + { + action.LatchedTarget.IsLatched = false; + action.LatchedTarget.LatchCount = 0; + if (removeSlow) + { + action.LatchedTarget.ChangeCondition(TFCond_Dazed, true); + } + } + + action.LatchedTarget = SF2_BasePlayer(-1); +} + +static void OnEnd(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor) +{ + Unlatch(action); +} + +static void FireTongue(SF2_ChaserEntity actor, SF2_ChaserAttackAction_Tongue action) +{ + CBaseEntity target = actor.Target; + if (!target.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + float effectPos[3], myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + effectPos = attackData.TongueOffset; + VectorTransform(effectPos, myPos, myAng, effectPos); + + CBaseEntity tongue = CBaseEntity(CreateEntityByName("move_rope")); + + /*if (attackData.TongueAttachment[0] == '\0') + { + tongue.Teleport(effectPos, myAng, NULL_VECTOR); + SetVariantString("!activator"); + tongue.AcceptInput("SetParent", actor.index); + }*/ + + tongue.Teleport(effectPos, myAng, NULL_VECTOR); + SetVariantString("!activator"); + tongue.AcceptInput("SetParent", actor.index); + + char buffer[PLATFORM_MAX_PATH]; + tongue.KeyValue("Slack", "50"); + tongue.KeyValue("RopeMaterial", attackData.TongueMaterial); + FormatEx(buffer, sizeof(buffer), "rope_%i", controller.Index); + tongue.KeyValue("NextKey", buffer); + + CBaseEntity tongueEnd = CBaseEntity(CreateEntityByName("move_rope")); + tongueEnd.KeyValue("targetname", buffer); + tongueEnd.KeyValue("Slack", "50"); + tongueEnd.KeyValue("RopeMaterial", attackData.TongueMaterial); + tongueEnd.Teleport(effectPos, myAng, NULL_VECTOR); + tongueEnd.SetMoveType(MOVETYPE_FLY); + + tongue.Spawn(); + tongueEnd.Spawn(); + tongue.Activate(); + tongueEnd.Activate(); + + action.TongueEntity = tongue.index; + action.TongueEntityEnd = tongueEnd.index; + + float velocity[3], shootAng[3], targetPos[3]; + target.WorldSpaceCenter(targetPos); + SubtractVectors(targetPos, effectPos, shootAng); + NormalizeVector(shootAng, shootAng); + GetVectorAngles(shootAng, shootAng); + GetAngleVectors(shootAng, velocity, NULL_VECTOR, NULL_VECTOR); + ScaleVector(velocity, attackData.TongueSpeed[difficulty]); + + attackData.TongueLaunchSound.EmitSound(_, actor.index); + + CBaseEntity trail = CBaseEntity(CreateEntityByName("tf_projectile_grapplinghook")); + trail.SetModel("models/roller.mdl"); + trail.KeyValue("solid", "0"); + trail.Spawn(); + trail.Activate(); + trail.SetProp(Prop_Send, "m_usSolidFlags", 22); + SetEntityCollisionGroup(trail.index, 1); + trail.Teleport(effectPos, shootAng, velocity); + SetVariantString("!activator"); + tongueEnd.AcceptInput("SetParent", trail.index); + SetEntityOwner(trail.index, tongueEnd.index); + trail.SetRenderMode(RENDER_TRANSCOLOR); + trail.SetRenderColor(0, 0, 0, 0); + CreateTimer(0.1, Timer_Think, EntIndexToEntRef(trail.index), TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); + action.TongueEntityTrail = trail.index; + SetEntDataFloat(trail.index, GetEntSendPropOffs(trail.index, "m_iProjectileType") + 32, GetGameTime() + 9999.9); + + SetEntityOwner(trail.index, tongue.index); +} + +static Action Timer_Think(Handle timer, any ref) +{ + int entity = EntRefToEntIndex(ref); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } + CBaseEntity trail = CBaseEntity(entity); + int owner = trail.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + if (!IsValidEntity(owner)) + { + RemoveEntity(trail.index); + return Plugin_Stop; + } + float pos[3], mins[3] = {-8.0, ... }, maxs[3] = {8.0, ... }; + trail.GetAbsOrigin(pos); + TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, trail.index); + + if (TR_GetEntityIndex() == 0) + { + if (IsValidEntity(owner)) + { + RemoveEntity(owner); + } + RemoveEntity(trail.index); + return Plugin_Stop; + } + + return Plugin_Continue; +} + +static int OnCommandString(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, const char[] command) +{ + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + if (strcmp(command, "break tongue") == 0) + { + Unlatch(action, true); + if (attackData.Animations.GetAnimation("break", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return action.TrySuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle), RESULT_CRITICAL); + } + } + } + + if (strcmp(command, "latch tongue") == 0 && !action.InPullAnimation) + { + if (attackData.Animations.GetAnimation("pull", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + actor.ResetSequence(sequence); + actor.SetPropFloat(Prop_Send, "m_flCycle", cycle); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + } + } + action.InPullAnimation = true; + } + + return action.TryContinue(); +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + FireTongue(actor, action); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/beatbox_freeze.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/beatbox_freeze.sp new file mode 100644 index 00000000..8ec201b0 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/beatbox_freeze.sp @@ -0,0 +1,88 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserBeatBoxFreezeAction < NextBotAction +{ + public SF2_ChaserBeatBoxFreezeAction(bool wasMoving) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_BeatboxFreeze"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_EndDelay") + .DefineBoolField("m_WasMoving") + .EndDataMapDesc(); + } + SF2_ChaserBeatBoxFreezeAction action = view_as(g_Factory.Create()); + action.WasMoving = wasMoving; + return action; + } + + property float EndDelay + { + public get() + { + return this.GetDataFloat("m_EndDelay"); + } + + public set(float value) + { + this.SetDataFloat("m_EndDelay", value); + } + } + + property bool WasMoving + { + public get() + { + return this.GetData("m_WasMoving") != 0; + } + + public set(bool value) + { + this.SetData("m_WasMoving", value); + } + } +} + +static int OnStart(SF2_ChaserBeatBoxFreezeAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + INextBot bot = actor.MyNextBotPointer(); + actor.IsAttemptingToMove = false; + if (action.WasMoving) + { + actor.UpdateMovementAnimation(); + } + + bot.GetLocomotionInterface().Stop(); + + action.EndDelay = GetGameTime() + 0.5; + + return action.Continue(); +} + +static int Update(SF2_ChaserBeatBoxFreezeAction action, SF2_ChaserEntity actor, float interval) +{ + float gameTime = GetGameTime(); + if (IsBeatBoxBeating()) + { + action.EndDelay = gameTime + 0.5; + } + + if (action.EndDelay < gameTime) + { + return action.Done("I'm done being frozen from Beatbox."); + } + + return action.Continue(); +} + +static void OnEnd(SF2_ChaserBeatBoxFreezeAction action, SF2_ChaserEntity actor) +{ + actor.IsAttemptingToMove = action.WasMoving; + actor.UpdateMovementAnimation(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp new file mode 100644 index 00000000..c4b697e4 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -0,0 +1,246 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserChaseAction < NextBotAction +{ + public SF2_ChaserChaseAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Chase"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + return SF2_ChaserAttackLayerAction(); +} + +static int OnStart(SF2_ChaserChaseAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.PerformVoice(SF2BossSound_ChaseInitial); + actor.MaintainTarget = true; + + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + CBaseEntity target = actor.Target; + if (!target.IsValid()) + { + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "My target is no longer valid!"); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + + if (actor.InitialChaseDuration > 0.0) + { + actor.CurrentChaseDuration = actor.InitialChaseDuration; + actor.InitialChaseDuration = 0.0; + } + + if (data.AlertOnChaseInfo.OnChangeState[difficulty]) + { + actor.ForceChaseOtherBosses(); + } + + actor.AlertWithBoss = false; + + return action.Continue(); +} + +static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + if (!controller.IsValid()) + { + return action.Continue(); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + + if (!originalData.IsPvEBoss && IsBeatBoxBeating(2) && actor.State != STATE_ATTACK && !actor.IsInChaseInitial) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + + CBaseEntity target = actor.Target; + float gameTime = GetGameTime(); + int difficulty = controller.Difficulty; + INextBot bot = actor.MyNextBotPointer(); + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + attackEliminated = true; + } + + if (target.IsValid()) + { + actor.ProcessCloak(target); + + if (ShouldClientBeForceChased(controller, target)) + { + SetClientForceChaseState(controller, target, false); + } + if (IsValidClient(target.index)) + { + actor.SetAutoChaseCount(SF2_BasePlayer(target.index), 0); + } + + if (!actor.IsAttacking) + { + if (actor.CurrentChaseDuration <= 0.0) + { + actor.State = STATE_ALERT; + return action.ChangeTo(SF2_ChaserAlertAction(_, data.AlertData.RunOnSuspect[difficulty]), "Oh he got away..."); + } + } + + actor.RegisterProjectiles(); + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (!originalData.IsPvEBoss && player.IsValid && player.HasEscaped) + { + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "Our target escaped, that is no good!"); + } + + if (player.IsValid && player.CanSeeSlender(controller.Index, false, _, !attackEliminated)) + { + float maxRange = data.ChaseDurationAddMaxRange[difficulty]; + float distanceRatio = bot.GetRangeTo(player.index) / maxRange; + if (maxRange > 0.0 && distanceRatio < 1.0) + { + float durationTimeAddMin = data.ChaseDurationAddVisibleMin[difficulty]; + float durationTimeAddMax = data.ChaseDurationAddVisibleMax[difficulty]; + float durationAdd = durationTimeAddMin + ((durationTimeAddMax - durationTimeAddMin) * distanceRatio); + + actor.CurrentChaseDuration += durationAdd * GetGameFrameTime(); + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + } + else + { + actor.CurrentChaseDuration -= GetGameFrameTime(); + } + } + else + { + if (!actor.IsAttacking) + { + if (actor.KillTarget.IsValid()) + { + return action.SuspendFor(SF2_ChaserTauntKillAction(), "Let's taunt the kill victim cause funny."); + } + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "No valid target? No problem!"); + } + } + + if (data.ChaseOnLookData.Enabled[difficulty] && controller.ChaseOnLookTargets.FindValue(target.index) != -1) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + + if (gameTime >= actor.NextVoiceTime && !actor.IsAttacking) + { + actor.PerformVoice(SF2BossSound_Chasing); + } + + #if defined DEBUG + SendDebugMessageToPlayer(target.index, DEBUG_BOSS_CHASE, 1, "actor[%i].CurrentChaseDuration: %f", actor.index, actor.CurrentChaseDuration); + #endif + + return action.Continue(); +} + +static void OnResume(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + if (actor.WasStunned) + { + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + if (controller.GetProfileData().StunData.ChaseInitialOnEnd[controller.Difficulty]) + { + actor.PerformVoice(SF2BossSound_ChaseInitial); + } + } + } +} + +static void OnEnd(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + if (!actor.Controller.IsValid()) + { + return; + } + SF2NPC_Chaser controller = actor.Controller; + actor.EndCloak(); + PathFollower path = actor.Controller.Path; + float gameTime = GetGameTime(); + if (path.IsValid()) + { + path.Invalidate(); + } + actor.MaintainTarget = false; + actor.HasSmelled = false; + int difficulty = actor.Controller.Difficulty; + if (actor.FollowedCompanionChase) + { + SF2ChaserBossProfileData data; + data = actor.Controller.GetProfileData(); + actor.FollowCooldownChase = GetGameTime() + data.AlertOnChaseInfo.FollowCooldown[difficulty]; + } + if (actor.Teleporters != null) + { + actor.Teleporters.Clear(); + } + + SF2ChaserBossProfileData chaserData; + chaserData = actor.Controller.GetProfileData(); + float cooldown = chaserData.AutoChaseAfterChaseCooldown[difficulty]; + if (cooldown > 0.0) + { + float nextTime = gameTime + cooldown; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (nextTime > actor.GetAutoChaseCooldown(client)) + { + actor.SetAutoChaseCooldown(client, gameTime + cooldown); + } + } + + for (int i = 1; i < GetMaxEntities(); i++) + { + if (!IsValidEntity(i)) + { + continue; + } + + SetTargetMarkState(controller, CBaseEntity(i), false); + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp new file mode 100644 index 00000000..2f249f60 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp @@ -0,0 +1,91 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserChaseInitialAction < NextBotAction +{ + public SF2_ChaserChaseInitialAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_ChaseInitial"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + //g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } + + public static bool IsPossible(SF2_ChaserEntity actor) + { + if (SF_IsSlaughterRunMap()) + { + return false; + } + + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + if (!data.AnimationData.HasAnimationSection(g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial])) + { + return false; + } + + return true; + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor) +{ + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + actor.IsInChaseInitial = true; + + if (data.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int Update(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + if (action.ActiveChild == NULL_ACTION) + { + return action.Done("Sequence action finished"); + } + + ILocomotion loco = actor.MyNextBotPointer().GetLocomotionInterface(); + loco.Stop(); + + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor) +{ + actor.IsInChaseInitial = false; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp new file mode 100644 index 00000000..f6086962 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp @@ -0,0 +1,111 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserChaseLayerAction < NextBotAction +{ + public SF2_ChaserChaseLayerAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_ChaseLayer"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor) +{ + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.MovementType = SF2NPCMoveType_Run; + actor.UpdateMovementAnimation(); + + if (SF2_ChaserChaseInitialAction.IsPossible(actor)) + { + return action.SuspendFor(SF2_ChaserChaseInitialAction()); + } + return action.Continue(); +} + +static int Update(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + int interrputConditions = actor.InterruptConditions; + INextBot bot = actor.MyNextBotPointer(); + CBaseEntity target = actor.Target; + + PathFollower path = controller.Path; + ILocomotion loco = bot.GetLocomotionInterface(); + + bool tooClose = target.IsValid() && + (interrputConditions & COND_ENEMYVISIBLE) != 0 && + bot.IsRangeLessThan(target.index, 8.0); + + if (tooClose && path.IsValid()) + { + path.Invalidate(); + } + else if (target.IsValid()) + { + float pos[3]; + target.GetAbsOrigin(pos); + if (actor.Teleporters.Length > 0) + { + CBaseEntity(actor.Teleporters.Get(0)).GetAbsOrigin(pos); + } + + if (!bot.IsRangeLessThanEx(pos, 8.0)) + { + if ((interrputConditions & COND_NEWENEMY) != 0 || path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) + { + path.ComputeToPos(bot, pos); + } + } + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + return action.Continue(); +} + +static void OnSuspend(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + actor.IsAttemptingToMove = false; +} + +static int OnResume(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.MovementType = SF2NPCMoveType_Run; + actor.UpdateMovementAnimation(); + if (actor.WasStunned) + { + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + if (controller.GetProfileData().StunData.ChaseInitialOnEnd[controller.Difficulty] && SF2_ChaserChaseInitialAction.IsPossible(actor)) + { + return action.SuspendFor(SF2_ChaserChaseInitialAction()); + } + } + } + return action.Continue(); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp new file mode 100644 index 00000000..1d0942d1 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp @@ -0,0 +1,297 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserDeathAction < NextBotAction +{ + public SF2_ChaserDeathAction(CBaseEntity attacker = view_as(-1)) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Death"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_Attacker") + .EndDataMapDesc(); + } + SF2_ChaserDeathAction action = view_as(g_Factory.Create()); + + action.Attacker = attacker.index; + return action; + } + + property int Attacker + { + public get() + { + return this.GetDataEnt("m_Attacker"); + } + + public set(int value) + { + this.SetDataEnt("m_Attacker", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int difficulty = controller.Difficulty; + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + + controller.WasKilled = true; + + actor.IsAttemptingToMove = false; + loco.Stop(); + + actor.EndCloak(); + + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + actor.PerformVoice(SF2BossSound_Death); + + actor.State = STATE_DEATH; + + if (originalData.IsPvEBoss) + { + KillPvEBoss(actor.index); + } + + if (originalData.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Death], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Death], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserDeathAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + actor.GroundSpeedOverride = true; + int difficulty = controller.Difficulty; + + if (data.DeathData.StartEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.DeathData.StartEffects, controller.Index, false, pos, ang, _, _, true); + } + + if (data.DeathData.KeyDrop) + { + if (SF_IsBoxingMap() && data.BoxingBoss && !g_SlenderBoxingBossIsKilled[controller.Index] && !view_as(controller).GetProfileData().IsPvEBoss) + { + g_SlenderBoxingBossKilled++; + if ((g_SlenderBoxingBossKilled == g_SlenderBoxingBossCount)) + { + NPC_DropKey(controller.Index, data.DeathData.KeyModel, data.DeathData.KeyTrigger); + } + g_SlenderBoxingBossIsKilled[controller.Index] = true; + } + else + { + NPC_DropKey(controller.Index, data.DeathData.KeyModel, data.DeathData.KeyTrigger); + } + } + + actor.DropItem(true); + + actor.RemoveAllGestures(); + CBaseNPC_RemoveAllLayers(actor.index); + + if (data.DeathData.AddHealthPerDeath[difficulty] > 0.0) + { + controller.SetDeathHealth(difficulty, controller.GetDeathHealth(difficulty) + data.DeathData.AddHealthPerDeath[difficulty]); + } + + return action.Continue(); +} + +static int Update(SF2_ChaserDeathAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + return action.Continue(); + } + + return action.Done("I am actually fully dead now"); +} + +static void OnEnd(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + + if (!controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + if (data.DeathData.EndEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.DeathData.EndEffects, controller.Index, false, pos, ang, _, _, true); + } + + if (data.DeathData.RemoveOnDeath) + { + SpawnGibs(actor); + if (data.DeathData.RagdollOnDeath) + { + actor.AcceptInput("BecomeRagdoll"); + } + if (controller.IsCopy) + { + controller.Remove(); + } + else + { + SF2NPC_BaseNPC newMaster = SF2_INVALID_NPC; + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC other = SF2NPC_BaseNPC(i); + if (!other.IsValid()) + { + continue; + } + + if (other == controller) + { + continue; + } + + if (!other.IsCopy) + { + continue; + } + + if (other.CopyMaster == controller && CBaseEntity(other.EntIndex).IsValid()) + { + newMaster = other; + newMaster.CopyMaster = SF2_INVALID_NPC; + break; + } + } + + // Let's switch copy masters + if (newMaster.IsValid()) + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC other = SF2NPC_BaseNPC(i); + if (!other.IsValid()) + { + continue; + } + + if (other == controller) + { + continue; + } + + if (!other.IsCopy) + { + continue; + } + + if (other.CopyMaster == controller) + { + other.CopyMaster = newMaster; + } + } + } + + controller.Remove(); + } + } + else if (data.DeathData.DisappearOnDeath) + { + SpawnGibs(actor); + controller.UnSpawn(true); + } + else if (data.DeathData.RagdollOnDeath) + { + actor.AcceptInput("BecomeRagdoll"); + } +} + +static void OnAnimationEvent(SF2_ChaserDeathAction action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + actor.CastAnimEvent(event); + actor.CastAnimEvent(event, true); +} + +static void SpawnGibs(SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.DeathData.Gibs == null) + { + return; + } + float pos[3], ang[3], vel[3]; + char model[PLATFORM_MAX_PATH]; + actor.WorldSpaceCenter(pos); + actor.GetAbsAngles(ang); + for (int i = 0; i < data.DeathData.Gibs.Length; i++) + { + ang[1] = GetRandomFloat(-180.0, 180.0); + + for(int i2 = 0; i2 < 2; i2++) + { + vel[i2] += GetRandomFloat(-300.0, 300.0); + } + vel[2] = GetRandomFloat(-300.0, 300.0); + + data.DeathData.Gibs.GetString(i, model, sizeof(model)); + + if (strlen(model) > 0) + { + CBaseEntity gib = CBaseEntity(CreateEntityByName("prop_physics_multiplayer")); + gib.KeyValue("model", model); + gib.KeyValue("physicsmode", "2"); + + gib.Teleport(pos, ang, vel); + gib.Spawn(); + gib.Teleport(NULL_VECTOR, NULL_VECTOR, vel); + + SetEntityCollisionGroup(gib.index, 1); + gib.SetProp(Prop_Send, "m_usSolidFlags", 0); + gib.SetProp(Prop_Send, "m_nSolidType", 2); + gib.SetProp(Prop_Send, "m_nSkin", data.DeathData.GibSkin); + + int effects = 16 | 64; + gib.SetProp(Prop_Send, "m_fEffects", effects); + + SetVariantString("OnUser1 !self:Kill::10.0:1"); + gib.AcceptInput("AddOutput"); + gib.AcceptInput("FireUser1"); + } + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/despawn.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/despawn.sp new file mode 100644 index 00000000..85ba7791 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/despawn.sp @@ -0,0 +1,44 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserDespawnAction < NextBotAction +{ + public SF2_ChaserDespawnAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Despawn"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_ChaserDespawnAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.State = STATE_IDLE; + + actor.PerformVoice(SF2BossSound_Despawn); + + float rate = 1.0, duration = 0.0, cycle = 0.0; + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Despawn], rate, duration, cycle); + if (sequence != -1) + { + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + + return action.Done(); +} + +static int OnResume(SF2_ChaserDespawnAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + return action.Done("I am done despawning"); +} + +static void OnEnd(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor) +{ + RemoveEntity(actor.index); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp new file mode 100644 index 00000000..40bc5c64 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp @@ -0,0 +1,336 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserFleeToHealAction < NextBotAction +{ + public SF2_ChaserFleeToHealAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_FleeToHeal"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnMoveToSuccess, OnMoveToSuccess); + g_Factory.SetEventCallback(EventResponderType_OnMoveToFailure, OnMoveToFailure); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_FleeTime") + .DefineFloatField("m_HealTime") + .DefineFloatField("m_HealDuration") + .DefineBoolField("m_HasPath") + .DefineBoolField("m_IsHealing") + .DefineBoolField("m_PlayedAnimation") + .DefineFloatField("m_HealAnimationDuration") + .DefineBoolField("m_PlayedSound") + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } + + property float FleeTime + { + public get() + { + return this.GetDataFloat("m_FleeTime"); + } + + public set(float value) + { + this.SetDataFloat("m_FleeTime", value); + } + } + + property float HealTime + { + public get() + { + return this.GetDataFloat("m_HealTime"); + } + + public set(float value) + { + this.SetDataFloat("m_HealTime", value); + } + } + + property float HealDuration + { + public get() + { + return this.GetDataFloat("m_HealDuration"); + } + + public set(float value) + { + this.SetDataFloat("m_HealDuration", value); + } + } + + property bool HasPath + { + public get() + { + return this.GetData("m_HasPath") != 0; + } + + public set(bool value) + { + this.SetData("m_HasPath", value); + } + } + + property bool IsHealing + { + public get() + { + return this.GetData("m_IsHealing") != 0; + } + + public set(bool value) + { + this.SetData("m_IsHealing", value); + } + } + + property bool PlayedAnimation + { + public get() + { + return this.GetData("m_PlayedAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_PlayedAnimation", value); + } + } + + property float HealAnimationDuration + { + public get() + { + return this.GetDataFloat("m_HealAnimationDuration"); + } + + public set(float value) + { + this.SetDataFloat("m_HealAnimationDuration", value); + } + } + + property bool PlayedSound + { + public get() + { + return this.GetData("m_PlayedSound") != 0; + } + + public set(bool value) + { + this.SetData("m_PlayedSound", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + action.HealDuration = rageInfo.HealDuration; + action.HealTime = rageInfo.HealDelay; + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = controller.Difficulty; + action.IsHealing = false; + action.HasPath = false; + action.FleeTime = GetRandomFloat(5.0, 10.0); + if (rageInfo.Animations.GetAnimation("start", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int Update(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + return action.Continue(); + } + + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + PathFollower path = controller.Path; + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + + if (!action.IsHealing && !action.HasPath) + { + actor.IsRunningAway = true; + if (rageInfo.HealCloak) + { + actor.StartCloak(); + } + CNavArea area = actor.GetLastKnownArea(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, GetRandomFloat(rageInfo.FleeRange[0], rageInfo.FleeRange[1])); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + float navPos[3], myPos[3]; + collector.Get(i).GetCenter(navPos); + actor.GetAbsOrigin(myPos); + if (GetVectorSquareMagnitude(navPos, myPos) <= Pow(256.0, 2.0)) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + if (validAreaCount > 0) + { + float wanderPos[3]; + CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); + + if (wanderArea != NULL_AREA) + { + wanderArea.GetCenter(wanderPos); + + path.ComputeToPos(bot, wanderPos); + + action.HasPath = true; + } + } + + delete collector; + delete areaArray; + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + if (action.IsHealing) + { + if (!action.PlayedAnimation) + { + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + if (rageInfo.Animations.GetAnimation("healing", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + action.HealAnimationDuration = duration; + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + action.HealAnimationDuration = actor.SequenceDuration(sequence) / rate; + action.HealAnimationDuration *= (1.0 - cycle); + actor.ResetSequence(sequence); + actor.SetPropFloat(Prop_Send, "m_flCycle", cycle); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + } + } + action.HealAnimationDuration += gameTime; + action.PlayedAnimation = true; + } + + action.HealTime -= GetGameFrameTime(); + if (action.HealTime <= 0.0 && action.HealDuration > 0.0) + { + if (!action.PlayedSound) + { + actor.PerformVoiceEx(_, _, rageInfo.HealSounds, true); + action.PlayedSound = true; + } + float amount = actor.MaxHealth * rageInfo.HealAmount; + if (!data.DeathData.Enabled[difficulty]) + { + amount = actor.MaxStunHealth * rageInfo.HealAmount; + } + float increase = LerpFloats(0.0, amount, GetGameFrameTime() * (1.0 / rageInfo.HealDuration)); + if (data.DeathData.Enabled[difficulty]) + { + actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(increase + actor.GetProp(Prop_Data, "m_iHealth"))); + if (float(actor.GetProp(Prop_Data, "m_iHealth")) > actor.MaxHealth) + { + actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(actor.MaxHealth)); + } + } + else + { + actor.StunHealth += increase; + if (actor.StunHealth > actor.MaxStunHealth) + { + actor.StunHealth = actor.MaxStunHealth; + } + } + if (originalData.Healthbar) + { + UpdateHealthBar(controller.Index); + SetHealthBarColor(true); + } + action.HealDuration -= GetGameFrameTime(); + } + + if (action.HealAnimationDuration < gameTime) + { + actor.IsSelfHealing = false; + SetHealthBarColor(false); + return action.Done(); + } + } + + if (!action.IsHealing) + { + action.FleeTime -= GetGameFrameTime(); + if (action.FleeTime <= 0.0) + { + path.Invalidate(); + actor.EndCloak(); + actor.IsRunningAway = false; + action.IsHealing = true; + actor.IsSelfHealing = true; + } + } + + return action.Continue(); +} + +static void OnMoveToSuccess(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, Path path) +{ + actor.EndCloak(); + actor.IsRunningAway = false; + action.IsHealing = true; + actor.IsSelfHealing = true; +} + +static void OnMoveToFailure(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, Path path, MoveToFailureType reason) +{ + actor.EndCloak(); + actor.IsRunningAway = false; + action.IsHealing = true; + actor.IsSelfHealing = true; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp new file mode 100644 index 00000000..b8c2d8b9 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp @@ -0,0 +1,424 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserIdleAction < NextBotAction +{ + public SF2_ChaserIdleAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Idle"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_InitialState") + .DefineFloatField("m_NextWanderTime") + .DefineFloatField("m_NextTurnTime") + .DefineVectorField("m_LookPos") + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } + + property bool InitialState + { + public get() + { + return this.GetData("m_InitialState") != 0; + } + + public set(bool value) + { + this.SetData("m_InitialState", value); + } + } + + property float NextWanderTime + { + public get() + { + return this.GetDataFloat("m_NextWanderTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextWanderTime", value); + } + } + + property float NextTurnTime + { + public get() + { + return this.GetDataFloat("m_NextTurnTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextTurnTime", value); + } + } + + public void GetLookPosition(float pos[3]) + { + this.GetDataVector("m_LookPos", pos); + } + + public void SetLookPosition(const float pos[3]) + { + this.SetDataVector("m_LookPos", pos); + } +} + +static int OnStart(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + INextBot bot = actor.MyNextBotPointer(); + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + + action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + action.NextWanderTime = gameTime + GetRandomFloat(data.WanderEnterTimeMin[difficulty], data.WanderEnterTimeMax[difficulty]); + action.InitialState = true; + + bot.GetLocomotionInterface().Stop(); + + actor.MovementType = SF2NPCMoveType_Walk; + actor.IsAttemptingToMove = false; + actor.UpdateMovementAnimation(); + + for (int i = 0; i < MAX_NPCTELEPORTER; i++) + { + if (actor.Controller.GetTeleporter(i) != INVALID_ENT_REFERENCE) + { + actor.Controller.SetTeleporter(i, INVALID_ENT_REFERENCE); + } + } + + if (!actor.HasSmelled) + { + actor.SmellCooldown = gameTime + GetRandomFloat(data.SmellData.CooldownMin[difficulty], data.SmellData.CooldownMax[difficulty]); + } + else + { + actor.SmellCooldown = gameTime + GetRandomFloat(data.SmellData.CooldownAfterStateMin[difficulty], data.SmellData.CooldownAfterStateMax[difficulty]); + } + + g_SlenderTimeUntilKill[controller.Index] = GetGameTime() + NPCGetIdleLifetime(controller.Index, difficulty); + + return action.Continue(); +} + +static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + if (!controller.IsValid()) + { + return action.Continue(); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + + if (!originalData.IsPvEBoss && IsBeatBoxBeating(2)) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + int interruptConditions = actor.InterruptConditions; + CBaseEntity target = actor.Target; + + PathFollower path = controller.Path; + + if (data.ChaseOnLookData.Enabled[difficulty] && controller.ChaseOnLookTargets.Length > 0) + { + SF2_BasePlayer lookTarget = SF2_BasePlayer(controller.ChaseOnLookTargets.Get(0)); + if (lookTarget.IsValid && !lookTarget.IsEliminated && lookTarget.IsAlive && !lookTarget.IsInGhostMode && !lookTarget.HasEscaped) + { + actor.Target = lookTarget; + actor.OldTarget = lookTarget; + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone has looked at me, GET THEM!"); + } + } + + if (target.IsValid()) + { + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly || + view_as(controller).GetProfileData().IsPvEBoss) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "We must endless chase, GET THEM!"); + } + + if ((interruptConditions & COND_ENEMYRECHASE) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Wait hold up, someone needs rechasing. GET THEM!"); + } + else if ((interruptConditions & COND_ENEMYNEAR) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone is too close, get them!"); + } + else if ((interruptConditions & COND_SAWENEMY) != 0) + { + float pos[3]; + target.GetAbsOrigin(pos); + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "I saw someone!"); + } + } + + SF2_BasePlayer alertTarget = actor.AlertTriggerTarget; + if ((interruptConditions & COND_ALERT_TRIGGER) != 0 && alertTarget.IsValid) + { + float pos[3]; + actor.GetAlertTriggerPosition(alertTarget, pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Someone made noise, let's go!"); + } + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0 || actor.QueueForAlertState) + { + float pos[3]; + actor.GetAlertTriggerPositionEx(pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + actor.QueueForAlertState = false; + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "We got a sound hint, let's go!"); + } + + if ((interruptConditions & COND_DEBUG) != 0) + { + float pos[3]; + actor.GetForceWanderPosition(pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnWander[difficulty]), "An admin told me to go here!"); + } + + if (actor.FollowedCompanionAlert || actor.AlertWithBoss) + { + float pos[3]; + actor.GetForceWanderPosition(pos); + if (actor.AlertWithBoss) + { + actor.AlertWithBoss = false; + } + if (!IsNullVector(pos)) + { + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnWander[difficulty]), "One of my mates found something!"); + } + } + + if (data.SmellData.Enabled[difficulty] && actor.SmellCooldown <= gameTime && actor.SmellPlayerList != null) + { + if (actor.SmellPlayerList.Length >= data.SmellData.PlayerCount[difficulty]) + { + actor.State = STATE_ALERT; + path.Invalidate(); + return action.ChangeTo(SF2_ChaserSmellAction(), "That smelly smell, it is, smelly."); + } + } + + bool isAbleToWander = data.CanWander[difficulty]; + bool canWalk = true; + if (controller.HasAttribute(SF2Attribute_BlockWalkSpeedUnderDifficulty)) + { + int value = RoundToNearest(controller.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)); + if (difficulty < value) + { + isAbleToWander = false; + canWalk = false; + } + } + + if (actor.DebugShouldGoToPos && canWalk) + { + float debugPos[3]; + actor.GetForceWanderPosition(debugPos); + path.Invalidate(); + float min = data.WanderTimeMin[difficulty]; + float max = data.WanderTimeMax[difficulty]; + + action.NextWanderTime = gameTime + GetRandomFloat(min, max); + path.ComputeToPos(bot, debugPos); + actor.DebugShouldGoToPos = false; + } + + if ((SF_SpecialRound(SPECIALROUND_BEACON) || g_RenevantBeaconEffect) && !actor.WasInBacon) + { + SF2_BasePlayer player = actor.GetClosestPlayer(); + if (player.IsValid) + { + float pos[3]; + player.GetAbsOrigin(pos); + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + actor.WasInBacon = true; + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "What is this smell of bacon?"); + } + } + + if (isAbleToWander) + { + if (gameTime >= action.NextWanderTime && GetRandomFloat(0.0, 1.0) <= 0.25) + { + float min = data.WanderTimeMin[difficulty]; + float max = data.WanderTimeMax[difficulty]; + + action.NextWanderTime = gameTime + GetRandomFloat(min, max); + + float rangeMin = data.WanderRangeMin[difficulty]; + float rangeMax = data.WanderRangeMax[difficulty]; + float range = GetRandomFloat(rangeMin, rangeMax); + + CNavArea area = actor.GetLastKnownArea(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, range); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) + { + continue; + } + if (collector.Get(i).GetCostSoFar() < rangeMin) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + if (validAreaCount > 0) + { + float wanderPos[3]; + CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); + + if (wanderArea != NULL_AREA) + { + wanderArea.GetCenter(wanderPos); + + path.ComputeToPos(bot, wanderPos); + } + } + + delete collector; + delete areaArray; + } + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + if (actor.IsAttemptingToMove) + { + action.NextTurnTime = -1.0; + } + else + { + if (data.IdleData.TurnEnabled[difficulty]) + { + if (!action.InitialState) + { + float lookPos[3]; + action.GetLookPosition(lookPos); + loco.FaceTowards(lookPos); + } + if (action.NextTurnTime <= 0.0) + { + action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + } + } + } + + if (data.IdleData.TurnEnabled[difficulty] && action.NextTurnTime > 0.0 && action.NextTurnTime <= gameTime) + { + float myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + myAng[1] += GetRandomFloat(-data.IdleData.TurnAngle[difficulty], data.IdleData.TurnAngle[difficulty]); + float lookAt[3]; + lookAt[0] = 50.0; + VectorTransform(lookAt, myPos, myAng, lookAt); + action.SetLookPosition(lookAt); + action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + action.InitialState = false; + } + + if (gameTime >= actor.NextVoiceTime) + { + actor.PerformVoice(SF2BossSound_Idle); + } + + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp new file mode 100644 index 00000000..bf7dca70 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -0,0 +1,565 @@ +#pragma semicolon 1 +// The purpose of this action is to be the master action + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserMainAction < NextBotAction +{ + public SF2_ChaserMainAction() + { + return view_as(g_Factory.Create()); + } + + public static NextBotActionFactory GetFactory() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Main"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetEventCallback(EventResponderType_OnInjured, OnInjured); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.SetEventCallback(EventResponderType_OnContact, OnContact); + g_Factory.SetEventCallback(EventResponderType_OnKilled, OnKilled); + g_Factory.SetEventCallback(EventResponderType_OnLeaveGround, OnLeaveGround); + g_Factory.SetEventCallback(EventResponderType_OnCommandString, OnCommandString); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_LastStuckTime") + .DefineVectorField("m_LastPos") + .DefineVectorField("m_UnstuckPos") + .EndDataMapDesc(); + } + return g_Factory; + } + + property float LastStuckTime + { + public get() + { + return this.GetDataFloat("m_LastStuckTime"); + } + + public set(float value) + { + this.SetDataFloat("m_LastStuckTime", value); + } + } + + public void GetLastPos(float pos[3]) + { + this.GetDataVector("m_LastPos", pos); + } + + public void SetLastPos(const float pos[3]) + { + this.SetDataVector("m_LastPos", pos); + } + + public void GetUnstuckPos(float pos[3]) + { + this.GetDataVector("m_UnstuckPos", pos); + } + + public void SetUnstuckPos(const float pos[3]) + { + this.SetDataVector("m_UnstuckPos", pos); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + return SF2_ChaserSpawnAction(); +} + +static void OnStart(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + float pos[3]; + actor.MyNextBotPointer().GetLocomotionInterface().GetFeet(pos); + action.SetLastPos(pos); + action.LastStuckTime = GetGameTime(); +} + +static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid() && (controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return action.Done("I'm a faker"); + } + + if (!controller.IsValid()) + { + return action.Continue(); + } + + if (actor.ShouldDespawn) + { + return action.ChangeTo(SF2_ChaserDespawnAction()); + } + + float gameTime = GetGameTime(); + + int difficulty = controller.Difficulty; + + actor.FollowOtherBossAlert(); + + actor.FollowOtherBossChase(); + + if (actor.State == STATE_ATTACK) + { + actor.DoAttackMiscConditions(actor.GetAttackName()); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (data.StunData.FlashlightStun[difficulty] && actor.CanBeStunned() && actor.CanTakeDamage() && actor.FlashlightTick < gameTime && + !IsNightVisionEnabled()) + { + bool inFlashlight = false; + float customDamage = 1.0; + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + int attacker = -1; + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!IsTargetValidForSlender(actor, player, attackEliminated)) + { + continue; + } + + if (!player.UsingFlashlight) + { + continue; + } + + float hisPos[3], myPos[3], myEyeAng[3], hisEyeAng[3], requiredAng[3]; + player.GetAbsOrigin(hisPos); + actor.GetAbsOrigin(myPos); + + if (GetVectorDistance(myPos, hisPos, true) > Pow(SF2_FLASHLIGHT_LENGTH, 2.0)) + { + continue; + } + + actor.GetAbsAngles(myEyeAng); + player.GetEyeAngles(hisEyeAng); + SubtractVectors(hisPos, myPos, requiredAng); + GetVectorAngles(requiredAng, requiredAng); + + if (FloatAbs(AngleDiff(myEyeAng[1], requiredAng[1])) > 70.0 || FloatAbs(AngleDiff(hisEyeAng[1], requiredAng[1])) < 130.0 || FloatAbs(AngleDiff(requiredAng[0], hisEyeAng[0])) > 30.0) + { + continue; + } + + if (!actor.IsLOSClearFromTarget(player)) + { + continue; + } + + Action fwdAction; + Call_StartForward(g_OnBossPreFlashlightDamageFwd); + Call_PushCell(actor); + Call_PushCell(player); + Call_Finish(fwdAction); + if (fwdAction == Plugin_Stop) + { + continue; + } + + inFlashlight = true; + if (!IsClassConfigsValid()) + { + if (player.Class == TFClass_Engineer) + { + customDamage = 2.0; + } + } + else + { + customDamage = g_ClassFlashlightDamageMultiplier[view_as(player.Class)]; + } + attacker = i; + break; + } + + if (inFlashlight) + { + actor.StunHealth -= data.StunData.FlashlightStunDamage[difficulty] * customDamage; + actor.FlashlightTick = gameTime + 0.1; + + Event event = CreateEvent("npc_hurt"); + if (event) + { + event.SetInt("entindex", actor.index); + event.SetInt("health", actor.GetProp(Prop_Data, "m_iHealth")); + event.SetInt("damageamount", RoundToFloor(data.StunData.FlashlightStunDamage[difficulty] * customDamage)); + event.SetBool("crit", false); + + if (IsValidClient(attacker)) + { + event.SetInt("attacker_player", GetClientUserId(attacker)); + event.SetInt("weaponid", 0); + } + else + { + event.SetInt("attacker_player", 0); + event.SetInt("weaponid", 0); + } + + event.Fire(); + } + + if (actor.StunHealth <= 0.0) + { + return action.SuspendFor(SF2_ChaserStunnedAction(CBaseEntity(attacker)), "I was stunned by a flashlight"); + } + } + } + + if (originalData.InstantKillRadius > 0.0) + { + if (gameTime >= actor.LastKillTime && !actor.IsKillingSomeone) + { + float worldSpace[3], myPos[3]; + actor.WorldSpaceCenter(worldSpace); + actor.GetAbsOrigin(myPos); + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (view_as(controller).GetProfileData().IsPvEBoss) + { + attackEliminated = true; + } + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!IsTargetValidForSlender(actor, client, attackEliminated)) + { + continue; + } + + if (client.IsInDeathCam) + { + continue; + } + + if (actor.MyNextBotPointer().GetRangeSquaredTo(client.index) > Pow(originalData.InstantKillRadius, 2.0) || + !client.CanSeeSlender(controller.Index, false, _, !attackEliminated)) + { + continue; + } + + actor.LastKillTime = gameTime + originalData.InstantKillCooldown[difficulty]; + client.StartDeathCam(controller.Index, myPos); + actor.CheckTauntKill(SF2_BasePlayer(client.index)); + } + } + } + + if (actor.IsKillingSomeone) + { + return action.SuspendFor(SF2_DeathCamAction()); + } + + actor.ProcessTraps(); + + #if defined DEBUG + if (actor.IsAttemptingToMove && controller.Path.IsValid()) + { + PathFollower path = controller.Path; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || (g_PlayerDebugFlags[player.index] & DEBUG_BOSS_PATH) == 0) + { + continue; + } + + Segment init, end; + init = path.FirstSegment(); + while (init != NULL_PATH_SEGMENT) + { + end = path.NextSegment(init); + if (end == NULL_PATH_SEGMENT) + { + break; + } + + float from[3], to[3]; + int color[4] = { 25, 94, 255, 255 }; + init.GetPos(from); + end.GetPos(to); + TE_SetupBeamPoints(from, + to, + g_ShockwaveBeam, + g_ShockwaveHalo, + 0, + 30, + 0.1, + 5.0, + 5.0, + 5, + 0.0, + color, + 1); + + TE_SendToClient(player.index); + + init = end; + } + } + } + #endif + + if (!actor.IsAttacking && (controller.Flags & SFF_ATTACKPROPS) != 0) + { + float myPos[3], propPos[3]; + actor.GetAbsOrigin(myPos); + for (int i = 0; i < g_BreakableProps.Length; i++) + { + CBaseEntity prop = CBaseEntity(EntRefToEntIndex(g_BreakableProps.Get(i))); + if (!prop.IsValid()) + { + continue; + } + + prop.GetAbsOrigin(propPos); + if (GetVectorSquareMagnitude(myPos, propPos) > Pow(1500.0, 2.0)) + { + continue; + } + + if (!actor.IsLOSClearFromTarget(prop)) + { + continue; + } + + NextBotAction attackAction = actor.GetAttackAction(prop, true); + if (attackAction != NULL_ACTION) + { + actor.EndCloak(); + return action.SuspendFor(attackAction, "Fuck you prop."); + } + } + } + + UnstuckCheck(action, actor); + + return action.Continue(); +} + +static int OnResume(SF2_ChaserMainAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + action.LastStuckTime = GetGameTime() + 0.75; + if (!actor.IsInChaseInitial) + { + actor.UpdateMovementAnimation(); + } + if (actor.WasStunned) + { + actor.WasStunned = false; + } + return action.Continue(); +} + +static int OnInjured(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damageType) +{ + if (actor.CanBeStunned() && IsValidClient(attacker.index) && actor.CanTakeDamage(attacker, inflictor, damage)) + { + actor.StunHealth -= damage; + if (actor.StunHealth <= 0.0) + { + if (actor.GetProp(Prop_Data, "m_takedamage") == DAMAGE_EVENTS_ONLY) // Stun health only + { + return action.TrySuspendFor(SF2_ChaserStunnedAction(attacker), RESULT_IMPORTANT, "I was stunned by someone"); + } + else + { + if (!actor.IsRaging) + { + return action.TrySuspendFor(SF2_ChaserStunnedAction(attacker), RESULT_IMPORTANT, "I was stunned by someone"); + } + } + } + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int search = actor.RageIndex + 1; + if ((data.BoxingBoss || view_as(controller).GetProfileData().IsPvEBoss) && + actor.CanTakeDamage(attacker, inflictor, damage) && data.Rages != null && search < data.Rages.Length && !actor.IsRaging) + { + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(search, rageInfo, sizeof(rageInfo)); + float maxHealth = actor.MaxHealth; + float health = float(actor.GetProp(Prop_Data, "m_iHealth")); + if (!data.DeathData.Enabled[controller.Difficulty]) + { + maxHealth = actor.MaxStunHealth; + health = actor.StunHealth; + } + if (health <= maxHealth * rageInfo.PercentageThreshold) + { + return action.TrySuspendFor(SF2_ChaserRageAction(), RESULT_IMPORTANT, "I need to rage!"); + } + } + + return action.TryContinue(); +} + +static void OnAnimationEvent(SF2_ChaserMainAction action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + actor.CastAnimEvent(event); + actor.CastAnimEvent(event, true); +} + +static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = actor.Controller; + PathFollower path = controller.Path; + float gameTime = GetGameTime(); + + float goalPos[3], myPos[3]; + if (path.IsValid()) + { + path.GetEndPosition(goalPos); + } + actor.GetAbsOrigin(myPos); + + if (!path.IsValid() || !actor.IsAttemptingToMove || loco.GetDesiredSpeed() <= 0.0 || actor.MovementType == SF2NPCMoveType_Attack || (path.IsValid() && GetVectorSquareMagnitude(myPos, goalPos) <= Pow(16.0, 2.0))) + { + action.LastStuckTime = gameTime; + return; + } + + float lastPos[3]; + action.GetLastPos(lastPos); + + if (bot.IsRangeLessThanEx(lastPos, 0.13) || loco.GetGroundSpeed() <= 0.1) + { + if (action.LastStuckTime > gameTime - 0.75) + { + return; + } + float destination[3]; + if (!NPCFindUnstuckPosition(actor, lastPos, destination)) + { + float mins[3], maxs[3]; + actor.GetPropVector(Prop_Send, "m_vecMins", mins); + actor.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + float range = ((maxs[0] + maxs[1]) / 2.0) + (FloatAbs(mins[0] + mins[1]) / 2.0); + if (GetVectorDistance(myPos, goalPos, true) > Pow(range + 16.0, 2.0)) + { + controller.UnSpawn(true); + return; + } + } + action.LastStuckTime = gameTime + 0.75; + actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); + } + else + { + loco.GetFeet(lastPos); + action.SetLastPos(lastPos); + action.LastStuckTime += 0.03; + if (action.LastStuckTime > gameTime) + { + action.LastStuckTime = gameTime; + } + } +} + +static void OnContact(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity other, Address result) +{ + if (IsValidClient(other.index)) + { + return; + } + + char classname[64]; + other.GetClassname(classname, sizeof(classname)); + if (strcmp(classname, "obj_dispenser", false) == 0 || + strcmp(classname, "obj_teleporter", false) == 0 || strcmp(classname, "func_breakable", false) == 0) + { + SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); + } + + // Destroy mini sentires, not non-mini sentries + if (strcmp(classname, "obj_sentrygun", false) == 0 && other.GetProp(Prop_Send, "m_bMiniBuilding")) + { + SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); + } + + if (strcmp(classname, "prop_physics") == 0 || strcmp(classname, "prop_dynamic") == 0) + { + if (other.GetProp(Prop_Data, "m_iHealth") > 0) + { + SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); + } + } +} + +static int OnKilled(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damageType, CBaseEntity weapon, const float damageForce[3], const float damagePosition[3], int damageCustom) +{ + actor.State = STATE_DEATH; + Call_StartForward(g_OnBossKilledFwd); + Call_PushCell(actor.Controller); + Call_PushCell(attacker.index); + Call_Finish(); + return action.TryChangeTo(SF2_ChaserDeathAction(attacker), RESULT_CRITICAL); +} + +static void OnLeaveGround(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity ground) +{ + if (!actor.IsJumping) + { + return; + } + + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + NextBotGroundLocomotion loco = npc.GetLocomotion(); + float velocity[3]; + actor.MyNextBotPointer().GetLocomotionInterface().GetVelocity(velocity); + velocity[2] += loco.GetStepHeight() * 4.0; + loco.SetVelocity(velocity); + actor.IsJumping = false; +} + +static int OnCommandString(SF2_ChaserMainAction action, SF2_ChaserEntity actor, const char[] command) +{ + if (StrContains(command, "debug attack ") == 0 && !actor.IsAttacking) + { + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + char attack[128]; + strcopy(attack, sizeof(attack), command); + ReplaceString(attack, sizeof(attack), "debug attack ", ""); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attack, attackData); + return action.TrySuspendFor(SF2_ChaserAttackAction(attack, attackData.Index, attackData.Duration[difficulty] + GetGameTime()), RESULT_IMPORTANT); + } + + if (strcmp(command, "suspend for action") == 0) + { + NextBotAction nbAction = actor.GetSuspendForAction(); + if (nbAction != NULL_ACTION) + { + return action.TrySuspendFor(nbAction, RESULT_CRITICAL); + } + } + + return action.TryContinue(); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp new file mode 100644 index 00000000..558ff469 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp @@ -0,0 +1,141 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserRageAction < NextBotAction +{ + public SF2_ChaserRageAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Rage"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_HasRaged") + .DefineBoolField("m_IsHealing") + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } + + property bool HasRaged + { + public get() + { + return this.GetData("m_HasRaged") != 0; + } + + public set(bool value) + { + this.SetData("m_HasRaged", value); + } + } + + property bool IsHealing + { + public get() + { + return this.GetData("m_IsHealing") != 0; + } + + public set(bool value) + { + this.SetData("m_IsHealing", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserRageAction action, SF2_ChaserEntity actor) +{ + actor.RageIndex++; + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + data = controller.GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = controller.Difficulty; + actor.IsAttemptingToMove = false; + loco.Stop(); + controller.Path.Invalidate(); + + actor.IsRaging = true; + + actor.OverrideInvincible = rageInfo.Invincible; + + actor.PerformVoiceEx(_, _, rageInfo.StartSounds, true); + + actor.EndCloak(); + + if (actor.State == STATE_ATTACK) + { + actor.CancelAttack = true; + } + + if (rageInfo.IsHealing) + { + SetHealthBarColor(true); + action.IsHealing = true; + actor.IsGoingToHeal = true; + return SF2_ChaserFleeToHealAction(); + } + + if (rageInfo.Animations.GetAnimation("start", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int Update(SF2_ChaserRageAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + if (!action.IsHealing) + { + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float pos[3]; + target.GetAbsOrigin(pos); + actor.MyNextBotPointer().GetLocomotionInterface().FaceTowards(pos); + } + } + return action.Continue(); + } + + return action.Done("I am no longer raging"); +} + +static void OnEnd(SF2_ChaserRageAction action, SF2_ChaserEntity actor) +{ + actor.IsRaging = false; + actor.OverrideInvincible = false; + actor.IsGoingToHeal = false; + if (actor.Controller.IsValid()) + { + SF2ChaserBossProfileData data; + data = actor.Controller.GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + if (rageInfo.IncreaseDifficulty) + { + actor.Controller.Difficulty += 1; + if (actor.Controller.Difficulty > Difficulty_Apollyon) + { + actor.Controller.Difficulty = Difficulty_Apollyon; + } + } + actor.UpdateMovementAnimation(); + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp new file mode 100644 index 00000000..1501333f --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp @@ -0,0 +1,191 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserSmellAction < NextBotAction +{ + public SF2_ChaserSmellAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Smell"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } + + public static bool IsPossible(SF2_ChaserEntity actor) + { + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + if (!data.AnimationData.HasAnimationSection(g_SlenderAnimationsList[SF2BossAnimation_Smell])) + { + return false; + } + + return true; + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserSmellAction action, SF2_ChaserEntity actor) +{ + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + actor.IsAttemptingToMove = false; + + actor.PerformVoice(SF2BossSound_Smell); + + if (data.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Smell], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Smell], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.IsAllowedToDespawn = false; + + return action.Continue(); +} + +static int Update(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + PathFollower path = controller.Path; + g_SlenderTimeUntilKill[controller.Index] = GetGameTime() + NPCGetIdleLifetime(controller.Index, difficulty); + if (action.ActiveChild == NULL_ACTION) + { + float bestDistance = Pow(data.SmellData.SmellRange[difficulty], 2.0); + bool found = false; + float pos[3], myPos[3], bestPos[3]; + actor.GetAbsOrigin(myPos); + + CBaseEntity target = CBaseEntity(-1); + for (int i = 0; i < actor.SmellPlayerList.Length; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(actor.SmellPlayerList.Get(i)); + if (!player.IsValid) + { + continue; + } + player.GetAbsOrigin(pos); + float distance = GetVectorSquareMagnitude(pos, myPos); + if (distance <= bestDistance) + { + found = true; + bestDistance = distance; + bestPos = pos; + target = CBaseEntity(player.index); + } + } + if (found) + { + if (!data.SmellData.ShouldChase[difficulty]) + { + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(bestPos, data.AlertData.RunOnSuspect[difficulty]), "I smelled someone, what is it?"); + } + else + { + actor.Target = target; + SF2_BasePlayer player = SF2_BasePlayer(target.index); + player.SetForceChaseState(controller, true); + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "I smelled someone and I need to chase them, GO GO GO!"); + } + } + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "Sniffers found nothing, go back to idle."); + } + + int interruptConditions = actor.InterruptConditions; + + ILocomotion loco = actor.MyNextBotPointer().GetLocomotionInterface(); + loco.Stop(); + + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + if ((interruptConditions & COND_SAWENEMY) != 0) + { + float pos[3]; + target.GetAbsOrigin(pos); + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Abort abort, I saw someone!"); + } + } + + SF2_BasePlayer alertTarget = actor.AlertTriggerTarget; + if ((interruptConditions & COND_ALERT_TRIGGER) != 0 && alertTarget.IsValid) + { + float pos[3]; + actor.GetAlertTriggerPosition(alertTarget, pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Stop! I heard someone!"); + } + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0) + { + float pos[3]; + actor.GetAlertTriggerPositionEx(pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Stop! I got a sound hint!"); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_ChaserSmellAction action, SF2_ChaserEntity actor) +{ + actor.IsAllowedToDespawn = true; + actor.HasSmelled = true; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/spawn.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/spawn.sp new file mode 100644 index 00000000..48bcc11d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/spawn.sp @@ -0,0 +1,55 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserSpawnAction < NextBotAction +{ + public SF2_ChaserSpawnAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Spawn"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.State = STATE_IDLE; + + float rate = 1.0, duration = 0.0, cycle = 0.0; + if (actor.GetOverrideSpawnAnimation()[0] != '\0') + { + int sequence = LookupProfileAnimation(actor.index, actor.GetOverrideSpawnAnimation()); + if (sequence != -1) + { + rate = actor.GetPropFloat(Prop_Data, "m_OverrideSpawnAnimationRate"); + duration = actor.GetPropFloat(Prop_Data, "m_OverrideSpawnAnimationDuration"); + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + } + + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Spawn], rate, duration, cycle); + if (sequence != -1) + { + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + + return action.ChangeTo(SF2_ChaserIdleAction()); +} + +static int OnResume(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + return action.ChangeTo(SF2_ChaserIdleAction()); +} + +static void OnEnd(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor) +{ + Call_StartForward(g_OnBossFinishSpawningFwd); + Call_PushCell(actor.Controller); + Call_Finish(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp new file mode 100644 index 00000000..27ff1590 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp @@ -0,0 +1,236 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserStunnedAction < NextBotAction +{ + public SF2_ChaserStunnedAction(CBaseEntity attacker = view_as(-1)) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Stunned"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_Attacker") + .DefineIntField("m_OldState") + .EndDataMapDesc(); + } + SF2_ChaserStunnedAction action = view_as(g_Factory.Create()); + + action.Attacker = attacker; + return action; + } + + property CBaseEntity Attacker + { + public get() + { + return CBaseEntity(EntRefToEntIndex(this.GetDataEnt("m_Attacker"))); + } + + public set(CBaseEntity value) + { + this.SetDataEnt("m_Attacker", EnsureEntRef(value.index)); + } + } + + property int OldState + { + public get() + { + return this.GetData("m_OldState"); + } + + public set(int value) + { + this.SetData("m_OldState", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) +{ + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + data = baseController.GetProfileData(); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + actor.IsAttemptingToMove = false; + loco.Stop(); + + if (actor.State == STATE_ATTACK) + { + actor.CancelAttack = true; + } + + action.OldState = actor.State; + actor.PreviousState = actor.State; + + actor.IsStunned = true; + + actor.PerformVoice(SF2BossSound_Stun); + + actor.EndCloak(); + + if (data.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Stun], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Stun], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + int difficulty = controller.Difficulty; + data = controller.GetProfileData(); + + if (data.StunData.StartEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.StunData.StartEffects, controller.Index, false, pos, ang, _, _, true); + } + + if (data.KeyDrop) + { + if (SF_IsBoxingMap() && data.StunData.Disappear[difficulty] && data.BoxingBoss && !g_SlenderBoxingBossIsKilled[controller.Index] && !view_as(controller).GetProfileData().IsPvEBoss) + { + g_SlenderBoxingBossKilled++; + if ((g_SlenderBoxingBossKilled == g_SlenderBoxingBossCount)) + { + NPC_DropKey(controller.Index, data.KeyModel, data.KeyTrigger); + } + g_SlenderBoxingBossIsKilled[controller.Index] = true; + } + else + { + NPC_DropKey(controller.Index, data.KeyModel, data.KeyTrigger); + } + } + + actor.DropItem(); + + actor.RemoveAllGestures(); + CBaseNPC_RemoveAllLayers(actor.index); + + if (actor.State == STATE_CHASE) + { + actor.CurrentChaseDuration += data.ChaseDurationAddOnStun[difficulty]; + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + + actor.State = STATE_STUN; + + if (controller.HasAttribute(SF2Attribute_AddStunHealthOnStun)) + { + actor.MaxStunHealth += controller.GetAttributeValue(SF2Attribute_AddStunHealthOnStun); + } + else + { + actor.MaxStunHealth += data.StunData.AddHealthPerStun[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_AddSpeedOnStun)) + { + controller.SetAddSpeed(controller.GetAttributeValue(SF2Attribute_AddSpeedOnStun)); + } + else + { + controller.SetAddSpeed(data.StunData.AddSpeedPerStun[difficulty]); + } + + if (controller.HasAttribute(SF2Attribute_AddAccelerationOnStun)) + { + controller.SetAddAcceleration(controller.GetAttributeValue(SF2Attribute_AddAccelerationOnStun)); + } + else + { + controller.SetAddAcceleration(data.StunData.AddAccelerationPerStun[difficulty]); + } + + actor.GroundSpeedOverride = true; + + Call_StartForward(g_OnBossStunnedFwd); + Call_PushCell(actor.Controller.Index); + Call_PushCell(action.Attacker.index); + Call_Finish(); + + return action.Continue(); +} + +static int Update(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + return action.Continue(); + } + + if (actor.Controller.GetProfileData().StunData.Disappear[actor.Controller.Difficulty]) + { + actor.Controller.UnSpawn(true); + } + return action.Done("I am no longer stunned"); +} + +static int OnSuspend(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + + if (!controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + if (data.StunData.EndEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.StunData.EndEffects, controller.Index, false, pos, ang, _, _, true); + } + + actor.IsStunned = false; + actor.GroundSpeedOverride = false; + actor.StunHealth = actor.MaxStunHealth; + + actor.NextStunTime = GetGameTime() + data.StunData.Cooldown[actor.Controller.Difficulty]; + + actor.UpdateMovementAnimation(); + + actor.WasStunned = true; + actor.State = action.OldState; + + if (actor.State == STATE_IDLE && action.Attacker.IsValid()) + { + float pos[3]; + action.Attacker.GetAbsOrigin(pos); + actor.SetAlertTriggerPositionEx(pos); + actor.QueueForAlertState = true; + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/tauntkill.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/tauntkill.sp new file mode 100644 index 00000000..f4a2b7aa --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/tauntkill.sp @@ -0,0 +1,74 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserTauntKillAction < NextBotAction +{ + public SF2_ChaserTauntKillAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_TauntKill"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + } + return view_as(g_Factory.Create()); + } + + public static bool IsPossible(SF2_ChaserEntity actor) + { + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + if (!data.AnimationData.HasAnimationSection(g_SlenderAnimationsList[SF2BossAnimation_TauntKill])) + { + return false; + } + + return true; + } +} + + +static int OnStart(SF2_ChaserTauntKillAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + float rate = 1.0, duration = 0.0, cycle = 0.0; + + actor.PerformVoice(SF2BossSound_TauntKill); + actor.KillTarget = CBaseEntity(-1); + + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_TauntKill], rate, duration, cycle); + if (sequence != -1) + { + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + } + + return action.Done("I'm done taunting someone after I killed them."); +} + +static int OnResume(SF2_ChaserTauntKillAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + } + + return action.Done("I'm done taunting someone after I killed them."); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp new file mode 100644 index 00000000..927ddd4b --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -0,0 +1,5784 @@ +#pragma semicolon 1 + +#include "actions/mainlayer.sp" +#include "actions/idle.sp" +#include "actions/alert.sp" +#include "actions/smell.sp" +#include "actions/chase.sp" +#include "actions/chaseinitial.sp" +#include "actions/attacklayer.sp" +#include "actions/chaselayer.sp" +#include "actions/attack.sp" +#include "actions/stun.sp" +#include "actions/death.sp" +#include "actions/spawn.sp" +#include "actions/tauntkill.sp" +#include "actions/beatbox_freeze.sp" +#include "actions/rage.sp" +#include "actions/flee_to_heal.sp" +#include "actions/despawn.sp" + +#include "postures/rage_phase.sp" +#include "postures/running_away.sp" +#include "postures/within_bounds.sp" +#include "postures/within_range.sp" +#include "postures/on_look.sp" +#include "postures/on_alert.sp" +#include "postures/on_cloak.sp" + +static CEntityFactory g_Factory; + +methodmap SF2_ChaserEntity < SF2_BaseBoss +{ + public SF2_ChaserEntity(int entity) + { + return view_as(entity); + } + + public bool IsValid() + { + if (!CBaseCombatCharacter(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_npc_boss_chaser", OnCreate, OnRemove); + g_Factory.DeriveFromFactory(SF2_BaseBoss.GetFactory()); + g_Factory.SetInitialActionFactory(SF2_ChaserMainAction.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_IsAllowedToDespawn") + .DefineStringField("m_OverrideSpawnAnimation") + .DefineFloatField("m_OverrideSpawnAnimationRate") + .DefineFloatField("m_OverrideSpawnAnimationDuration") + .DefineFloatField("m_Override") + .DefineBoolField("m_IsStunned") + .DefineBoolField("m_WasStunned") + .DefineFloatField("m_StunHealth") + .DefineFloatField("m_NextStunTime") + .DefineFloatField("m_MaxStunHealth") + .DefineFloatField("m_MaxHealth") + .DefineBoolField("m_IsInChaseInitial") + .DefineStringField("m_Posture") + .DefineStringField("m_DefaultPosture") + .DefineBoolField("m_IsAttacking") + .DefineBoolField("m_CancelAttack") + .DefineStringField("m_AttackName") + .DefineIntField("m_AttackIndex") + .DefineIntField("m_NextAttackTime") + .DefineFloatField("m_AttackRunDuration") + .DefineFloatField("m_AttackRunDelay") + .DefineFloatField("m_NextVoiceTime") + .DefineIntField("m_MovementType") + .DefineIntField("m_AlertTriggerCount", MAXTF2PLAYERS) + .DefineVectorField("m_AlertTriggerPosition", MAXTF2PLAYERS) + .DefineEntityField("m_AlertTriggerTarget") + .DefineFloatField("m_AlertSoundTriggerCooldown", MAXTF2PLAYERS) + .DefineVectorField("m_AlertTriggerPositionEx") + .DefineFloatField("m_AlertChangePositionCooldown") + .DefineIntField("m_AutoChaseCount", MAXTF2PLAYERS) + .DefineFloatField("m_AutoChaseAddCooldown", MAXTF2PLAYERS) + .DefineFloatField("m_AutoChaseCooldown", MAXTF2PLAYERS) + .DefineBoolField("m_MaintainTarget") + .DefineBoolField("m_AlertWithBoss") + .DefineBoolField("m_FollowedCompanionAlert") + .DefineFloatField("m_FollowCooldownAlert") + .DefineBoolField("m_FollowedCompanionChase") + .DefineFloatField("m_FollowCooldownChase") + .DefineIntField("m_SmellPlayerList") + .DefineFloatField("m_SmellCooldown") + .DefineBoolField("m_HasSmelled") + .DefineFloatField("m_ProjectileCooldown") + .DefineBoolField("m_IsReloadingProjectiles") + .DefineIntField("m_ProjectileAmmo") + .DefineFloatField("m_ProjectileReloadTime") + .DefineEntityField("m_KillTarget") + .DefineBoolField("m_HasCloaked") + .DefineFloatField("m_CloakTime") + .DefineBoolField("m_PlayCloakAnimation") + .DefineIntField("m_RageIndex") + .DefineBoolField("m_IsRaging") + .DefineBoolField("m_IsGoingToHeal") + .DefineBoolField("m_IsRunningAway") + .DefineBoolField("m_IsSelfHealing") + .DefineBoolField("m_OverrideInvincible") + .DefineFloatField("m_TrapCooldown") + .DefineBoolField("m_WasInBacon") + .DefineFloatField("m_FlashlightTick") + .DefineBoolField("m_ShouldDespawn") + .DefineBoolField("m_GroundSpeedOverride") + .DefineBoolField("m_QueueForAlertState") + .EndDataMapDesc(); + g_Factory.Install(); + + AddNormalSoundHook(Hook_ChaserSoundHook); + + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerTakeDamagePostPFwd.AddFunction(null, OnPlayerTakeDamagePost); + g_OnPlayerDeathPrePFwd.AddFunction(null, OnPlayerDeathPre); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + + SF2_ChaserAttackAction.Initialize(); + InitializePostureRagePhase(); + InitializePostureRunningAway(); + InitializePostureWithinBounds(); + InitializePostureWithinRange(); + InitializePostureOnLook(); + InitializePostureOnAlert(); + InitializePostureOnCloak(); + } + + property SF2NPC_Chaser Controller + { + public get() + { + return view_as(view_as(this).Controller); + } + + public set(SF2NPC_Chaser controller) + { + view_as(this).Controller = controller; + } + } + + property bool IsAllowedToDespawn + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAllowedToDespawn") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsAllowedToDespawn", value); + } + } + + public char[] GetOverrideSpawnAnimation() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_OverrideSpawnAnimation", buffer, sizeof(buffer)); + return buffer; + } + + public void SetOverrideSpawnAnimation(const char[] name, float duration = 0.0, float rate = 1.0) + { + this.SetPropString(Prop_Data, "m_OverrideSpawnAnimation", name); + this.SetPropFloat(Prop_Data, "m_OverrideSpawnAnimationRate", rate); + this.SetPropFloat(Prop_Data, "m_OverrideSpawnAnimationDuration", duration); + } + + public bool CanBeStunned() + { + if (SF_IsSlaughterRunMap() && !view_as(this.Controller).GetProfileData().IsPvEBoss) + { + return false; + } + + if (!this.Controller.IsValid()) + { + return false; + } + + if (!this.Controller.GetProfileData().StunData.Enabled[this.Controller.Difficulty]) + { + return false; + } + + if (this.IsStunned) + { + return false; + } + + if (this.MaxStunHealth <= 0.0) + { + return false; + } + + if (GetGameTime() < this.NextStunTime) + { + return false; + } + + if (this.IsKillingSomeone) + { + return false; + } + + if (this.OverrideInvincible) + { + return false; + } + + return true; + } + + public bool CanTakeDamage(CBaseEntity attacker = view_as(-1), CBaseEntity inflictor = view_as(-1), float damage = 0.0) + { + if (SF_IsSlaughterRunMap() && !view_as(this.Controller).GetProfileData().IsPvEBoss) + { + return false; + } + + if (!this.IsAttacking) + { + return true; + } + + if (this.OverrideInvincible) + { + return false; + } + + if (attacker.IsValid() || inflictor.IsValid()) + { + Action action; + Call_StartForward(g_OnBossPreTakeDamageFwd); + Call_PushCell(this); + Call_PushCell(attacker); + Call_PushCell(inflictor); + Call_PushCell(damage); + Call_Finish(action); + if (action == Plugin_Stop) + { + return false; + } + } + + int difficulty = this.Controller.Difficulty; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(this.GetAttackName(), attackData); + + return !attackData.ImmuneToDamage[difficulty]; + } + + property bool IsStunned + { + public get() + { + return this.GetProp(Prop_Data, "m_IsStunned") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsStunned", value); + } + } + + property bool WasStunned + { + public get() + { + return this.GetProp(Prop_Data, "m_WasStunned") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_WasStunned", value); + } + } + + property float StunHealth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_StunHealth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_StunHealth", value); + } + } + + property float MaxStunHealth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_MaxStunHealth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_MaxStunHealth", value); + } + } + + property float MaxHealth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_MaxHealth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_MaxHealth", value); + } + } + + property float NextStunTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_NextStunTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_NextStunTime", value); + } + } + + property bool IsInChaseInitial + { + public get() + { + return this.GetProp(Prop_Data, "m_IsInChaseInitial") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsInChaseInitial", value); + } + } + + public int GetPosture(char[] buffer, int bufferSize) + { + return this.GetPropString(Prop_Data, "m_Posture", buffer, bufferSize); + } + + public void SetPosture(const char[] posture) + { + if (posture[0] == '\0') + { + return; + } + + char currentPosture[64]; + this.GetPosture(currentPosture, sizeof(currentPosture)); + + this.SetPropString(Prop_Data, "m_Posture", posture); + + if (this.Controller.IsValid() && strcmp(currentPosture, posture) != 0 && this.IsAttemptingToMove) + { + this.UpdateMovementAnimation(); + } + } + + public int GetDefaultPosture(char[] buffer, int bufferSize) + { + return this.GetPropString(Prop_Data, "m_DefaultPosture", buffer, bufferSize); + } + + public void SetDefaultPosture(const char[] posture) + { + if (posture[0] == '\0') + { + return; + } + + if (strcmp(posture, SF2_PROFILE_CHASER_DEFAULT_POSTURE) != 0) + { + if (!this.Controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfilePostureInfo postureInfo; + + if (!data.GetPosture(posture, postureInfo)) + { + return; + } + } + + this.SetPropString(Prop_Data, "m_DefaultPosture", posture); + } + + public bool CanUpdatePosture() + { + if (this.State == STATE_DEATH) + { + return false; + } + + if (this.IsAttacking) + { + return false; + } + + if (this.IsStunned) + { + return false; + } + + if (this.IsInChaseInitial) + { + return false; + } + + return true; + } + + property bool IsAttacking + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAttacking") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsAttacking", value); + } + } + + property bool CancelAttack + { + public get() + { + return this.GetProp(Prop_Data, "m_CancelAttack") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_CancelAttack", value); + } + } + + public char[] GetAttackName() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_AttackName", buffer, sizeof(buffer)); + return buffer; + } + + public void SetAttackName(const char[] name) + { + this.SetPropString(Prop_Data, "m_AttackName", name); + } + + property int AttackIndex + { + public get() + { + return this.GetProp(Prop_Data, "m_AttackIndex"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_AttackIndex", value); + } + } + + property StringMap NextAttackTime + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_NextAttackTime")); + } + + public set(StringMap value) + { + this.SetProp(Prop_Data, "m_NextAttackTime", value); + } + } + + public float GetNextAttackTime(const char[] attackName) + { + if (this.NextAttackTime == null) + { + return -1.0; + } + + float value = -1.0; + if (!this.NextAttackTime.GetValue(attackName, value)) + { + return -1.0; + } + + return value; + } + + public void SetNextAttackTime(const char[] attackName, float time) + { + if (this.NextAttackTime == null) + { + return; + } + + this.NextAttackTime.SetValue(attackName, time); + } + + property float AttackRunDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AttackRunDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AttackRunDuration", value); + } + } + + property float AttackRunDelay + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AttackRunDelay"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AttackRunDelay", value); + } + } + + property float NextVoiceTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_NextVoiceTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_NextVoiceTime", value); + } + } + + property bool IsAttemptingToMove + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAttemptingToMove") != 0; + } + + public set(bool value) + { + bool oldValue = this.IsAttemptingToMove; + this.SetProp(Prop_Data, "m_IsAttemptingToMove", value); + + if (oldValue != value) + { + this.UpdateMovementAnimation(); + } + } + } + + property SF2NPCMoveTypes MovementType + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_MovementType")); + } + + public set(SF2NPCMoveTypes value) + { + SF2NPCMoveTypes oldType = this.MovementType; + this.SetProp(Prop_Data, "m_MovementType", value); + + if (oldType != value && this.IsAttemptingToMove) + { + this.UpdateMovementAnimation(); + } + } + } + + public int GetAlertTriggerCount(SF2_BasePlayer player) + { + return this.GetProp(Prop_Data, "m_AlertTriggerCount", _, player.index); + } + + public void SetAlertTriggerCount(SF2_BasePlayer player, int value) + { + this.SetProp(Prop_Data, "m_AlertTriggerCount", value, _, player.index); + } + + public void GetAlertTriggerPosition(SF2_BasePlayer player, float buffer[3]) + { + this.GetPropVector(Prop_Data, "m_AlertTriggerPosition", buffer, player.index); + } + + public void SetAlertTriggerPosition(SF2_BasePlayer player, float buffer[3]) + { + this.SetPropVector(Prop_Data, "m_AlertTriggerPosition", buffer, player.index); + } + + public float GetAlertSoundTriggerCooldown(SF2_BasePlayer player) + { + return this.GetPropFloat(Prop_Data, "m_AlertSoundTriggerCooldown", player.index); + } + + public void SetAlertSoundTriggerCooldown(SF2_BasePlayer player, float value) + { + this.SetPropFloat(Prop_Data, "m_AlertSoundTriggerCooldown", value, player.index); + } + + public void GetAlertTriggerPositionEx(float buffer[3]) + { + this.GetPropVector(Prop_Data, "m_AlertTriggerPositionEx", buffer); + } + + public void SetAlertTriggerPositionEx(float buffer[3]) + { + this.SetPropVector(Prop_Data, "m_AlertTriggerPositionEx", buffer); + } + + property float AlertChangePositionCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AlertChangePositionCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AlertChangePositionCooldown", value); + } + } + + public int GetAutoChaseCount(SF2_BasePlayer player) + { + return this.GetProp(Prop_Data, "m_AutoChaseCount", _, player.index); + } + + public void SetAutoChaseCount(SF2_BasePlayer player, int value) + { + this.SetProp(Prop_Data, "m_AutoChaseCount", value, _, player.index); + } + + public float GetAutoChaseAddCooldown(SF2_BasePlayer player) + { + return this.GetPropFloat(Prop_Data, "m_AutoChaseAddCooldown", player.index); + } + + public void SetAutoChaseAddCooldown(SF2_BasePlayer player, float value) + { + this.SetPropFloat(Prop_Data, "m_AutoChaseAddCooldown", value, player.index); + } + + public float GetAutoChaseCooldown(SF2_BasePlayer player) + { + return this.GetPropFloat(Prop_Data, "m_AutoChaseCooldown", player.index); + } + + public void SetAutoChaseCooldown(SF2_BasePlayer player, float value) + { + this.SetPropFloat(Prop_Data, "m_AutoChaseCooldown", value, player.index); + } + + property SF2_BasePlayer AlertTriggerTarget + { + public get() + { + return SF2_BasePlayer(this.GetPropEnt(Prop_Data, "m_AlertTriggerTarget")); + } + + public set(SF2_BasePlayer value) + { + this.SetPropEnt(Prop_Data, "m_AlertTriggerTarget", value.index); + } + } + + property bool MaintainTarget + { + public get() + { + return this.GetProp(Prop_Data, "m_MaintainTarget") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_MaintainTarget", value); + } + } + + property bool AlertWithBoss + { + public get() + { + return this.GetProp(Prop_Data, "m_AlertWithBoss") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_AlertWithBoss", value); + } + } + + property bool FollowedCompanionAlert + { + public get() + { + return this.GetProp(Prop_Data, "m_FollowedCompanionAlert") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_FollowedCompanionAlert", value); + } + } + + property float FollowCooldownAlert + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_FollowCooldownAlert"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_FollowCooldownAlert", value); + } + } + + property bool FollowedCompanionChase + { + public get() + { + return this.GetProp(Prop_Data, "m_FollowedCompanionChase") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_FollowedCompanionChase", value); + } + } + + property float FollowCooldownChase + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_FollowCooldownChase"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_FollowCooldownChase", value); + } + } + + property ArrayList SmellPlayerList + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_SmellPlayerList")); + } + + public set(ArrayList value) + { + this.SetProp(Prop_Data, "m_SmellPlayerList", value); + } + } + + property float SmellCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SmellCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SmellCooldown", value); + } + } + + property bool HasSmelled + { + public get() + { + return this.GetProp(Prop_Data, "m_HasSmelled") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_HasSmelled", value); + } + } + + property float ProjectileCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_ProjectileCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_ProjectileCooldown", value); + } + } + + property bool IsReloadingProjectiles + { + public get() + { + return this.GetProp(Prop_Data, "m_IsReloadingProjectiles") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsReloadingProjectiles", value); + } + } + + property int ProjectileAmmo + { + public get() + { + return this.GetProp(Prop_Data, "m_ProjectileAmmo"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_ProjectileAmmo", value); + } + } + + property float ProjectileReloadTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_ProjectileReloadTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_ProjectileReloadTime", value); + } + } + + property CBaseEntity KillTarget + { + public get() + { + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_KillTarget"))); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_KillTarget", EnsureEntRef(entity.index)); + } + } + + property bool HasCloaked + { + public get() + { + return this.GetProp(Prop_Data, "m_HasCloaked") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_HasCloaked", value); + } + } + + property float CloakTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_CloakTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_CloakTime", value); + } + } + + property bool PlayCloakAnimation + { + public get() + { + return this.GetProp(Prop_Data, "m_PlayCloakAnimation") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_PlayCloakAnimation", value); + } + } + + property int RageIndex + { + public get() + { + return this.GetProp(Prop_Data, "m_RageIndex"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_RageIndex", value); + } + } + + property bool IsRaging + { + public get() + { + return this.GetProp(Prop_Data, "m_IsRaging") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsRaging", value); + } + } + + property bool IsGoingToHeal + { + public get() + { + return this.GetProp(Prop_Data, "m_IsGoingToHeal") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsGoingToHeal", value); + } + } + + property bool IsRunningAway + { + public get() + { + return this.GetProp(Prop_Data, "m_IsRunningAway") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsRunningAway", value); + } + } + + property bool IsSelfHealing + { + public get() + { + return this.GetProp(Prop_Data, "m_IsSelfHealing") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsSelfHealing", value); + } + } + + property bool OverrideInvincible + { + public get() + { + return this.GetProp(Prop_Data, "m_OverrideInvincible") != 0; + } + + public set(bool value) + { + bool old = this.GetProp(Prop_Data, "m_OverrideInvincible") != 0; + + this.SetProp(Prop_Data, "m_OverrideInvincible", value); + + if (old != value) + { + if (value) + { + this.SetProp(Prop_Data, "m_takedamage", DAMAGE_EVENTS_ONLY); + } + else + { + this.SetProp(Prop_Data, "m_takedamage", DAMAGE_YES); + } + } + } + } + + property float TrapCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_TrapCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_TrapCooldown", value); + } + } + + property bool WasInBacon + { + public get() + { + return this.GetProp(Prop_Data, "m_WasInBacon") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_WasInBacon", value); + } + } + + property float FlashlightTick + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_FlashlightTick"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_FlashlightTick", value); + } + } + + property bool ShouldDespawn + { + public get() + { + return this.GetProp(Prop_Data, "m_ShouldDespawn") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_ShouldDespawn", value); + } + } + + property bool GroundSpeedOverride + { + public get() + { + return this.GetProp(Prop_Data, "m_GroundSpeedOverride") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_GroundSpeedOverride", value); + } + } + + property bool QueueForAlertState + { + public get() + { + return this.GetProp(Prop_Data, "m_QueueForAlertState") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_QueueForAlertState", value); + } + } + + public SF2_BasePlayer GetClosestPlayer() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return SF2_INVALID_PLAYER; + } + int difficulty = controller.Difficulty; + float myPos[3]; + this.GetAbsOrigin(myPos); + SF2BossProfileData data; + data = view_as(controller).GetProfileData(); + + SF2_BasePlayer closest = SF2_INVALID_PLAYER; + float range = Pow(data.SearchRange[difficulty], 2.0); + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.IsInGhostMode || client.IsProxy || !client.IsAlive || client.IsEliminated) + { + continue; + } + + float clientPos[3]; + client.GetAbsOrigin(clientPos); + + float distance = GetVectorSquareMagnitude(myPos, clientPos); + + if (distance < range) + { + closest = client; + range = distance; + } + } + + if (closest.IsValid) + { + return closest; + } + + return SF2_INVALID_PLAYER; + } + + public void UpdateMovementAnimation() + { + if (this.IsSelfHealing) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; + } + + char animation[64]; + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_Idle]); + + if (this.IsAttemptingToMove) + { + switch (this.MovementType) + { + case SF2NPCMoveType_Walk: + { + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_Walk]); + } + case SF2NPCMoveType_Run: + { + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_Run]); + } + case SF2NPCMoveType_Attack: + { + return; + } + } + } + if (this.IsKillingSomeone) + { + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_DeathCam]); + } + + char posture[64]; + this.GetPosture(posture, sizeof(posture)); + this.ResetProfileAnimation(animation, _, _, _, posture); + } + + public bool PerformVoice(int soundType = -1, const char[] attackName = "") + { + SF2BossProfileSoundInfo soundInfo; + return this.PerformVoiceEx(soundType, attackName, soundInfo); + } + + public bool PerformVoiceEx(int soundType = -1, const char[] attackName = "", SF2BossProfileSoundInfo soundInfo, bool isSet = false) + { + if (soundType == -1 && !isSet) + { + return false; + } + + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + ArrayList soundList; + if (!isSet) + { + switch (soundType) + { + case SF2BossSound_Idle: + { + soundInfo = data.IdleSounds; + } + case SF2BossSound_Alert: + { + soundInfo = data.AlertSounds; + } + case SF2BossSound_Chasing: + { + soundInfo = data.ChasingSounds; + } + case SF2BossSound_ChaseInitial: + { + soundInfo = data.ChaseInitialSounds; + } + case SF2BossSound_Stun: + { + soundInfo = data.StunnedSounds; + } + case SF2BossSound_Death: + { + soundInfo = data.DeathSounds; + } + case SF2BossSound_Attack: + { + return this.CheckNestedSoundSection(data.AttackSounds, attackName, soundInfo, soundList); + } + case SF2BossSound_AttackKilled: + { + soundInfo = data.AttackKilledSounds; + } + case SF2BossSound_TauntKill: + { + soundInfo = data.TauntKillSounds; + } + case SF2BossSound_Smell: + { + soundInfo = data.SmellSounds; + } + case SF2BossSound_AttackBegin: + { + return this.CheckNestedSoundSection(data.AttackBeginSounds, attackName, soundInfo, soundList); + } + case SF2BossSound_AttackEnd: + { + return this.CheckNestedSoundSection(data.AttackEndSounds, attackName, soundInfo, soundList); + } + case SF2BossSound_SelfHeal: + { + soundInfo = data.SelfHealSounds; + } + case SF2BossSound_RageAll: + { + soundInfo = data.RageSounds1; + } + case SF2BossSound_RageTwo: + { + soundInfo = data.RageSounds2; + } + case SF2BossSound_RageThree: + { + soundInfo = data.RageSounds3; + } + case SF2BossSound_Despawn: + { + soundInfo = data.DespawnSounds; + } + } + } + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + return this.PerformVoiceCooldown(soundInfo, soundList); + } + return false; + } + + public bool CheckNestedSoundSection(ArrayList list, const char[] attackName, SF2BossProfileSoundInfo soundInfo, ArrayList soundList) + { + if (this.SearchSoundsWithSectionName(list, attackName, soundInfo)) + { + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + return this.PerformVoiceCooldown(soundInfo, soundList); + } + } + return false; + } + + public bool PerformVoiceCooldown(SF2BossProfileSoundInfo soundInfo, ArrayList soundList) + { + char buffer[PLATFORM_MAX_PATH]; + float gameTime = GetGameTime(); + soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); + if (buffer[0] != '\0') + { + float threshold = GetRandomFloat(0.0, 1.0); + float cooldown = GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); + if (threshold > soundInfo.Chance) + { + this.NextVoiceTime = gameTime + cooldown; + return false; + } + soundInfo.EmitSound(_, this.index, _, _, SF_SpecialRound(SPECIALROUND_TINYBOSSES) ? 25 : 0); + this.NextVoiceTime = gameTime + cooldown; + return true; + } + return false; + } + + public void CastAnimEvent(int event, bool footstep = false) + { + SF2BossProfileData data; + data = view_as(this.Controller).GetProfileData(); + + ArrayList arraySounds = data.EventSounds; + ArrayList arrayEvents = data.EventIndexes; + + if (footstep) + { + arraySounds = data.FootstepEventSounds; + arrayEvents = data.FootstepEventIndexes; + } + + if (arraySounds == null || arrayEvents == null) + { + return; + } + + int foundIndex = arrayEvents.FindValue(event); + if (foundIndex == -1) + { + return; + } + + SF2BossProfileSoundInfo soundInfo; + arraySounds.GetArray(foundIndex, soundInfo, sizeof(soundInfo)); + + if (soundInfo.Paths == null) + { + return; + } + + soundInfo.EmitSound(_, this.index); + SF2ChaserBossProfileData chaserData; + chaserData = this.Controller.GetProfileData(); + if (footstep && chaserData.EarthquakeFootsteps) + { + float myPos[3]; + this.GetAbsOrigin(myPos); + + UTIL_ScreenShake(myPos, chaserData.EarthquakeFootstepAmplitude, + chaserData.EarthquakeFootstepFrequency, chaserData.EarthquakeFootstepDuration, + chaserData.EarthquakeFootstepRadius, 0, chaserData.EarthquakeFootstepAirShake); + } + } + + public void CastFootstep() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileSoundInfo info; + info = data.FootstepSounds; + + info.EmitSound(_, this.index); + this.LegacyFootstepTime = this.LegacyFootstepInterval + GetGameTime(); + } + + public void UpdateAlertTriggerCount(SF2_BasePlayer player, int amount) + { + if (this.State != STATE_IDLE && this.State != STATE_ALERT) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; + } + + int difficulty = this.Controller.Difficulty; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + int threshold = data.SoundCountToAlert[difficulty]; + + if (threshold <= 0) + { + return; + } + + if (amount > threshold) + { + amount = threshold; + } + + this.SetAlertTriggerCount(player, this.GetAlertTriggerCount(player) + amount); + + float gameTime = GetGameTime(); + if (this.GetAlertTriggerCount(player) >= threshold && this.AlertChangePositionCooldown < gameTime) + { + this.InterruptConditions |= COND_ALERT_TRIGGER; + + float pos[3]; + player.GetAbsOrigin(pos); + + this.SetAlertTriggerPosition(player, pos); + this.AlertTriggerTarget = player; + this.AlertChangePositionCooldown = gameTime + 0.3; + } + } + + public void UpdateAlertTriggerCountEx(float pos[3]) + { + if (this.State != STATE_IDLE && this.State != STATE_ALERT) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; + } + + this.InterruptConditions |= COND_ALERT_TRIGGER_POS; + + this.SetAlertTriggerPositionEx(pos); + } + + public void UpdateAutoChaseCount(SF2_BasePlayer player, int amount) + { + if (this.State != STATE_ALERT) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; + } + + if (this.GetAutoChaseCooldown(player) > GetGameTime()) + { + return; + } + + int difficulty = this.Controller.Difficulty; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + + if (data.AutoChaseCount[difficulty] <= 0) + { + return; + } + + this.SetAutoChaseCount(player, amount); + + if (this.GetAutoChaseCount(player) >= data.AutoChaseCount[difficulty]) + { + player.SetForceChaseState(this.Controller, true); + SetTargetMarkState(this.Controller, player, true); + this.SetAutoChaseCount(player, 0); + } + } + + public NextBotAction GetAttackAction(CBaseEntity target, bool props = false) + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return NULL_ACTION; + } + INextBot bot = this.MyNextBotPointer(); + bool canAttack = !this.IsAttacking && !this.IsKillingSomeone; + if (canAttack && !bot.GetLocomotionInterface().IsOnGround()) + { + canAttack = false; + } + + if (!canAttack) + { + return NULL_ACTION; + } + + char class[64]; + target.GetClassname(class, sizeof(class)); + float gameTime = GetGameTime(); + char attackName[64], posture[64]; + this.GetPosture(posture, sizeof(posture)); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + ArrayList arrayAttacks = null; + SF2ChaserBossProfileAttackData attackData; + for (int index = 0; index < data.Attacks.Length; index++) + { + data.GetAttackFromIndex(index, attackData); + + if (attackData.Type == SF2BossAttackType_Invalid) + { + continue; + } + + if (gameTime < this.GetNextAttackTime(attackData.Name)) + { + continue; + } + + if (props && !attackData.CanUseAgainstProps) + { + continue; + } + + if (attackData.DontInterruptChaseInitial[difficulty] && this.IsInChaseInitial) + { + continue; + } + + if (!attackData.CanBeUsedWithPosture(posture)) + { + continue; + } + + if (!attackData.StartThroughWalls[difficulty] && (this.InterruptConditions & COND_ENEMYVISIBLE) == 0) + { + continue; + } + + if (attackData.Type == SF2BossAttackType_Custom) + { + Action result = Plugin_Continue; + Call_StartForward(g_OnChaserGetCustomAttackPossibleStatePFwd); + Call_PushCell(this); + Call_PushString(attackData.Name); + Call_PushCell(target); + Call_Finish(result); + + if (result != Plugin_Continue) + { + if (arrayAttacks == null) + { + arrayAttacks = new ArrayList(); + } + arrayAttacks.Push(index); + } + continue; + } + + if (difficulty < attackData.UseOnDifficulty) + { + continue; + } + + if (difficulty >= attackData.BlockOnDifficulty) + { + continue; + } + + if (!props) + { + // Why must tanks use a different prop data, WHY? + float health = strcmp(class, "tank_boss", false) != 0 ? float(target.GetProp(Prop_Send, "m_iHealth")) : float(target.GetProp(Prop_Data, "m_iHealth")); + if (attackData.UseOnHealth != -1.0 && health < attackData.UseOnHealth) + { + continue; + } + if (attackData.BlockOnHealth != -1.0 && health >= attackData.BlockOnHealth) + { + continue; + } + } + + attackName = attackData.Name; + + if (arrayAttacks == null) + { + arrayAttacks = new ArrayList(); + } + arrayAttacks.Push(index); + } + + if (arrayAttacks == null || arrayAttacks.Length == 0) + { + if (arrayAttacks != null) + { + delete arrayAttacks; + } + return NULL_ACTION; + } + + Call_StartForward(g_OnBossPreAttackFwd); + Call_PushCell(controller.Index); + Call_PushCell(arrayAttacks); + Call_Finish(); + + float eyePos[3], targetPos[3], direction[3], eyeAng[3]; + this.GetAbsAngles(eyeAng); + controller.GetEyePosition(eyePos); + target.GetAbsOrigin(targetPos); + SubtractVectors(targetPos, eyePos, direction); + GetVectorAngles(direction, direction); + direction[2] = 180.0; + + float distance = bot.GetRangeSquaredTo(target.index); + float fov = FloatAbs(AngleDiff(direction[1], eyeAng[1])); + + arrayAttacks.Sort(Sort_Random, Sort_Integer); + for (int i = 0; i < arrayAttacks.Length; i++) + { + data.GetAttackFromIndex(arrayAttacks.Get(i), attackData); + if (attackData.Type != SF2BossAttackType_Custom) + { + float beginRange = attackData.BeginRange[difficulty]; + float beginFOV = attackData.BeginFOV[difficulty]; + if (distance > Pow(beginRange, 2.0)) + { + continue; + } + if (fov > beginFOV) + { + continue; + } + } + + attackName = attackData.Name; + delete arrayAttacks; + + return SF2_ChaserAttackAction(attackName, attackData.Index, attackData.Duration[difficulty] + gameTime); + } + + delete arrayAttacks; + return NULL_ACTION; + } + + public NextBotAction GetCustomAttack(const char[] attackName, CBaseEntity target) + { + NextBotAction nbAction = NULL_ACTION; + Action action = Plugin_Continue; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + Call_StartForward(g_OnBossGetCustomAttackActionFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_PushArrayEx(attackData, sizeof(SF2ChaserBossProfileAttackData), SM_PARAM_COPYBACK); + Call_PushCell(target); + Call_PushCellRef(nbAction); + Call_Finish(action); + + if (action != Plugin_Continue) + { + return nbAction; + } + + return NULL_ACTION; + } + + public bool IsCustomAttackPossible(const char[] attackName, CBaseEntity target) + { + Action action = Plugin_Continue; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + Call_StartForward(g_OnIsBossCustomAttackPossibleFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_PushArrayEx(attackData, sizeof(SF2ChaserBossProfileAttackData), SM_PARAM_COPYBACK); + Call_PushCell(target); + Call_Finish(action); + + return action == Plugin_Continue; + } + + public NextBotAction IsAttackTransitionPossible(const char[] attackName, bool end = false, float& duration = 0.0) + { + SF2NPC_BaseNPC baseController = view_as(this.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + char section[32]; + if (!end) + { + strcopy(section, sizeof(section), g_SlenderAnimationsList[SF2BossAnimation_AttackBegin]); + } + else + { + strcopy(section, sizeof(section), g_SlenderAnimationsList[SF2BossAnimation_AttackEnd]); + } + if (!data.AnimationData.HasAnimationSection(section)) + { + return NULL_ACTION; + } + + char animName[64]; + float rate = 1.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + if (!data.AnimationData.GetAnimation(section, difficulty, animName, sizeof(animName), rate, duration, cycle, _, _, _, attackName)) + { + return NULL_ACTION; + } + + int sequence = this.SelectProfileAnimation(section, rate, duration, cycle, _, _, _, attackName); + if (sequence == -1) + { + return NULL_ACTION; + } + + if (duration <= 0.0) + { + duration = this.SequenceDuration(sequence) / rate; + duration *= (1.0 - cycle); + } + + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + + public void DoAttackMiscConditions(const char[] attackName) + { + INextBot bot = this.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + CBaseEntity target = this.Target; + int difficulty = controller.Difficulty; + + if (target.IsValid()) + { + bool aimAtTarget = false; + + if ((controller.HasAttribute(SF2Attribute_AlwaysLookAtTarget) || controller.HasAttribute(SF2Attribute_AlwaysLookAtTargetWhileAttacking)) + && !attackData.IgnoreAlwaysLooking[difficulty]) + { + aimAtTarget = true; + } + + if (attackData.Type == SF2BossAttackType_LaserBeam || attackData.Type == SF2BossAttackType_Projectile || attackData.Type == SF2BossAttackType_Ranged) + { + aimAtTarget = true; + } + + if (aimAtTarget) + { + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); + } + } + } + + public NextBotAction GetSuspendForAction() + { + NextBotAction nbAction = NULL_ACTION; + Action action = Plugin_Continue; + Call_StartForward(g_OnChaserBossGetSuspendActionFwd); + Call_PushCell(this); + Call_PushCellRef(nbAction); + Call_Finish(action); + + if (action != Plugin_Continue) + { + return nbAction; + } + + return NULL_ACTION; + } + + public void DoShockwave(const char[] attackName) + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + SF2ChaserBossProfileShockwaveData shockwaveData; + shockwaveData = attackData.Shockwave; + + if (!shockwaveData.Enabled) + { + return; + } + + int difficulty = controller.Difficulty; + float radius = shockwaveData.Radius[difficulty]; + if (radius <= 0.0) + { + return; + } + + if (shockwaveData.Effects != null) + { + SlenderSpawnEffects(shockwaveData.Effects, controller.Index, false); + } + + float force = shockwaveData.Force[difficulty]; + + float myWorldSpace[3], myPos[3]; + this.WorldSpaceCenter(myWorldSpace); + this.GetAbsOrigin(myPos); + bool eliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + eliminated = true; + } + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + + if (!IsTargetValidForSlender(this, player, eliminated)) + { + continue; + } + + float clientWorldSpace[3]; + player.WorldSpaceCenter(clientWorldSpace); + + if (GetVectorDistance(myWorldSpace, clientWorldSpace, true) > Pow(radius, 2.0)) + { + continue; + } + + TR_TraceRayFilter(myWorldSpace, clientWorldSpace, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, RayType_EndPoint, + TraceRayDontHitAnyEntity, this.index); + + if (!TR_DidHit() || TR_GetEntityIndex() == player.index) + { + float targetPos[3]; + player.GetAbsOrigin(targetPos); + if (targetPos[2] > myPos[2] + shockwaveData.Height[difficulty]) + { + continue; + } + + if (force > 0.0) + { + float velocity[3]; + MakeVectorFromPoints(myPos, targetPos, velocity); + GetVectorAngles(velocity, velocity); + velocity[0] += 30.0; + GetAngleVectors(velocity, velocity, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(velocity, velocity); + ScaleVector(velocity, force); + velocity[2] += force; + player.SetPropVector(Prop_Data, "m_vecBaseVelocity", velocity); + } + + float amount = shockwaveData.BatteryDrainPercent[difficulty]; + if (!IsInfiniteFlashlightEnabled() && amount > 0.0) + { + player.FlashlightBatteryLife -= amount; + } + + float sprintAmount = shockwaveData.StaminaDrainPercent[difficulty]; + if (!IsInfiniteSprintEnabled() && sprintAmount > 0.0) + { + player.Stamina -= sprintAmount; + } + + shockwaveData.ApplyDamageEffects(player, difficulty, SF2_ChaserBossEntity(this.index)); + } + } + } + + public bool SearchSoundsWithSectionName(ArrayList base, const char[] name, SF2BossProfileSoundInfo output) + { + if (base == null || base.Length <= 0) + { + return false; + } + if (base.Length == 1) + { + base.GetArray(0, output, sizeof(output)); + if (output.SectionName[0] == '\0') + { + return true; + } + } + for (int i = 0; i < base.Length; i++) + { + base.GetArray(i, output, sizeof(output)); + if (strcmp(output.SectionName, name) == 0) + { + return true; + } + } + return false; + } + + public void DoAlwaysLookAt(CBaseEntity target) + { + if (!target.IsValid()) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && !player.IsAlive) + { + return; + } + + INextBot bot = this.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + bool tooClose = this.GetIsVisible(player) && bot.IsRangeLessThan(target.index, 16.0) && this.State != STATE_STUN && this.State != STATE_DEATH && !this.IsKillingSomeone; + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + if (!tooClose && !controller.HasAttribute(SF2Attribute_AlwaysLookAtTarget) && !controller.HasAttribute(SF2Attribute_AlwaysLookAtTargetWhileChasing)) + { + return; + } + + if ((this.InterruptConditions & COND_ENEMYVISIBLE) == 0) + { + return; + } + + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); + } + + public void RegisterProjectiles(bool &isFake = false) + { + if (g_RestartSessionEnabled) + { + return; + } + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (!data.ProjectilesEnabled) + { + return; + } + + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + + if (this.ProjectileCooldown > gameTime) + { + return; + } + + if ((this.InterruptConditions & COND_ENEMYVISIBLE) == 0) + { + return; + } + + if (!data.ProjectileClips) + { + if (controller.Flags & SFF_FAKE) + { + isFake = true; + return; + } + + this.ShootProjectile(); + } + else + { + if (this.ProjectileAmmo > 0) + { + this.ProjectileAmmo--; + this.ShootProjectile(); + } + else + { + if (!this.IsReloadingProjectiles) + { + this.ProjectileReloadTime = gameTime + data.ProjectileReloadTime[difficulty]; + this.IsReloadingProjectiles = true; + } + if (this.ProjectileReloadTime <= gameTime && this.IsReloadingProjectiles) + { + this.ProjectileAmmo = data.ProjectileClipSize[difficulty]; + this.IsReloadingProjectiles = false; + } + } + } + } + + public void ShootProjectile() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + float targetPos[3], myPos[3], myAng[3]; + this.GetAbsOrigin(myPos); + this.GetAbsAngles(myAng); + this.Target.WorldSpaceCenter(targetPos); + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int randomPosMin = data.ProjectileRandomPosMin; + int randomPosMax = data.ProjectileRandomPosMax; + ArrayList array = data.ProjectilePosOffsets; + + bool attackWaiters = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + attackWaiters = true; + } + + float effectPos[3]; + + if (randomPosMin == randomPosMax) + { + array.GetArray(0, effectPos); + } + else + { + array.GetArray(GetRandomInt(randomPosMin, randomPosMax), effectPos); + } + + VectorTransform(effectPos, myPos, myAng, effectPos); + + for (int i = 0; i < data.ProjectileCount[difficulty]; i++) + { + float direction[3], angle[3]; + SubtractVectors(targetPos, effectPos, direction); + float deviation = data.ProjectileDeviation[difficulty]; + + if (deviation != 0) + { + direction[0] += GetRandomFloat(-deviation, deviation); + direction[1] += GetRandomFloat(-deviation, deviation); + direction[2] += GetRandomFloat(-deviation, deviation); + } + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + switch (data.ProjectileType) + { + case SF2BossProjectileType_Fireball: + { + SF2_ProjectileFireball.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.FireballExplodeSound, data.FireballTrail, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.FireballShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Iceball: + { + SF2_ProjectileIceball.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.FireballExplodeSound, data.IceballTrail, data.IceballSlowDuration[difficulty], data.IceballSlowPercent[difficulty], data.IceballSlowSound, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.FireballShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Rocket: + { + SF2_ProjectileRocket.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.CriticalProjectiles, data.RocketTrail, data.RocketExplodeParticle, data.RocketExplodeSound, data.RocketModel, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.RocketShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_SentryRocket: + { + SF2_ProjectileSentryRocket.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.CriticalProjectiles, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.SentryRocketShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Mangler: + { + SF2_ProjectileCowMangler.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.ManglerShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Grenade: + { + SF2_ProjectileGrenade.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.CriticalProjectiles, "pipebombtrail_blue", ROCKET_EXPLODE_PARTICLE, ROCKET_IMPACT, "models/weapons/w_models/w_grenade_grenadelauncher.mdl", attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.GrenadeShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Arrow: + { + SF2_ProjectileArrow.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.CriticalProjectiles, "pipebombtrail_blue", "weapons/fx/rics/arrow_impact_flesh2.wav", "models/weapons/w_models/w_arrow.mdl", attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.ArrowShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Baseball: + { + SF2_ProjectileBaseball.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.CriticalProjectiles, "models/weapons/w_models/w_baseball.mdl", attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.BaseballShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + } + } + + if (data.ShootGestures) + { + this.RemoveAllGestures(); + char gesture[64]; + strcopy(gesture, sizeof(gesture), data.ShootGestureName); + + int sequence = this.LookupSequence(gesture); + if (sequence != -1) + { + this.AddGestureSequence(sequence); + } + } + + this.ProjectileCooldown = GetRandomFloat(data.ProjectileCooldownMin[difficulty], data.ProjectileCooldownMax[difficulty]) + GetGameTime(); + } + + public void CheckTauntKill(SF2_BasePlayer player) + { + if (SF2_ChaserTauntKillAction.IsPossible(this) && player.IsValid) + { + this.KillTarget = player; + } + } + + public void ForceAlertOtherBosses() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + + if (!data.AlertOnAlertInfo.Enabled[difficulty]) + { + return; + } + + if (!data.AlertOnAlertInfo.Copies[difficulty] && !data.AlertOnAlertInfo.Companions[difficulty]) + { + return; + } + + float goalPos[3]; + if (controller.Path.IsValid()) + { + controller.Path.GetEndPosition(goalPos); + } + else + { + this.GetAbsOrigin(goalPos); + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (!otherChaser.IsValid()) + { + continue; + } + + if (otherChaser.State == STATE_ALERT || otherChaser.State == STATE_CHASE || otherChaser.State == STATE_STUN || otherChaser.State == STATE_DEATH || otherChaser.State == STATE_ATTACK) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnAlertInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnAlertInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnAlertInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnAlertInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + otherChaser.SetForceWanderPosition(goalPos); + otherChaser.AlertWithBoss = true; + } + } + + public void FollowOtherBossAlert() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + float gameTime = GetGameTime(); + + if (!data.AlertOnAlertInfo.Enabled[difficulty]) + { + return; + } + + if (!data.AlertOnAlertInfo.Copies[difficulty] && !data.AlertOnAlertInfo.Companions[difficulty]) + { + return; + } + + if (!data.AlertOnAlertInfo.Follow[difficulty] || this.FollowedCompanionAlert || gameTime < this.FollowCooldownAlert) + { + return; + } + + if (this.State == STATE_ALERT || this.State == STATE_CHASE || this.State == STATE_STUN || this.State == STATE_DEATH || this.State == STATE_ATTACK) + { + return; + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (otherChaser.State != STATE_ALERT) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + + if (!otherData.AlertOnAlertInfo.Copies[difficulty] && !otherData.AlertOnAlertInfo.Companions[difficulty]) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnAlertInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnAlertInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnAlertInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnAlertInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + float goalPos[3]; + if (!otherController.Path.IsValid()) + { + otherChaser.GetAbsOrigin(goalPos); + } + else + { + otherController.Path.GetEndPosition(goalPos); + } + this.SetForceWanderPosition(goalPos); + this.FollowedCompanionAlert = true; + otherChaser.FollowedCompanionAlert = true; + return; + } + } + + public void ForceChaseOtherBosses() + { + CBaseEntity target = this.Target; + if (!target.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + + if (!data.AlertOnChaseInfo.Enabled[difficulty]) + { + return; + } + + if (!data.AlertOnChaseInfo.Copies[difficulty] && !data.AlertOnChaseInfo.Companions[difficulty]) + { + return; + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (!otherChaser.IsValid()) + { + continue; + } + + if (otherChaser.State == STATE_CHASE || otherChaser.State == STATE_STUN || otherChaser.State == STATE_DEATH || otherChaser.State == STATE_ATTACK) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnChaseInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnChaseInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnChaseInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnChaseInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid) + { + player.SetForceChaseState(otherController, true); + } + } + } + + public void FollowOtherBossChase() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + float gameTime = GetGameTime(); + + if (!data.AlertOnChaseInfo.Enabled[difficulty] || !data.AlertOnChaseInfo.Follow[difficulty]) + { + return; + } + + if (!data.AlertOnChaseInfo.Copies[difficulty] && !data.AlertOnChaseInfo.Companions[difficulty]) + { + return; + } + + if (this.FollowedCompanionChase && this.State != STATE_CHASE && this.State != STATE_STUN && this.State != STATE_DEATH && this.State != STATE_ATTACK && gameTime >= this.FollowCooldownChase) + { + this.FollowedCompanionChase = false; + } + + if (this.FollowedCompanionChase) + { + return; + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (otherChaser.State != STATE_CHASE && otherChaser.State != STATE_STUN && otherChaser.State != STATE_ATTACK) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + + if (!otherData.AlertOnChaseInfo.Copies[difficulty] && !otherData.AlertOnChaseInfo.Companions[difficulty]) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnChaseInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnChaseInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnChaseInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnChaseInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + SF2_BasePlayer player = SF2_BasePlayer(otherChaser.Target.index); + if (player.IsValid) + { + player.SetForceChaseState(controller, true); + this.FollowedCompanionChase = true; + this.Target = otherChaser.Target; + return; + } + } + } + + public void ProcessCloak(CBaseEntity target) + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + if (!data.CloakData.Enabled[difficulty]) + { + return; + } + + if (this.IsInChaseInitial) + { + return; + } + + if (this.IsAttacking) + { + return; + } + + if (!target.IsValid()) + { + return; + } + + float targetPos[3], myPos[3]; + target.GetAbsOrigin(targetPos); + this.GetAbsOrigin(myPos); + if (this.HasCloaked && GetVectorSquareMagnitude(targetPos, myPos) <= Pow(data.CloakData.DecloakRange[difficulty], 2.0)) + { + this.EndCloak(); + return; + } + + if (!this.HasCloaked && GetVectorSquareMagnitude(targetPos, myPos) > Pow(data.CloakData.CloakRange[difficulty], 2.0)) + { + return; + } + + if (this.CloakTime < gameTime) + { + if (this.HasCloaked) + { + this.EndCloak(); + } + else + { + this.StartCloak(); + } + } + } + + public void StartCloak() + { + if (this.HasCloaked) + { + return; + } + + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.CloakData.Enabled[difficulty]) + { + return; + } + + this.SetRenderMode(view_as(data.CloakData.CloakRenderMode)); + this.SetRenderColor(data.CloakData.CloakRenderColor[0], data.CloakData.CloakRenderColor[1], data.CloakData.CloakRenderColor[2], data.CloakData.CloakRenderColor[3]); + this.HasCloaked = true; + this.CloakTime = GetGameTime() + data.CloakData.Duration[difficulty]; + float worldPos[3]; + this.WorldSpaceCenter(worldPos); + SlenderToggleParticleEffects(this.index); + SlenderSpawnEffects(data.CloakData.CloakEffects, controller.Index, false); + Call_StartForward(g_OnBossCloakedFwd); + Call_PushCell(controller.Index); + Call_Finish(); + } + + public void EndCloak() + { + if (!this.HasCloaked) + { + return; + } + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.CloakData.Enabled[difficulty]) + { + return; + } + + this.SetRenderMode(view_as(controller.GetRenderMode)); + this.SetRenderColor(controller.GetRenderColor(0), controller.GetRenderColor(1), controller.GetRenderColor(2), controller.GetRenderColor(3)); + this.HasCloaked = false; + this.CloakTime = GetGameTime() + data.CloakData.Cooldown[difficulty]; + float worldPos[3]; + this.WorldSpaceCenter(worldPos); + SlenderToggleParticleEffects(this.index, true); + SlenderSpawnEffects(data.CloakData.DecloakEffects, controller.Index, false); + Call_StartForward(g_OnBossDecloakedFwd); + Call_PushCell(controller.Index); + Call_Finish(); + } + + public void DropItem(bool death = false) + { + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + bool check = !death ? data.StunData.ItemDrop[difficulty] : data.DeathData.ItemDrop[difficulty]; + if (!check) + { + return; + } + + int type = !death ? data.StunData.ItemDropType[difficulty] : data.DeathData.ItemDropType[difficulty]; + char class[64]; + switch (type) + { + case 1: + { + class = "item_ammopack_small"; + } + + case 2: + { + class = "item_ammopack_medium"; + } + + case 3: + { + class = "item_ammopack_full"; + } + + case 4: + { + class = "item_healthkit_small"; + } + + case 5: + { + class = "item_healthkit_medium"; + } + + case 6: + { + class = "item_healthkit_full"; + } + + case 7: + { + class = "item_healthammokit"; + } + } + + float myPos[3]; + this.GetAbsOrigin(myPos); + CBaseEntity item = CBaseEntity(CreateEntityByName(class)); + item.KeyValue("OnPlayerTouch", "!self,Kill,,0,-1"); + item.Spawn(); + item.SetProp(Prop_Send, "m_iTeamNum", 0); + item.Teleport(myPos, NULL_VECTOR, NULL_VECTOR); + } + + public void ProcessTraps() + { + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.Traps[difficulty]) + { + return; + } + + if (this.State != STATE_IDLE && this.State != STATE_ALERT) + { + return; + } + + float gameTime = GetGameTime(); + if (this.TrapCooldown > gameTime) + { + return; + } + + float myPos[3], myAng[3]; + this.GetAbsOrigin(myPos); + this.GetAbsAngles(myAng); + Trap_SpawnTrap(myPos, myAng, controller); + this.TrapCooldown = gameTime + data.TrapCooldown[difficulty]; + } + + public static SF2_ChaserEntity Create(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(CreateEntityByName("sf2_npc_boss_chaser")); + if (!chaser.IsValid()) + { + return SF2_ChaserEntity(-1); + } + if (controller == SF2_INVALID_NPC) + { + return SF2_ChaserEntity(-1); + } + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + controller.GetProfile(profile, sizeof(profile)); + chaser.Controller = view_as(controller); + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(chaser.Controller).GetProfileData(); + + char buffer[PLATFORM_MAX_PATH]; + + int difficulty = g_DifficultyConVar.IntValue; + + GetSlenderModel(controller.Index, _, buffer, sizeof(buffer)); + chaser.SetModel(buffer); + chaser.SetRenderMode(view_as(g_SlenderRenderMode[controller.Index])); + chaser.SetRenderFx(view_as(g_SlenderRenderFX[controller.Index])); + chaser.SetRenderColor(g_SlenderRenderColor[controller.Index][0], g_SlenderRenderColor[controller.Index][1], + g_SlenderRenderColor[controller.Index][2], g_SlenderRenderColor[controller.Index][3]); + + chaser.SetDefaultPosture(SF2_PROFILE_CHASER_DEFAULT_POSTURE); + chaser.SetPosture(SF2_PROFILE_CHASER_DEFAULT_POSTURE); + + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + float scaleModel = controller.ModelScale * 0.5; + chaser.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); + } + else + { + chaser.SetPropFloat(Prop_Send, "m_flModelScale", controller.ModelScale); + } + + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + CBaseNPC_Locomotion locomotion = npc.GetLocomotion(); + + npc.flStepSize = 18.0; + npc.flGravity = 800.0; + npc.flDeathDropHeight = 99999.0; + npc.flJumpHeight = 512.0; + npc.flFrictionForward = 0.0; + npc.flFrictionSideways = 3.0; + + npc.flMaxYawRate = originalData.TurnRate; + + float addStunHealth = data.StunData.AddHealthPerPlayer[difficulty]; + float classAdd; + int count; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + + if (originalData.IsPvEBoss && !player.IsEliminated) + { + continue; + } + + if (!originalData.IsPvEBoss && (player.IsEliminated || player.HasEscaped)) + { + continue; + } + count++; + + switch (player.Class) + { + case TFClass_Scout: + { + classAdd += data.StunData.AddHealthPerScout[difficulty]; + } + + case TFClass_Soldier: + { + classAdd += data.StunData.AddHealthPerSoldier[difficulty]; + } + + case TFClass_Pyro: + { + classAdd += data.StunData.AddHealthPerPyro[difficulty]; + } + + case TFClass_DemoMan: + { + classAdd += data.StunData.AddHealthPerDemoman[difficulty]; + } + + case TFClass_Heavy: + { + classAdd += data.StunData.AddHealthPerHeavy[difficulty]; + } + + case TFClass_Engineer: + { + classAdd += data.StunData.AddHealthPerEngineer[difficulty]; + } + + case TFClass_Medic: + { + classAdd += data.StunData.AddHealthPerMedic[difficulty]; + } + + case TFClass_Sniper: + { + classAdd += data.StunData.AddHealthPerSniper[difficulty]; + } + + case TFClass_Spy: + { + classAdd += data.StunData.AddHealthPerSpy[difficulty]; + } + } + } + + addStunHealth *= float(count); + chaser.StunHealth = data.StunData.Health[controller.Difficulty] + addStunHealth + classAdd; + chaser.MaxStunHealth = chaser.StunHealth; + + locomotion.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); + locomotion.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); + + float pathingMins[3], pathingMaxs[3]; + + if (controller.RaidHitbox) + { + pathingMins = g_SlenderDetectMins[controller.Index]; + pathingMaxs = g_SlenderDetectMaxs[controller.Index]; + + chaser.SetPropVector(Prop_Send, "m_vecMins", g_SlenderDetectMins[controller.Index]); + chaser.SetPropVector(Prop_Send, "m_vecMaxs", g_SlenderDetectMaxs[controller.Index]); + + chaser.SetPropVector(Prop_Send, "m_vecMinsPreScaled", g_SlenderDetectMins[controller.Index]); + chaser.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", g_SlenderDetectMaxs[controller.Index]); + } + else + { + pathingMins = HULL_HUMAN_MINS; + pathingMaxs = HULL_HUMAN_MAXS; + + chaser.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); + chaser.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); + + chaser.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); + chaser.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); + } + + npc.SetBodyMins(pathingMins); + npc.SetBodyMaxs(pathingMaxs); + + if (SF_IsBoxingMap() || originalData.IsPvEBoss) + { + SetEntityCollisionGroup(chaser.index, COLLISION_GROUP_DEBRIS_TRIGGER); + } + + for (int difficulty2 = 0; difficulty2 < Difficulty_Max; difficulty2++) + { + g_SlenderTimeUntilKill[controller.Index] = GetGameTime() + NPCGetIdleLifetime(controller.Index, difficulty2); + } + + IVision vision = chaser.MyNextBotPointer().GetVisionInterface(); + vision.SetFieldOfView(originalData.FOV); + vision.ForgetAllKnownEntities(); + + chaser.NextAttackTime = new StringMap(); + + chaser.TrapCooldown = GetGameTime() + data.TrapCooldown[controller.Difficulty]; + + chaser.Teleport(pos, ang, NULL_VECTOR); + + chaser.Spawn(); + chaser.Activate(); + + return chaser; + } + + public void InvokeOnStartAttack(const char[] attackName) + { + Call_StartForward(g_OnChaserBossStartAttackFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_Finish(); + } + + public void InvokeOnEndAttack(const char[] attackName) + { + Call_StartForward(g_OnChaserBossEndAttackFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_Finish(); + } + + public static void SetupAPI() + { + CreateNative("SF2_ChaserBossEntity.IsValid.get", Native_GetIsValid); + CreateNative("SF2_ChaserBossEntity.IsAttemptingToMove.get", Native_GetIsAttemptingToMove); + CreateNative("SF2_ChaserBossEntity.IsAttacking.get", Native_GetIsAttacking); + CreateNative("SF2_ChaserBossEntity.IsStunned.get", Native_GetIsStunned); + CreateNative("SF2_ChaserBossEntity.StunHealth.get", Native_GetStunHealth); + CreateNative("SF2_ChaserBossEntity.MaxStunHealth.get", Native_GetMaxStunHealth); + CreateNative("SF2_ChaserBossEntity.CanBeStunned.get", Native_GetCanBeStunned); + CreateNative("SF2_ChaserBossEntity.CanTakeDamage.get", Native_GetCanTakeDamage); + CreateNative("SF2_ChaserBossEntity.IsRaging.get", Native_GetIsRaging); + CreateNative("SF2_ChaserBossEntity.IsRunningAway.get", Native_GetIsRunningAway); + CreateNative("SF2_ChaserBossEntity.IsSelfHealing.get", Native_GetIsSelfHealing); + CreateNative("SF2_ChaserBossEntity.ProfileData", Native_GetProfileData); + CreateNative("SF2_ChaserBossEntity.PerformVoice", Native_PerformVoice); + CreateNative("SF2_ChaserBossEntity.PerformCustomVoice", Native_PerformCustomVoice); + CreateNative("SF2_ChaserBossEntity.GetDefaultPosture", Native_GetDefaultPosture); + CreateNative("SF2_ChaserBossEntity.SetDefaultPosture", Native_SetDefaultPosture); + CreateNative("SF2_ChaserBossEntity.GetAttackName", Native_GetAttackName); + CreateNative("SF2_ChaserBossEntity.AttackIndex.get", Native_GetAttackIndex); + CreateNative("SF2_ChaserBossEntity.GetNextAttackTime", Native_GetNextAttackTime); + CreateNative("SF2_ChaserBossEntity.SetNextAttackTime", Native_SetNextAttackTime); + CreateNative("SF2_ChaserBossEntity.DropItem", Native_DropItem); + CreateNative("SF2_ChaserBossEntity.CreateSoundHint", Native_CreateSoundHint); + CreateNative("SF2_ChaserBossEntity.GroundSpeedOverride.get", Native_GetGroundSpeedOverride); + CreateNative("SF2_ChaserBossEntity.GroundSpeedOverride.set", Native_SetGroundSpeedOverride); + } +} + +static void EntityCreated(CBaseEntity ent, const char[] classname) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + SetClientForceChaseState(SF2NPC_BaseNPC(i), ent, false); + SetTargetMarkState(SF2NPC_BaseNPC(i), ent, false); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); + chaser.SetAlertTriggerCount(client, 0); + chaser.SetAlertSoundTriggerCooldown(client, 0.0); + chaser.SetAutoChaseCount(client, 0); + chaser.SetAutoChaseCooldown(client, -1.0); + client.SetForceChaseState(controller, false); + SetTargetMarkState(controller, client, false); + + int index = controller.ChaseOnLookTargets.FindValue(entity); + if (index != -1) + { + controller.ChaseOnLookTargets.Erase(index); + } + } +} + +static void OnPlayerTakeDamagePost(SF2_BasePlayer client, int attacker, int inflictor, float damage, int damageType) +{ + SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); + if (!boss.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = boss.Controller; + if (!controller.IsValid()) + { + return; + } + + Call_StartForward(g_OnClientDamagedByBossFwd); + Call_PushCell(client.index); + Call_PushCell(controller); + Call_PushCell(boss.index); + Call_PushFloat(damage); + Call_PushCell(damageType); + Call_Finish(); + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + char attack[64]; + strcopy(attack, sizeof(attack), boss.GetAttackName()); + if (attack[0] != '\0') + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attack, attackData); + + if (attackData.HitEffects != null) + { + SlenderSpawnEffects(attackData.HitEffects, controller.Index, false, _, _, _, client.index); + } + } +} + +static void OnPlayerDeathPre(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) + { + return; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); + + int index = controller.ChaseOnLookTargets.FindValue(entity); + if (index != -1) + { + controller.ChaseOnLookTargets.Erase(index); + } + } + + SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); + if (!boss.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = boss.Controller; + if (!controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + char attack[64]; + strcopy(attack, sizeof(attack), boss.GetAttackName()); + if (attack[0] != '\0') + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attack, attackData); + + if (attackData.KillEffects != null) + { + SlenderSpawnEffects(attackData.KillEffects, controller.Index, false, _, _, _, client.index); + } + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) + { + return; + } + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); + + int index = controller.ChaseOnLookTargets.FindValue(entity); + if (index != -1) + { + controller.ChaseOnLookTargets.Erase(index); + } + } + + SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); + if (!boss.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = boss.Controller; + if (!controller.IsValid()) + { + return; + } + + boss.PerformVoice(SF2BossSound_AttackKilled); + + SF2BossProfileSoundInfo info; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + info = data.AttackKilledClientSounds; + info.EmitSound(true, client.index); + + info = data.AttackKilledAllSounds; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + info.EmitSound(true, i); + } +} + +static void OnCreate(SF2_ChaserEntity ent) +{ + ent.AttackIndex = -1; + ent.Target = CBaseEntity(-1); + ent.OldTarget = CBaseEntity(-1); + ent.IsAllowedToDespawn = true; + SDKHook(ent.index, SDKHook_Think, Think); + SDKHook(ent.index, SDKHook_ThinkPost, ThinkPost); + SDKHook(ent.index, SDKHook_SpawnPost, SpawnPost); + SDKHook(ent.index, SDKHook_OnTakeDamage, OnTakeDamage); + SDKHook(ent.index, SDKHook_OnTakeDamageAlivePost, OnTakeDamageAlivePost); + SDKHook(ent.index, SDKHook_TraceAttack, TraceOnHit); + SetEntityTransmitState(ent.index, FL_EDICT_FULLCHECK); + g_DHookShouldTransmit.HookEntity(Hook_Pre, ent.index, ShouldTransmit); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent.index, UpdateTransmitState); +} + +static void OnRemove(SF2_ChaserEntity ent) +{ + if (ent.SmellPlayerList != null) + { + delete ent.SmellPlayerList; + } + delete ent.NextAttackTime; + + KillPvEBoss(ent.index); +} + +static Action Think(int entIndex) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(entIndex); + int interruptConditions = 0; + CBaseEntity target = ProcessVision(chaser, interruptConditions); + chaser.InterruptConditions |= interruptConditions; + chaser.Target = target; + + if (chaser.State == STATE_CHASE && !chaser.IsRaging) + { + chaser.DoAlwaysLookAt(chaser.Target); + } + + float gameTime = GetGameTime(); + if (chaser.LegacyFootstepInterval > 0.0 && chaser.LegacyFootstepTime < gameTime) + { + chaser.CastFootstep(); + } + + chaser.CheckVelocityCancel(); + + return Plugin_Continue; +} + +static void ThinkPost(int entIndex) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(entIndex); + SF2NPC_Chaser controller = chaser.Controller; + SF2BossProfileData data; + data = view_as(controller).GetProfileData(); + + ProcessSpeed(chaser); + + ProcessBody(chaser); + + if (data.CustomOutlines && data.RainbowOutline) + { + chaser.ProcessRainbowOutline(); + } + + if (chaser.State == STATE_CHASE && !chaser.IsRaging) + { + chaser.DoAlwaysLookAt(chaser.Target); + } + + chaser.InterruptConditions = 0; + chaser.SetNextThink(GetGameTime()); + + #if defined DEBUG + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || (g_PlayerDebugFlags[player.index] & DEBUG_BOSS_HITBOX) == 0) + { + continue; + } + + float mins[3], maxs[3], origin[3]; + chaser.GetAbsOrigin(origin); + npc.GetBodyMins(mins); + npc.GetBodyMaxs(maxs); + TE_DrawBox(player.index, origin, origin, mins, maxs, 0.1, g_ShockwaveBeam, { 0, 255, 0, 255 }); + } + #endif +} + +static void SpawnPost(int entIndex) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(entIndex); + SF2NPC_Chaser controller = chaser.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.DeathData.Enabled[difficulty]) + { + chaser.SetProp(Prop_Data, "m_iHealth", 2000000000); + chaser.SetProp(Prop_Data, "m_takedamage", DAMAGE_EVENTS_ONLY); + } + else + { + float addDeathHealth = data.DeathData.AddHealthPerPlayer[difficulty]; + int count; + float add = 0.0; + float result = controller.GetDeathHealth(difficulty); + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + if (g_Enabled) + { + if (originalData.IsPvEBoss) + { + if (!player.IsEliminated) + { + continue; + } + } + else + { + if (player.IsEliminated || !player.IsAlive || player.HasEscaped) + { + continue; + } + } + } + else + { + if (player.Team != TFTeam_Red && player.Team != TFTeam_Blue) + { + continue; + } + } + count++; + + switch (player.Class) + { + case TFClass_Scout: + { + add += data.DeathData.AddHealthPerScout[difficulty]; + } + + case TFClass_Soldier: + { + add += data.DeathData.AddHealthPerSoldier[difficulty]; + } + + case TFClass_Pyro: + { + add += data.DeathData.AddHealthPerPyro[difficulty]; + } + + case TFClass_DemoMan: + { + add += data.DeathData.AddHealthPerDemoman[difficulty]; + } + + case TFClass_Heavy: + { + add += data.DeathData.AddHealthPerHeavy[difficulty]; + } + + case TFClass_Engineer: + { + add += data.DeathData.AddHealthPerEngineer[difficulty]; + } + + case TFClass_Medic: + { + add += data.DeathData.AddHealthPerMedic[difficulty]; + } + + case TFClass_Sniper: + { + add += data.DeathData.AddHealthPerSniper[difficulty]; + } + + case TFClass_Spy: + { + add += data.DeathData.AddHealthPerSpy[difficulty]; + } + } + } + addDeathHealth *= float(count); + result += addDeathHealth + add; + + chaser.SetProp(Prop_Data, "m_iHealth", RoundToFloor(result)); + chaser.SetProp(Prop_Data, "m_takedamage", DAMAGE_YES); + } + chaser.MaxHealth = float(chaser.GetProp(Prop_Data, "m_iHealth")); + if (SF2_ChaserSmellAction.IsPossible(chaser)) + { + chaser.SmellPlayerList = new ArrayList(); + } + else + { + chaser.SmellPlayerList = null; + } + chaser.RageIndex = -1; + + if (originalData.Healthbar) + { + controller.Flags |= SFF_NOTELEPORT; + CreateTimer(0.1, Timer_UpdateHealthBar, controller.UniqueID, TIMER_FLAG_NO_MAPCHANGE); + } +} + +static Action Timer_UpdateHealthBar(Handle timer, any id) +{ + int bossIndex = NPCGetFromUniqueID(id); + if (bossIndex == -1) + { + return Plugin_Continue; + } + + UpdateHealthBar(bossIndex); + + return Plugin_Stop; +} + +static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damageType, int &weapon, float damageForce[3], float damagePosition[3], int damageCustom) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(victim); + SF2_BasePlayer player = SF2_BasePlayer(attacker); + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); + + if (player.IsValid && player.IsProxy) + { + return Plugin_Handled; + } + + if (!chaser.CanTakeDamage(CBaseEntity(attacker), CBaseEntity(inflictor), damage)) + { + return Plugin_Handled; + } + + if (player.IsValid || g_Buildings.FindValue(EntIndexToEntRef(inflictor)) != -1) + { + if (CBaseEntity(inflictor).GetProp(Prop_Data, "m_iTeamNum") == chaser.Team && (chaser.Controller.Flags & SFF_ATTACKWAITERS) == 0) + { + return Plugin_Handled; + } + + if (data.IsPvEBoss && player.IsValid && !player.IsInPvE) + { + SDKHooks_TakeDamage(inflictor, chaser.index, chaser.index, CBaseEntity(inflictor).GetProp(Prop_Data, "m_iHealth") * 4.0); + return Plugin_Handled; + } + } + + SF2ChaserBossProfileData chaserData; + chaserData = chaser.Controller.GetProfileData(); + int difficulty = chaser.Controller.Difficulty; + bool changed = false; + + if (chaserData.DamageResistances != null) + { + SF2ChaserBossProfileResistanceData resistanceData; + for (int i = 0; i < chaserData.DamageResistances.Length; i++) + { + chaserData.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); + if (resistanceData.DamageTypes == null) + { + continue; + } + + for (int i2 = 0; i2 < resistanceData.DamageTypes.Length; i++) + { + int type = resistanceData.DamageTypes.Get(i); + if (damageType & type || damageType == type) + { + damage *= resistanceData.Multiplier[difficulty]; + changed = true; + break; + } + } + + if (changed) + { + break; + } + } + } + + if (player.IsValid) + { + CBaseEntity activeWeapon = CBaseEntity(player.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (activeWeapon.IsValid() && chaserData.DamageResistances != null) + { + SF2ChaserBossProfileResistanceData resistanceData; + int itemIndex = activeWeapon.GetProp(Prop_Send, "m_iItemDefinitionIndex"); + for (int i = 0; i < chaserData.DamageResistances.Length; i++) + { + chaserData.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); + if (resistanceData.Weapons == null) + { + continue; + } + + if (resistanceData.Weapons.FindValue(itemIndex) == -1) + { + continue; + } + + damage *= resistanceData.Multiplier[difficulty]; + changed = true; + break; + } + } + + float myEyePos[3], clientEyePos[3], buffer[3], myAng[3]; + player.GetEyePosition(clientEyePos); + chaser.Controller.GetEyePosition(myEyePos); + SubtractVectors(clientEyePos, myEyePos, buffer); + GetVectorAngles(buffer, buffer); + chaser.GetAbsAngles(myAng); + + CBaseEntity weaponEnt = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); + if (weaponEnt.IsValid() && weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon == weaponEnt.index) + { + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 447, 426, 1181: // Whip + Eviction Notice + Hot Hand + { + player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); + } + + case 416: // Market Gardener + { + float zVelocity[3]; + player.GetPropVector(Prop_Data, "m_vecVelocity", zVelocity); + if (zVelocity[2] < 0.0) // A soldier has the market gardener and is currently falling down, like Minecraft with its critical hits. + { + damageType |= DMG_CRIT; + damage *= 0.667; + + changed = true; + } + } + } + + if (player.Class == TFClass_Spy && (data.IsPvEBoss || SF_IsBoxingMap()) && chaser.State != STATE_DEATH) + { + if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 75.0 && chaserData.BackstabDamageScale > 0.0) + { + damageType = DMG_CRIT; + EmitSoundToClient(player.index, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + weaponEnt.SetPropFloat(Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); + player.SetPropFloat(Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + player.SetPropFloat(Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); + CBaseAnimating model = CBaseAnimating(player.GetPropEnt(Prop_Send, "m_hViewModel")); + if (model.IsValid() && model.index > MaxClients) + { + int sequence = 0; + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 4, 194, 665, 727, 794, 803, 883, 892, 901, 910, 959, 968: // Butterfly knives + { + sequence = 42; + } + case 225, 356, 461, 574, 649: // Non-butterfly knives + { + sequence = 16; + } + case 423, 1071, 30758: // Multi-class knives + { + sequence = 21; + } + case 638: // Sharp Dresser + { + sequence = 32; + } + } + model.SetProp(Prop_Send, "m_nSequence", sequence); + } + + damage = chaser.MaxHealth * chaserData.BackstabDamageScale; + if (!chaserData.DeathData.Enabled[difficulty]) + { + damage = chaser.MaxStunHealth * chaserData.BackstabDamageScale; + } + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 356: // Kunai + { + int health = player.Health + 100; + if (health > 210) + { + health = 210; + } + SetEntityHealth(player.index, health); + } + + case 461: // Big Earner + { + player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); + } + } + + player.SetProp(Prop_Send, "m_iRevengeCrits", player.GetProp(Prop_Send, "m_iRevengeCrits") + 1); + + changed = true; + } + } + } + + weaponEnt = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); + if (weaponEnt.IsValid() && weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon == weaponEnt.index) + { + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 15, 202, 41, 298, 312, 424, 654, 793, 802, 811, 832, 850, 882, 891, 900, 909, 958, 967: // Miniguns + { + damage *= 0.65; + changed = true; + } + + case 40, 1146: // Backburner + { + if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 60.0 && chaserData.BackstabDamageScale > 0.0) + { + damageType |= DMG_CRIT; + changed = true; + } + } + } + } + } + + return changed ? Plugin_Changed : Plugin_Continue; +} + +static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float damage, int damageType, int weaponEntIndex, const float vecDamageForce[3], const float vecDamagePosition[3]) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(victim); + SF2_BasePlayer player = SF2_BasePlayer(attacker); + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); + + bool broadcastDamage = false; + + if (!chaser.CanTakeDamage(CBaseEntity(attacker), CBaseEntity(inflictor), damage)) + { + damage = 0.0; + return; + } + + if (player.IsValid) + { + CBaseEntity weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); + if (weapon.IsValid()) + { + int index = weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex"); + switch (index) + { + case 594: // Phlog + { + bool isInRage = player.GetProp(Prop_Send, "m_bRageDraining") != 0; + if (!isInRage) + { + float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); + rage += (damage / 10.0); + if (rage > 100.0) + { + rage = 100.0; + } + player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); + } + } + } + + if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon.index == weaponEntIndex) + { + switch (index) + { + case 527: // Widowmaker + { + GivePlayerAmmo(player.index, RoundToNearest(damage), 3, true); + } + + case 228, 1085: // Black Box + { + int health = player.Health; + int maxHealth = player.GetProp(Prop_Data, "m_iMaxHealth"); + float regen = FloatClamp(damage / 90.0, 0.0, 1.0); + int newHealth = health + RoundToNearest(20.0 * regen); + if (newHealth <= maxHealth) + { + SetEntityHealth(player.index, newHealth); + } + else + { + SetEntityHealth(player.index, maxHealth); + } + ShowHealthRegen(player.index, RoundToNearest(20.0 * regen), index); + } + + case 448: // Soda Popper + { + if (!player.InCondition(TFCond_CritHype)) + { + float hype = player.GetPropFloat(Prop_Send, "m_flHypeMeter"); + float add = (damage / 350.0) * 100.0; + float result = FloatClamp(hype + add, 0.0, 100.0); + player.SetPropFloat(Prop_Send, "m_flHypeMeter", result); + } + } + + case 772: // Baby Face's Blaster + { + float hype = player.GetPropFloat(Prop_Send, "m_flHypeMeter"); + float add = (damage / 100.0) * 100.0; + float result = FloatClamp(hype + add, 0.0, 100.0); + player.SetPropFloat(Prop_Send, "m_flHypeMeter", result); + result /= 100.0; + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 360.0 + (160.0 * result)); + } + } + } + } + + weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Secondary)); + if (weapon.IsValid()) + { + int index = weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex"); + switch (index) + { + case 129, 1001, 226, 354: // Banners + { + float requiredRage = 6.0; + if (index == 354) + { + requiredRage = 4.8; + } + + if (player.GetProp(Prop_Send, "m_bRageDraining") == 0) + { + float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); + rage += (damage / requiredRage); + if (rage > 100.0) + { + rage = 100.0; + } + player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); + } + } + } + + if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon.index == weaponEntIndex) + { + switch (index) + { + case 773: // Pocket pistol + { + ShowHealthRegen(player.index, 3, index); + int health = player.Health; + int maxHealth = player.GetProp(Prop_Data, "m_iMaxHealth"); + int newHealth = health + 3; + if (newHealth <= maxHealth) + { + SetEntityHealth(player.index, newHealth); + } + else + { + SetEntityHealth(player.index, maxHealth); + } + } + } + } + } + + weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); + if (weapon.IsValid() && weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon.index == weaponEntIndex) + { + switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 37: // Ubersaw + { + CBaseEntity secondary = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Secondary)); + if (secondary.IsValid()) + { + secondary.SetPropFloat(Prop_Send, "m_flChargeLevel", secondary.GetPropFloat(Prop_Send, "m_flChargeLevel") + 0.25); + if (secondary.GetPropFloat(Prop_Send, "m_flChargeLevel") > 1.0) + { + secondary.SetPropFloat(Prop_Send, "m_flChargeLevel", 1.0); + } + } + } + } + } + } + + if (damage > 0.0) + { + if (!player.IsValid || !player.IsProxy) + { + if (chaser.CanBeStunned()) + { + broadcastDamage = true; + } + if (chaser.GetProp(Prop_Data, "m_takedamage") == DAMAGE_YES) + { + broadcastDamage = true; + } + } + else + { + damage = 0.0; + } + } + + if (damage > 0.0 && player.IsValid && player.InCondition(TFCond_RegenBuffed) && (SF_IsBoxingMap() || data.IsPvEBoss)) + { + int health = player.Health; + int maxHealth = player.GetProp(Prop_Data, "m_iMaxHealth"); + float mult = damage * 0.475; + int newHealth = health + RoundToCeil(mult); + if (newHealth <= maxHealth) + { + SetEntityHealth(player.index, newHealth); + } + else + { + SetEntityHealth(player.index, maxHealth); + } + ShowHealthRegen(player.index, RoundToCeil(mult)); + } + + if (broadcastDamage) + { + bool miniCrit = false; + + Event event = CreateEvent("npc_hurt"); + if (event) + { + event.SetInt("entindex", chaser.index); + event.SetInt("health", chaser.GetProp(Prop_Data, "m_iHealth")); + event.SetInt("damageamount", RoundToFloor(damage)); + event.SetBool("crit", (damageType & DMG_CRIT) ? true : false); + + if (player.IsValid) + { + event.SetInt("attacker_player", player.UserID); + event.SetInt("weaponid", 0); + } + else + { + event.SetInt("attacker_player", 0); + event.SetInt("weaponid", 0); + } + + event.Fire(); + } + + if (player.IsValid) + { + miniCrit = player.IsMiniCritBoosted() && !player.IsCritBoosted(); + + if ((damageType & DMG_CRIT) != 0) + { + float myEyePos[3]; + chaser.GetAbsOrigin(myEyePos); + float myEyePosEx[3]; + chaser.GetPropVector(Prop_Send, "m_vecMaxs", myEyePosEx); + myEyePos[2] += myEyePosEx[2]; + + if (!miniCrit) + { + TE_Particle(g_Particles[CriticalHit], myEyePos, myEyePos); + TE_SendToClient(player.index); + + EmitSoundToClient(player.index, CRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + else + { + TE_Particle(g_Particles[MiniCritHit], myEyePos, myEyePos); + TE_SendToClient(player.index); + + EmitSoundToClient(player.index, MINICRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + } + } + } + + if (data.Healthbar) + { + UpdateHealthBar(chaser.Controller.Index); + if (chaser.IsGoingToHeal) + { + SetHealthBarColor(true); + } + } +} + +static Action TraceOnHit(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& ammotype, int hitbox, int hitgroup) +{ + bool changed = false; + + if (IsValidClient(attacker)) + { + if ((damagetype & DMG_USE_HITLOCATIONS) && hitgroup == 1) + { + damagetype = damagetype | DMG_CRIT; + changed = true; + } + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(victim); + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return changed ? Plugin_Changed : Plugin_Continue; + } + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.DamageResistances != null) + { + SF2ChaserBossProfileResistanceData resistanceData; + for (int i = 0; i < data.DamageResistances.Length; i++) + { + data.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); + if (resistanceData.HitboxGroups == null) + { + continue; + } + + if (resistanceData.HitboxGroups.FindValue(hitgroup) == -1) + { + continue; + } + + damage *= resistanceData.Multiplier[difficulty]; + changed = true; + break; + } + } + return changed ? Plugin_Changed : Plugin_Continue; +} + +static MRESReturn UpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = SetEntityTransmitState(entIndex, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn ShouldTransmit(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} + +static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditions = 0) +{ + interruptConditions = 0; + IVision vision = chaser.MyNextBotPointer().GetVisionInterface(); + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return CBaseEntity(-1); + } + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (originalData.IsPvEBoss) + { + attackEliminated = originalData.IsPvEBoss; + } + float gameTime = GetGameTime(); + + int difficulty = controller.Difficulty; + + float playerDists[2049]; + int playerInterruptFlags[2049]; + + float traceMins[3] = { -16.0, ... }; + traceMins[2] = 0.0; + float traceMaxs[3] = { 16.0, ... }; + traceMaxs[2] = 0.0; + float buffer[3], myEyeAng[3]; + chaser.GetAbsAngles(myEyeAng); + float traceStartPos[3], traceEndPos[3], myPos[3], targetPos[3]; + controller.GetEyePosition(traceStartPos); + chaser.GetAbsOrigin(myPos); + + int state = chaser.State; + + if (originalData.EyeData.Type == 1) + { + if (chaser.EyeBoneIndex <= 0) + { + chaser.EyeBoneIndex = LookupBone(chaser.index, originalData.EyeData.Bone); + } + GetBonePosition(chaser.index, chaser.EyeBoneIndex, traceStartPos, myEyeAng); + float offset[3]; + controller.GetEyePositionOffset(offset); + AddVectors(originalData.EyeData.OffsetAng, myEyeAng, myEyeAng); + VectorTransform(offset, traceStartPos, myEyeAng, traceStartPos); + } + + int oldTarget = chaser.OldTarget.index; + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, CBaseEntity(oldTarget), attackEliminated)) + { + chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(CBaseEntity(oldTarget))) + { + chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + + // The m_iState is extra hacky and has the potential to be eliviated by Red Tape Recorders, but who cares + if (IsValidEntity(oldTarget) && g_Buildings.FindValue(EntIndexToEntRef(oldTarget)) != -1 && (GetEntProp(oldTarget, Prop_Send, "m_bCarried") || GetEntProp(oldTarget, Prop_Send, "m_iState") == 0)) + { + int owner = GetEntPropEnt(oldTarget, Prop_Send, "m_hBuilder"); + if (IsValidEntity(owner) && IsPlayerAlive(owner)) + { + chaser.OldTarget = CBaseEntity(owner); + oldTarget = owner; + } + else + { + chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + } + + int bestNewTarget = oldTarget; + float searchRange = originalData.SearchRange[difficulty]; + float bestNewTargetDist = Pow(searchRange, 2.0); + if (IsValidEntity(bestNewTarget)) + { + CBaseEntity(bestNewTarget).GetAbsOrigin(targetPos); + bestNewTargetDist = GetVectorSquareMagnitude(myPos, targetPos); + if (bestNewTargetDist > Pow(searchRange, 2.0)) + { + bestNewTargetDist = Pow(searchRange, 2.0); + } + } + + if (chaser.SmellPlayerList != null) + { + chaser.SmellPlayerList.Clear(); + } + + ArrayList valids = new ArrayList(); + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, client, attackEliminated)) + { + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(client)) + { + continue; + } + + valids.Push(EntIndexToEntRef(client.index)); + } + + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) + { + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(entity)) + { + continue; + } + + valids.Push(EntIndexToEntRef(entity.index)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) + { + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(entity)) + { + continue; + } + + valids.Push(EntIndexToEntRef(entity.index)); + } + + for (int i = 0; i < valids.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(valids.Get(i))); + if (!entity.IsValid()) + { + continue; + } + SF2_BasePlayer player = SF2_BasePlayer(entity.index); + + #if defined DEBUG + if (player.IsValid && g_PlayerDebugFlags[player.index] & DEBUG_BOSS_EYES) + { + float end[3]; + end[0] = 1000.0; + VectorTransform(end, traceStartPos, myEyeAng, end); + int color[4] = { 0, 255, 0, 255 }; + TE_SetupBeamPoints(traceStartPos, end, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); + TE_SendToClient(player.index); + } + #endif + + chaser.SetIsVisible(entity, false); + chaser.SetInFOV(entity, false); + chaser.SetIsNear(entity, false); + + if (player.IsValid) + { + player.GetEyePosition(traceEndPos); + } + else + { + entity.WorldSpaceCenter(traceEndPos); + } + + entity.GetAbsOrigin(targetPos); + + bool isVisible, isTraceVisible; + int traceHitEntity; + TR_TraceHullFilter(traceStartPos, + traceEndPos, + traceMins, + traceMaxs, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, + TraceRayBossVisibility, + chaser.index); + + isVisible = !TR_DidHit(); + traceHitEntity = TR_GetEntityIndex(); + + if (!isVisible && traceHitEntity == entity.index) + { + isVisible = true; + isTraceVisible = true; + } + + if (isVisible) + { + isVisible = NPCShouldSeeEntity(controller.Index, entity.index); + } + + float dist = GetVectorSquareMagnitude(myPos, targetPos); + + if (player.IsValid && g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) + { + int fogEntity = player.GetDataEnt(g_PlayerFogCtrlOffset); + if (IsValidEdict(fogEntity)) + { + if (GetEntData(fogEntity, g_FogCtrlEnableOffset) && + dist >= SquareFloat(GetEntDataFloat(fogEntity, g_FogCtrlEndOffset))) + { + isVisible = false; + isTraceVisible = false; + } + } + } + + if (dist > Pow(originalData.SearchRange[difficulty], 2.0)) + { + isVisible = false; + } + + float priorityValue; + + chaser.SetIsVisible(entity, isVisible); + + // Near radius check. + if (chaser.GetIsVisible(entity) && + dist <= SquareFloat(data.WakeRadius)) + { + chaser.SetIsNear(entity, true); + playerInterruptFlags[entity.index] |= COND_ENEMYNEAR; + } + if (player.IsValid && chaser.GetIsVisible(player) && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) + { + TF2_StunPlayer(player.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); + } + + // FOV check. + SubtractVectors(traceEndPos, traceStartPos, buffer); + GetVectorAngles(buffer, buffer); + + if (FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (vision.GetFieldOfView() * 0.5)) + { + chaser.SetInFOV(entity, true); + } + + if (chaser.GetIsVisible(entity)) + { + if (chaser.GetInFOV(entity)) + { + playerInterruptFlags[entity.index] |= COND_SAWENEMY; + } + } + + if (isTraceVisible) + { + playerInterruptFlags[entity.index] |= COND_ENEMYVISIBLE; + } + + playerDists[entity.index] = dist; + + if (chaser.SmellPlayerList != null && data.SmellData.Enabled[difficulty]) + { + if (player.IsValid && dist < Pow(data.SmellData.PlayerRange[difficulty], 2.0)) + { + chaser.SmellPlayerList.Push(player.index); + } + } + + if (player.IsValid && (player.IsTrapped || (player.IsReallySprinting && data.AutoChaseSprinters[difficulty] && chaser.GetAutoChaseCooldown(player) < gameTime)) + && (chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) && dist <= SquareFloat(searchRange)) + { + player.SetForceChaseState(controller, true); + SetTargetMarkState(controller, player, true); + } + + if (data.ChaseOnLookData.Enabled[difficulty] && isTraceVisible && player.IsValid && controller.ChaseOnLookTargets.FindValue(player.index) == -1) + { + bool shouldCalculate = false; + if (data.ChaseOnLookData.RequiredFOV[difficulty] <= 0.0) + { + shouldCalculate = chaser.GetInFOV(player); + } + else + { + shouldCalculate = FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (data.ChaseOnLookData.RequiredFOV[difficulty] * 0.5); + } + if (shouldCalculate) + { + float eyeAng[3], expectedAng[3], lookPos[3]; + player.GetEyeAngles(eyeAng); + chaser.GetAbsOrigin(myPos); + lookPos = data.ChaseOnLookData.RequiredLookPosition; + VectorTransform(lookPos, myPos, myEyeAng, lookPos); + SubtractVectors(lookPos, traceEndPos, expectedAng); + GetVectorAngles(expectedAng, expectedAng); + float minimumXAng = data.ChaseOnLookData.MinimumXAngle[difficulty] * 0.5; + float maximumXAng = data.ChaseOnLookData.MaximumXAngle[difficulty] * 0.5; + float minimumYAng = data.ChaseOnLookData.MinimumYAngle[difficulty] * 0.5; + float maximumYAng = data.ChaseOnLookData.MaximumYAngle[difficulty] * 0.5; + float xAng, yAng; + xAng = AngleDiff(eyeAng[0], expectedAng[0]); + yAng = FloatAbs(AngleDiff(eyeAng[1], expectedAng[1])); + if ((xAng >= minimumXAng && xAng < maximumXAng) && (yAng >= minimumYAng && yAng < maximumYAng) && + ((data.ChaseOnLookData.AddTargets[difficulty]) || (!data.ChaseOnLookData.AddTargets[difficulty] && controller.ChaseOnLookTargets.Length == 0))) + { + controller.ChaseOnLookTargets.Push(player.index); + SF2BossProfileSoundInfo soundInfo; + soundInfo = originalData.ScareSounds; + soundInfo.EmitSound(true, player.index); + player.ChangeCondition(TFCond_MarkedForDeathSilent); + player.SetForceChaseState(controller, true); + SetTargetMarkState(controller, player, true); + } + } + } + + if (ShouldClientBeForceChased(controller, entity)) + { + bestNewTarget = entity.index; + playerInterruptFlags[entity.index] |= COND_ENEMYRECHASE; + } + + if (entity.index != oldTarget) + { + playerInterruptFlags[entity.index] |= COND_NEWENEMY; + } + + if (controller.HasAttribute(SF2Attribute_IgnoreNonMarkedForChase)) + { + if (!IsTargetMarked(controller, entity) && controller.ChaseOnLookTargets.FindValue(entity) == -1) + { + playerInterruptFlags[entity.index] = 0; + continue; + } + } + + if (player.IsValid && !SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !data.ChasesEndlessly && !g_RenevantBossesChaseEndlessly) + { + priorityValue = g_PageMax > 0 ? ((float(player.PageCount) / float(g_PageMax)) / 4.0) : 0.0; + } + + if (player.IsValid) + { + TFClassType classType = player.Class; + int classToInt = view_as(classType); + if (!IsClassConfigsValid()) + { + if ((classType == TFClass_Medic || player.HasRegenItem) && !SF_IsBoxingMap()) + { + priorityValue += 0.2; + } + + if (classType == TFClass_Spy) + { + priorityValue += 0.1; + } + } + else + { + if (!SF_IsBoxingMap() && player.HasRegenItem) + { + priorityValue += 0.2; + } + priorityValue += g_ClassBossPriorityMultiplier[classToInt]; + } + } + + if (chaser.GetIsNear(entity) || (chaser.GetIsVisible(entity) && chaser.GetInFOV(entity))) + { + if (dist <= SquareFloat(searchRange)) + { + // Subtract distance to increase priority. + dist -= ((dist * priorityValue)); + + if (dist < bestNewTargetDist) + { + bestNewTarget = entity.index; + bestNewTargetDist = dist; + playerInterruptFlags[entity.index] |= COND_SAWENEMY; + } + } + } + } + + delete valids; + CNavArea myArea = chaser.GetLastKnownArea(); + + if (!originalData.IsPvEBoss && myArea != NULL_AREA && (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly || g_RenevantBossesChaseEndlessly)) + { + if (!IsTargetValidForSlender(chaser, CBaseEntity(bestNewTarget), attackEliminated)) + { + if (state != STATE_CHASE && (NPCAreAvailablePlayersAlive() || g_Buildings.Length > 0 || g_WhitelistedEntities.Length > 0)) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsTargetValidForSlender(chaser, CBaseEntity(i), false)) + { + continue; + } + arrayRaidTargets.Push(EntIndexToEntRef(i)); + } + + if (!g_Enabled) + { + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!IsTargetValidForSlender(chaser, building, false)) + { + continue; + } + + // Do not go looking for buildings that are carried or are in an inactive state + if (building.GetProp(Prop_Send, "m_bCarried") || building.GetProp(Prop_Send, "m_iState") == 0) + { + continue; + } + arrayRaidTargets.Push(g_Buildings.Get(i)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + if (!IsTargetValidForSlender(chaser, CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))), false)) + { + continue; + } + arrayRaidTargets.Push(g_WhitelistedEntities.Get(i)); + } + } + + if (arrayRaidTargets.Length > 0) + { + int raidTarget = EntRefToEntIndex(arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1))); + if (IsValidClient(raidTarget)) + { + if (!g_PlayerEliminated[raidTarget]) + { + bestNewTarget = raidTarget; + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); + } + } + else + { + bestNewTarget = raidTarget; + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); + } + } + delete arrayRaidTargets; + } + } + chaser.CurrentChaseDuration = data.ChaseDuration[difficulty] + GetGameTime(); + } + + if (originalData.IsPvEBoss) + { + if (!IsPvETargetValid(SF2_BasePlayer(bestNewTarget))) + { + if (state != STATE_CHASE) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsPvETargetValid(CBaseEntity(i))) + { + continue; + } + arrayRaidTargets.Push(EntIndexToEntRef(i)); + } + + if (arrayRaidTargets.Length > 0) + { + int raidTarget = EntRefToEntIndex(arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1))); + if (IsValidClient(raidTarget) && IsClientInPvE(raidTarget)) + { + bestNewTarget = raidTarget; + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); + } + } + delete arrayRaidTargets; + } + } + chaser.CurrentChaseDuration = data.ChaseDuration[difficulty] + GetGameTime(); + } + + if (bestNewTarget != INVALID_ENT_REFERENCE) + { + interruptConditions = playerInterruptFlags[bestNewTarget]; + if (bestNewTarget != oldTarget) + { + chaser.Teleporters.Clear(); + } + chaser.OldTarget = CBaseEntity(bestNewTarget); + } + + return CBaseEntity(bestNewTarget); +} + +static void ProcessSpeed(SF2_ChaserEntity chaser) +{ + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return; + } + SF2NPC_BaseNPC baseController = view_as(controller); + int difficulty = controller.Difficulty; + SF2NPCMoveTypes moveType = chaser.MovementType; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + float gameTime = GetGameTime(); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = baseController.GetProfileData(); + SF2ChaserBossProfilePostureInfo postureInfo; + char posture[64]; + if (data.Postures != null) + { + chaser.GetPosture(posture, sizeof(posture)); + } + + float speed, acceleration; + + acceleration = originalData.Acceleration[difficulty]; + if (!IsNullString(posture) && data.GetPosture(posture, postureInfo)) + { + speed = postureInfo.Acceleration[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_ReducedAccelerationOnLook) && controller.CanBeSeen(_, true)) + { + acceleration *= controller.GetAttributeValue(SF2Attribute_ReducedAccelerationOnLook); + } + acceleration += controller.GetAddAcceleration(); + + Action action = Plugin_Continue; + float forwardSpeed; + switch (moveType) + { + case SF2NPCMoveType_Walk: + { + speed = data.WalkSpeed[difficulty]; + if (!IsNullString(posture) && data.GetPosture(posture, postureInfo)) + { + speed = postureInfo.WalkSpeed[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_ReducedWalkSpeedOnLook) && controller.CanBeSeen(_, true)) + { + speed *= controller.GetAttributeValue(SF2Attribute_ReducedWalkSpeedOnLook); + } + + forwardSpeed = speed; + Call_StartForward(g_OnBossGetWalkSpeedFwd); + Call_PushCell(controller.Index); + Call_PushFloatRef(forwardSpeed); + Call_Finish(action); + if (action == Plugin_Changed) + { + speed = forwardSpeed; + } + } + case SF2NPCMoveType_Run: + { + speed = originalData.RunSpeed[difficulty]; + if (!IsNullString(posture) && data.GetPosture(posture, postureInfo)) + { + speed = postureInfo.Speed[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_ReducedSpeedOnLook) && controller.CanBeSeen(_, true)) + { + speed *= controller.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); + } + + speed += baseController.GetAddSpeed(); + forwardSpeed = speed; + + Call_StartForward(g_OnBossGetSpeedFwd); + Call_PushCell(controller.Index); + Call_PushFloatRef(forwardSpeed); + Call_Finish(action); + if (action == Plugin_Changed) + { + speed = forwardSpeed; + } + } + case SF2NPCMoveType_Attack: + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(chaser.GetAttackName(), attackData); + float attackSpeed = attackData.RunSpeed[difficulty]; + if (attackData.RunDelay[difficulty] > 0.0 && chaser.AttackRunDelay > gameTime) + { + attackSpeed = 0.0; + } + if (attackData.RunDuration[difficulty] > 0.0 && chaser.AttackRunDuration < gameTime) + { + attackSpeed = 0.0; + } + if (attackData.RunGroundSpeed[difficulty]) + { + chaser.GroundSpeedOverride = true; + } + speed = attackSpeed; + acceleration = attackData.RunAcceleration[difficulty]; + } + } + + if (g_InProxySurvivalRageMode) + { + speed *= 1.25; + } + + if (difficulty > Difficulty_Normal) + { + speed = (speed + (speed * GetDifficultyModifier(difficulty)) / 15.0); + acceleration = (acceleration + (acceleration * GetDifficultyModifier(difficulty)) / 15.0); + } + + if (moveType == SF2NPCMoveType_Run) + { + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) + { + if (speed < 520.0) + { + speed = 520.0; + } + acceleration += 9001.0; + } + if (SF_IsSlaughterRunMap()) + { + float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; + if (!originalData.SlaughterRunData.CustomMinimumSpeed[difficulty] && speed < slaughterSpeed) + { + speed = slaughterSpeed; + } + acceleration += 10000.0; + } + } + + if ((!originalData.IsPvEBoss && IsBeatBoxBeating(2)) || chaser.IsKillingSomeone) + { + speed = 0.0; + } + + if (!chaser.GroundSpeedOverride) + { + npc.flWalkSpeed = speed * 0.9; + npc.flRunSpeed = speed; + npc.flAcceleration = acceleration; + } + else + { + float vel[3], myPos[3], res[3], matrix[3][4], ang[3]; + float cycle = chaser.GetPropFloat(Prop_Data, "m_flCycle"); + if (cycle < 1.0) + { + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + Address poseParams = GetEntityAddress(chaser.index) + view_as
(FindSendPropInfo("CBaseAnimating", "m_flPoseParameter")); + int sequence = chaser.GetProp(Prop_Send, "m_nSequence"); + + CBaseAnimating_GetSequenceVelocity(chaser.GetModelPtr(), sequence, cycle, poseParams, vel); + chaser.GetAbsOrigin(myPos); + chaser.GetLocalAngles(ang); + AngleMatrix(ang, matrix); + VectorRotate(vel, matrix, res); + AddVectors(res, myPos, res); + if (moveType != SF2NPCMoveType_Attack) + { + speed = GetVectorLength(vel) * chaser.GetPropFloat(Prop_Send, "m_flPlaybackRate") * 0.9 * chaser.GetPropFloat(Prop_Send, "m_flModelScale"); + } + else + { + speed *= GetVectorLength(vel) * chaser.GetPropFloat(Prop_Send, "m_flPlaybackRate") * 0.9 * chaser.GetPropFloat(Prop_Send, "m_flModelScale"); + } + npc.flAcceleration = speed * 10.0; + npc.flWalkSpeed = speed; + npc.flRunSpeed = speed; + loco.Run(); + if (moveType != SF2NPCMoveType_Attack) + { + loco.Approach(res, 999999.9); + } + } + } +} + +static void ProcessBody(SF2_ChaserEntity chaser) +{ + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + if (data.Postures != null && chaser.CanUpdatePosture()) + { + char posture[64], resultPosture[64]; + chaser.GetDefaultPosture(posture, sizeof(posture)); + strcopy(resultPosture, sizeof(resultPosture), posture); + Action result = Plugin_Continue; + Call_StartForward(g_OnChaserUpdatePosturePFwd); + Call_PushCell(controller); + Call_PushStringEx(resultPosture, sizeof(resultPosture), 0, SM_PARAM_COPYBACK); + Call_PushCell(sizeof(resultPosture)); + Call_Finish(result); + + if (result == Plugin_Changed) + { + strcopy(posture, sizeof(posture), resultPosture); + } + + chaser.SetPosture(posture); + } + + INextBot bot = chaser.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + + float speed = loco.GetGroundSpeed(); + + if (speed < 0.01) + { + if (chaser.MoveXParameter != -1) + { + chaser.SetPoseParameter(chaser.MoveXParameter, 0.0); + } + + if (chaser.MoveYParameter != -1) + { + chaser.SetPoseParameter(chaser.MoveYParameter, 0.0); + } + + if (chaser.MoveScaleParameter != -1) + { + chaser.SetPoseParameter(chaser.MoveScaleParameter, 0.0); + } + return; + } + + if (!chaser.IsAttemptingToMove) + { + return; + } + + switch (chaser.MoveParameterType) + { + case SF2NPCMoveParameter_XY: + { + float forwardVector[3], rightVector[3], upVector[3], motionVector[3]; + chaser.GetVectors(forwardVector, rightVector, upVector); + loco.GetGroundMotionVector(motionVector); + + chaser.SetPoseParameter(chaser.MoveXParameter, GetVectorDotProduct(motionVector, forwardVector)); + chaser.SetPoseParameter(chaser.MoveYParameter, GetVectorDotProduct(motionVector, rightVector)); + + float groundSpeed = chaser.GetPropFloat(Prop_Data, "m_flGroundSpeed"); + if (groundSpeed != 0.0 && loco.IsOnGround() && chaser.State != STATE_ATTACK) + { + float rate = (speed / groundSpeed); + if (rate < 0.0) + { + rate = 0.0; + } + if (rate > 12.0) + { + rate = 12.0; + } + + chaser.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + } + } + case SF2NPCMoveParameter_Yaw: + { + float fwd[3], right[3], up[3]; + chaser.GetVectors(fwd, right, up); + + float motion[3]; + loco.GetGroundMotionVector(motion); + + float deltaTime = GetGameFrameTime(); + if (deltaTime > 0.0) + { + float myAng[3], normalAngle, estimateYaw; + estimateYaw = chaser.EstimatedYaw; + chaser.GetLocalAngles(myAng); + + if (motion[0] == 0.0 && motion[1] == 0.0) + { + float yawDelta = myAng[1] - estimateYaw; + yawDelta = AngleNormalize(yawDelta); + + if (deltaTime < 0.25) + { + yawDelta *= (deltaTime * 4.0); + } + else + { + yawDelta *= deltaTime; + } + + estimateYaw += yawDelta; + estimateYaw = AngleNormalize(estimateYaw); + } + else + { + estimateYaw = (ArcTangent2(motion[1], motion[0]) * 180.0 / 3.14159); + estimateYaw = FloatClamp(estimateYaw, -180.0, 180.0); + } + normalAngle = AngleNormalize(myAng[1]); + chaser.EstimatedYaw = estimateYaw; + float actualYaw = normalAngle - estimateYaw; + actualYaw = AngleNormalize(-actualYaw); + + chaser.SetPoseParameter(chaser.MoveYawParameter, actualYaw); + } + } + } + + if (chaser.MoveScaleParameter != -1) + { + float scale = npc.flRunSpeed / speed; + chaser.SetPoseParameter(chaser.MoveScaleParameter, scale); + } + + if (data.OldAnimationAI) + { + float velocity, returnFloat, groundSpeed; + velocity = loco.GetGroundSpeed(); + groundSpeed = chaser.GetPropFloat(Prop_Data, "m_flGroundSpeed"); + if (groundSpeed != 0.0 && groundSpeed > 10.0) + { + returnFloat = (velocity / groundSpeed) * chaser.AnimationPlaybackRate; + + if (loco.IsOnGround() && chaser.IsAttemptingToMove && chaser.State != STATE_ATTACK) + { + if (returnFloat > 12.0) + { + returnFloat = 12.0; + } + if (returnFloat < -4.0) + { + returnFloat = -4.0; + } + chaser.SetPropFloat(Prop_Send, "m_flPlaybackRate", returnFloat); + } + } + else + { + velocity = (velocity + ((npc.flRunSpeed * GetDifficultyModifier(controller.Difficulty)) / 15.0)) / npc.flRunSpeed; + + if (loco.IsOnGround() && chaser.IsAttemptingToMove && chaser.State != STATE_ATTACK) + { + float playbackSpeed = velocity * chaser.AnimationPlaybackRate; + if (playbackSpeed > 12.0) + { + playbackSpeed = 12.0; + } + if (playbackSpeed < -4.0) + { + playbackSpeed = -4.0; + } + chaser.SetPropFloat(Prop_Send, "m_flPlaybackRate", playbackSpeed); + } + } + } +} + +static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) +{ + if (SF2_ChaserEntity(other).IsValid() || SF2_StatueEntity(other).IsValid()) + { + return false; + } + + if (IsValidEntity(other)) + { + SF2_BasePlayer player = SF2_BasePlayer(other); + INextBot bot = loco.GetBot(); + SF2_ChaserEntity chaser = SF2_ChaserEntity(bot.GetEntity()); + + if (chaser.Controller.IsValid() && (chaser.Controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return false; + } + + if (chaser.State == STATE_DEATH) + { + return false; + } + + char class[64]; + GetEntityClassname(other, class, sizeof(class)); + if (player.IsValid) + { + if (player.IsInGhostMode) + { + return false; + } + + if (chaser.IsValid() && chaser.Controller.IsValid()) + { + SF2BossProfileData data; + SF2NPC_BaseNPC controller = view_as(chaser.Controller); + data = controller.GetProfileData(); + if ((data.IsPvEBoss && player.IsInPvE) || (controller.Flags & SFF_ATTACKWAITERS) != 0) + { + return true; + } + } + + if (g_Enabled && !SF_IsBoxingMap() && !player.IsProxy && !player.IsInGhostMode && player.Team != TFTeam_Blue && !player.IsInDeathCam) + { + return true; + } + + if (!g_Enabled && player.Team != chaser.Team) + { + return true; + } + } + + if (chaser.IsValid() && chaser.Controller.IsValid()) + { + SF2BossProfileData data; + SF2NPC_BaseNPC controller = view_as(chaser.Controller); + data = controller.GetProfileData(); + if (data.IsPvEBoss && SF2_ChaserEntity(other).IsValid()) + { + return false; + } + + if (strcmp(class, "obj_sentrygun", false) || strcmp(class, "obj_dispenser", false) || strcmp(class, "obj_teleporter", false)) + { + if (chaser.Team != CBaseEntity(other).GetProp(Prop_Data, "m_iTeamNum")) + { + return true; + } + else + { + return false; + } + } + } + + if (strcmp(class, "tank_boss", false) == 0) + { + return true; + } + } + return loco.CallBaseFunction(other); +} + +static Action Hook_ChaserSoundHook(int clients[64], int &numClients, char sample[PLATFORM_MAX_PATH], int &entity, int &channel, float &volume, int &level, int &pitch, int &flags) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + SF2_BasePlayer client = SF2_BasePlayer(entity); + if (!client.IsValid || !client.IsAlive || client.IsEliminated || client.HasEscaped) + { + return Plugin_Continue; + } + + switch (channel) + { + case SNDCHAN_VOICE: + { + if (StrContains(sample, "vo/halloween_scream") != -1) + { + return Plugin_Continue; + } + + OnPlayerEmitSound(client, SoundType_Voice); + } + case SNDCHAN_BODY: + { + if (!(client.GetFlags() & FL_ONGROUND)) + { + return Plugin_Continue; + } + + if (StrContains(sample, "player/footsteps", false) == -1 && StrContains(sample, "step", false) == -1) + { + return Plugin_Continue; + } + + bool isLoudStep = false, isCrouchStep = false; + if (client.IsSprinting && !(client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked"))) + { + isLoudStep = true; + } + else if (client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked")) + { + isCrouchStep = true; + } + + SoundType soundType = SoundType_Footstep; + if (isLoudStep) + { + soundType = SoundType_LoudFootstep; + } + else if (isCrouchStep) + { + soundType = SoundType_QuietFootstep; + } + + OnPlayerEmitSound(client, soundType); + } + case SNDCHAN_ITEM, SNDCHAN_WEAPON: + { + if (StrContains(sample, "swing", false) == -1 && StrContains(sample, "impact", false) == -1 && + StrContains(sample, "hit", false) == -1 && StrContains(sample, "slice", false) == -1 && + StrContains(sample, "reload", false) == -1 && StrContains(sample, "woosh", false) == -1 && + StrContains(sample, "eviction", false) == -1 && StrContains(sample, "holy", false) == -1 && + StrContains(sample, "flashlight", false) == -1) + { + return Plugin_Continue; + } + + OnPlayerEmitSound(client, StrContains(sample, "flashlight", false) != -1 ? SoundType_Flashlight : SoundType_Weapon); + } + case SNDCHAN_STATIC: + { + if (StrContains(sample, "happy_birthday_tf", false) == -1 && StrContains(sample, "jingle_bells_nm", false) == -1) + { + return Plugin_Continue; + } + + OnPlayerEmitSound(client, SoundType_Voice); + } + } + + return Plugin_Continue; +} + +static void OnPlayerEmitSound(SF2_BasePlayer client, SoundType soundType) +{ + float gameTime = GetGameTime(); + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + if (NPCGetUniqueID(bossIndex) == -1) + { + continue; + } + + if (!NPCShouldHearEntity(bossIndex, client.index, soundType)) + { + continue; + } + + int entity = NPCGetEntIndex(bossIndex); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + continue; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + TFClassType class = client.Class; + int classToInt = view_as(class); + + float myPos[3], hisPos[3]; + chaser.GetAbsOrigin(myPos); + client.GetAbsOrigin(hisPos); + + int difficulty = controller.Difficulty; + + float hearRadius = view_as(controller).GetProfileData().SearchSoundRange[difficulty]; + if (hearRadius <= 0.0) + { + continue; + } + + float distance = GetVectorSquareMagnitude(hisPos, myPos); + + Handle trace = null; + bool hit = false; + + float myEyePos[3]; + controller.GetEyePosition(myEyePos); + + float cooldown = 0.0; + int addThreshold = 0; + + switch (soundType) + { + case SoundType_Footstep, SoundType_LoudFootstep, SoundType_QuietFootstep: + { + if (!(client.GetFlags() & FL_ONGROUND)) + { + continue; + } + + if (soundType == SoundType_QuietFootstep) + { + addThreshold = data.QuietFootstepSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.QuietFootstepSenses.Cooldown[difficulty]; + distance *= 1.85; + } + else if (soundType == SoundType_LoudFootstep) + { + addThreshold = data.LoudFootstepSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.LoudFootstepSenses.Cooldown[difficulty]; + distance *= 0.66; + } + else + { + addThreshold = data.FootstepSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.FootstepSenses.Cooldown[difficulty]; + } + + trace = TR_TraceRayFilterEx(myPos, hisPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_WINDOW, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, chaser.index); + hit = TR_DidHit(trace); + delete trace; + } + case SoundType_Voice, SoundType_Flashlight: + { + float hisEyePos[3]; + client.GetEyePosition(hisEyePos); + + trace = TR_TraceRayFilterEx(myEyePos, hisEyePos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_WINDOW, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, chaser.index); + hit = TR_DidHit(trace); + delete trace; + + if (!IsClassConfigsValid() || soundType == SoundType_Voice) + { + distance *= 0.5; + } + else if (IsClassConfigsValid() && soundType == SoundType_Flashlight) + { + distance *= g_ClassFlashlightSoundRadius[classToInt]; + } + + if (soundType == SoundType_Voice) + { + addThreshold = data.VoiceSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.VoiceSenses.Cooldown[difficulty]; + } + else if (soundType == SoundType_Flashlight) + { + addThreshold = data.FlashlightSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.FlashlightSenses.Cooldown[difficulty]; + } + } + case SoundType_Weapon: + { + addThreshold = data.WeaponSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + + float hisMins[3], hisMaxs[3]; + client.GetPropVector(Prop_Send, "m_vecMins", hisMins); + client.GetPropVector(Prop_Send, "m_vecMaxs", hisMaxs); + + float middle[3]; + for (int i = 0; i < 2; i++) + { + middle[i] = (hisMins[i] + hisMaxs[i]) / 2.0; + } + + float endPos[3]; + endPos = hisPos; + AddVectors(hisPos, middle, endPos); + + trace = TR_TraceRayFilterEx(myEyePos, endPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_WINDOW, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, chaser.index); + hit = TR_DidHit(trace); + delete trace; + + distance *= 0.66; + + cooldown = data.WeaponSenses.Cooldown[difficulty]; + } + } + + if (hit) + { + distance *= 1.66; + } + + if (!IsClassConfigsValid()) + { + if (class == TFClass_Spy) + { + distance *= 1.3; + } + + if (class == TFClass_Scout) + { + distance *= 0.8; + } + } + else + { + distance *= g_ClassBossHearingSensitivity[classToInt]; + } + + if (distance > SquareFloat(hearRadius)) + { + continue; + } + + if (chaser.GetAlertSoundTriggerCooldown(client) > gameTime && cooldown > 0.0) + { + continue; + } + + chaser.SetAlertSoundTriggerCooldown(client, gameTime + cooldown); + chaser.UpdateAlertTriggerCount(client, addThreshold); + + if (data.AutoChaseEnabled[difficulty] && chaser.GetAutoChaseAddCooldown(client) < gameTime) + { + int count = 0; + switch (soundType) + { + case SoundType_Footstep: + { + count = data.AutoChaseAddFootstep[difficulty]; + } + case SoundType_QuietFootstep: + { + count = data.AutoChaseAddQuietFootstep[difficulty]; + } + case SoundType_LoudFootstep: + { + count = data.AutoChaseAddLoudFootstep[difficulty]; + } + case SoundType_Voice: + { + count = data.AutoChaseAddVoice[difficulty]; + } + case SoundType_Flashlight, SoundType_Weapon: + { + count = data.AutoChaseAddWeapon[difficulty]; + } + } + if (count > 0) + { + chaser.SetAutoChaseAddCooldown(client, gameTime + 0.3); + chaser.UpdateAutoChaseCount(client, chaser.GetAutoChaseCount(client) + count); + } + } + } +} + +static any Native_GetIsValid(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + return bossEntity.IsValid(); +} + +static any Native_GetIsAttemptingToMove(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsAttemptingToMove; +} + +static any Native_GetIsAttacking(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsAttacking; +} + +static any Native_GetIsStunned(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsStunned; +} + +static any Native_GetStunHealth(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.StunHealth; +} + +static any Native_GetMaxStunHealth(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.MaxStunHealth; +} + +static any Native_GetCanBeStunned(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.CanBeStunned(); +} + +static any Native_GetCanTakeDamage(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.CanTakeDamage(); +} + +static any Native_GetIsRaging(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsRaging; +} + +static any Native_GetIsRunningAway(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsRunningAway; +} + +static any Native_GetIsSelfHealing(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsSelfHealing; +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + SF2ChaserBossProfileData data; + data = bossEntity.Controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_PerformVoice(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + char attackName[64]; + GetNativeString(3, attackName, sizeof(attackName)); + return bossEntity.PerformVoice(GetNativeCell(2), attackName); +} + +static any Native_PerformCustomVoice(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + SF2BossProfileSoundInfo soundInfo; + GetNativeArray(2, soundInfo, sizeof(soundInfo)); + return bossEntity.PerformVoiceCooldown(soundInfo, soundInfo.Paths); +} + +static any Native_GetDefaultPosture(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + int bufferSize = GetNativeCell(3); + char[] posture = new char[bufferSize]; + int result = bossEntity.GetDefaultPosture(posture, bufferSize); + SetNativeString(2, posture, bufferSize); + + return result; +} + +static any Native_SetDefaultPosture(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + int bufferSize; + GetNativeStringLength(2, bufferSize); + bufferSize++; + char[] buffer = new char[bufferSize]; + GetNativeString(2, buffer, bufferSize); + bossEntity.SetDefaultPosture(buffer); + + return 0; +} + +static any Native_GetAttackName(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + int bufferSize = GetNativeCell(3); + char[] attack = new char[bufferSize]; + int result = strcopy(attack, bufferSize, bossEntity.GetAttackName()); + SetNativeString(2, attack, bufferSize); + + return result; +} + +static any Native_GetAttackIndex(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.AttackIndex; +} + +static any Native_GetNextAttackTime(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + int bufferSize; + GetNativeStringLength(2, bufferSize); + bufferSize++; + char[] buffer = new char[bufferSize]; + GetNativeString(2, buffer, bufferSize); + + return bossEntity.GetNextAttackTime(buffer); +} + +static any Native_SetNextAttackTime(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + int bufferSize; + GetNativeStringLength(2, bufferSize); + bufferSize++; + char[] buffer = new char[bufferSize]; + GetNativeString(2, buffer, bufferSize); + + float time = GetNativeCell(3); + + bossEntity.SetNextAttackTime(buffer, time); + return 0; +} + +static any Native_DropItem(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + bossEntity.DropItem(GetNativeCell(2)); + + return 0; +} + +static any Native_CreateSoundHint(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + float position[3]; + GetNativeArray(2, position, 3); + bossEntity.UpdateAlertTriggerCountEx(position); + + return 0; +} + +static any Native_GetGroundSpeedOverride(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + return bossEntity.GroundSpeedOverride; +} + +static any Native_SetGroundSpeedOverride(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + bossEntity.GroundSpeedOverride = GetNativeCell(2); + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_alert.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_alert.sp new file mode 100644 index 00000000..8717a3cd --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_alert.sp @@ -0,0 +1,47 @@ +#pragma semicolon 1 + +void InitializePostureOnAlert() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionAlertInfo alertInfo; + alertInfo = postureInfo.AlertInfo; + if (!alertInfo.Enabled[difficulty]) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (chaser.IsValid() && chaser.State == STATE_ALERT) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_cloak.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_cloak.sp new file mode 100644 index 00000000..83e0c159 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_cloak.sp @@ -0,0 +1,47 @@ +#pragma semicolon 1 + +void InitializePostureOnCloak() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionCloakInfo cloakInfo; + cloakInfo = postureInfo.CloakInfo; + if (!cloakInfo.Enabled[difficulty]) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (chaser.IsValid() && chaser.HasCloaked) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_look.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_look.sp new file mode 100644 index 00000000..556d917a --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_look.sp @@ -0,0 +1,46 @@ +#pragma semicolon 1 + +void InitializePostureOnLook() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionLookAtInfo lookAtInfo; + lookAtInfo = postureInfo.LookAtInfo; + if (!lookAtInfo.Enabled[difficulty]) + { + continue; + } + + if (controller.CanBeSeen(_, true)) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/rage_phase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/rage_phase.sp new file mode 100644 index 00000000..46ead006 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/rage_phase.sp @@ -0,0 +1,54 @@ +#pragma semicolon 1 + +void InitializePostureRagePhase() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + SF2PostureConditionRagePhase ragePhaseInfo; + ragePhaseInfo = postureInfo.RagePhaseInfo; + if (!ragePhaseInfo.Enabled) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid() || chaser.RageIndex == -1) + { + continue; + } + + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(chaser.RageIndex, rageInfo, sizeof(rageInfo)); + int index = ragePhaseInfo.Names.FindString(rageInfo.Name); + + if (index != -1) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/running_away.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/running_away.sp new file mode 100644 index 00000000..c9532dd0 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/running_away.sp @@ -0,0 +1,44 @@ +#pragma semicolon 1 + +void InitializePostureRunningAway() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + SF2PostureConditionRunAwayInfo runAwayInfo; + runAwayInfo = postureInfo.RunAwayInfo; + if (!runAwayInfo.Enabled) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (chaser.IsValid() && chaser.IsRunningAway) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_bounds.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_bounds.sp new file mode 100644 index 00000000..1ae3d000 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_bounds.sp @@ -0,0 +1,51 @@ +#pragma semicolon 1 + +void InitializePostureWithinBounds() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + SF2PostureConditionWithinBoundsInfo boundsInfo; + boundsInfo = postureInfo.BoundsInfo; + if (!boundsInfo.Enabled) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + float myPos[3]; + chaser.GetAbsOrigin(myPos); + if (IsSpaceOccupiedIgnorePlayersAndEnts(myPos, boundsInfo.Mins, boundsInfo.Maxs, chaser.index)) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_range.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_range.sp new file mode 100644 index 00000000..1beadd3d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_range.sp @@ -0,0 +1,76 @@ +#pragma semicolon 1 + +void InitializePostureWithinRange() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + float gameTime = GetGameTime(); + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionWithinRangeInfo rangeInfo; + rangeInfo = postureInfo.RangeInfo; + if (!rangeInfo.Enabled[difficulty]) + { + continue; + } + + if (rangeInfo.CurrentCooldown > gameTime) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid() || chaser.State != STATE_CHASE) + { + continue; + } + + if (chaser.MyNextBotPointer().GetLocomotionInterface().GetGroundSpeed() < 0.01) + { + continue; + } + + CBaseEntity target = chaser.Target; + if (!target.IsValid()) + { + continue; + } + + float range = controller.Path.GetLength() - controller.Path.GetCursorPosition(); + + if (range > rangeInfo.MinRange[difficulty] && range < rangeInfo.MaxRange[difficulty]) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + else + { + rangeInfo.CurrentCooldown = gameTime + rangeInfo.Cooldown[difficulty]; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp b/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp new file mode 100644 index 00000000..f2069a50 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp @@ -0,0 +1,12 @@ +#pragma semicolon 1 + +#include "base/entity.sp" +#include "chaser/entity.sp" +#include "statue/entity.sp" + +void SetupEntityActions() +{ + SF2_BaseBoss.Initialize(); + SF2_ChaserEntity.Initialize(); + SF2_StatueEntity.Initialize(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp new file mode 100644 index 00000000..74817078 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp @@ -0,0 +1,183 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueChaseAction < NextBotAction +{ + public SF2_StatueChaseAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Chase"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_StatueChaseAction action, SF2_StatueEntity actor, NextBotAction priorAction) +{ + SF2NPC_Statue controller = actor.Controller; + int difficulty = controller.Difficulty; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + if (actor.InitialChaseDuration > 0.0) + { + actor.CurrentChaseDuration = actor.InitialChaseDuration; + actor.InitialChaseDuration = 0.0; + } + + actor.State = STATE_CHASE; + return action.Continue(); +} + +static int Update(SF2_StatueChaseAction action, SF2_StatueEntity actor) +{ + if (actor.CurrentChaseDuration <= 0.0) + { + actor.Target = CBaseEntity(-1); + actor.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + return action.ChangeTo(SF2_StatueIdleAction(), "Oh he got away..."); + } + + CBaseEntity target = actor.Target; + if (!target.IsValid()) + { + return action.ChangeTo(SF2_StatueIdleAction(), "No target? No problem, idle."); + } + + float gameTime = GetGameTime(); + + if (actor.LastKillTime > gameTime) + { + return action.Continue(); + } + + SF2NPC_Statue controller = actor.Controller; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + int difficulty = controller.Difficulty; + INextBot bot = actor.MyNextBotPointer(); + PathFollower path = controller.Path; + ILocomotion loco = bot.GetLocomotionInterface(); + float myPos[3]; + actor.GetAbsOrigin(myPos); + + actor.IsMoving = !controller.CanBeSeen(true, true, !attackEliminated) && actor.LastKillTime <= gameTime; + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && player.HasEscaped) + { + return action.ChangeTo(SF2_StatueIdleAction(), "Our target escaped, that is no good!"); + } + + if (player.IsValid && player.ShouldBeForceChased(controller)) + { + player.SetForceChaseState(controller, false); + } + + bool visible = (actor.InterruptConditions & COND_ENEMYVISIBLE) != 0; + + if (visible) + { + float maxRange = data.ChaseDurationAddMaxRange[difficulty]; + if (maxRange > 0.0 && player.IsValid && player.CanSeeSlender(controller.Index, false, _, !attackEliminated)) + { + float distanceRatio = bot.GetRangeTo(player.index) / maxRange; + if (distanceRatio < 1.0) + { + float durationTimeAddMin = data.ChaseDurationAddVisibilityMin[difficulty]; + float durationTimeAddMax = data.ChaseDurationAddVisibilityMax[difficulty]; + float durationAdd = durationTimeAddMin + ((durationTimeAddMax - durationTimeAddMin) * distanceRatio); + + actor.CurrentChaseDuration += durationAdd * GetGameFrameTime(); + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + } + else + { + actor.CurrentChaseDuration -= GetGameFrameTime(); + } + } + else + { + actor.CurrentChaseDuration -= GetGameFrameTime(); + } + + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + + bool tooClose = target.IsValid() && + visible && + bot.IsRangeLessThan(target.index, 8.0); + + if ((tooClose || !actor.IsMoving) && path.IsValid()) + { + path.Invalidate(); + } + else + { + float pos[3]; + target.GetAbsOrigin(pos); + if (actor.Teleporters.Length > 0) + { + CBaseEntity(actor.Teleporters.Get(0)).GetAbsOrigin(pos); + } + + if (!bot.IsRangeLessThanEx(pos, 8.0) && actor.IsMoving) + { + if (path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) + { + path.ComputeToPos(bot, pos); + } + } + } + + if (!path.IsValid()) + { + loco.Stop(); + } + else + { + path.Update(bot); + } + + if (actor.IsMoving) + { + g_SlenderStatueIdleLifeTime[controller.Index] = gameTime + data.IdleLifeTime[difficulty]; + + if (bot.GetRangeSquaredTo(target.index) <= Pow(originalData.InstantKillRadius, 2.0) && visible) + { + if (controller.Flags & SFF_FAKE) + { + controller.MarkAsFake(); + return action.Done("Faker"); + } + else + { + actor.LastKillTime = gameTime + originalData.InstantKillCooldown[difficulty]; + player.StartDeathCam(controller.Index, myPos); + } + } + } + return action.Continue(); +} + +static void OnEnd(SF2_StatueChaseAction action, SF2_StatueEntity actor) +{ + if (actor.Teleporters != null) + { + actor.Teleporters.Clear(); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/idle.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/idle.sp new file mode 100644 index 00000000..c21f6dff --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/idle.sp @@ -0,0 +1,39 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueIdleAction < NextBotAction +{ + public SF2_StatueIdleAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Idle"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_StatueIdleAction action, SF2_StatueEntity actor, NextBotAction priorAction) +{ + actor.State = STATE_IDLE; + return action.Continue(); +} + +static int Update(SF2_StatueIdleAction action, SF2_StatueEntity actor) +{ + CBaseEntity target = actor.Target; + if (actor.LastKillTime > GetGameTime()) + { + return action.Continue(); + } + + if (target.IsValid()) + { + return action.ChangeTo(SF2_StatueChaseAction(), "I sense someone, lock onto them!"); + } + actor.IsMoving = false; + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp new file mode 100644 index 00000000..a424187c --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp @@ -0,0 +1,281 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueBaseAction < NextBotAction +{ + public SF2_StatueBaseAction() + { + return view_as(g_Factory.Create()); + } + + public static NextBotActionFactory GetFactory() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Main"); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + } + return g_Factory; + } +} + +static int Update(SF2_StatueBaseAction action, SF2_StatueEntity actor, float interval) +{ + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + INextBot bot = npc.GetBot(); + + SF2NPC_Statue controller = actor.Controller; + int bossIndex = controller.Index; + + int difficulty = g_DifficultyConVar.IntValue; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + controller.GetProfile(profile, sizeof(profile)); + + bool attackWaiters = !!(controller.Flags & SFF_ATTACKWAITERS); + + float gameTime = GetGameTime(); + float chaseDurationTimeAddMin = controller.AddChaseDurationMin(difficulty); + float chaseDurationTimeAddMax = controller.AddChaseDurationMax(difficulty); + bool move = false; + if (PeopleCanSeeSlender(bossIndex, true, true, !attackWaiters)) + { + if (actor.ChaseTime < gameTime) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + actor.ChaseTime += chaseDurationTimeAddMin; + if (actor.ChaseTime > (gameTime + controller.GetChaseDuration(difficulty))) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + for (int i = 0; i < MAX_NPCTELEPORTER; i++) + { + if (controller.GetTeleporter(i) != INVALID_ENT_REFERENCE) + { + controller.SetTeleporter(i, INVALID_ENT_REFERENCE); + } + } + } + + SF2_BasePlayer origTarget = SF2_BasePlayer(actor.Target); + if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap() && origTarget.IsValid) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + + if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap()) + { + if (!origTarget.IsValid || (origTarget.IsValid && origTarget.IsEliminated)) + { + if (NPCAreAvailablePlayersAlive()) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || + !IsPlayerAlive(i) || + g_PlayerEliminated[i] || + IsClientInGhostMode(i) || + DidClientEscape(i)) + { + continue; + } + arrayRaidTargets.Push(i); + } + if (arrayRaidTargets.Length > 0) + { + SF2_BasePlayer raidTarget = SF2_BasePlayer(arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1))); + if (raidTarget.IsValid && !raidTarget.IsEliminated) + { + actor.Target = raidTarget.index; + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + } + delete arrayRaidTargets; + } + } + } + + int bestPlayer = -1; + ArrayList array = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || !IsPlayerAlive(i) || + IsClientInDeathCam(i) || (!attackWaiters && g_PlayerEliminated[i]) || + DidClientEscape(i) || IsClientInGhostMode(i) || + !PlayerCanSeeSlender(i, bossIndex, false, false, !attackWaiters)) + { + continue; + } + + if (!NPCShouldSeeEntity(bossIndex, i)) + { + continue; + } + + array.Push(i); + } + + if (array.Length > 0) + { + float slenderPos[3]; + actor.GetAbsOrigin(slenderPos); + + float tempPos[3]; + int tempPlayer = -1; + float tempDist = SquareFloat(16384.0); + for (int i = 0; i < array.Length; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(array.Get(i)); + client.GetAbsOrigin(tempPos); + if (GetVectorSquareMagnitude(tempPos, slenderPos) < tempDist) + { + tempPlayer = client.index; + tempDist = GetVectorSquareMagnitude(tempPos, slenderPos); + } + if (SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(tempPos, slenderPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) + { + client.Stun(SPECIALROUND_BOO_DURATION, 0.0, TF_STUNFLAGS_GHOSTSCARE); + } + } + + bestPlayer = tempPlayer; + if (bestPlayer != -1) + { + actor.Target = bestPlayer; + if (actor.ChaseTime < gameTime) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + } + } + + delete array; + + if (!PeopleCanSeeSlender(bossIndex, true, true, !attackWaiters)) + { + SF2_BasePlayer target = SF2_BasePlayer(actor.Target); + if (IsTargetValidForSlender(target, attackWaiters) && gameTime < actor.ChaseTime && (gameTime - actor.LastKillTime) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) + { + move = true; + float slenderPos[3], pos[3]; + actor.GetAbsOrigin(slenderPos); + target.GetAbsOrigin(pos); + if (NPCGetTeleporter(bossIndex, 0) != INVALID_ENT_REFERENCE) + { + CBaseEntity teleporter = CBaseEntity(EntRefToEntIndex(NPCGetTeleporter(bossIndex, 0))); + if (teleporter.IsValid() && teleporter.index > MaxClients) + { + teleporter.GetAbsOrigin(pos); + } + } + controller.Path.ComputeToPos(bot, pos); + + float maxRange = NPCStatueGetModelChangeDistance(bossIndex, difficulty); + float dist = GetVectorSquareMagnitude(slenderPos, pos); + + char buffer[PLATFORM_MAX_PATH]; + + if (dist < SquareFloat(maxRange * 0.33)) + { + controller.GetModel(2, buffer, sizeof(buffer)); + } + else if (dist < SquareFloat(maxRange * 0.66)) + { + controller.GetModel(1, buffer, sizeof(buffer)); + } + else + { + controller.GetModel(_, buffer, sizeof(buffer)); + } + + // Fallback if error. + if (buffer[0] == '\0' || strcmp(buffer, "models/") == 0) + { + controller.GetModel(_, buffer, sizeof(buffer)); + } + + actor.SetModel(buffer); + + if (target.CanSeeSlender(bossIndex, false, !attackWaiters)) + { + float distRatio = (dist / SquareFloat(maxRange)); + + float chaseDurationAdd = chaseDurationTimeAddMax - ((chaseDurationTimeAddMax - chaseDurationTimeAddMin) * distRatio); + + if (chaseDurationAdd > 0.0) + { + actor.ChaseTime += chaseDurationAdd; + if (actor.ChaseTime > (gameTime + controller.GetChaseDuration(difficulty))) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + } + } + + if (dist <= SquareFloat(controller.InstantKillRadius)) + { + if (controller.Flags & SFF_FAKE) + { + controller.MarkAsFake(); + return action.Done("Faker"); + } + else + { + actor.LastKillTime = gameTime; + target.StartDeathCam(bossIndex, pos); + g_LastStuckTime[bossIndex] = gameTime + controller.GetInstantKillCooldown(difficulty) + 0.1; + if (controller.GetInstantKillCooldown(difficulty) > 0.0) + { + actor.IsMoving = false; + loco.ClearStuckStatus(); + } + actor.Target = -1; + } + } + + g_SlenderStatueIdleLifeTime[bossIndex] = gameTime + controller.GetIdleLifetime(difficulty); + } + else + { + float origin[3]; + actor.GetAbsOrigin(origin); + origin[2] -= 10.0; + g_LastPos[bossIndex] = origin; + } + } + + SF2BossProfileSoundInfo soundInfo; + if (move) + { + ArrayList soundList; + char buffer[PLATFORM_MAX_PATH]; + GetStatueProfileSingleMoveSounds(profile, soundInfo); + soundInfo.EmitSound(_, actor.index); + + GetStatueProfileMoveSounds(profile, soundInfo); + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); + if (buffer[0] != '\0') + { + EmitSoundToAll(buffer, actor.index, soundInfo.Channel, soundInfo.Level, SND_CHANGEVOL, soundInfo.Volume, soundInfo.Pitch); + } + } + soundList = null; + } + else + { + GetStatueProfileMoveSounds(profile, soundInfo); + soundInfo.StopAllSounds(actor.index); + } + actor.IsMoving = move; + + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp new file mode 100644 index 00000000..03b2cd07 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp @@ -0,0 +1,180 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueBaseAction < NextBotAction +{ + public SF2_StatueBaseAction() + { + return view_as(g_Factory.Create()); + } + + public static NextBotActionFactory GetFactory() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Main"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_LastStuckTime") + .DefineVectorField("m_LastPos") + .DefineVectorField("m_UnstuckPos") + .EndDataMapDesc(); + } + return g_Factory; + } + + property float LastStuckTime + { + public get() + { + return this.GetDataFloat("m_LastStuckTime"); + } + + public set(float value) + { + this.SetDataFloat("m_LastStuckTime", value); + } + } + + public void GetLastPos(float pos[3]) + { + this.GetDataVector("m_LastPos", pos); + } + + public void SetLastPos(const float pos[3]) + { + this.SetDataVector("m_LastPos", pos); + } + + public void GetUnstuckPos(float pos[3]) + { + this.GetDataVector("m_UnstuckPos", pos); + } + + public void SetUnstuckPos(const float pos[3]) + { + this.SetDataVector("m_UnstuckPos", pos); + } +} + +static NextBotAction InitialContainedAction(SF2_StatueBaseAction action, SF2_StatueEntity actor) +{ + return SF2_StatueIdleAction(); +} + +static int Update(SF2_StatueBaseAction action, SF2_StatueEntity actor, float interval) +{ + SF2NPC_Statue controller = actor.Controller; + if (controller.IsValid() && (controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return action.Done("I'm a faker"); + } + + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + SF2BossProfileSoundInfo soundInfo; + CBaseEntity target = actor.Target; + float myPos[3]; + actor.GetAbsOrigin(myPos); + int difficulty = controller.Difficulty; + + if (actor.LastKillTime > GetGameTime()) + { + actor.IsMoving = false; + } + + if (actor.IsMoving) + { + soundInfo = data.SingleMoveSounds; + soundInfo.EmitSound(_, actor.index); + soundInfo = data.MoveSounds; + soundInfo.EmitSound(_, actor.index); + + if (target.IsValid()) + { + float targetPos[3]; + target.GetAbsOrigin(targetPos); + + float distance = GetVectorSquareMagnitude(targetPos, myPos); + float maxRange = Pow(data.ModelChangeDistanceMax[difficulty], 2.0); + char model[PLATFORM_MAX_PATH]; + if (distance < maxRange * 0.33) + { + data.ModelsCloseDist.GetString(difficulty, model, sizeof(model)); + } + else if (distance < maxRange * 0.66) + { + data.ModelsAverageDist.GetString(difficulty, model, sizeof(model)); + } + else + { + originalData.Models.GetString(difficulty, model, sizeof(model)); + } + + if (model[0] == '\0' || strcmp(model, "models/") == 0) + { + originalData.Models.GetString(difficulty, model, sizeof(model)); + } + + actor.SetModel(model); + } + } + else + { + soundInfo = data.MoveSounds; + soundInfo.StopAllSounds(actor.index); + } + + UnstuckCheck(action, actor); + + return action.Continue(); +} + +static void UnstuckCheck(SF2_StatueBaseAction action, SF2_StatueEntity actor) +{ + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Statue controller = actor.Controller; + PathFollower path = controller.Path; + float gameTime = GetGameTime(); + + if (!path.IsValid() || !actor.IsMoving || loco.GetDesiredSpeed() <= 0.0) + { + action.LastStuckTime = gameTime; + return; + } + + float lastPos[3], myPos[3]; + action.GetLastPos(lastPos); + actor.GetAbsOrigin(myPos); + + if (bot.IsRangeLessThanEx(lastPos, 0.13) || loco.GetGroundSpeed() <= 0.1) + { + if (action.LastStuckTime > gameTime - 0.75) + { + return; + } + float destination[3]; + if (!NPCFindUnstuckPosition(actor, lastPos, destination)) + { + controller.UnSpawn(true); + return; + } + action.LastStuckTime = gameTime + 0.75; + actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); + } + else + { + loco.GetFeet(lastPos); + action.SetLastPos(lastPos); + action.LastStuckTime += 0.03; + if (action.LastStuckTime > gameTime) + { + action.LastStuckTime = gameTime; + } + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp new file mode 100644 index 00000000..299c486d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -0,0 +1,582 @@ +#pragma semicolon 1 + +#include "actions/mainlayer.sp" +#include "actions/idle.sp" +#include "actions/chase.sp" + +static CEntityFactory g_Factory; + +methodmap SF2_StatueEntity < SF2_BaseBoss +{ + public SF2_StatueEntity(int entity) + { + return view_as(entity); + } + + public bool IsValid() + { + if (!CBaseCombatCharacter(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_npc_boss_statue", OnCreate); + g_Factory.DeriveFromFactory(SF2_BaseBoss.GetFactory()); + g_Factory.SetInitialActionFactory(SF2_StatueBaseAction.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_IsMoving") + .DefineFloatField("m_LastKillTime") + .EndDataMapDesc(); + g_Factory.Install(); + } + + property SF2NPC_Statue Controller + { + public get() + { + return view_as(view_as(this).Controller); + } + + public set(SF2NPC_Statue controller) + { + view_as(this).Controller = controller; + } + } + + property bool IsMoving + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_IsMoving")); + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsMoving", value); + } + } + property float LastKillTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LastKillTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LastKillTime", value); + } + } + + public void DoAlwaysLookAt(CBaseEntity target) + { + if (!target.IsValid()) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && !player.IsAlive) + { + return; + } + + INextBot bot = this.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + if ((this.InterruptConditions & COND_ENEMYVISIBLE) == 0) + { + return; + } + + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); + } + + public static SF2_StatueEntity Create(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) + { + SF2_StatueEntity statue = SF2_StatueEntity(CreateEntityByName("sf2_npc_boss_statue")); + if (!statue.IsValid()) + { + return SF2_StatueEntity(-1); + } + if (controller == SF2_INVALID_NPC) + { + return SF2_StatueEntity(-1); + } + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + controller.GetProfile(profile, sizeof(profile)); + statue.Controller = view_as(controller); + + SF2BossProfileData originalData; + originalData = view_as(statue.Controller).GetProfileData(); + + char buffer[PLATFORM_MAX_PATH]; + + GetSlenderModel(controller.Index, _, buffer, sizeof(buffer)); + statue.SetModel(buffer); + statue.SetRenderMode(view_as(g_SlenderRenderMode[controller.Index])); + statue.SetRenderFx(view_as(g_SlenderRenderFX[controller.Index])); + statue.SetRenderColor(g_SlenderRenderColor[controller.Index][0], g_SlenderRenderColor[controller.Index][1], + g_SlenderRenderColor[controller.Index][2], g_SlenderRenderColor[controller.Index][3]); + + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + float scaleModel = controller.ModelScale * 0.5; + statue.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); + } + else + { + statue.SetPropFloat(Prop_Send, "m_flModelScale", controller.ModelScale); + } + + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(statue.index); + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + + npc.flMaxYawRate = 0.0; + npc.flStepSize = 18.0; + npc.flGravity = 800.0; + npc.flDeathDropHeight = 99999.0; + npc.flJumpHeight = 512.0; + npc.flMaxYawRate = originalData.TurnRate; + loco.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); + loco.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); + + statue.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); + statue.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); + + statue.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); + statue.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); + + npc.SetBodyMins(HULL_HUMAN_MINS); + npc.SetBodyMaxs(HULL_HUMAN_MAXS); + + statue.SetProp(Prop_Data, "m_nSolidType", SOLID_BBOX); + SetEntityCollisionGroup(statue.index, COLLISION_GROUP_DEBRIS_TRIGGER); + + statue.Teleport(pos, ang, NULL_VECTOR); + + statue.Spawn(); + statue.Activate(); + + return statue; + } + + public static void SetupAPI() + { + CreateNative("SF2_StatueBossEntity.IsValid.get", Native_GetIsValid); + CreateNative("SF2_StatueBossEntity.IsMoving.get", Native_GetIsMoving); + CreateNative("SF2_StatueBossEntity.LastKillTime.get", Native_GetLastKillTime); + CreateNative("SF2_StatueBossEntity.ProfileData", Native_GetProfileData); + } +} + +static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) +{ + return false; +} + +static void OnCreate(SF2_StatueEntity ent) +{ + ent.LastKillTime = 0.0; + SDKHook(ent.index, SDKHook_Think, Think); + SDKHook(ent.index, SDKHook_ThinkPost, ThinkPost); + SetEntityTransmitState(ent.index, FL_EDICT_FULLCHECK); + g_DHookShouldTransmit.HookEntity(Hook_Pre, ent.index, ShouldTransmit); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent.index, UpdateTransmitState); +} + +static Action Think(int entIndex) +{ + SF2_StatueEntity statue = SF2_StatueEntity(entIndex); + int interruptConditions = 0; + CBaseEntity target = ProcessVision(statue, interruptConditions); + statue.InterruptConditions |= interruptConditions; + statue.Target = target; + + if (statue.IsMoving) + { + statue.DoAlwaysLookAt(statue.Target); + } + + statue.CheckVelocityCancel(); + + return Plugin_Continue; +} + +static void ThinkPost(int entIndex) +{ + SF2_StatueEntity statue = SF2_StatueEntity(entIndex); + + ProcessSpeed(statue); + + if (NPCGetCustomOutlinesState(statue.Controller.Index) && NPCGetRainbowOutlineState(statue.Controller.Index)) + { + statue.ProcessRainbowOutline(); + } + + if (statue.IsMoving) + { + statue.DoAlwaysLookAt(statue.Target); + } + + statue.InterruptConditions = 0; + statue.SetNextThink(GetGameTime()); +} + +static MRESReturn UpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = SetEntityTransmitState(entIndex, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn ShouldTransmit(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} + +static CBaseEntity ProcessVision(SF2_StatueEntity statue, int &interruptConditions = 0) +{ + interruptConditions = 0; + SF2NPC_Statue controller = statue.Controller; + if (!controller.IsValid()) + { + return CBaseEntity(-1); + } + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int difficulty = controller.Difficulty; + + float playerDists[MAXTF2PLAYERS]; + int playerInterruptFlags[MAXTF2PLAYERS]; + + float traceMins[3] = { -16.0, ... }; + traceMins[2] = 0.0; + float traceMaxs[3] = { 16.0, ... }; + traceMaxs[2] = 0.0; + float myEyeAng[3]; + statue.GetAbsAngles(myEyeAng); + float traceStartPos[3], traceEndPos[3], myPos[3], targetPos[3]; + controller.GetEyePosition(traceStartPos); + statue.GetAbsOrigin(myPos); + + int oldTarget = statue.OldTarget.index; + if (!IsTargetValidForSlender(statue, CBaseEntity(oldTarget), attackEliminated)) + { + statue.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + + int bestNewTarget = oldTarget; + float searchRange = originalData.SearchRange[difficulty]; + float bestNewTargetDist = Pow(searchRange, 2.0); + if (IsValidEntity(bestNewTarget)) + { + CBaseEntity(bestNewTarget).GetAbsOrigin(targetPos); + bestNewTargetDist = GetVectorSquareMagnitude(myPos, targetPos); + if (bestNewTargetDist > Pow(searchRange, 2.0)) + { + bestNewTargetDist = Pow(searchRange, 2.0); + } + } + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + + #if defined DEBUG + if (client.IsValid && g_PlayerDebugFlags[client.index] & DEBUG_BOSS_EYES) + { + for (int i2 = 1; i2 <= MaxClients; i2++) + { + if (!IsTargetValidForSlender(statue, SF2_BasePlayer(i2), attackEliminated)) + { + continue; + } + float eyes[3]; + SF2_BasePlayer(i2).GetEyePosition(eyes); + int color[4] = { 0, 255, 0, 255 }; + TE_SetupBeamPoints(traceStartPos, eyes, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); + TE_SendToClient(client.index); + } + } + #endif + + if (!IsTargetValidForSlender(statue, client, attackEliminated)) + { + continue; + } + + statue.SetIsVisible(client, false); + statue.SetInFOV(client, false); + statue.SetIsNear(client, false); + + client.GetEyePosition(traceEndPos); + + float dist = 99999999999.9; + + bool isVisible = false; + int traceHitEntity; + TR_TraceHullFilter(traceStartPos, + traceEndPos, + traceMins, + traceMaxs, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, + TraceRayBossVisibility, + statue.index); + + isVisible = !TR_DidHit(); + traceHitEntity = TR_GetEntityIndex(); + + if (!isVisible && traceHitEntity == client.index) + { + isVisible = true; + } + + if (isVisible) + { + isVisible = NPCShouldSeeEntity(controller.Index, client.index); + } + + dist = GetVectorSquareMagnitude(traceStartPos, traceEndPos); + + if (g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) + { + int fogEntity = client.GetDataEnt(g_PlayerFogCtrlOffset); + if (IsValidEdict(fogEntity)) + { + if (GetEntData(fogEntity, g_FogCtrlEnableOffset) && + dist >= SquareFloat(GetEntDataFloat(fogEntity, g_FogCtrlEndOffset))) + { + isVisible = false; + } + } + } + + if (dist > Pow(originalData.SearchRange[difficulty], 2.0)) + { + isVisible = false; + } + + statue.SetIsVisible(client, isVisible); + + if (statue.GetIsVisible(client) && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) + { + TF2_StunPlayer(client.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); + } + + if (client.ShouldBeForceChased(controller)) + { + bestNewTarget = client.index; + playerInterruptFlags[client.index] |= COND_ENEMYRECHASE; + } + + if (statue.GetIsVisible(client)) + { + playerInterruptFlags[client.index] |= COND_ENEMYVISIBLE; + } + + if (client.index != oldTarget) + { + playerInterruptFlags[client.index] |= COND_NEWENEMY; + } + + playerDists[client.index] = dist; + + if (statue.GetIsVisible(client)) + { + if (dist <= SquareFloat(searchRange)) + { + if (dist < bestNewTargetDist) + { + bestNewTarget = client.index; + bestNewTargetDist = dist; + playerInterruptFlags[client.index] |= COND_SAWENEMY; + } + } + } + } + + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || g_RenevantBossesChaseEndlessly) + { + if (!IsTargetValidForSlender(statue, SF2_BasePlayer(bestNewTarget), attackEliminated)) + { + if (statue.State != STATE_CHASE && NPCAreAvailablePlayersAlive()) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || + !IsPlayerAlive(i) || + g_PlayerEliminated[i] || + IsClientInGhostMode(i) || + DidClientEscape(i)) + { + continue; + } + arrayRaidTargets.Push(i); + } + if (arrayRaidTargets.Length > 0) + { + int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); + if (IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) + { + bestNewTarget = raidTarget; + SF2_BasePlayer(bestNewTarget).SetForceChaseState(controller, true); + } + } + delete arrayRaidTargets; + } + } + statue.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + + if (bestNewTarget != INVALID_ENT_REFERENCE) + { + interruptConditions = playerInterruptFlags[bestNewTarget]; + statue.OldTarget = CBaseEntity(bestNewTarget); + } + + return CBaseEntity(bestNewTarget); +} + +static void ProcessSpeed(SF2_StatueEntity statue) +{ + SF2NPC_Statue controller = statue.Controller; + SF2NPC_BaseNPC baseController = view_as(controller); + int difficulty = controller.Difficulty; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(statue.index); + SF2BossProfileData originalData; + originalData = baseController.GetProfileData(); + + float speed, acceleration; + + acceleration = originalData.Acceleration[difficulty]; + if (controller.HasAttribute(SF2Attribute_ReducedAccelerationOnLook) && controller.CanBeSeen(_, true)) + { + acceleration *= controller.GetAttributeValue(SF2Attribute_ReducedAccelerationOnLook); + } + acceleration += controller.GetAddAcceleration(); + + speed = originalData.RunSpeed[difficulty] * 10.0; // Backwards compatibility + if (controller.HasAttribute(SF2Attribute_ReducedSpeedOnLook) && controller.CanBeSeen(_, true)) + { + speed *= controller.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); + } + + speed += baseController.GetAddSpeed(); + + float forwardSpeed = speed; + Action action = Plugin_Continue; + Call_StartForward(g_OnBossGetSpeedFwd); + Call_PushCell(controller.Index); + Call_PushFloatRef(forwardSpeed); + Call_Finish(action); + if (action == Plugin_Changed) + { + speed = forwardSpeed; + } + + if (g_InProxySurvivalRageMode) + { + speed *= 1.25; + } + + speed = (speed + (speed * GetDifficultyModifier(difficulty)) / 15.0); + acceleration = (acceleration + (acceleration * GetDifficultyModifier(difficulty)) / 15.0); + + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) + { + if (speed < 520.0) + { + speed = 520.0; + } + acceleration += 9001.0; + } + if (SF_IsSlaughterRunMap()) + { + float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; + if (!originalData.SlaughterRunData.CustomMinimumSpeed[difficulty] && speed < slaughterSpeed) + { + speed = slaughterSpeed; + } + acceleration += 10000.0; + } + + if ((!originalData.IsPvEBoss && IsBeatBoxBeating(2)) || statue.IsKillingSomeone || !statue.IsMoving) + { + speed = 0.0; + } + + npc.flWalkSpeed = speed * 0.9; + npc.flRunSpeed = speed; + npc.flAcceleration = acceleration; +} + +static any Native_GetIsValid(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + SF2_StatueEntity bossEntity = SF2_StatueEntity(entity); + return bossEntity.IsValid(); +} + +static any Native_GetIsMoving(Handle plugin, int numParams) +{ + int ent = GetNativeCell(1); + if (!IsValidEntity(ent)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", ent); + } + + SF2_StatueEntity boss = SF2_StatueEntity(ent); + return boss.IsMoving; +} + +static any Native_GetLastKillTime(Handle plugin, int numParams) +{ + int ent = GetNativeCell(1); + if (!IsValidEntity(ent)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", ent); + } + + SF2_StatueEntity boss = SF2_StatueEntity(ent); + return boss.LastKillTime; +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_StatueEntity bossEntity = SF2_StatueEntity(entity); + + SF2StatueBossProfileData data; + data = bossEntity.Controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/npc/glow.sp b/addons/sourcemod/scripting/sf2/npc/glow.sp new file mode 100644 index 00000000..627000a1 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/glow.sp @@ -0,0 +1,191 @@ +static const int g_DefaultColor[4] = { 150, 0, 255, 255 }; + +void SetupNPCGlows() +{ + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyChange); + g_OnSpecialRoundStartPFwd.AddFunction(null, OnSpecialRoundStart); + g_OnBossSpawnPFwd.AddFunction(null, OnBossSpawn); + g_OnPlayerChangePlayStatePFwd.AddFunction(null, OnPlayerChangePlayState); + g_OnPlayerChangeGhostStatePFwd.AddFunction(null, OnPlayerChangeGhostState); + g_OnPlayerChangeProxyStatePFwd.AddFunction(null, OnPlayerChangeProxyState); + g_OnPlayerTurnOnFlashlightPFwd.AddFunction(null, OnPlayerTurnOnFlashlight); + g_OnPlayerTurnOffFlashlightPFwd.AddFunction(null, OnPlayerTurnOffFlashlight); + g_OnPlayerFlashlightBreakPFwd.AddFunction(null, OnPlayerFlashlightBreak); + g_OnRenevantTriggerWavePFwd.AddFunction(null, OnRenevantTriggerWave); + g_OnWallHaxDebugPFwd.AddFunction(null, OnWallHaxDebug); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnDifficultyChange(int oldDifficulty, int newDifficulty) +{ + +} + +static void OnSpecialRoundStart(int specialRound) +{ + if (SF_SpecialRound(SPECIALROUND_WALLHAX)) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static void OnBossSpawn(SF2NPC_BaseNPC controller) +{ + SF2BossProfileData data; + data = controller.GetProfileData(); + if (data.IsPvEBoss) + { + return; + } + + int color[4]; + color = g_DefaultColor; + if (data.CustomOutlines) + { + color = data.OutlineColor; + } + CreateGlowEntity(controller.EntIndex, color); + UpdateVisibility(controller); +} + +static void OnPlayerChangePlayState(SF2_BasePlayer client, bool state, bool queue) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangeGhostState(SF2_BasePlayer client, bool state) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangeProxyState(SF2_BasePlayer client, bool state) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerTurnOnFlashlight(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerTurnOffFlashlight(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerFlashlightBreak(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnRenevantTriggerWave(int wave, RenevantWave condition) +{ + if (condition == RenevantWave_WallHax) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static void OnWallHaxDebug() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + UpdateVisibilityOfOtherGlows(client); + } +} + +static bool ShouldBeVisibleToPlayer(SF2NPC_BaseNPC controller, SF2_BasePlayer other) +{ + CBaseEntity bossEntity = CBaseEntity(controller.EntIndex); + if (!bossEntity.IsValid()) + { + return false; + } + + if (!other.IsEliminated) + { + if (IsNightVisionEnabled() && g_NightVisionType == 2 && other.UsingFlashlight) + { + return true; + } + return SF_SpecialRound(SPECIALROUND_WALLHAX) || g_RenevantWallHax || g_EnableWallHaxConVar.BoolValue; + } + + return other.IsProxy || other.IsInGhostMode || other.IsSourceTV; +} + +static void UpdateVisibility(SF2NPC_BaseNPC controller) +{ + CBaseEntity bossEntity = CBaseEntity(controller.EntIndex); + if (!bossEntity.IsValid()) + { + return; + } + + int clients[MAXPLAYERS + 1]; + int numClients = 0; + bool states[MAXPLAYERS + 1]; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer other = SF2_BasePlayer(i); + if (!other.IsValid) + { + continue; + } + + int i2 = numClients++; + clients[i2] = i; + states[i2] = ShouldBeVisibleToPlayer(controller, other); + } + + SetGlowVisibility(bossEntity.index, clients, numClients, states); +} + +static void UpdateVisibilityOfOtherGlows(SF2_BasePlayer client) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i); + if (!controller.IsValid()) + { + continue; + } + + CBaseEntity bossEntity = CBaseEntity(controller.EntIndex); + if (!bossEntity.IsValid()) + { + continue; + } + + SetGlowVisibilityForClient(bossEntity.index, client.index, ShouldBeVisibleToPlayer(controller, client)); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index 81ccd03b..2d8ffd0c 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -5,326 +5,46 @@ #pragma semicolon 1 -bool g_NpcOriginalVisibility[MAX_BOSSES]; +static SF2ChaserBossProfileData g_NpcChaserProfileData[MAX_BOSSES]; -static float g_NpcWalkSpeed[MAX_BOSSES][Difficulty_Max]; -static float g_NpcMaxWalkSpeed[MAX_BOSSES][Difficulty_Max]; - -static float g_NpcWakeRadius[MAX_BOSSES]; - -static bool g_NpcHasStunEnabled[MAX_BOSSES]; -static float g_NpcStunCooldown[MAX_BOSSES]; -static bool g_NpcStunFlashlightEnabled[MAX_BOSSES]; -static bool g_NpcHasKeyDrop[MAX_BOSSES]; -static float g_NpcStunFlashlightDamage[MAX_BOSSES]; -static float g_NpcStunInitialHealth[MAX_BOSSES]; static float g_NpcStunAddHealth[MAX_BOSSES]; -static float g_NpcStunHealth[MAX_BOSSES]; -static bool g_NpcChaseInitialOnStun[MAX_BOSSES]; + +static float g_NpcDeathInitialHealth[MAX_BOSSES][Difficulty_Max]; +static float g_NpcDeathHealth[MAX_BOSSES][Difficulty_Max]; static float g_NpcAlertGracetime[MAX_BOSSES][Difficulty_Max]; static float g_NpcAlertDuration[MAX_BOSSES][Difficulty_Max]; static float g_NpcChaseDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcCloakEnabled[MAX_BOSSES]; -static float g_NpcCloakCooldown[MAX_BOSSES][Difficulty_Max]; -static float g_NpcCloakRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcDecloakRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcCloakDuration[MAX_BOSSES][Difficulty_Max]; -float g_NpcNextDecloakTime[MAX_BOSSES]; -static float g_NpcCloakSpeedMultiplier[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcChaseOnLook[MAX_BOSSES]; ArrayList g_NpcChaseOnLookTarget[MAX_BOSSES] = { null, ... }; -static bool g_NpcHasProjectileEnabled[MAX_BOSSES]; -static float g_IceballSlowdownDuration[MAX_BOSSES][Difficulty_Max]; -static float g_IceballSlowdownPercent[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileCooldownMin[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileCooldownMax[MAX_BOSSES][Difficulty_Max]; -float g_NpcProjectileCooldown[MAX_BOSSES]; -static float g_NpcProjectileSpeed[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileDamage[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileRadius[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileDeviation[MAX_BOSSES][Difficulty_Max]; -static int g_NpcProjectileCount[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasCriticalRockets[MAX_BOSSES]; -static bool g_NpcHasUseShootGesture[MAX_BOSSES]; -static bool g_NpcHasUseProjectileAmmo[MAX_BOSSES]; -static int g_NpcProjectileLoadedAmmo[MAX_BOSSES][Difficulty_Max]; -int g_NpcProjectileAmmo[MAX_BOSSES]; -static float g_NpcProjectileReloadTime[MAX_BOSSES][Difficulty_Max]; -float g_NpcProjectileTimeToReload[MAX_BOSSES]; -static bool g_NpcHasUseChargeUpProjectiles[MAX_BOSSES]; -static float g_NpcProjectileChargeUpTime[MAX_BOSSES][Difficulty_Max]; -static int g_NpcProjectileType[MAX_BOSSES]; -bool g_NpcReloadingProjectiles[MAX_BOSSES]; - -bool g_NpcHasAlwaysLookAtTarget[MAX_BOSSES]; -bool g_NpcHasAlwaysLookAtTargetWhileAttacking[MAX_BOSSES]; -bool g_NpcHasAlwaysLookAtTargetWhileChasing[MAX_BOSSES]; - static float g_NpcSearchWanderRangeMin[MAX_BOSSES][Difficulty_Max]; static float g_NpcSearchWanderRangeMax[MAX_BOSSES][Difficulty_Max]; -//The advanced damage effects -static bool g_NpcHasUseAdvancedDamageEffects[MAX_BOSSES]; -static bool g_NpcHasAdvancedDamageEffectsRandom[MAX_BOSSES]; -static bool g_NpcHasAttachDamageParticle[MAX_BOSSES]; - -static int g_NpcRandomAttackIndexes[MAX_BOSSES]; -static float g_NpcRandomDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcRandomSlowdown[MAX_BOSSES][Difficulty_Max]; -static int g_NpcRandomStunType[MAX_BOSSES]; - -static bool g_NpcHasJaratePlayerEnabled[MAX_BOSSES]; -static int g_NpcJarateAttackIndexes[MAX_BOSSES]; -static float g_NpcJarateDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasJaratePlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasMilkPlayerEnabled[MAX_BOSSES]; -static int g_NpcMilkAttackIndexes[MAX_BOSSES]; -static float g_NpcMilkDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasMilkPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasGasPlayerEnabled[MAX_BOSSES]; -static int g_NpcGasAttackIndexes[MAX_BOSSES]; -static float g_NpcGasDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasGasPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasMarkPlayerEnabled[MAX_BOSSES]; -static int g_NpcMarkAttackIndexes[MAX_BOSSES]; -static float g_NpcMarkDuration[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasSilentMarkPlayerEnabled[MAX_BOSSES]; -static int g_NpcSilentMarkAttackIndexes[MAX_BOSSES]; -static float g_NpcSilentMarkDuration[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasIgnitePlayerEnabled[MAX_BOSSES]; -static int g_NpcIgniteAttackIndexes[MAX_BOSSES]; -static float g_NpcIgniteDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcIgniteDelay[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasStunPlayerEnabled[MAX_BOSSES]; -static int g_NpcStunAttackIndexes[MAX_BOSSES]; -static float g_NpcStunAttackDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStunAttackSlowdown[MAX_BOSSES][Difficulty_Max]; -static int g_NpcStunAttackType[MAX_BOSSES]; -static bool g_NpcHasStunPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasBleedPlayerEnabled[MAX_BOSSES]; -static int g_NpcBleedAttackIndexes[MAX_BOSSES]; -static float g_NpcBleedDuration[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasElectricPlayerEnabled[MAX_BOSSES]; -static int g_NpcElectricAttackIndexes[MAX_BOSSES]; -static float g_NpcElectricDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcElectricSlowdown[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasElectricPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasSmitePlayerEnabled[MAX_BOSSES]; -static int g_NpcSmiteAttackIndexes[MAX_BOSSES]; -static float g_NpcSmiteDamage[MAX_BOSSES]; -static int g_NpcSmiteDamageType[MAX_BOSSES]; -static int g_NpcSmiteColorR[MAX_BOSSES]; -static int g_NpcSmiteColorG[MAX_BOSSES]; -static int g_NpcSmiteColorB[MAX_BOSSES]; -static int g_NpcSmiteTransparency[MAX_BOSSES]; -static bool g_NpcHasSmiteMessage[MAX_BOSSES]; - -static bool g_NpcHasShockwaveEnabled[MAX_BOSSES]; -static float g_NpcShockwaveHeight[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveDrain[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveForce[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasShockwaveStunEnabled[MAX_BOSSES]; -static float g_NpcShockwaveStunDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveStunSlowdown[MAX_BOSSES][Difficulty_Max]; -static int g_NpcShockwaveAttackIndexes[MAX_BOSSES]; -static float g_NpcShockwaveWidth[MAX_BOSSES][2]; -static float g_NpcShockwaveAmplitude[MAX_BOSSES]; - -static int g_NpcState[MAX_BOSSES] = { -1, ... }; -static int g_NpcCurrentAnimationSequence[MAX_BOSSES] = { -1, ... }; -static bool g_NpcHasCurrentAnimationSequenceIsLooped[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesChaseInitialAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesCloakStartAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesCloakEndAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUseFireAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUseStartFleeAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesHealAnimation[MAX_BOSSES] = { false, ... }; -static float g_NpcTimeUntilChaseAfterInitial[MAX_BOSSES]; -float g_NpcCurrentAnimationSequencePlaybackRate[MAX_BOSSES] = { 1.0, ... }; -static char g_NpcCurrentAnimationSequenceName[MAX_BOSSES][256]; -bool g_NpcAlreadyAttacked[MAX_BOSSES]; - bool g_NpcCopyAlerted[MAX_BOSSES]; -static bool g_NpcHasEarthquakeFootstepsEnabled[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsAmplitude[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsFrequency[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsDuration[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsRadius[MAX_BOSSES]; -static bool g_NpcHasEarthquakeFootstepsAirShake[MAX_BOSSES]; - -static int g_NpcSoundCountToAlert[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasDisappearOnStun[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasDropItemOnStun[MAX_BOSSES]; -static int g_NpcDropItemType[MAX_BOSSES]; static bool g_NpcHasIsBoxingBoss[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasNormalSoundHookEnabled[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasCloakToHealEnabled[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasCanUseChaseInitialAnimation[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasOldAnimationAIState[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasCanUseAlertWalkingAnimation[MAX_BOSSES] = { false, ... }; - -static bool g_NpcHasXenobladeBreakComboSystem[MAX_BOSSES]; -static float g_NpcXenobladeBreakDuration[MAX_BOSSES]; -static float g_NpcXenobladeToppleDuration[MAX_BOSSES]; -static float g_NpcXenobladeToppleSlowdown[MAX_BOSSES]; -static float g_NpcXenobladeDazeDuration[MAX_BOSSES]; - -static char jaratePlayerParticle[PLATFORM_MAX_PATH]; -static char milkPlayerParticle[PLATFORM_MAX_PATH]; -static char gasPlayerParticle[PLATFORM_MAX_PATH]; -static char stunPlayerParticle[PLATFORM_MAX_PATH]; -static char electricPlayerParticleRed[PLATFORM_MAX_PATH]; -static char electricPlayerParticleBlue[PLATFORM_MAX_PATH]; -static char keyModel[PLATFORM_MAX_PATH]; bool g_NpcStealingLife[MAX_BOSSES]; Handle g_NpcLifeStealTimer[MAX_BOSSES]; static Handle g_NpcInstantKillThink[MAX_BOSSES]; -static bool g_NpcHasTrapsEnabled[MAX_BOSSES]; -static int g_NpcTrapType[MAX_BOSSES]; -static float g_NpcNextTrapSpawn[MAX_BOSSES][Difficulty_Max]; - -//Special thanks to The Gaben -bool g_SlenderHasDamageParticleEffect[MAX_BOSSES]; -char damageEffectParticle[PLATFORM_MAX_PATH]; -char damageEffectSound[PLATFORM_MAX_PATH]; - -static int g_NpcAutoChaseThreshold[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddGeneral[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddFootstep[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddLoudFootstep[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddVoice[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddWeapon[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasAutoChaseSprinters[MAX_BOSSES] = { false, ... }; -float g_NpcAutoChaseSprinterCooldown[MAX_BOSSES]; - -bool g_NpcInAutoChase[MAX_BOSSES]; - -float g_NpcLaserTimer[MAX_BOSSES]; - -//KF2 Patriarch's Heal Logic -static bool g_NpcHasCanSelfHeal[MAX_BOSSES] = { false, ... }; -bool g_NpcIsRunningToHeal[MAX_BOSSES] = { false, ... }; -float g_NpcFleeHealTimer[MAX_BOSSES]; -bool g_NpcIsHealing[MAX_BOSSES] = { false, ... }; -bool g_NpcSetHealDestination[MAX_BOSSES] = { false, ... }; -static float g_NpcStartSelfHealPercentage[MAX_BOSSES]; -static float g_NpcSelfHealPercentageOne[MAX_BOSSES]; -static float g_NpcSelfHealPercentageTwo[MAX_BOSSES]; -static float g_NpcSelfHealPercentageThree[MAX_BOSSES]; -int g_NpcSelfHealStage[MAX_BOSSES]; -int g_NpcHealCount[MAX_BOSSES]; - //Boxing stuff static int g_NpcBoxingCurrentDifficulty[MAX_BOSSES]; static int g_NpcBoxingRagePhase[MAX_BOSSES]; -static bool g_NpcHasHasCrawling[MAX_BOSSES] = { false, ... }; -bool g_NpcIsCrawling[MAX_BOSSES] = { false, ... }; -bool g_NpcChangeToCrawl[MAX_BOSSES] = { false, ... }; -static float g_NpcCrawlSpeedMultiplier[MAX_BOSSES][Difficulty_Max]; -float g_NpcCrawlDetectMins[MAX_BOSSES][3]; -float g_NpcCrawlDetectMaxs[MAX_BOSSES][3]; - -enum struct BaseAttackStructure -{ - int baseAttackType; - float baseAttackDamage; - float baseAttackDamageVsProps; - float baseAttackDamageForce; - int baseAttackDamageType; - float baseAttackDamageDelay; - float baseAttackRange; - float baseAttackDuration; - float baseAttackSpread; - float baseAttackBeginRange; - float baseAttackBeginFOV; - float baseAttackCooldown; - int baseAttackDisappear; - int baseAttackRepeat; - int baseAttackMaxRepeats; - int currentAttackRepeat; - int weaponAttackIndex; - - bool baseAttackLifeSteal; - float baseAttackLifeStealDuration; - - float baseAttackProjectileDamage; - float baseAttackProjectileSpeed; - float baseAttackProjectileRadius; - float baseAttackProjectileDeviation; - int baseAttackProjectileCount; - int baseAttackProjectileType; - bool baseAttackProjectileCrits; - float baseAttackProjectileIceSlowdownPercent; - float baseAttackProjectileIceSlowdownDuration; - - int baseAttackBulletCount; - float baseAttackBulletDamage; - float baseAttackBulletSpread; - - float baseAttackNextAttackTime; - - float baseAttackLaserDamage; - float baseAttackLaserSize; - int baseAttackLaserColor[3]; - bool baseAttackLaserAttachment; - float baseAttackLaserDuration; - float baseAttackLaserNoise; - - bool baseAttackPullIn; - - bool baseAttackWhileRunning; - float baseAttackRunSpeed; - float baseAttackRunDuration; - float baseAttackRunDelay; - - int baseAttackUseOnDifficulty; - int baseAttackBlockOnDifficulty; - - int baseAttackExplosiveDanceRadius; - - int baseAttackWeaponTypeInt; - bool baseAttackIgnoreAlwaysLooking; - bool baseAttackGestures; - bool baseAttackDeathCamOnLowHealth; - float baseAttackUseOnHealth; - float baseAttackBlockOnHealth; - bool baseAttackDontInterruptChaseInitial; -} +static bool g_ClientShouldBeForceChased[MAX_BOSSES][2049]; +static bool g_IsTargetMarked[MAX_BOSSES][2049]; -int g_SlenderOldState[MAX_BOSSES]; -float g_LastPos[MAX_BOSSES][3]; +GlobalForward g_OnChaserBossStartAttackFwd; +GlobalForward g_OnChaserBossEndAttackFwd; -static int g_NpcBaseAttacksCount[MAX_BOSSES]; -BaseAttackStructure g_NpcBaseAttacks[MAX_BOSSES][SF2_CHASER_BOSS_MAX_ATTACKS][Difficulty_Max]; -static int g_NpcCurrentAttackIndex[MAX_BOSSES]; -float g_NpcBaseAttackRunDurationTime[MAX_BOSSES][SF2_CHASER_BOSS_MAX_ATTACKS]; -float g_NpcBaseAttackRunDelayTime[MAX_BOSSES][SF2_CHASER_BOSS_MAX_ATTACKS]; - -#include "sf2/npc/npc_chaser_mind.sp" +/*#include "sf2/npc/npc_chaser_mind.sp" #include "sf2/npc/npc_chaser_attacks.sp" #include "sf2/npc/npc_chaser_pathing.sp" -#include "sf2/npc/npc_chaser_projectiles.sp" -#include "sf2/npc/npc_creeper.sp" -#include "sf2/methodmaps.sp" +#include "sf2/npc/npc_chaser_projectiles.sp"*/ +#include "npc_creeper.sp" void NPCChaserInitialize() { @@ -332,4048 +52,500 @@ void NPCChaserInitialize() { NPCChaserResetValues(npcIndex); } -} -void NPCChaserResetAnimationInfo(int npcIndex, int iSequence = 0) -{ - g_NpcCurrentAnimationSequence[npcIndex] = iSequence; - g_NpcHasCurrentAnimationSequenceIsLooped[npcIndex] = false; - g_NpcCurrentAnimationSequencePlaybackRate[npcIndex] = 1.0; - g_NpcUsesChaseInitialAnimation[npcIndex] = false; - g_NpcUsesCloakStartAnimation[npcIndex] = false; - g_NpcUsesCloakEndAnimation[npcIndex] = false; - g_NpcUseFireAnimation[npcIndex] = false; - g_NpcUsesHealAnimation[npcIndex] = false; - g_NpcUseStartFleeAnimation[npcIndex] = false; - for (int i = 0; i < 3; i++) - { - g_NpcUsesRageAnimation[npcIndex][i] = false; - } + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); } -float NPCChaserGetWalkSpeed(int npcIndex, int difficulty) +static void OnPutInServer(SF2_BasePlayer client) { - return g_NpcWalkSpeed[npcIndex][difficulty]; + ResetClientNPCStates(client); } -float NPCChaserGetMaxWalkSpeed(int npcIndex, int difficulty) +static void OnPlayerSpawn(SF2_BasePlayer client) { - return g_NpcMaxWalkSpeed[npcIndex][difficulty]; + ResetClientNPCStates(client); } -float NPCChaserGetWakeRadius(int npcIndex) +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { - return g_NpcWakeRadius[npcIndex]; + if (!fake) + { + ResetClientNPCStates(client); + } } -int NPCChaserGetAttackCount(int npcIndex) +static void OnPlayerEscape(SF2_BasePlayer client) { - return g_NpcBaseAttacksCount[npcIndex]; + ResetClientNPCStates(client); } -int NPCChaserGetAttackType(int npcIndex, int attackIndex) +static void OnBossRemoved(SF2NPC_BaseNPC npc) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackType; + if (npc.Type == SF2BossType_Chaser) + { + NPCChaserOnRemoveProfile(npc.Index); + } } -float NPCChaserGetAttackDamage(int npcIndex, int attackIndex, int difficulty) +SF2ChaserBossProfileData NPCChaserGetProfileData(int npcIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamage; + return g_NpcChaserProfileData[npcIndex]; } -float NPCChaserGetAttackDamageVsProps(int npcIndex, int attackIndex) +void NPCChaserSetProfileData(int npcIndex, SF2ChaserBossProfileData value) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackDamageVsProps; + g_NpcChaserProfileData[npcIndex] = value; } -float NPCChaserGetAttackDamageForce(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamageForce; -} -/* -float NPCChaserGetAttackLifeStealDuration(int npcIndex,int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLifeStealDuration; -} -*/ -bool NPCChaserGetAttackLifeStealState(int npcIndex, int attackIndex, int difficulty) +float NPCChaserGetAddStunHealth(int npcIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackLifeSteal; + return g_NpcStunAddHealth[npcIndex]; } -bool NPCChaserGetAttackWhileRunningState(int npcIndex, int attackIndex, int difficulty) +void NPCChaserSetAddStunHealth(int npcIndex, float amount) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackWhileRunning; + g_NpcStunAddHealth[npcIndex] += amount; } -float NPCChaserGetAttackRunSpeed(int npcIndex, int attackIndex, int difficulty) +float NPCChaserGetDeathInitialHealth(int npcIndex, int difficulty) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRunSpeed; + return g_NpcDeathInitialHealth[npcIndex][difficulty]; } -float NPCChaserGetAttackRunDuration(int npcIndex, int attackIndex, int difficulty) +float NPCChaserGetDeathHealth(int npcIndex, int difficulty) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRunDuration; + return g_NpcDeathHealth[npcIndex][difficulty]; } -float NPCChaserGetAttackRunDelay(int npcIndex, int attackIndex, int difficulty) +void NPCChaserSetDeathHealth(int npcIndex, int difficulty, float amount) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRunDelay; + g_NpcDeathHealth[npcIndex][difficulty] = amount; } -int NPCChaserGetAttackUseOnDifficulty(int npcIndex, int attackIndex) +bool NPCChaserIsBoxingBoss(int npcIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackUseOnDifficulty; + return g_NpcHasIsBoxingBoss[npcIndex]; } -int NPCChaserGetAttackBlockOnDifficulty(int npcIndex, int attackIndex) +int NPCChaserGetBoxingDifficulty(int npcIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackBlockOnDifficulty; + return g_NpcBoxingCurrentDifficulty[npcIndex]; } -int NPCChaserGetAttackExplosiveDanceRadius(int npcIndex, int attackIndex, int difficulty) +void NPCChaserSetBoxingDifficulty(int npcIndex, int value) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackExplosiveDanceRadius; + g_NpcBoxingCurrentDifficulty[npcIndex] = value; } -int NPCChaserGetAttackWeaponTypeInt(int npcIndex, int attackIndex) +bool ShouldClientBeForceChased(SF2NPC_BaseNPC controller, CBaseEntity client) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackWeaponTypeInt; + return g_ClientShouldBeForceChased[controller.Index][client.index]; } -int NPCChaserGetAttackProjectileType(int npcIndex, int attackIndex) +void SetClientForceChaseState(SF2NPC_BaseNPC controller, CBaseEntity client, bool value) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackProjectileType; + g_ClientShouldBeForceChased[controller.Index][client.index] = value; } -float NPCChaserGetAttackProjectileDamage(int npcIndex, int attackIndex, int difficulty) +bool IsTargetMarked(SF2NPC_BaseNPC controller, CBaseEntity entity) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileDamage; + return g_IsTargetMarked[controller.Index][entity.index]; } -float NPCChaserGetAttackProjectileSpeed(int npcIndex, int attackIndex, int difficulty) +void SetTargetMarkState(SF2NPC_BaseNPC controller, CBaseEntity entity, bool value) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileSpeed; + g_IsTargetMarked[controller.Index][entity.index] = value; } -float NPCChaserGetAttackProjectileRadius(int npcIndex, int attackIndex, int difficulty) +ArrayList NPCChaserGetAutoChaseTargets(int npcIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileRadius; + return g_NpcChaseOnLookTarget[npcIndex]; } -float NPCChaserGetAttackProjectileDeviation(int npcIndex, int attackIndex, int difficulty) +void ResetClientNPCStates(CBaseEntity client) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileDeviation; + for (int i = 0; i < MAX_BOSSES; i++) + { + g_ClientShouldBeForceChased[i][client.index] = false; + } } -int NPCChaserGetAttackProjectileCount(int npcIndex, int attackIndex, int difficulty) +void NPCChaserOnSelectProfile(int npcIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileCount; -} + SF2NPC_Chaser chaser = SF2NPC_Chaser(npcIndex); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(npcIndex, profile, sizeof(profile)); + SF2ChaserBossProfileData profileData; + g_ChaserBossProfileData.GetArray(profile, profileData, sizeof(profileData)); + g_NpcChaserProfileData[npcIndex] = profileData; -bool NPCChaserGetAttackProjectileCrits(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileCrits; -} + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + g_NpcDeathInitialHealth[npcIndex][difficulty] = GetChaserProfileDeathHealth(profile, difficulty); + g_NpcDeathHealth[npcIndex][difficulty] = g_NpcDeathInitialHealth[npcIndex][difficulty]; + } -float NPCChaserGetAttackProjectileIceSlowdownPercent(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileIceSlowdownPercent; -} + if (g_NpcChaseOnLookTarget[npcIndex] == null) + { + g_NpcChaseOnLookTarget[npcIndex] = new ArrayList(); + } -float NPCChaserGetAttackProjectileIceSlowdownDuration(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileIceSlowdownDuration; -} + chaser.SetAddSpeed(-chaser.GetAddSpeed()); + chaser.SetAddAcceleration(-chaser.GetAddAcceleration()); + chaser.StunHealthAdd = -chaser.StunHealthAdd; -int NPCChaserGetAttackBulletCount(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBulletCount; -} + g_NpcHasIsBoxingBoss[npcIndex] = GetChaserProfileBoxingState(profile); -float NPCChaserGetAttackBulletDamage(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBulletDamage; -} + g_NpcStealingLife[npcIndex] = false; + g_NpcLifeStealTimer[npcIndex] = null; -float NPCChaserGetAttackBulletSpread(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBulletSpread; -} + g_NpcBoxingCurrentDifficulty[npcIndex] = 1; + g_NpcBoxingRagePhase[npcIndex] = 0; -float NPCChaserGetAttackLaserDamage(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackLaserDamage; -} + g_NpcCopyAlerted[npcIndex] = false; -float NPCChaserGetAttackLaserSize(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserSize; + if (chaser.HasAttribute(SF2Attribute_ReducedSpeedOnLook) || chaser.HasAttribute(SF2Attribute_ReducedWalkSpeedOnLook) || chaser.HasAttribute(SF2Attribute_ReducedAccelerationOnLook)) + { + chaser.SetAffectedBySight(true); + } } -void NPCChaserGetAttackLaserColor(int npcIndex, int attackIndex, int color[3]) +void NPCChaserOnRemoveProfile(int npcIndex) { - color[0] = g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserColor[0]; - color[1] = g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserColor[1]; - color[2] = g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserColor[2]; + NPCChaserResetValues(npcIndex); } -bool NPCChaserGetAttackLaserAttachmentState(int npcIndex, int attackIndex) +/** + * Resets all global variables on a specified NPC. Usually this should be done last upon removing a boss from the game. + */ +static void NPCChaserResetValues(int npcIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserAttachment; -} + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + g_NpcAlertGracetime[npcIndex][difficulty] = 0.0; + g_NpcAlertDuration[npcIndex][difficulty] = 0.0; + g_NpcChaseDuration[npcIndex][difficulty] = 0.0; -float NPCChaserGetAttackLaserDuration(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackLaserDuration; -} + g_NpcSearchWanderRangeMin[npcIndex][difficulty] = 0.0; + g_NpcSearchWanderRangeMax[npcIndex][difficulty] = 0.0; -float NPCChaserGetAttackLaserNoise(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserNoise; -} + g_NpcDeathInitialHealth[npcIndex][difficulty] = 0.0; + NPCChaserSetDeathHealth(npcIndex, difficulty, 0.0); + } -bool NPCChaserGetAttackPullIn(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackPullIn; -} + g_NpcHasIsBoxingBoss[npcIndex] = false; -int NPCChaserGetAttackDamageType(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamageType; -} + g_NpcStunAddHealth[npcIndex] = 0.0; -int NPCChaserGetAttackDisappear(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDisappear; -} + g_NpcStealingLife[npcIndex] = false; + g_NpcLifeStealTimer[npcIndex] = null; + if (g_NpcChaseOnLookTarget[npcIndex] != null) + { + delete g_NpcChaseOnLookTarget[npcIndex]; + g_NpcChaseOnLookTarget[npcIndex] = null; + } -int NPCChaserGetAttackRepeat(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackRepeat; -} + NPCSetAddSpeed(npcIndex, -NPCGetAddSpeed(npcIndex)); + NPCSetAddAcceleration(npcIndex, -NPCGetAddAcceleration(npcIndex)); + NPCChaserSetAddStunHealth(npcIndex, -NPCChaserGetAddStunHealth(npcIndex)); -int NPCChaserGetMaxAttackRepeats(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackMaxRepeats; -} + g_NpcBoxingCurrentDifficulty[npcIndex] = 0; + g_NpcBoxingRagePhase[npcIndex] = 0; -bool NPCChaserGetAttackIgnoreAlwaysLooking(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackIgnoreAlwaysLooking; + g_NpcCopyAlerted[npcIndex] = false; } -float NPCChaserGetAttackDamageDelay(int npcIndex, int attackIndex, int difficulty) +SF2_ChaserEntity Spawn_Chaser(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamageDelay; -} + int bossIndex = controller.Index; -float NPCChaserGetAttackRange(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRange; -} + NPCSetAddSpeed(bossIndex, -NPCGetAddSpeed(bossIndex)); + NPCSetAddAcceleration(bossIndex, -NPCGetAddAcceleration(bossIndex)); + NPCChaserSetAddStunHealth(bossIndex, -NPCChaserGetAddStunHealth(bossIndex)); + /*char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); -float NPCChaserGetAttackDuration(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDuration; -} + g_LastStuckTime[bossIndex] = 0.0; + g_SlenderOldState[bossIndex] = STATE_IDLE; + g_NpcCopyAlerted[bossIndex] = false; + g_NpcNextDecloakTime[bossIndex] = -1.0; + g_NpcIsCrawling[bossIndex] = false; + g_NpcChangeToCrawl[bossIndex] = false; + g_SlenderSoundPositionSetCooldown[bossIndex] = 0.0; -float NPCChaserGetAttackSpread(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackSpread; -} + //g_NpcInstantKillThink[bossIndex] = CreateTimer(0.0, Timer_InstantKillThink, bossIndex, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); -float NPCChaserGetAttackBeginRange(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBeginRange; -} + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + g_SlenderNextWanderPos[bossIndex][difficulty] = GetGameTime() + + GetRandomFloat(GetChaserProfileWanderEnterTimeMin(profile, difficulty), GetChaserProfileWanderEnterTimeMax(profile, difficulty)); + }*/ -float NPCChaserGetAttackBeginFOV(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBeginFOV; + return SF2_ChaserEntity.Create(controller, pos, ang); } -bool NPCChaserGetAttackGestureState(int npcIndex, int attackIndex) +void Despawn_Chaser(int bossIndex) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackGestures; -} + g_NpcInstantKillThink[bossIndex] = null; -bool NPCChaserGetAttackDeathCamOnLowHealth(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackDeathCamOnLowHealth; -} + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } -float NPCChaserGetAttackUseOnHealth(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackUseOnHealth; + SF2_BasePlayer player = SF2_BasePlayer(i); + player.SetForceChaseState(SF2NPC_BaseNPC(bossIndex), false); + } } -float NPCChaserGetAttackBlockOnHealth(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackBlockOnHealth; -} +// So this is how the thought process of the bosses should go. +// 1. Search for enemy; either by sight or by sound. +// - Any noticeable sounds should be investigated. +// - Too many sounds will put me in alert mode. +// 2. Alert of an enemy; I saw something or I heard something unusual +// - Go to the position where I last heard the sound. +// - Keep on searching until I give up. Then drop back to idle mode. +// 3. Found an enemy! Give chase! +// - Keep on chasing until enemy is killed or I give up. +// - Keep a path in memory as long as I still have him in my sights. +// - If I lose sight or I'm unable to traverse safely, find paths around obstacles and follow memorized path. +// - If I reach the end of my path and I still don't see him and I still want to pursue him, keep on going in the direction I'm going. -bool NPCChaserGetAttackInterruptChaseInitialState(int npcIndex, int attackIndex, int difficulty) +bool IsTargetValidForSlender(SF2_BaseBoss boss, CBaseEntity target, bool includeEliminated = false) { - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDontInterruptChaseInitial; -} + if (!target.IsValid()) + { + return false; + } -void NPCSetCurrentAttackIndex(int npcIndex, int attackIndex) -{ - g_NpcCurrentAttackIndex[npcIndex] = attackIndex; -} + if (target.index <= 0) + { + return false; + } -int NPCGetCurrentAttackIndex(int npcIndex) -{ - return g_NpcCurrentAttackIndex[npcIndex]; -} + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && (!player.IsAlive || + player.IsInDeathCam || + player.IsInGhostMode || + player.HasEscaped || + player.InCondition(view_as(130)) || + player.Team == TFTeam_Spectator)) + { + return false; + } -void NpcSetCurrentAttackRepeat(int npcIndex, int attackIndex, int value) -{ - g_NpcBaseAttacks[npcIndex][attackIndex][1].currentAttackRepeat = value; -} + if (!g_Enabled) + { + if (target.GetProp(Prop_Data, "m_iTeamNum") == boss.Team) + { + return false; + } + } + else + { + if (!includeEliminated && player.IsValid && player.IsEliminated) + { + return false; + } -int NpcGetCurrentAttackRepeat(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].currentAttackRepeat; -} + if (target.GetProp(Prop_Data, "m_iTeamNum") == boss.Team) + { + return false; + } + } -float NPCChaserGetAttackCooldown(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackCooldown; + return true; } -float NPCChaserGetNextAttackTime(int npcIndex, int attackIndex) +bool IsTargetValidForSlenderEx(CBaseEntity target, int bossIndex, bool includeEliminated = false) { - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackNextAttackTime; -} + if (!target.IsValid()) + { + return false; + } -void NPCChaserSetNextAttackTime(int npcIndex, int attackIndex, float value) -{ - g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackNextAttackTime = value; -} + if (target.index <= 0) + { + return false; + } -bool NPCChaserIsStunEnabled(int npcIndex) -{ - return g_NpcHasStunEnabled[npcIndex]; -} + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && (!player.IsAlive || + player.IsInDeathCam || + player.IsInGhostMode || + player.HasEscaped || + player.InCondition(view_as(130)) || + player.Team == TFTeam_Spectator)) + { + return false; + } -bool NPCChaserIsStunByFlashlightEnabled(int npcIndex) -{ - return g_NpcStunFlashlightEnabled[npcIndex]; -} + if (g_Enabled) + { + if (!includeEliminated && player.IsValid && player.IsEliminated) + { + return false; + } -bool NPCChaserCanChaseInitialOnStun(int npcIndex) -{ - return g_NpcChaseInitialOnStun[npcIndex]; -} + if (!g_SlenderTeleportIgnoreChases[bossIndex]) + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_Chaser npc = SF2NPC_Chaser(i); + if (!npc.IsValid()) + { + continue; + } -bool NPCChaseHasKeyDrop(int npcIndex) -{ - return g_NpcHasKeyDrop[npcIndex]; -} + SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); + if (!chaser.IsValid()) + { + continue; + } -float NPCChaserGetStunFlashlightDamage(int npcIndex) -{ - return g_NpcStunFlashlightDamage[npcIndex]; -} + SF2BossProfileData data; + data = view_as(npc).GetProfileData(); -float NPCChaserGetStunCooldown(int npcIndex) -{ - return g_NpcStunCooldown[npcIndex]; -} + int state = chaser.State; + if (!data.IsPvEBoss && (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN)) + { + return false; + } + } + } + } -float NPCChaserGetStunHealth(int npcIndex) -{ - return g_NpcStunHealth[npcIndex]; + return true; } -void NPCChaserSetStunHealth(int npcIndex, float amount) +bool IsPvETargetValid(CBaseEntity target) { - g_NpcStunHealth[npcIndex] = amount; -} + if (!target.IsValid()) + { + return false; + } -void NPCChaserSetStunInitialHealth(int npcIndex, float amount) -{ - g_NpcStunInitialHealth[npcIndex] = amount; -} + SF2_BasePlayer player = SF2_BasePlayer(target.index); -float NPCChaserGetAddStunHealth(int npcIndex) -{ - return g_NpcStunAddHealth[npcIndex]; -} + if (player.IsValid && (!player.IsAlive || !player.IsInPvE || player.IsInGhostMode)) + { + return false; + } -void NPCChaserSetAddStunHealth(int npcIndex, float amount) -{ - g_NpcStunAddHealth[npcIndex] += amount; -} - -void NPCChaserAddStunHealth(int npcIndex, float amount) -{ - if (GetGameTime() >= g_SlenderNextStunTime[npcIndex]) - { - NPCChaserSetStunHealth(npcIndex, NPCChaserGetStunHealth(npcIndex) + amount); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_STUN, 0, "Boss %i, new amount: %0.0f", npcIndex, NPCChaserGetStunHealth(npcIndex)); - #endif - } -} - -float NPCChaserGetStunInitialHealth(int npcIndex) -{ - return g_NpcStunInitialHealth[npcIndex]; -} - -float NPCChaserGetAlertGracetime(int npcIndex, int difficulty) -{ - return g_NpcAlertGracetime[npcIndex][difficulty]; -} - -float NPCChaserGetAlertDuration(int npcIndex, int difficulty) -{ - return g_NpcAlertDuration[npcIndex][difficulty]; -} - -float NPCChaserGetChaseDuration(int npcIndex, int difficulty) -{ - return g_NpcChaseDuration[npcIndex][difficulty]; -} - -float NPCChaserGetWanderRangeMin(int npcIndex, int difficulty) -{ - return g_NpcSearchWanderRangeMin[npcIndex][difficulty]; -} - -float NPCChaserGetWanderRangeMax(int npcIndex, int difficulty) -{ - return g_NpcSearchWanderRangeMax[npcIndex][difficulty]; -} - -bool NPCChaserIsCloakEnabled(int npcIndex) -{ - return g_NpcCloakEnabled[npcIndex]; -} - -bool NPCChaserIsCloaked(int npcIndex) -{ - return g_NpcHasCloaked[npcIndex]; -} - -float NPCChaserGetCloakCooldown(int npcIndex, int difficulty) -{ - return g_NpcCloakCooldown[npcIndex][difficulty]; -} - -float NPCChaserGetCloakRange(int npcIndex, int difficulty) -{ - return g_NpcCloakRange[npcIndex][difficulty]; -} - -float NPCChaserGetDecloakRange(int npcIndex, int difficulty) -{ - return g_NpcDecloakRange[npcIndex][difficulty]; -} - -float NPCChaserGetCloakDuration(int npcIndex, int difficulty) -{ - return g_NpcCloakDuration[npcIndex][difficulty]; -} - -float NPCChaserGetCloakSpeedMultiplier(int npcIndex, int difficulty) -{ - return g_NpcCloakSpeedMultiplier[npcIndex][difficulty]; -} - -bool NPCChaserIsProjectileEnabled(int npcIndex) -{ - return g_NpcHasProjectileEnabled[npcIndex]; -} - -bool NPCChaserHasCriticalRockets(int npcIndex) -{ - return g_NpcHasCriticalRockets[npcIndex]; -} - -bool NPCChaserUseShootGesture(int npcIndex) -{ - return g_NpcHasUseShootGesture[npcIndex]; -} - -bool NPCChaserUseProjectileAmmo(int npcIndex) -{ - return g_NpcHasUseProjectileAmmo[npcIndex]; -} - -int NPCChaserGetProjectileType(int npcIndex) -{ - return g_NpcProjectileType[npcIndex]; -} - -float NPCChaserGetProjectileCooldownMin(int npcIndex, int difficulty) -{ - return g_NpcProjectileCooldownMin[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileCooldownMax(int npcIndex, int difficulty) -{ - return g_NpcProjectileCooldownMax[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileSpeed(int npcIndex, int difficulty) -{ - return g_NpcProjectileSpeed[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileDamage(int npcIndex, int difficulty) -{ - return g_NpcProjectileDamage[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileRadius(int npcIndex, int difficulty) -{ - return g_NpcProjectileRadius[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileDeviation(int npcIndex, int difficulty) -{ - return g_NpcProjectileDeviation[npcIndex][difficulty]; -} - -int NPCChaserGetProjectileCount(int npcIndex, int difficulty) -{ - return g_NpcProjectileCount[npcIndex][difficulty]; -} - -float NPCChaserGetIceballSlowdownDuration(int npcIndex, int difficulty) -{ - return g_IceballSlowdownDuration[npcIndex][difficulty]; -} - -float NPCChaserGetIceballSlowdownPercent(int npcIndex, int difficulty) -{ - return g_IceballSlowdownPercent[npcIndex][difficulty]; -} - -int NPCChaserGetLoadedProjectiles(int npcIndex, int difficulty) -{ - return g_NpcProjectileLoadedAmmo[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileReloadTime(int npcIndex, int difficulty) -{ - return g_NpcProjectileReloadTime[npcIndex][difficulty]; -} -/* -float NPCChaserGetProjectileChargeUpDuration(int npcIndex,int difficulty) -{ - return g_NpcProjectileChargeUpTime[npcIndex][difficulty]; -} -*/ - -bool NPCChaserUseAdvancedDamageEffects(int npcIndex) -{ - return g_NpcHasUseAdvancedDamageEffects[npcIndex]; -} - -bool NPCChaserUseRandomAdvancedDamageEffects(int npcIndex) -{ - return g_NpcHasAdvancedDamageEffectsRandom[npcIndex]; -} - -bool NPCChaserAttachDamageParticle(int npcIndex) -{ - return g_NpcHasAttachDamageParticle[npcIndex]; -} - -int NPCChaserRandomEffectIndexes(int npcIndex) -{ - return g_NpcRandomAttackIndexes[npcIndex]; -} - -float NPCChaserRandomEffectDuration(int npcIndex, int difficulty) -{ - return g_NpcRandomDuration[npcIndex][difficulty]; -} - -float NPCChaserRandomEffectSlowdown(int npcIndex, int difficulty) -{ - return g_NpcRandomSlowdown[npcIndex][difficulty]; -} - -int NPCChaserRandomEffectStunType(int npcIndex) -{ - return g_NpcRandomStunType[npcIndex]; -} - -bool NPCChaserJaratePlayerOnHit(int npcIndex) -{ - return g_NpcHasJaratePlayerEnabled[npcIndex]; -} - -int NPCChaserGetJarateAttackIndexes(int npcIndex) -{ - return g_NpcJarateAttackIndexes[npcIndex]; -} - -float NPCChaserGetJarateDuration(int npcIndex, int difficulty) -{ - return g_NpcJarateDuration[npcIndex][difficulty]; -} - -bool NPCChaserGetJarateBeamParticle(int npcIndex) -{ - return g_NpcHasJaratePlayerBeamParticle[npcIndex]; -} - -bool NPCChaserMilkPlayerOnHit(int npcIndex) -{ - return g_NpcHasMilkPlayerEnabled[npcIndex]; -} - -int NPCChaserGetMilkAttackIndexes(int npcIndex) -{ - return g_NpcMilkAttackIndexes[npcIndex]; -} - -float NPCChaserGetMilkDuration(int npcIndex, int difficulty) -{ - return g_NpcMilkDuration[npcIndex][difficulty]; -} - -bool NPCChaserGetMilkBeamParticle(int npcIndex) -{ - return g_NpcHasMilkPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserGasPlayerOnHit(int npcIndex) -{ - return g_NpcHasGasPlayerEnabled[npcIndex]; -} - -int NPCChaserGetGasAttackIndexes(int npcIndex) -{ - return g_NpcGasAttackIndexes[npcIndex]; -} - -float NPCChaserGetGasDuration(int npcIndex, int difficulty) -{ - return g_NpcGasDuration[npcIndex][difficulty]; -} - -bool NPCChaserGetGasBeamParticle(int npcIndex) -{ - return g_NpcHasGasPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserMarkPlayerOnHit(int npcIndex) -{ - return g_NpcHasMarkPlayerEnabled[npcIndex]; -} - -int NPCChaserGetMarkAttackIndexes(int npcIndex) -{ - return g_NpcMarkAttackIndexes[npcIndex]; -} - -float NPCChaserGetMarkDuration(int npcIndex, int difficulty) -{ - return g_NpcMarkDuration[npcIndex][difficulty]; -} - -bool NPCChaserSilentMarkPlayerOnHit(int npcIndex) -{ - return g_NpcHasSilentMarkPlayerEnabled[npcIndex]; -} - -int NPCChaserGetSilentMarkAttackIndexes(int npcIndex) -{ - return g_NpcSilentMarkAttackIndexes[npcIndex]; -} - -float NPCChaserGetSilentMarkDuration(int npcIndex, int difficulty) -{ - return g_NpcSilentMarkDuration[npcIndex][difficulty]; -} - -bool NPCChaserIgnitePlayerOnHit(int npcIndex) -{ - return g_NpcHasIgnitePlayerEnabled[npcIndex]; -} - -int NPCChaserGetIgniteAttackIndexes(int npcIndex) -{ - return g_NpcIgniteAttackIndexes[npcIndex]; -} - -float NPCChaserGetIgniteDuration(int npcIndex, int difficulty) -{ - return g_NpcIgniteDuration[npcIndex][difficulty]; -} - -float NPCChaserGetIgniteDelay(int npcIndex, int difficulty) -{ - return g_NpcIgniteDelay[npcIndex][difficulty]; -} - -bool NPCChaserStunPlayerOnHit(int npcIndex) -{ - return g_NpcHasStunPlayerEnabled[npcIndex]; -} - -int NPCChaserGetStunAttackIndexes(int npcIndex) -{ - return g_NpcStunAttackIndexes[npcIndex]; -} - -float NPCChaserGetStunAttackDuration(int npcIndex, int difficulty) -{ - return g_NpcStunAttackDuration[npcIndex][difficulty]; -} - -float NPCChaserGetStunAttackSlowdown(int npcIndex, int difficulty) -{ - return g_NpcStunAttackSlowdown[npcIndex][difficulty]; -} - -int NPCChaserGetStunAttackType(int npcIndex) -{ - return g_NpcStunAttackType[npcIndex]; -} - -bool NPCChaserGetStunBeamParticle(int npcIndex) -{ - return g_NpcHasStunPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserBleedPlayerOnHit(int npcIndex) -{ - return g_NpcHasBleedPlayerEnabled[npcIndex]; -} - -int NPCChaserGetBleedAttackIndexes(int npcIndex) -{ - return g_NpcBleedAttackIndexes[npcIndex]; -} - -float NPCChaserGetBleedDuration(int npcIndex, int difficulty) -{ - return g_NpcBleedDuration[npcIndex][difficulty]; -} - -bool NPCChaserElectricPlayerOnHit(int npcIndex) -{ - return g_NpcHasElectricPlayerEnabled[npcIndex]; -} - -int NPCChaserGetElectricAttackIndexes(int npcIndex) -{ - return g_NpcElectricAttackIndexes[npcIndex]; -} - -float NPCChaserGetElectricDuration(int npcIndex, int difficulty) -{ - return g_NpcElectricDuration[npcIndex][difficulty]; -} - -float NPCChaserGetElectricSlowdown(int npcIndex, int difficulty) -{ - return g_NpcElectricSlowdown[npcIndex][difficulty]; -} - -bool NPCChaserGetElectricBeamParticle(int npcIndex) -{ - return g_NpcHasElectricPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserSmitePlayerOnHit(int npcIndex) -{ - return g_NpcHasSmitePlayerEnabled[npcIndex]; -} - -bool NPCChaserSmitePlayerMessage(int npcIndex) -{ - return g_NpcHasSmiteMessage[npcIndex]; -} - -int NPCChaserGetSmiteAttackIndexes(int npcIndex) -{ - return g_NpcSmiteAttackIndexes[npcIndex]; -} - -bool NPCChaserXenobladeSystemEnabled(int npcIndex) -{ - return g_NpcHasXenobladeBreakComboSystem[npcIndex]; -} - -float NPCChaserGetXenobladeBreakDuration(int npcIndex) -{ - return g_NpcXenobladeBreakDuration[npcIndex]; -} - -float NPCChaserGetXenobladeToppleDuration(int npcIndex) -{ - return g_NpcXenobladeToppleDuration[npcIndex]; -} - -float NPCChaserGetXenobladeToppleSlowdown(int npcIndex) -{ - return g_NpcXenobladeToppleSlowdown[npcIndex]; -} - -float NPCChaserGetXenobladeDazeDuration(int npcIndex) -{ - return g_NpcXenobladeDazeDuration[npcIndex]; -} - -float NPCChaserGetSmiteDamage(int npcIndex) -{ - return g_NpcSmiteDamage[npcIndex]; -} - -int NPCChaserGetSmiteDamageType(int npcIndex) -{ - return g_NpcSmiteDamageType[npcIndex]; -} - -int NPCChaserGetSmiteColorR(int npcIndex) -{ - return g_NpcSmiteColorR[npcIndex]; -} - -int NPCChaserGetSmiteColorG(int npcIndex) -{ - return g_NpcSmiteColorG[npcIndex]; -} - -int NPCChaserGetSmiteColorB(int npcIndex) -{ - return g_NpcSmiteColorB[npcIndex]; -} - -int NPCChaserGetSmiteColorTrans(int npcIndex) -{ - return g_NpcSmiteTransparency[npcIndex]; -} - -bool NPCChaserGetTrapState(int npcIndex) -{ - return g_NpcHasTrapsEnabled[npcIndex]; -} - -int NPCChaserGetTrapType(int npcIndex) -{ - return g_NpcTrapType[npcIndex]; -} - -float NPCChaserGetTrapSpawnTime(int npcIndex, int difficulty) -{ - return g_NpcNextTrapSpawn[npcIndex][difficulty]; -} - -bool NPCChaserDamageParticlesEnabled(int npcIndex) -{ - return g_SlenderHasDamageParticleEffect[npcIndex]; -} - -bool NPCChaserGetSelfHealState(int npcIndex) -{ - return g_NpcHasCanSelfHeal[npcIndex]; -} - -float NPCChaserGetStartSelfHealPercentage(int npcIndex) -{ - return g_NpcStartSelfHealPercentage[npcIndex]; -} - -float NPCChaserGetSelfHealPercentageOne(int npcIndex) -{ - return g_NpcSelfHealPercentageOne[npcIndex]; -} - -float NPCChaserGetSelfHealPercentageTwo(int npcIndex) -{ - return g_NpcSelfHealPercentageTwo[npcIndex]; -} - -float NPCChaserGetSelfHealPercentageThree(int npcIndex) -{ - return g_NpcSelfHealPercentageThree[npcIndex]; -} - -bool NPCChaserIsAutoChaseEnabled(int npcIndex) -{ - return g_SlenderHasAutoChaseEnabled[npcIndex]; -} - -int NPCChaserAutoChaseThreshold(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseThreshold[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddGeneral(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddGeneral[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddFootstep(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddFootstep[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddLoudFootstep(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddLoudFootstep[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddVoice(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddVoice[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddWeapon(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddWeapon[npcIndex][difficulty]; -} - -bool NPCChaserCanAutoChaseSprinters(int npcIndex) -{ - return g_NpcHasAutoChaseSprinters[npcIndex]; -} - -bool NPCChaserShockwaveOnAttack(int npcIndex) -{ - return g_NpcHasShockwaveEnabled[npcIndex]; -} - -bool NPCChaserShockwaveStunEnabled(int npcIndex) -{ - return g_NpcHasShockwaveStunEnabled[npcIndex]; -} - -int NPCChaserGetShockwaveAttackIndexes(int npcIndex) -{ - return g_NpcShockwaveAttackIndexes[npcIndex]; -} - -float NPCChaserGetShockwaveDrain(int npcIndex, int difficulty) -{ - return g_NpcShockwaveDrain[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveForce(int npcIndex, int difficulty) -{ - return g_NpcShockwaveForce[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveHeight(int npcIndex, int difficulty) -{ - return g_NpcShockwaveHeight[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveRange(int npcIndex, int difficulty) -{ - return g_NpcShockwaveRange[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveStunDuration(int npcIndex, int difficulty) -{ - return g_NpcShockwaveStunDuration[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveStunSlowdown(int npcIndex, int difficulty) -{ - return g_NpcShockwaveStunSlowdown[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveWidth(int npcIndex, int iType) -{ - return g_NpcShockwaveWidth[npcIndex][iType]; -} - -float NPCChaserGetShockwaveAmplitude(int npcIndex) -{ - return g_NpcShockwaveAmplitude[npcIndex]; -} - -bool NPCChaserGetEarthquakeFootstepsState(int npcIndex) -{ - return g_NpcHasEarthquakeFootstepsEnabled[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsAmplitude(int npcIndex) -{ - return g_NpcEarthquakeFootstepsAmplitude[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsFrequency(int npcIndex) -{ - return g_NpcEarthquakeFootstepsFrequency[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsDuration(int npcIndex) -{ - return g_NpcEarthquakeFootstepsDuration[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsRadius(int npcIndex) -{ - return g_NpcEarthquakeFootstepsRadius[npcIndex]; -} - -bool NPCChaserGetEarthquakeFootstepsAirShakeState(int npcIndex) -{ - return g_NpcHasEarthquakeFootstepsAirShake[npcIndex]; -} - -int NPCChaserGetSoundCountToAlert(int npcIndex, int difficulty) -{ - return g_NpcSoundCountToAlert[npcIndex][difficulty]; -} - -bool NPCChaserCanDisappearOnStun(int npcIndex) -{ - return g_NpcHasDisappearOnStun[npcIndex]; -} - -bool NPCChaserCanDropItemOnStun(int npcIndex) -{ - return g_NpcHasDropItemOnStun[npcIndex]; -} - -int NPCChaserItemDropTypeStun(int npcIndex) -{ - return g_NpcDropItemType[npcIndex]; -} - -bool NPCChaserIsBoxingBoss(int npcIndex) -{ - return g_NpcHasIsBoxingBoss[npcIndex]; -} - -bool NPCChaserNormalSoundHookEnabled(int npcIndex) -{ - return g_NpcHasNormalSoundHookEnabled[npcIndex]; -} - -bool NPCChaserCanCloakToHeal(int npcIndex) -{ - return g_NpcHasCloakToHealEnabled[npcIndex]; -} - -bool NPCChaserCanUseChaseInitialAnimation(int npcIndex) -{ - return g_NpcHasCanUseChaseInitialAnimation[npcIndex]; -} - -bool NPCChaserOldAnimationAIEnabled(int npcIndex) -{ - return g_NpcHasOldAnimationAIState[npcIndex]; -} - -bool NPCChaserCanUseAlertWalkingAnimation(int npcIndex) -{ - return g_NpcHasCanUseAlertWalkingAnimation[npcIndex]; -} - -bool NPCChaserCanCrawl(int npcIndex) -{ - return g_NpcHasHasCrawling[npcIndex]; -} - -float NPCChaserGetCrawlSpeedMultiplier(int npcIndex, int difficulty) -{ - return g_NpcCrawlSpeedMultiplier[npcIndex][difficulty]; -} - -int NPCChaserGetState(int npcIndex) -{ - return g_NpcState[npcIndex]; -} - -void NPCChaserSetState(int npcIndex, int state) -{ - g_NpcState[npcIndex] = state; -} - -int NPCChaserGetBoxingDifficulty(int npcIndex) -{ - return g_NpcBoxingCurrentDifficulty[npcIndex]; -} - -void NPCChaserSetBoxingDifficulty(int npcIndex, int value) -{ - g_NpcBoxingCurrentDifficulty[npcIndex] = value; -} - -int NPCChaserGetBoxingRagePhase(int npcIndex) -{ - return g_NpcBoxingRagePhase[npcIndex]; -} - -void NPCChaserSetBoxingRagePhase(int npcIndex, int value) -{ - g_NpcBoxingRagePhase[npcIndex] = value; -} - -bool NPCChaserCanChaseOnLook(int npcIndex) -{ - return g_NpcChaseOnLook[npcIndex]; -} - -void NPCChaserOnSelectProfile(int npcIndex, bool invincible) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(npcIndex, profile, sizeof(profile)); - - g_NpcWakeRadius[npcIndex] = GetChaserProfileWakeRadius(profile); - - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - g_NpcWalkSpeed[npcIndex][difficulty] = GetChaserProfileWalkSpeed(profile, difficulty); - g_NpcMaxWalkSpeed[npcIndex][difficulty] = GetChaserProfileMaxWalkSpeed(profile, difficulty); - - g_NpcAlertGracetime[npcIndex][difficulty] = GetChaserProfileAlertGracetime(profile, difficulty); - g_NpcAlertDuration[npcIndex][difficulty] = GetChaserProfileAlertDuration(profile, difficulty); - g_NpcChaseDuration[npcIndex][difficulty] = GetChaserProfileChaseDuration(profile, difficulty); - - g_NpcSoundCountToAlert[npcIndex][difficulty] = GetChaserProfileSoundCountToAlert(profile, difficulty); - - g_NpcCloakCooldown[npcIndex][difficulty] = GetChaserProfileCloakCooldown(profile, difficulty); - g_NpcCloakRange[npcIndex][difficulty] = GetChaserProfileCloakRange(profile, difficulty); - g_NpcDecloakRange[npcIndex][difficulty] = GetChaserProfileDecloakRange(profile, difficulty); - g_NpcCloakDuration[npcIndex][difficulty] = GetChaserProfileCloakDuration(profile, difficulty); - g_NpcCloakSpeedMultiplier[npcIndex][difficulty] = GetChaserProfileCloakSpeedMultiplier(profile, difficulty); - - g_NpcProjectileCooldownMin[npcIndex][difficulty] = GetChaserProfileProjectileCooldownMin(profile, difficulty); - g_NpcProjectileCooldownMax[npcIndex][difficulty] = GetChaserProfileProjectileCooldownMax(profile, difficulty); - g_NpcProjectileSpeed[npcIndex][difficulty] = GetChaserProfileProjectileSpeed(profile, difficulty); - g_NpcProjectileDamage[npcIndex][difficulty] = GetChaserProfileProjectileDamage(profile, difficulty); - g_NpcProjectileRadius[npcIndex][difficulty] = GetChaserProfileProjectileRadius(profile, difficulty); - g_NpcProjectileDeviation[npcIndex][difficulty] = GetChaserProfileProjectileDeviation(profile, difficulty); - g_NpcProjectileCount[npcIndex][difficulty] = GetChaserProfileProjectileCount(profile, difficulty); - g_IceballSlowdownDuration[npcIndex][difficulty] = GetChaserProfileIceballSlowdownDuration(profile, difficulty); - g_IceballSlowdownPercent[npcIndex][difficulty] = GetChaserProfileIceballSlowdownPercent(profile, difficulty); - g_NpcProjectileLoadedAmmo[npcIndex][difficulty] = GetChaserProfileProjectileLoadedAmmo(profile, difficulty); - g_NpcProjectileReloadTime[npcIndex][difficulty] = GetChaserProfileProjectileAmmoReloadTime(profile, difficulty); - g_NpcProjectileChargeUpTime[npcIndex][difficulty] = GetChaserProfileProjectileChargeUpTime(profile, difficulty); - g_NpcProjectileAmmo[npcIndex] = g_NpcProjectileLoadedAmmo[npcIndex][difficulty]; - g_NpcProjectileTimeToReload[npcIndex] = g_NpcProjectileReloadTime[npcIndex][difficulty]; - - g_NpcRandomDuration[npcIndex][difficulty] = GetChaserProfileRandomEffectDuration(profile, difficulty); - g_NpcRandomSlowdown[npcIndex][difficulty] = GetChaserProfileRandomEffectSlowdown(profile, difficulty); - g_NpcJarateDuration[npcIndex][difficulty] = GetChaserProfileJaratePlayerDuration(profile, difficulty); - g_NpcMilkDuration[npcIndex][difficulty] = GetChaserProfileMilkPlayerDuration(profile, difficulty); - g_NpcGasDuration[npcIndex][difficulty] = GetChaserProfileGasPlayerDuration(profile, difficulty); - g_NpcMarkDuration[npcIndex][difficulty] = GetChaserProfileMarkPlayerDuration(profile, difficulty); - g_NpcSilentMarkDuration[npcIndex][difficulty] = GetChaserProfileSilentMarkPlayerDuration(profile, difficulty); - g_NpcIgniteDuration[npcIndex][difficulty] = GetChaserProfileIgnitePlayerDuration(profile, difficulty); - g_NpcIgniteDelay[npcIndex][difficulty] = GetChaserProfileIgnitePlayerDelay(profile, difficulty); - g_NpcStunAttackDuration[npcIndex][difficulty] = GetChaserProfileStunPlayerDuration(profile, difficulty); - g_NpcStunAttackSlowdown[npcIndex][difficulty] = GetChaserProfileStunPlayerSlowdown(profile, difficulty); - g_NpcBleedDuration[npcIndex][difficulty] = GetChaserProfileBleedPlayerDuration(profile, difficulty); - g_NpcElectricDuration[npcIndex][difficulty] = GetChaserProfileEletricPlayerDuration(profile, difficulty); - g_NpcElectricSlowdown[npcIndex][difficulty] = GetChaserProfileEletricPlayerSlowdown(profile, difficulty); - - g_NpcShockwaveDrain[npcIndex][difficulty] = GetChaserProfileShockwaveDrain(profile, difficulty); - g_NpcShockwaveForce[npcIndex][difficulty] = GetChaserProfileShockwaveForce(profile, difficulty); - g_NpcShockwaveHeight[npcIndex][difficulty] = GetChaserProfileShockwaveHeight(profile, difficulty); - g_NpcShockwaveRange[npcIndex][difficulty] = GetChaserProfileShockwaveRange(profile, difficulty); - g_NpcShockwaveStunDuration[npcIndex][difficulty] = GetChaserProfileShockwaveStunDuration(profile, difficulty); - g_NpcShockwaveStunSlowdown[npcIndex][difficulty] = GetChaserProfileShockwaveStunSlowdown(profile, difficulty); - - g_NpcNextTrapSpawn[npcIndex][difficulty] = GetChaserProfileTrapSpawnCooldown(profile, difficulty); - g_SlenderNextTrapPlacement[npcIndex] = GetGameTime() + g_NpcNextTrapSpawn[npcIndex][difficulty]; - - g_NpcSearchWanderRangeMin[npcIndex][difficulty] = GetChaserProfileWanderRangeMin(profile, difficulty); - g_NpcSearchWanderRangeMax[npcIndex][difficulty] = GetChaserProfileWanderRangeMax(profile, difficulty); - - g_NpcAutoChaseThreshold[npcIndex][difficulty] = GetChaserProfileAutoChaseCount(profile, difficulty); - g_NpcAutoChaseAddGeneral[npcIndex][difficulty] = GetChaserProfileAutoChaseAddGeneral(profile, difficulty); - g_NpcAutoChaseAddFootstep[npcIndex][difficulty] = GetChaserProfileAutoChaseAddFootstep(profile, difficulty); - g_NpcAutoChaseAddLoudFootstep[npcIndex][difficulty] = GetChaserProfileAutoChaseAddLoudFootstep(profile, difficulty); - g_NpcAutoChaseAddVoice[npcIndex][difficulty] = GetChaserProfileAutoChaseAddVoice(profile, difficulty); - g_NpcAutoChaseAddWeapon[npcIndex][difficulty] = GetChaserProfileAutoChaseAddWeapon(profile, difficulty); - g_NpcCrawlSpeedMultiplier[npcIndex][difficulty] = GetChaserProfileCrawlSpeedMultiplier(profile, difficulty); - } - - g_NpcBaseAttacksCount[npcIndex] = GetChaserProfileAttackCount(profile); - // Get attack data. - for (int i = 0; i < g_NpcBaseAttacksCount[npcIndex]; i++) - { - g_NpcBaseAttacks[npcIndex][i][1].baseAttackType = GetChaserProfileAttackType(profile, i); - for (int diffAtk = 0; diffAtk < Difficulty_Max; diffAtk++) - { - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamage = GetChaserProfileAttackDamage(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunSpeed = GetChaserProfileAttackRunSpeed(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackCooldown = GetChaserProfileAttackCooldown(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDamage = GetChaserProfileAttackProjectileDamage(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileSpeed = GetChaserProfileAttackProjectileSpeed(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileRadius = GetChaserProfileAttackProjectileRadius(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDeviation = GetChaserProfileAttackProjectileDeviation(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCount = GetChaserProfileAttackProjectileCount(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownPercent = GetChaserProfileAttackProjectileIceSlowPercent(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownDuration = GetChaserProfileAttackProjectileIceSlowDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletCount = GetChaserProfileAttackBulletCount(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletDamage = GetChaserProfileAttackBulletDamage(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletSpread = GetChaserProfileAttackBulletSpread(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDamage = GetChaserProfileAttackLaserDamage(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageForce = GetChaserProfileAttackDamageForce(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageType = GetChaserProfileAttackDamageType(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageDelay = GetChaserProfileAttackDamageDelay(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRange = GetChaserProfileAttackRange(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDuration = GetChaserProfileAttackDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackSpread = GetChaserProfileAttackSpread(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginRange = GetChaserProfileAttackBeginRange(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginFOV = GetChaserProfileAttackBeginFOV(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDisappear = GetChaserProfileAttackDisappear(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackIgnoreAlwaysLooking = GetChaserProfileAttackIgnoreAlwaysLooking(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeSteal = GetChaserProfileAttackLifeStealState(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeStealDuration = GetChaserProfileAttackLifeStealDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCrits = GetChaserProfileAttackCritProjectiles(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackWhileRunning = GetChaserProfileAttackRunWhileAttackingState(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDuration = GetChaserProfileAttackRunDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDelay = GetChaserProfileAttackRunDelay(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackExplosiveDanceRadius = GetChaserProfileAttackExplosiveDanceRadius(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDuration = GetChaserProfileAttackLaserDuration(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDontInterruptChaseInitial = GetChaserProfileAttackChaseInitialInterruptState(profile, i, diffAtk); - } - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDamageVsProps = GetChaserProfileAttackDamageVsProps(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackRepeat = GetChaserProfileAttackRepeat(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackMaxRepeats = GetChaserProfileMaxAttackRepeats(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackNextAttackTime = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackProjectileType = GetChaserProfileAttackProjectileType(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserSize = GetChaserProfileAttackLaserSize(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnDifficulty = GetChaserProfileAttackUseOnDifficulty(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnDifficulty = GetChaserProfileAttackBlockOnDifficulty(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackWeaponTypeInt = GetChaserProfileAttackWeaponTypeInt(profile, i); - - int laserColor[3]; - GetChaserProfileAttackLaserColor(profile, i, laserColor); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor = laserColor; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserAttachment = GetChaserProfileEnableLaserAttachment(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserNoise = GetChaserProfileAttackLaserNoise(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackPullIn = GetChaserProfileAttackPullIn(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackGestures = GetChaserProfileAttackGesturesState(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDeathCamOnLowHealth = GetChaserProfileAttackDeathCamLowHealth(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnHealth = GetChaserProfileAttackUseOnHealth(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnHealth = GetChaserProfileAttackBlockOnHealth(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].currentAttackRepeat = 0; - } - - // Get stun data. - if (!invincible) - { - g_NpcHasStunEnabled[npcIndex] = GetChaserProfileStunState(profile); - } - else - { - g_NpcHasStunEnabled[npcIndex] = false; - } - g_NpcStunCooldown[npcIndex] = GetChaserProfileStunCooldown(profile); - g_NpcStunFlashlightEnabled[npcIndex] = GetChaserProfileStunFlashlightState(profile); - g_NpcStunFlashlightDamage[npcIndex] = GetChaserProfileStunFlashlightDamage(profile); - g_NpcStunInitialHealth[npcIndex] = GetChaserProfileStunHealth(profile); - g_NpcChaseInitialOnStun[npcIndex] = GetChaserProfileStunOnChaseInitial(profile); - - g_NpcHasKeyDrop[npcIndex] = GetChaserProfileKeyDrop(profile); - - g_NpcCloakEnabled[npcIndex] = GetChaserProfileCloakState(profile); - g_NpcNextDecloakTime[npcIndex] = -1.0; - GetChaserProfileCloakOnSound(profile, g_SlenderCloakOnSound[npcIndex], sizeof(g_SlenderCloakOnSound[])); - GetChaserProfileCloakOffSound(profile, g_SlenderCloakOffSound[npcIndex], sizeof(g_SlenderCloakOffSound[])); - - float stunHealthPerPlayer = GetChaserProfileStunHealthPerPlayer(profile); - int count; - for (int client; client <= MaxClients; client++) - { - SF2_BasePlayer player = SF2_BasePlayer(client); - if (player.IsValid && player.IsEliminated) - { - count++; - } - } - - stunHealthPerPlayer *= float(count); - g_NpcStunInitialHealth[npcIndex] += stunHealthPerPlayer; - - g_NpcHasAlwaysLookAtTarget[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_AlwaysLookAtTarget); - g_NpcHasAlwaysLookAtTargetWhileAttacking[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_AlwaysLookAtTargetWhileAttacking); - g_NpcHasAlwaysLookAtTargetWhileChasing[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_AlwaysLookAtTargetWhileChasing); - g_NpcIgnoreNonMarkedForChase[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_IgnoreNonMarkedForChase); - - g_NpcChaseOnLook[npcIndex] = GetChaserProfileChaseOnLook(profile); - if (g_NpcChaseOnLookTarget[npcIndex] == null && g_NpcChaseOnLook[npcIndex]) - { - g_NpcChaseOnLookTarget[npcIndex] = new ArrayList(); - } - - NPCChaserSetStunHealth(npcIndex, NPCChaserGetStunInitialHealth(npcIndex)); - - NPCSetAddSpeed(npcIndex, -NPCGetAddSpeed(npcIndex)); - NPCSetAddMaxSpeed(npcIndex, -NPCGetAddMaxSpeed(npcIndex)); - NPCSetAddAcceleration(npcIndex, -NPCGetAddAcceleration(npcIndex)); - NPCChaserSetAddStunHealth(npcIndex, -NPCChaserGetAddStunHealth(npcIndex)); - - g_NpcHasEarthquakeFootstepsEnabled[npcIndex] = GetChaserProfileEarthquakeFootstepState(profile); - g_NpcEarthquakeFootstepsAmplitude[npcIndex] = GetChaserProfileEarthquakeFootstepAmplitude(profile); - g_NpcEarthquakeFootstepsFrequency[npcIndex] = GetChaserProfileEarthquakeFootstepFrequency(profile); - g_NpcEarthquakeFootstepsDuration[npcIndex] = GetChaserProfileEarthquakeFootstepDuration(profile); - g_NpcEarthquakeFootstepsRadius[npcIndex] = GetChaserProfileEarthquakeFootstepRadius(profile); - g_NpcHasEarthquakeFootstepsAirShake[npcIndex] = GetChaserProfileEarthquakeFootstepAirShake(profile); - - g_NpcHasDisappearOnStun[npcIndex] = GetChaserProfileDisappearOnStun(profile); - g_NpcHasDropItemOnStun[npcIndex] = GetChaserProfileStunItemDropState(profile); - g_NpcDropItemType[npcIndex] = GetChaserProfileStunItemDropType(profile); - g_NpcHasIsBoxingBoss[npcIndex] = GetChaserProfileBoxingState(profile); - g_NpcHasNormalSoundHookEnabled[npcIndex] = GetChaserProfileNormalSoundHook(profile); - g_NpcHasCloakToHealEnabled[npcIndex] = GetChaserProfileCloakToHeal(profile); - g_NpcHasCanUseChaseInitialAnimation[npcIndex] = GetChaserProfileChaseInitialAnimationState(profile); - g_NpcHasOldAnimationAIState[npcIndex] = GetChaserProfileOldAnimState(profile); - g_NpcHasCanUseAlertWalkingAnimation[npcIndex] = GetChaserProfileAlertWalkingState(profile); - g_NpcOriginalVisibility[npcIndex] = GetChaserProfileUnnerfedVisibility(profile); - - g_NpcHasHasCrawling[npcIndex] = GetChaserProfileCrawlState(profile); - GetChaserProfileCrawlMins(profile, g_NpcCrawlDetectMins[npcIndex]); - GetChaserProfileCrawlMaxs(profile, g_NpcCrawlDetectMaxs[npcIndex]); - - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - g_NpcCrawlDetectMins[npcIndex][0] /= 2.0; - g_NpcCrawlDetectMins[npcIndex][1] /= 2.0; - g_NpcCrawlDetectMins[npcIndex][2] /= 2.0; - g_NpcCrawlDetectMaxs[npcIndex][0] /= 2.0; - g_NpcCrawlDetectMaxs[npcIndex][1] /= 2.0; - g_NpcCrawlDetectMaxs[npcIndex][2] /= 2.0; - } - - g_NpcAlreadyAttacked[npcIndex] = false; - g_NpcVelocityCancel[npcIndex] = false; - - g_NpcStealingLife[npcIndex] = false; - g_NpcLifeStealTimer[npcIndex] = null; - - g_NpcHasCloaked[npcIndex] = false; - - //Get Projectile Data - g_NpcHasProjectileEnabled[npcIndex] = GetChaserProfileProjectileState(profile); - g_NpcProjectileType[npcIndex] = GetChaserProfileProjectileType(profile); - g_NpcHasCriticalRockets[npcIndex] = GetChaserProfileCriticalRockets(profile); - GetChaserProfileFireballShootSound(profile, g_SlenderFireballShootSound[npcIndex], sizeof(g_SlenderFireballShootSound[])); - GetChaserProfileFireballTrail(profile, g_SlenderFireballTrail[npcIndex], sizeof(g_SlenderFireballTrail[])); - GetChaserProfileFireballExplodeSound(profile, g_SlenderFireballExplodeSound[npcIndex], sizeof(g_SlenderFireballExplodeSound[])); - - GetChaserProfileIceballSlowSound(profile, g_SlenderIceballImpactSound[npcIndex], sizeof(g_SlenderIceballImpactSound[])); - GetChaserProfileIceballTrail(profile, g_SlenderIceballTrail[npcIndex], sizeof(g_SlenderIceballTrail)); - - GetChaserProfileRocketShootSound(profile, g_SlenderRocketShootSound[npcIndex], sizeof(g_SlenderRocketShootSound[])); - GetChaserProfileRocketModel(profile, g_SlenderRocketModel[npcIndex], sizeof(g_SlenderRocketModel[])); - GetChaserProfileRocketExplodeSound(profile, g_SlenderRocketExplodeSound[npcIndex], sizeof(g_SlenderRocketExplodeSound[])); - GetChaserProfileRocketTrail(profile, g_SlenderRocketTrailParticle[npcIndex], sizeof(g_SlenderRocketTrailParticle[])); - GetChaserProfileRocketExplodeParticle(profile, g_SlenderRocketExplodeParticle[npcIndex], sizeof(g_SlenderRocketExplodeParticle[])); - GetChaserProfileSentryRocketShootSound(profile, g_SlenderSentryRocketShootSound[npcIndex], sizeof(g_SlenderSentryRocketShootSound[])); - - GetChaserProfileGrenadeShootSound(profile, g_SlenderGrenadeShootSound[npcIndex], sizeof(g_SlenderGrenadeShootSound[])); - - GetChaserProfileArrowShootSound(profile, g_SlenderArrowShootSound[npcIndex], sizeof(g_SlenderArrowShootSound[])); - - GetChaserProfileManglerShootSound(profile, g_SlenderManglerShootSound[npcIndex], sizeof(g_SlenderManglerShootSound[])); - - GetChaserProfileBaseballShootSound(profile, g_SlenderBaseballShootSound[npcIndex], sizeof(g_SlenderBaseballShootSound[])); - - g_NpcHasUseShootGesture[npcIndex] = GetChaserProfileGestureShoot(profile); - g_NpcHasUseProjectileAmmo[npcIndex] = GetChaserProfileProjectileAmmoState(profile); - g_NpcHasUseChargeUpProjectiles[npcIndex] = GetChaserProfileChargeUpProjectilesState(profile); - g_NpcProjectileCooldown[npcIndex] = 0.0; - g_NpcReloadingProjectiles[npcIndex] = false; - - g_NpcHasUseAdvancedDamageEffects[npcIndex] = GetChaserProfileEnableAdvancedDamageEffects(profile); - g_NpcHasAdvancedDamageEffectsRandom[npcIndex] = GetChaserProfileEnableAdvancedDamageEffectsRandom(profile); - g_NpcHasAttachDamageParticle[npcIndex] = GetChaserProfileEnableAdvancedDamageParticles(profile); - g_NpcRandomAttackIndexes[npcIndex] = GetChaserProfileRandomAttackIndexes(profile); - g_NpcRandomStunType[npcIndex] = GetChaserProfileRandomStunType(profile); - - g_NpcHasJaratePlayerEnabled[npcIndex] = GetChaserProfileJarateState(profile); - g_NpcJarateAttackIndexes[npcIndex] = GetChaserProfileJarateAttackIndexes(profile); - g_NpcHasJaratePlayerBeamParticle[npcIndex] = GetChaserProfileJarateBeamParticle(profile); - GetChaserProfileJarateHitSound(profile, g_SlenderJarateHitSound[npcIndex], sizeof(g_SlenderJarateHitSound[])); - - g_NpcHasMilkPlayerEnabled[npcIndex] = GetChaserProfileMilkState(profile); - g_NpcMilkAttackIndexes[npcIndex] = GetChaserProfileMilkAttackIndexes(profile); - g_NpcHasMilkPlayerBeamParticle[npcIndex] = GetChaserProfileMilkBeamParticle(profile); - GetChaserProfileMilkHitSound(profile, g_SlenderMilkHitSound[npcIndex], sizeof(g_SlenderMilkHitSound[])); - - g_NpcHasGasPlayerEnabled[npcIndex] = GetChaserProfileGasState(profile); - g_NpcGasAttackIndexes[npcIndex] = GetChaserProfileGasAttackIndexes(profile); - g_NpcHasGasPlayerBeamParticle[npcIndex] = GetChaserProfileGasBeamParticle(profile); - GetChaserProfileGasHitSound(profile, g_SlenderGasHitSound[npcIndex], sizeof(g_SlenderGasHitSound[])); - - g_NpcHasMarkPlayerEnabled[npcIndex] = GetChaserProfileMarkState(profile); - g_NpcMarkAttackIndexes[npcIndex] = GetChaserProfileMarkAttackIndexes(profile); - - g_NpcHasSilentMarkPlayerEnabled[npcIndex] = GetChaserProfileSilentMarkState(profile); - g_NpcSilentMarkAttackIndexes[npcIndex] = GetChaserProfileSilentMarkAttackIndexes(profile); - - g_NpcHasIgnitePlayerEnabled[npcIndex] = GetChaserProfileIgniteState(profile); - g_NpcIgniteAttackIndexes[npcIndex] = GetChaserProfileIgniteAttackIndexes(profile); - - g_NpcHasStunPlayerEnabled[npcIndex] = GetChaserProfileStunAttackState(profile); - g_NpcStunAttackIndexes[npcIndex] = GetChaserProfileStunAttackIndexes(profile); - g_NpcStunAttackType[npcIndex] = GetChaserProfileStunDamageType(profile); - g_NpcHasStunPlayerBeamParticle[npcIndex] = GetChaserProfileStunAttackBeamParticle(profile); - GetChaserProfileStunHitSound(profile, g_SlenderStunHitSound[npcIndex], sizeof(g_SlenderStunHitSound[])); - - g_NpcHasBleedPlayerEnabled[npcIndex] = GetChaserProfileBleedState(profile); - g_NpcBleedAttackIndexes[npcIndex] = GetChaserProfileBleedAttackIndexes(profile); - - g_NpcHasElectricPlayerEnabled[npcIndex] = GetChaserProfileEletricAttackState(profile); - g_NpcElectricAttackIndexes[npcIndex] = GetChaserProfileEletricAttackIndexes(profile); - g_NpcHasElectricPlayerBeamParticle[npcIndex] = GetChaserProfileEletricBeamParticle(profile); - - g_NpcHasSmitePlayerEnabled[npcIndex] = GetChaserProfileSmiteState(profile); - g_NpcHasSmiteMessage[npcIndex] = GetChaserProfileSmiteMessage(profile); - g_NpcSmiteAttackIndexes[npcIndex] = GetChaserProfileSmiteAttackIndexes(profile); - g_NpcSmiteDamage[npcIndex] = GetChaserProfileSmiteDamage(profile); - g_NpcSmiteDamageType[npcIndex] = GetChaserProfileSmiteDamageType(profile); - GetChaserProfileSmiteHitSound(profile, g_SlenderSmiteSound[npcIndex], sizeof(g_SlenderSmiteSound[])); - - g_NpcHasXenobladeBreakComboSystem[npcIndex] = GetChaserProfileXenobladeCombo(profile); - g_NpcXenobladeBreakDuration[npcIndex] = GetChaserProfileXenobladeBreakDuration(profile); - g_NpcXenobladeToppleDuration[npcIndex] = GetChaserProfileXenobladeToppleDuration(profile); - g_NpcXenobladeToppleSlowdown[npcIndex] = GetChaserProfileXenobladeToppleSlowdown(profile); - g_NpcXenobladeDazeDuration[npcIndex] = GetChaserProfileXenobladeDazeDuration(profile); - - int smiteColor[4]; - GetChaserProfileSmiteColor(profile, smiteColor); - - g_NpcSmiteColorR[npcIndex] = smiteColor[0]; - g_NpcSmiteColorG[npcIndex] = smiteColor[1]; - g_NpcSmiteColorB[npcIndex] = smiteColor[2]; - g_NpcSmiteTransparency[npcIndex] = smiteColor[3]; - g_SlenderHasDamageParticleEffect[npcIndex] = GetChaserProfileDamageParticleState(profile); - - g_NpcHasShockwaveEnabled[npcIndex] = GetChaserProfileShockwaveState(profile); - g_NpcHasShockwaveStunEnabled[npcIndex] = GetChaserProfileShockwaveStunState(profile); - g_NpcShockwaveAttackIndexes[npcIndex] = GetChaserProfileShockwaveAttackIndexes(profile); - g_NpcShockwaveWidth[npcIndex][0] = GetChaserProfileShockwaveWidth1(profile); - g_NpcShockwaveWidth[npcIndex][1] = GetChaserProfileShockwaveWidth2(profile); - g_NpcShockwaveAmplitude[npcIndex] = GetChaserProfileShockwaveAmplitude(profile); - GetChaserProfileShockwaveBeamSprite(profile, g_SlenderShockwaveBeamSprite[npcIndex], sizeof(g_SlenderShockwaveBeamSprite[])); - GetChaserProfileShockwaveHaloSprite(profile, g_SlenderShockwaveHaloSprite[npcIndex], sizeof(g_SlenderShockwaveHaloSprite[])); - - g_NpcHasTrapsEnabled[npcIndex] = GetChaserProfileTrapState(profile); - g_NpcTrapType[npcIndex] = GetChaserProfileTrapType(profile); - GetChaserProfileTrapModel(profile, g_SlenderTrapModel[npcIndex], sizeof(g_SlenderTrapModel[])); - GetChaserProfileTrapDeploySound(profile, g_SlenderTrapDeploySound[npcIndex], sizeof(g_SlenderTrapDeploySound[])); - GetChaserProfileTrapMissSound(profile, g_SlenderTrapMissSound[npcIndex], sizeof(g_SlenderTrapMissSound[])); - GetChaserProfileTrapCatchSound(profile, g_SlenderTrapHitSound[npcIndex], sizeof(g_SlenderTrapHitSound[])); - GetChaserProfileTrapAnimIdle(profile, g_SlenderTrapAnimIdle[npcIndex], sizeof(g_SlenderTrapAnimIdle[])); - GetChaserProfileTrapAnimClose(profile, g_SlenderTrapAnimClose[npcIndex], sizeof(g_SlenderTrapAnimClose[])); - GetChaserProfileTrapAnimOpen(profile, g_SlenderTrapAnimOpen[npcIndex], sizeof(g_SlenderTrapAnimOpen[])); - - g_SlenderHasAutoChaseEnabled[npcIndex] = GetChaserProfileAutoChaseState(profile); - g_NpcHasAutoChaseSprinters[npcIndex] = GetChaserProfileAutoChaseSprinterState(profile); - g_NpcAutoChaseSprinterCooldown[npcIndex] = 0.0; - g_NpcInAutoChase[npcIndex] = false; - g_SlenderIsAutoChasingLoudPlayer[npcIndex] = false; - - g_SlenderChasesEndlessly[npcIndex] = GetChaserProfileEndlessChasingState(profile); - - g_NpcLaserTimer[npcIndex] = 0.0; - - g_NpcHasCanSelfHeal[npcIndex] = GetChaserProfileSelfHealState(profile); - g_NpcStartSelfHealPercentage[npcIndex] = GetChaserProfileSelfHealStartPercentage(profile); - g_NpcSelfHealPercentageOne[npcIndex] = GetChaserProfileSelfHealPercentageOne(profile); - g_NpcSelfHealPercentageTwo[npcIndex] = GetChaserProfileSelfHealPercentageTwo(profile); - g_NpcSelfHealPercentageThree[npcIndex] = GetChaserProfileSelfHealPercentageThree(profile); - g_NpcIsRunningToHeal[npcIndex] = false; - g_NpcIsHealing[npcIndex] = false; - g_NpcFleeHealTimer[npcIndex] = 0.0; - g_NpcSelfHealStage[npcIndex] = 0; - g_NpcHealCount[npcIndex] = 0; - g_NpcSetHealDestination[npcIndex] = false; - - g_NpcBoxingCurrentDifficulty[npcIndex] = 1; - g_NpcBoxingRagePhase[npcIndex] = 0; - - g_NpcCopyAlerted[npcIndex] = false; -} - -void NPCChaserOnRemoveProfile(int npcIndex) -{ - NPCChaserResetValues(npcIndex); -} - -/** - * Resets all global variables on a specified NPC. Usually this should be done last upon removing a boss from the game. - */ -static void NPCChaserResetValues(int npcIndex) -{ - g_NpcWakeRadius[npcIndex] = 0.0; - - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - g_NpcWalkSpeed[npcIndex][difficulty] = 0.0; - g_NpcMaxWalkSpeed[npcIndex][difficulty] = 0.0; - - g_NpcAlertGracetime[npcIndex][difficulty] = 0.0; - g_NpcAlertDuration[npcIndex][difficulty] = 0.0; - g_NpcChaseDuration[npcIndex][difficulty] = 0.0; - - g_NpcSoundCountToAlert[npcIndex][difficulty] = 0; - - g_NpcCloakCooldown[npcIndex][difficulty] = 0.0; - g_NpcCloakRange[npcIndex][difficulty] = 0.0; - g_NpcDecloakRange[npcIndex][difficulty] = 0.0; - g_NpcCloakDuration[npcIndex][difficulty] = 0.0; - g_NpcCloakSpeedMultiplier[npcIndex][difficulty] = 0.0; - - g_NpcProjectileCooldownMin[npcIndex][difficulty] = 0.0; - g_NpcProjectileCooldownMax[npcIndex][difficulty] = 0.0; - g_NpcProjectileSpeed[npcIndex][difficulty] = 0.0; - g_NpcProjectileDamage[npcIndex][difficulty] = 0.0; - g_NpcProjectileRadius[npcIndex][difficulty] = 0.0; - g_NpcProjectileDeviation[npcIndex][difficulty] = 0.0; - g_NpcProjectileCount[npcIndex][difficulty] = 0; - g_IceballSlowdownDuration[npcIndex][difficulty] = 0.0; - g_IceballSlowdownPercent[npcIndex][difficulty] = 0.0; - g_NpcProjectileLoadedAmmo[npcIndex][difficulty] = 0; - g_NpcProjectileReloadTime[npcIndex][difficulty] = 0.0; - g_NpcProjectileChargeUpTime[npcIndex][difficulty] = 0.0; - - g_NpcRandomDuration[npcIndex][difficulty] = 0.0; - g_NpcRandomSlowdown[npcIndex][difficulty] = 0.0; - g_NpcJarateDuration[npcIndex][difficulty] = 0.0; - g_NpcMilkDuration[npcIndex][difficulty] = 0.0; - g_NpcGasDuration[npcIndex][difficulty] = 0.0; - g_NpcMarkDuration[npcIndex][difficulty] = 0.0; - g_NpcSilentMarkDuration[npcIndex][difficulty] = 0.0; - g_NpcIgniteDuration[npcIndex][difficulty] = 0.0; - g_NpcIgniteDelay[npcIndex][difficulty] = 0.0; - g_NpcStunAttackDuration[npcIndex][difficulty] = 0.0; - g_NpcStunAttackSlowdown[npcIndex][difficulty] = 0.0; - g_NpcBleedDuration[npcIndex][difficulty] = 0.0; - g_NpcElectricDuration[npcIndex][difficulty] = 0.0; - g_NpcElectricSlowdown[npcIndex][difficulty] = 0.0; - - g_NpcShockwaveDrain[npcIndex][difficulty] = 0.0; - g_NpcShockwaveForce[npcIndex][difficulty] = 0.0; - g_NpcShockwaveHeight[npcIndex][difficulty] = 0.0; - g_NpcShockwaveRange[npcIndex][difficulty] = 0.0; - g_NpcShockwaveStunDuration[npcIndex][difficulty] = 0.0; - g_NpcShockwaveStunSlowdown[npcIndex][difficulty] = 0.0; - - g_NpcNextTrapSpawn[npcIndex][difficulty] = 0.0; - g_SlenderNextTrapPlacement[npcIndex] = 0.0; - - g_NpcSearchWanderRangeMin[npcIndex][difficulty] = 0.0; - g_NpcSearchWanderRangeMax[npcIndex][difficulty] = 0.0; - - g_NpcAutoChaseThreshold[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddGeneral[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddFootstep[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddLoudFootstep[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddVoice[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddWeapon[npcIndex][difficulty] = 0; - - g_NpcCrawlSpeedMultiplier[npcIndex][difficulty] = 0.0; - } - - // Clear attack data. - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS - 1; i++) - { - // Base attack data. - g_NpcBaseAttacks[npcIndex][i][1].baseAttackType = SF2BossAttackType_Invalid; - for (int diffAtk = 0; diffAtk < Difficulty_Max; diffAtk++) - { - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamage = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunSpeed = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackCooldown = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDamage = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileSpeed = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileRadius = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDeviation = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCount = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownPercent = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletCount = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletDamage = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletSpread = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDamage = 0.0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageForce = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageType = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageDelay = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRange = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackSpread = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginRange = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginFOV = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDisappear = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackIgnoreAlwaysLooking = false; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeSteal = false; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeStealDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCrits = false; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackWhileRunning = false; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDelay = 0.0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackExplosiveDanceRadius = 0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDuration = 0.0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDontInterruptChaseInitial = false; - } - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDamageVsProps = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackRepeat = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackMaxRepeats = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackNextAttackTime = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].weaponAttackIndex = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackProjectileType = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserSize = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor[0] = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor[1] = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor[2] = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserAttachment = false; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserNoise = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackPullIn = false; - g_NpcBaseAttacks[npcIndex][i][1].currentAttackRepeat = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnDifficulty = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnDifficulty = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackWeaponTypeInt = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackGestures = false; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDeathCamOnLowHealth = false; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnHealth = -1.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnHealth = -1.0; - } - - g_NpcHasEarthquakeFootstepsEnabled[npcIndex] = false; - g_NpcEarthquakeFootstepsAmplitude[npcIndex] = 0.0; - g_NpcEarthquakeFootstepsFrequency[npcIndex] = 0.0; - g_NpcEarthquakeFootstepsDuration[npcIndex] = 0.0; - g_NpcEarthquakeFootstepsRadius[npcIndex] = 0.0; - g_NpcHasEarthquakeFootstepsAirShake[npcIndex] = false; - - g_NpcHasDisappearOnStun[npcIndex] = false; - g_NpcHasDropItemOnStun[npcIndex] = false; - g_NpcDropItemType[npcIndex] = 0; - g_NpcHasIsBoxingBoss[npcIndex] = false; - g_NpcHasNormalSoundHookEnabled[npcIndex] = false; - g_NpcHasCloakToHealEnabled[npcIndex] = false; - g_NpcHasCanUseChaseInitialAnimation[npcIndex] = false; - g_NpcHasOldAnimationAIState[npcIndex] = false; - g_NpcHasCanUseAlertWalkingAnimation[npcIndex] = false; - - g_NpcHasHasCrawling[npcIndex] = false; - g_NpcIsCrawling[npcIndex] = false; - - g_NpcHasStunEnabled[npcIndex] = false; - g_NpcAlreadyAttacked[npcIndex] = false; - g_NpcStunCooldown[npcIndex] = 0.0; - g_NpcStunFlashlightEnabled[npcIndex] = false; - g_NpcStunInitialHealth[npcIndex] = 0.0; - g_NpcStunAddHealth[npcIndex] = 0.0; - g_NpcChaseInitialOnStun[npcIndex] = false; - g_NpcOriginalVisibility[npcIndex] = false; - - g_NpcCloakEnabled[npcIndex] = false; - g_NpcNextDecloakTime[npcIndex] = -1.0; - g_NpcHasCloaked[npcIndex] = false; - g_NpcVelocityCancel[npcIndex] = false; - g_NpcStealingLife[npcIndex] = false; - g_NpcLifeStealTimer[npcIndex] = null; - g_NpcChaseOnLook[npcIndex] = false; - if (g_NpcChaseOnLookTarget[npcIndex] != null) - { - delete g_NpcChaseOnLookTarget[npcIndex]; - g_NpcChaseOnLookTarget[npcIndex] = null; - } - - g_NpcHasAlwaysLookAtTarget[npcIndex] = false; - g_NpcHasAlwaysLookAtTargetWhileAttacking[npcIndex] = false; - g_NpcHasAlwaysLookAtTargetWhileChasing[npcIndex] = false; - g_NpcIgnoreNonMarkedForChase[npcIndex] = false; - - g_NpcUsedRage1[npcIndex] = false; - g_NpcUsedRage2[npcIndex] = false; - g_NpcHasUsedRage3[npcIndex] = false; - - g_NpcHasProjectileEnabled[npcIndex] = false; - g_NpcProjectileType[npcIndex] = SF2BossProjectileType_Invalid; - g_NpcHasCriticalRockets[npcIndex] = false; - g_NpcHasUseShootGesture[npcIndex] = false; - g_NpcHasUseProjectileAmmo[npcIndex] = false; - g_NpcHasUseChargeUpProjectiles[npcIndex] = false; - g_NpcProjectileAmmo[npcIndex] = 0; - g_NpcProjectileCooldown[npcIndex] = 0.0; - g_NpcProjectileTimeToReload[npcIndex] = 0.0; - g_NpcReloadingProjectiles[npcIndex] = false; - - g_NpcHasUseAdvancedDamageEffects[npcIndex] = false; - g_NpcHasAdvancedDamageEffectsRandom[npcIndex] = false; - g_NpcHasAttachDamageParticle[npcIndex] = false; - g_NpcRandomAttackIndexes[npcIndex] = 0; - g_NpcRandomStunType[npcIndex] = 0; - g_NpcHasJaratePlayerEnabled[npcIndex] = false; - g_NpcJarateAttackIndexes[npcIndex] = 0; - g_NpcHasMilkPlayerEnabled[npcIndex] = false; - g_NpcMilkAttackIndexes[npcIndex] = 0; - g_NpcHasGasPlayerEnabled[npcIndex] = false; - g_NpcGasAttackIndexes[npcIndex] = 0; - g_NpcHasMarkPlayerEnabled[npcIndex] = false; - g_NpcMarkAttackIndexes[npcIndex] = 0; - g_NpcHasSilentMarkPlayerEnabled[npcIndex] = false; - g_NpcSilentMarkAttackIndexes[npcIndex] = 0; - g_NpcHasIgnitePlayerEnabled[npcIndex] = false; - g_NpcIgniteAttackIndexes[npcIndex] = 0; - g_NpcHasStunPlayerEnabled[npcIndex] = false; - g_NpcStunAttackIndexes[npcIndex] = 0; - g_NpcStunAttackType[npcIndex] = 0; - g_NpcHasBleedPlayerEnabled[npcIndex] = false; - g_NpcBleedAttackIndexes[npcIndex] = 0; - g_NpcHasElectricPlayerEnabled[npcIndex] = false; - g_NpcElectricAttackIndexes[npcIndex] = 0; - g_NpcHasSmitePlayerEnabled[npcIndex] = false; - g_NpcHasSmiteMessage[npcIndex] = false; - g_NpcSmiteAttackIndexes[npcIndex] = 0; - g_NpcHasXenobladeBreakComboSystem[npcIndex] = false; - g_NpcXenobladeBreakDuration[npcIndex] = 0.0; - g_NpcXenobladeToppleDuration[npcIndex] = 0.0; - g_NpcXenobladeToppleSlowdown[npcIndex] = 0.0; - g_NpcXenobladeDazeDuration[npcIndex] = 0.0; - g_NpcSmiteDamage[npcIndex] = 0.0; - g_NpcSmiteDamageType[npcIndex] = 0; - g_NpcSmiteColorR[npcIndex] = 0; - g_NpcSmiteColorG[npcIndex] = 0; - g_NpcSmiteColorB[npcIndex] = 0; - g_NpcSmiteTransparency[npcIndex] = 0; - g_NpcHasShockwaveEnabled[npcIndex] = false; - g_NpcHasShockwaveStunEnabled[npcIndex] = false; - g_NpcShockwaveAttackIndexes[npcIndex] = 0; - g_NpcShockwaveWidth[npcIndex][0] = 0.0; - g_NpcShockwaveWidth[npcIndex][1] = 0.0; - g_NpcShockwaveAmplitude[npcIndex] = 0.0; - g_NpcTimeUntilChaseAfterInitial[npcIndex] = 0.0; - g_SlenderHasDamageParticleEffect[npcIndex] = false; - - g_NpcHasTrapsEnabled[npcIndex] = false; - g_NpcTrapType[npcIndex] = 0; - - g_SlenderHasAutoChaseEnabled[npcIndex] = false; - g_NpcInAutoChase[npcIndex] = false; - g_NpcHasAutoChaseSprinters[npcIndex] = false; - g_NpcAutoChaseSprinterCooldown[npcIndex] = 0.0; - g_SlenderIsAutoChasingLoudPlayer[npcIndex] = false; - - g_SlenderChasesEndlessly[npcIndex] = false; - - NPCSetAddSpeed(npcIndex, -NPCGetAddSpeed(npcIndex)); - NPCSetAddMaxSpeed(npcIndex, -NPCGetAddMaxSpeed(npcIndex)); - NPCSetAddAcceleration(npcIndex, -NPCGetAddAcceleration(npcIndex)); - NPCChaserSetAddStunHealth(npcIndex, -NPCChaserGetAddStunHealth(npcIndex)); - - NPCChaserSetStunHealth(npcIndex, 0.0); - - g_NpcState[npcIndex] = -1; - - g_NpcLaserTimer[npcIndex] = 0.0; - - g_NpcHasCanSelfHeal[npcIndex] = false; - g_NpcStartSelfHealPercentage[npcIndex] = 0.0; - g_NpcSelfHealPercentageOne[npcIndex] = 0.0; - g_NpcSelfHealPercentageTwo[npcIndex] = 0.0; - g_NpcSelfHealPercentageThree[npcIndex] = 0.0; - g_NpcIsRunningToHeal[npcIndex] = false; - g_NpcIsHealing[npcIndex] = false; - g_NpcFleeHealTimer[npcIndex] = 0.0; - g_NpcSelfHealStage[npcIndex] = 0; - g_NpcHealCount[npcIndex] = 0; - g_NpcSetHealDestination[npcIndex] = false; - - g_NpcBoxingCurrentDifficulty[npcIndex] = 0; - g_NpcBoxingRagePhase[npcIndex] = 0; - - g_NpcCopyAlerted[npcIndex] = false; -} - -void Spawn_Chaser(int bossIndex) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - g_LastStuckTime[bossIndex] = 0.0; - g_SlenderOldState[bossIndex] = STATE_IDLE; - g_NpcCopyAlerted[bossIndex] = false; - g_NpcNextDecloakTime[bossIndex] = -1.0; - g_NpcIsCrawling[bossIndex] = false; - g_NpcChangeToCrawl[bossIndex] = false; - g_SlenderSoundPositionSetCooldown[bossIndex] = 0.0; - - NPCSetAddSpeed(bossIndex, -NPCGetAddSpeed(bossIndex)); - NPCSetAddMaxSpeed(bossIndex, -NPCGetAddMaxSpeed(bossIndex)); - NPCSetAddAcceleration(bossIndex, -NPCGetAddAcceleration(bossIndex)); - NPCChaserSetAddStunHealth(bossIndex, -NPCChaserGetAddStunHealth(bossIndex)); - - g_NpcInstantKillThink[bossIndex] = CreateTimer(0.0, Timer_InstantKillThink, bossIndex, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); - - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - g_SlenderNextWanderPos[bossIndex][difficulty] = GetGameTime() + - GetRandomFloat(GetChaserProfileWanderEnterTimeMin(profile, difficulty), GetChaserProfileWanderEnterTimeMax(profile, difficulty)); - } - -} - -void Despawn_Chaser(int bossIndex) -{ - g_NpcInstantKillThink[bossIndex] = null; -} - -static Action Timer_InstantKillThink(Handle timer, int bossIndex) -{ - if (NPCGetUniqueID(bossIndex) == -1) - { - return Plugin_Stop; - } - - if (timer != g_NpcInstantKillThink[bossIndex]) - { - return Plugin_Stop; - } - - CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); - if (!boss.IsValid()) - { - return Plugin_Stop; - } - - float radius = NPCGetInstantKillRadius(bossIndex); - - if (radius < 0.0) - { - return Plugin_Stop; - } - - if (g_SlenderInDeathcam[bossIndex]) - { - return Plugin_Continue; - } - - if ((NPCGetFlags(bossIndex) & SFF_MARKEDASFAKE)) - { - return Plugin_Continue; - } - - float slenderPos[3]; - boss.GetAbsOrigin(slenderPos); - - bool attackWaiters = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int ref = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - if (!ref || ref == INVALID_ENT_REFERENCE) - { - return Plugin_Continue; - } - SF2_BasePlayer target = SF2_BasePlayer(ref); - - if (!target.IsValid || target.IsInDeathCam || target.IsInGhostMode || !target.IsAlive || target.IsProxy) - { - return Plugin_Continue; - } - - if (!attackWaiters && target.IsEliminated) - { - return Plugin_Continue; - } - - float myPos[3]; - - target.GetAbsOrigin(myPos); - myPos[2] += 35.0; - slenderPos[2] += 35.0; - - if ((GetVectorSquareMagnitude(myPos, slenderPos) <= SquareFloat(radius) && - (GetGameTime() - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) - && !g_SlenderInDeathcam[bossIndex]) - { - if (target.CanSeeSlender(bossIndex, false, _, !attackWaiters)) - { - g_SlenderLastKill[bossIndex] = GetGameTime(); - slenderPos[2] -= 35.0; - target.StartDeathCam(bossIndex, slenderPos); - } - } - - return Plugin_Continue; -} - -// So this is how the thought process of the bosses should go. -// 1. Search for enemy; either by sight or by sound. -// - Any noticeable sounds should be investigated. -// - Too many sounds will put me in alert mode. -// 2. Alert of an enemy; I saw something or I heard something unusual -// - Go to the position where I last heard the sound. -// - Keep on searching until I give up. Then drop back to idle mode. -// 3. Found an enemy! Give chase! -// - Keep on chasing until enemy is killed or I give up. -// - Keep a path in memory as long as I still have him in my sights. -// - If I lose sight or I'm unable to traverse safely, find paths around obstacles and follow memorized path. -// - If I reach the end of my path and I still don't see him and I still want to pursue him, keep on going in the direction I'm going. - -bool IsTargetValidForSlender(SF2_BasePlayer target, bool includeEliminated = false) -{ - if (!target.IsValid) - { - return false; - } - - if (target.IsValid) - { - if (!target.IsAlive || - target.IsInDeathCam || - (!includeEliminated && target.IsEliminated) || - target.IsInGhostMode || - target.HasEscaped) - { - return false; - } - } - - return true; -} - -SF2_BasePlayer NPCChaserGetClosestPlayer(int slender) -{ - if (!g_Enabled) - { - return SF2_INVALID_PLAYER; - } - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return SF2_INVALID_PLAYER; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return SF2_INVALID_PLAYER; - } - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float position[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", position); - - SF2_BasePlayer closestTarget = SF2_INVALID_PLAYER; - float searchRadius = NPCGetSearchRadius(bossIndex, difficulty); - - for (int i = 1; i <= MaxClients; i++) - { - SF2_BasePlayer client = SF2_BasePlayer(i); - if (!client.IsValid || client.IsInGhostMode || client.IsProxy || !client.IsAlive || client.IsEliminated) - { - continue; - } - - float clientPos[3]; - client.GetAbsOrigin(clientPos); - - float distance = GetVectorSquareMagnitude(position, clientPos); - - if (distance < SquareFloat(searchRadius)) - { - closestTarget = client; - searchRadius = distance; - } - } - - if (closestTarget.IsValid) - { - return closestTarget; - } - - return SF2_INVALID_PLAYER; -} - -bool NPCGetWanderPosition(SF2NPC_Chaser boss) -{ - // We're allowed to move in wander mode. Get a new wandering position and create a path to follow. - // If the position can't be reached, then just get to the closest area that we can get. - int difficulty = GetLocalGlobalDifficulty(boss.Index); - float myPos[3]; - boss.GetAbsOrigin(myPos); - float wanderRangeMin = NPCChaserGetWanderRangeMin(boss.Index, difficulty); - float wanderRangeMax = NPCChaserGetWanderRangeMax(boss.Index, difficulty); - float wanderRange = GetRandomFloat(wanderRangeMin, wanderRangeMax); - CBaseCombatCharacter npc = CBaseCombatCharacter(boss.EntIndex); - CNavArea navArea = npc.GetLastKnownArea(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(navArea, wanderRange); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) - { - continue; - } - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - if (areaArray.Length <= 0) - { - return false; - } - float wanderPos[3]; - CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); - if (wanderArea == NULL_AREA) - { - return false; - } - wanderArea.GetCenter(wanderPos); - - boss.SetGoalPos(wanderPos); - - g_SlenderNextPathTime[boss.Index] = -1.0; // We're not going to wander around too much, so no need for a time constraint. - - delete collector; - delete areaArray; - return true; -} - -void NPCChaserUpdateBossAnimation(int bossIndex, int ent, int state, bool spawn = false) -{ - char animation[256]; - float playbackRate, tempFootsteps, cycle, footstepTime, tempDuration, duration; - bool animationFound = false; - int index; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileMasterAnimationsData animData; - GetBossProfileAnimationsData(profile, animData); - - bool clearLayers = GetChaserProfileClearLayersState(profile); - - switch (state) - { - case STATE_IDLE: - { - if (!spawn) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Spawn], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - } - case STATE_WANDER: - { - if (NPCGetFlags(bossIndex) & SFF_WANDERMOVE) - { - if (!g_NpcIsCrawling[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Walk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_CrawlWalk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - } - case STATE_ALERT: - { - if (!g_NpcIsCrawling[bossIndex]) - { - if (NPCChaserCanUseAlertWalkingAnimation(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_WalkAlert], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Walk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_CrawlWalk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - case STATE_CHASE: - { - if (!g_NpcUsesChaseInitialAnimation[bossIndex] && - !NPCIsRaging(bossIndex) && - !g_NpcUsesHealAnimation[bossIndex] && !g_NpcUseStartFleeAnimation[bossIndex] && - !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex] && !g_NpcIsCrawling[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Run], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else if (g_NpcUsesChaseInitialAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (NPCIsRaging(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Rage], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (g_NpcIsCrawling[bossIndex] && !g_NpcUsesChaseInitialAnimation[bossIndex] && - !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex] && - !NPCIsRaging(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_CrawlRun], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - case STATE_ATTACK: - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index, NPCGetCurrentAttackIndex(bossIndex)); - } - case STATE_STUN: - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Stun], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, footstepTime, index); - } - case STATE_DEATHCAM: - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, tempFootsteps, index); - } - } - - if (g_NpcUseFireAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Shoot], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index, NPCGetCurrentAttackIndex(bossIndex)); - } - - if (NPCIsRaging(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Rage], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (g_NpcUseStartFleeAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_FleeInitial], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (g_NpcUsesHealAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Heal], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, tempFootsteps, index); - } - - if (playbackRate < -12.0) - { - playbackRate = -12.0; - } - if (playbackRate > 12.0) - { - playbackRate = 12.0; - } - - if (animationFound && animation[0] != '\0') - { - Action action = Plugin_Continue; - Call_StartForward(g_OnBossAnimationUpdateFwd); - Call_PushCell(bossIndex); - Call_Finish(action); - if (action != Plugin_Handled) - { - g_SlenderFootstepTime[bossIndex] = footstepTime; - g_SlenderAnimationDuration[bossIndex] = duration; - g_NpcCurrentAnimationSequencePlaybackRate[bossIndex] = playbackRate; - g_NpcCurrentAnimationSequence[bossIndex] = EntitySetAnimation(ent, animation, playbackRate, _, cycle); - g_NpcCurrentAnimationSequenceName[bossIndex] = animation; - EntitySetAnimation(ent, animation, playbackRate, _, cycle); //Fix an issue where an anim could start on the wrong frame. - if (g_NpcCurrentAnimationSequence[bossIndex] <= -1) - { - g_NpcCurrentAnimationSequence[bossIndex] = 0; - } - bool animationLoop = (state == STATE_IDLE || state == STATE_ALERT || - (state == STATE_CHASE && !NPCIsRaging(bossIndex) - && !g_NpcUseStartFleeAnimation[bossIndex] && !g_NpcUsesHealAnimation[bossIndex] && !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex]) - || state == STATE_WANDER); - if (state == STATE_ATTACK && NPCChaserGetAttackWhileRunningState(bossIndex, NPCGetCurrentAttackIndex(bossIndex), difficulty)) - { - animationLoop = !!GetEntProp(ent, Prop_Data, "m_bSequenceLoops"); - } - if (state == STATE_CHASE && g_NpcUsesChaseInitialAnimation[bossIndex]) - { - animationLoop = !!GetEntProp(ent, Prop_Data, "m_bSequenceLoops"); - } - SetEntProp(ent, Prop_Data, "m_bSequenceLoops", animationLoop); - } - } - if (state == STATE_ATTACK && NPCChaserGetAttackGestureState(bossIndex, NPCGetCurrentAttackIndex(bossIndex))) - { - float gestureCycle; - animationFound = animData.GetGesture(NPCGetCurrentAttackIndex(bossIndex), g_SlenderAnimationsList[SF2BossAnimation_Attack], difficulty, animation, sizeof(animation), playbackRate, cycle); - if (animationFound && animation[0] != '\0') - { - CBaseCombatCharacter overlay = CBaseCombatCharacter(ent); - int gesture = overlay.LookupSequence(animation); - if (gesture != -1) - { - float gestureDuration = overlay.SequenceDuration(gesture); - int layer = overlay.AddLayeredSequence(gesture, 1); - overlay.SetLayerDuration(layer, gestureDuration); - overlay.SetLayerPlaybackRate(layer, playbackRate); - overlay.SetLayerCycle(layer, gestureCycle); - overlay.SetLayerAutokill(layer, true); - } - } - } - if (!g_IsSlenderAttacking[bossIndex] && clearLayers) - { - CBaseNPC_RemoveAllLayers(ent); - } -} - -void SlenderAlertAllValidBosses(int bossIndex, int target = -1, int bestTarget = -1) -{ - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - float myPos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - int difficulty = GetLocalGlobalDifficulty(bossIndex); - if (NPCHasAttribute(bossIndex, SF2Attribute_AlertCopies)) - { - for (int bossCheck = 0; bossCheck < MAX_BOSSES; bossCheck++) - { - if (NPCGetUniqueID(bossCheck) != -1 && !g_SlenderSpawning[bossCheck] && - g_SlenderState[bossCheck] != STATE_ATTACK && g_SlenderState[bossCheck] != STATE_STUN && g_SlenderState[bossCheck] != STATE_CHASE && - (g_SlenderCopyMaster[bossCheck] == bossIndex || g_SlenderCopyMaster[bossIndex] == bossCheck || - (g_SlenderCopyMaster[bossIndex] == g_SlenderCopyMaster[bossCheck] && g_SlenderCopyMaster[bossIndex] != -1 && g_SlenderCopyMaster[bossCheck] != -1))) - { - int bossEnt = NPCGetEntIndex(bossCheck); - if (!bossEnt || bossEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - float copyPos[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", copyPos); - - float dist1 = GetVectorSquareMagnitude(copyPos, myPos); - - if ((dist1 <= SquareFloat(NPCGetSearchRadius(bossCheck, difficulty)) || dist1 <= SquareFloat(NPCGetHearingRadius(bossCheck, difficulty)))) - { - if (IsValidClient(target)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(target); - GetClientAbsOrigin(target, g_SlenderGoalPos[bossCheck]); - } - else if (IsValidClient(bestTarget)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(bestTarget); - GetClientAbsOrigin(bestTarget, g_SlenderGoalPos[bossCheck]); - } - g_SlenderState[bossCheck] = STATE_CHASE; - NPCChaserUpdateBossAnimation(bossCheck, bossEnt, STATE_CHASE); - g_NpcCopyAlerted[bossCheck] = true; - g_SlenderTimeUntilNoPersistence[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - g_SlenderTimeUntilAlert[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - SlenderPerformVoice(bossCheck, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(bossCheck) && !g_NpcUsesChaseInitialAnimation[bossCheck] && !SF_IsSlaughterRunMap()) - { - int copySlender = NPCGetEntIndex(bossCheck); - if (copySlender && copySlender != INVALID_ENT_REFERENCE && g_SlenderChaseInitialTimer[bossCheck] == null && (g_NpcChaseOnLookTarget[bossCheck] == null || g_NpcChaseOnLookTarget[bossCheck].Length <= 0)) - { - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(copySlender); - g_NpcUsesChaseInitialAnimation[bossCheck] = true; - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - NPCChaserUpdateBossAnimation(bossCheck, copySlender, g_SlenderState[bossCheck]); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossCheck, profile, sizeof(profile)); - g_SlenderChaseInitialTimer[bossCheck] = CreateTimer(g_SlenderAnimationDuration[bossCheck], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(copySlender), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } - if (NPCHasAttribute(bossIndex, SF2Attribute_AlertCompanions)) - { - for (int bossCheck = 0; bossCheck < MAX_BOSSES; bossCheck++) - { - if (NPCGetUniqueID(bossCheck) != -1 && !g_SlenderSpawning[bossCheck] && - g_SlenderState[bossCheck] != STATE_ATTACK && g_SlenderState[bossCheck] != STATE_STUN && g_SlenderState[bossCheck] != STATE_CHASE && - (g_SlenderCompanionMaster[bossCheck] == bossIndex || g_SlenderCompanionMaster[bossIndex] == bossCheck || - ((g_SlenderCompanionMaster[bossIndex] == g_SlenderCompanionMaster[bossCheck] && g_SlenderCompanionMaster[bossIndex] != -1 && g_SlenderCompanionMaster[bossCheck] != -1)))) - { - int bossEnt = NPCGetEntIndex(bossCheck); - if (!bossEnt || bossEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - float copyPos[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", copyPos); - - float dist1 = GetVectorSquareMagnitude(copyPos, myPos); - - if (dist1 <= SquareFloat(NPCGetSearchRadius(bossCheck, difficulty)) || dist1 <= SquareFloat(NPCGetHearingRadius(bossCheck, difficulty))) - { - if (IsValidClient(target)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(target); - GetClientAbsOrigin(target, g_SlenderGoalPos[bossCheck]); - } - else if (IsValidClient(bestTarget)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(bestTarget); - GetClientAbsOrigin(bestTarget, g_SlenderGoalPos[bossCheck]); - } - g_SlenderState[bossCheck] = STATE_CHASE; - NPCChaserUpdateBossAnimation(bossCheck, bossEnt, STATE_CHASE); - g_NpcCopyAlerted[bossCheck] = true; - g_SlenderTimeUntilNoPersistence[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - g_SlenderTimeUntilAlert[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - SlenderPerformVoice(bossCheck, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(bossCheck) && !g_NpcUsesChaseInitialAnimation[bossCheck] && !SF_IsSlaughterRunMap()) - { - int copySlender = NPCGetEntIndex(bossCheck); - if (copySlender && copySlender != INVALID_ENT_REFERENCE && g_SlenderChaseInitialTimer[bossCheck] == null && (g_NpcChaseOnLookTarget[bossCheck] == null || g_NpcChaseOnLookTarget[bossCheck].Length <= 0)) - { - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(copySlender); - g_NpcUsesChaseInitialAnimation[bossCheck] = true; - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - NPCChaserUpdateBossAnimation(bossCheck, copySlender, g_SlenderState[bossCheck]); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossCheck, profile, sizeof(profile)); - g_SlenderChaseInitialTimer[bossCheck] = CreateTimer(g_SlenderAnimationDuration[bossCheck], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(copySlender), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } -} - -Action Timer_SlenderChaseInitialTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderChaseInitialTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesChaseInitialAnimation[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesChaseInitialAnimation[bossIndex] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - g_SlenderChaseInitialTimer[bossIndex] = null; - return Plugin_Stop; -} - -Action Timer_SlenderRageOneTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderRage1Timer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesRageAnimation[bossIndex][0]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesRageAnimation[bossIndex][0] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - return Plugin_Stop; -} - -Action Timer_SlenderRageTwoTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderRage2Timer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesRageAnimation[bossIndex][1]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesRageAnimation[bossIndex][1] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - return Plugin_Stop; -} - -Action Timer_SlenderRageThreeTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderRage3Timer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesRageAnimation[bossIndex][2]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesRageAnimation[bossIndex][2] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - return Plugin_Stop; -} - -Action Timer_SlenderSpawnTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderSpawnTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_SlenderSpawning[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - SDKHook(slender, SDKHook_Think, SlenderChaseBossProcessMovement); - g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, entref, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderSpawning[bossIndex] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - NPCChaserUpdateBossAnimation(bossIndex, slender, STATE_IDLE); - Call_StartForward(g_OnBossFinishSpawningFwd); - Call_PushCell(bossIndex); - Call_Finish(); - return Plugin_Stop; -} - -Action Timer_SlenderHealAnimationTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderHealTimer[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcSetHealDestination[bossIndex] = false; - g_NpcIsRunningToHeal[bossIndex] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - g_NpcUseStartFleeAnimation[bossIndex] = false; - g_NpcUsesHealAnimation[bossIndex] = false; - g_NpcIsHealing[bossIndex] = false; - g_NpcFleeHealTimer[bossIndex] = GetGameTime(); - g_NpcHealCount[bossIndex] = 0; - g_NpcSelfHealStage[bossIndex]++; - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - g_SlenderHealDelayTimer[bossIndex] = null; - g_SlenderStartFleeTimer[bossIndex] = null; - g_SlenderHealTimer[bossIndex] = null; - g_BossPathFollower[bossIndex].Invalidate(); - return Plugin_Stop; -} - -Action Timer_SlenderHealDelayTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderHealDelayTimer[bossIndex]) - { - return Plugin_Stop; - } - - SlenderPerformVoice(bossIndex, _, SF2BossSound_SelfHeal); - - g_NpcHealCount[bossIndex] = 0; - - g_SlenderHealEventTimer[bossIndex] = CreateTimer(0.05, Timer_SlenderHealEventTimer, EntIndexToEntRef(slender), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Stop; -} - -Action Timer_SlenderHealEventTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderHealEventTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcIsHealing[bossIndex]) - { - return Plugin_Stop; - } - - if (g_SlenderState[bossIndex] == STATE_STUN) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - float checkPercentage; - switch (g_NpcSelfHealStage[bossIndex]) - { - case 0: - { - checkPercentage = NPCChaserGetSelfHealPercentageOne(bossIndex) * NPCChaserGetStunInitialHealth(bossIndex); - } - case 1: - { - checkPercentage = NPCChaserGetSelfHealPercentageTwo(bossIndex) * NPCChaserGetStunInitialHealth(bossIndex); - } - case 2, 3: - { - checkPercentage = NPCChaserGetSelfHealPercentageThree(bossIndex) * NPCChaserGetStunInitialHealth(bossIndex); - } - } - float regenHealth = checkPercentage / 10.0; - - if (NPCChaserGetStunHealth(bossIndex) + regenHealth < NPCChaserGetStunInitialHealth(bossIndex)) - { - NPCChaserAddStunHealth(bossIndex, regenHealth); - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - g_NpcHealCount[bossIndex]++; - } - else - { - float addRemainHealth = NPCChaserGetStunInitialHealth(bossIndex) - NPCChaserGetStunHealth(bossIndex); - NPCChaserAddStunHealth(bossIndex, addRemainHealth); - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - g_NpcIsHealing[bossIndex] = false; - g_NpcHealCount[bossIndex] = 0; - return Plugin_Stop; - } - - if (g_NpcHealCount[bossIndex] >= 10) - { - g_NpcIsHealing[bossIndex] = false; - g_NpcHealCount[bossIndex] = 0; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_SlenderFleeAnimationTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderStartFleeTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUseStartFleeAnimation[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - g_SlenderTarget[bossIndex] = INVALID_ENT_REFERENCE; - - g_NpcUseStartFleeAnimation[bossIndex] = false; - g_NpcIsRunningToHeal[bossIndex] = true; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - - return Plugin_Stop; -} - -void SlenderDoDamageEffects(int bossIndex, int attackIndex, SF2_BasePlayer client) -{ - if (!client.IsValid) - { - return; - } - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - float flMyEyePos[3]; - NPCGetEyePosition(bossIndex, flMyEyePos); - - if (NPCChaserUseRandomAdvancedDamageEffects(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserRandomEffectIndexes(bossIndex); - GetChaserProfileRandomAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserRandomEffectDuration(bossIndex, difficulty) > 0.0) - { - int randomEffect = GetRandomInt(0, 6); - switch (randomEffect) - { - case 0: - { - client.ChangeCondition(TFCond_Jarated, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 1: - { - client.ChangeCondition(TFCond_Milked, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 2: - { - client.ChangeCondition(TFCond_Gas, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 3: - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 4: - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - } - case 5: - { - client.Ignite(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 6: - { - switch (NPCChaserRandomEffectStunType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserRandomEffectDuration(bossIndex, difficulty) > 0.0) - { - int randomEffect = GetRandomInt(0, 6); - switch (randomEffect) - { - case 0: - { - client.ChangeCondition(TFCond_Jarated, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 1: - { - client.ChangeCondition(TFCond_Milked, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 2: - { - client.ChangeCondition(TFCond_Gas, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 3: - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 4: - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - } - case 5: - { - client.Ignite(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 6: - { - switch (NPCChaserRandomEffectStunType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - } - } - } - } - } - } - if (NPCChaserJaratePlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetJarateAttackIndexes(bossIndex); - GetChaserProfileJarateAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetJarateDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileJarateParticle(profile, jaratePlayerParticle, sizeof(jaratePlayerParticle)); - if (jaratePlayerParticle[0] == '\0') - { - jaratePlayerParticle = JARATE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetJarateBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, jaratePlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, jaratePlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, jaratePlayerParticle, 35.0); - } - if (g_SlenderJarateHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderJarateHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Jarated, _, NPCChaserGetJarateDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetJarateDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileJarateParticle(profile, jaratePlayerParticle, sizeof(jaratePlayerParticle)); - if (jaratePlayerParticle[0] == '\0') - { - jaratePlayerParticle = JARATE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetJarateBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, jaratePlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, jaratePlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, jaratePlayerParticle, 35.0); - } - if (g_SlenderJarateHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderJarateHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Jarated, _, NPCChaserGetJarateDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserMilkPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetMilkAttackIndexes(bossIndex); - GetChaserProfileMilkAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetMilkDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileMilkParticle(profile, milkPlayerParticle, sizeof(milkPlayerParticle)); - if (milkPlayerParticle[0] == '\0') - { - milkPlayerParticle = MILK_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetMilkBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, milkPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, milkPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, milkPlayerParticle, 55.0); - } - if (g_SlenderMilkHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderMilkHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Milked, _, NPCChaserGetMilkDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetMilkDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileMilkParticle(profile, milkPlayerParticle, sizeof(milkPlayerParticle)); - if (milkPlayerParticle[0] == '\0') - { - milkPlayerParticle = MILK_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetMilkBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, milkPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, milkPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, milkPlayerParticle, 55.0); - } - if (g_SlenderMilkHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderMilkHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Milked, _, NPCChaserGetMilkDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserGasPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetGasAttackIndexes(bossIndex); - GetChaserProfileGasAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetGasDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileGasParticle(profile, gasPlayerParticle, sizeof(gasPlayerParticle)); - if (gasPlayerParticle[0] == '\0') - { - gasPlayerParticle = GAS_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetGasBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, gasPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, gasPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, gasPlayerParticle, 55.0); - } - if (g_SlenderGasHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderGasHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Gas, _, NPCChaserGetGasDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetGasDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileGasParticle(profile, gasPlayerParticle, sizeof(gasPlayerParticle)); - if (gasPlayerParticle[0] == '\0') - { - gasPlayerParticle = GAS_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetGasBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, gasPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, gasPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, gasPlayerParticle, 55.0); - } - if (g_SlenderGasHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderGasHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Gas, _, NPCChaserGetGasDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserMarkPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetMarkAttackIndexes(bossIndex); - GetChaserProfileMarkAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetMarkDuration(bossIndex, difficulty) > 0.0) - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserGetMarkDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetMarkDuration(bossIndex, difficulty)) - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserGetMarkDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserSilentMarkPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetSilentMarkAttackIndexes(bossIndex); - GetChaserProfileSilentMarkAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetSilentMarkDuration(bossIndex, difficulty) > 0.0) - { - client.ChangeCondition(TFCond_MarkedForDeathSilent, _, NPCChaserGetSilentMarkDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetSilentMarkDuration(bossIndex, difficulty)) - { - client.ChangeCondition(TFCond_MarkedForDeathSilent, _, NPCChaserGetSilentMarkDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserIgnitePlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetIgniteAttackIndexes(bossIndex); - GetChaserProfileIgniteAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetIgniteDuration(bossIndex, difficulty)) - { - g_PlayerIgniteDurationEffect[client.index] = NPCChaserGetIgniteDuration(bossIndex, difficulty); - if (NPCChaserGetIgniteDelay(bossIndex, difficulty) > 0.0 && g_PlayerIgniteTimer[client.index] == null) - { - g_PlayerIgniteTimer[client.index] = CreateTimer(NPCChaserGetIgniteDelay(bossIndex, difficulty), Timer_SlenderChaseBossAttackIgniteHit, EntIndexToEntRef(client.index), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - client.Ignite(true, _, g_PlayerIgniteDurationEffect[client.index]); - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetIgniteDuration(bossIndex, difficulty)) - { - g_PlayerIgniteDurationEffect[client.index] = NPCChaserGetIgniteDuration(bossIndex, difficulty); - if (NPCChaserGetIgniteDelay(bossIndex, difficulty) > 0.0 && g_PlayerIgniteTimer[client.index] == null) - { - g_PlayerIgniteTimer[client.index] = CreateTimer(NPCChaserGetIgniteDelay(bossIndex, difficulty), Timer_SlenderChaseBossAttackIgniteHit, EntIndexToEntRef(client.index), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - client.Ignite(true, _, g_PlayerIgniteDurationEffect[client.index]); - } - } - } - } - } - if (NPCChaserStunPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetStunAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileStunAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetStunAttackDuration(bossIndex, difficulty)) - { - GetChaserProfileStunParticle(profile, stunPlayerParticle, sizeof(stunPlayerParticle)); - if (stunPlayerParticle[0] == '\0') - { - stunPlayerParticle = STUN_PARTICLE; - } - if (NPCChaserGetStunAttackType(bossIndex) != 4) - { - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetStunBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, stunPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, stunPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, stunPlayerParticle, 55.0); - } - } - if (g_SlenderStunHitSound[bossIndex][0] != '\0' && NPCChaserGetStunAttackType(bossIndex) != 4) //No sound, what? - { - EmitSoundToAll(g_SlenderStunHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetStunAttackDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileStunParticle(profile, stunPlayerParticle, sizeof(stunPlayerParticle)); - if (stunPlayerParticle[0] == '\0') - { - stunPlayerParticle = STUN_PARTICLE; - } - if (NPCChaserGetStunAttackType(bossIndex) != 4) - { - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetStunBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, stunPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, stunPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, stunPlayerParticle, 55.0); - } - } - if (g_SlenderStunHitSound[bossIndex][0] != '\0' && NPCChaserGetStunAttackType(bossIndex) != 4) //No sound, what? - { - EmitSoundToAll(g_SlenderStunHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - } - } - } - } - if (NPCChaserBleedPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetBleedAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileBleedAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetBleedDuration(bossIndex, difficulty)) - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserGetBleedDuration(bossIndex, difficulty)); - break; - } - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetBleedDuration(bossIndex, difficulty) > 0.0) - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserGetBleedDuration(bossIndex, difficulty)); - } - } - } - } - } - if (NPCChaserElectricPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetElectricAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileEletricAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetElectricDuration(bossIndex, difficulty)) - { - GetChaserProfileElectricRedParticle(profile, electricPlayerParticleRed, sizeof(electricPlayerParticleRed)); - GetChaserProfileElectricBlueParticle(profile, electricPlayerParticleBlue, sizeof(electricPlayerParticleBlue)); - if (electricPlayerParticleRed[0] == '\0') - { - electricPlayerParticleRed = ELECTRIC_RED_PARTICLE; - } - if (electricPlayerParticleBlue[0] == '\0') - { - electricPlayerParticleBlue = ELECTRIC_BLUE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (client.Team == TFTeam_Red) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleRed, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleRed, 35.0, client); - } - } - else if (client.Team == TFTeam_Blue) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - } - } - else - { - if (client.Team == TFTeam_Red) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleRed, 55.0); - } - else if (client.Team == TFTeam_Blue) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleBlue, 55.0); - } - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3, 4: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetElectricDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileElectricRedParticle(profile, electricPlayerParticleRed, sizeof(electricPlayerParticleRed)); - GetChaserProfileElectricBlueParticle(profile, electricPlayerParticleBlue, sizeof(electricPlayerParticleBlue)); - if (electricPlayerParticleRed[0] == '\0') - { - electricPlayerParticleRed = ELECTRIC_RED_PARTICLE; - } - if (electricPlayerParticleBlue[0] == '\0') - { - electricPlayerParticleBlue = ELECTRIC_BLUE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (client.Team == TFTeam_Red) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleRed, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleRed, 35.0, client); - } - } - else if (client.Team == TFTeam_Blue) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - } - } - else - { - if (client.Team == TFTeam_Red) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleRed, 55.0); - } - else if (client.Team == TFTeam_Blue) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleBlue, 55.0); - } - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3, 4: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - } - } - } - } - } - if (NPCChaserSmitePlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetSmiteAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileSmiteAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1) - { - PerformSmiteBoss(bossIndex, client, EntIndexToEntRef(slender)); - client.TakeDamage(_, slender, slender, NPCChaserGetSmiteDamage(bossIndex), NPCChaserGetSmiteDamageType(bossIndex), _, view_as( { 0.0, 0.0, 0.0 } ), flMyEyePos); - if (NPCChaserSmitePlayerMessage(bossIndex)) - { - char player[32]; - FormatEx(player, sizeof(player), "%N", client.index); - - char name[SF2_MAX_NAME_LENGTH]; - NPCGetBossName(bossIndex, name, sizeof(name)); - if (name[0] == '\0') - { - strcopy(name, sizeof(name), profile); - } - if (client.Team == TFTeam_Red) - { - CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Smote target", name, player); - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex) - { - PerformSmiteBoss(bossIndex, client, EntIndexToEntRef(slender)); - client.TakeDamage(_, slender, slender, NPCChaserGetSmiteDamage(bossIndex), NPCChaserGetSmiteDamageType(bossIndex), _, view_as( { 0.0, 0.0, 0.0 } ), flMyEyePos); - if (NPCChaserSmitePlayerMessage(bossIndex)) - { - char player[32]; - FormatEx(player, sizeof(player), "%N", client.index); - - char name[SF2_MAX_NAME_LENGTH]; - NPCGetBossName(bossIndex, name, sizeof(name)); - if (name[0] == '\0') - { - strcopy(name, sizeof(name), profile); - } - if (client.Team == TFTeam_Red) - { - CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Smote target", name, player); - } - } - } - } - } - } -} - -void NPCClearAttackStats(int bossIndex, bool killTimers = false) -{ - if (!killTimers) - { - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - } - else + if (g_Buildings.FindValue(EntIndexToEntRef(target.index)) != -1) { - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) + int owner = target.GetPropEnt(Prop_Send, "m_hBuilder"); + if (IsValidClient(owner) && !IsClientInPvE(owner)) { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); + return false; } } - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; -} -static bool NPCPropPhysicsAttack(int bossIndex, int prop) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH], model[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - ArrayList props = GetChaserProfileAttackPropModels(profile); - if (props == null) - { - return false; - } - if (!IsValidEntity(prop)) - { - return false; - } - GetEntPropString(prop, Prop_Data, "m_ModelName", model, sizeof(model)); - int arrayIndex = props.FindString(model); - if (arrayIndex == -1) - { - return false; - } return true; } -void NPC_DropKey(int bossIndex) -{ - char buffer[PLATFORM_MAX_PATH], profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - GetChaserProfileKeyTrigger(profile, buffer, sizeof(buffer)); - if (buffer[0] != '\0') +void NPC_DropKey(int bossIndex, const char[] model, const char[] trigger) +{ + char buffer[PLATFORM_MAX_PATH]; + float myPos[3], vel[3]; + int boss = NPCGetEntIndex(bossIndex); + GetEntPropVector(boss, Prop_Data, "m_vecAbsOrigin", myPos); + FormatEx(buffer, sizeof(buffer), "sf2_key_%s", trigger); + + int touchBox = CreateEntityByName("tf_halloween_pickup"); + DispatchKeyValue(touchBox, "targetname", buffer); + DispatchKeyValue(touchBox, "powerup_model", model); + DispatchKeyValue(touchBox, "modelscale", "2.0"); + DispatchKeyValue(touchBox, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); + DispatchKeyValue(touchBox, "pickup_particle", "utaunt_firework_teamcolor_red"); + DispatchKeyValue(touchBox, "TeamNum", "0"); + TeleportEntity(touchBox, myPos, NULL_VECTOR, NULL_VECTOR); + SetEntityModel(touchBox, model); + SetEntProp(touchBox, Prop_Data, "m_iEFlags", 12845056); + DispatchSpawn(touchBox); + ActivateEntity(touchBox); + SetEntityModel(touchBox, model); + + int key = CreateEntityByName("tf_halloween_pickup"); + DispatchKeyValue(key, "targetname", buffer); + DispatchKeyValue(key, "powerup_model", PAGE_MODEL); + DispatchKeyValue(key, "modelscale", "2.0"); + DispatchKeyValue(key, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); + DispatchKeyValue(key, "pickup_particle", "utaunt_firework_teamcolor_red"); + DispatchKeyValue(key, "TeamNum", "0"); + TeleportEntity(key, myPos, NULL_VECTOR, NULL_VECTOR); + SetEntityModel(key, PAGE_MODEL); + SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); + DispatchSpawn(key); + ActivateEntity(key); + + SetEntityRenderMode(key, RENDER_TRANSCOLOR); + SetEntityRenderColor(key, 0, 0, 0, 1); + + int glow = CreateEntityByName("tf_taunt_prop"); + DispatchKeyValue(glow, "targetname", buffer); + DispatchKeyValue(glow, "powerup_model", model); + TeleportEntity(glow, myPos, NULL_VECTOR, NULL_VECTOR); + DispatchSpawn(glow); + ActivateEntity(glow); + SetEntityModel(glow, model); + + SetEntProp(glow, Prop_Send, "m_bGlowEnabled", 1); + SetEntPropFloat(glow, Prop_Send, "m_flModelScale", 2.0); + SetEntityRenderMode(glow, RENDER_TRANSCOLOR); + SetEntityRenderColor(glow, 0, 0, 0, 1); + + SetVariantString("!activator"); + AcceptEntityInput(touchBox, "SetParent", key); + + SetVariantString("!activator"); + AcceptEntityInput(glow, "SetParent", key); + + SetEntityModel(key, PAGE_MODEL); + SetEntityMoveType(key, MOVETYPE_FLYGRAVITY); + + HookSingleEntityOutput(touchBox, "OnRedPickup", KeyTrigger); + + vel[0] = GetRandomFloat(-300.0, 300.0); + vel[1] = GetRandomFloat(-300.0, 300.0); + vel[2] = GetRandomFloat(700.0, 900.0); + + SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); + + TeleportEntity(key, myPos, NULL_VECTOR, vel); + SetEntPropFloat(key, Prop_Send, "m_flModelScale", 2.0); + SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); + SetEntProp(key, Prop_Data, "m_MoveCollide", 1); + + SDKHook(key, SDKHook_SetTransmit, Hook_KeySetTransmit); + SDKHook(glow, SDKHook_SetTransmit, Hook_KeySetTransmit); + SDKHook(touchBox, SDKHook_SetTransmit, Hook_KeySetTransmit); + + //The key can be stuck somewhere to prevent that, make an auto collect. + float timeLeft = float(g_RoundTime); + if (timeLeft > 60.0) + { + timeLeft = 30.0; + } + else { - float myPos[3], vel[3]; - int boss = NPCGetEntIndex(bossIndex); - GetEntPropVector(boss, Prop_Data, "m_vecAbsOrigin", myPos); - Format(buffer, PLATFORM_MAX_PATH, "sf2_key_%s", buffer); - - int touchBox = CreateEntityByName("tf_halloween_pickup"); - DispatchKeyValue(touchBox, "targetname", buffer); - // New key model - GetChaserProfileKeyModel(profile, keyModel, sizeof(keyModel)); - DispatchKeyValue(touchBox, "powerup_model", keyModel); - DispatchKeyValue(touchBox, "modelscale", "2.0"); - DispatchKeyValue(touchBox, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); - DispatchKeyValue(touchBox, "pickup_particle", "utaunt_firework_teamcolor_red"); - DispatchKeyValue(touchBox, "TeamNum", "0"); - TeleportEntity(touchBox, myPos, NULL_VECTOR, NULL_VECTOR); - if (keyModel[0] == '\0') - { - SetEntityModel(touchBox, SF_KEYMODEL); - } - else - { - SetEntityModel(touchBox, keyModel); - } - SetEntProp(touchBox, Prop_Data, "m_iEFlags", 12845056); - DispatchSpawn(touchBox); - ActivateEntity(touchBox); - if (keyModel[0] == '\0') - { - SetEntityModel(touchBox, SF_KEYMODEL); - } - else - { - SetEntityModel(touchBox, keyModel); - } - - int key = CreateEntityByName("tf_halloween_pickup"); - DispatchKeyValue(key, "targetname", buffer); - DispatchKeyValue(key, "powerup_model", PAGE_MODEL); - DispatchKeyValue(key, "modelscale", "2.0"); - DispatchKeyValue(key, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); - DispatchKeyValue(key, "pickup_particle", "utaunt_firework_teamcolor_red"); - DispatchKeyValue(key, "TeamNum", "0"); - TeleportEntity(key, myPos, NULL_VECTOR, NULL_VECTOR); - SetEntityModel(key, PAGE_MODEL); - SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); - DispatchSpawn(key); - ActivateEntity(key); - - SetEntityRenderMode(key, RENDER_TRANSCOLOR); - SetEntityRenderColor(key, 0, 0, 0, 1); - - int glow = CreateEntityByName("tf_taunt_prop"); - DispatchKeyValue(glow, "targetname", buffer); - if (keyModel[0] == '\0') - { - DispatchKeyValue(glow, "powerup_model", SF_KEYMODEL); - } - else - { - DispatchKeyValue(glow, "powerup_model", keyModel); - } - TeleportEntity(glow, myPos, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(glow); - ActivateEntity(glow); - if (keyModel[0] == '\0') - { - SetEntityModel(glow, SF_KEYMODEL); - } - else - { - SetEntityModel(glow, keyModel); - } - - SetEntProp(glow, Prop_Send, "m_bGlowEnabled", 1); - SetEntPropFloat(glow, Prop_Send, "m_flModelScale", 2.0); - SetEntityRenderMode(glow, RENDER_TRANSCOLOR); - SetEntityRenderColor(glow, 0, 0, 0, 1); - - SetVariantString("!activator"); - AcceptEntityInput(touchBox, "SetParent", key); - - SetVariantString("!activator"); - AcceptEntityInput(glow, "SetParent", key); - - SetEntityModel(key, PAGE_MODEL); - SetEntityMoveType(key, MOVETYPE_FLYGRAVITY); - - HookSingleEntityOutput(touchBox, "OnRedPickup", KeyTrigger); - - vel[0] = GetRandomFloat(-300.0, 300.0); - vel[1] = GetRandomFloat(-300.0, 300.0); - vel[2] = GetRandomFloat(700.0, 900.0); - - SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); - - TeleportEntity(key, myPos, NULL_VECTOR, vel); - SetEntPropFloat(key, Prop_Send, "m_flModelScale", 2.0); - SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); - SetEntProp(key, Prop_Data, "m_MoveCollide", 1); - - SDKHook(key, SDKHook_SetTransmit, Hook_KeySetTransmit); - SDKHook(glow, SDKHook_SetTransmit, Hook_KeySetTransmit); - SDKHook(touchBox, SDKHook_SetTransmit, Hook_KeySetTransmit); - - //The key can be stuck somewhere to prevent that, make an auto collect. - float timeLeft = float(g_RoundTime); - if (timeLeft > 60.0) - { - timeLeft = 30.0; - } - else - { - timeLeft = timeLeft - 20.0; - } - CreateTimer(timeLeft, CollectKey, EntIndexToEntRef(touchBox), TIMER_FLAG_NO_MAPCHANGE); + timeLeft = timeLeft - 20.0; } + CreateTimer(timeLeft, CollectKey, EntIndexToEntRef(touchBox), TIMER_FLAG_NO_MAPCHANGE); } -void KeyTrigger(const char[] output, int caller, int activator, float delay) +static void KeyTrigger(const char[] output, int caller, int activator, float delay) { TriggerKey(caller); } -Action Hook_KeySetTransmit(int entity, int other) +static Action Hook_KeySetTransmit(int entity, int other) { if (!IsValidClient(other)) { @@ -4393,7 +565,7 @@ Action Hook_KeySetTransmit(int entity, int other) return Plugin_Handled; } -Action CollectKey(Handle timer, any entref) +static Action CollectKey(Handle timer, any entref) { int ent = EntRefToEntIndex(entref); if (ent == INVALID_ENT_REFERENCE) @@ -4411,7 +583,7 @@ Action CollectKey(Handle timer, any entref) return Plugin_Stop; } -void TriggerKey(int caller) +static void TriggerKey(int caller) { char targetName[PLATFORM_MAX_PATH]; GetEntPropString(caller, Prop_Data, "m_iName", targetName, sizeof(targetName)); @@ -4468,37 +640,258 @@ void TriggerKey(int caller) EmitSoundToAll("ui/itemcrate_smash_ultrarare_short.wav", caller, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); } -bool NPC_CanAttackProps(int bossIndex, float flAttackRange, float flAttackFOV) +void NPCChaser_InitializeAPI() +{ + CreateNative("SF2_GetBossCurrentAttackIndex", Native_GetBossCurrentAttackIndex); + CreateNative("SF2_GetBossAttackIndexType", Native_GetBossAttackIndexType); + CreateNative("SF2_GetBossAttackIndexDamage", Native_GetBossAttackIndexDamage); + CreateNative("SF2_UpdateBossAnimation", Native_UpdateBossAnimation); + CreateNative("SF2_GetBossAttackIndexDamageType", Native_GetBossAttackIndexDamageType); + + CreateNative("SF2_PerformBossVoice", Native_PerformVoice); + CreateNative("SF2_CreateBossSoundHint", Native_CreateBossSoundHint); + + CreateNative("SF2_GetChaserProfileFromBossIndex", Native_GetProfileData); + CreateNative("SF2_GetChaserProfileFromName", Native_GetProfileDataEx); + CreateNative("SF2_SetEntityForceChaseState", Native_SetForceChaseState); + + g_OnChaserBossStartAttackFwd = new GlobalForward("SF2_OnChaserBossStartAttack", ET_Ignore, Param_Cell, Param_String); + g_OnChaserBossEndAttackFwd = new GlobalForward("SF2_OnChaserBossEndAttack", ET_Ignore, Param_Cell, Param_String); + + SF2_ChaserEntity.SetupAPI(); +} + +static any Native_GetBossCurrentAttackIndex(Handle plugin, int numParams) { - int prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_physics")) > MaxClients) + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) { - if (NPCAttackValidateTarget(bossIndex, prop, flAttackRange, flAttackFOV, true)) - { - if (NPCPropPhysicsAttack(bossIndex, prop)) - { - return true; - } - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return 0; + } + + return chaser.AttackIndex; +} + +static any Native_PerformVoice(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return 0; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(3), attackData); + + return chaser.PerformVoice(GetNativeCell(2), attackData.Name); +} + +static any Native_CreateBossSoundHint(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return 0; + } + + float position[3]; + GetNativeArray(3, position, 3); + + chaser.UpdateAlertTriggerCountEx(position); + + return 0; +} + +static any Native_GetBossAttackIndexType(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return 0; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(2), attackData); + + return attackData.Type; +} + +static any Native_GetBossAttackIndexDamage(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return 0; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(2), attackData); + + return attackData.Damage[GetNativeCell(3)]; +} + +static any Native_UpdateBossAnimation(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return 0; } - prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_*")) > MaxClients) + + char value[64]; + bool spawn = GetNativeCell(4); + switch (GetNativeCell(3)) { - if (GetEntProp(prop, Prop_Data, "m_iHealth") > 0) + case STATE_IDLE, STATE_ALERT, STATE_CHASE: { - if (NPCAttackValidateTarget(bossIndex, prop, flAttackRange, flAttackFOV, true)) + if (!chaser.IsInChaseInitial && !spawn) + { + chaser.UpdateMovementAnimation(); + return 0; + } + else { - return true; + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial]); } } - } - prop = -1; - while ((prop = FindEntityByClassname(prop, "obj_*")) > MaxClients) - { - if (NPCAttackValidateTarget(bossIndex, prop, flAttackRange, flAttackFOV, true)) + + case STATE_ATTACK: + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Attack]); + } + + case STATE_STUN: + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Stun]); + } + + case STATE_DEATHCAM: + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_DeathCam]); + } + + case STATE_DEATH: { - return true; + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Death]); } } - return false; + + if (spawn) + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Spawn]); + } + + chaser.ResetProfileAnimation(value, _, chaser.GetAttackName()); + + return 0; +} + +static any Native_GetBossAttackIndexDamageType(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return 0; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(2), attackData); + + return attackData.DamageType[1]; +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_GetProfileDataEx(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetNativeString(1, profile, sizeof(profile)); + SF2ChaserBossProfileData data; + if (!g_ChaserBossProfileData.GetArray(profile, data, sizeof(data))) + { + return false; + } + + SetNativeArray(2, data, sizeof(data)); + return true; +} + +static any Native_SetForceChaseState(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + CBaseEntity target = CBaseEntity(GetNativeCell(2)); + if (!target.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", controller.Index); + } + + SetClientForceChaseState(controller, target, GetNativeCell(3)); + return 0; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp deleted file mode 100644 index 38351b67..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp +++ /dev/null @@ -1,1815 +0,0 @@ -#if defined _sf2_npc_chaser_attacks_included - #endinput -#endif - -#define _sf2_npc_chaser_attacks_included - -#pragma semicolon 1 - -void PerformSmiteBoss(int client, SF2_BasePlayer target, any entref) -{ - if (!g_Enabled) - { - return; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - // define where the lightning strike ends - float clientPos[3]; - target.GetAbsOrigin(clientPos); - clientPos[2] -= 26; // increase y-axis by 26 to strike at player's chest instead of the ground - - // get random numbers for the x and y starting positions - int randomX = GetRandomInt(-500, 500); - int randomY = GetRandomInt(-500, 500); - - // define where the lightning strike starts - float startPos[3]; - startPos[0] = clientPos[0] + randomX; - startPos[1] = clientPos[1] + randomY; - startPos[2] = clientPos[2] + 800; - - // define the color of the strike - int color[4]; - color[0] = NPCChaserGetSmiteColorR(bossIndex); - color[1] = NPCChaserGetSmiteColorG(bossIndex); - color[2] = NPCChaserGetSmiteColorB(bossIndex); - color[3] = NPCChaserGetSmiteColorTrans(bossIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - - // define the direction of the sparks - float dir[3]; - - TE_SetupBeamPoints(startPos, clientPos, g_LightningSprite, 0, 0, 0, 0.2, 20.0, 10.0, 0, 1.0, color, 3); - TE_SendToAll(); - - TE_SetupSparks(clientPos, dir, 5000, 1000); - TE_SendToAll(); - - TE_SetupEnergySplash(clientPos, dir, false); - TE_SendToAll(); - - TE_SetupSmoke(clientPos, g_SmokeSprite, 5.0, 10); - TE_SendToAll(); - - EmitAmbientSound(g_SlenderSmiteSound[bossIndex], startPos, client, SNDLEVEL_SCREAMING); - -} - -Action Timer_SlenderStealLife(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (!g_IsSlenderAttacking[bossIndex]) - { - return Plugin_Stop; - } - - if (timer != g_NpcLifeStealTimer[bossIndex]) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - float damage = NPCChaserGetAttackDamage(bossIndex, attackIndex, difficulty); - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - damage *= 0.5; - } - - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - float targetDist; - Handle traceHandle = null; - - float myPos[3], myEyePos[3], myEyeAng[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - float attackRange = NPCChaserGetAttackRange(bossIndex, attackIndex, difficulty); - float attackFOV = NPCChaserGetAttackSpread(bossIndex, attackIndex, difficulty); - float attackDamageForce = NPCChaserGetAttackDamageForce(bossIndex, attackIndex, difficulty); - - int i = -1; - while ((i = FindEntityByClassname(i, "player")) != -1) - { - SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) - { - continue; - } - - if (!attackEliminated && player.IsEliminated) - { - continue; - } - - float targetPos[3], vecClientPos[3]; - player.GetEyePosition(targetPos); - player.GetAbsOrigin(vecClientPos); - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - bool traceDidHit = TR_DidHit(traceHandle); - int traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (traceDidHit && traceHitEntity != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPos); - for (int i2 = 0; i2 < 3; i2++) - { - targetPos[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - traceDidHit = TR_DidHit(traceHandle); - traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - } - - if (!traceDidHit || traceHitEntity == player.index) - { - targetDist = GetVectorSquareMagnitude(targetPos, myEyePos); - - if (targetDist <= SquareFloat(attackRange)) - { - float direction[3]; - SubtractVectors(targetPos, myEyePos, direction); - GetVectorAngles(direction, direction); - - if (FloatAbs(AngleDiff(direction[1], myEyeAng[1])) <= attackFOV) - { - GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(direction, direction); - ScaleVector(direction, attackDamageForce); - - float healthRecover = damage * 2.0; - NPCChaserAddStunHealth(bossIndex, healthRecover); - - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - player.TakeDamage(_, slender, slender, damage * 0.25, 3, _, direction, myEyePos); - } - } - } - } - delete traceHandle; - - return Plugin_Continue; -} - -Action Timer_SlenderChaseBossAttack(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - if (timer != g_SlenderAttackTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - float damage = NPCChaserGetAttackDamage(bossIndex, attackIndex, difficulty); - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - damage *= 0.5; - } - float damageVsProps = NPCChaserGetAttackDamageVsProps(bossIndex, attackIndex); - int damageType = NPCChaserGetAttackDamageType(bossIndex, attackIndex, difficulty); - - // Damage all players within range. - float myPos[3], myEyePos[3], myEyeAng[3], vecMyRot[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", vecMyRot); - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - AddVectors(g_SlenderEyePosOffset[bossIndex], myEyeAng, myEyeAng); - - float viewPunch[3]; - GetChaserProfileAttackPunchVelocity(profile, attackIndex, viewPunch); - - float targetDist; - Handle traceHandle = null; - Handle traceShockwave = null; - - float attackRange = NPCChaserGetAttackRange(bossIndex, attackIndex, difficulty); - float attackFOV = NPCChaserGetAttackSpread(bossIndex, attackIndex, difficulty); - float attackDamageForce = NPCChaserGetAttackDamageForce(bossIndex, attackIndex, difficulty); - - bool hit = false; - - if (g_SlenderState[bossIndex] == STATE_CHASE) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - if (g_LastStuckTime[bossIndex] != 0.0) - { - g_LastStuckTime[bossIndex] = GetGameTime(); - } - loco.ClearStuckStatus(); - if (g_SlenderChaseInitialTimer[bossIndex] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[bossIndex]); - } - - if (NPCChaserGetAttackRepeat(bossIndex, attackIndex) == 1) - { - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttack, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - if (g_SlenderBackupAtkTimer[bossIndex] == null && g_NpcAlreadyAttacked[bossIndex]) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - } - else if (NPCChaserGetAttackRepeat(bossIndex, attackIndex) == 2) - { - if (NpcGetCurrentAttackRepeat(bossIndex, attackIndex) < NPCChaserGetMaxAttackRepeats(bossIndex, attackIndex)) - { - ArrayList repeatArray = GetChaserProfileAttackRepeatTimers(profile, attackIndex); - float delay = repeatArray.Get(NpcGetCurrentAttackRepeat(bossIndex, attackIndex)); - g_SlenderAttackTimer[bossIndex] = CreateTimer(delay, Timer_SlenderChaseBossAttack, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - NpcSetCurrentAttackRepeat(bossIndex, attackIndex, NpcGetCurrentAttackRepeat(bossIndex, attackIndex) + 1); - if (g_SlenderBackupAtkTimer[bossIndex] == null && g_NpcAlreadyAttacked[bossIndex]) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - } - } - switch (NPCChaserGetAttackType(bossIndex, attackIndex)) - { - case SF2BossAttackType_Melee: - { - int prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_physics")) > MaxClients) - { - if (NPCAttackValidateTarget(bossIndex, prop, attackRange, attackFOV)) - { - hit = true; - SDKHooks_TakeDamage(prop, slender, slender, damageVsProps, damageType, _, _, myEyePos); - float spreadVel = 1800.0; - float vertVel = 1300.0; - float vel[3]; - GetAngleVectors(myEyeAng, vel, NULL_VECTOR, NULL_VECTOR); - ScaleVector(vel,spreadVel); - vel[2] = ((GetURandomFloat() + 0.1) * vertVel) * ((GetURandomFloat() + 0.1) * 2); - TeleportEntity(prop, NULL_VECTOR, NULL_VECTOR, vel); - } - } - - prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_dynamic")) > MaxClients) - { - if (GetEntProp(prop, Prop_Data, "m_iHealth") > 0) - { - if (NPCAttackValidateTarget(bossIndex, prop, attackRange, attackFOV)) - { - hit = true; - SDKHooks_TakeDamage(prop, slender, slender, damageVsProps, damageType, _, _, myEyePos); - } - } - } - prop = -1; - while ((prop = FindEntityByClassname(prop, "obj_*")) > MaxClients) - { - if (GetEntProp(prop, Prop_Data, "m_iHealth") > 0) - { - if (NPCAttackValidateTarget(bossIndex, prop, attackRange, attackFOV)) - { - hit = true; - SDKHooks_TakeDamage(prop, slender, slender, damageVsProps, damageType, _, _, myEyePos); - } - } - } - if (NPCChaserShockwaveOnAttack(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetShockwaveAttackIndexes(bossIndex); - GetChaserProfileShockwaveAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex+1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int index = 0; index < count && index < NPCChaserGetAttackCount(bossIndex); index++) - { - int forIndex = StringToInt(allowedIndexesList[index]); - if (forIndex == attackIndex + 1) - { - int beamColor[3], haloColor[3]; - int color1[4], color2[4]; - float myShockPos[3]; - - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myShockPos); - myShockPos[2] += 10; - - GetChaserProfileShockwaveColor1(profile, beamColor); - GetChaserProfileShockwaveColor2(profile, haloColor); - - color1[0] = beamColor[0]; - color1[1] = beamColor[1]; - color1[2] = beamColor[2]; - color1[3] = GetChaserProfileShockwaveAlpha1(profile); - - color2[0] = haloColor[0]; - color2[1] = haloColor[1]; - color2[2] = haloColor[2]; - color2[3] = GetChaserProfileShockwaveAlpha2(profile); - - int modelBeam, modelHalo; - modelBeam = GetChaserProfileShockwaveBeamModel(profile); - modelHalo = GetChaserProfileShockwaveHaloModel(profile); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.2, NPCChaserGetShockwaveWidth(bossIndex,1), NPCChaserGetShockwaveAmplitude(bossIndex), color2, 15, 0); //Inner - TE_SendToAll(); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.3, NPCChaserGetShockwaveWidth(bossIndex,0), NPCChaserGetShockwaveAmplitude(bossIndex), color1, 15, 0); //Outer - TE_SendToAll(); - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex+1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex) - { - int beamColor[3], haloColor[3]; - int color1[4], color2[4]; - float myShockPos[3]; - - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myShockPos); - myShockPos[2] += 10; - - GetChaserProfileShockwaveColor1(profile, beamColor); - GetChaserProfileShockwaveColor2(profile, haloColor); - - color1[0] = beamColor[0]; - color1[1] = beamColor[1]; - color1[2] = beamColor[2]; - color1[3] = GetChaserProfileShockwaveAlpha1(profile); - - color2[0] = haloColor[0]; - color2[1] = haloColor[1]; - color2[2] = haloColor[2]; - color2[3] = GetChaserProfileShockwaveAlpha2(profile); - - int modelBeam, modelHalo; - modelBeam = PrecacheModel(g_SlenderShockwaveBeamSprite[bossIndex], true); - modelHalo = PrecacheModel(g_SlenderShockwaveHaloSprite[bossIndex], true); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.2, NPCChaserGetShockwaveWidth(bossIndex,1), NPCChaserGetShockwaveAmplitude(bossIndex), color2, 15, 0); //Inner - TE_SendToAll(); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.3, NPCChaserGetShockwaveWidth(bossIndex,0), NPCChaserGetShockwaveAmplitude(bossIndex), color1, 15, 0); //Outer - TE_SendToAll(); - } - } - } - } - int i = -1; - while ((i = FindEntityByClassname(i, "player")) != -1) - { - SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) - { - continue; - } - - if (!attackEliminated && player.IsEliminated) - { - continue; - } - - float targetPos[3], vecClientPos[3], targetPosShockwave[3]; - player.GetEyePosition(targetPos); - targetPosShockwave = targetPos; - player.GetAbsOrigin(vecClientPos); - - if (NPCChaserShockwaveOnAttack(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetShockwaveAttackIndexes(bossIndex); - GetChaserProfileShockwaveAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex+1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int index = 0; index < count && index < NPCChaserGetAttackCount(bossIndex); index++) - { - int forIndex = StringToInt(allowedIndexesList[index]); - if (forIndex == attackIndex + 1) - { - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - bool traceDidHitShockwave = TR_DidHit(traceShockwave); - int traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - - if (traceDidHitShockwave && traceHitEntityShockwave != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPosShockwave); - for (int i2 = 0; i2 < 3; i2++) - { - targetPosShockwave[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - traceDidHitShockwave = TR_DidHit(traceShockwave); - traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - } - - if (!traceDidHitShockwave || traceHitEntityShockwave == player.index) - { - float targetDistShockwave = GetVectorSquareMagnitude(targetPos, myEyePos); - - if ((vecClientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) - { - float percentLife; - percentLife = player.FlashlightBatteryLife - NPCChaserGetShockwaveDrain(bossIndex, difficulty); - if (percentLife < 0.0) - { - percentLife = 0.0; - } - player.FlashlightBatteryLife = percentLife; - - float directionForce[3]; - float newClientPosit[3]; - player.GetAbsOrigin(newClientPosit); - newClientPosit[2] += 10.0; - - MakeVectorFromPoints(myPos, newClientPosit, directionForce); - - NormalizeVector(directionForce, directionForce); - - ScaleVector(directionForce, NPCChaserGetShockwaveForce(bossIndex, difficulty)); - - if (directionForce[2] < 0.0) - { - directionForce[2] *= -1.0; - } - - player.SetPropVector(Prop_Data, "m_vecBaseVelocity", directionForce); - - if (NPCChaserShockwaveStunEnabled(bossIndex)) - { - player.Stun(NPCChaserGetShockwaveStunDuration(bossIndex, difficulty), NPCChaserGetShockwaveStunSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN, player.index); - } - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex+1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex) - { - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - bool traceDidHitShockwave = TR_DidHit(traceShockwave); - int traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - - if (traceDidHitShockwave && traceHitEntityShockwave != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPosShockwave); - for (int i2 = 0; i2 < 3; i2++) - { - targetPosShockwave[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - traceDidHitShockwave = TR_DidHit(traceShockwave); - traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - } - - if (!traceDidHitShockwave || traceHitEntityShockwave == player.index) - { - float targetDistShockwave = GetVectorSquareMagnitude(targetPos, myEyePos); - - if ((vecClientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) - { - float percentLife; - percentLife = player.FlashlightBatteryLife - NPCChaserGetShockwaveDrain(bossIndex, difficulty); - if (percentLife < 0.0) - { - percentLife = 0.0; - } - player.FlashlightBatteryLife = percentLife; - - float directionForce[3]; - float newClientPosit[3]; - player.GetAbsOrigin(newClientPosit); - newClientPosit[2] += 10.0; - - MakeVectorFromPoints(myPos, newClientPosit, directionForce); - - NormalizeVector(directionForce, directionForce); - - ScaleVector(directionForce, NPCChaserGetShockwaveForce(bossIndex, difficulty)); - - if (directionForce[2] < 0.0) - { - directionForce[2] *= -1.0; - } - - player.SetPropVector(Prop_Data, "m_vecBaseVelocity", directionForce); - - if (NPCChaserShockwaveStunEnabled(bossIndex)) - { - player.Stun(NPCChaserGetShockwaveStunDuration(bossIndex, difficulty), NPCChaserGetShockwaveStunSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN, player.index); - } - } - } - } - } - } - } - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - bool traceDidHit = TR_DidHit(traceHandle); - int traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (traceDidHit && traceHitEntity != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPos); - for (int i2 = 0; i2 < 3; i2++) - { - targetPos[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - traceDidHit = TR_DidHit(traceHandle); - traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - } - - if (!traceDidHit || traceHitEntity == player.index) - { - targetDist = GetVectorSquareMagnitude(targetPos, myEyePos); - - if (targetDist <= SquareFloat(attackRange)) - { - float direction[3]; - SubtractVectors(targetPos, myEyePos, direction); - GetVectorAngles(direction, direction); - - if (FloatAbs(AngleDiff(direction[1], myEyeAng[1])) <= attackFOV) - { - GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(direction, direction); - ScaleVector(direction, attackDamageForce); - - if (SF_SpecialRound(SPECIALROUND_MULTIEFFECT) || g_RenevantMultiEffect) - { - int effect = GetRandomInt(0, 6); - switch (effect) - { - case 1: - { - if (!player.InCondition(TFCond_Bleeding)) - { - player.Bleed(true, _, 4.0); - } - } - case 2: - { - player.Ignite(true); - } - case 3: - { - player.ChangeCondition(TFCond_Jarated, _, 4.0); - } - case 4: - { - player.ChangeCondition(TFCond_CritMmmph, _, 3.0); - } - case 5: - { - player.ChangeCondition(TFCond_Gas, _, 3.0); - } - case 6: - { - int effectRare = GetRandomInt(1, 30); - switch (effectRare) - { - case 1, 14, 25, 30: - { - int newHealth = player.GetProp(Prop_Send, "m_iHealth")+view_as(damage); - if (newHealth > 450) - { - newHealth = 450; - } - player.ChangeCondition(TFCond_MegaHeal, _, 2.0); - player.SetProp(Prop_Send, "m_iHealth", newHealth); - damage = 0.0; - } - case 7, 27: - { - //It's over 9000! - damage = 9001.0; - } - case 5, 16, 18, 22, 23, 26: - { - ScaleVector(direction, 1200.0); - } - } - } - } - } - - if (player.InCondition(TFCond_UberchargedCanteen) && player.InCondition(TFCond_CritOnFirstBlood) && player.InCondition(TFCond_UberBulletResist) && player.InCondition(TFCond_UberBlastResist) && player.InCondition(TFCond_UberFireResist) && player.InCondition(TFCond_MegaHeal)) //Remove Powerplay - { - player.ChangeCondition(TFCond_UberchargedCanteen, true); - player.ChangeCondition(TFCond_CritOnFirstBlood, true); - player.ChangeCondition(TFCond_UberBulletResist, true); - player.ChangeCondition(TFCond_UberBlastResist, true); - player.ChangeCondition(TFCond_UberFireResist, true); - player.ChangeCondition(TFCond_MegaHeal, true); - TF2_SetPlayerPowerPlay(player.index, false); - } - Call_StartForward(g_OnClientDamagedByBossFwd); - Call_PushCell(player.index); - Call_PushCell(bossIndex); - Call_PushCell(slender); - Call_PushFloat(damage); - Call_PushCell(damageType); - Call_Finish(); - float checkHealth = float(player.GetProp(Prop_Send, "m_iHealth")); - if ((NPCHasAttribute(bossIndex, SF2Attribute_DeathCamOnLowHealth) || NPCChaserGetAttackDeathCamOnLowHealth(bossIndex, attackIndex))) - { - float checkDamage = damage; - - if ((damageType & DMG_ACID) && !player.InCondition(TFCond_DefenseBuffed)) - { - checkDamage *= 3; - } - else if (((damageType & DMG_POISON) || player.InCondition(TFCond_Jarated) || player.InCondition(TFCond_MarkedForDeath) || player.InCondition(TFCond_MarkedForDeathSilent)) && !player.InCondition(TFCond_DefenseBuffed)) - { - checkDamage *= 1.35; - } - else if (player.InCondition(TFCond_DefenseBuffed)) - { - checkDamage *= 0.65; - } - - if (checkDamage > checkHealth) - { - player.StartDeathCam(bossIndex, myPos); - } - } - if (NPCChaserGetAttackPullIn(bossIndex, attackIndex)) - { - float newClientPos[3]; - player.GetAbsOrigin(newClientPos); - newClientPos[2] += 10.0; - - float effectAng[3] = {0.0, 0.0, 0.0}; - - VectorTransform(myEyePos, myPos, vecMyRot, myEyePos); - AddVectors(effectAng, vecMyRot, effectAng); - - float pullDirection[3], pullAngle[3]; - SubtractVectors(newClientPos, myEyePos, pullDirection); - NormalizeVector(pullDirection, pullDirection); - GetVectorAngles(pullDirection, pullAngle); - ScaleVector(pullAngle, 1200.0); - pullAngle[2] = 10.0; - - player.Teleport(newClientPos, NULL_VECTOR, pullAngle); - - player.TakeDamage(_, slender, slender, damage, damageType|DMG_PREVENT_PHYSICS_FORCE, _, direction, myEyePos); - } - else - { - player.TakeDamage(_, slender, slender, damage, damageType, _, direction, myEyePos); - } - player.ViewPunch(viewPunch); - - if (player.InCondition(TFCond_Gas)) - { - player.Ignite(true); - player.ChangeCondition(TFCond_Gas, true); - } - if (player.InCondition(TFCond_Milked)) - { - NPCChaserAddStunHealth(bossIndex, damage * 0.6); - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - } - float duration = NPCGetAttributeValue(bossIndex, SF2Attribute_BleedPlayerOnHit); - if (duration > 0.0 && !player.InCondition(TFCond_Bleeding) && !NPCChaserBleedPlayerOnHit(bossIndex)) - { - player.Bleed(true, _, duration); - } - if (NPCHasAttribute(bossIndex, SF2Attribute_IgnitePlayerOnHit)) - { - player.Ignite(true); - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_StunPlayerOnHit); - if (duration > 0.0) - { - float slowdown = NPCGetAttributeValue(bossIndex, SF2Attribute_StunPlayerPercentage); - if (slowdown > 0.0) - { - player.Stun(duration, slowdown, TF_STUNFLAGS_SMALLBONK, player.index); - } - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_JaratePlayerOnHit); - if (duration > 0.0 && !NPCChaserJaratePlayerOnHit(bossIndex)) - { - player.ChangeCondition(TFCond_Jarated, _, duration); - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_MilkPlayerOnHit); - if (duration > 0.0 && !NPCChaserMilkPlayerOnHit(bossIndex)) - { - player.ChangeCondition(TFCond_Milked, _, duration); - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_GasPlayerOnHit); - if (duration > 0.0 && !NPCChaserGasPlayerOnHit(bossIndex)) - { - player.ChangeCondition(TFCond_Gas, _, duration); - } - - if (NPCChaserUseAdvancedDamageEffects(bossIndex)) - { - SlenderDoDamageEffects(bossIndex, attackIndex, player); - } - if (NPCChaserDamageParticlesEnabled(bossIndex)) - { - GetChaserProfileDamageParticleName(profile, damageEffectParticle, sizeof(damageEffectParticle)); - if (damageEffectParticle[0] != '\0') - { - bool beamParticle = GetChaserProfileDamageParticleBeamState(profile); - if (beamParticle) - { - SlenderCreateParticleBeamClient(bossIndex, damageEffectParticle, 35.0, player); - } - else - { - SlenderCreateParticleAttach(bossIndex, damageEffectParticle, 35.0, player); - } - GetChaserProfileDamageParticleSound(profile, damageEffectSound, sizeof(damageEffectSound)); - if (damageEffectSound[0] != '\0') - { - EmitSoundToAll(damageEffectSound, slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - if (NPCChaserXenobladeSystemEnabled(bossIndex)) - { - switch (damageType) - { - case 134217728: - { - player.ChangeCondition(TFCond_TeleportedGlow, _, NPCChaserGetXenobladeBreakDuration(bossIndex), player.index); - } - case 131072: - { - if (player.InCondition(TFCond_TeleportedGlow)) - { - player.Stun(NPCChaserGetXenobladeToppleDuration(bossIndex), NPCChaserGetXenobladeToppleSlowdown(bossIndex), TF_STUNFLAGS_LOSERSTATE, player.index); - player.ChangeCondition(TFCond_TeleportedGlow, true); - player.ChangeCondition(TFCond_MedigunDebuff, _, NPCChaserGetXenobladeToppleDuration(bossIndex), player.index); - } - } - case 268435456: - { - if (player.InCondition(TFCond_MedigunDebuff)) - { - player.Stun(NPCChaserGetXenobladeDazeDuration(bossIndex), 1.0, 10, player.index); - player.ChangeCondition(TFCond_MedigunDebuff, true); - } - } - } - } - - // Add stress - float stressScalar = damage / 125.0; - if (stressScalar > 1.0) - { - stressScalar = 1.0; - } - ClientAddStress(player.index, 0.33 * stressScalar); - - hit = true; - } - } - } - } - - if (hit) - { - // Fling it. - int phys = CreateEntityByName("env_physexplosion"); - if (phys != -1) - { - TeleportEntity(phys, myEyePos, NULL_VECTOR, NULL_VECTOR); - DispatchKeyValue(phys, "spawnflags", "1"); - DispatchKeyValueFloat(phys, "radius", attackRange); - DispatchKeyValueFloat(phys, "magnitude", attackDamageForce); - DispatchSpawn(phys); - ActivateEntity(phys); - AcceptEntityInput(phys, "Explode"); - RemoveEntity(phys); - } - ArrayList hitSounds = GetChaserProfileHitSounds(profile); - if (hitSounds != null && hitSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - hitSounds.GetArray(hitSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - } - else - { - ArrayList missSounds = GetChaserProfileMissSounds(profile); - if (missSounds != null && missSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - missSounds.GetArray(missSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - } - } - case SF2BossAttackType_Ranged: - { - //BULLETS ANYONE? Thx Pelipoika - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - char particleName[PLATFORM_MAX_PATH]; - GetChaserProfileAttackBulletTrace(profile, attackIndex, particleName, sizeof(particleName)); - float vecSpread = NPCChaserGetAttackBulletSpread(bossIndex, attackIndex, difficulty); - ArrayList bulletSounds = GetChaserProfileBulletShootSounds(profile); - if (bulletSounds != null && bulletSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - bulletSounds.GetArray(bulletSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - - float effectPos[3], clientPos[3], basePos[3], baseAng[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - if (target.IsValid) - { - target.GetEyePosition(clientPos); - clientPos[2] -= 20.0; - } - else - { - clientPos[2] = 50.0; - clientPos[0] = 10.0; - VectorTransform(clientPos, basePos, baseAng, clientPos); - } - GetChaserProfileAttackBulletOffset(profile, attackIndex, effectPos); - VectorTransform(effectPos, basePos, baseAng, effectPos); - AddVectors(effectAng, baseAng, effectAng); - - float shootDirection[3], shootAng[3]; - if (!target.IsValid) - { - shootAng[0] = myEyeAng[0]; - shootAng[1] = myEyeAng[1]; - shootAng[2] = myEyeAng[2]; - } - SubtractVectors(clientPos, effectPos, shootDirection); - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - for (int i = 0; i < NPCChaserGetAttackBulletCount(bossIndex, attackIndex, difficulty); i++) - { - float x, y; - x = GetRandomFloat( -0.5, 0.5 ) + GetRandomFloat( -0.5, 0.5 ); - y = GetRandomFloat( -0.5, 0.5 ) + GetRandomFloat( -0.5, 0.5 ); - - float dirShooting[3], vecRight[3], vecUp[3]; - GetAngleVectors(shootAng, dirShooting, vecRight, vecUp); - - float dir[3]; - dir[0] = dirShooting[0] + x * vecSpread * vecRight[0] + y * vecSpread * vecUp[0]; - dir[1] = dirShooting[1] + x * vecSpread * vecRight[1] + y * vecSpread * vecUp[1]; - dir[2] = dirShooting[2] + x * vecSpread * vecRight[2] + y * vecSpread * vecUp[2]; - NormalizeVector(dir, dir); - - float end[3]; - end[0] = effectPos[0] + dir[0] * 9001.0; - end[1] = effectPos[1] + dir[1] * 9001.0; - end[2] = effectPos[2] + dir[2] * 9001.0; - - // Fire a bullet (ignoring the shooter). - Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - if (TR_GetFraction(trace) < 1.0) - { - // Verify we have an entity at the point of impact. - if (TR_GetEntityIndex(trace) == -1) - { - delete trace; - return Plugin_Stop; - } - - float endPos[3]; - TR_GetEndPosition(endPos, trace); - - if (TR_GetEntityIndex(trace) <= 0 || TR_GetEntityIndex(trace) > MaxClients) - { - float normal[3]; - TR_GetPlaneNormal(trace, normal); - GetVectorAngles(normal, normal); - //CreateParticle("impact_concrete", endPos, normal); - } - - // Regular impact effects. - char effect[PLATFORM_MAX_PATH]; - FormatEx(effect, PLATFORM_MAX_PATH, "%s", particleName); - - if (particleName[0] != '\0') - { - int tblidx = FindStringTable("ParticleEffectNames"); - if (tblidx == INVALID_STRING_TABLE) - { - LogError("Could not find string table: ParticleEffectNames"); - return Plugin_Stop; - } - char tmp[256]; - int count = GetStringTableNumStrings(tblidx); - int stridx = INVALID_STRING_INDEX; - for (int i2 = 0; i2 < count; i2++) - { - ReadStringTable(tblidx, i2, tmp, sizeof(tmp)); - if (strcmp(tmp, effect, false) == 0) - { - stridx = i2; - break; - } - } - if (stridx == INVALID_STRING_INDEX) - { - LogError("Could not find particle: %s", effect); - return Plugin_Stop; - } - - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", effectPos[0]); - TE_WriteFloat("m_vecOrigin[1]", effectPos[1]); - TE_WriteFloat("m_vecOrigin[2]", effectPos[2]); - TE_WriteNum("m_iParticleSystemIndex", stridx); - TE_WriteNum("entindex", slender); - TE_WriteNum("m_iAttachType", 2); - TE_WriteNum("m_iAttachmentPointIndex", 0); - TE_WriteNum("m_bResetParticles", false); - TE_WriteNum("m_bControlPoint1", 1); - TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); - TE_SendToAll(); - } - - // TE_SetupBeamPoints(effectPos, endPos, g_iPathLaserModelIndex, g_iPathLaserModelIndex, 0, 30, 0.1, 0.1, 0.1, 5, 0.0, view_as({255, 0, 255, 255}), 30); - // TE_SendToAll(); - if (NPCChaserUseAdvancedDamageEffects(bossIndex)) - { - SlenderDoDamageEffects(bossIndex, attackIndex, SF2_BasePlayer(TR_GetEntityIndex(trace))); - } - SDKHooks_TakeDamage(TR_GetEntityIndex(trace), slender, slender, NPCChaserGetAttackBulletDamage(bossIndex, attackIndex, difficulty), DMG_BULLET, _, CalculateBulletDamageForce(dir, 1.0), endPos); - } - - delete trace; - } - } - case SF2BossAttackType_Projectile: - { - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - if (target.IsValid && target.IsAlive && !target.IsInGhostMode) - { - ArrayList projectileSounds = GetChaserProfileProjectileShootSounds(profile); - if (projectileSounds != null && projectileSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - projectileSounds.GetArray(projectileSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - NPCChaserProjectileAttackShoot(bossIndex, slender, target.index, profile); - if (GetChaserProfileShootAnimationsState(profile)) - { - g_NpcUseFireAnimation[bossIndex] = true; - int state = g_SlenderState[bossIndex]; - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - } - else //Our target is unavailable, abort. - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - } - } - Call_StartForward(g_OnBossAttackedFwd); - Call_PushCell(bossIndex); - Call_PushCell(attackIndex); - Call_Finish(); - if (NPCChaserGetAttackDisappear(bossIndex, attackIndex, difficulty) != 1 && NPCChaserGetAttackRepeat(bossIndex, attackIndex) < 1) - { - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEnd, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - } - if (NPCChaserGetAttackRepeat(bossIndex, attackIndex) >= 1 && !g_NpcAlreadyAttacked[bossIndex]) - { - g_SlenderBackupAtkTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEndBackup, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - g_NpcAlreadyAttacked[bossIndex] = true; - } - if (NPCChaserGetAttackDisappear(bossIndex, attackIndex, difficulty) == 1) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - RemoveSlender(bossIndex); - return Plugin_Stop; - } - delete traceHandle; - delete traceShockwave; - return Plugin_Stop; -} - -static float[] CalculateBulletDamageForce(const float bulletDir[3], float scale) -{ - float force[3]; force = bulletDir; - NormalizeVector(force, force); - ScaleVector(force, g_PhysicsPushScaleConVar.FloatValue); - ScaleVector(force, scale); - return force; -} - -Action Timer_SlenderChaseBossAttackIgniteHit(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int player = EntRefToEntIndex(entref); - if (!player || player == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - if (timer != g_PlayerIgniteTimer[player]) - { - return Plugin_Stop; - } - - TF2_IgnitePlayer(player, player, g_PlayerIgniteDurationEffect[player]); - g_PlayerIgniteTimer[player] = null; - return Plugin_Stop; -} - -Action Timer_SlenderPrepareExplosiveDance(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - CreateTimer(0.13, Timer_SlenderChaseBossExplosiveDance, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Stop; -} - -static Action Timer_SlenderChaseBossExplosiveDance(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - static int exploded = 0; - - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - exploded=0; - return Plugin_Stop; - } - - // Damage all players within range. - float myPos[3], myEyePos[3], myEyeAng[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int range = NPCChaserGetAttackExplosiveDanceRadius(bossIndex, attackIndex, difficulty); - - AddVectors(g_SlenderEyePosOffset[bossIndex], myEyeAng, myEyeAng); - - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - if (!g_IsSlenderAttacking[bossIndex]) - { - exploded=0; - return Plugin_Stop; - } - - exploded++; - if (exploded <= 35) - { - float slenderPosition[3], explosionPosition[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - explosionPosition[2] = slenderPosition[2] + 50.0; - for (int e = 0; e < 5; e++) - { - for (int i = 1; i <= MaxClients; i++) - { - SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) - { - continue; - } - - if (!attackEliminated && player.IsEliminated) - { - continue; - } - - int explosivePower = CreateEntityByName("env_explosion"); - if (explosivePower != -1) - { - DispatchKeyValueFloat(explosivePower, "DamageForce", 180.0); - - SetEntProp(explosivePower, Prop_Data, "m_iMagnitude", 666, 4); - SetEntProp(explosivePower, Prop_Data, "m_iRadiusOverride", 200, 4); - SetEntPropEnt(explosivePower, Prop_Data, "m_hOwnerEntity", slender); - explosionPosition[0]=slenderPosition[0]+float(GetRandomInt(-range, range)); - explosionPosition[1]=slenderPosition[1]+float(GetRandomInt(-range, range)); - TeleportEntity(explosivePower, explosionPosition, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(explosivePower); - - AcceptEntityInput(explosivePower, "Explode"); - RemoveEntity(explosivePower); - CreateTimer(0.1, Timer_KillEntity, EntIndexToEntRef(explosivePower), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - else - { - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEnd, EntIndexToEntRef(slender)); - exploded = 0; - return Plugin_Stop; - } - return Plugin_Continue; -} - -Action Timer_SlenderChaseBossAttackBeginLaser(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderAttackTimer[bossIndex]) - { - return Plugin_Stop; - } - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - g_NpcLaserTimer[bossIndex] = GetGameTime() + NPCChaserGetAttackLaserDuration(bossIndex, attackIndex, difficulty); - - g_SlenderLaserTimer[bossIndex] = CreateTimer(0.1, Timer_SlenderChaseBossAttackLaser, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEnd, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - - NPCChaserSetNextAttackTime(bossIndex, attackIndex, GetGameTime()+NPCChaserGetAttackCooldown(bossIndex, attackIndex, difficulty)); - - return Plugin_Stop; -} - -static Action Timer_SlenderChaseBossAttackLaser(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderLaserTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (GetGameTime() >= g_NpcLaserTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - return Plugin_Stop; - } - - CBaseCombatCharacter animationEntity = CBaseCombatCharacter(slender); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - if (!g_IsSlenderAttacking[bossIndex]) - { - return Plugin_Stop; - } - - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - if (target.IsValid && target.IsAlive && !target.IsInGhostMode) - { - float effectPos[3]; - float clientPos[3]; - - target.GetEyePosition(clientPos); - clientPos[2] -= 20.0; - - float basePos[3], baseAng[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - GetChaserProfileAttackLaserOffset(profile, attackIndex, effectPos); - VectorTransform(effectPos, basePos, baseAng, effectPos); - AddVectors(effectAng, baseAng, effectAng); - - float shootDirection[3], shootAng[3]; - SubtractVectors(clientPos, effectPos, shootDirection); - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - float dirShooting[3]; - GetAngleVectors(shootAng, dirShooting, NULL_VECTOR, NULL_VECTOR); - - float dir[3]; - dir[0] = dirShooting[0]; - dir[1] = dirShooting[1]; - dir[2] = dirShooting[2]; - NormalizeVector(dir, dir); - - float end[3]; - end[0] = effectPos[0] + dir[0] * 9001.0; - end[1] = effectPos[1] + dir[1] * 9001.0; - end[2] = effectPos[2] + dir[2] * 9001.0; - - Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - if (TR_GetFraction(trace) < 1.0) - { - // Verify we have an entity at the point of impact. - if (TR_GetEntityIndex(trace) == -1) - { - delete trace; - return Plugin_Stop; - } - - float endPos[3]; - TR_GetEndPosition(endPos, trace); - - if (TR_GetEntityIndex(trace) <= 0 || TR_GetEntityIndex(trace) > MaxClients) - { - float normal[3]; TR_GetPlaneNormal(trace, normal); - GetVectorAngles(normal, normal); - } - - int color[3]; - NPCChaserGetAttackLaserColor(bossIndex, attackIndex, color); - int colorReal[4]; - colorReal[0] = color[0]; - colorReal[1] = color[1]; - colorReal[2] = color[2]; - colorReal[3] = 255; - - if (NPCChaserGetAttackLaserAttachmentState(bossIndex, attackIndex)) - { - char attachment[PLATFORM_MAX_PATH]; - GetChaserProfileAttackLaserAttachmentName(profile, attackIndex, attachment, sizeof(attachment)); - int attachmentIndex = animationEntity.LookupAttachment(attachment); - float targetEntPos[3], tempAng[3]; - animationEntity.GetAttachment(attachmentIndex, targetEntPos, tempAng); - TE_SetupBeamPoints(targetEntPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, NPCChaserGetAttackLaserSize(bossIndex, attackIndex), NPCChaserGetAttackLaserSize(bossIndex, attackIndex), 5, NPCChaserGetAttackLaserNoise(bossIndex, attackIndex), colorReal, 1); - TE_SendToAll(); - } - else - { - TE_SetupBeamPoints(effectPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, NPCChaserGetAttackLaserSize(bossIndex, attackIndex), NPCChaserGetAttackLaserSize(bossIndex, attackIndex), 5, NPCChaserGetAttackLaserNoise(bossIndex, attackIndex), colorReal, 1); - TE_SendToAll(); - } - if (NPCChaserUseAdvancedDamageEffects(bossIndex)) - { - SlenderDoDamageEffects(bossIndex, attackIndex, SF2_BasePlayer(TR_GetEntityIndex(trace))); - } - SDKHooks_TakeDamage(TR_GetEntityIndex(trace), slender, slender, NPCChaserGetAttackLaserDamage(bossIndex, attackIndex, difficulty), DMG_SHOCK|DMG_ALWAYSGIB, _, _, endPos); - } - - delete trace; - - } - return Plugin_Continue; -} - -bool NPCAttackValidateTarget(int bossIndex,int target, float attackRange, float attackFOV, bool checkBlock = false) -{ - int bossEntity = NPCGetEntIndex(bossIndex); - - float myEyePos[3], myEyeAng[3]; - NPCGetEyePosition(bossIndex, myEyePos); - if (target > MaxClients) - { - //float flVecMaxs[3]; - myEyePos[2]+=30.0; - } - GetEntPropVector(bossEntity, Prop_Data, "m_angAbsRotation", myEyeAng); - AddVectors(g_SlenderEyeAngOffset[bossIndex], myEyeAng, myEyeAng); - - float targetPos[3], targetMins[3], targetMaxs[3]; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - GetEntPropVector(target, Prop_Send, "m_vecMins", targetMins); - GetEntPropVector(target, Prop_Send, "m_vecMaxs", targetMaxs); - - for (int i = 0; i < 3; i++) - { - targetPos[i] += (targetMins[i] + targetMaxs[i]) / 2.0; - } - - float targetDist = GetVectorSquareMagnitude(targetPos, myEyePos); - if (targetDist <= SquareFloat(attackRange)) - { - float direction[3]; - SubtractVectors(g_SlenderGoalPos[bossIndex], myEyePos, direction); - GetVectorAngles(direction, direction); - - if (FloatAbs(AngleDiff(direction[1], myEyeAng[1])) <= attackFOV / 2.0) - { - Handle traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - bossEntity); - - bool traceDidHit = TR_DidHit(traceHandle); - int traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (!traceDidHit || traceHitEntity == target) - { - if (checkBlock) - { - float pos[3], posForward[3]; - GetEntPropVector(bossEntity, Prop_Data, "m_vecAbsOrigin", pos); - GetPositionForward(pos, myEyeAng, posForward, SquareRoot(targetDist+SquareFloat(50.0))); - if (NPCGetRaidHitbox(bossIndex) == 1) - { - traceHandle = TR_TraceHullFilterEx(pos, - posForward, - g_SlenderDetectMins[bossIndex], - g_SlenderDetectMaxs[bossIndex], - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - TraceRayBossVisibility, - bossEntity); - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - traceHandle = TR_TraceHullFilterEx(pos, - posForward, - HULL_HUMAN_MINS, - HULL_HUMAN_MAXS, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - TraceRayBossVisibility, - bossEntity); - } - traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (traceHitEntity == target) - { - return true; - } - } - else - { - return true; - } - } - delete traceHandle; - } - } - - return false; -} - -static Action Timer_SlenderChaseBossAttackEnd(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderAttackTimer[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - loco.ClearStuckStatus(); - - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - g_SlenderLaserTimer[bossIndex] = null; - CBaseNPC_RemoveAllLayers(slender); - return Plugin_Stop; -} - -static Action Timer_SlenderChaseBossAttackEndBackup(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderBackupAtkTimer[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - loco.ClearStuckStatus(); - - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - g_SlenderLaserTimer[bossIndex] = null; - CBaseNPC_RemoveAllLayers(slender); - return Plugin_Stop; -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp deleted file mode 100644 index 67e93799..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp +++ /dev/null @@ -1,2670 +0,0 @@ -#if defined _sf2_npc_chaser_mind_included -#endinput -#endif - -#define _sf2_npc_chaser_mind_included - -#pragma semicolon 1 - -char cloakParticle[PLATFORM_MAX_PATH]; - -Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are so big, you get a file dedicated to only you -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_Chaser chaserBoss = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); - if (chaserBoss == SF2_INVALID_NPC_CHASER) - { - return Plugin_Stop; - } - - if (chaserBoss.Flags & SFF_MARKEDASFAKE) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - if (npc == INVALID_NPC) - { - return Plugin_Stop; - } - - if (timer != g_SlenderEntityThink[chaserBoss.Index]) - { - return Plugin_Stop; - } - - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - INextBot bot = npc.GetBot(); - CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npc.GetEntity()); - - float gameTime = GetGameTime(); - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_IDLE, 1, "g_SlenderTimeUntilKill[%d]: %f", chaserBoss.Index, g_SlenderTimeUntilKill[chaserBoss.Index] - gameTime); - #endif - - float myPos[3], myEyeAng[3]; - float buffer[3]; - - char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - chaserBoss.GetProfile(slenderProfile, sizeof(slenderProfile)); - - npcEntity.GetAbsOrigin(myPos); - npcEntity.GetAbsAngles(myEyeAng); - - AddVectors(myEyeAng, g_SlenderEyeAngOffset[chaserBoss.Index], myEyeAng); - - int difficulty = GetLocalGlobalDifficulty(chaserBoss.Index); - - float originalSpeed, originalMaxSpeed, originalAcceleration; - if (!g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !SF_IsSlaughterRunMap() && !g_Renevant90sEffect) - { - originalSpeed = chaserBoss.GetSpeed(difficulty); - originalMaxSpeed = chaserBoss.GetMaxSpeed(difficulty); - originalAcceleration = chaserBoss.GetAcceleration(difficulty); - } - else if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || SF_IsSlaughterRunMap() || g_Renevant90sEffect) - { - originalSpeed = chaserBoss.GetSpeed(difficulty); - originalMaxSpeed = chaserBoss.GetMaxSpeed(difficulty); - originalAcceleration = chaserBoss.GetAcceleration(difficulty); - if (originalSpeed < (SF_IsSlaughterRunMap() ? 580.0 : 520.0)) - { - originalSpeed = SF_IsSlaughterRunMap() ? 580.0 : 520.0; - } - if (originalMaxSpeed < (SF_IsSlaughterRunMap() ? 580.0 : 520.0)) - { - originalMaxSpeed = SF_IsSlaughterRunMap() ? 580.0 : 520.0; - } - if (originalAcceleration < (SF_IsSlaughterRunMap() ? 10000.0 : 9001.0)) - { - originalAcceleration = SF_IsSlaughterRunMap() ? 10000.0 : 9001.0; - } - } - originalSpeed += chaserBoss.GetAddSpeed(); - originalMaxSpeed += chaserBoss.GetAddMaxSpeed(); - originalAcceleration += chaserBoss.GetAddAcceleration(); - } - else - { - originalSpeed = 0.0; - originalMaxSpeed = 0.0; - originalAcceleration = chaserBoss.GetAcceleration(difficulty); - } - float originalWalkSpeed = chaserBoss.GetWalkSpeed(difficulty); - float originalMaxWalkSpeed = chaserBoss.GetMaxWalkSpeed(difficulty); - - if (g_InProxySurvivalRageMode) - { - originalSpeed *= 1.25; - originalWalkSpeed *= 1.25; - originalMaxSpeed *= 1.25; - originalMaxWalkSpeed *= 1.25; - } - float speed, maxSpeed, acceleration; - if (!g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - speed = originalSpeed; - maxSpeed = originalMaxSpeed; - } - else - { - speed = 0.0; - maxSpeed = 0.0; - } - acceleration = originalAcceleration; - if (g_RoundDifficultyModifier > 1.0) - { - speed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - maxSpeed = originalMaxSpeed + ((originalMaxSpeed * g_RoundDifficultyModifier) / 20.0); - acceleration = originalAcceleration + ((originalAcceleration * g_RoundDifficultyModifier) / 15.0); - } - else - { - speed = originalSpeed; - maxSpeed = originalMaxSpeed; - acceleration = originalAcceleration; - } - if (speed < originalSpeed) - { - speed = originalSpeed; - } - if (speed > maxSpeed) - { - speed = maxSpeed; - } - if (acceleration < originalAcceleration) - { - acceleration = originalAcceleration; - } - - float walkSpeed = originalWalkSpeed; - float maxWalkSpeed = originalMaxWalkSpeed; - if (g_RoundDifficultyModifier > 1.0) - { - walkSpeed = (originalWalkSpeed + (originalWalkSpeed * g_RoundDifficultyModifier) / 15.0); - maxWalkSpeed = (originalMaxWalkSpeed + (originalMaxWalkSpeed * g_RoundDifficultyModifier) / 20.0); - } - if (walkSpeed < originalWalkSpeed) - { - walkSpeed = originalWalkSpeed; - } - if (walkSpeed > maxWalkSpeed) - { - walkSpeed = maxWalkSpeed; - } - - g_SlenderSpeedMultiplier[chaserBoss.Index] = 1.0; - if (chaserBoss.CanBeSeen(_, true)) - { - if (chaserBoss.HasAttribute(SF2Attribute_ReducedSpeedOnLook)) - { - g_SlenderSpeedMultiplier[chaserBoss.Index] = chaserBoss.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); - speed *= chaserBoss.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); - } - - if (chaserBoss.HasAttribute(SF2Attribute_ReducedWalkSpeedOnLook)) - { - walkSpeed *= chaserBoss.GetAttributeValue(SF2Attribute_ReducedWalkSpeedOnLook); - } - - if (chaserBoss.HasAttribute(SF2Attribute_ReducedAccelerationOnLook)) - { - walkSpeed *= chaserBoss.GetAttributeValue(SF2Attribute_ReducedAccelerationOnLook); - } - } - if (g_NpcHasCloaked[chaserBoss.Index]) - { - speed *= chaserBoss.GetCloakSpeedMultiplier(difficulty); - maxSpeed *= chaserBoss.GetCloakSpeedMultiplier(difficulty); - } - if (g_NpcIsCrawling[chaserBoss.Index]) - { - walkSpeed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - maxWalkSpeed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - speed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - maxSpeed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - } - Action action = Plugin_Continue; - float forwardSpeed = walkSpeed; - Call_StartForward(g_OnBossGetWalkSpeedFwd); - Call_PushCell(chaserBoss.Index); - Call_PushFloatRef(forwardSpeed); - Call_Finish(action); - if (action == Plugin_Changed) - { - walkSpeed = forwardSpeed; - } - - action = Plugin_Continue; - forwardSpeed = speed; - Call_StartForward(g_OnBossGetSpeedFwd); - Call_PushCell(chaserBoss.Index); - Call_PushFloatRef(forwardSpeed); - Call_Finish(action); - if (action == Plugin_Changed) - { - speed = forwardSpeed; - } - - if (difficulty >= RoundToNearest(chaserBoss.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty))) - { - g_SlenderCalculatedWalkSpeed[chaserBoss.Index] = walkSpeed; - } - else - { - g_SlenderCalculatedWalkSpeed[chaserBoss.Index] = 0.0; - } - - if (!g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - g_SlenderCalculatedSpeed[chaserBoss.Index] = speed; - } - else - { - g_SlenderCalculatedSpeed[chaserBoss.Index] = 0.0; - } - g_SlenderCalculatedAcceleration[chaserBoss.Index] = acceleration; - g_SlenderCalculatedMaxSpeed[chaserBoss.Index] = maxSpeed; - g_SlenderCalculatedMaxWalkSpeed[chaserBoss.Index] = maxWalkSpeed; - - int oldState = g_SlenderState[chaserBoss.Index]; - int oldTarget = EntRefToEntIndex(g_SlenderTarget[chaserBoss.Index]); - - int bestNewTarget = INVALID_ENT_REFERENCE; - int soundTarget = INVALID_ENT_REFERENCE; - float searchRange = NPCGetSearchRadius(chaserBoss.Index, difficulty); - float searchSoundRange = NPCGetHearingRadius(chaserBoss.Index, difficulty); - float bestNewTargetDist = SquareFloat(searchRange); - int state = oldState; - - bool playerInFOV[MAXTF2PLAYERS]; - bool playerNear[MAXTF2PLAYERS]; - bool playerMadeNoise[MAXTF2PLAYERS]; - bool playerInTrap[MAXTF2PLAYERS]; - float playerDists[MAXTF2PLAYERS]; - bool playerVisible[MAXTF2PLAYERS]; - - bool attackEliminated = !!(chaserBoss.Flags & SFF_ATTACKWAITERS); - bool stunEnabled = chaserBoss.StunEnabled; - - float traceMins[3] = { -16.0, ... }; - traceMins[2] = 0.0; - float traceMaxs[3] = { 16.0, ... }; - traceMaxs[2] = 0.0; - - char class[32]; - - bool building = false; - if (oldTarget > MaxClients) - { - GetEdictClassname(oldTarget, class, sizeof(class)); - if (strcmp(class, "obj_sentrygun") == 0 && !GetEntProp(oldTarget, Prop_Send, "m_bCarried")) - { - building = true; - } - if (strcmp(class, "obj_dispenser") == 0 && !GetEntProp(oldTarget, Prop_Send, "m_bCarried")) - { - building = true; - } - if (strcmp(class, "obj_teleporter") == 0 && !GetEntProp(oldTarget, Prop_Send, "m_bCarried")) - { - building = true; - } - } - - if (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) - { - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderSoundTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - - if (g_SlenderSoundTarget[chaserBoss.Index] != INVALID_ENT_REFERENCE) - { - soundTarget = EntRefToEntIndex(g_SlenderSoundTarget[chaserBoss.Index]); - } - - bool inFlashlight = false; - bool doubleFlashlightDamage = false; - float customFlashlightDamage = 1.0; - - // Gather data about the players around me and get the best new target, in case my old target is invalidated. - int ent = -1; - while ((ent = FindEntityByClassname(ent, "player")) != -1) - { - SF2_BasePlayer player = SF2_BasePlayer(ent); - if (!IsTargetValidForSlender(player, attackEliminated)) - { - continue; - } - TFClassType classType = player.Class; - int classToInt = view_as(classType); - - float traceStartPos[3], traceEndPos[3]; - chaserBoss.GetEyePosition(traceStartPos); - player.GetEyePosition(traceEndPos); - - float dist = 99999999999.9; - - bool isVisible; - int traceHitEntity; - if (!g_NpcOriginalVisibility[chaserBoss.Index]) - { - Handle trace = TR_TraceRayFilterEx(traceStartPos, - traceEndPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayBossVisibility, - npcEntity.index); - isVisible = !TR_DidHit(trace); - traceHitEntity = TR_GetEntityIndex(trace); - - delete trace; - } - else - { - TR_TraceHullFilter(traceStartPos, - traceEndPos, - traceMins, - traceMaxs, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - TraceRayBossVisibility, - npcEntity.index); - - isVisible = !TR_DidHit(); - traceHitEntity = TR_GetEntityIndex(); - } - - if (!isVisible && traceHitEntity == player.index) - { - isVisible = true; - } - - if (isVisible) - { - isVisible = NPCShouldSeeEntity(chaserBoss.Index, player.index); - } - - if (g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) - { - int fogEntity = player.GetDataEnt(g_PlayerFogCtrlOffset); - if (IsValidEdict(fogEntity)) - { - if (GetEntData(fogEntity, g_FogCtrlEnableOffset) && - GetVectorSquareMagnitude(traceStartPos, traceEndPos) >= SquareFloat(GetEntDataFloat(fogEntity, g_FogCtrlEndOffset))) - { - isVisible = false; - } - } - } - - float priorityValue; - - playerVisible[player.index] = isVisible; - - // Near radius check. - if (!g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && playerVisible[player.index] && - GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(NPCChaserGetWakeRadius(chaserBoss.Index))) - { - playerNear[player.index] = true; - } - if (playerVisible[player.index] && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) - { - TF2_StunPlayer(player.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); - } - - // FOV check. - SubtractVectors(traceEndPos, traceStartPos, buffer); - GetVectorAngles(buffer, buffer); - - if (FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (NPCGetFOV(chaserBoss.Index) * 0.5)) - { - playerInFOV[player.index] = true; - } - - if (!SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !g_SlenderChasesEndlessly[chaserBoss.Index]) - { - priorityValue = g_PageMax > 0 ? ((float(player.PageCount) / float(g_PageMax))/4.0) : 0.0; - } - - if (!IsClassConfigsValid()) - { - if ((classType == TFClass_Medic || player.HasRegenItem) && !SF_IsBoxingMap()) - { - priorityValue += 0.2; - } - - if (classType == TFClass_Spy) - { - priorityValue += 0.1; - } - } - else - { - if (!SF_IsBoxingMap() && player.HasRegenItem) - { - priorityValue += 0.2; - } - priorityValue += g_ClassBossPriorityMultiplier[classToInt]; - } - - //Taunt alerts - if (!g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && state != STATE_ALERT && state != STATE_ATTACK && state != STATE_STUN && state != STATE_CHASE && - GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(NPCGetTauntAlertRange(chaserBoss.Index, difficulty)) && player.InCondition(TFCond_Taunting)) - { - if (g_SlenderTauntAlertCount[chaserBoss.Index] < 5) - { - float targetPos[3]; - if (player.IsValid) - { - player.GetAbsOrigin(targetPos); - } - bestNewTarget = player.index; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - chaserBoss.SetGoalPos(targetPos); - g_SlenderAutoChaseCount[chaserBoss.Index] += NPCChaserAutoChaseAddVoice(chaserBoss.Index, difficulty) * 2; - state = STATE_ALERT; - g_SlenderTauntAlertCount[chaserBoss.Index]++; - } - else - { - playerMadeNoise[player.index] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - g_SlenderTauntAlertCount[chaserBoss.Index] = 0; - } - } - - //Trap check - if (g_PlayerTrapped[player.index] && GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(searchRange)) - { - if (state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - playerInTrap[player.index] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - } - } - - if (NPCChaserCanAutoChaseSprinters(chaserBoss.Index) && IsClientReallySprinting(ent) && GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(searchSoundRange) && gameTime >= g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - playerMadeNoise[player.index] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - } - - if (NPCChaserIsAutoChaseEnabled(chaserBoss.Index)) - { - if (IsValidClient(soundTarget) && g_SlenderAutoChaseCount[chaserBoss.Index] >= NPCChaserAutoChaseThreshold(chaserBoss.Index, difficulty) && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - playerMadeNoise[soundTarget] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - } - } - - dist = GetVectorSquareMagnitude(traceStartPos, traceEndPos); - playerDists[player.index] = dist; - - if (!g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && playerVisible[ent] && (playerNear[ent] || playerInFOV[ent])) - { - float targetPos[3]; - player.GetAbsOrigin(targetPos); - if (!g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] && !g_NpcIgnoreNonMarkedForChase[chaserBoss.Index]) - { - if (dist <= SquareFloat(searchRange)) - { - // Subtract distance to increase priority. - dist -= ((dist * priorityValue)); - - if (dist < bestNewTargetDist) - { - bestNewTarget = player.index; - bestNewTargetDist = dist; - g_SlenderInterruptConditions[chaserBoss.Index] |= COND_SAWENEMY; - g_SlenderSeeTarget[chaserBoss.Index] = EntIndexToEntRef(player.index); - } - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][0] = targetPos[0]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][1] = targetPos[1]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][2] = targetPos[2]; - g_SlenderLastFoundPlayer[chaserBoss.Index][player.index] = gameTime; - } - } - else if ((g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] && g_NpcIgnoreNonMarkedForChase[chaserBoss.Index]) || NPCChaserCanChaseOnLook(chaserBoss.Index)) - { - if (g_NpcChaseOnLookTarget[chaserBoss.Index].Length > 0 && g_NpcChaseOnLookTarget[chaserBoss.Index].FindValue(ent) != -1 && - dist <= SquareFloat(searchRange)) - { - // Subtract distance to increase priority. - dist -= ((dist * priorityValue)); - - if (dist < bestNewTargetDist) - { - bestNewTarget = player.index; - bestNewTargetDist = dist; - g_SlenderInterruptConditions[chaserBoss.Index] |= COND_SAWENEMY; - g_SlenderSeeTarget[chaserBoss.Index] = EntIndexToEntRef(player.index); - } - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][0] = targetPos[0]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][1] = targetPos[1]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][2] = targetPos[2]; - g_SlenderLastFoundPlayer[chaserBoss.Index][player.index] = gameTime; - } - } - } - - if ((playerMadeNoise[player.index] || playerInTrap[player.index]) && state != STATE_CHASE && state != STATE_STUN && state != STATE_ATTACK) - { - if (g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && playerMadeNoise[player.index]) - { - bestNewTarget = player.index; - state = STATE_CHASE; - SlenderAlertAllValidBosses(chaserBoss.Index, player.index, bestNewTarget); - player.GetAbsOrigin(g_SlenderGoalPos[chaserBoss.Index]); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderGiveUp[chaserBoss.Index] = false; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = true; - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - } - else if (playerInTrap[player.index] || (!g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && playerMadeNoise[ent])) - { - bestNewTarget = player.index; - state = STATE_CHASE; - SlenderAlertAllValidBosses(chaserBoss.Index, player.index, bestNewTarget); - player.GetAbsOrigin(g_SlenderGoalPos[chaserBoss.Index]); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderGiveUp[chaserBoss.Index] = false; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - } - } - - if (chaserBoss.StunByFlashlightEnabled && IsClientUsingFlashlight(ent) && playerInFOV[ent]) // Check to see if someone is facing at us with flashlight on. Only if I'm facing them too. BLINDNESS! - { - if (GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(SF2_FLASHLIGHT_LENGTH)) - { - float eyeAng[3], requiredAng[3]; - player.GetEyeAngles(eyeAng); - SubtractVectors(traceEndPos, traceStartPos, requiredAng); - GetVectorAngles(requiredAng, requiredAng); - - if (FloatAbs(AngleDiff(eyeAng[0], requiredAng[0])) <= 45.0 && FloatAbs(AngleDiff(requiredAng[1], eyeAng[1])) >= 135.0) - { - if (playerVisible[player.index]) - { - inFlashlight = true; - if (!IsClassConfigsValid()) - { - if (classType == TFClass_Engineer) - { - doubleFlashlightDamage = true; - } - } - else - { - customFlashlightDamage = g_ClassFlashlightDamageMultiplier[classToInt]; - } - } - } - } - } - if (NPCChaserCanChaseOnLook(chaserBoss.Index) && playerInFOV[player.index] && playerVisible[player.index]) - { - float eyeAng[3], requiredAng[3]; - player.GetEyeAngles(eyeAng); - SubtractVectors(traceEndPos, traceStartPos, requiredAng); - GetVectorAngles(requiredAng, requiredAng); - if (FloatAbs(AngleDiff(eyeAng[0], requiredAng[0])) <= 45.0 && FloatAbs(AngleDiff(requiredAng[1], eyeAng[1])) >= 135.0) - { - if (g_NpcChaseOnLookTarget[chaserBoss.Index].FindValue(player.index) == -1) - { - g_NpcChaseOnLookTarget[chaserBoss.Index].Push(player.index); - SF2BossProfileSoundInfo soundInfo; - GetBossProfileScareSounds(slenderProfile, soundInfo); - soundInfo.EmitSound(true, player.index); - player.ChangeCondition(TFCond_MarkedForDeathSilent); - if (NPCChaserCanUseChaseInitialAnimation(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, STATE_CHASE); - g_SlenderChaseInitialTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } - - if (IsValidClient(EntRefToEntIndex(g_SlenderTarget[chaserBoss.Index])) && playerVisible[EntRefToEntIndex(g_SlenderTarget[chaserBoss.Index])]) - { - g_SlenderTargetIsVisible[chaserBoss.Index] = true; - } - else - { - g_SlenderTargetIsVisible[chaserBoss.Index] = false; - } - - if (g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index]) - { - if (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0) - { - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - g_SlenderSoundTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - soundTarget = INVALID_ENT_REFERENCE; - } - } - - // Damage us if we're in a flashlight. - if (inFlashlight) - { - if (stunEnabled) - { - if (chaserBoss.StunByFlashlightEnabled && !(SF_SpecialRound(SPECIALROUND_NIGHTVISION)) && !(g_NightvisionEnabledConVar.BoolValue)) - { - if (chaserBoss.StunHealth > 0) - { - if (!IsClassConfigsValid()) - { - if (!doubleFlashlightDamage) - { - chaserBoss.AddStunHealth(-chaserBoss.StunFlashlightDamage); - } - else - { - chaserBoss.AddStunHealth(-chaserBoss.StunFlashlightDamage * 1.5); - } - } - else - { - chaserBoss.AddStunHealth(-chaserBoss.StunFlashlightDamage * customFlashlightDamage); - } - if (chaserBoss.StunHealth <= 0.0 && state != STATE_STUN) - { - chaserBoss.TriggerStun(npcEntity.index, slenderProfile, myPos); - Call_StartForward(g_OnBossStunnedFwd); - Call_PushCell(chaserBoss.Index); - Call_PushCell(-1); - Call_Finish(); - state = STATE_STUN; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - } - } - } - } - - // Process the target that we should have. - int target = oldTarget; - - if (target && target != INVALID_ENT_REFERENCE) - { - if (!IsTargetValidForSlender(SF2_BasePlayer(target), attackEliminated)) - { - // Clear our target; he's not valid anymore. - oldTarget = target; - target = INVALID_ENT_REFERENCE; - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - } - else - { - // Clear our target; he's not valid anymore. - oldTarget = target; - target = INVALID_ENT_REFERENCE; - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - - if (building) - { - target = bestNewTarget; - } - - //We should never give up, but sometimes it happens. - if (g_SlenderGiveUp[chaserBoss.Index]) - { - //Damit our target is unreachable for some unexplained reasons, haaaaaaaaaaaa! - if (!SF_IsRaidMap() || !SF_BossesChaseEndlessly() || !SF_IsProxyMap() || !g_SlenderChasesEndlessly[chaserBoss.Index] || !SF_IsBoxingMap() || !SF_IsSlaughterRunMap()) - { - state = STATE_IDLE; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - g_SlenderGiveUp[chaserBoss.Index] = false; - } - - if ((SF_BossesChaseEndlessly() || g_SlenderChasesEndlessly[chaserBoss.Index] || SF_IsSlaughterRunMap() || SF_IsRaidMap() || SF_IsProxyMap() || SF_IsBoxingMap()) && !(chaserBoss.Flags & SFF_NOTELEPORT)) - { - //RemoveSlender(chaserBoss.Index); - } - //Do not, ok? - g_SlenderGiveUp[chaserBoss.Index] = false; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - - int interruptConditions = g_SlenderInterruptConditions[chaserBoss.Index]; - - if ((SF_IsRaidMap() || (SF_BossesChaseEndlessly() || g_SlenderChasesEndlessly[chaserBoss.Index]) || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) && !g_SlenderGiveUp[chaserBoss.Index] && !building && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (!IsValidClient(target) || (IsValidClient(target) && g_PlayerEliminated[target])) - { - if (state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN && NPCAreAvailablePlayersAlive()) - { - ArrayList arrayRaidTargets = new ArrayList(); - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || - !IsPlayerAlive(i) || - g_PlayerEliminated[i] || - IsClientInGhostMode(i) || - DidClientEscape(i)) - { - continue; - } - arrayRaidTargets.Push(i); - } - if(arrayRaidTargets.Length > 0) - { - int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); - if(IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) - { - bestNewTarget = raidTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - target = bestNewTarget; - } - } - delete arrayRaidTargets; - } - } - } - - if (NPCChaserCanChaseOnLook(chaserBoss.Index)) - { - if (state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN && NPCAreAvailablePlayersAlive() && g_NpcChaseOnLookTarget[chaserBoss.Index].Length != 0) - { - for (int i = 0; i < g_NpcChaseOnLookTarget[chaserBoss.Index].Length; i++) - { - int lookClient = g_NpcChaseOnLookTarget[chaserBoss.Index].Get(i); - if (IsValidClient(lookClient) && !g_PlayerEliminated[lookClient] && IsPlayerAlive(lookClient) && IsClientInGame(lookClient) && - !IsClientInGhostMode(lookClient) && !DidClientEscape(lookClient)) - { - bestNewTarget = lookClient; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(lookClient); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - target = bestNewTarget; - SlenderAlertAllValidBosses(chaserBoss.Index, target, bestNewTarget); - } - else - { - i--; - } - } - } - } - - // Process which state we should be in. - switch (state) - { - case STATE_IDLE, STATE_WANDER: - { - for (int i = 0; i < MAX_NPCTELEPORTER; i++) - { - if (chaserBoss.GetTeleporter(i) != INVALID_ENT_REFERENCE) - { - chaserBoss.SetTeleporter(i, INVALID_ENT_REFERENCE); - } - } - if (state == STATE_WANDER) - { - if ((!g_BossPathFollower[chaserBoss.Index].IsValid() && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) || g_SlenderNextWanderPos[chaserBoss.Index][difficulty] < gameTime) - { - float min = GetChaserProfileWanderEnterTimeMin(slenderProfile, difficulty); - float max = GetChaserProfileWanderEnterTimeMax(slenderProfile, difficulty); - g_SlenderNextWanderPos[chaserBoss.Index][difficulty] = gameTime + GetRandomFloat(min, max); - state = STATE_IDLE; - } - } - else - { - if ((chaserBoss.Flags & SFF_WANDERMOVE) && gameTime >= g_SlenderNextWanderPos[chaserBoss.Index][difficulty] && GetRandomFloat(0.0, 1.0) <= 0.25 && - difficulty >= RoundToNearest(chaserBoss.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)) && NPCGetWanderPosition(chaserBoss)) - { - float min = GetChaserProfileWanderTimeMin(slenderProfile, difficulty); - float max = GetChaserProfileWanderTimeMax(slenderProfile, difficulty); - g_SlenderNextWanderPos[chaserBoss.Index][difficulty] = gameTime + GetRandomFloat(min, max); - state = STATE_WANDER; - } - } - if (SF_SpecialRound(SPECIALROUND_BEACON) || g_RenevantBeaconEffect) - { - if (!g_SlenderInBacon[chaserBoss.Index]) - { - SF2_BasePlayer clientToPos = NPCChaserGetClosestPlayer(npcEntity.index); - float clientPosition[3]; - if (clientToPos.IsValid) - { - clientToPos.GetAbsOrigin(clientPosition); - } - bestNewTarget = clientToPos.index; - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - chaserBoss.SetGoalPos(clientPosition); - g_SlenderInBacon[chaserBoss.Index] = true; - } - } - if (interruptConditions & COND_SAWENEMY) - { - if (NPCChaserIsAutoChaseEnabled(chaserBoss.Index)) - { - g_SlenderAutoChaseCount[chaserBoss.Index] += NPCChaserAutoChaseAddGeneral(chaserBoss.Index, difficulty); - } - // I saw someone over here. Automatically put me into alert mode. - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - int clientToPos = EntRefToEntIndex(g_SlenderSeeTarget[chaserBoss.Index]); - float clientPosition[3]; - if (IsValidClient(clientToPos)) - { - GetClientAbsOrigin(clientToPos, clientPosition); - bestNewTarget = clientToPos; - chaserBoss.SetGoalPos(clientPosition); - } - } - else if (interruptConditions & COND_HEARDSUSPICIOUSSOUND) - { - // Sound counts will be reset after the boss hears a sound after a certain amount of time. - // The purpose of sound counts is to induce boss focusing on sounds suspicious entities are making. - - int count = 0; - if (interruptConditions & COND_HEARDFOOTSTEP) - { - count += GetChaserProfileFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPLOUD) - { - count += GetChaserProfileLoudFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPQUIET) - { - count += GetChaserProfileQuietFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDWEAPON) - { - count += GetChaserProfileWeaponAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFLASHLIGHT) - { - count += GetChaserProfileFlashlightAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDVOICE) - { - count += GetChaserProfileVoiceAddThreshold(slenderProfile, difficulty); - } - - bool discardMasterPos = gameTime >= g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index]; - - if (discardMasterPos) - { - g_SlenderTargetSoundCount[chaserBoss.Index] = 0; - } - - if ((GetVectorSquareMagnitude(g_SlenderTargetSoundTempPos[chaserBoss.Index], g_SlenderTargetSoundMasterPos[chaserBoss.Index]) <= SquareFloat(GetChaserProfileSoundPosDistanceTolerance(slenderProfile, difficulty)) || - discardMasterPos) && count > 0) - { - g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index] = gameTime + GetChaserProfileSoundPosDiscardTime(slenderProfile, difficulty); - g_SlenderTargetSoundMasterPos[chaserBoss.Index][0] = g_SlenderTargetSoundTempPos[chaserBoss.Index][0]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][1] = g_SlenderTargetSoundTempPos[chaserBoss.Index][1]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][2] = g_SlenderTargetSoundTempPos[chaserBoss.Index][2]; - g_SlenderTargetSoundCount[chaserBoss.Index] += count; - } - if (g_SlenderTargetSoundCount[chaserBoss.Index] >= NPCChaserGetSoundCountToAlert(chaserBoss.Index, difficulty)) - { - // Someone's making some noise over there! Time to investigate. - g_SlenderInvestigatingSound[chaserBoss.Index] = true; // This is just so that our sound position would be the goal position. - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - } - } - if (NPCChaserGetTrapState(chaserBoss.Index)) - { - if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount < 32) - { - Trap_SpawnTrap(myPos, myEyeAng, chaserBoss.Index); - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - else if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount >= 32) - { - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - } - } - case STATE_ALERT: - { - if (state == STATE_ALERT) - { - if (!g_BossPathFollower[chaserBoss.Index].IsValid() && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) - { - if (!(interruptConditions & COND_HEARDSUSPICIOUSSOUND)) - { - state = STATE_IDLE; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - else if ((interruptConditions & COND_HEARDSUSPICIOUSSOUND) && GetVectorSquareMagnitude(g_SlenderGoalPos[chaserBoss.Index], myPos) <= SquareFloat(20.0)) - { - state = STATE_IDLE; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - } - } - if (gameTime >= g_SlenderTimeUntilIdle[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && !g_NpcIsRunningToHeal[chaserBoss.Index]) - { - state = STATE_IDLE; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - else if (IsValidClient(bestNewTarget)) - { - if (gameTime >= g_SlenderTimeUntilChase[chaserBoss.Index] || playerNear[bestNewTarget]) - { - float traceStartPos[3], traceEndPos[3]; - NPCGetEyePosition(chaserBoss.Index, traceStartPos); - - if (IsValidClient(bestNewTarget)) - { - GetClientEyePosition(bestNewTarget, traceEndPos); - } - else - { - float targetMins[3], targetMaxs[3]; - GetEntPropVector(bestNewTarget, Prop_Send, "m_vecMins", targetMins); - GetEntPropVector(bestNewTarget, Prop_Send, "m_vecMaxs", targetMaxs); - GetEntPropVector(bestNewTarget, Prop_Data, "m_vecAbsOrigin", traceEndPos); - for (int i = 0; i < 3; i++) - { - traceEndPos[i] += ((targetMins[i] + targetMaxs[i]) / 2.0); - } - } - - Handle trace = TR_TraceRayFilterEx(traceStartPos, - traceEndPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayBossVisibility, - npcEntity.index); - - bool isVisible = !TR_DidHit(trace); - int traceHitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (!isVisible && traceHitEntity == bestNewTarget) - { - isVisible = true; - } - - if (building || ((playerNear[bestNewTarget] || playerInFOV[bestNewTarget]) && playerVisible[bestNewTarget])) - { - // AHAHAHAH! I GOT YOU NOW! - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - SlenderAlertAllValidBosses(chaserBoss.Index, target, bestNewTarget); - } - } - if (playerInTrap[bestNewTarget] || playerMadeNoise[bestNewTarget]) - { - // AHAHAHAH! I GOT YOU NOW! - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - SlenderAlertAllValidBosses(chaserBoss.Index, target, bestNewTarget); - } - } - if ((building)) - { - state = STATE_CHASE; - } - else - { - if (interruptConditions & COND_SAWENEMY) - { - if (IsValidClient(bestNewTarget)) - { - chaserBoss.SetGoalPos(g_SlenderLastFoundPlayerPos[chaserBoss.Index][bestNewTarget]); - } - } - else if (interruptConditions & COND_HEARDSUSPICIOUSSOUND) - { - int count = 0; - if (interruptConditions & COND_HEARDFOOTSTEP) - { - count += GetChaserProfileFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPLOUD) - { - count += GetChaserProfileLoudFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPQUIET) - { - count += GetChaserProfileQuietFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDWEAPON) - { - count += GetChaserProfileWeaponAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFLASHLIGHT) - { - count += GetChaserProfileFlashlightAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDVOICE) - { - count += GetChaserProfileVoiceAddThreshold(slenderProfile, difficulty); - } - bool discardMasterPos = gameTime >= g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index]; - - if ((GetVectorSquareMagnitude(g_SlenderTargetSoundTempPos[chaserBoss.Index], g_SlenderTargetSoundMasterPos[chaserBoss.Index]) <= SquareFloat(GetChaserProfileSoundPosDistanceTolerance(slenderProfile, difficulty)) || - discardMasterPos) && count > 0) - { - g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index] = gameTime + GetChaserProfileSoundPosDiscardTime(slenderProfile, difficulty); - g_SlenderTargetSoundMasterPos[chaserBoss.Index][0] = g_SlenderTargetSoundTempPos[chaserBoss.Index][0]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][1] = g_SlenderTargetSoundTempPos[chaserBoss.Index][1]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][2] = g_SlenderTargetSoundTempPos[chaserBoss.Index][2]; - - // We have to manually set the goal position here because the goal position will not be changed due to no change in state. - chaserBoss.SetGoalPos(g_SlenderTargetSoundMasterPos[chaserBoss.Index]); - - g_SlenderInvestigatingSound[chaserBoss.Index] = true; - } - } - - for (int attackIndex = 0; attackIndex < NPCChaserGetAttackCount(chaserBoss.Index); attackIndex++) - { - if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_LaserBeam) - { - continue; - } - - bool blockingProp = false; - - if (chaserBoss.Flags & SFF_ATTACKPROPS) - { - blockingProp = NPC_CanAttackProps(chaserBoss.Index, NPCChaserGetAttackRange(chaserBoss.Index, attackIndex, difficulty), NPCChaserGetAttackSpread(chaserBoss.Index, attackIndex, difficulty)); - } - - if (blockingProp) - { - state = STATE_ATTACK; - NPCSetCurrentAttackIndex(chaserBoss.Index, attackIndex); - break; - } - } - } - if (NPCChaserGetTrapState(chaserBoss.Index)) - { - if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount < 32) - { - Trap_SpawnTrap(myPos, myEyeAng, chaserBoss.Index); - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - else if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount >= 32) - { - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - } - } - case STATE_CHASE, STATE_ATTACK, STATE_STUN: - { - if (state == STATE_CHASE) - { - if (IsValidEntity(target)) - { - float traceStartPos[3], traceEndPos[3]; - NPCGetEyePosition(chaserBoss.Index, traceStartPos); - if (chaserBoss.CloakEnabled) - { - float cloakDist = GetVectorSquareMagnitude(g_SlenderGoalPos[chaserBoss.Index], myPos); - float cloakRange = chaserBoss.GetCloakRange(difficulty); - float decloakRange = chaserBoss.GetDecloakRange(difficulty); - if (cloakDist <= SquareFloat(cloakRange) && !g_NpcHasCloaked[chaserBoss.Index] && g_SlenderNextCloakTime[chaserBoss.Index] <= gameTime && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index]) - { - // Time for a more cloaking aproach! - SetEntityRenderMode(npcEntity.index, view_as(GetChaserProfileCloakRenderMode(slenderProfile))); - - int cloakColor[4]; - GetChaserProfileCloakRenderColor(slenderProfile, cloakColor); - - SetEntityRenderColor(npcEntity.index, cloakColor[0], cloakColor[1], cloakColor[2], cloakColor[3]); - g_NpcNextDecloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakDuration(chaserBoss.Index, difficulty); - SlenderToggleParticleEffects(npcEntity.index); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOnSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_NpcHasCloaked[chaserBoss.Index] = true; - Call_StartForward(g_OnBossCloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - if ((cloakDist <= SquareFloat(decloakRange) || gameTime >= g_NpcNextDecloakTime[chaserBoss.Index]) && g_NpcHasCloaked[chaserBoss.Index]) - { - //Come back now! - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], g_SlenderRenderColor[chaserBoss.Index][3]); - - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - g_NpcHasCloaked[chaserBoss.Index] = false; - } - } - - if (chaserBoss.ProjectileEnabled) - { - if (chaserBoss.Flags & SFF_FAKE) - { - SlenderMarkAsFake(chaserBoss.Index); - } - if (g_NpcProjectileCooldown[chaserBoss.Index] <= gameTime && !NPCChaserUseProjectileAmmo(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && (building || (playerInFOV[target] && CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target)))) && !g_NpcHasCloaked[chaserBoss.Index]) - { - NPCChaserProjectileShoot(chaserBoss.Index, npcEntity.index, target, slenderProfile, myPos); - } - else if (chaserBoss.ProjectileUsesAmmo) - { - if (g_NpcProjectileCooldown[chaserBoss.Index] <= gameTime && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && (building || (playerInFOV[target] && CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target)))) && !g_NpcHasCloaked[chaserBoss.Index] && (g_NpcProjectileAmmo[chaserBoss.Index] != 0)) - { - NPCChaserProjectileShoot(chaserBoss.Index, npcEntity.index, target, slenderProfile, myPos); - g_NpcProjectileAmmo[chaserBoss.Index] = g_NpcProjectileAmmo[chaserBoss.Index] - 1; - } - if (g_NpcProjectileAmmo[chaserBoss.Index] == 0 && !g_NpcReloadingProjectiles[chaserBoss.Index]) - { - g_NpcProjectileTimeToReload[chaserBoss.Index] = gameTime + NPCChaserGetProjectileReloadTime(chaserBoss.Index, difficulty); - g_NpcReloadingProjectiles[chaserBoss.Index] = true; - } - if (g_NpcProjectileTimeToReload[chaserBoss.Index] <= gameTime && g_NpcProjectileAmmo[chaserBoss.Index] == 0) - { - g_NpcProjectileAmmo[chaserBoss.Index] = NPCChaserGetLoadedProjectiles(chaserBoss.Index, difficulty); - g_NpcReloadingProjectiles[chaserBoss.Index] = false; - } - } - } - - if (IsValidClient(target)) - { - GetClientEyePosition(target, traceEndPos); - } - else - { - float targetMins[3], targetMaxs[3]; - GetEntPropVector(target, Prop_Send, "m_vecMins", targetMins); - GetEntPropVector(target, Prop_Send, "m_vecMaxs", targetMaxs); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", traceEndPos); - for (int i = 0; i < 3; i++) - { - traceEndPos[i] += ((targetMins[i] + targetMaxs[i]) / 2.0); - } - } - - bool isDeathPosVisible = false; - - if (g_SlenderChaseDeathPositionBool[chaserBoss.Index]) - { - Handle trace = TR_TraceRayFilterEx(traceStartPos, - g_SlenderChaseDeathPosition[chaserBoss.Index], - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayBossVisibility, - npcEntity.index); - isDeathPosVisible = !TR_DidHit(trace); - delete trace; - } - - if (!building && !playerVisible[target] && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (gameTime >= g_SlenderTimeUntilAlert[chaserBoss.Index] || (!attackEliminated && g_PlayerEliminated[target])) - { - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - g_NpcInAutoChase[chaserBoss.Index] = false; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - else if (isDeathPosVisible || (!attackEliminated && g_PlayerEliminated[target])) - { - state = STATE_IDLE; - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - g_NpcInAutoChase[chaserBoss.Index] = false; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else if ((building || CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target))) && !NPCIsRaging(chaserBoss.Index) && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; // We're not chasing a dead player after all! Reset. - - float attackDirection[3], attackEyePos[3], clientPosAttack[3], attackDist; - NPCGetEyePosition(chaserBoss.Index, attackEyePos); - if (IsValidClient(target)) - { - GetClientEyePosition(target, clientPosAttack); - } - else - { - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", clientPosAttack); - clientPosAttack[2] += 20.0; - } - attackDist = GetVectorSquareMagnitude(myPos, g_SlenderGoalPos[chaserBoss.Index]); - SubtractVectors(clientPosAttack, attackEyePos, attackDirection); - GetVectorAngles(attackDirection, attackDirection); - attackDirection[2] = 180.0; - - float attackBeginRangeEx, attackBeginFOVEx; - - float fov = FloatAbs(AngleDiff(attackDirection[1], myEyeAng[1])); - float clientHealth = float(GetEntProp(target, Prop_Send, "m_iHealth")); - ArrayList arrayAttacks = new ArrayList(); - for (int attackIndex2 = 0; attackIndex2 < NPCChaserGetAttackCount(chaserBoss.Index); attackIndex2++) - { - if ((difficulty >= NPCChaserGetAttackUseOnDifficulty(chaserBoss.Index, attackIndex2) && difficulty < NPCChaserGetAttackBlockOnDifficulty(chaserBoss.Index, attackIndex2)) && - NPCChaserGetNextAttackTime(chaserBoss.Index, attackIndex2) <= gameTime) - { - arrayAttacks.Push(attackIndex2); - } - if (IsValidClient(target) && (NPCChaserGetAttackUseOnHealth(chaserBoss.Index, attackIndex2) != -1.0 && clientHealth < NPCChaserGetAttackUseOnHealth(chaserBoss.Index, attackIndex2)) || - (NPCChaserGetAttackBlockOnHealth(chaserBoss.Index, attackIndex2) != -1.0 && clientHealth >= NPCChaserGetAttackBlockOnHealth(chaserBoss.Index, attackIndex2))) - { - int eraseAttack = arrayAttacks.FindValue(attackIndex2); - if (eraseAttack != -1) - { - arrayAttacks.Erase(eraseAttack); - } - } - if (g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && - NPCChaserGetAttackInterruptChaseInitialState(chaserBoss.Index, attackIndex2, difficulty)) - { - int eraseAttack = arrayAttacks.FindValue(attackIndex2); - if (eraseAttack != -1) - { - arrayAttacks.Erase(eraseAttack); - } - } - } - Call_StartForward(g_OnBossPreAttackFwd); - Call_PushCell(chaserBoss.Index); - Call_PushCell(arrayAttacks); - Call_Finish(); - if (arrayAttacks != null && arrayAttacks.Length > 0) - { - int randomAttackIndex = arrayAttacks.Get(GetRandomInt(0, arrayAttacks.Length - 1)); - attackBeginRangeEx = NPCChaserGetAttackBeginRange(chaserBoss.Index, randomAttackIndex, difficulty); - attackBeginFOVEx = NPCChaserGetAttackBeginFOV(chaserBoss.Index, randomAttackIndex, difficulty); - if (attackDist <= SquareFloat(attackBeginRangeEx) && fov <= (attackBeginFOVEx / 2.0) && - !NPCIsRaging(chaserBoss.Index) && - !g_NpcUseStartFleeAnimation[chaserBoss.Index] && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - state = STATE_ATTACK; - NPCSetCurrentAttackIndex(chaserBoss.Index, randomAttackIndex); - } - } - if (arrayAttacks != null) - { - delete arrayAttacks; - } - } - if (state == STATE_CHASE) - { - for (int attackIndex = 0; attackIndex < NPCChaserGetAttackCount(chaserBoss.Index); attackIndex++) - { - if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_LaserBeam) - { - continue; - } - - bool blockingProp = false; - - if (chaserBoss.Flags & SFF_ATTACKPROPS) - { - blockingProp = NPC_CanAttackProps(chaserBoss.Index, NPCChaserGetAttackRange(chaserBoss.Index, attackIndex, difficulty), NPCChaserGetAttackSpread(chaserBoss.Index, attackIndex, difficulty)); - } - - if (blockingProp) - { - state = STATE_ATTACK; - NPCSetCurrentAttackIndex(chaserBoss.Index, attackIndex); - break; - } - } - } - } - if ((!IsValidEntity(target) && !IsValidEntity(bestNewTarget)) || (!building && !IsTargetValidForSlender(SF2_BasePlayer(target), attackEliminated)) && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - // Even if the target isn't valid anymore, see if I still have some ways to go on my current path, - // because I shouldn't actually know that the target has died until I see it. - if (!g_BossPathFollower[chaserBoss.Index].IsValid()) - { - state = STATE_IDLE; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - } - } - else if (state == STATE_ATTACK) - { - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - int attackIndex = NPCGetCurrentAttackIndex(chaserBoss.Index); - bool cancelLos = GetChaserProfileAttackCancelLOSState(slenderProfile, attackIndex, difficulty); - float cancelDistance = GetChaserProfileAttackCancelDistance(slenderProfile, attackIndex, difficulty); - if (cancelLos) - { - if (!IsValidClient(target) || !playerVisible[target]) - { - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - loco.ClearStuckStatus(); - g_SlenderTimeUntilAttackEnd[chaserBoss.Index] = -1.0; - NPCClearAttackStats(chaserBoss.Index, true); - if (IsValidClient(target)) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - g_SlenderGiveUp[chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - if (cancelDistance > 0.0) - { - float distance = GetVectorSquareMagnitude(myPos, g_SlenderGoalPos[chaserBoss.Index]); - if (distance > SquareFloat(cancelDistance)) - { - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - loco.ClearStuckStatus(); - NPCClearAttackStats(chaserBoss.Index, true); - if (IsValidClient(target)) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - g_SlenderGiveUp[chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - } - if (!g_IsSlenderAttacking[chaserBoss.Index] || (g_SlenderTimeUntilAttackEnd[chaserBoss.Index] != -1.0 && g_SlenderTimeUntilAttackEnd[chaserBoss.Index] <= gameTime)) - { - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - loco.ClearStuckStatus(); - g_SlenderTimeUntilAttackEnd[chaserBoss.Index] = -1.0; - NPCClearAttackStats(chaserBoss.Index); - - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - if (IsValidClient(target)) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - // Chase him again! - g_SlenderGiveUp[chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - // Target isn't valid anymore. We killed him, Mac! - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - else if (state == STATE_STUN) - { - if (gameTime >= g_SlenderTimeUntilRecover[chaserBoss.Index]) - { - g_LastStuckTime[chaserBoss.Index] = 0.0; - if (chaserBoss.CanDisappearOnStun()) - { - RemoveSlender(chaserBoss.Index); - } - loco.ClearStuckStatus(); - float value = chaserBoss.GetAttributeValue(SF2Attribute_AddStunHealhOnStun); - if (value > 0.0) - { - chaserBoss.StunHealth = chaserBoss.InitialStunHealth; - chaserBoss.StunHealthAdd = value; - chaserBoss.AddStunHealth(chaserBoss.StunHealthAdd); - } - else - { - chaserBoss.StunHealth = chaserBoss.InitialStunHealth; - } - value = chaserBoss.GetAttributeValue(SF2Attribute_AddSpeedOnStun); - if (value > 0.0) - { - chaserBoss.SetAddSpeed(value); - } - value = chaserBoss.GetAttributeValue(SF2Attribute_AddMaxSpeedOnStun); - if (value > 0.0) - { - chaserBoss.SetAddMaxSpeed(value); - } - value = chaserBoss.GetAttributeValue(SF2Attribute_AddAccelerationOnStun); - if (value > 0.0) - { - chaserBoss.SetAddAcceleration(value); - } - g_SlenderNextStunTime[chaserBoss.Index] = gameTime + chaserBoss.StunCooldown; - if (IsValidClient(target)) - { - // Chase him again! - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - // WHAT DA FUUUUUUUUUUUQ. TARGET ISN'T VALID. AUSDHASUIHD - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - } - } - - if (state != STATE_STUN) - { - if (stunEnabled) - { - if (chaserBoss.StunHealth <= 0 && g_SlenderNextStunTime[chaserBoss.Index] <= gameTime) - { - state = STATE_STUN; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - if (SF_IsBoxingMap() && NPCChaserIsBoxingBoss(chaserBoss.Index)) - { - if (!NPCChaserGetSelfHealState(chaserBoss.Index)) - { - float percent = chaserBoss.InitialStunHealth > 0 ? (chaserBoss.StunHealth / chaserBoss.InitialStunHealth) : 0.0; - if (percent < 0.75 && percent >= 0.5 && !g_NpcUsesRageAnimation[chaserBoss.Index][0] && !g_NpcUsedRage1[chaserBoss.Index]) - { - g_NpcUsesRageAnimation[chaserBoss.Index][0] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 2 && difficulty < 2) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - - g_NpcUsedRage1[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderRage1Timer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderRageOneTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - else if (percent < 0.5 && percent >= 0.25 && !g_NpcUsesRageAnimation[chaserBoss.Index][1] && !g_NpcUsedRage2[chaserBoss.Index]) - { - g_NpcUsesRageAnimation[chaserBoss.Index][1] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 3 && difficulty < 3) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 3); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageTwoSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageTwo); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - - g_NpcUsedRage2[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderRage2Timer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderRageTwoTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - else if (percent < 0.25 && !g_NpcUsesRageAnimation[chaserBoss.Index][2] && !g_NpcHasUsedRage3[chaserBoss.Index]) - { - g_NpcUsesRageAnimation[chaserBoss.Index][2] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 4 && difficulty < 4) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 4); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritOnFirstBlood, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, CRIT_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 8.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 4: - { - TF2_RegeneratePlayer(client); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageThreeSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageThree); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - - g_NpcHasUsedRage3[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderRage3Timer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderRageThreeTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - } - else - { - float percent = chaserBoss.InitialStunHealth > 0 ? (chaserBoss.StunHealth / chaserBoss.InitialStunHealth) : 0.0; - if (percent < NPCChaserGetStartSelfHealPercentage(chaserBoss.Index) && g_NpcSelfHealStage[chaserBoss.Index] < 3) - { - if (g_NpcIsRunningToHeal[chaserBoss.Index] || g_NpcIsHealing[chaserBoss.Index]) - { - oldTarget = target; - target = INVALID_ENT_REFERENCE; - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - if (g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcSetHealDestination[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - float min = GetChaserProfileHealTimeMin(slenderProfile); - float max = GetChaserProfileHealTimeMax(slenderProfile); - g_NpcFleeHealTimer[chaserBoss.Index] = gameTime + GetRandomFloat(min, max); - - float wanderRangeMin = GetChaserProfileHealRangeMin(slenderProfile); - float wanderRangeMax = GetChaserProfileHealRangeMax(slenderProfile); - float wanderRange = GetRandomFloat(wanderRangeMin, wanderRangeMax); - - CNavArea navArea = npcEntity.GetLastKnownArea(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(navArea, wanderRange); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) - { - continue; - } - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - if (areaArray.Length > 0) - { - float fleePos[3]; - CNavArea fleeArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); - if (fleeArea != NULL_AREA) - { - fleeArea.GetCenter(fleePos); - - chaserBoss.SetGoalPos(fleePos); - } - } - - if (!g_NpcHasCloaked[chaserBoss.Index] && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && chaserBoss.CloakEnabled && NPCChaserCanCloakToHeal(chaserBoss.Index)) - { - // Time for a more cloaking aproach! - SetEntityRenderMode(npcEntity.index, view_as(GetChaserProfileCloakRenderMode(slenderProfile))); - - int cloakColor[4]; - GetChaserProfileCloakRenderColor(slenderProfile, cloakColor); - - SetEntityRenderColor(npcEntity.index, cloakColor[0], cloakColor[1], cloakColor[2], cloakColor[3]); - g_NpcHasCloaked[chaserBoss.Index] = true; - g_NpcNextDecloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakDuration(chaserBoss.Index, difficulty); - SlenderToggleParticleEffects(npcEntity.index); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOnSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - Call_StartForward(g_OnBossCloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - - g_NpcSetHealDestination[chaserBoss.Index] = true; - - delete collector; - delete areaArray; - } - if (!g_BossPathFollower[chaserBoss.Index].IsValid()) - { - g_NpcSetHealDestination[chaserBoss.Index] = false; - } - if ((GetVectorSquareMagnitude(g_SlenderGoalPos[chaserBoss.Index], myPos) < SquareFloat(125.0) || g_NpcFleeHealTimer[chaserBoss.Index] < gameTime) && g_NpcSetHealDestination[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], g_SlenderRenderColor[chaserBoss.Index][3]); - - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - float timerFloat = GetChaserProfileHealAnimTimer(slenderProfile); - g_NpcHealCount[chaserBoss.Index] = 0; - g_NpcUsesHealAnimation[chaserBoss.Index] = true; - g_NpcIsRunningToHeal[chaserBoss.Index] = false; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderHealTimer[chaserBoss.Index] = CreateTimer(timerFloat, Timer_SlenderHealAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_SlenderHealDelayTimer[chaserBoss.Index] = CreateTimer(GetChaserProfileHealFunctionTimer(slenderProfile), Timer_SlenderHealDelayTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcIsHealing[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - } - if (!g_NpcUseStartFleeAnimation[chaserBoss.Index] && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - switch (g_NpcSelfHealStage[chaserBoss.Index]) - { - case 0: - { - g_NpcUseStartFleeAnimation[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 2 && difficulty < 2) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderStartFleeTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderFleeAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcHealCount[chaserBoss.Index] = 0; - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - } - case 1: - { - g_NpcUseStartFleeAnimation[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 3 && difficulty < 3) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 3); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderStartFleeTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderFleeAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcHealCount[chaserBoss.Index] = 0; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageTwoSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageTwo); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - } - case 2: - { - g_NpcUseStartFleeAnimation[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 4 && difficulty < 4) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 4); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritOnFirstBlood, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, CRIT_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 8.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 4: - { - TF2_RegeneratePlayer(client); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderStartFleeTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderFleeAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcHealCount[chaserBoss.Index] = 0; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageThreeSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageThree); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - } - } - } - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - } - } - } - } - - if (!IsValidClient(target) && g_SlenderTeleportTargetIsCamping[chaserBoss.Index] && g_SlenderTeleportTarget[chaserBoss.Index] != INVALID_ENT_REFERENCE) //We spawned, and our target is a camper kill him! - { - int campingTarget = EntRefToEntIndex(g_SlenderTeleportTarget[chaserBoss.Index]); - if (MaxClients >= campingTarget > 0 && IsTargetValidForSlender(SF2_BasePlayer(campingTarget))) - { - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(campingTarget); - state = STATE_CHASE; - GetClientAbsOrigin(campingTarget, g_SlenderGoalPos[chaserBoss.Index]); - } - g_SlenderTeleportTargetIsCamping[chaserBoss.Index] = false; - } - - if (NPCChaserCanChaseOnLook(chaserBoss.Index) && g_NpcChaseOnLookTarget[chaserBoss.Index].Length > 0 && IsValidClient(target) && state != STATE_ATTACK && state != STATE_STUN) - { - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = true; - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - if ((SF_IsRaidMap() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_BossesChaseEndlessly() || g_SlenderChasesEndlessly[chaserBoss.Index] || SF_IsSlaughterRunMap()) && state != STATE_ATTACK && state != STATE_STUN && IsValidClient(target) && !g_SlenderGiveUp[chaserBoss.Index]) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - if ((SF_IsRaidMap() || SF_IsBoxingMap()) && (g_NpcIsRunningToHeal[chaserBoss.Index] || g_NpcIsHealing[chaserBoss.Index])) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - } - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare) && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN && IsValidClient(target) && g_PlayerScaredByBoss[target][chaserBoss.Index] && !g_NpcChasingScareVictin[chaserBoss.Index] && !g_NpcLostChasingScareVictim[chaserBoss.Index]) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_NpcChasingScareVictin[chaserBoss.Index] = true; - g_PlayerScaredByBoss[target][chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - if (IsValidClient(bestNewTarget) && (playerInTrap[bestNewTarget] || playerMadeNoise[bestNewTarget]) && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(target); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - } - // Finally, set our new state. - g_SlenderState[chaserBoss.Index] = state; - - if (oldState != state) - { - g_BossPathFollower[chaserBoss.Index].Invalidate(); - - switch (state) - { - case STATE_IDLE, STATE_WANDER: - { - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - g_SlenderTimeUntilIdle[chaserBoss.Index] = 0.0; - g_SlenderTimeUntilAlert[chaserBoss.Index] = 0.0; - g_SlenderTimeUntilChase[chaserBoss.Index] = 0.0; - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - g_NpcLostChasingScareVictim[chaserBoss.Index] = false; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - - if (oldState != STATE_IDLE && oldState != STATE_WANDER) - { - g_SlenderTargetSoundCount[chaserBoss.Index] = 0; - g_SlenderInvestigatingSound[chaserBoss.Index] = false; - g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index] = -1.0; - - g_SlenderTimeUntilKill[chaserBoss.Index] = gameTime + NPCGetIdleLifetime(chaserBoss.Index, difficulty); - } - - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], 0); - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - } - - case STATE_ALERT: - { - g_SlenderGiveUp[chaserBoss.Index] = false; - - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - // Set our goal position. - if (g_SlenderInvestigatingSound[chaserBoss.Index]) - { - chaserBoss.SetGoalPos(g_SlenderTargetSoundMasterPos[chaserBoss.Index]); - } - - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = -1.0; - g_NpcLostChasingScareVictim[chaserBoss.Index] = false; - g_SlenderTimeUntilChase[chaserBoss.Index] = gameTime + NPCChaserGetAlertGracetime(chaserBoss.Index, difficulty); - - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], 0); - - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - } - case STATE_CHASE, STATE_ATTACK: - { - g_SlenderInvestigatingSound[chaserBoss.Index] = false; - g_SlenderTargetSoundCount[chaserBoss.Index] = 0; - - if (oldState != STATE_ATTACK && oldState != STATE_CHASE && oldState != STATE_STUN) - { - g_SlenderTimeUntilIdle[chaserBoss.Index] = -1.0; - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderTimeUntilChase[chaserBoss.Index] = -1.0; - - float persistencyTime = GetChaserProfileChasePersistencyTimeInit(slenderProfile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + persistencyTime; - } - } - - if (state == STATE_ATTACK) - { - if (NPCIsRaging(chaserBoss.Index)) - { - state = oldState; - } - else - { - g_IsSlenderAttacking[chaserBoss.Index] = true; - npcEntity.RemoveAllGestures(); - CBaseNPC_RemoveAllLayers(npcEntity.index); - loco.ClearStuckStatus(); - int attackIndex = NPCGetCurrentAttackIndex(chaserBoss.Index); - if (!NPCChaserGetAttackWhileRunningState(chaserBoss.Index, attackIndex, difficulty)) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) != SF2BossAttackType_ExplosiveDance && NPCChaserGetAttackType(chaserBoss.Index, attackIndex) != SF2BossAttackType_LaserBeam) - { - g_SlenderAttackTimer[chaserBoss.Index] = CreateTimer(NPCChaserGetAttackDamageDelay(chaserBoss.Index, attackIndex, difficulty), Timer_SlenderChaseBossAttack, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcAlreadyAttacked[chaserBoss.Index] = false; - NpcSetCurrentAttackRepeat(chaserBoss.Index, attackIndex, 0); - NPCChaserSetNextAttackTime(chaserBoss.Index, attackIndex, gameTime + NPCChaserGetAttackCooldown(chaserBoss.Index, attackIndex, difficulty)); - } - else if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_ExplosiveDance) - { - g_SlenderAttackTimer[chaserBoss.Index] = CreateTimer(NPCChaserGetAttackDamageDelay(chaserBoss.Index, attackIndex, difficulty), Timer_SlenderPrepareExplosiveDance, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - NPCChaserSetNextAttackTime(chaserBoss.Index, attackIndex, gameTime + NPCChaserGetAttackCooldown(chaserBoss.Index, attackIndex, difficulty)); - } - else if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_LaserBeam) - { - g_SlenderAttackTimer[chaserBoss.Index] = CreateTimer(NPCChaserGetAttackDamageDelay(chaserBoss.Index, attackIndex, difficulty), Timer_SlenderChaseBossAttackBeginLaser, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - if (NPCChaserGetAttackLifeStealState(chaserBoss.Index, attackIndex, difficulty) && NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Melee) - { - if (!g_NpcStealingLife[chaserBoss.Index]) - { - g_NpcLifeStealTimer[chaserBoss.Index] = CreateTimer(0.5, Timer_SlenderStealLife, EntIndexToEntRef(npcEntity.index), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - g_NpcStealingLife[chaserBoss.Index] = true; - } - } - - g_SlenderTimeUntilAttackEnd[chaserBoss.Index] = gameTime + NPCChaserGetAttackDuration(chaserBoss.Index, attackIndex, difficulty) + 0.01; - - g_NpcBaseAttackRunDurationTime[chaserBoss.Index][attackIndex] = gameTime + NPCChaserGetAttackRunDuration(chaserBoss.Index, attackIndex, difficulty); - - g_NpcBaseAttackRunDelayTime[chaserBoss.Index][attackIndex] = gameTime + NPCChaserGetAttackRunDelay(chaserBoss.Index, attackIndex, difficulty); - - float persistencyTime = GetChaserProfileChaseAttackPersistencyTimeInit(slenderProfile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + persistencyTime; - } - - persistencyTime = GetChaserProfileChaseAttackPersistencyTimeAdd(slenderProfile); - if (persistencyTime >= 0.0) - { - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] < gameTime) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime; - } - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] += persistencyTime; - } - - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], 0); - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - SlenderPerformVoice(chaserBoss.Index, attackIndex, SF2BossSound_Attack); - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - loco.ClearStuckStatus(); - } - } - else - { - if (oldState != STATE_ATTACK && !building) - { - // Sound handling. - if (!NPCChaserCanChaseInitialOnStun(chaserBoss.Index)) - { - if (oldState != STATE_STUN) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0)) - { - g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - g_SlenderChaseInitialTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0)) - { - g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - g_SlenderChaseInitialTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } - } - NPCChaserSetState(chaserBoss.Index, state); - // Call our forward. - Call_StartForward(g_OnBossChangeStateFwd); - Call_PushCell(chaserBoss.Index); - Call_PushCell(oldState); - Call_PushCell(state); - Call_Finish(); - if (NPCChaserNormalSoundHookEnabled(chaserBoss.Index) && state != STATE_WANDER && - !NPCIsRaging(chaserBoss.Index) && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - if (state == STATE_CHASE) - { - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileChaseInitialSounds(slenderProfile, soundInfo); - if (soundInfo.Paths == null || soundInfo.Paths.Length <= 0) - { - g_SlenderNextVoiceSound[chaserBoss.Index] = 0.0; - } - } - else - { - g_SlenderNextVoiceSound[chaserBoss.Index] = 0.0; - } - } - } - - if (oldState != state && !g_SlenderSpawning[chaserBoss.Index]) - { - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - } - - if (g_NpcChangeToCrawl[chaserBoss.Index] && (state == STATE_CHASE || state == STATE_WANDER || state == STATE_ALERT)) - { - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - g_NpcChangeToCrawl[chaserBoss.Index] = false; - } - - if (NPCChaserOldAnimationAIEnabled(chaserBoss.Index)) - { - float slenderVelocity, returnFloat; - slenderVelocity = loco.GetGroundSpeed(); - returnFloat = (slenderVelocity / GetEntPropFloat(npcEntity.index, Prop_Data, "m_flGroundSpeed")) * g_NpcCurrentAnimationSequencePlaybackRate[chaserBoss.Index]; - if (GetEntPropFloat(npcEntity.index, Prop_Data, "m_flGroundSpeed") != 0.0 && GetEntPropFloat(npcEntity.index, Prop_Data, "m_flGroundSpeed") > 10.0) - { - if (loco.IsOnGround()) - { - if (state == STATE_WANDER && (chaserBoss.Flags & SFF_WANDERMOVE) || state == STATE_ALERT) - { - float playbackSpeed = returnFloat; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - if (state == STATE_CHASE && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - float playbackSpeed = returnFloat; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - } - } - else - { - float slenderVelocityOld; - float velocity, walkVelocity, generalVel; - slenderVelocityOld = loco.GetGroundSpeed(); - generalVel = slenderVelocityOld; - if (g_SlenderCalculatedSpeed[chaserBoss.Index] <= 0.0) - { - velocity = 0.0; - } - else - { - velocity = (generalVel + ((g_SlenderCalculatedSpeed[chaserBoss.Index] * g_RoundDifficultyModifier)/15))/g_SlenderCalculatedSpeed[chaserBoss.Index]; - } - - if (g_SlenderCalculatedWalkSpeed[chaserBoss.Index] <= 0.0) - { - walkVelocity = 0.0; - } - else - { - walkVelocity = (generalVel + ((g_SlenderCalculatedWalkSpeed[chaserBoss.Index] * g_RoundDifficultyModifier)/15))/g_SlenderCalculatedWalkSpeed[chaserBoss.Index]; - } - if (loco.IsOnGround()) - { - if (state == STATE_WANDER && (chaserBoss.Flags & SFF_WANDERMOVE) || state == STATE_ALERT) - { - float playbackSpeed = walkVelocity * g_NpcCurrentAnimationSequencePlaybackRate[chaserBoss.Index]; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - if (state == STATE_CHASE && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - float playbackSpeed = velocity * g_NpcCurrentAnimationSequencePlaybackRate[chaserBoss.Index]; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - } - } - } - - switch (state) - { - case STATE_WANDER, STATE_ALERT, STATE_CHASE, STATE_ATTACK: - { - // These deal with movement, therefore we need to set our - // destination first. That is, if we don't have one. (nav mesh only) - - if (state == STATE_WANDER) - { - if (GetVectorSquareMagnitude(myPos, g_SlenderGoalPos[chaserBoss.Index]) <= SquareFloat(64.0)) - { - if (chaserBoss.Flags & SFF_WANDERMOVE) - { - if (!NPCGetWanderPosition(chaserBoss)) - { - g_BossPathFollower[chaserBoss.Index].Invalidate(); - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, STATE_IDLE); - } - } - } - } - else if (state == STATE_ALERT) - { - if (interruptConditions & COND_SAWENEMY) - { - if (IsValidEntity(bestNewTarget)) - { - if ((building) || (((playerInFOV[bestNewTarget] || playerNear[bestNewTarget]) && playerVisible[bestNewTarget]) || (playerMadeNoise[bestNewTarget] || playerInTrap[bestNewTarget]))) - { - // Constantly update my path if I see him. - if (gameTime >= g_SlenderNextPathTime[chaserBoss.Index]) - { - GetEntPropVector(bestNewTarget, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - g_SlenderNextPathTime[chaserBoss.Index] = gameTime + 0.4; - } - } - } - } - } - else if ((state == STATE_CHASE || state == STATE_ATTACK) && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (IsValidEntity(bestNewTarget)) - { - oldTarget = target; - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - } - - if (target != INVALID_ENT_REFERENCE) - { - if (oldTarget != target) - { - // Brand new target! We need a path, and we need to reset our persistency, if needed. - float persistencyTime = GetChaserProfileChaseNewTargetPersistencyTimeInit(slenderProfile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + persistencyTime; - } - - persistencyTime = GetChaserProfileChaseNewTargetPersistencyTimeAdd(slenderProfile); - if (persistencyTime >= 0.0) - { - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] < gameTime) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime; - } - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] += persistencyTime; - } - - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - } - else if ((building) || ((playerInFOV[target] && playerVisible[target]) || gameTime <= g_SlenderTimeUntilNoPersistence[chaserBoss.Index])) - { - // Constantly update my path if I see him or if I'm still being persistent. - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - } - } - if (chaserBoss.GetTeleporter(0) != INVALID_ENT_REFERENCE) - { - int teleporter = EntRefToEntIndex(chaserBoss.GetTeleporter(0)); - if (IsValidEntity(teleporter) && teleporter > MaxClients) - { - GetEntPropVector(teleporter, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - } - } - } - if ((!g_SlenderInDeathcam[chaserBoss.Index] && (state == STATE_WANDER && (chaserBoss.Flags & SFF_WANDERMOVE) && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) - || (state == STATE_ALERT && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) - || (state == STATE_CHASE && g_SlenderCalculatedSpeed[chaserBoss.Index] > 0.0) - || (state == STATE_ATTACK))) - { - g_BossPathFollower[chaserBoss.Index].ComputeToPos(bot, g_SlenderGoalPos[chaserBoss.Index]); - } - if (state == STATE_CHASE || state == STATE_ATTACK) - { - if (IsValidClient(target)) - { - #if defined DEBUG - SendDebugMessageToPlayer(target, DEBUG_BOSS_CHASE, 1, "g_SlenderTimeUntilAlert[%d]: %f\ng_SlenderTimeUntilNoPersistence[%d]: %f", chaserBoss.Index, g_SlenderTimeUntilAlert[chaserBoss.Index] - gameTime, chaserBoss.Index, g_SlenderTimeUntilNoPersistence[chaserBoss.Index] - gameTime); - #endif - - if ((building || (playerInFOV[target] && playerVisible[target])) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index]) - { - float distRatio = (playerDists[target] / SquareFloat(NPCGetSearchRadius(chaserBoss.Index, difficulty))); - - float chaseDurationTimeAddMin = GetChaserProfileChaseDurationAddVisibleMin(slenderProfile); - float chaseDurationTimeAddMax = GetChaserProfileChaseDurationAddVisibleMax(slenderProfile); - - float chaseDurationAdd = chaseDurationTimeAddMax - ((chaseDurationTimeAddMax - chaseDurationTimeAddMin) * distRatio); - - if (chaseDurationAdd > 0.0) - { - g_SlenderTimeUntilAlert[chaserBoss.Index] += chaseDurationAdd; - if (g_SlenderTimeUntilAlert[chaserBoss.Index] > (gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty))) - { - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - } - } - - float persistencyTimeAddMin = GetChaserProfileChasePersistencyAddVisibleMin(slenderProfile); - float persistencyTimeAddMax = GetChaserProfileChasePersistencyAddVisibleMax(slenderProfile); - - float persistencyTimeAdd = persistencyTimeAddMax - ((persistencyTimeAddMax - persistencyTimeAddMin) * distRatio); - - if (persistencyTimeAdd > 0.0) - { - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] < gameTime) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime; - } - - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] += persistencyTimeAdd; - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] > (gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty))) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - } - } - } - } - } - } - } - - // Sound handling. - if (gameTime >= g_SlenderNextVoiceSound[chaserBoss.Index]) - { - switch (state) - { - case STATE_IDLE, STATE_WANDER: - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_Idle); - } - case STATE_ALERT: - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_Alert); - } - case STATE_CHASE: - { - if (!g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index]) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_Chasing); - } - } - } - } - - if (gameTime >= g_SlenderNextFootstepSound[chaserBoss.Index]) - { - SlenderCastFootstep(chaserBoss.Index); - } - - // Reset our interrupt conditions. - g_SlenderInterruptConditions[chaserBoss.Index] = 0; - - return Plugin_Continue; -} - -Action Timer_SlenderPublicDeathCamThink(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_Chaser chaserBoss = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); - if (chaserBoss == SF2_INVALID_NPC_CHASER) - { - return Plugin_Stop; - } - - if (timer != g_SlenderEntityThink[chaserBoss.Index]) - { - return Plugin_Stop; - } - - if (!g_SlenderInDeathcam[chaserBoss.Index]) - { - return Plugin_Stop; - } - CBaseCombatCharacter npcEntity = CBaseCombatCharacter(slender); - int client = EntRefToEntIndex(g_SlenderDeathCamTarget[chaserBoss.Index]); - - bool attackWaiters = !!(chaserBoss.Flags & SFF_ATTACKWAITERS); - - if (!IsValidClient(client) || (IsValidClient(client) && (!IsPlayerAlive(client) || (GetClientTeam(client) == TFTeam_Blue && !attackWaiters)))) - { - if (g_SlenderDeathCamTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderDeathCamTimer[chaserBoss.Index]); - } - else - { - if ((chaserBoss.Flags & SFF_FAKE)) - { - if (g_SlenderInDeathcam[chaserBoss.Index]) - { - g_SlenderInDeathcam[chaserBoss.Index] = false; - } - SlenderMarkAsFake(chaserBoss.Index); - } - else - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - if (!NPCChaserIsCloaked(chaserBoss.Index)) - { - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], g_SlenderRenderColor[chaserBoss.Index][3]); - } - if (g_SlenderEntityThink[chaserBoss.Index] != null) - { - KillTimer(g_SlenderEntityThink[chaserBoss.Index]); - } - g_SlenderEntityThink[chaserBoss.Index] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, EntIndexToEntRef(npcEntity.index), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderInDeathcam[chaserBoss.Index] = false; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, g_SlenderState[chaserBoss.Index]); - } - } - } - - return Plugin_Continue; -} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp deleted file mode 100644 index 652a23b0..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp +++ /dev/null @@ -1,1191 +0,0 @@ -#if defined _sf2_npc_chaser_pathing_included - #endinput -#endif -#define _sf2_npc_chaser_pathing_included - -#pragma semicolon 1 - -static float g_NpcEstimatedYaw[MAX_BOSSES] = { 0.0, ... }; - -void SlenderChaseBossProcessMovement(int bossEnt) -{ - if (!g_Enabled) - { - return; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(bossEnt); - if (npc == INVALID_NPC) - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderChaseBossProcessMovement); //What no boss? - return; - } - - int bossIndex = NPCGetFromEntIndex(bossEnt); - if (bossIndex == -1) - { - //Boss is invalid somehow, and the hook wasn't killed. - SDKUnhook(bossEnt, SDKHook_Think, SlenderChaseBossProcessMovement); - return; - } - - INextBot bot = npc.GetBot(); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - CBaseCombatCharacter combatChar = CBaseCombatCharacter(bossEnt); - - char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, slenderProfile, sizeof(slenderProfile)); - - float myPos[3], myEyeAng[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", myPos); - GetEntPropVector(bossEnt, Prop_Data, "m_angAbsRotation", myEyeAng); - - int state = g_SlenderState[bossIndex]; - int oldState = g_SlenderOldState[bossIndex]; - g_SlenderOldState[bossIndex] = state; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - if (!g_SlenderInDeathcam[bossIndex]) - { - switch (state) - { - case STATE_WANDER, STATE_ALERT: - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; - npc.flRunSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; - } - loco.SetSpeedLimit(g_SlenderCalculatedMaxWalkSpeed[bossIndex]); - } - case STATE_CHASE: - { - if (!g_NpcUsesChaseInitialAnimation[bossIndex] && !NPCIsRaging(bossIndex) - && !g_NpcUsesHealAnimation[bossIndex] && !g_NpcUseStartFleeAnimation[bossIndex]) - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = g_SlenderCalculatedSpeed[bossIndex]; - npc.flRunSpeed = g_SlenderCalculatedSpeed[bossIndex]; - } - } - else - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - loco.SetSpeedLimit(g_SlenderCalculatedMaxSpeed[bossIndex]); - } - case STATE_ATTACK: - { - int currentAttackIndex = NPCGetCurrentAttackIndex(bossIndex); - if (NPCChaserGetAttackWhileRunningState(bossIndex, currentAttackIndex, difficulty) && - !NPCIsRaging(bossIndex) && GetGameTime() >= g_NpcBaseAttackRunDelayTime[bossIndex][currentAttackIndex]) - { - if (NPCChaserGetAttackRunDuration(bossIndex, currentAttackIndex, difficulty) > 0.0) - { - if (GetGameTime() < g_NpcBaseAttackRunDurationTime[bossIndex][currentAttackIndex]) - { - float attackSpeed, originalSpeed; - originalSpeed = NPCChaserGetAttackRunSpeed(bossIndex, currentAttackIndex, difficulty); - if (g_RoundDifficultyModifier > 1.0) - { - attackSpeed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - } - else - { - attackSpeed = originalSpeed; - } - if (npc != INVALID_NPC) - { - attackSpeed *= g_SlenderSpeedMultiplier[bossIndex]; - float groundSpeed = GetEntPropFloat(bossEnt, Prop_Data, "m_flGroundSpeed") * g_SlenderSpeedMultiplier[bossIndex]; - if (groundSpeed > 0.0 && attackSpeed < groundSpeed) - { - npc.flWalkSpeed = groundSpeed; - npc.flRunSpeed = groundSpeed; - } - else - { - npc.flWalkSpeed = attackSpeed; - npc.flRunSpeed = attackSpeed; - } - } - } - else - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - } - else - { - float attackSpeed, originalSpeed; - originalSpeed = NPCChaserGetAttackRunSpeed(bossIndex, currentAttackIndex, difficulty); - - if (g_RoundDifficultyModifier > 1.0) - { - attackSpeed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - } - else - { - attackSpeed = originalSpeed; - } - - if (npc != INVALID_NPC) - { - attackSpeed *= g_SlenderSpeedMultiplier[bossIndex]; - float groundSpeed = GetEntPropFloat(bossEnt, Prop_Data, "m_flGroundSpeed") * g_SlenderSpeedMultiplier[bossIndex]; - if (groundSpeed > 0.0 && attackSpeed < groundSpeed) - { - npc.flWalkSpeed = groundSpeed; - npc.flRunSpeed = groundSpeed; - } - else - { - npc.flWalkSpeed = attackSpeed; - npc.flRunSpeed = attackSpeed; - } - } - } - } - else - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - loco.SetSpeedLimit(999999.9); - } - case STATE_STUN: - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - } - } - else - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - if (state == STATE_ATTACK) - { - int currentAttackIndex = NPCGetCurrentAttackIndex(bossIndex); - if (NPCChaserGetAttackWhileRunningState(bossIndex, currentAttackIndex, difficulty)) - { - npc.flAcceleration = 99999.9; - } - else - { - npc.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; - } - } - else - { - npc.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; - } - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - // Process angles. - bool changeAngle = false; - float posToAt[3]; - if (state != STATE_STUN && !g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) - { - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - - if (g_NpcHasAlwaysLookAtTarget[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - else - { - switch (state) - { - case STATE_CHASE: - { - bool canSeeTarget = false; - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(100.0)) - { - TR_TraceRayFilter(myEyePos, targetPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharacters, bossEnt); - canSeeTarget = !TR_DidHit(); - } - } - - if (canSeeTarget) - { - canSeeTarget = NPCShouldSeeEntity(bossIndex, target); - } - - if (g_NpcHasAlwaysLookAtTargetWhileChasing[bossIndex] || canSeeTarget) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - - } - } - - if (g_NpcUsesChaseInitialAnimation[bossIndex] || NPCIsRaging(bossIndex) || g_NpcUseStartFleeAnimation[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!loco.IsOnGround() || loco.IsClimbingOrJumping()) - { - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(400.0)) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - case STATE_ATTACK: - { - if (g_NpcHasAlwaysLookAtTargetWhileAttacking[bossIndex] && !NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && ((NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_LaserBeam))) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && (NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_LaserBeam)) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - } - } - - if (changeAngle) - { - loco.FaceTowards(posToAt); - } - - if (!g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) - { - // Process our desired speed. - bool pfUpdate = false; - switch (state) - { - case STATE_WANDER: - { - if ((NPCGetFlags(bossIndex) & SFF_WANDERMOVE)) - { - pfUpdate = true; - loco.Run(); - } - } - case STATE_ALERT: - { - pfUpdate = true; - loco.Run(); - } - case STATE_CHASE: - { - pfUpdate = true; - loco.Run(); - } - case STATE_ATTACK: - { - if (NPCChaserGetAttackWhileRunningState(bossIndex, attackIndex, difficulty)) - { - pfUpdate = true; - loco.Run(); - } - } - } - if (pfUpdate) - { - g_BossPathFollower[bossIndex].Update(bot); - } - else - { - loco.Stop(); - } - if (state != oldState) - { - bot.Update(); - } - if (!SF_IsBoxingMap() && loco.IsOnGround() && !loco.IsClimbingOrJumping() && (state == STATE_ALERT || state == STATE_CHASE || state == STATE_WANDER || state == STATE_ATTACK) && - !g_NpcUsesChaseInitialAnimation[bossIndex] && !NPCIsRaging(bossIndex) && - !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex] && !g_NpcUseStartFleeAnimation[bossIndex]) - { - float pathNodePos[3], pathEndPos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetForward(pathNodePos); - segment.GetPos(pathEndPos); - ScaleVector(pathNodePos, segment.length); - AddVectors(pathEndPos, pathNodePos, pathEndPos); - - if (g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, CLIMB_UP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, JUMP_OVER_GAP, 120.0)|| g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, LADDER_UP, 120.0)) - { - CBaseNPC_Jump(loco, myPos, pathEndPos); - } - } - } - } - - if (!loco.IsClimbingOrJumping() && !g_SlenderSpawning[bossIndex]) - { - float hullCheckMins[3], hullCheckMaxs[3]; - if (NPCGetRaidHitbox(bossIndex) == 1) - { - hullCheckMins = g_SlenderDetectMins[bossIndex]; - hullCheckMaxs = g_SlenderDetectMaxs[bossIndex]; - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - hullCheckMins = HULL_HUMAN_MINS; - hullCheckMaxs = HULL_HUMAN_MAXS; - } - hullCheckMins[0] -= 20.0; - hullCheckMins[1] -= 20.0; - - hullCheckMaxs[0] += 20.0; - hullCheckMaxs[1] += 20.0; - - hullCheckMins[2] += loco.GetStepHeight(); - hullCheckMaxs[2] += 5.0; - - if (!g_NpcVelocityCancel[bossIndex] && IsSpaceOccupiedIgnorePlayersAndEnts(myPos, hullCheckMins, hullCheckMaxs, bossEnt))//The boss will start to merge with shits, cancel out velocity. - { - float origin[3]; - loco.SetVelocity(origin); - g_NpcVelocityCancel[bossIndex] = true; - } - } - else - g_NpcVelocityCancel[bossIndex] = false; - - if (NPCChaserCanCrawl(bossIndex) && !g_SlenderSpawning[bossIndex] && - state != STATE_ATTACK && state != STATE_STUN && state != STATE_IDLE && loco.IsOnGround() && !loco.IsClimbingOrJumping()) - { - float crawlDetectMins[3], crawlDetectMaxs[3]; - crawlDetectMins = g_NpcCrawlDetectMins[bossIndex]; - crawlDetectMaxs = g_NpcCrawlDetectMaxs[bossIndex]; - - if (IsSpaceOccupiedIgnorePlayersAndEnts(myPos, crawlDetectMins, crawlDetectMaxs, bossEnt) && !g_NpcIsCrawling[bossIndex]) - { - NPCChaserUpdateBossAnimation(bossIndex, bossEnt, g_SlenderState[bossIndex]); - g_NpcIsCrawling[bossIndex] = true; - g_NpcChangeToCrawl[bossIndex] = true; - } - if (!IsSpaceOccupiedIgnorePlayersAndEnts(myPos, crawlDetectMins, crawlDetectMaxs, bossEnt) && g_NpcIsCrawling[bossIndex]) - { - NPCChaserUpdateBossAnimation(bossIndex, bossEnt, g_SlenderState[bossIndex]); - g_NpcIsCrawling[bossIndex] = false; - g_NpcChangeToCrawl[bossIndex] = true; - } - } - - if ((state == STATE_CHASE || state == STATE_ALERT || state == STATE_WANDER) && !g_SlenderInDeathcam[bossIndex]) - { - int pitch = combatChar.LookupPoseParameter("body_pitch"); - int yaw = combatChar.LookupPoseParameter("body_yaw"); - float dir[3], ang[3], npcCenter[3], lookPos[3]; - combatChar.WorldSpaceCenter(npcCenter); - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - if (target && target != INVALID_ENT_REFERENCE) - { - GetClientEyePosition(target, lookPos); - lookPos[2] -= 20.0; - } - else - { - CopyVector(g_SlenderGoalPos[bossIndex], lookPos); - } - SubtractVectors(npcCenter, lookPos, dir); - NormalizeVector(dir, dir); - GetVectorAngles(dir, ang); - - float pitchPose = combatChar.GetPoseParameter(pitch); - float yawPose = combatChar.GetPoseParameter(yaw); - - ang[0] = UTIL_Clamp(UTIL_AngleNormalize(ang[0]), -44.0, 89.0); - combatChar.SetPoseParameter(pitch, UTIL_ApproachAngle(state == STATE_CHASE && g_SlenderTargetIsVisible[bossIndex] ? ang[0] : 0.0, pitchPose, (NPCGetTurnRate(bossIndex)/1000.0) * 16.0)); - ang[1] = UTIL_Clamp(-UTIL_AngleNormalize(UTIL_AngleDiff(UTIL_AngleNormalize(ang[1]), UTIL_AngleNormalize(myEyeAng[1]+180.0))), -44.0, 44.0); - combatChar.SetPoseParameter(yaw, UTIL_ApproachAngle(state == STATE_CHASE && g_SlenderTargetIsVisible[bossIndex] ? ang[1] : 0.0, yawPose, (NPCGetTurnRate(bossIndex)/1000.0) * 16.0)); - - int moveX = combatChar.LookupPoseParameter("move_x"); - int moveY = combatChar.LookupPoseParameter("move_y"); - - float groundSpeed = loco.GetGroundSpeed(); - if (moveX > 0 || moveY > 0) - { - if (groundSpeed != 0.0) - { - float forwardVector[3], rightVector[3], upVector[3], motionVector[3]; - combatChar.GetVectors(forwardVector, rightVector, upVector); - loco.GetGroundMotionVector(motionVector); - - if (moveX >= 0) - { - combatChar.SetPoseParameter(moveX, GetVectorDotProduct(motionVector,forwardVector)); - } - if (moveY >= 0) - { - combatChar.SetPoseParameter(moveY, GetVectorDotProduct(motionVector,rightVector)); - } - } - else - { - if (moveX >= 0) - { - combatChar.SetPoseParameter(moveX, 0.0); - } - if (moveY >= 0) - { - combatChar.SetPoseParameter(moveY, 0.0); - } - } - float _groundSpeed = GetEntPropFloat(bossEnt, Prop_Data, "m_flGroundSpeed"); - if (_groundSpeed != 0.0 && loco.IsOnGround()) - { - float returnValue = (groundSpeed / _groundSpeed) * g_NpcCurrentAnimationSequencePlaybackRate[bossIndex]; - if (returnValue < -4.0) - { - returnValue = -4.0; - } - if (returnValue > 12.0) - { - returnValue = 12.0; - } - - SetEntPropFloat(bossEnt, Prop_Send, "m_flPlaybackRate", returnValue); - } - } - - int moveScale = combatChar.LookupPoseParameter("move_scale"); - int moveYaw = combatChar.LookupPoseParameter("move_yaw"); - - float fwd[3], right[3], up[3]; - combatChar.GetVectors(fwd, right, up); - - float motionVector[3]; - loco.GetGroundMotionVector(motionVector); - - if (moveScale > 0) - { - float moveSpeed = loco.GetGroundSpeed(); - float scale = npc.flRunSpeed / moveSpeed; - combatChar.SetPoseParameter(moveScale, scale); - } - - if (moveYaw > 0) - { - float deltaTime = GetGameFrameTime(); - if (deltaTime > 0.0) - { - float myAngle[3], normalAngle, estimateYaw; - estimateYaw = g_NpcEstimatedYaw[bossIndex]; - combatChar.GetLocalAngles(myAngle); - - if (motionVector[0] == 0.0 && motionVector[1] == 0.0) - { - float yawDelta = myAngle[1] - estimateYaw; - yawDelta = AngleNormalize(yawDelta); - - if (deltaTime < 0.25) - { - yawDelta *= (deltaTime * 4.0); - } - else - { - yawDelta *= deltaTime; - } - - estimateYaw += yawDelta; - estimateYaw = AngleNormalize(estimateYaw); - } - else - { - estimateYaw = (ArcTangent2(motionVector[1], motionVector[0]) * 180.0 / 3.14159); - estimateYaw = FloatClamp(estimateYaw, -180.0, 180.0); - } - normalAngle = AngleNormalize(myAngle[1]); - g_NpcEstimatedYaw[bossIndex] = estimateYaw; - float actualYaw = normalAngle - estimateYaw; - actualYaw = AngleNormalize(-actualYaw); - - combatChar.SetPoseParameter(moveYaw, actualYaw); - } - } - } - - if (state != STATE_IDLE && state != STATE_STUN && state != STATE_ATTACK && !g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) - { - bool runUnstuck = (state == STATE_CHASE && g_SlenderCalculatedSpeed[bossIndex] > 0.0); - if (!runUnstuck) - { - runUnstuck = (state == STATE_ALERT && g_SlenderCalculatedWalkSpeed[bossIndex] > 0.0); - } - if (!runUnstuck) - { - runUnstuck = (state == STATE_WANDER && (NPCGetFlags(bossIndex) & SFF_WANDERMOVE) && g_SlenderCalculatedWalkSpeed[bossIndex] > 0.0 && - difficulty >= RoundToNearest(NPCGetAttributeValue(bossIndex, SF2Attribute_BlockWalkSpeedUnderDifficulty))); - } - if (runUnstuck) - { - if (loco.GetGroundSpeed() <= 0.1 || GetVectorSquareMagnitude(myPos, g_LastPos[bossIndex]) < 0.1 || loco.IsStuck()) - { - bool blockingProp = false; - - if (NPCGetFlags(bossIndex) & SFF_ATTACKPROPS) - { - for (int attackIndex2 = 0; attackIndex2 < NPCChaserGetAttackCount(bossIndex); attackIndex2++) - { - if (NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_LaserBeam) - { - continue; - } - blockingProp = NPC_CanAttackProps(bossIndex,NPCChaserGetAttackRange(bossIndex, attackIndex2, difficulty), NPCChaserGetAttackSpread(bossIndex, attackIndex2, difficulty)); - if (blockingProp) - { - break; - } - } - } - if (!blockingProp) - { - if (g_LastStuckTime[bossIndex] == 0.0) - { - g_LastStuckTime[bossIndex] = GetGameTime(); - } - - if ((g_LastStuckTime[bossIndex] <= GetGameTime()-1.0 || loco.GetStuckDuration() >= 1.0) && - !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex]) - { - float movePos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else - { - g_BossPathFollower[bossIndex].GetClosestPosition(myPos, movePos); - } - bool pathResolved = false; - - if (SlenderChaseBoss_OnStuckResolvePath(bossEnt, myPos, myEyeAng, movePos, movePos)) - { - if (NPCGetRaidHitbox(bossIndex) == 1) - { - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - } - if (!pathResolved) - { - if (NPCGetRaidHitbox(bossIndex) == 1) - { - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - CNavArea area = combatChar.GetLastKnownArea(); - if (area == NULL_AREA && segment != NULL_PATH_SEGMENT) - { - area = segment.area; - } - if (area != NULL_AREA) - { - area.GetCenter(movePos); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !g_RestartSessionEnabled) - { - RemoveSlender(bossIndex);//We are stuck there's no way out for us, unspawn, players are just going to abuse that we are stuck. - } - else if (SF_IsBoxingMap()) - { - float teleportPos[3]; - ArrayList respawnPoint = new ArrayList(); - char name[32]; - int ent = -1; - while ((ent = FindEntityByClassname(ent, "info_target")) != -1) - { - GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); - if (StrContains(name, "sf2_boss_respawnpoint", false)) - { - respawnPoint.Push(ent); - } - } - ent = -1; - if (respawnPoint.Length > 0) - { - ent = respawnPoint.Get(GetRandomInt(0,respawnPoint.Length-1)); - } - - delete respawnPoint; - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - else if (!SF_IsBoxingMap() && g_RestartSessionEnabled) - { - ArrayList spawnPoint = new ArrayList(); - float teleportPos[3]; - int ent = -1, spawnTeam = 0; - while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) - { - spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); - if (spawnTeam == TFTeam_Red) - { - spawnPoint.Push(ent); - } - - } - ent = -1; - if (spawnPoint.Length > 0) - { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); - } - - delete spawnPoint; - - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - } - } - } - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - CNavArea area = combatChar.GetLastKnownArea(); - if (area == NULL_AREA && segment != NULL_PATH_SEGMENT) - { - area = segment.area; - } - if (area != NULL_AREA) - { - area.GetCenter(movePos); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !g_RestartSessionEnabled) - { - RemoveSlender(bossIndex);//We are stuck there's no way out for us, unspawn, players are just going to abuse that we are stuck. - } - else if (SF_IsBoxingMap()) - { - float teleportPos[3]; - ArrayList respawnPoint = new ArrayList(); - char name[32]; - int ent = -1; - while ((ent = FindEntityByClassname(ent, "info_target")) != -1) - { - GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); - if (StrContains(name, "sf2_boss_respawnpoint", false)) - { - respawnPoint.Push(ent); - } - } - ent = -1; - if (respawnPoint.Length > 0) - { - ent = respawnPoint.Get(GetRandomInt(0,respawnPoint.Length-1)); - } - - delete respawnPoint; - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - else if (!SF_IsBoxingMap() && g_RestartSessionEnabled) - { - ArrayList spawnPoint = new ArrayList(); - float teleportPos[3]; - int ent = -1, spawnTeam = 0; - while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) - { - spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); - if (spawnTeam == TFTeam_Red) - { - spawnPoint.Push(ent); - } - - } - ent = -1; - if (spawnPoint.Length > 0) - { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); - } - - delete spawnPoint; - - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - } - } - } - } - } - if (pathResolved) - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; - } - } - } - } - else - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; - g_LastPos[bossIndex] = myPos; - } - } - } - - if (GetGameTime() >= g_SlenderNextFootstepSound[bossIndex]) - { - SlenderCastFootstep(bossIndex); - } - - return; -} - -void SlenderSetNextThink(int bossEnt) -{ - if (!g_Enabled) - { - return; - } - - CBaseCombatCharacter(bossEnt).SetNextThink(GetGameTime()); - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(bossEnt); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - int bossIndex = NPCGetFromEntIndex(bossEnt); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - //From Pelipoika's rainbow outline plugin - if (NPCGetCustomOutlinesState(bossIndex) && NPCGetRainbowOutlineState(bossIndex)) - { - int glow = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - int color[4]; - color[0] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 0) * 127.5 + 127.5); - color[1] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 2) * 127.5 + 127.5); - color[2] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 4) * 127.5 + 127.5); - color[3] = 255; - if (IsValidEntity(glow)) - { - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - } - } - - int state = g_SlenderState[bossIndex]; - bool changeAngle = false; - float posToAt[3]; - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - if (state != STATE_STUN && !g_SlenderSpawning[bossIndex]) - { - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - - if (g_NpcHasAlwaysLookAtTarget[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - else - { - switch (state) - { - case STATE_CHASE: - { - bool canSeeTarget = false; - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(100.0)) - { - TR_TraceRayFilter(myEyePos, targetPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharacters, bossEnt); - canSeeTarget = !TR_DidHit(); - } - } - - if (canSeeTarget) - { - canSeeTarget = NPCShouldSeeEntity(bossIndex, target); - } - - if (g_NpcHasAlwaysLookAtTargetWhileChasing[bossIndex] || canSeeTarget) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - - } - } - - if (g_NpcUsesChaseInitialAnimation[bossIndex] || NPCIsRaging(bossIndex) || g_NpcUseStartFleeAnimation[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!loco.IsOnGround() || loco.IsClimbingOrJumping()) - { - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(400.0)) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - case STATE_ATTACK: - { - if (g_NpcHasAlwaysLookAtTargetWhileAttacking[bossIndex] && !NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && ((NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_LaserBeam))) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && (NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_LaserBeam)) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - } - } - - if (changeAngle) - { - loco.FaceTowards(posToAt); - } - - } - - return; -} - -void CBaseNPC_Jump(NextBotGroundLocomotion nextbotLocomotion, float startPos[3], float endPos[3]) -{ - float jumpVel[3]; - endPos[2] += nextbotLocomotion.GetStepHeight(); - float actualHeight = endPos[2] - startPos[2]; - float height = actualHeight; - if (height < 16.0) - { - height = 16.0; - } - - float additionalHeight = 20.0; - if (height < 32.0) - { - additionalHeight += 8.0; - } - float gravity = nextbotLocomotion.GetGravity(); - - height += additionalHeight; - - float speed = SquareRoot(2.0 * gravity * height); - float time = (speed / gravity); - - time += SquareRoot((2.0 * additionalHeight) / gravity); - - SubtractVectors(endPos, startPos, jumpVel); - jumpVel[0] /= time; - jumpVel[1] /= time; - jumpVel[2] /= time; - - jumpVel[2] = speed; - - float flJumpSpeed = GetVectorLength(jumpVel, true); - float flMaxSpeed = SquareFloat(650.0); - if (flJumpSpeed > flMaxSpeed) - { - jumpVel[0] *= (flMaxSpeed / flJumpSpeed); - jumpVel[1] *= (flMaxSpeed / flJumpSpeed); - jumpVel[2] *= (flMaxSpeed / flJumpSpeed); - } - - nextbotLocomotion.Jump(); - nextbotLocomotion.SetVelocity(jumpVel); -} - -bool SlenderChaseBoss_OnStuckResolvePath(int slender, float myPos[3], float myAng[3], float goalPosition[3], float returnFloat[3]) -{ - //We are stuck, try to find a free pos to path on the right or left. - int attemp = 1; - while (attemp <= 2) - { - float yawMin, yawMax, yawInc; - if (attemp == 1) //We will first try on the right - { - yawMin = 10.0; - yawMax = 90.0; - yawInc = 5.0; - } - else if (attemp == 2) //Then on the left - { - yawMin = -90.0; - yawMax = -10.0; - yawInc = 5.0; - } - //Note: Actually there's no right and left, it's just to give you an idea on how this will be done. - for(int y=RoundToNearest(yawMin); y<=RoundToNearest(yawMax); y+=RoundToNearest(yawInc)) - { - myAng[1] += float(y); - for(int r=30; r<=300; r+=10) - { - float freePos[3]; - GetPositionForward(myPos, myAng, freePos, float(r)); - - // Perform a line of sight check to avoid spawning players in unreachable map locations. - TR_TraceRayFilter(myPos, freePos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - - if (!TR_DidHit()) - { - // Perform an other line of sight check to avoid moving in a area that can't reach the original goal! - TR_TraceRayFilter(freePos, goalPosition, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - if (!TR_DidHit()) - { - TR_TraceHullFilter(myPos, freePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, TraceRayDontHitAnyEntity, slender); - - if (!TR_DidHit()) - { - returnFloat = freePos; - myAng[1] -= float(y); - return true; - } - } - else - { - //This free position can't bring us to the goal position. Give up on this angle. - break; - } - } - else - { - // We hit something that breaks the line of sight. Give up on this angle. - break; - } - } - myAng[1] -= float(y); - } - attemp++; - } - return false; -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp deleted file mode 100644 index 8f131f67..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp +++ /dev/null @@ -1,1107 +0,0 @@ -#if defined _sf2_npc_chaser_projectiles_included - #endinput -#endif -#define _sf2_npc_chaser_projectiles_included - -#pragma semicolon 1 - -static char gestureShootAnim[PLATFORM_MAX_PATH]; -static char baseballModel[PLATFORM_MAX_PATH]; - -//static float g_projectileSpeed[2049]; -//static float g_projectileDamage[2049]; -//static float g_projectileRadius[2049]; -//static float g_projectileIceSlowPercent[2049]; -//static float g_projectileIceSlowDuration[2049]; -//static float g_flOriginalProjectilePos[2049][3]; -//static int g_projectileType[2049]; -//static int g_projectileRotateState[2049]; // 0 = 0 degrees, 1 = 1-180 degrees, 2 = 181-360 degrees -//static Handle g_hProjectileTimer[2049]; - -/*public Action Timer_ProjectileThink(Handle timer, any entref) -{ - int projectile = EntRefToEntIndex(entref); - if (!projectile || projectile == INVALID_ENT_REFERENCE) return Plugin_Stop; - - if (timer != g_hProjectileTimer[projectile]) return Plugin_Stop; - - float flPos[3], flAng[3], flNewPos[3]; - GetEntPropVector(projectile, Prop_Data, "m_vecAbsOrigin", flPos); - GetEntPropVector(projectile, Prop_Data, "m_angAbsRotation", flAng); - - flNewPos[0] = flPos[0]; - flNewPos[1] = flPos[1]; - flNewPos[2] = flPos[2]; - - float flSpeed = (g_projectileSpeed[projectile] - 64.0) / 54.0; - AddVectors(g_flOriginalProjectilePos[projectile], flNewPos, flNewPos); - flNewPos[0] += g_flOriginalProjectilePos[projectile][0]; - flNewPos[1] += g_flOriginalProjectilePos[projectile][1]; - switch (g_projectileRotateState[projectile]) - { - case 0: flNewPos[2] = g_flOriginalProjectilePos[projectile][2]; - case 1: flNewPos[2] += g_flOriginalProjectilePos[projectile][2]/flAng[0]; - case 2: flNewPos[2] -= g_flOriginalProjectilePos[projectile][2]/flAng[0]; - } - - TeleportEntity(projectile, flNewPos, flAng, NULL_VECTOR); - - return Plugin_Continue; -}*/ - -int NPCChaserProjectileShoot(int bossIndex, int slender, int target, const char[] slenderProfile, float myPos[3]) -{ - if (g_RestartSessionEnabled) - { - return -1; - } - - CBaseCombatCharacter combatChar = CBaseCombatCharacter(slender); - - int projectileType = NPCChaserGetProjectileType(bossIndex); - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int projectileEnt; - float shootDist = GetVectorSquareMagnitude(g_SlenderGoalPos[bossIndex], myPos); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char projectileName[45]; - float clientPos[3]; - float slenderPosition[3]; - float nonRocketScaleMins[3], nonRocketScaleMaxs[3]; - nonRocketScaleMins[0] = -10.0; - nonRocketScaleMins[1] = -10.0; - nonRocketScaleMins[2] = -10.0; - nonRocketScaleMaxs[0] = 10.0; - nonRocketScaleMaxs[1] = 10.0; - nonRocketScaleMaxs[2] = 10.0; - NPCGetEyePosition(bossIndex, slenderPosition); - GetClientEyePosition(target, clientPos); - switch (projectileType) - { - case SF2BossProjectileType_Grenade: - { - if (shootDist < SquareFloat(600.0)) - { - clientPos[2] += 0.0; - } - else if (shootDist > SquareFloat(600.0) && shootDist < SquareFloat(1000.0)) - { - clientPos[2] += 60.0; - } - else if (shootDist > SquareFloat(1000.0)) - { - clientPos[2] += 120.0; - } - } - case SF2BossProjectileType_Arrow, SF2BossProjectileType_Baseball: - { - clientPos[2] -= 0.0; - } - default: - { - clientPos[2] -= 25.0; - } - } - - float basePos[3], baseAng[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - - float effectPos[3], tempEffectPos[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - - int randomPosMin = GetChaserProfileRandomProjectilePosMin(profile); - int randomPosMax = GetChaserProfileRandomProjectilePosMax(profile); - ArrayList posArray = GetChaserProfileProjectilePositionsArray(profile); - - if (randomPosMin == randomPosMax) - { - posArray.GetArray(0, tempEffectPos); - } - else - { - int randomProjectilePos = GetRandomInt(randomPosMin, randomPosMax); - posArray.GetArray(randomProjectilePos, tempEffectPos); - } - - posArray = null; - - VectorTransform(tempEffectPos, basePos, baseAng, tempEffectPos); - AddVectors(effectAng, baseAng, effectAng); - - float spread = NPCChaserGetProjectileDeviation(bossIndex, difficulty); - - int team = 3; - int teamNon = 5; - - float min = NPCChaserGetProjectileCooldownMin(bossIndex, difficulty); - float max = NPCChaserGetProjectileCooldownMax(bossIndex, difficulty); - - for (int i = 0; i < NPCChaserGetProjectileCount(bossIndex, difficulty); i++) - { - if (NPCChaserGetProjectileCount(bossIndex, difficulty) != 1) - { - effectPos[0] = tempEffectPos[0] + GetRandomFloat(-10.0, 10.0); - effectPos[1] = tempEffectPos[1] + GetRandomFloat(-10.0, 10.0); - effectPos[2] = tempEffectPos[2] + GetRandomFloat(-10.0, 10.0); - } - else - { - effectPos[0] = tempEffectPos[0]; - effectPos[1] = tempEffectPos[1]; - effectPos[2] = tempEffectPos[2]; - } - float shootDirection[3], shootAng[3]; - SubtractVectors(clientPos, effectPos, shootDirection); - if (spread != 0.0) - { - shootDirection[0] += GetRandomFloat(-spread, spread); - shootDirection[1] += GetRandomFloat(-spread, spread); - shootDirection[2] += GetRandomFloat(-spread, spread); - } - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - switch (projectileType) - { - case SF2BossProjectileType_Fireball: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - AttachParticle(projectileEnt, g_SlenderFireballTrail[bossIndex]); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flModelScale", 1.0); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage to nothing - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_FIREBALL); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderFireballShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Iceball: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - AttachParticle(projectileEnt, g_SlenderIceballTrail[bossIndex]); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flModelScale", 1.0); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage to nothing - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_ICEBALL); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderFireballShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Rocket: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, NPCChaserGetProjectileDamage(bossIndex, difficulty), true); // set damage - ProjectileSetFlags(projectileEnt, PROJ_ROCKET); - SetEntityModel(projectileEnt, g_SlenderRocketModel[bossIndex]); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderRocketShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - /*case SF2BossProjectileType_Rocket: - { - projectileName = "prop_dynamic_override"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - g_projectileSpeed[projectileEnt] = NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - //if (NPCChaserHasCriticalRockets(bossIndex)) SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - //SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - g_hProjectileTimer[projectileEnt] = CreateTimer(BOSS_THINKRATE, Timer_ProjectileThink, EntIndexToEntRef(projectileEnt), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - ProjectileSetFlags(projectileEnt, PROJ_ROCKET); - SetEntityModel(projectileEnt, ROCKET_MODEL); - - //SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - GetEntPropVector(projectileEnt, Prop_Data, "m_vecOrigin", g_flOriginalProjectilePos[projectileEnt]); - g_flOriginalProjectilePos[projectileEnt][0] /= (g_projectileSpeed[projectileEnt] - 64.0) / 54.0; - g_flOriginalProjectilePos[projectileEnt][1] /= (g_projectileSpeed[projectileEnt] - 64.0) / 54.0; - g_flOriginalProjectilePos[projectileEnt][2] /= (g_projectileSpeed[projectileEnt] - 64.0) / 54.0; - PrintToChatAll("%f", shootAng[0]); - if (shootAng[0] == 0.0 || shootAng[0] == 180.0) - g_projectileRotateState[projectileEnt] = 0; - else if (shootAng[0] > 0.0 && shootAng[0] < 180.0) - g_projectileRotateState[projectileEnt] = 1; - else if (shootAng[0] > 180.0) - g_projectileRotateState[projectileEnt] = 2; - - CreateTimer(1.0, Timer_KillEntity, EntIndexToEntRef(projectileEnt), TIMER_FLAG_NO_MAPCHANGE); - - if (NPCChaserUseShootGesture(bossIndex)) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderRocketShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - }*/ - case SF2BossProjectileType_Grenade: - { - projectileName = "tf_projectile_pipe"; - if (shootDist <= SquareFloat(1800.0)) - { - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - TeleportEntity(projectileEnt, effectPos, shootAng, NULL_VECTOR); - DispatchSpawn(projectileEnt); - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", 3, 1); - SetEntProp(projectileEnt, Prop_Send, "m_nSkin", 1, 1); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Send, "m_DmgRadius", NPCChaserGetProjectileRadius(bossIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Send, "m_flDamage", NPCChaserGetProjectileDamage(bossIndex, difficulty)); - TeleportEntity(projectileEnt, NULL_VECTOR, NULL_VECTOR, velocity); - SetEntDataFloat(projectileEnt, g_FullDamageData, NPCChaserGetProjectileDamage(bossIndex, difficulty)); - ProjectileSetFlags(projectileEnt, PROJ_GRENADE); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderGrenadeShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - } - case SF2BossProjectileType_SentryRocket: - { - projectileName = "tf_projectile_sentryrocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_SentryRocket", "m_iDeflected") + 4, NPCChaserGetProjectileDamage(bossIndex, difficulty), true); // set damage - ProjectileSetFlags(projectileEnt, PROJ_SENTRYROCKET); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderSentryRocketShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Arrow: - { - projectileName = "tf_point_weapon_mimic"; - if (shootDist <= SquareFloat(1250.0)) - { - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCrits", 1, 1); - } - SetEntPropFloat(projectileEnt, Prop_Data, "m_flDamage", NPCChaserGetProjectileDamage(bossIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flSpeedMin", NPCChaserGetProjectileSpeed(bossIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flSpeedMax", NPCChaserGetProjectileSpeed(bossIndex, difficulty)); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - ProjectileSetFlags(projectileEnt, PROJ_ARROW); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - DispatchKeyValueVector(projectileEnt, "origin", effectPos); - DispatchKeyValueVector(projectileEnt, "angles", shootAng); - DispatchKeyValue(projectileEnt, "WeaponType", "2"); - - AcceptEntityInput(projectileEnt, "FireOnce"); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - CreateTimer(5.0, Timer_KillEntity, EntIndexToEntRef(projectileEnt), TIMER_FLAG_NO_MAPCHANGE); - if (i == 0) - { - EmitSoundToAll(g_SlenderArrowShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - } - case SF2BossProjectileType_Mangler: - { - projectileName = "tf_projectile_energy_ball"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntProp(projectileEnt, Prop_Send, "m_CollisionGroup", 4); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", 5, 1); - SetEntProp(projectileEnt, Prop_Data, "m_takedamage", 0); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", view_as( { 3.0, 3.0, 3.0 } )); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", view_as( { 3.0, 3.0, 3.0 } )); - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - ProjectileSetFlags(projectileEnt, PROJ_MANGLER); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderManglerShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Baseball: - { - projectileName = "tf_projectile_stun_ball"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - GetChaserProfileBaseballModel(slenderProfile, baseballModel, sizeof(baseballModel)); - if (baseballModel[0] == '\0') - { - baseballModel = BASEBALL_MODEL; - } - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hThrower", slender); - SetEntityModel(projectileEnt, baseballModel); - ProjectileSetFlags(projectileEnt, PROJ_BASEBALL); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderBaseballShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - } - } - - return projectileEnt; -} - -int NPCChaserProjectileAttackShoot(int bossIndex, int slender, int target, const char[] slenderProfile) -{ - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - int projectileType = NPCChaserGetAttackProjectileType(bossIndex, attackIndex); - int projectileEnt; - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - char projectileName[45]; - float clientPos[3]; - float slenderPosition[3]; - float nonRocketScaleMins[3], nonRocketScaleMaxs[3]; - nonRocketScaleMins[0] = -10.0; - nonRocketScaleMins[1] = -10.0; - nonRocketScaleMins[2] = -10.0; - nonRocketScaleMaxs[0] = 10.0; - nonRocketScaleMaxs[1] = 10.0; - nonRocketScaleMaxs[2] = 10.0; - NPCGetEyePosition(bossIndex, slenderPosition); - GetClientEyePosition(target, clientPos); - clientPos[2] -= 25.0; - - float basePos[3], baseAng[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - - float effectPos[3], tempEffectPos[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - - GetChaserProfileAttackProjectileOffset(slenderProfile, attackIndex, tempEffectPos); - VectorTransform(tempEffectPos, basePos, baseAng, tempEffectPos); - AddVectors(effectAng, baseAng, effectAng); - - float spread = NPCChaserGetAttackProjectileDeviation(bossIndex, attackIndex, difficulty); - - int team = 3; - int teamNon = 5; - for (int i = 0; i < NPCChaserGetAttackProjectileCount(bossIndex, attackIndex, difficulty); i++) - { - if (NPCChaserGetAttackProjectileCount(bossIndex, attackIndex, difficulty) != 1) - { - effectPos[0] = tempEffectPos[0] + GetRandomFloat(-10.0, 10.0); - effectPos[1] = tempEffectPos[1] + GetRandomFloat(-10.0, 10.0); - effectPos[2] = tempEffectPos[2] + GetRandomFloat(-10.0, 10.0); - } - else - { - effectPos[0] = tempEffectPos[0]; - effectPos[1] = tempEffectPos[1]; - effectPos[2] = tempEffectPos[2]; - } - - float shootDirection[3], shootAng[3]; - SubtractVectors(clientPos, effectPos, shootDirection); - if (spread != 0.0) - { - shootDirection[0] += GetRandomFloat(-spread, spread); - shootDirection[1] += GetRandomFloat(-spread, spread); - shootDirection[2] += GetRandomFloat(-spread, spread); - } - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - switch (projectileType) - { - case 0: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - char fireballTrail[PLATFORM_MAX_PATH]; - GetChaserProfileAttackFireballTrail(slenderProfile, attackIndex, fireballTrail, sizeof(fireballTrail)); - AttachParticle(projectileEnt, fireballTrail); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_FIREBALL_ATTACK); - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileAttackTouch); - } - } - case 1: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - char rocketModel[PLATFORM_MAX_PATH]; - GetChaserProfileAttackRocketModel(slenderProfile, attackIndex, rocketModel, sizeof(rocketModel)); - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - if (NPCChaserGetAttackProjectileCrits(bossIndex, attackIndex, difficulty)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty), true); // set damage - ProjectileSetFlags(projectileEnt, PROJ_ROCKET); - if (strcmp(rocketModel, ROCKET_MODEL, true) != 0) - { - int model; - model = PrecacheModel(rocketModel, true); - SetEntProp(projectileEnt, Prop_Send, "m_nModelIndexOverrides", model); - } - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - } - } - case 2: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - char fireballTrail[PLATFORM_MAX_PATH]; - GetChaserProfileAttackIceballTrail(slenderProfile, attackIndex, fireballTrail, sizeof(fireballTrail)); - AttachParticle(projectileEnt, fireballTrail); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage to nothing - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileAttackTouch); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_ICEBALL_ATTACK); - } - } - case 3: - { - projectileName = "tf_projectile_pipe"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - - TeleportEntity(projectileEnt, effectPos, shootAng, NULL_VECTOR); - DispatchSpawn(projectileEnt); - if (NPCChaserGetAttackProjectileCrits(bossIndex, attackIndex, difficulty)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", 3, 1); - SetEntProp(projectileEnt, Prop_Send, "m_nSkin", 1, 1); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Send, "m_DmgRadius", NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Send, "m_flDamage", NPCChaserGetAttackProjectileRadius(bossIndex, attackIndex, difficulty)); - SetEntDataFloat(projectileEnt, g_FullDamageData, NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty)); - TeleportEntity(projectileEnt, NULL_VECTOR, NULL_VECTOR, velocity); - ProjectileSetFlags(projectileEnt, PROJ_GRENADE); - - //SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - } - } - } - } - - return projectileEnt; -} - -static Action Hook_ProjectileAttackTouch(int entity, int other) -{ - switch (ProjectileGetFlags(entity)) - { - case PROJ_ICEBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - EmitSoundToAll(FIREBALL_IMPACT, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - CreateGeneralParticle(entity, "spell_batball_impact_blue"); - } - } - case PROJ_FIREBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - EmitSoundToAll(FIREBALL_IMPACT, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - CreateGeneralParticle(entity, "bombinomicon_burningdebris"); - } - } - } - switch (ProjectileGetFlags(entity)) - { - case PROJ_FIREBALL_ATTACK, PROJ_ICEBALL_ATTACK: - { - float entPos[3]; - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - entPos[2] += 10.0; - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - float radius = NPCChaserGetAttackProjectileRadius(bossIndex, attackIndex, difficulty); - for (int client = 1; client <= MaxClients; client++) - { - if (!IsValidClient(client) || !IsClientInGame(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) - { - continue; - } - - if (!attackEliminated && g_PlayerEliminated[client]) - { - continue; - } - - float targetPos[3]; - GetClientEyePosition(client, targetPos); - - Handle trace = TR_TraceRayFilterEx(entPos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, - RayType_EndPoint, - TraceRayBossVisibility, - entity); - - bool isVisible = !TR_DidHit(trace); - int traceHitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (!isVisible && traceHitEntity == client) - { - isVisible = true; - } - - if (isVisible) - { - float distance = GetVectorSquareMagnitude(entPos, targetPos); - if (distance <= SquareFloat(radius)) - { - SDKHooks_TakeDamage(client, entity, entity, NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty), DMG_BLAST); - if (TF2_IsPlayerInCondition(client, TFCond_Gas)) - { - TF2_IgnitePlayer(client, client); - TF2_RemoveCondition(client, TFCond_Gas); - } - if ((ProjectileGetFlags(entity) & PROJ_FIREBALL_ATTACK)) - { - TF2_IgnitePlayer(client, client); - } - else if ((ProjectileGetFlags(entity) & PROJ_ICEBALL_ATTACK)) - { - EmitSoundToClient(client, ICEBALL_IMPACT, _, MUSIC_CHAN); - TF2_StunPlayer(client, NPCChaserGetAttackProjectileIceSlowdownDuration(bossIndex, attackIndex, difficulty), NPCChaserGetAttackProjectileIceSlowdownPercent(bossIndex, attackIndex, difficulty), TF_STUNFLAG_SLOWDOWN, client); - } - } - } - } - RemoveEntity(entity); - } - } - } - } - - return Plugin_Handled; -} - -static Action Hook_ProjectileTouch(int entity, int other) -{ - switch (ProjectileGetFlags(entity)) - { - case PROJ_ICEBALL, PROJ_ICEBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - EmitSoundToAll(g_SlenderFireballExplodeSound[bossIndex], entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - CreateGeneralParticle(entity, "spell_batball_impact_blue"); - } - } - case PROJ_FIREBALL, PROJ_FIREBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - EmitSoundToAll(g_SlenderFireballExplodeSound[bossIndex], entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - CreateGeneralParticle(entity, "bombinomicon_burningdebris"); - } - } - } - switch (ProjectileGetFlags(entity)) - { - case PROJ_BASEBALL: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - if (other > 0 && other <= MaxClients) - { - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - int difficulty = GetLocalGlobalDifficulty(bossIndex); - SDKHooks_TakeDamage(other, slender, slender, NPCChaserGetProjectileDamage(bossIndex, difficulty), 1048576); - if (TF2_IsPlayerInCondition(other, TFCond_Gas)) - { - TF2_IgnitePlayer(other, other); - TF2_RemoveCondition(other, TFCond_Gas); - } - RemoveEntity(other); - } - } - } - case PROJ_MANGLER, PROJ_FIREBALL, PROJ_ICEBALL: - { - float entPos[3]; - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - entPos[2] += 10.0; - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - float radius = NPCChaserGetProjectileRadius(bossIndex, difficulty); - float fallOff = NPCChaserGetProjectileRadius(bossIndex, difficulty)/2.0; - for (int client = 1; client <= MaxClients; client++) - { - if (!IsValidClient(client) || !IsClientInGame(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) - { - continue; - } - - if (!attackEliminated && g_PlayerEliminated[client]) - { - continue; - } - - float targetPos[3]; - GetClientEyePosition(client, targetPos); - - Handle trace = TR_TraceRayFilterEx(entPos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, - RayType_EndPoint, - TraceRayBossVisibility, - entity); - - bool isVisible = !TR_DidHit(trace); - int traceHitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (!isVisible && traceHitEntity == client) isVisible = true; - - if (isVisible) - { - float distance = GetVectorSquareMagnitude(entPos, targetPos); - if (distance <= SquareFloat(radius)) - { - float finalDamage; - if (distance <= SquareFloat(fallOff)) - { - finalDamage = NPCChaserGetProjectileDamage(bossIndex, difficulty); - } - else - { - float multiplier = (1.0 - ((distance - SquareFloat(fallOff)) / (SquareFloat(radius) - SquareFloat(fallOff)))); - finalDamage = multiplier * NPCChaserGetProjectileDamage(bossIndex, difficulty); - } - if ((ProjectileGetFlags(entity) & PROJ_MANGLER)) - { - SDKHooks_TakeDamage(client, entity, entity, finalDamage, DMG_BLAST); - } - else - { - SDKHooks_TakeDamage(client, entity, entity, NPCChaserGetProjectileDamage(bossIndex, difficulty), DMG_BLAST); - } - if (TF2_IsPlayerInCondition(client, TFCond_Gas)) - { - TF2_IgnitePlayer(client, client); - TF2_RemoveCondition(client, TFCond_Gas); - } - if ((ProjectileGetFlags(entity) & PROJ_FIREBALL)) - { - TF2_IgnitePlayer(client, client); - } - else if ((ProjectileGetFlags(entity) & PROJ_ICEBALL)) - { - EmitSoundToClient(client, g_SlenderIceballImpactSound[bossIndex], _, MUSIC_CHAN); - TF2_StunPlayer(client, NPCChaserGetIceballSlowdownDuration(bossIndex, difficulty), NPCChaserGetIceballSlowdownPercent(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN, client); - } - } - } - } - if ((ProjectileGetFlags(entity) & PROJ_MANGLER)) - { - int randomSound = GetRandomInt(0, 2); - switch (randomSound) - { - case 0: - { - EmitSoundToAll(MANGLER_EXPLODE1, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 1: - { - EmitSoundToAll(MANGLER_EXPLODE2, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - EmitSoundToAll(MANGLER_EXPLODE3, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - RemoveEntity(entity); - } - } - } - } - - return Plugin_Handled; -} - -static Action Hook_ProjectileTransmit(int ent, int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - return Plugin_Handled; -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp deleted file mode 100644 index d6e6f874..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp +++ /dev/null @@ -1,694 +0,0 @@ -#if defined _sf2_npc_chaser_takedamage_included - #endinput -#endif - -#define _sf2_npc_chaser_takedamage_included - -#pragma semicolon 1 - -//death_enable -//death_health -//death_duration -//death_cooldown -//death_disappear -//death_remove -//death_health_per_ -//"add death health on death" -/* - "death" - { - "1" - { - "animation_death" "diebackward" - "animation_death_playbackrate" "1.0" - } - } -*/ - -Action Hook_SlenderOnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) -{ - if (!IsValidEntity(victim)) - { - //Theres no boss, what? - SDKUnhook(victim, SDKHook_OnTakeDamageAlive, Hook_SlenderOnTakeDamage); - return Plugin_Stop; - } - - float myPos[3], myEyeAng[3], clientPos[3], buffer[3], traceStartPos[3], traceEndPos[3]; - GetEntPropVector(victim, Prop_Data, "m_vecAbsOrigin", myPos); - - int maxHealth = GetEntProp(victim, Prop_Data, "m_iMaxHealth"); - if (GetEntProp(victim, Prop_Data, "m_iHealth") != maxHealth) - { - SetEntProp(victim, Prop_Data, "m_iHealth", maxHealth); - } - - int bossIndex = NPCGetFromEntIndex(victim); - if (bossIndex == -1) - { - //Theres still no boss, how did this happen? - SDKUnhook(victim, SDKHook_OnTakeDamageAlive, Hook_SlenderOnTakeDamage); - return Plugin_Stop; - } - int slender = victim; - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - NPCGetEyePosition(bossIndex, traceStartPos); - - bool miniCrit = false; - if (IsValidClient(attacker)) - { - GetClientAbsOrigin(attacker, clientPos); - GetClientEyePosition(attacker, traceEndPos); - } - float shootDist = GetVectorSquareMagnitude(clientPos, myPos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - - AddVectors(myEyeAng, g_SlenderEyeAngOffset[bossIndex], myEyeAng); - - if (IsValidClient(attacker) && SF_IsBoxingMap() && (TF2_IsPlayerInCondition(attacker, TFCond_RegenBuffed)) && !NPCIsRaging(bossIndex)) - { - int health = GetClientHealth(attacker); - float multipliedDamage = damage; - multipliedDamage *= 0.475; - int newHealth = health + RoundToCeil(multipliedDamage); - if (newHealth <= GetEntProp(attacker, Prop_Data, "m_iMaxHealth")) - { - SetEntityHealth(attacker, newHealth); - } - else - { - int clientMaxHealth = GetEntProp(attacker, Prop_Data, "m_iMaxHealth"); - SetEntityHealth(attacker, clientMaxHealth); - } - } - - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Scout) - { - int stick = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(stick) && GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 349 && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && g_SlenderIsBurning[bossIndex] && !NPCIsRaging(bossIndex)) - { - damagetype = DMG_CRIT; - damage *= 3.0; - } - if (IsValidEntity(stick) && (GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 325 || GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 452) && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 5.0; - } - if (IsValidEntity(stick) && GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 355 && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderMarkedTimer[bossIndex] = CreateTimer(15.0, Timer_BossMarked, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - g_SlenderIsMarked[bossIndex] = true; - } - if (IsValidEntity(stick) && GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 648 && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && shootDist > SquareFloat(72.0) && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 5.0; - } - } - if (IsValidClient(attacker) && TF2_GetPlayerClass(attacker) == TFClass_Pyro) - { - //Probably the only time where buffing the phlog is a good thing. - int phlog = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); - int fragment = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(phlog) && GetEntProp(phlog, Prop_Send, "m_iItemDefinitionIndex") == TF_WEAPON_PHLOGISTINATOR && GetEntPropFloat(attacker, Prop_Send, "m_flNextRageEarnTime") <= GetGameTime() && !(GetEntProp(attacker, Prop_Send, "m_bRageDraining"))) - { - float rage = GetEntPropFloat(attacker, Prop_Send, "m_flRageMeter"); - rage += (damage / 30.00); - if (rage > 100.0) - { - rage = 100.0; - } - SetEntPropFloat(attacker, Prop_Send, "m_flRageMeter", rage); - } - if (IsValidEntity(fragment) && SF_IsBoxingMap() && GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 348 && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBurnTimer[bossIndex] = CreateTimer(0.5, Timer_BossBurn, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBurningTimer[bossIndex] = GetGameTime() + 15.0; - g_SlenderIsBurning[bossIndex] = true; - } - if (IsValidEntity(fragment) && SF_IsBoxingMap() && - (GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 38 || GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 457 || GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 1000) - && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && g_SlenderIsBurning[bossIndex] && !NPCIsRaging(bossIndex)) - { - g_SlenderBurnTimer[bossIndex] = null; - g_SlenderStopBurningTimer[bossIndex] = GetGameTime(); - g_SlenderIsBurning[bossIndex] = false; - miniCrit = true; - damage *= 1.35; - } - if (IsValidEntity(fragment) && GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 1181 && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - if (IsValidEntity(fragment) && SF_IsBoxingMap() && - (GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 813 || GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 834) - && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex) && (g_SlenderIsMarked[bossIndex])) - { - damagetype = DMG_CRIT; - damage *= 3.0; - } - } - if (IsValidClient(attacker) && TF2_GetPlayerClass(attacker) == TFClass_Soldier) - { - int weapon = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(weapon) && GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex") == 447 && weapon == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) // Whip - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - else if (IsValidEntity(weapon) && GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex") == 416 && weapon == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) - { - float zVelocity[3]; - GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); - if (zVelocity[2] < 0.0) // A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. - { - damagetype |= DMG_CRIT; - damage *= 2.0; - } - } - float requiredRage = 6.0; - if (IsValidEntity(weapon) && GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex") == 354) // Concheror - { - requiredRage = 4.8; - } - bool draining = !!GetEntProp(attacker, Prop_Send, "m_bRageDraining"); - if (!draining) - { - float rage = GetEntPropFloat(attacker, Prop_Send, "m_flRageMeter"); - rage += (damage / requiredRage); - if (rage > 100.0) - { - rage = 100.0; - } - SetEntPropFloat(attacker, Prop_Send, "m_flRageMeter", rage); - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Heavy) - { - int gloves = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(gloves) && GetEntProp(gloves, Prop_Send, "m_iItemDefinitionIndex") == 426 && gloves == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - if (IsValidEntity(gloves) && GetEntProp(gloves, Prop_Send, "m_iItemDefinitionIndex") == 43 && gloves == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && !IsClientCritBoosted(attacker) && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_PlayerHitsToCrits[attacker]++; - if (g_PlayerHitsToCrits[attacker] == 5) - { - TF2_AddCondition(attacker, TFCond_CritOnFlagCapture, 5.0); - g_PlayerHitsToCrits[attacker] = 0; - } - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Engineer) - { - int wrench = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(wrench) && GetEntProp(wrench, Prop_Send, "m_iItemDefinitionIndex") == 155 && wrench == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 5.0; - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Sniper) - { - int sharpy = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(sharpy) && GetEntProp(sharpy, Prop_Send, "m_iItemDefinitionIndex") == 171 && sharpy == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 6.0; - } - if (IsValidEntity(sharpy) && GetEntProp(sharpy, Prop_Send, "m_iItemDefinitionIndex") == 232 && sharpy == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && g_SlenderIsMarked[bossIndex] && !NPCIsRaging(bossIndex)) - { - damagetype = DMG_CRIT; - damage *= 3.0; - miniCrit = false; - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_DemoMan) - { - int sword = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(sword) && - (GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 132 || GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 266 || GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 482 || GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 1082) - && sword == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_PlayerHitsToHeads[attacker]++; - if (g_PlayerHitsToHeads[attacker] == 5) - { - if (!TF2_IsPlayerInCondition(attacker, TFCond_DemoBuff)) - { - TF2_AddCondition(attacker, TFCond_DemoBuff, -1.0); - } - int decapitations = GetEntProp(attacker, Prop_Send, "m_iDecapitations"); - int health = GetClientHealth(attacker); - SetEntProp(attacker, Prop_Send, "m_iDecapitations", decapitations+1); - SetEntityHealth(attacker, health+15); - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 0.01); - g_PlayerHitsToHeads[attacker] = 0; - } - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Spy) - { - int stabbingTime = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - char weaponClass[64]; - GetEdictClassname(stabbingTime, weaponClass, sizeof(weaponClass)); - if (IsValidEntity(stabbingTime) && stabbingTime == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && (strcmp(weaponClass, "tf_weapon_knife", false) == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy", false) == 0)) && SF_IsBoxingMap()) - { - SubtractVectors(traceEndPos, traceStartPos, buffer); - GetVectorAngles(buffer, buffer); - - if (FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) >= (NPCGetBackstabFOV(bossIndex) * 0.5) && !NPCIsRaging(bossIndex) && GetChaserProfileBackstabDamageScale(profile) > 0.0) - { - damagetype = DMG_CRIT; - EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - SetEntPropFloat(stabbingTime, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); - int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); - if (vm > MaxClients) - { - int meleeIndex = GetEntProp(stabbingTime, Prop_Send, "m_iItemDefinitionIndex"); - int anim = 41; - switch (meleeIndex) - { - case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: - { - anim = 15; - } - case 638: - { - anim = 31; - } - } - SetEntProp(vm, Prop_Send, "m_nSequence", anim); - damage = NPCChaserGetStunInitialHealth(bossIndex) * GetChaserProfileBackstabDamageScale(profile); - NPCSetAddSpeed(bossIndex, 12.5); - NPCSetAddMaxSpeed(bossIndex, 25.0); - NPCSetAddAcceleration(bossIndex, 100.0); - - if (meleeIndex == 356) //Kunai - { - int health = GetClientHealth(attacker) + 100; - if (health > 210) - { - health = 210; - } - SetEntityHealth(attacker, health); - } - switch (meleeIndex) - { - case 356: //Kunai - { - int health = GetClientHealth(attacker) + 100; - if (health > 210) - { - health = 210; - } - SetEntityHealth(attacker, health); - } - case 461: //Big Earner - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - } - } - } - } - } - - if (IsValidClient(attacker) && TF2_IsMiniCritBuffed(attacker))//Mini crit boosted - { - miniCrit = true; - } - - if ((g_SlenderIsMarked[bossIndex]) && !NPCIsRaging(bossIndex)) - { - miniCrit = true; - damage *= 1.35; - } - if (IsValidClient(attacker) && ((SF_IsBoxingMap() && GetClientTeam(attacker) == TFTeam_Blue) || (g_PlayerProxy[attacker]))) - { - damage = 0.0; - } - if (!IsValidClient(attacker)) - { - damage = 0.0; - } - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - if (SF_IsBoxingMap() && IsValidClient(attacker) && !attackEliminated && (GetClientTeam(attacker) == TFTeam_Blue)) - { - damage = 0.0; - } - - ArrayList resistances = GetChaserProfileDamageResistances(profile); - if (resistances != null) - { - for (int i = 0; i < resistances.Length; i++) - { - int type = resistances.Get(i); - if (damagetype & type || damagetype == type) - { - damage = 0.0; - return Plugin_Changed; - } - } - } - - if (NPCGetType(bossIndex) == SF2BossType_Chaser && damage > 0.0) - { - int state = g_SlenderState[bossIndex]; - - if (NPCChaserIsStunEnabled(bossIndex) && !g_SlenderSpawning[bossIndex]) - { - if (g_SlenderNextStunTime[bossIndex] <= GetGameTime() && !NPCIsRaging(bossIndex) && !g_SlenderInDeathcam[bossIndex] && !g_RestartSessionEnabled) - { - NPCChaserAddStunHealth(bossIndex, -damage); - if (NPCChaserGetStunHealth(bossIndex) <= 0.0 && state != STATE_STUN) - { - NPCBossTriggerStun(bossIndex, slender, profile, myPos); - Call_StartForward(g_OnBossStunnedFwd); - Call_PushCell(bossIndex); - Call_PushCell(attacker); - Call_Finish(); - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) - { - int clientMaxHealth = SDKCall(g_SDKGetMaxHealth, attacker); - float healthToRecover = float(clientMaxHealth)/7.5; - int _healthToRecover = RoundToNearest(healthToRecover) + GetEntProp(attacker, Prop_Send, "m_iHealth"); - SetEntityHealth(attacker, _healthToRecover); - } - } - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - - Event event = CreateEvent("npc_hurt"); - if (event) - { - event.SetInt("entindex", slender); - event.SetInt("health", 2147483646); - event.SetInt("damageamount", RoundToFloor(damage)); - event.SetBool("crit", (damagetype & DMG_CRIT) ? true : false); - - if (IsValidClient(attacker)) - { - event.SetInt("attacker_player", GetClientUserId(attacker)); - event.SetInt("weaponid", 0); - } - else - { - event.SetInt("attacker_player", 0); - event.SetInt("weaponid", 0); - } - - event.Fire(); - } - if (IsValidClient(attacker)) - { - if ((damagetype & DMG_CRIT) && !miniCrit) - { - float myEyePos[3]; - SlenderGetAbsOrigin(bossIndex, myEyePos); - float myEyePosEx[3]; - GetEntPropVector(slender, Prop_Send, "m_vecMaxs", myEyePosEx); - myEyePos[2] += myEyePosEx[2]; - - TE_Particle(g_Particles[CriticalHit], myEyePos, myEyePos); - TE_SendToClient(attacker); - - EmitSoundToClient(attacker, CRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - else if (miniCrit) - { - float myEyePos[3]; - SlenderGetAbsOrigin(bossIndex, myEyePos); - float myEyePosEx[3]; - GetEntPropVector(slender, Prop_Send, "m_vecMaxs", myEyePosEx); - myEyePos[2]+=myEyePosEx[2]; - - TE_Particle(g_Particles[MiniCritHit], myEyePos, myEyePos); - TE_SendToClient(attacker); - - EmitSoundToClient(attacker, MINICRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - } - } - - return Plugin_Changed; -} - -void UpdateHealthBar(int bossIndex, int optionalSetPercent = -1) -{ - float maxHealth = NPCChaserGetStunInitialHealth(bossIndex); - float health = NPCChaserGetStunHealth(bossIndex); - if (g_HealthBar == -1) - { - return; - } - int healthPercent; - SetEntProp(g_HealthBar, Prop_Send, "m_iBossState", 0); - healthPercent = RoundToCeil((health/maxHealth)*float(255)); - if (healthPercent>255) - { - healthPercent=255; - } - else if (healthPercent<=0) - { - healthPercent=0; - } - if (optionalSetPercent > -1) - { - healthPercent = optionalSetPercent; - } - SetEntProp(g_HealthBar, Prop_Send, "m_iBossHealthPercentageByte", healthPercent); -} - -void NPCBossTriggerStun(int bossIndex, int victim, char profile[SF2_MAX_PROFILE_NAME_LENGTH], float position[3]) -{ - if (bossIndex == -1) - { - return; - } - if (!victim || victim == INVALID_ENT_REFERENCE) - { - return; - } - int difficulty = GetLocalGlobalDifficulty(bossIndex); - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(victim); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - CBaseCombatCharacter overlay = CBaseCombatCharacter(victim); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - int state = g_SlenderState[bossIndex]; - bool doChasePersistencyInit = false; - overlay.RemoveAllGestures(); - CBaseNPC_RemoveAllLayers(victim); - if (g_LastStuckTime[bossIndex] != 0.0) - { - g_LastStuckTime[bossIndex] = GetGameTime(); - } - loco.ClearStuckStatus(); - g_SlenderState[bossIndex] = STATE_STUN; - if (g_SlenderChaseInitialTimer[bossIndex] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[bossIndex]); - } - if (NPCChaseHasKeyDrop(bossIndex)) - { - if (SF_IsBoxingMap()) - { - g_SlenderBoxingBossKilled += 1; - if ((g_SlenderBoxingBossKilled == g_SlenderBoxingBossCount) && !g_SlenderBoxingBossIsKilled[bossIndex]) - { - NPC_DropKey(bossIndex); - } - g_SlenderBoxingBossIsKilled[bossIndex] = true; - } - else - { - NPC_DropKey(bossIndex); - } - } - - if (NPCChaserCanDropItemOnStun(bossIndex)) - { - switch (NPCChaserItemDropTypeStun(bossIndex)) - { - case 1: - { - int smallammo = CreateEntityByName("item_ammopack_small"); - DispatchKeyValue(smallammo, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(smallammo)) - { - SetEntProp(smallammo, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(smallammo, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 2: - { - int medammo = CreateEntityByName("item_ammopack_medium"); - DispatchKeyValue(medammo, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(medammo)) - { - SetEntProp(medammo, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(medammo, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 3: - { - int fullammo = CreateEntityByName("item_ammopack_full"); - DispatchKeyValue(fullammo, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(fullammo)) - { - SetEntProp(fullammo, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(fullammo, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 4: - { - int smallhp = CreateEntityByName("item_healthkit_small"); - DispatchKeyValue(smallhp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(smallhp)) - { - SetEntProp(smallhp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(smallhp, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 5: - { - int medhp = CreateEntityByName("item_healthkit_medium"); - DispatchKeyValue(medhp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(medhp)) - { - SetEntProp(medhp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(medhp, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 6: - { - int fullhp = CreateEntityByName("item_healthkit_full"); - DispatchKeyValue(fullhp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(fullhp)) - { - SetEntProp(fullhp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(fullhp, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 7: - { - int smallammohp = CreateEntityByName("item_healthammokit"); - DispatchKeyValue(smallammohp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(smallammohp)) - { - SetEntProp(smallammohp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(smallammohp, position, NULL_VECTOR, NULL_VECTOR); - } - } - } - } - if (g_SlenderChaseInitialTimer[bossIndex] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[bossIndex]); - } - g_NpcUsesHealAnimation[bossIndex] = false; - g_NpcUseStartFleeAnimation[bossIndex] = false; - g_NpcIsHealing[bossIndex] = false; - g_NpcIsRunningToHeal[bossIndex] = false; - g_SlenderHealDelayTimer[bossIndex] = null; - g_SlenderHealTimer[bossIndex] = null; - g_SlenderStartFleeTimer[bossIndex] = null; - g_NpcSelfHealStage[bossIndex] = 12; - if (SF_IsBoxingMap() && NPCChaserIsBoxingBoss(bossIndex)) - { - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - char player[32]; - FormatEx(player, sizeof(player), "%N", client); - char bossName[SF2_MAX_NAME_LENGTH]; - NPCGetBossName(bossIndex, bossName, sizeof(bossName)); - if (bossName[0] == '\0') - { - strcopy(bossName, sizeof(bossName), profile); - } - CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Boxing Win Message", player, bossName); - } - } - } - if (state != STATE_CHASE && state != STATE_ATTACK) - { - doChasePersistencyInit = true; - } - if (g_IsSlenderAttacking[bossIndex]) - { - // Cancel attacking. - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - } - - if (g_NpcHasCloaked[bossIndex] && NPCChaserIsCloakEnabled(bossIndex)) - { - SetEntityRenderMode(victim, RENDER_NORMAL); - SetEntityRenderColor(victim, g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], 0); - - g_NpcHasCloaked[bossIndex] = false; - SlenderToggleParticleEffects(victim, true); - GetChaserProfileCloakParticle(profile, cloakParticle, sizeof(cloakParticle)); - if (cloakParticle[0] == '\0') - { - cloakParticle = "drg_cow_explosioncore_charged_blue"; - } - SlenderCreateParticle(bossIndex, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[bossIndex], victim, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[bossIndex] = GetGameTime() + NPCChaserGetCloakCooldown(bossIndex, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(bossIndex); - Call_Finish(); - } - - if (!doChasePersistencyInit) - { - float persistencyTime = GetChaserProfileChaseStunPersistencyTimeInit(profile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[bossIndex] = GetGameTime() + persistencyTime; - } - - persistencyTime = GetChaserProfileChaseStunPersistencyTimeAdd(profile); - if (persistencyTime >= 0.0) - { - if (g_SlenderTimeUntilNoPersistence[bossIndex] < GetGameTime()) - { - g_SlenderTimeUntilNoPersistence[bossIndex] = GetGameTime(); - } - g_SlenderTimeUntilNoPersistence[bossIndex] += persistencyTime; - } - } - else - { - float persistencyTime = GetChaserProfileChaseAttackPersistencyTimeInit(profile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[bossIndex] = GetGameTime() + persistencyTime; - } - } - - NPCChaserUpdateBossAnimation(bossIndex, victim, g_SlenderState[bossIndex]); - - g_SlenderTimeUntilRecover[bossIndex] = GetGameTime() + g_SlenderAnimationDuration[bossIndex]; - - // Sound handling. Ignore time check. - SlenderPerformVoice(bossIndex, _, SF2BossSound_Stun); -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp index 9fd90a62..3aabc616 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp @@ -5,654 +5,86 @@ #pragma semicolon 1 -static bool g_NpcStatueMoving[MAX_BOSSES]; - -static float g_NpcStatueChaseDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStatueChaseDurationAddMin[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStatueChaseDurationAddMax[MAX_BOSSES][Difficulty_Max]; - -static float g_NpcTimeUntilAbandon[MAX_BOSSES] = { -1.0, ... }; - static float g_NpcStatueIdleLifetime[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStatueModelChangeDistMax[MAX_BOSSES][Difficulty_Max]; +static SF2StatueBossProfileData g_StatueProfileData[MAX_BOSSES]; -float NPCStatueChaseDuration(int npcIndex, int difficulty) +SF2StatueBossProfileData NPCStatueGetProfileData(int npcIndex) { - return g_NpcStatueChaseDuration[npcIndex][difficulty]; + return g_StatueProfileData[npcIndex]; } -void NPCStatueOnSelectProfile(const char[] profile, int npcIndex) +void NPCStatueSetProfileData(int npcIndex, SF2StatueBossProfileData value) { - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - g_NpcStatueChaseDuration[npcIndex][difficulty] = GetStatueProfileChaseDuration(profile, difficulty); - g_NpcStatueChaseDurationAddMin[npcIndex][difficulty] = GetStatueProfileChaseDurationAddVisibleMin(profile, difficulty); - g_NpcStatueChaseDurationAddMax[npcIndex][difficulty] = GetStatueProfileChaseDurationAddVisibleMax(profile, difficulty); - - g_NpcStatueIdleLifetime[npcIndex][difficulty] = GetStatueProfileIdleLifeTime(profile, difficulty); - g_NpcStatueModelChangeDistMax[npcIndex][difficulty] = GetStatueProfileModelChangeDistMax(profile, difficulty); - } + g_StatueProfileData[npcIndex] = value; } -void Spawn_Statue(int npcIndex) +float NPCStatueGetIdleLifetime(int npcIndex, int difficulty) { - g_NpcStatueMoving[npcIndex] = false; - g_SlenderTarget[npcIndex] = INVALID_ENT_REFERENCE; - g_SlenderStatueIdleLifeTime[npcIndex] = GetGameTime() + g_NpcStatueIdleLifetime[npcIndex][GetLocalGlobalDifficulty()]; + return g_NpcStatueIdleLifetime[npcIndex][difficulty]; } -void SlenderStatueBossProcessMovement(int bossEnt) +void NPCStatueOnSelectProfile(int npcIndex) { - if (!g_Enabled) - { - return; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(bossEnt); - if (npc == INVALID_NPC) - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderStatueBossProcessMovement); //What no boss? - return; - } - - int bossIndex = NPCGetFromEntIndex(bossEnt); - if (bossIndex == -1) - { - //Boss is invalid somehow, and the hook wasn't killed. - SDKUnhook(bossEnt, SDKHook_Think, SlenderStatueBossProcessMovement); - return; - } - - //From Pelipoika's rainbow outline plugin - if (NPCGetCustomOutlinesState(bossIndex) && NPCGetRainbowOutlineState(bossIndex)) - { - int glow = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - int color[4]; - color[0] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 0) * 127.5 + 127.5); - color[1] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 2) * 127.5 + 127.5); - color[2] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 4) * 127.5 + 127.5); - color[3] = 255; - if (IsValidEntity(glow)) - { - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - } - } - - INextBot bot = npc.GetBot(); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - CBaseCombatCharacter combatChar = CBaseCombatCharacter(bossEnt); - - char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, slenderProfile, sizeof(slenderProfile)); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float myPos[3], myEyeAng[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", myPos); - GetEntPropVector(bossEnt, Prop_Data, "m_angAbsRotation", myEyeAng); - - int state = g_SlenderState[bossIndex]; - g_SlenderOldState[bossIndex] = state; - - float originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - float speed = originalSpeed; - speed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - - if (!g_SlenderInDeathcam[bossIndex] && g_NpcStatueMoving[bossIndex]) - { - npc.flWalkSpeed = speed * 10.0; - npc.flRunSpeed = speed * 10.0; - } - else - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - npc.flAcceleration = 50000.0; - - // Process angles. - bool changeAngle = false; - float posToAt[3]; - if (g_NpcStatueMoving[bossIndex]) - { - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - - if (changeAngle) - { - float ang[3]; - SubtractVectors(posToAt, myPos, ang); - GetVectorAngles(ang, ang); - AddVectors(ang, g_SlenderEyeAngOffset[bossIndex], ang); - for (int cell2 = 0; cell2 < 3; cell2++) - { - ang[cell2] = AngleNormalize(ang[cell2]); - } - ang[0] = 0.0; - TeleportEntity(bossEnt, NULL_VECTOR, ang, NULL_VECTOR); - } - - if (!g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) - { - if (g_NpcStatueMoving[bossIndex]) - { - loco.Run(); - g_BossPathFollower[bossIndex].Update(bot); - bot.Update(); - } - else - { - loco.Stop(); - } - if (loco.IsOnGround() && !loco.IsClimbingOrJumping() && g_NpcStatueMoving[bossIndex]) - { - float pathNodePos[3], pathEndPos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetForward(pathNodePos); - segment.GetPos(pathEndPos); - ScaleVector(pathNodePos, segment.length); - AddVectors(pathEndPos, pathNodePos, pathEndPos); - - if (g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, JUMP_OVER_GAP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, CLIMB_UP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, LADDER_UP, 120.0)) - { - CBaseNPC_Jump(loco, myPos, pathEndPos); - } - } - } - } - - if (!loco.IsClimbingOrJumping() && !g_SlenderSpawning[bossIndex]) - { - float hullcheckmins[3], hullcheckmaxs[3]; - hullcheckmins = HULL_HUMAN_MINS; - hullcheckmaxs = HULL_HUMAN_MAXS; - - hullcheckmins[0] -= 20.0; - hullcheckmins[1] -= 20.0; - - hullcheckmaxs[0] += 20.0; - hullcheckmaxs[1] += 20.0; - - hullcheckmins[2] += loco.GetStepHeight(); - hullcheckmaxs[2] += 5.0; - - if (!g_NpcVelocityCancel[bossIndex] && IsSpaceOccupiedIgnorePlayersAndEnts(myPos, hullcheckmins, hullcheckmaxs, bossEnt))//The boss will start to merge with shits, cancel out velocity. - { - float origin[3]; - loco.SetVelocity(origin); - g_NpcVelocityCancel[bossIndex] = true; - } - } - else + SF2NPC_Statue statue = SF2NPC_Statue(npcIndex); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(npcIndex, profile, sizeof(profile)); + g_StatueBossProfileData.GetArray(profile, g_StatueProfileData[npcIndex], sizeof(g_StatueProfileData[])); + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - g_NpcVelocityCancel[bossIndex] = false; + g_NpcStatueIdleLifetime[npcIndex][difficulty] = g_StatueProfileData[npcIndex].IdleLifeTime[difficulty]; } - if (g_NpcStatueMoving[bossIndex] && !g_SlenderInDeathcam[bossIndex] && GetGameTime() < g_NpcTimeUntilAbandon[bossIndex] && (GetGameTime() - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) - { - bool runUnstuck = true; - if (runUnstuck) - { - if (GetVectorSquareMagnitude(myPos, g_LastPos[bossIndex]) < 0.1 || loco.IsStuck()) - { - bool blockingProp = false; - - if (!blockingProp) - { - if (g_LastStuckTime[bossIndex] == 0.0) g_LastStuckTime[bossIndex] = GetGameTime(); - - if ((g_LastStuckTime[bossIndex] <= GetGameTime()-1.0 || loco.GetStuckDuration() >= 1.0) && - !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex]) - { - float movePos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else - { - g_BossPathFollower[bossIndex].GetClosestPosition(myPos, movePos); - } - bool pathResolved = false; - - if (SlenderChaseBoss_OnStuckResolvePath(bossEnt, myPos, myEyeAng, movePos, movePos)) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - CNavArea area = combatChar.GetLastKnownArea(); - if (area == NULL_AREA && segment != NULL_PATH_SEGMENT) - { - area = segment.area; - } - if (area != NULL_AREA) - { - area.GetCenter(movePos); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!SF_IsSlaughterRunMap() && !g_RestartSessionEnabled) - { - RemoveSlender(bossIndex);//We are stuck there's no way out for us, unspawn, players are just going to abuse that we are stuck. - } - else - { - ArrayList spawnPoint = new ArrayList(); - float teleportPos[3]; - int ent = -1, spawnTeam = 0; - while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) - { - spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); - if (spawnTeam == TFTeam_Red) - { - spawnPoint.Push(ent); - } - - } - ent = -1; - if (spawnPoint.Length > 0) - { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); - } - - delete spawnPoint; - - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - } - } - } - } - if (pathResolved) - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; - } - } - } - } - else - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; - g_LastPos[bossIndex] = myPos; - } - } - } - - return; + statue.SetAffectedBySight(true); } -void SlenderStatueSetNextThink(int bossEnt) +SF2_StatueEntity Spawn_Statue(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) { - if (!g_Enabled) - { - return; - } - - CBaseCombatCharacter(bossEnt).SetNextThink(GetGameTime()); - - return; + g_SlenderStatueIdleLifeTime[controller.Index] = GetGameTime() + g_NpcStatueIdleLifetime[controller.Index][controller.Difficulty]; + return SF2_StatueEntity.Create(controller, pos, ang); } -Action Timer_SlenderBlinkBossThink(Handle timer, any entref) +void Despawn_Statue(SF2NPC_Statue controller, CBaseEntity bossEnt) { - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + // Stop all possible looping sounds. + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.MoveSounds; + soundInfo.StopAllSounds(bossEnt.index); +} - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } +void NPCStatue_InitializeAPI() +{ + CreateNative("SF2_GetStatueProfileFromBossIndex", Native_GetProfileData); + CreateNative("SF2_GetStatueProfileFromName", Native_GetProfileDataEx); - if (timer != g_SlenderEntityThink[bossIndex]) - { - return Plugin_Stop; - } + SF2_StatueEntity.SetupAPI(); +} - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - if (npc == INVALID_NPC) +static any Native_GetProfileData(Handle plugin, int numParams) +{ + SF2NPC_Statue controller = SF2NPC_Statue(GetNativeCell(1)); + if (!controller.IsValid()) { - return Plugin_Stop; + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); } - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - INextBot bot = npc.GetBot(); - - int difficulty = g_DifficultyConVar.IntValue; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} +static any Native_GetProfileDataEx(Handle plugin, int numParams) +{ char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - bool attackWaiters = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - if (NPCGetType(bossIndex) == SF2BossType_Statue) + GetNativeString(1, profile, sizeof(profile)); + SF2StatueBossProfileData data; + if (!g_StatueBossProfileData.GetArray(profile, data, sizeof(data))) { - float gameTime = GetGameTime(); - float chaseDurationTimeAddMin = g_NpcStatueChaseDurationAddMin[bossIndex][difficulty]; - float chaseDurationTimeAddMax = g_NpcStatueChaseDurationAddMax[bossIndex][difficulty]; - bool move = false; - if (PeopleCanSeeSlender(bossIndex, true, SlenderUsesBlink(bossIndex), !attackWaiters)) - { - if (g_NpcTimeUntilAbandon[bossIndex] < gameTime) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - g_NpcTimeUntilAbandon[bossIndex] += chaseDurationTimeAddMin; - if (g_NpcTimeUntilAbandon[bossIndex] > (gameTime + NPCStatueChaseDuration(bossIndex, difficulty))) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - for (int i = 0; i < MAX_NPCTELEPORTER; i++) - { - if (NPCGetTeleporter(bossIndex, i) != INVALID_ENT_REFERENCE) - { - NPCSetTeleporter(bossIndex, i, INVALID_ENT_REFERENCE); - } - } - } - - int origTarget = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap() && IsValidClient(origTarget)) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - - if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap()) - { - if (!IsValidClient(origTarget) || (IsValidClient(origTarget) && g_PlayerEliminated[origTarget])) - { - if (NPCAreAvailablePlayersAlive()) - { - ArrayList arrayRaidTargets = new ArrayList(); - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || - !IsPlayerAlive(i) || - g_PlayerEliminated[i] || - IsClientInGhostMode(i) || - DidClientEscape(i)) - { - continue; - } - arrayRaidTargets.Push(i); - } - if(arrayRaidTargets.Length > 0) - { - int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); - if(IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) - { - g_SlenderTarget[bossIndex] = EntIndexToEntRef(raidTarget); - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - } - delete arrayRaidTargets; - } - } - } - - int bestPlayer = -1; - ArrayList array = new ArrayList(); - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || IsClientInDeathCam(i) || (!attackWaiters && g_PlayerEliminated[i]) || DidClientEscape(i) || IsClientInGhostMode(i) || !PlayerCanSeeSlender(i, bossIndex, false, false, !attackWaiters)) - { - continue; - } - - if (!NPCShouldSeeEntity(bossIndex, i)) - { - continue; - } - - array.Push(i); - } - - if (array.Length) - { - float slenderPos[3]; - SlenderGetAbsOrigin(bossIndex, slenderPos); - - float tempPos[3]; - int tempPlayer = -1; - float tempDist = SquareFloat(16384.0); - for (int i = 0; i < array.Length; i++) - { - int client = array.Get(i); - GetClientAbsOrigin(client, tempPos); - if (GetVectorSquareMagnitude(tempPos, slenderPos) < tempDist) - { - tempPlayer = client; - tempDist = GetVectorSquareMagnitude(tempPos, slenderPos); - } - if (SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(tempPos, slenderPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) - { - TF2_StunPlayer(client, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); - } - } - - bestPlayer = tempPlayer; - if (bestPlayer != -1) - { - g_SlenderTarget[bossIndex] = EntIndexToEntRef(bestPlayer); - if (g_NpcTimeUntilAbandon[bossIndex] < gameTime) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - } - } - - delete array; - - if (!PeopleCanSeeSlender(bossIndex, true, SlenderUsesBlink(bossIndex), !attackWaiters)) - { - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - if (IsTargetValidForSlender(target, attackWaiters) && gameTime < g_NpcTimeUntilAbandon[bossIndex] && (gameTime - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) - { - move = true; - float slenderPos[3], pos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPos); - target.GetAbsOrigin(pos); - if (NPCGetTeleporter(bossIndex, 0) != INVALID_ENT_REFERENCE) - { - int teleporter = EntRefToEntIndex(NPCGetTeleporter(bossIndex, 0)); - if (IsValidEntity(teleporter) && teleporter > MaxClients) - { - GetEntPropVector(teleporter, Prop_Data, "m_vecAbsOrigin", pos); - } - } - g_BossPathFollower[bossIndex].ComputeToPos(bot, pos); - - float maxRange = g_NpcStatueModelChangeDistMax[bossIndex][difficulty]; - float dist = GetVectorSquareMagnitude(slenderPos, pos); - - char buffer[PLATFORM_MAX_PATH]; - - if (dist < SquareFloat(maxRange * 0.33)) - { - GetSlenderModel(bossIndex, 2, buffer, sizeof(buffer)); - } - else if (dist < SquareFloat(maxRange * 0.66)) - { - GetSlenderModel(bossIndex, 1, buffer, sizeof(buffer)); - } - else - { - GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); - } - - // Fallback if error. - if (buffer[0] == '\0' || strcmp(buffer, "models/") == 0) - { - GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); - } - - SetEntityModel(slender, buffer); - - if (target.CanSeeSlender(bossIndex, false, !attackWaiters)) - { - float distRatio = (dist / SquareFloat(maxRange)); - - float chaseDurationAdd = chaseDurationTimeAddMax - ((chaseDurationTimeAddMax - chaseDurationTimeAddMin) * distRatio); - - if (chaseDurationAdd > 0.0) - { - g_NpcTimeUntilAbandon[bossIndex] += chaseDurationAdd; - if (g_NpcTimeUntilAbandon[bossIndex] > (gameTime + NPCStatueChaseDuration(bossIndex, difficulty))) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - } - } - - if (dist <= SquareFloat(NPCGetInstantKillRadius(bossIndex))) - { - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - return Plugin_Stop; - } - else - { - g_SlenderLastKill[bossIndex] = gameTime; - target.StartDeathCam(bossIndex, pos); - g_LastStuckTime[bossIndex] = gameTime + NPCGetInstantKillCooldown(bossIndex, difficulty) + 0.1; - if (NPCGetInstantKillCooldown(bossIndex, difficulty) > 0.0) - { - g_NpcStatueMoving[bossIndex] = false; - loco.ClearStuckStatus(); - } - g_SlenderTarget[bossIndex] = INVALID_ENT_REFERENCE; - } - } - - g_SlenderStatueIdleLifeTime[bossIndex] = gameTime + g_NpcStatueIdleLifetime[bossIndex][difficulty]; - } - else - { - float origin[3]; - SlenderGetAbsOrigin(bossIndex, origin); - origin[2] -= 10.0; - g_LastPos[bossIndex] = origin; - } - } - - SF2BossProfileSoundInfo soundInfo; - if (move) - { - ArrayList soundList; - char buffer[PLATFORM_MAX_PATH]; - GetStatueProfileSingleMoveSounds(profile, soundInfo); - soundInfo.EmitSound(_, slender); - - GetStatueProfileMoveSounds(profile, soundInfo); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0') - { - EmitSoundToAll(buffer, slender, soundInfo.Channel, soundInfo.Level, SND_CHANGEVOL, soundInfo.Volume, soundInfo.Pitch); - } - } - soundList = null; - } - else - { - GetStatueProfileMoveSounds(profile, soundInfo); - soundInfo.StopAllSounds(slender); - } - g_NpcStatueMoving[bossIndex] = move; + return false; } - return Plugin_Continue; -} \ No newline at end of file + SetNativeArray(2, data, sizeof(data)); + return true; +} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_death_msg.sp b/addons/sourcemod/scripting/sf2/npc/npc_death_msg.sp deleted file mode 100644 index a60225eb..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_death_msg.sp +++ /dev/null @@ -1 +0,0 @@ -//Work in progress \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/old_pvp.sp b/addons/sourcemod/scripting/sf2/old_pvp.sp new file mode 100644 index 00000000..ce8a5dd5 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/old_pvp.sp @@ -0,0 +1,1230 @@ +#if defined _sf2_pvp_included + #endinput +#endif +#define _sf2_pvp_included + +#pragma semicolon 1 + +#define SF2_PVP_SPAWN_SOUND "items/pumpkin_drop.wav" +#define FLAME_HIT_DELAY 0.05 + +static ConVar g_PvPArenaLeaveTimeConVar = null; +static ConVar g_PvPArenaProjectileZapConVar = null; + +static const char g_PvPProjectileClasses[][] = +{ + "tf_projectile_rocket", + "tf_projectile_sentryrocket", + "tf_projectile_stun_ball", + "tf_projectile_ball_ornament", + "tf_projectile_cleaver", + "tf_projectile_energy_ball", + "tf_projectile_flare", + "tf_projectile_balloffire", + "tf_projectile_jar", + "tf_projectile_jar_milk", + "tf_projectile_pipe", + "tf_projectile_pipe_remote", + "tf_projectile_stun_ball", + "tf_projectile_throwable_breadmonster", + "tf_projectile_throwable_brick", + "tf_projectile_throwable", + //Don't change + "tf_projectile_arrow", + "tf_projectile_healing_bolt", + "tf_projectile_energy_ring", + "tf_projectile_syringe" +}; + +static const char g_PvPProjectileClassesNoTouch[][] = +{ + "tf_projectile_flare" +}; + +static bool g_PlayerInPvP[MAXTF2PLAYERS]; +static bool g_PlayerIsLeavingPvP[MAXTF2PLAYERS]; +Handle g_PlayerPvPTimer[MAXTF2PLAYERS]; +Handle g_PlayerPvPRespawnTimer[MAXTF2PLAYERS]; +static int g_PlayerPvPTimerCount[MAXTF2PLAYERS]; +static ArrayList g_PlayerEnteredPvPTriggers[MAXTF2PLAYERS] = { null, ... }; + +//Blood +static int g_PvPUserIdLastTrace; +static float g_TimeLastTrace; + +static ArrayList g_PvPBallsOfFire; + +static GlobalForward g_OnPlayerEnterPvP; +static GlobalForward g_OnPlayerExitPvP; + +enum struct PvPProjectile_BallOfFire +{ + int EntIndex; + ArrayList TouchedEntities; + + void Init(int entIndex) + { + this.EntIndex = entIndex; + this.TouchedEntities = new ArrayList(); + } + + void OnTouchPost(int otherEntity) + { + if (IsRoundEnding()) + { + return; + } + + int ent = this.EntIndex; + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + if (!IsValidClient(ownerEntity) || otherEntity == ownerEntity || !IsClientInPvP(ownerEntity)) + { + return; + } + + if (IsValidEntity(otherEntity)) + { + if (this.TouchedEntities.FindValue(otherEntity) == -1) + { + this.TouchedEntities.Push(otherEntity); + + if (IsValidClient(otherEntity) && IsClientInPvP(otherEntity) && GetEntProp(otherEntity, Prop_Send, "m_iTeamNum") == GetEntProp(ownerEntity, Prop_Send, "m_iTeamNum")) + { + float damage = GetEntDataFloat(ent, FindSendPropInfo("CTFProjectile_BallOfFire", "m_iDeflected") + 4); + float damageBonus = TF2_IsPlayerInCondition(otherEntity, TFCond_OnFire) ? g_DragonsFuryBurningBonusConVar.FloatValue : 1.0; + float damagePos[3]; + GetEntPropVector(ent, Prop_Data, "m_vecOrigin", damagePos); + // int damageCustom = 79; + + if (TF2_GetPlayerClass(otherEntity) == TFClass_Pyro) + { + TF2_AddCondition(otherEntity, TFCond_BurningPyro, g_DragonsFuryBurnDurationConVar.FloatValue * 0.5, ownerEntity); + } + + TF2_IgnitePlayer(otherEntity, ownerEntity); + SDKHooks_TakeDamage(otherEntity, ownerEntity, ownerEntity, damage * damageBonus, 0x1220000, GetEntPropEnt(ownerEntity, Prop_Send, "m_hActiveWeapon"), NULL_VECTOR, damagePos); + } + } + } + } + + void Cleanup() + { + delete this.TouchedEntities; + } +} + +void PvP_Initialize() +{ + g_PvPArenaLeaveTimeConVar = CreateConVar("sf2_player_pvparena_leavetime", "5"); + g_PvPArenaProjectileZapConVar = CreateConVar("sf2_pvp_projectile_removal", "0", "This is an experimental code! It could make your server crash, if you get any crash disable this cvar"); + + g_PvPBallsOfFire = new ArrayList(sizeof(PvPProjectile_BallOfFire)); + + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnGameFramePFwd.AddFunction(null, GameFrame); + g_OnRoundStartPFwd.AddFunction(null, RoundStart); + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + + AddTempEntHook("TFBlood", TempEntHook_PvPBlood); + AddTempEntHook("World Decal", TempEntHook_PvPDecal); + AddTempEntHook("Entity Decal", TempEntHook_PvPDecal); +} + +void PvP_SetupMenus() +{ + g_MenuSettingsPvP = new Menu(Menu_SettingsPvP); + g_MenuSettingsPvP.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Settings PvP Menu Title"); + g_MenuSettingsPvP.AddItem("0", "Toggle automatic spawning"); + g_MenuSettingsPvP.AddItem("1", "Toggle spawn protection"); + g_MenuSettingsPvP.ExitBackButton = true; +} + +static void MapStart() +{ + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + if (IsValidEntity(ent)) + { + char strName[50]; + GetEntPropString(ent, Prop_Data, "m_iName", strName, sizeof(strName)); + if (strcmp(strName, "sf2_pvp_trigger") == 0) + { + //StartTouch seems to be unreliable if a player is teleported/spawned in the trigger + //SDKHook(ent, SDKHook_StartTouch, PvP_OnTriggerStartTouch); + //But end touch works fine. + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch); + } + } + } + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_respawnroom")) != -1) + { + if (IsValidEntity(ent) && SF_IsBoxingMap()) + { + SDKHook(ent, SDKHook_StartTouch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_Touch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerStartTouchBoxing); + } + } +} + +static void RoundStart() +{ + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + if (IsValidEntity(ent)) + { + char strName[50]; + GetEntPropString(ent, Prop_Data, "m_iName", strName, sizeof(strName)); + if (strcmp(strName, "sf2_pvp_trigger") == 0) + { + //Add physics object flag, so we can zap projectiles! + int flags = GetEntProp(ent, Prop_Data, "m_spawnflags"); + flags |= TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS; + //flags |= TRIGGER_PHYSICS_OBJECTS; + flags |= TRIGGER_PHYSICS_DEBRIS; + SetEntProp(ent, Prop_Data, "m_spawnflags", flags); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch); + } + } + } + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_respawnroom")) != -1) + { + if (IsValidEntity(ent) && SF_IsBoxingMap()) + { + //Add physics object flag, so we can zap projectiles! + int flags = GetEntProp(ent, Prop_Data, "m_spawnflags"); + flags |= TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS; + //flags |= TRIGGER_PHYSICS_OBJECTS; + flags |= TRIGGER_PHYSICS_DEBRIS; + SetEntProp(ent, Prop_Data, "m_spawnflags", flags); + SDKHook(ent, SDKHook_StartTouch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_Touch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerStartTouchBoxing); + } + } +} + +void PvP_Precache() +{ + PrecacheSound2(SF2_PVP_SPAWN_SOUND, true); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + g_PlayerEnteredPvPTriggers[client.index] = new ArrayList(); + + PvP_ForceResetPlayerPvPData(client.index); + + SDKHook(client.index, SDKHook_TraceAttack, ClientTraceAttack); + SDKHook(client.index, SDKHook_WeaponEquipPost, ClientWeaponEquipPost); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + PvP_SetPlayerPvPState(client.index, false, false, false); + + if (g_PlayerEnteredPvPTriggers[client.index] != null) + { + delete g_PlayerEnteredPvPTriggers[client.index]; + g_PlayerEnteredPvPTriggers[client.index] = null; + } +} + +static void GameFrame() +{ + // Process through PvP projectiles. + for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, g_PvPProjectileClasses[i])) != -1) + { + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + bool changeProjectileTeam = false; + + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + if (IsValidClient(ownerEntity) && IsClientInPvP(ownerEntity) && GetClientTeam(ownerEntity) != TFTeam_Red) + { + changeProjectileTeam = true; + } + else if (throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + if (IsValidClient(ownerEntity) && IsClientInPvP(ownerEntity) && GetClientTeam(ownerEntity) != TFTeam_Red) + { + changeProjectileTeam = true; + } + } + + if (changeProjectileTeam) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + } + } + + // Process through PvP flame entities. + { + static float mins[3] = { -6.0, ... }; + static float maxs[3] = { 6.0, ... }; + + float origin[3]; + + Handle trace = null; + int ent = -1; + int ownerEntity = INVALID_ENT_REFERENCE; + int hitEntity = INVALID_ENT_REFERENCE; + + while ((ent = FindEntityByClassname(ent, "tf_flame_manager")) != -1) + { + ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (IsValidEdict(ownerEntity)) + { + // tf_flame's initial owner SHOULD be the flamethrower that it originates from. + // If not, then something's completely bogus. + + ownerEntity = GetEntPropEnt(ownerEntity, Prop_Data, "m_hOwnerEntity"); + } + + if (IsValidClient(ownerEntity) && (IsRoundInWarmup() || IsClientInPvP(ownerEntity))) + { + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", origin); + + trace = TR_TraceHullFilterEx(origin, origin, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, ownerEntity); + hitEntity = TR_GetEntityIndex(trace); + delete trace; + + if (IsValidEntity(hitEntity)) + { + PvP_OnFlameEntityStartTouchPost(ent, hitEntity); + } + } + } + delete trace; + } +} + +static void EntityCreated(CBaseEntity ent, const char[] classname) +{ + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x083EFF3EFF+ %i(%s)", ent.index, classname); + #endif + for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + SDKHook(ent.index, SDKHook_Spawn, Hook_PvPProjectileSpawn); + SDKHook(ent.index, SDKHook_SpawnPost, Hook_PvPProjectileSpawnPost); + break; + } + } + + for (int i = 0; i < sizeof(g_PvPProjectileClassesNoTouch); i++) + { + if (strcmp(classname, g_PvPProjectileClassesNoTouch[i], false) == 0) + { + SDKHook(ent.index, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + break; + } + } +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x08FF4040FF- %i(%s)", ent.index, classname); + #endif + + if (strcmp(classname, "tf_projectile_balloffire", false) == 0) + { + int index = g_PvPBallsOfFire.FindValue(ent.index); + if (index != -1) + { + PvPProjectile_BallOfFire projectileData; + g_PvPBallsOfFire.GetArray(index, projectileData, sizeof(projectileData)); + projectileData.Cleanup(); + g_PvPBallsOfFire.Erase(index); + } + } +} + +static void ClientWeaponEquipPost(int client, int weapon) +{ + if (!IsValidClient(client) || !IsValidEdict(weapon)) + { + return; + } + g_DHookWeaponGetCustomDamageType.HookEntity(Hook_Pre, weapon, WeaponGetCustomDamageType); +} + +static Action Hook_PvPProjectile_OnTouch(int projectile, int client) +{ + // Check if the projectile hit a player outside of pvp area + // Without that, cannon balls can bounce players which should not happen because they are outside of pvp. + bool remove = false; + if (IsValidClient(client) && !IsClientInPvP(client)) + { + remove = true; + } + + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (owner == client) + { + remove = false; + } + + if (remove) + { + float vel[3]; + CBaseEntity(client).GetAbsVelocity(vel); + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vel); + RemoveEntity(projectile); + return Plugin_Handled; + } + + return Plugin_Continue; +} + +static Action Hook_PvPProjectileSpawn(int ent) +{ + char class[64]; + GetEntityClassname(ent, class, sizeof(class)); + + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (ownerEntity == -1 && throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + } + + if (IsValidClient(ownerEntity)) + { + if (IsClientInPvP(ownerEntity)) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + } + + return Plugin_Continue; +} + +static void Hook_PvPProjectileSpawnPost(int ent) +{ + if (!IsValidEntity(ent)) + { + return; + } + + char class[64]; + GetEntityClassname(ent, class, sizeof(class)); + + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (ownerEntity == -1 && throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + } + + if (IsValidClient(ownerEntity)) + { + if (IsRoundInWarmup() || IsClientInPvP(ownerEntity)) + { + static const char fixWeaponNotCollidingWithTeammates[][] = + { + "tf_projectile_rocket", + "tf_projectile_sentryrocket", + "tf_projectile_flare" + }; + + for (int i = 0; i < sizeof(fixWeaponNotCollidingWithTeammates); i++) + { + if (IsValidEntity(ent) && strcmp(class, fixWeaponNotCollidingWithTeammates[i], false) == 0) + { + DHookEntity(g_DHookProjectileCanCollideWithTeammates, false, ent, _, Hook_PvPProjectileCanCollideWithTeammates); + break; + } + } + + if (strcmp(class, "tf_projectile_pipe", false) == 0 && GetEntProp(ent, Prop_Send, "m_iType") == 3) + { + /* + Loose Cannon's projectiles + KR: I'm assuming that stopping non-PvP players from getting bounced is the reason why this is implemented. + Despite hooking onto Touch, the knockback still happens half of the time. StartTouch yields the same result + so this is really the best that can be done. + */ + SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + } + else if (strcmp(class, "tf_projectile_balloffire", false) == 0) + { + /* + Replicate projectile logic for Dragon's Fury projectiles. + KR: The projectile checks the team of its owner entity (the player), not itself. CBaseEntity::InSameTeam() + could be hooked to change this, but I think that would be too game-changing and not really worth doing for + a single case, so the enemy player logic is sort of replicated. + */ + + PvPProjectile_BallOfFire projectileData; + projectileData.Init(ent); + g_PvPBallsOfFire.PushArray(projectileData, sizeof(projectileData)); + + SDKHook(ent, SDKHook_TouchPost, Hook_PvPProjectileBallOfFireTouchPost); + } + + if (g_PlayerPvPTimer[ownerEntity] == null) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + else + { + //Client is not in pvp, remove the projectile + if (g_PvPArenaProjectileZapConVar.BoolValue) + { + PvP_ZapProjectile(ent, false); + } + } + } + } +} + +static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntity) +{ + int index = g_PvPBallsOfFire.FindValue(projectile); + if (index != -1) + { + PvPProjectile_BallOfFire projectileData; + g_PvPBallsOfFire.GetArray(index, projectileData, sizeof(projectileData)); + projectileData.OnTouchPost(otherEntity); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + PvP_SetPlayerPvPState(client.index, false, false, false); + + g_PlayerIsLeavingPvP[client.index] = false; + + if (client.IsAlive && client.IsParticipating) + { + if (!client.IsInGhostMode && !client.IsProxy) + { + if (client.IsEliminated || client.HasEscaped) + { + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + + if (autoSpawn) + { + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.12, Timer_TeleportPlayerToPvP, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + else + { + g_PlayerPvPRespawnTimer[client.index] = null; + } + } + } +} + +void PvP_ZapProjectile(int projectile, bool effects=true) +{ + if (!IsValidEntity(projectile)) + { + return; + } + + //Add zap effects + if (effects) + { + float pos[3]; + GetEntPropVector(projectile, Prop_Send, "m_vecOrigin", pos); + //Spawn the particle. + TE_Particle(g_Particles[ZapParticle], pos, pos); + TE_SendToAll(); + //Play zap sound. + EmitSoundToAll(ZAP_SOUND, projectile, SNDCHAN_AUTO, SNDLEVEL_CAR); + SetEntityRenderMode(projectile, RENDER_TRANSCOLOR); + SetEntityRenderColor(projectile, 0, 0, 0, 1); + } + RemoveEntity(projectile); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + if (client.IsInPvP) + { + g_PlayerIsLeavingPvP[client.index] = false; + } + + if (!client.IsInGhostMode && !client.IsProxy) + { + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + + if (autoSpawn) + { + if (client.IsEliminated || client.HasEscaped) + { + if (!IsRoundEnding()) + { + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.3, Timer_RespawnPlayer, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + + PvP_SetPlayerPvPState(client.index, false, false, false); + } +} + +void PvP_OnClientGhostModeEnable(int client) +{ + g_PlayerPvPRespawnTimer[client] = null; +} + +void PvP_OnTriggerStartTouch(int trigger,int other) +{ + char name[64]; + GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); + + if (StrContains(name, "sf2_pvp_trigger", false) == 0 || SF2TriggerPvPEntity(trigger).IsValid()) + { + if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + { + if (!g_PlayerEliminated[other] && !DidClientEscape(other)) + { + return; + } + //Use valve's kill code if the player is stuck. + if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) + { + TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); + } + //Resize the player. + SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); + + int entRef = EnsureEntRef(trigger); + if (g_PlayerEnteredPvPTriggers[other].FindValue(entRef) == -1) + { + g_PlayerEnteredPvPTriggers[other].Push(entRef); + } + + if (IsClientInPvP(other)) + { + if (g_PlayerIsLeavingPvP[other]) + { + // Player left and came back again, but is still in PvP mode. + g_PlayerPvPTimerCount[other] = 0; + g_PlayerPvPTimer[other] = null; + g_PlayerIsLeavingPvP[other] = false; + } + } + else + { + PvP_SetPlayerPvPState(other, true); + } + } + } +} + +void PvP_OnTriggerEndTouch(int trigger, int other) +{ + if (IsValidClient(other)) + { + if (g_PlayerEnteredPvPTriggers[other] != null) + { + int triggerEntRef = EnsureEntRef(trigger); + for (int i = g_PlayerEnteredPvPTriggers[other].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredPvPTriggers[other].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + } + } + + if (IsClientInPvP(other)) + { + if (g_PlayerEnteredPvPTriggers[other].Length == 0) + { + g_PlayerPvPTimerCount[other] = g_PvPArenaLeaveTimeConVar.IntValue; + if (g_PlayerPvPTimerCount[other] != 0) + { + g_PlayerPvPTimer[other] = CreateTimer(1.0, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerIsLeavingPvP[other] = true; + } + else + { + g_PlayerPvPTimer[other] = CreateTimer(0.1, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + + //A projectile went off pvp area. + if (other > MaxClients && IsValidEntity(other)) + { + //Get entity's classname. + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + //Yup it's a projectile zap it! + //But we have to wait to prevent some bugs. + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static void PvP_OnTriggerStartTouchBoxing(int trigger, int other) +{ + if (other > MaxClients && IsValidEntity(other) && !IsRoundInWarmup() && !IsRoundEnding()) + { + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static Action EntityStillAlive(Handle timer, int ref) +{ + int ent = EntRefToEntIndex(ref); + if (ent && ent != INVALID_ENT_REFERENCE) + { + PvP_ZapProjectile(ent); + } + return Plugin_Stop; +} +/** + * Enables/Disables PvP mode on the player. + */ +void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles = true, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool oldInPvP = g_PlayerInPvP[player.index]; + if (status == oldInPvP) + { + return; // no change + } + + if (status && player.IsInPvE) + { + PvE_ForceResetPlayerPvEData(player.index); + PvE_SetPlayerPvEState(player.index, false, false); + } + + g_PlayerInPvP[player.index] = status; + g_PlayerPvPTimer[player.index] = null; + g_PlayerPvPRespawnTimer[player.index] = null; + g_PlayerPvPTimerCount[player.index] = 0; + + if (removeProjectiles) + { + // Remove previous projectiles. + PvP_RemovePlayerProjectiles(player.index); + } + + if (status) + { + Call_StartForward(g_OnPlayerEnterPvP); + Call_PushCell(player.index); + Call_Finish(); + } + else + { + Call_StartForward(g_OnPlayerExitPvP); + Call_PushCell(player.index); + Call_Finish(); + } + + if (regenerate) + { + // Regenerate player but keep health the same. + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +static void PvP_OnFlameEntityStartTouchPost(int flame, int other) //Thanks Fire +{ + static float lastHit[MAXPLAYERS + 1]; + + SF2_BasePlayer client = SF2_BasePlayer(other); + if (client.IsValid) + { + float time = GetEngineTime(); + if (lastHit[client.index] < time) + { + lastHit[client.index] = time + FLAME_HIT_DELAY; + + if ((IsRoundInWarmup() || client.IsInPvP) && !IsRoundEnding()) + { + int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); + if (IsValidEdict(flamethrower)) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.index != client.index && ownerEntity.IsValid) + { + if (IsRoundInWarmup() || ownerEntity.IsInPvP) + { + if (client.Team == ownerEntity.Team && ownerEntity.Team != TFTeam_Red) + { + //TF2_MakeBleed(other, ownerEntity, 4.0); + client.Ignite(_, ownerEntity.index); + client.TakeDamage(_, ownerEntity.index, ownerEntity.index, 10.0, ownerEntity.IsCritBoosted() ? (DMG_BURN | DMG_PREVENT_PHYSICS_FORCE | DMG_ACID) : DMG_BURN | DMG_PREVENT_PHYSICS_FORCE); + } + } + } + } + } + } + } +} + +/** + * Forcibly resets global vars of the player relating to PvP. Ignores checking. + */ +void PvP_ForceResetPlayerPvPData(int client) +{ + g_PlayerInPvP[client] = false; + g_PlayerPvPTimer[client] = null; + g_PlayerPvPTimerCount[client] = 0; + g_PlayerPvPRespawnTimer[client] = null; + g_PlayerIsLeavingPvP[client] = false; + + if (g_PlayerEnteredPvPTriggers[client] != null) + { + g_PlayerEnteredPvPTriggers[client].Clear(); + } +} + +static void PvP_RemovePlayerProjectiles(int client) +{ + for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, g_PvPProjectileClasses[i])) != -1) + { + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + bool mine = false; + + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + if (ownerEntity == client) + { + mine = true; + } + else if (throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + if (ownerEntity == client) + { + mine = true; + } + } + + if (mine) + { + RemoveEntity(ent); + } + } + } +} + +static Action Timer_TeleportPlayerToPvP(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (g_PlayerProxy[client]) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvPRespawnTimer[client]) + { + return Plugin_Stop; + } + g_PlayerPvPRespawnTimer[client] = null; + + ArrayList spawnPointList = new ArrayList(); + ArrayList clearSpawnPointList = new ArrayList(); + + int ent = -1; + + { + float myPos[3]; + float mins[3], maxs[3]; + GetEntPropVector(client, Prop_Send, "m_vecMins", mins); + GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); + + char name[32]; + + ent = -1; + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); + + if (!StrContains(name, "sf2_pvp_spawnpoint", false)) + { + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", myPos); + + spawnPointList.Push(ent); + if (!IsSpaceOccupiedPlayer(myPos, mins, maxs, client)) + { + clearSpawnPointList.Push(ent); + } + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "sf2_info_player_pvpspawn")) != -1) + { + SF2PlayerPvPSpawnEntity spawnPoint = SF2PlayerPvPSpawnEntity(ent); + if (!spawnPoint.IsValid() || !spawnPoint.Enabled) + { + continue; + } + + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", myPos); + + spawnPointList.Push(ent); + if (!IsSpaceOccupiedPlayer(myPos, mins, maxs, client)) + { + clearSpawnPointList.Push(ent); + } + } + } + + int num; + if ((num = clearSpawnPointList.Length) > 0) + { + ent = clearSpawnPointList.Get(GetRandomInt(0, num - 1)); + } + else if ((num = spawnPointList.Length) > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, num - 1)); + } + + delete spawnPointList; + delete clearSpawnPointList; + + if (num > 0) + { + float pos[3], ang[3]; + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); + TeleportEntity(client, pos, ang, { 0.0, 0.0, 0.0 }); + + EmitAmbientSound(SF2_PVP_SPAWN_SOUND, pos, _, SNDLEVEL_NORMAL, _, 1.0); + if (g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection) + { + TF2_AddCondition(client, TFCond_UberchargedCanteen, 1.5); + } + + SF2PlayerPvPSpawnEntity spawnPoint = SF2PlayerPvPSpawnEntity(ent); + if (spawnPoint.IsValid()) + { + spawnPoint.FireOutput("OnSpawn", client); + } + } + return Plugin_Stop; +} + +static Action Timer_PlayerPvPLeaveCountdown(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvPTimer[client]) + { + return Plugin_Stop; + } + + if (!IsClientInPvP(client)) + { + return Plugin_Stop; + } + + if (g_PlayerPvPTimerCount[client] <= 0) + { + PvP_SetPlayerPvPState(client, false); + g_PlayerIsLeavingPvP[client] = false; + + // Force them to their melee weapon and stop taunting, to prevent tposing and what not. + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + TF2_RemoveCondition(client, TFCond_Taunting); + return Plugin_Stop; + } + + g_PlayerPvPTimerCount[client]--; + + //if (!g_PlayerProxyAvailableInForce[client]) + { + SetHudTextParams(-1.0, 0.75, + 1.0, + 255, 255, 255, 255, + _, + _, + 0.25, 1.25); + + ShowSyncHudText(client, g_HudSync, "%T", "SF2 Exiting PvP Arena", client, g_PlayerPvPTimerCount[client]); + } + + return Plugin_Continue; +} +bool IsClientInPvP(int client) +{ + return g_PlayerInPvP[client]; +} + +bool IsClientLeavingPvP(int client) +{ + return g_PlayerIsLeavingPvP[client]; +} +//////////////// +// Blood effects +//////////////// + +//Actually we are blocking the damages if the player is injured and not in pvp. So this hook is useless +static Action TempEntHook_PvPBlood(const char[] te_name, int[] players, int numPlayers, float delay) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + int client = TE_ReadNum("entindex"); + if (IsValidClient(client) && g_PlayerEliminated[client] && !IsClientInPvP(client)) + { + //Block effect + return Plugin_Stop; + } + return Plugin_Continue; +} + +//Credits to STT creators for this decal trace +static Action ClientTraceAttack(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &ammotype, int hitbox, int hitgroup) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (IsValidClient(victim)) + { + g_PvPUserIdLastTrace = GetClientUserId(victim); + g_TimeLastTrace = GetEngineTime(); + } + + return Plugin_Continue; +} + +static Action TempEntHook_PvPDecal(const char[] te_name, int[] players, int numPlayers, float delay) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + // Blocks the blood decals on the walls / nearby entities + + int client = GetClientOfUserId(g_PvPUserIdLastTrace); + if (IsValidClient(client) && g_PlayerEliminated[client] && !IsClientInPvP(client)) + { + if (g_TimeLastTrace != 0.0 && GetEngineTime() - g_TimeLastTrace < 0.1) + { + //PrintToChatAll("Blocked blood stain for %N (%f)", client, GetEngineTime() - g_TimeLastTrace); + return Plugin_Stop; + } + } + + return Plugin_Continue; +} + +static MRESReturn WeaponGetCustomDamageType(int weapon, DHookReturn returnHandle, DHookParam params) +{ + if (!g_Enabled) + { + return MRES_Ignored; + } + + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(weapon, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.IsValid && ownerEntity.IsInPvP && IsValidEntity(weapon) && ownerEntity) + { + int customDamageType = returnHandle.Value; + if (customDamageType != -1) + { + MRESReturn hookResult = GetWeaponCustomDamageType(weapon, ownerEntity.index, customDamageType); + if (hookResult != MRES_Ignored) + { + returnHandle.Value = customDamageType; + return hookResult; + } + } + else + { + return MRES_Ignored; + } + } + else + { + return MRES_Ignored; + } + + return MRES_Ignored; +} + +static MRESReturn GetWeaponCustomDamageType(int weapon, int client, int &customDamageType) +{ + if (IsValidClient(client) && IsClientInPvP(client) && IsValidEntity(weapon) && IsValidEdict(weapon) && IsValidEntity(client)) + { + static const char fixWeaponPenetrationClasses[][] = + { + "tf_weapon_sniperrifle", + "tf_weapon_sniperrifle_decap", + "tf_weapon_sniperrifle_classic" + }; + + char weaponName[256]; + GetEntityClassname(weapon, weaponName, sizeof(weaponName)); + + /* + * Fixes the sniper rifle not damaging teammates. + * + * WHY? For every other hitscan weapon in the game, simply enforcing lag compensation in CTFPlayer::WantsLagCompensationOnEntity() + * works. However, when it comes to weapons that penetrate teammates, the bullet trace will not iterate through teammates. This is + * the case with all sniper rifles, and is the reason why damage is never normally dealt to teammates despite having friendly fire + * on and lag compensation. + * + * In this case, the type of penetration is determined by CTFWeaponBase::GetCustomDamageType(). For Snipers, default value is + * TF_DMG_CUSTOM_PENETRATE_MY_TEAM (11) (piss rifle is TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE (14)). This value specifies + * penetration of the bullet through teammates without damaging them. The damage type is switched to 0, and for the Machina at + * full charge, TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS (12). + * + */ + for (int i = 0; i < sizeof(fixWeaponPenetrationClasses); i++) + { + if (strcmp(weaponName, fixWeaponPenetrationClasses[i], false) == 0) + { + customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS + int itemDefIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); + + if ((itemDefIndex == 526 || itemDefIndex == 30665) && GetEntPropFloat(weapon, Prop_Send, "m_flChargedDamage") >= 150.0) // The Machina, Shooting Star + { + customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS + } + else + { + customDamageType = 0; // no penetration behavior. + } + + return MRES_Supercede; + } + } + } + + return MRES_Ignored; +} + +static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHookReturn returnHandle, DHookParam params) +{ + if (!g_Enabled) + { + return MRES_Ignored; + } + + if (!IsValidEntity(projectile)) + { + return MRES_Ignored; + } + + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (IsValidClient(owner) && (IsRoundInWarmup() || IsClientInPvP(owner))) + { + returnHandle.Value = true; + return MRES_Supercede; + } + + return MRES_Ignored; +} + +// API + +void PvP_InitializeAPI() +{ + CreateNative("SF2_IsClientInPvP", Native_IsClientInPvP); + g_OnPlayerEnterPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); + g_OnPlayerExitPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); +} + +static int Native_IsClientInPvP(Handle plugin,int numParams) +{ + return IsClientInPvP(GetNativeCell(1)); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/playergroups.sp b/addons/sourcemod/scripting/sf2/playergroups.sp index e508793b..a95d07af 100644 --- a/addons/sourcemod/scripting/sf2/playergroups.sp +++ b/addons/sourcemod/scripting/sf2/playergroups.sp @@ -15,6 +15,7 @@ static int g_PlayerGroupLeader[SF2_MAX_PLAYER_GROUPS] = { -1, ... }; static int g_PlayerGroupID[SF2_MAX_PLAYER_GROUPS] = { -1, ... }; static int g_PlayerGroupQueuePoints[SF2_MAX_PLAYER_GROUPS]; static int g_PlayerGroupPlaying[SF2_MAX_PLAYER_GROUPS] = { false, ... }; +static bool g_PlayerGroupOptInHarder[SF2_MAX_PLAYER_GROUPS] = { false, ... }; static StringMap g_PlayerGroupNames; static bool g_PlayerGroupInvitedPlayer[SF2_MAX_PLAYER_GROUPS][MAXTF2PLAYERS]; static int g_PlayerGroupInvitedPlayerCount[SF2_MAX_PLAYER_GROUPS][MAXTF2PLAYERS]; @@ -43,7 +44,7 @@ int GetPlayerGroupFromID(int groupID) return -1; } -void SendPlayerGroupInvitation(int client,int groupID,int inviter=-1) +void SendPlayerGroupInvitation(int client, int groupID, int inviter = -1) { if (!IsValidClient(client) || !IsClientParticipating(client)) { @@ -133,18 +134,18 @@ void SendPlayerGroupInvitation(int client,int groupID,int inviter=-1) strcopy(leaderName, sizeof(leaderName), "nobody"); } - Handle menu = CreateMenu(Menu_GroupInvite); - SetMenuTitle(menu, "%t%T\n \n", "SF2 Prefix", "SF2 Group Invite Menu Description", client, leaderName, groupName); + Menu menu = new Menu(Menu_GroupInvite); + menu.SetTitle("%t%T\n \n", "SF2 Prefix", "SF2 Group Invite Menu Description", client, leaderName, groupName); char groupIDString[64]; FormatEx(groupIDString, sizeof(groupIDString), "%d", groupID); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, groupIDString, buffer); + menu.AddItem(groupIDString, buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); - DisplayMenu(menu, client, 10); + menu.AddItem("0", buffer); + menu.Display(client, 10); SetPlayerGroupInvitedPlayer(groupIndex, client, true); SetPlayerGroupInvitedPlayerCount(groupIndex, client, GetPlayerGroupInvitedPlayerCount(groupIndex, client) + 1); @@ -156,7 +157,7 @@ void SendPlayerGroupInvitation(int client,int groupID,int inviter=-1) } } -static int Menu_GroupInvite(Handle menu, MenuAction action,int param1,int param2) +static int Menu_GroupInvite(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -167,7 +168,7 @@ static int Menu_GroupInvite(Handle menu, MenuAction action,int param1,int param2 if (param2 == 0) { char groupID[64]; - GetMenuItem(menu, param2, groupID, sizeof(groupID)); + menu.GetItem(param2, groupID, sizeof(groupID)); int groupIndex = GetPlayerGroupFromID(StringToInt(groupID)); if (IsPlayerGroupActive(groupIndex)) { @@ -219,20 +220,20 @@ void DisplayResetGroupQueuePointsMenuToClient(int client) return; } - Handle menu = CreateMenu(Menu_ResetGroupQueuePoints); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Reset Group Queue Points Menu Title", client, "SF2 Reset Group Queue Points Menu Description", client); + Menu menu = new Menu(Menu_ResetGroupQueuePoints); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Reset Group Queue Points Menu Title", client, "SF2 Reset Group Queue Points Menu Description", client); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ResetGroupQueuePoints(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -240,7 +241,10 @@ static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1, } else if (action == MenuAction_Cancel) { - if (param2 == MenuCancel_ExitBack) DisplayAdminGroupMenuToClient(param1); + if (param2 == MenuCancel_ExitBack) + { + DisplayAdminGroupMenuToClient(param1); + } } else if (action == MenuAction_Select) { @@ -274,6 +278,99 @@ static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1, return 0; } +void DisplayOptInHardDifficultiesToClient(int client) +{ + int groupIndex = ClientGetPlayerGroup(client); + if (!IsPlayerGroupActive(groupIndex)) + { + // His group isn't valid anymore. Take him back to the main menu. + DisplayGroupMainMenuToClient(client); + CPrintToChat(client, "%T", "SF2 Group Does Not Exist", client); + return; + } + + if (GetPlayerGroupLeader(groupIndex) != client) + { + DisplayAdminGroupMenuToClient(client); + CPrintToChat(client, "%T", "SF2 Not Group Leader", client); + return; + } + + Menu menu = new Menu(Menu_OptInHarderDifficulties); + menu.SetTitle("%t Opt in to voting higher difficulties\n \nAre you sure you want your group to opt in being able to\nvote for Nightmare + Apollyon? Your group must\nbe 100%% filled to be able to vote for Nightmare + Apollyon.\n \n", "SF2 Prefix"); + + char buffer[256]; + FormatEx(buffer, sizeof(buffer), "Opt in"); + menu.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "Opt out"); + menu.AddItem("0", buffer); + + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); +} + +static int Menu_OptInHarderDifficulties(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayAdminGroupMenuToClient(param1); + } + } + else if (action == MenuAction_Select) + { + int groupIndex = ClientGetPlayerGroup(param1); + if (!IsPlayerGroupActive(groupIndex) || GetPlayerGroupLeader(groupIndex) != param1) + { + CPrintToChat(param1, "%T", "SF2 Not Group Leader", param1); + } + else + { + switch (param2) + { + case 0: + { + SetPlayerGroupOptInHarder(groupIndex, true); + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + if (ClientGetPlayerGroup(i) == groupIndex) + { + CPrintToChat(i, "Your group is now opted in to vote for Nightmare + Apollyon."); + } + } + } + case 1: + { + SetPlayerGroupOptInHarder(groupIndex, false); + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + if (ClientGetPlayerGroup(i) == groupIndex) + { + CPrintToChat(i, "Your group is now opted out to vote for Nightmare + Apollyon."); + } + } + } + } + } + + DisplayAdminGroupMenuToClient(param1); + } + return 0; +} + void CheckPlayerGroup(int groupIndex) { if (!IsPlayerGroupActive(groupIndex)) @@ -400,6 +497,7 @@ int CreatePlayerGroup() SetPlayerGroupLeader(index, -1); SetPlayerGroupName(index, ""); SetPlayerGroupPlaying(index, false); + SetPlayerGroupOptInHarder(index, false); for (int i = 1; i <= MaxClients; i++) { @@ -425,6 +523,7 @@ void RemovePlayerGroup(int groupIndex) SetPlayerGroupLeader(groupIndex, -1); g_PlayerGroupActive[groupIndex] = false; SetPlayerGroupID(groupIndex, -1); + SetPlayerGroupOptInHarder(groupIndex, false); } void ClearPlayerGroupMembers(int groupIndex) @@ -462,9 +561,9 @@ int GetPlayerGroupID(int groupIndex) return g_PlayerGroupID[groupIndex]; } -void SetPlayerGroupID(int groupIndex,int iID) +void SetPlayerGroupID(int groupIndex, int id) { - g_PlayerGroupID[groupIndex] = iID; + g_PlayerGroupID[groupIndex] = id; } bool IsPlayerGroupActive(int groupIndex) @@ -507,6 +606,16 @@ void SetPlayerGroupPlaying(int groupIndex, bool toggle) g_PlayerGroupPlaying[groupIndex] = toggle; } +bool IsPlayerGroupOptInHarder(int groupIndex) +{ + return (IsPlayerGroupActive(groupIndex) && g_PlayerGroupOptInHarder[groupIndex]); +} + +void SetPlayerGroupOptInHarder(int groupIndex, bool value) +{ + g_PlayerGroupOptInHarder[groupIndex] = value; +} + int GetPlayerGroupLeader(int groupIndex) { return g_PlayerGroupLeader[groupIndex]; @@ -578,17 +687,17 @@ bool HasPlayerGroupInvitedPlayer(int groupIndex,int client) return g_PlayerGroupInvitedPlayer[groupIndex][client]; } */ -void SetPlayerGroupInvitedPlayer(int groupIndex,int client, bool toggle) +void SetPlayerGroupInvitedPlayer(int groupIndex, int client, bool toggle) { g_PlayerGroupInvitedPlayer[groupIndex][client] = toggle; } -int GetPlayerGroupInvitedPlayerCount(int groupIndex,int client) +int GetPlayerGroupInvitedPlayerCount(int groupIndex, int client) { return g_PlayerGroupInvitedPlayerCount[groupIndex][client]; } -void SetPlayerGroupInvitedPlayerCount(int groupIndex,int client,int amount) +void SetPlayerGroupInvitedPlayerCount(int groupIndex, int client, int amount) { g_PlayerGroupInvitedPlayerCount[groupIndex][client] = amount; } @@ -598,9 +707,9 @@ float GetPlayerGroupInvitedPlayerTime(int groupIndex,int client) return g_PlayerGroupInvitedPlayerTime[groupIndex][client]; } -void SetPlayerGroupInvitedPlayerTime(int groupIndex,int client, float flTime) +void SetPlayerGroupInvitedPlayerTime(int groupIndex, int client, float time) { - g_PlayerGroupInvitedPlayerTime[groupIndex][client] = flTime; + g_PlayerGroupInvitedPlayerTime[groupIndex][client] = time; } int ClientGetPlayerGroup(int client) @@ -608,7 +717,7 @@ int ClientGetPlayerGroup(int client) return g_PlayerCurrentGroup[client]; } -void ClientSetPlayerGroup(int client,int groupIndex) +void ClientSetPlayerGroup(int client, int groupIndex) { int oldPlayerGroup = ClientGetPlayerGroup(client); if (oldPlayerGroup == groupIndex) diff --git a/addons/sourcemod/scripting/sf2/playergroups/menus.sp b/addons/sourcemod/scripting/sf2/playergroups/menus.sp index 49284e58..cf8705ed 100644 --- a/addons/sourcemod/scripting/sf2/playergroups/menus.sp +++ b/addons/sourcemod/scripting/sf2/playergroups/menus.sp @@ -8,8 +8,8 @@ void DisplayGroupMainMenuToClient(int client) { - Handle menu = CreateMenu(Menu_GroupMain); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Group Main Menu Title", client, "SF2 Group Main Menu Description", client); + Menu menu = new Menu(Menu_GroupMain); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Group Main Menu Title", client, "SF2 Group Main Menu Description", client); int groupIndex = ClientGetPlayerGroup(client); bool groupIsActive = IsPlayerGroupActive(groupIndex); @@ -24,18 +24,18 @@ void DisplayGroupMainMenuToClient(int client) Format(buffer, sizeof(buffer), "%T", "SF2 View Current Group Info Menu Title", client); } - AddMenuItem(menu, "0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + menu.AddItem("0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); Format(buffer, sizeof(buffer), "%T", "SF2 Create Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupIsActive ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem("0", buffer, groupIsActive ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); Format(buffer, sizeof(buffer), "%T", "SF2 Leave Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + menu.AddItem("0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_GroupMain(Handle menu, MenuAction action,int param1,int param2) +static int Menu_GroupMain(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -43,7 +43,10 @@ static int Menu_GroupMain(Handle menu, MenuAction action,int param1,int param2) } else if (action == MenuAction_Cancel) { - if (param2 == MenuCancel_ExitBack) DisplayMenu(g_MenuMain, param1, 30); + if (param2 == MenuCancel_ExitBack) + { + g_MenuMain.Display(param1, 30); + } } else if (action == MenuAction_Select) { @@ -77,19 +80,19 @@ void DisplayCreateGroupMenuToClient(int client) return; } - Handle menu = CreateMenu(Menu_CreateGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Create Group Menu Title", client, "SF2 Create Group Menu Description", client, GetMaxPlayersForRound(), g_PlayerQueuePoints[client]); + Menu menu = new Menu(Menu_CreateGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Create Group Menu Title", client, "SF2 Create Group Menu Description", client, GetMaxPlayersForRound(), g_PlayerQueuePoints[client]); char buffer[256]; Format(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); Format(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_CreateGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_CreateGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -146,19 +149,19 @@ void DisplayLeaveGroupMenuToClient(int client) char groupName[SF2_MAX_PLAYER_GROUP_NAME_LENGTH]; GetPlayerGroupName(groupIndex, groupName, sizeof(groupName)); - Handle menu = CreateMenu(Menu_LeaveGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Leave Group Menu Title", client, "SF2 Leave Group Menu Description", client, groupName); + Menu menu = new Menu(Menu_LeaveGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Leave Group Menu Title", client, "SF2 Leave Group Menu Description", client, groupName); char buffer[256]; Format(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); Format(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_LeaveGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_LeaveGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -207,28 +210,30 @@ void DisplayAdminGroupMenuToClient(int client) int maxPlayers = GetMaxPlayersForRound(); int queuePoints = GetPlayerGroupQueuePoints(groupIndex); - Handle menu = CreateMenu(Menu_AdminGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Admin Group Menu Title", client, "SF2 Admin Group Menu Description", client, groupName, leaderName, memberCount, maxPlayers, queuePoints); + Menu menu = new Menu(Menu_AdminGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Admin Group Menu Title", client, "SF2 Admin Group Menu Description", client, groupName, leaderName, memberCount, maxPlayers, queuePoints); char buffer[256]; - Format(buffer, sizeof(buffer), "%T", "SF2 View Group Members Menu Title", client); - AddMenuItem(menu, "0", buffer); - Format(buffer, sizeof(buffer), "%T", "SF2 Set Group Name Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Set Group Leader Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Invite To Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client && memberCount < maxPlayers ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Kick From Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client && memberCount > 1 ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Reset Group Queue Points Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 View Group Members Menu Title", client); + menu.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Set Group Name Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Set Group Leader Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Invite To Group Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client && memberCount < maxPlayers ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Kick From Group Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client && memberCount > 1 ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Reset Group Queue Points Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "Opt in for Nightmare + Apollyon voting"); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_AdminGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_AdminGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -272,6 +277,10 @@ static int Menu_AdminGroup(Handle menu, MenuAction action,int param1,int param2) { DisplayResetGroupQueuePointsMenuToClient(param1); } + case 6: + { + DisplayOptInHardDifficultiesToClient(param1); + } } } else @@ -295,9 +304,6 @@ void DisplayViewGroupMembersMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayViewGroupMembersMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -317,8 +323,8 @@ void DisplayViewGroupMembersMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_ViewGroupMembers); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, "SF2 View Group Members Menu Description", client); + Menu menu = new Menu(Menu_ViewGroupMembers); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, "SF2 View Group Members Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -328,11 +334,11 @@ void DisplayViewGroupMembersMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -342,12 +348,9 @@ void DisplayViewGroupMembersMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplayViewGroupMembersMenuToClient.", playersHandle); - #endif } -static int Menu_ViewGroupMembers(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ViewGroupMembers(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -386,9 +389,6 @@ void DisplaySetGroupLeaderMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplaySetGroupLeaderMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -412,8 +412,8 @@ void DisplaySetGroupLeaderMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_SetGroupLeader); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Leader Menu Title", client, "SF2 Set Group Leader Menu Description", client); + Menu menu = new Menu(Menu_SetGroupLeader); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Leader Menu Title", client, "SF2 Set Group Leader Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -423,11 +423,11 @@ void DisplaySetGroupLeaderMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -437,12 +437,9 @@ void DisplaySetGroupLeaderMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplaySetGroupLeaderMenuToClient.", playersHandle); - #endif } -static int Menu_SetGroupLeader(Handle menu, MenuAction action,int param1,int param2) +static int Menu_SetGroupLeader(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -461,7 +458,7 @@ static int Menu_SetGroupLeader(Handle menu, MenuAction action,int param1,int par if (IsPlayerGroupActive(groupIndex) && GetPlayerGroupLeader(groupIndex) == param1) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); int userid = StringToInt(info); int player = GetClientOfUserId(userid); @@ -504,9 +501,6 @@ void DisplayKickFromGroupMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayKickFromGroupMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -530,8 +524,8 @@ void DisplayKickFromGroupMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_KickFromGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Kick From Group Menu Title", client, "SF2 Kick From Group Menu Description", client); + Menu menu = new Menu(Menu_KickFromGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Kick From Group Menu Title", client, "SF2 Kick From Group Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -541,11 +535,11 @@ void DisplayKickFromGroupMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -555,12 +549,9 @@ void DisplayKickFromGroupMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplayKickFromGroupMenuToClient.", playersHandle); - #endif } -static int Menu_KickFromGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_KickFromGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -579,7 +570,7 @@ static int Menu_KickFromGroup(Handle menu, MenuAction action,int param1,int para if (IsPlayerGroupActive(groupIndex) && GetPlayerGroupLeader(groupIndex) == param1) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); int userid = StringToInt(info); int player = GetClientOfUserId(userid); @@ -623,16 +614,16 @@ void DisplaySetGroupNameMenuToClient(int client) return; } - Handle menu = CreateMenu(Menu_SetGroupName); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Name Menu Title", client, "SF2 Set Group Name Menu Description", client); + Menu menu = new Menu(Menu_SetGroupName); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Name Menu Title", client, "SF2 Set Group Name Menu Description", client); char buffer[256]; Format(buffer, sizeof(buffer), "%T", "Back", client); - AddMenuItem(menu, "0", buffer); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.AddItem("0", buffer); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_SetGroupName(Handle menu, MenuAction action,int param1,int param2) +static int Menu_SetGroupName(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -679,9 +670,6 @@ void DisplayInviteToGroupMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayInviteToGroupMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsClientParticipating(i)) @@ -705,8 +693,8 @@ void DisplayInviteToGroupMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_InviteToGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Invite To Group Menu Title", client, "SF2 Invite To Group Menu Description", client); + Menu menu = new Menu(Menu_InviteToGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Invite To Group Menu Title", client, "SF2 Invite To Group Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -716,11 +704,11 @@ void DisplayInviteToGroupMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -730,12 +718,9 @@ void DisplayInviteToGroupMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplayInviteToGroupMenuToClient.", playersHandle); - #endif } -static int Menu_InviteToGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_InviteToGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -754,10 +739,10 @@ static int Menu_InviteToGroup(Handle menu, MenuAction action,int param1,int para if (IsPlayerGroupActive(groupIndex) && GetPlayerGroupLeader(groupIndex) == param1) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); int userid = StringToInt(info); - int iInvitedPlayer = GetClientOfUserId(userid); - SendPlayerGroupInvitation(iInvitedPlayer, GetPlayerGroupID(groupIndex), param1); + int invitedPlayer = GetClientOfUserId(userid); + SendPlayerGroupInvitation(invitedPlayer, GetPlayerGroupID(groupIndex), param1); } DisplayInviteToGroupMenuToClient(param1); diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index de88852a..f4dc6782 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -19,7 +19,8 @@ static ArrayList g_SelectableAdminBossProfileList = null; static ArrayList g_SelectableBoxingBossProfileList = null; static ArrayList g_SelectableRenevantBossProfileList = null; static ArrayList g_SelectableRenevantBossAdminProfileList = null; -static ArrayList g_hSelectableBossProfileQueueList = null; +static ArrayList g_SelectableBossProfileQueueList = null; +ArrayList g_BossProfileRequested = null; StringMap g_BossProfileData = null; @@ -32,6 +33,7 @@ ConVar g_BossPackEndOfMapVoteConVar; ConVar g_BossPackVoteStartTimeConVar; ConVar g_BossPackVoteStartRoundConVar; ConVar g_BossPackVoteShuffleConVar; +ConVar g_MaxCorePackBosses; static bool g_BossPackVoteEnabled = false; @@ -42,9 +44,12 @@ static char mapBossPack[64]; GlobalForward g_OnBossProfileLoadedFwd; static GlobalForward g_OnBossProfileUnloadedFwd; -#include "sf2/profiles/profiles_boss_functions.sp" -#include "sf2/profiles/profile_chaser.sp" -#include "sf2/profiles/profile_statue.sp" +static ArrayList g_AllBosses; +static StringMap g_AllBossesName; + +#include "profiles/profiles_boss_functions.sp" +#include "profiles/profile_chaser.sp" +#include "profiles/profile_statue.sp" void SetupBossProfileNatives() { @@ -53,9 +58,6 @@ void SetupBossProfileNatives() CreateNative("SF2_GetBossProfileFloat", Native_GetBossProfileFloat); CreateNative("SF2_GetBossProfileString", Native_GetBossProfileString); CreateNative("SF2_GetBossProfileVector", Native_GetBossProfileVector); - CreateNative("SF2_GetBossProfileDifficultyNumValues", Native_GetBossProfileDifficultyNumValues); - CreateNative("SF2_GetBossProfileDifficultyBoolValues", Native_GetBossProfileDifficultyBoolValues); - CreateNative("SF2_GetBossProfileDifficultyFloatValues", Native_GetBossProfileDifficultyFloatValues); CreateNative("SF2_GetBossAttackProfileNum", Native_GetBossAttackProfileNum); CreateNative("SF2_GetBossAttackProfileFloat", Native_GetBossAttackProfileFloat); CreateNative("SF2_GetBossAttackProfileString", Native_GetBossAttackProfileString); @@ -67,6 +69,8 @@ void SetupBossProfileNatives() CreateNative("SF2_GetBossProfileData", Native_GetBossProfileData); CreateNative("SF2_GetChaserBossProfileData", Native_GetChaserBossProfileData); CreateNative("SF2_GetStatueBossProfileData", Native_GetStatueBossProfileData); + CreateNative("SF2_TranslateProfileActivityFromName", Native_TranslateProfileActivityFromName); + CreateNative("SF2_LookupProfileAnimation", Native_LookupProfileAnimation); } void InitializeBossProfiles() @@ -88,6 +92,8 @@ void InitializeBossProfiles() g_BossPackVoteStartRoundConVar = CreateConVar("sf2_boss_profile_pack_endvote_startround", "2", "Specifies when to start the vote based on rounds remaining on the map.", FCVAR_NOTIFY); g_BossPackVoteShuffleConVar = CreateConVar("sf2_boss_profile_pack_endvote_shuffle", "0", "Shuffles the menu options of boss pack endvotes if enabled."); + g_MaxCorePackBosses = CreateConVar("sf2_max_core_pack_bosses", "-1", "Determines how many bosses can load randomly from the core pack, if set to less than 0 will keep this feature off. Note that companion bosses will still load if needed."); + InitializeStatueProfiles(); InitializeChaserProfiles(); } @@ -110,6 +116,18 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_CROUCHIDLE_AIM_STIMULATED", ACT_CROUCHIDLE_AIM_STIMULATED); g_Activities.SetValue("ACT_CROUCHIDLE_AGITATED", ACT_CROUCHIDLE_AGITATED); g_Activities.SetValue("ACT_STAND", ACT_STAND); // standing up from crouch + g_Activities.SetValue("ACT_MP_STAND_PRIMARY", ACT_MP_STAND_PRIMARY); + g_Activities.SetValue("ACT_MP_STAND_SECONDARY", ACT_MP_STAND_SECONDARY); + g_Activities.SetValue("ACT_MP_STAND_MELEE", ACT_MP_STAND_MELEE); + g_Activities.SetValue("ACT_MP_STAND_SECONDARY2", ACT_MP_STAND_SECONDARY2); + g_Activities.SetValue("ACT_MP_STAND_ITEM1", ACT_MP_STAND_ITEM1); + g_Activities.SetValue("ACT_MP_STAND_ITEM2", ACT_MP_STAND_ITEM2); + g_Activities.SetValue("ACT_MP_CROUCH_PRIMARY", ACT_MP_CROUCH_PRIMARY); + g_Activities.SetValue("ACT_MP_CROUCH_SECONDARY", ACT_MP_CROUCH_SECONDARY); + g_Activities.SetValue("ACT_MP_CROUCH_MELEE", ACT_MP_CROUCH_MELEE); + g_Activities.SetValue("ACT_MP_CROUCH_SECONDARY2", ACT_MP_CROUCH_SECONDARY2); + g_Activities.SetValue("ACT_MP_CROUCH_ITEM1", ACT_MP_CROUCH_ITEM1); + g_Activities.SetValue("ACT_MP_CROUCH_ITEM2", ACT_MP_CROUCH_ITEM2); g_Activities.SetValue("ACT_WALK", ACT_WALK); g_Activities.SetValue("ACT_WALK_STIMULATED", ACT_WALK_STIMULATED); g_Activities.SetValue("ACT_WALK_AGITATED", ACT_WALK_AGITATED); @@ -119,6 +137,12 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_WALK_AIM_AGITATED", ACT_WALK_AIM_AGITATED); g_Activities.SetValue("ACT_WALK_CROUCH", ACT_WALK_CROUCH); g_Activities.SetValue("ACT_WALK_CROUCH_AIM", ACT_WALK_CROUCH_AIM); + g_Activities.SetValue("ACT_MP_WALK_PRIMARY", ACT_MP_WALK_PRIMARY); + g_Activities.SetValue("ACT_MP_WALK_SECONDARY", ACT_MP_WALK_SECONDARY); + g_Activities.SetValue("ACT_MP_WALK_MELEE", ACT_MP_WALK_MELEE); + g_Activities.SetValue("ACT_MP_WALK_SECONDARY2", ACT_MP_WALK_SECONDARY2); + g_Activities.SetValue("ACT_MP_WALK_ITEM1", ACT_MP_WALK_ITEM1); + g_Activities.SetValue("ACT_MP_WALK_ITEM2", ACT_MP_WALK_ITEM2); g_Activities.SetValue("ACT_RUN", ACT_RUN); g_Activities.SetValue("ACT_RUN_STIMULATED", ACT_RUN_STIMULATED); g_Activities.SetValue("ACT_RUN_AGITATED", ACT_RUN_AGITATED); @@ -128,6 +152,12 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_RUN_AIM_RELAXED", ACT_RUN_AIM_RELAXED); g_Activities.SetValue("ACT_RUN_CROUCH", ACT_RUN_CROUCH); g_Activities.SetValue("ACT_RUN_CROUCH_AIM", ACT_RUN_CROUCH_AIM); + g_Activities.SetValue("ACT_MP_RUN_PRIMARY", ACT_MP_RUN_PRIMARY); + g_Activities.SetValue("ACT_MP_RUN_SECONDARY", ACT_MP_RUN_SECONDARY); + g_Activities.SetValue("ACT_MP_RUN_MELEE", ACT_MP_RUN_MELEE); + g_Activities.SetValue("ACT_MP_RUN_SECONDARY2", ACT_MP_RUN_SECONDARY2); + g_Activities.SetValue("ACT_MP_RUN_ITEM1", ACT_MP_RUN_ITEM1); + g_Activities.SetValue("ACT_MP_RUN_ITEM2", ACT_MP_RUN_ITEM2); g_Activities.SetValue("ACT_JUMP", ACT_JUMP); g_Activities.SetValue("ACT_FLY", ACT_FLY); g_Activities.SetValue("ACT_LAND", ACT_LAND); @@ -159,6 +189,10 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_RELOAD_START", ACT_RELOAD_START); g_Activities.SetValue("ACT_RELOAD_FINISH", ACT_RELOAD_FINISH); g_Activities.SetValue("ACT_GESTURE_RELOAD", ACT_GESTURE_RELOAD); + g_Activities.SetValue("ACT_MP_ATTACK_STAND_MELEE", ACT_MP_ATTACK_STAND_MELEE); + g_Activities.SetValue("ACT_MP_ATTACK_CROUCH_MELEE", ACT_MP_ATTACK_CROUCH_MELEE); + g_Activities.SetValue("ACT_MP_ATTACK_SWIM_MELEE", ACT_MP_ATTACK_SWIM_MELEE); + g_Activities.SetValue("ACT_MP_ATTACK_AIRWALK_MELEE", ACT_MP_ATTACK_AIRWALK_MELEE); // Flinch activities g_Activities.SetValue("ACT_SMALL_FLINCH", ACT_SMALL_FLINCH); @@ -188,6 +222,22 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_DIE_GUTSHOT", ACT_DIE_GUTSHOT); g_Activities.SetValue("ACT_DIE_BACKSHOT", ACT_DIE_BACKSHOT); + // Rage activites + g_Activities.SetValue("ACT_BUSY_QUEUE", ACT_BUSY_QUEUE); + + // Flee starting activities + g_Activities.SetValue("ACT_SIGNAL1", ACT_SIGNAL1); + g_Activities.SetValue("ACT_SIGNAL2", ACT_SIGNAL2); + g_Activities.SetValue("ACT_SIGNAL3", ACT_SIGNAL3); + + // Heal activities + g_Activities.SetValue("ACT_USE", ACT_USE); + g_Activities.SetValue("ACT_BUSY_QUEUE", ACT_BUSY_QUEUE); + g_Activities.SetValue("ACT_SHIELD_UP", ACT_SHIELD_UP); + g_Activities.SetValue("ACT_SHIELD_UP_IDLE", ACT_SHIELD_UP_IDLE); + g_Activities.SetValue("ACT_CROUCHING_SHIELD_UP", ACT_CROUCHING_SHIELD_UP); + g_Activities.SetValue("ACT_CROUCHING_SHIELD_UP_IDLE", ACT_CROUCHING_SHIELD_UP_IDLE); + // Misc activities g_Activities.SetValue("ACT_TRANSITION", ACT_TRANSITION); // Spawn animation g_Activities.SetValue("ACT_DISARM", ACT_DISARM); // Spawn animation @@ -212,40 +262,50 @@ Activity TranslateProfileActivityFromName(const char[] activityName) return ACT_INVALID; } -/* -Command -*/ -Action Command_Pack(int client,int args) +int LookupProfileAnimation(int entity, const char[] animName) { - if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) + CBaseAnimating animator = CBaseAnimating(entity); + + int sequence = -1; + Activity activity = TranslateProfileActivityFromName(animName); + if (activity != ACT_INVALID) { - CPrintToChat(client,"{red}Boss pack vote is disabled on this server."); - return Plugin_Handled; + sequence = animator.SelectWeightedSequence(activity); + } + else + { + sequence = animator.LookupSequence(animName); } + + return sequence; +} + +void GetCurrentBossPack(char[] bossPackName, int length) +{ g_BossPackConfig.Rewind(); if (!g_BossPackConfig.JumpToKey("packs")) { - return Plugin_Handled; + return; } if (!g_BossPackConfig.JumpToKey(mapBossPack)) { - return Plugin_Handled; + return; } - char bossPackName[64]; - g_BossPackConfig.GetString("name", bossPackName, sizeof(bossPackName), mapBossPack); + g_BossPackConfig.GetString("name", bossPackName, length, mapBossPack); if (bossPackName[0] == '\0') { - FormatEx(bossPackName,sizeof(bossPackName),"Core Pack"); + FormatEx(bossPackName, length, "Core Pack"); } - CPrintToChat(client,"{dodgerblue}Pack: {lightblue}%s",bossPackName); - return Plugin_Handled; } -Action Command_NextPack(int client,int args) +/* +Command +*/ +Action Command_NextPack(int client, int args) { if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) { - CPrintToChat(client,"{red}Boss pack vote is disabled on this server."); + CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Disabled Boss Pack"); return Plugin_Handled; } @@ -254,7 +314,7 @@ Action Command_NextPack(int client,int args) if (strcmp(nextpack, "") == 0) { - CPrintToChat(client,"{dodgerblue}%t{lightblue}%t.","SF2 Prefix","Pending Vote"); + CPrintToChat(client,"{royalblue}%t {lightblue}%t.","SF2 Prefix","Pending Vote"); return Plugin_Handled; } @@ -271,12 +331,107 @@ Action Command_NextPack(int client,int args) g_BossPackConfig.GetString("name", bossPackName, sizeof(bossPackName), nextpack); if (bossPackName[0] == '\0') { - FormatEx(bossPackName,sizeof(bossPackName),"Core Pack"); + FormatEx(bossPackName, sizeof(bossPackName), "Core Pack"); + } + CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Boss Pack Next", bossPackName); + return Plugin_Handled; +} + +Action Command_BossOverride(int client, int args) +{ + if (args) + { + ArrayList bossList = GetBossProfileList(); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetCmdArgString(profile, sizeof(profile)); + + if(bossList.FindString(profile) == -1) + { + CReplyToCommand(client, "{royalblue}%t {default}Added profile \"%s\" to be forced loaded next pack", "SF2 Prefix", profile); + } + else + { + char displayName[SF2_MAX_NAME_LENGTH]; + NPCGetBossName(_, displayName, sizeof(displayName), profile); + CReplyToCommand(client, "{royalblue}%t {default}Added boss \"%s\" (%s) to be forced loaded next pack", "SF2 Prefix", displayName, profile); + } + + if(!g_BossProfileRequested) + { + g_BossProfileRequested = new ArrayList(sizeof(profile)); + } + + g_BossProfileRequested.PushString(profile); + } + else if (client) + { + Menu menu = new Menu(Command_BossOverrideHandle); + + ArrayList bossList = g_AllBosses; + + if (bossList != null) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + char displayName[SF2_MAX_NAME_LENGTH]; + for (int i = 0; i < bossList.Length; i++) + { + bossList.GetString(i, profile, sizeof(profile)); + displayName[0] = 0; + //NPCGetBossName(_, displayName, sizeof(displayName), profile); + g_AllBossesName.GetString(profile, displayName, sizeof(displayName)); + if (displayName[0] == '\0') + { + strcopy(displayName, sizeof(displayName), profile); + } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + /*if (data.Description.Hidden || data.IsPvEBoss) + { + continue; + }*/ + menu.AddItem(profile, displayName); + } + } + + menu.SetTitle("Force Load Next Pack\nUsage: sm_sf2_set_modboss \n "); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); + } + else + { + PrintToServer("[SM] Usage: sm_sf2_set_modboss "); } - CPrintToChat(client,"{dodgerblue}Next pack: {lightblue}%s",bossPackName); return Plugin_Handled; } +static int Command_BossOverrideHandle(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + delete menu; + } + case MenuAction_Cancel: + { + if(param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + case MenuAction_Select: + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + menu.GetItem(param2, profile, sizeof(profile)); + + FakeClientCommand(param1, "sm_sf2_set_modboss %s", profile); + } + } + + return 0; +} + void BossProfilesOnMapEnd() { ClearBossProfiles(); @@ -287,6 +442,8 @@ static void PreUnloadBossProfile(const char[] profile) SF2BossProfileData profileData; g_BossProfileData.GetArray(profile, profileData, sizeof(profileData)); + LogSF2Message("Unloading %s...", profile); + int bossType = GetBossProfileType(profile); switch (bossType) { @@ -352,6 +509,15 @@ void UnloadBossProfile(const char[] profile) g_SelectableRenevantBossAdminProfileList.Erase(index); } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + strcopy(setProfile, sizeof(setProfile), profile); + UnregisterPvESlenderBoss(setProfile); + } + g_BossProfileData.Remove(profile); g_Config.Rewind(); @@ -367,6 +533,11 @@ void UnloadBossProfile(const char[] profile) void ClearBossProfiles() { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + if (g_BossProfileList == null) + { + return; + } + for (int i = 0; i < g_BossProfileList.Length; i++) { g_BossProfileList.GetString(i, profile, sizeof(profile)); @@ -376,6 +547,10 @@ void ClearBossProfiles() continue; } + Call_StartForward(g_OnBossProfileUnloadedFwd); + Call_PushString(profile); + Call_Finish(); + PreUnloadBossProfile(profile); } @@ -459,21 +634,27 @@ void ReloadBossProfiles() g_SelectableRenevantBossAdminProfileList = new ArrayList(SF2_MAX_PROFILE_NAME_LENGTH); } - if (g_hSelectableBossProfileQueueList != null) + if (g_SelectableBossProfileQueueList != null) { - delete g_hSelectableBossProfileQueueList; + delete g_SelectableBossProfileQueueList; } + delete g_AllBosses; + g_AllBosses = new ArrayList(PLATFORM_MAX_PATH); + + delete g_AllBossesName; + g_AllBossesName = new StringMap(); + char configPath[PLATFORM_MAX_PATH]; // Only load profiles individually from configs/sf2/profiles or data/sf2/profiles directory. if (!g_UseAlternateConfigDirectoryConVar.BoolValue) { - LoadProfilesFromDirectory(FILE_PROFILES_DIR); + LoadProfilesFromDirectory(FILE_PROFILES_DIR, g_MaxCorePackBosses.IntValue); } else { - LoadProfilesFromDirectory(FILE_PROFILES_DIR_DATA); + LoadProfilesFromDirectory(FILE_PROFILES_DIR_DATA, g_MaxCorePackBosses.IntValue); } if (!g_UseAlternateConfigDirectoryConVar.BoolValue) @@ -505,7 +686,7 @@ void ReloadBossProfiles() char bossPackName[128]; g_BossPackConfig.GetSectionName(bossPackName, sizeof(bossPackName)); - bool autoLoad = !!g_BossPackConfig.GetNum("autoload"); + bool autoLoad = g_BossPackConfig.GetNum("autoload") != 0; if (autoLoad || (mapBossPack[0] != '\0' && strcmp(mapBossPack, bossPackName) == 0)) { @@ -527,12 +708,18 @@ void ReloadBossProfiles() if (DirExists(configPath)) { FormatEx(packConfigFilePath, sizeof(packConfigFilePath), "%s/%s", !g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_PROFILES_PACKS_DIR : FILE_PROFILES_PACKS_DIR_DATA, packConfigFile); - LoadProfilesFromDirectory(packConfigFilePath); + int maxLoadedBosses = -1; + if (g_BossPackConfig.JumpToKey("shuffler")) + { + maxLoadedBosses = g_BossPackConfig.GetNum("max", maxLoadedBosses); + g_BossPackConfig.GoBack(); + } + LoadProfilesFromDirectory(packConfigFilePath, maxLoadedBosses); } if (!voteBossPackLoaded) { - if (strcmp(mapBossPack, bossPackName) == 0) + if (!autoLoad && strcmp(mapBossPack, bossPackName) == 0) { voteBossPackLoaded = true; } @@ -573,7 +760,13 @@ void ReloadBossProfiles() if (DirExists(configPath)) { FormatEx(packConfigFilePath, sizeof(packConfigFilePath), "%s/%s", !g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_PROFILES_PACKS_DIR : FILE_PROFILES_PACKS_DIR_DATA, packConfigFile); - LoadProfilesFromDirectory(packConfigFilePath); + int maxLoadedBosses = -1; + if (g_BossPackConfig.JumpToKey("shuffler")) + { + maxLoadedBosses = g_BossPackConfig.GetNum("max", maxLoadedBosses); + g_BossPackConfig.GoBack(); + } + LoadProfilesFromDirectory(packConfigFilePath, maxLoadedBosses); } } } @@ -593,7 +786,7 @@ void ReloadBossProfiles() { g_BossPackVoteEnabled = false; } - g_hSelectableBossProfileQueueList = g_SelectableBossProfileList.Clone(); + g_SelectableBossProfileQueueList = g_SelectableBossProfileList.Clone(); g_BossProfilePackConVar.SetString(""); @@ -601,12 +794,14 @@ void ReloadBossProfiles() LogSF2Message("Time to take to load all boss configs: %f", profiler.Time); delete profiler; + + delete g_BossProfileRequested; } /** * Loads a profile from the specified file. */ -static bool LoadProfileFile(const char[] profilePath, char[] profileName, int profileNameLen, char[] errorReason, int errorReasonLen) +static bool LoadProfileFile(const char[] profilePath, char[] profileName, int profileNameLen, char[] errorReason, int errorReasonLen, bool lookIntoLoads = false, const char[] originalDir) { if (!FileExists(profilePath)) { @@ -624,14 +819,14 @@ static bool LoadProfileFile(const char[] profilePath, char[] profileName, int pr kv.GetSectionName(profileName, profileNameLen); - bool result = LoadBossProfile(kv, profileName, errorReason, errorReasonLen); + bool result = LoadBossProfile(kv, profileName, errorReason, errorReasonLen, lookIntoLoads, originalDir); delete kv; return result; } -static void LoadProfilesFromDirectory(const char[] relDirPath) +static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBosses = -1) { LogSF2Message("Loading boss profile files from directory %s...", relDirPath); @@ -653,11 +848,13 @@ static void LoadProfilesFromDirectory(const char[] relDirPath) int count = 0; - char filePath[PLATFORM_MAX_PATH]; - char fileName[PLATFORM_MAX_PATH]; + char filePath[PLATFORM_MAX_PATH], filePathEx[PLATFORM_MAX_PATH]; + char fileName[PLATFORM_MAX_PATH], fileNameEx[PLATFORM_MAX_PATH]; char profileName[SF2_MAX_PROFILE_NAME_LENGTH]; char errorReason[512]; - FileType fileType; + FileType fileType, fileTypeEx; + + ArrayList directories = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); while (directory.GetNext(fileName, sizeof(fileName), fileType)) { @@ -669,7 +866,173 @@ static void LoadProfilesFromDirectory(const char[] relDirPath) FormatEx(filePath, sizeof(filePath), "%s/%s", relDirPath, fileName); BuildPath(Path_SM, filePath, sizeof(filePath), filePath); - if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason))) + directories.PushString(filePath); + } + + ArrayList alwaysLoad; + ArrayList prioLoad; + + //if (maxLoadedBosses > 0 || g_BossProfileRequested) + { + alwaysLoad = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + prioLoad = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + + for (int i = 0; i < directories.Length; i++) + { + directories.GetString(i, filePath, sizeof(filePath)); + + if (!FileExists(filePath)) + { + continue; + } + KeyValues kv = new KeyValues("root"); + if (!FileToKeyValues(kv, filePath)) + { + delete kv; + continue; + } + kv.GetSectionName(profileName, sizeof(profileName)); + if(kv.GetNum("admin_only")) + { + g_AllBosses.PushString(profileName); + kv.GetString("name", fileName, sizeof(fileName)); + g_AllBossesName.SetString(profileName, fileName); + } + + if (kv.GetNum("always_load", false) != 0) + { + int index = directories.FindString(filePath); + if (index != -1) + { + directories.Erase(index); + } + alwaysLoad.PushString(filePath); + i--; + } + else if (g_BossProfileRequested && g_BossProfileRequested.FindString(profileName) != -1) + { + int index = directories.FindString(filePath); + if (index != -1) + { + directories.Erase(index); + } + prioLoad.PushString(filePath); + i--; + } + + if (kv.JumpToKey("always_load_with") && kv.GotoFirstSubKey(false)) + { + char key[64], boss[SF2_MAX_PROFILE_NAME_LENGTH]; + do + { + kv.GetSectionName(key, sizeof(key)); + kv.GetString(NULL_STRING, boss, sizeof(boss)); + if (boss[0] == '\0') + { + continue; + } + + delete directory; + directory = OpenDirectory(dirPath); + while (directory.GetNext(fileNameEx, sizeof(fileNameEx), fileTypeEx)) + { + if (fileTypeEx == FileType_Directory) + { + continue; + } + + FormatEx(filePathEx, sizeof(filePathEx), "%s/%s", relDirPath, fileNameEx); + BuildPath(Path_SM, filePathEx, sizeof(filePathEx), filePathEx); + + KeyValues kvEx = new KeyValues("root"); + if (!FileToKeyValues(kvEx, filePathEx)) + { + delete kvEx; + continue; + } + + char section[SF2_MAX_PROFILE_NAME_LENGTH]; + kvEx.GetSectionName(section, sizeof(section)); + + if (strcmp(section, boss, false) != 0) + { + delete kvEx; + continue; + } + + alwaysLoad.PushString(filePathEx); + delete kvEx; + break; + } + } + while (kv.GotoNextKey(false)); + } + + delete kv; + } + } + + delete directory; + + if (alwaysLoad != null) + { + for (int i = 0; i < alwaysLoad.Length; i++) + { + alwaysLoad.GetString(i, filePath, sizeof(filePath)); + + if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) + { + LogSF2Message("(ALWAYS LOAD) %s...FAILED (reason: %s)", filePath, errorReason); + } + else + { + LogSF2Message("(ALWAYS LOAD) %s...", profileName, filePath); + } + } + + delete alwaysLoad; + } + + if (prioLoad != null) + { + for (int i = 0; i < prioLoad.Length; i++) + { + if (maxLoadedBosses > 0 && count >= maxLoadedBosses) + { + break; + } + + prioLoad.GetString(i, filePath, sizeof(filePath)); + + if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) + { + LogSF2Message("(ADMIN LOAD) %s...FAILED (reason: %s)", filePath, errorReason); + } + else + { + LogSF2Message("(ADMIN LOAD) %s...", profileName, filePath); + count++; + } + } + + delete prioLoad; + } + + if (maxLoadedBosses > 0) + { + directories.Sort(Sort_Random, Sort_String); + } + + for (int i = 0; i < directories.Length; i++) + { + if (maxLoadedBosses > 0 && count >= maxLoadedBosses) + { + break; + } + + directories.GetString(i, filePath, sizeof(filePath)); + + if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) { LogSF2Message("%s...FAILED (reason: %s)", filePath, errorReason); } @@ -680,9 +1043,9 @@ static void LoadProfilesFromDirectory(const char[] relDirPath) } } - delete directory; + delete directories; - LogSF2Message("Loaded %d boss profile(s) from directory!", count, relDirPath); + LogSF2Message("Loaded %d boss profile(s) from directory %s!", count, relDirPath); } Handle g_BossPackVoteMapTimer = null; @@ -765,7 +1128,7 @@ void CheckRoundLimitForBossPackVote(int roundCount) void InitiateBossPackVote(int initiator) { - if (initiator<33) //A admin called the command, it's probably for a good reason + if (initiator < 33) //A admin called the command, it's probably for a good reason { g_BossPackVoteCompleted = false; } @@ -788,11 +1151,12 @@ void InitiateBossPackVote(int initiator) { return; } + Handle voteMenu = NativeVotes_Create(Menu_BossPackVote, NativeVotesType_Custom_Mult); NativeVotes_SetInitiator(voteMenu, initiator); char title[255]; - FormatEx(title,255,"%t%t","SF2 Prefix","SF2 Boss Pack Vote Menu Title"); - NativeVotes_SetDetails(voteMenu,title); + FormatEx(title, 255, "%t%t", "SF2 Prefix", "SF2 Boss Pack Vote Menu Title"); + NativeVotes_SetDetails(voteMenu, title); StringMap menuDisplayNamesTrie = new StringMap(); ArrayList menuOptionsInfo = new ArrayList(128); int voteIndex = 0; @@ -800,7 +1164,7 @@ void InitiateBossPackVote(int initiator) do { - if (!g_BossPackConfig.GetNum("autoload") && !!g_BossPackConfig.GetNum("show_in_vote", 1)) + if (g_BossPackConfig.GetNum("autoload", false) == 0 && g_BossPackConfig.GetNum("show_in_vote", true) != 0) { char bossPack[128]; g_BossPackConfig.GetSectionName(bossPack, sizeof(bossPack)); @@ -1225,18 +1589,18 @@ bool GetRandomRenevantBossProfile(char[] sBuffer, int iBufferLen) */ ArrayList GetSelectableBossProfileQueueList() { - if (g_hSelectableBossProfileQueueList.Length <= 0) //If every boss were selected at least once, refill the list. + if (g_SelectableBossProfileQueueList.Length <= 0) //If every boss were selected at least once, refill the list. { - delete g_hSelectableBossProfileQueueList; - g_hSelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); + delete g_SelectableBossProfileQueueList; + g_SelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); } - if (g_hSelectableBossProfileQueueList == null) + if (g_SelectableBossProfileQueueList == null) { - g_hSelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); + g_SelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); } - return g_hSelectableBossProfileQueueList; + return g_SelectableBossProfileQueueList; } void RemoveBossProfileFromQueueList(const char[] profile) @@ -1263,6 +1627,20 @@ static any Native_GetStatueBossProfileData(Handle plugin,int numParams) return g_StatueBossProfileData; } +static any Native_TranslateProfileActivityFromName(Handle plugin, int numParams) +{ + char activityName[64]; + GetNativeString(1, activityName, sizeof(activityName)); + return TranslateProfileActivityFromName(activityName); +} + +static any Native_LookupProfileAnimation(Handle plugin, int numParams) +{ + char animationName[64]; + GetNativeString(2, animationName, sizeof(animationName)); + return LookupProfileAnimation(GetNativeCell(1), animationName); +} + static any Native_IsBossProfileValid(Handle plugin,int numParams) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -1331,42 +1709,6 @@ static any Native_GetBossProfileVector(Handle plugin,int numParams) return success; } -static any Native_GetBossProfileDifficultyNumValues(Handle plugin,int numParams) -{ - char keyValue[PLATFORM_MAX_PATH]; - GetNativeString(2, keyValue, sizeof(keyValue)); - int result[Difficulty_Max]; - int defaultValue[Difficulty_Max]; - GetNativeArray(3, result, Difficulty_Max); - GetNativeArray(4, defaultValue, Difficulty_Max); - GetProfileDifficultyNumValues(GetNativeCell(1), keyValue, result, defaultValue); - return 0; -} - -static any Native_GetBossProfileDifficultyBoolValues(Handle plugin,int numParams) -{ - char keyValue[PLATFORM_MAX_PATH]; - GetNativeString(2, keyValue, sizeof(keyValue)); - bool result[Difficulty_Max]; - bool defaultValue[Difficulty_Max]; - GetNativeArray(3, result, Difficulty_Max); - GetNativeArray(4, defaultValue, Difficulty_Max); - GetProfileDifficultyBoolValues(GetNativeCell(1), keyValue, result, defaultValue); - return 0; -} - -static any Native_GetBossProfileDifficultyFloatValues(Handle plugin,int numParams) -{ - char keyValue[PLATFORM_MAX_PATH]; - GetNativeString(2, keyValue, sizeof(keyValue)); - float result[Difficulty_Max]; - float defaultValue[Difficulty_Max]; - GetNativeArray(3, result, Difficulty_Max); - GetNativeArray(4, defaultValue, Difficulty_Max); - GetProfileDifficultyFloatValues(GetNativeCell(1), keyValue, result, defaultValue); - return 0; -} - static any Native_GetBossAttackProfileNum(Handle plugin,int numParams) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -1438,9 +1780,9 @@ static any Native_GetRandomStringFromBossProfile(Handle plugin,int numParams) int bufferLen = GetNativeCell(4); char[] buffer = new char[bufferLen]; - int iIndex = GetNativeCell(5); + int index = GetNativeCell(5); - bool success = GetRandomStringFromProfile(profile, keyValue, buffer, bufferLen, iIndex); + bool success = GetRandomStringFromProfile(profile, keyValue, buffer, bufferLen, index); SetNativeString(3, buffer, bufferLen); return success; } diff --git a/addons/sourcemod/scripting/sf2/profiles/keymap.sp b/addons/sourcemod/scripting/sf2/profiles/keymap.sp new file mode 100644 index 00000000..58a54259 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/profiles/keymap.sp @@ -0,0 +1,901 @@ +#pragma semicolon 1 +#pragma newdecls required + +enum KeyMapKeyType +{ + Key_Type_Invalid = -1, + Key_Type_Value = 0, + Key_Type_Section +}; + +methodmap KeyMap < StringMap +{ + public KeyMap() + { + return view_as(new StringMap()); + } + + property StringMap Super + { + public get() + { + return view_as(this); + } + } + + property KeyMap Parent + { + public get() + { + KeyMap obj = null; + this.GetValue("__parent", obj); + return obj; + } + + public set(KeyMap val) + { + this.SetValue("__parent", val); + } + } + + property bool IsArray + { + public get() + { + bool value = false; + this.GetValue("__is_array", value); + return value; + } + + public set(bool value) + { + this.SetValue("__is_array", value); + } + } + + property int KeyLength + { + public get() + { + int value = 0; + this.GetValue("__key_length", value); + return value; + } + + public set(int value) + { + this.SetValue("__key_length", value); + } + } + + property int SectionLength + { + public get() + { + int value = 0; + this.GetValue("__section_length", value); + return value; + } + + public set(int value) + { + this.SetValue("__section_length", value); + } + } + + public KeyMapKeyType GetType(const char[] key) + { + int size = strlen(key) + 11 + 1; + char type[16]; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keytype", key); + if (!this.Super.GetString(formatter, type, sizeof(type))) + { + return Key_Type_Invalid; + } + if (strcmp(type, "value") == 0) + { + return Key_Type_Value; + } + else if (strcmp(type, "section") == 0) + { + return Key_Type_Section; + } + return Key_Type_Invalid; + } + + public void SetType(const char[] key, KeyMapKeyType value) + { + int size = strlen(key) + 11 + 1; + char type[16]; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keytype", key); + switch (value) + { + case Key_Type_Value: + { + strcopy(type, sizeof(type), "value"); + } + + case Key_Type_Section: + { + strcopy(type, sizeof(type), "section"); + } + + case Key_Type_Invalid: + { + strcopy(type, sizeof(type), "invalid"); + } + } + this.Super.SetString(formatter, type); + } + + public int GetInt(const char[] key, int def = -1) + { + char string[32]; + bool state = this.Super.GetString(key, string, sizeof(string)); + if (!state) + { + return def; + } + return StringToInt(string); + } + + public float GetFloat(const char[] key, float def = -1.0) + { + char string[32]; + bool state = this.Super.GetString(key, string, sizeof(string)); + if (!state) + { + return def; + } + return StringToFloat(string); + } + + public bool GetBool(const char[] key, bool def = false) + { + char string[32]; + bool state = this.Super.GetString(key, string, sizeof(string)); + if (!state) + { + return def; + } + return StringToBool(string); + } + + public void GetString(const char[] key, char[] buffer, int bufferSize, const char[] def = "") + { + bool status = this.Super.GetString(key, buffer, bufferSize); + if (!status) + { + strcopy(buffer, bufferSize, def); + } + } + + public KeyMap GetSection(const char[] key, KeyMap def = null) + { + if (this.GetType(key) != Key_Type_Section) + { + return def; + } + + KeyMap obj = def; + this.GetValue(key, obj); + return obj; + } + + public void SetKeyIndex(const char[] key, int value) + { + int size = strlen(key) + 16 + 1; + char[] formatter = new char[size]; + if (this.GetType(key) == Key_Type_Section) + { + FormatEx(formatter, size, "%i||__sectionindex", value); + } + else + { + FormatEx(formatter, size, "%i||__keyindex", value); + } + this.Super.SetString(formatter, key); + } + + public bool GetKeyNameFromIndex(int key, char[] buffer, int bufferSize) + { + char formatter[128]; + FormatEx(formatter, sizeof(formatter), "%i||__keyindex", key); + return this.Super.GetString(formatter, buffer, bufferSize); + } + + public bool GetSectionNameFromIndex(int key, char[] buffer, int bufferSize) + { + char formatter[128]; + FormatEx(formatter, sizeof(formatter), "%i||__sectionindex", key); + return this.Super.GetString(formatter, buffer, bufferSize); + } + + public void SetIndexKey(const char[] key, int value) + { + int size = strlen(key) + 21 + 1; + char[] formatter = new char[size]; + if (this.GetType(key) == Key_Type_Section) + { + FormatEx(formatter, size, "%s||__sectionindexvalue", key); + } + else + { + FormatEx(formatter, size, "%s||__keyindexvalue", key); + } + this.Super.SetValue(formatter, value); + } + + public int GetIndexFromKey(const char[] key) + { + int size = strlen(key) + 17 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keyindexvalue", key); + int value = -1; + this.Super.GetValue(formatter, value); + return value; + } + + public int GetIndexFromSection(const char[] key) + { + int size = strlen(key) + 21 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__sectionindexvalue", key); + int value = -1; + this.Super.GetValue(formatter, value); + return value; + } + + public void SetKeyValueLength(const char[] key, int value) + { + if (this.GetType(key) == Key_Type_Section) + { + return; + } + int size = strlen(key) + 12 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keyvaluesize", key); + this.Super.SetValue(formatter, value); + } + + public int GetKeyValueLength(const char[] key) + { + if (this.GetType(key) == Key_Type_Section) + { + return -1; + } + int size = strlen(key) + 12 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keyvaluesize", key); + this.Super.GetValue(formatter, size); + return size; + } + + public void SetKeyValue(const char[] key, const char[] value) + { + this.SetType(key, Key_Type_Value); + this.Super.SetString(key, value); + this.SetKeyIndex(key, this.KeyLength); + this.SetIndexKey(key, this.KeyLength); + this.KeyLength++; + this.SetKeyValueLength(key, strlen(value) + 1); + } + + public KeyMap SetKeySection(const char[] key) + { + KeyMap newValue = new KeyMap(); + this.SetSection(key, newValue); + this.SetType(key, Key_Type_Section); + newValue.SetSectionName(key); + newValue.Parent = this; + this.SetKeyIndex(key, this.SectionLength); + this.SetIndexKey(key, this.SectionLength); + this.SectionLength++; + + /* + KeyMap child = obj.GetSection(key); + if (child == null) + { + child = new KeyMap(); + obj.SetSection(key, child); + child.SetSectionName(key); + child.Parent = obj; + obj.SetKeyIndex(key, obj.SectionLength); + obj.SectionLength++; + } + + if (kv.GotoFirstSubKey(false)) + { + IterateKeyValues(kv, child); + kv.GoBack(); + } + } + else + { + obj.SetType(key, Key_Type_Value); + kv.GetString(NULL_STRING, value, sizeof(value)); + obj.SetString(key, value); + obj.SetKeyIndex(key, obj.KeyLength); + obj.KeyLength++; + } + */ + return newValue; + } + + public void RemoveKey(const char[] key) + { + int index = -1, currentIndex = -1, size; + if (this.GetType(key) == Key_Type_Section) + { + KeyMap section = this.GetSection(key); + index = this.GetIndexFromSection(key); + currentIndex = index + 1; + size = strlen(key) + 21 + 1; + char[] formatter3 = new char[size]; + FormatEx(formatter3, size, "%s||__sectionindexvalue", key); + this.Super.Remove(formatter3); + + while (index >= 0 && currentIndex < this.SectionLength) + { + char hiddenKey[512]; + this.GetSectionNameFromIndex(currentIndex, hiddenKey, sizeof(hiddenKey)); + this.SetKeyIndex(hiddenKey, currentIndex - 1); + this.SetIndexKey(hiddenKey, currentIndex - 1); + currentIndex++; + } + + CleanupKeyMap(section); + char formatter4[128]; + FormatEx(formatter4, sizeof(formatter4), "%i||__keyindex", currentIndex - 1); + this.Super.Remove(formatter4); + this.SectionLength--; + } + else + { + index = this.GetIndexFromKey(key); + currentIndex = index + 1; + size = strlen(key) + 17 + 1; + char[] formatter2 = new char[size]; + FormatEx(formatter2, size, "%s||__keyindexvalue", key); + this.Super.Remove(formatter2); + + char[] formatter3 = new char[size]; + FormatEx(formatter3, size, "%s||__keyvaluesize", key); + this.Super.Remove(formatter3); + while (index >= 0 && currentIndex < this.KeyLength) + { + char hiddenKey[512]; + this.GetKeyNameFromIndex(currentIndex, hiddenKey, sizeof(hiddenKey)); + this.SetKeyIndex(hiddenKey, currentIndex - 1); + this.SetIndexKey(hiddenKey, currentIndex - 1); + currentIndex++; + } + + char formatter4[128]; + FormatEx(formatter4, sizeof(formatter4), "%i||__keyindex", currentIndex - 1); + this.Super.Remove(formatter4); + this.KeyLength--; + } + + this.Super.Remove(key); + + size = strlen(key) + 11 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keytype", key); + this.Super.Remove(formatter); + } + + public void RenameKey(const char[] key, const char[] newKey) + { + KeyMapKeyType type = this.GetType(key); + int index = 0; + if (type == Key_Type_Section) + { + KeyMap section = this.GetSection(key); + this.SetSection(newKey, section); + section.SetSectionName(newKey); + index = this.GetIndexFromSection(key); + } + else + { + this.SetType(newKey, Key_Type_Value); + int valueSize = this.GetKeyValueLength(key); + char[] value = new char[valueSize]; + this.Super.GetString(key, value, valueSize); + this.Super.SetString(newKey, value); + index = this.GetIndexFromKey(key); + } + this.RemoveKey(key); + + this.SetKeyIndex(newKey, index); + this.SetIndexKey(newKey, index); + } + + public void SetSection(const char[] key, KeyMap value) + { + this.Super.SetValue(key, value); + this.SetType(key, Key_Type_Section); + } + + public KeyMap Clone() + { + KeyMap clone = CloneKeyMap(this); + clone.Parent = null; + KeyMap section = clone.GetSection("execution"); + if (section != null) + { + section.GetSection("paths"); + } + return clone; + } + + public void GetSectionName(char[] buffer, int bufferSize) + { + this.GetString("__section_name", buffer, bufferSize); + } + + public bool SetSectionName(const char[] buffer) + { + return this.Super.SetString("__section_name", buffer); + } + + public bool SetInt(const char[] key, int value) + { + char stringValue[128]; + IntToString(value, stringValue, sizeof(stringValue)); + this.SetString(key, stringValue); + return false; + } + + public bool SetFloat(const char[] key, float value) + { + char stringValue[128]; + FloatToString(value, stringValue, sizeof(stringValue)); + this.SetString(key, stringValue); + return false; + } + + public bool SetBool(const char[] key, bool value) + { + char stringValue[6]; + BoolToString(value, stringValue, sizeof(stringValue)); + this.SetString(key, stringValue); + return false; + } + + public int IndexOfSection(KeyMap value) + { + int length = this.Size; + for (int i = 0; i < length; i++) + { + char keyName[512]; + this.GetKeyNameFromIndex(i, keyName, sizeof(keyName)); + if (this.GetType(keyName) != Key_Type_Section) + { + continue; + } + + KeyMap current = this.GetSection(keyName); + if (value == current) + { + return i; + } + } + + return -1; + } + + public int IndexOfString(const char[] value) + { + int length = this.Size; + for (int i = 0; i < length; i++) + { + char keyName[512]; + this.GetKeyNameFromIndex(i, keyName, sizeof(keyName)); + if (this.GetType(keyName) != Key_Type_Value) + { + continue; + } + + char current[512]; + this.GetString(keyName, current, sizeof(current)); + if (strcmp(value, current) == 0) + { + return i; + } + } + + return -1; + } + + public bool ContainsString(const char[] value) + { + return this.IndexOfString(value) != -1; + } + + public void ConvertValuesSectionToArray(const char[] name) + { + KeyMap obj = this.GetSection(name); + if (obj == null) + { + return; + } + + KeyMap_Array array = new KeyMap_Array(Key_Type_Value); + + for (int i = 0; i < obj.KeyLength; i++) + { + char key[512]; + obj.GetKeyNameFromIndex(i, key, sizeof(key)); + + KeyMapKeyType type = obj.GetType(key); + if (!IsHiddenKey(key) && type == Key_Type_Value) + { + int size = obj.GetKeyValueLength(key); + char[] value = new char[size]; + obj.GetString(key, value, size); + array.PushString(value); + } + } + + delete obj; + + this.Remove(name); + this.SetSection(name, array); + array.SetSectionName(name); + array.Parent = this; + } + + public void ConvertSectionsSectionToArray(const char[] name) + { + KeyMap obj = this.GetSection(name); + if (obj == null) + { + return; + } + + KeyMap_Array array = new KeyMap_Array(Key_Type_Section); + + for (int i = 0; i < obj.SectionLength; i++) + { + char key[512]; + obj.GetSectionNameFromIndex(i, key, sizeof(key)); + + KeyMap section = obj.GetSection(key); + if (!IsHiddenKey(key) && section != null) + { + array.PushSection(section); + } + } + + delete obj; + + this.Remove(name); + this.SetSection(name, array); + array.SetSectionName(name); + array.Parent = this; + } +} + +methodmap KeyMap_Array < KeyMap +{ + public KeyMap_Array(KeyMapKeyType type = Key_Type_Invalid) + { + KeyMap_Array self = view_as(new KeyMap()); + self.IsArray = true; + self.SetType(type); + + return self; + } + + property KeyMap Super + { + public get() + { + return view_as(this); + } + } + + property int Length + { + public get() + { + return this.Super.GetInt("__length", 0); + } + + public set(int value) + { + this.Super.SetInt("__length", value); + } + } + + public bool CanUseType(KeyMapKeyType type) + { + return this.GetType() == Key_Type_Invalid || this.GetType() == type; + } + + public KeyMapKeyType GetType() + { + char key[64], type[16], formatter[128]; + this.GetSectionName(key, sizeof(key)); + FormatEx(formatter, sizeof(formatter), "%s||__arraytype", key); + if (this.Super.Super.GetString(formatter, type, sizeof(type))) + { + return Key_Type_Invalid; + } + if (strcmp(type, "value") == 0) + { + return Key_Type_Value; + } + else if (strcmp(type, "section") == 0) + { + return Key_Type_Section; + } + return Key_Type_Invalid; + } + + public void SetType(KeyMapKeyType value) + { + char key[64], type[16], formatter[128]; + this.GetSectionName(key, sizeof(key)); + FormatEx(formatter, sizeof(formatter), "%s||__arraytype", key); + switch (value) + { + case Key_Type_Value: + { + strcopy(type, sizeof(type), "value"); + } + + case Key_Type_Section: + { + strcopy(type, sizeof(type), "section"); + } + + case Key_Type_Invalid: + { + strcopy(type, sizeof(type), "invalid"); + } + } + this.Super.Super.SetString(formatter, type); + } + + public void SetKeyType(int key, KeyMapKeyType value) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + this.Super.SetType(keyName, value); + } + + public void GetString(int key, char[] buffer, int bufferSize, const char[] def = "") + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + this.Super.GetString(keyName, buffer, bufferSize, def); + } + + public int GetInt(int key, int def = -1) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetInt(keyName, def); + } + + public float GetFloat(int key, float def = -1.0) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetFloat(keyName, def); + } + + public bool GetBool(int key, bool def = false) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetBool(keyName, def); + } + + public KeyMap GetSection(int key, KeyMap def = null) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetSection(keyName, def); + } + + public KeyMapKeyType GetTypeFromKey(int key) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetType(keyName); + } + + public bool SetString(int index, const char[] value) + { + if (!this.CanUseType(Key_Type_Value)) + { + return false; + } + + char key[16]; + IntToString(index, key, sizeof(key)); + + return this.Super.SetString(key, value); + } + + public bool SetSection(int index, KeyMap value) + { + if (!this.CanUseType(Key_Type_Section)) + { + return false; + } + + char key[16]; + IntToString(index, key, sizeof(key)); + + return this.Super.SetValue(key, value); + } + + public int PushString(const char[] value) + { + int index = this.Length; + this.SetKeyType(index, Key_Type_Value); + if (!this.SetString(index, value)) + { + return -1; + } + this.Length++; + return index; + } + + public int PushSection(KeyMap value) + { + int index = this.Length; + this.SetKeyType(index, Key_Type_Section); + if (!this.SetSection(index, value)) + { + return -1; + } + this.Length++; + return index; + } + + public int IndexOf(const char[] value) + { + for (int i = 0; i < this.Length; i++) + { + if (this.GetTypeFromKey(i) != Key_Type_Value) + { + continue; + } + + char current[128]; + this.GetString(i, current, sizeof(current)); + if (strcmp(value, current) == 0) + { + return i; + } + } + + return -1; + } +} + +KeyMap KeyValuesToKeyMap(KeyValues kv, KeyMap obj = null) +{ + if (obj == null) + { + obj = new KeyMap(); + } + + if (kv.GotoFirstSubKey(false)) + { + IterateKeyValues(kv, obj); + kv.GoBack(); + } + + return obj; +} + +static void IterateKeyValues(KeyValues kv, KeyMap obj) +{ + char key[512], value[2048]; + + do + { + kv.GetSectionName(key, sizeof(key)); + + int keyLen = strlen(key); + for (int i = 0; i < keyLen; i++) + { + key[i] = CharToLower(key[i]); + } + + bool isSection = ((kv.NodesInStack() == 0) || (kv.GetDataType(NULL_STRING) == KvData_None && kv.NodesInStack() > 0)); + + if (isSection) + { + KeyMap child = obj.GetSection(key); + if (child == null) + { + child = obj.SetKeySection(key); + } + + if (kv.GotoFirstSubKey(false)) + { + IterateKeyValues(kv, child); + kv.GoBack(); + } + } + else + { + kv.GetString(NULL_STRING, value, sizeof(value)); + obj.SetKeyValue(key, value); + } + } + while (kv.GotoNextKey(false)); +} + +static KeyMap CloneKeyMap(KeyMap obj) +{ + if (obj == null) + { + return null; + } + + StringMap temp = view_as(obj).Clone(); + KeyMap clone = view_as(temp); + + for (int i = 0; i < clone.SectionLength; i++) + { + char key[512]; + clone.GetSectionNameFromIndex(i, key, sizeof(key)); + + KeyMap value = clone.GetSection(key); + if (value != null) + { + value = CloneKeyMap(value); + clone.SetSection(key, value); + value.Parent = clone; + } + } + + return clone; +} + +bool IsHiddenKey(const char[] key) +{ + return !strncmp(key, "__", 2); +} + +void CleanupKeyMap(KeyMap obj) +{ + if (obj == null) + { + return; + } + + StringMapSnapshot snapshot = obj.Snapshot(); + for (int i = 0; i < snapshot.Length; i += 1) + { + char key[512]; + snapshot.GetKey(i, key, sizeof(key)); + + KeyMap value = obj.GetSection(key); + if (value != null) + { + CleanupKeyMap(value); + } + } + + delete snapshot; + + delete obj; +} diff --git a/addons/sourcemod/scripting/sf2/profiles/objects.sp b/addons/sourcemod/scripting/sf2/profiles/objects.sp new file mode 100644 index 00000000..07d8a873 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/profiles/objects.sp @@ -0,0 +1,2964 @@ +#pragma semicolon 1 +#pragma newdecls required + +methodmap ProfileObject < KeyMap +{ + property KeyMap Super + { + public get() + { + return view_as(this); + } + } + + public bool ContainsDifficultyKey(const char[] key, int difficulty) + { + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + return this.ContainsKey(diffKey); + } + + public bool ContainsAnyKey(ArrayList keys) + { + if (keys == null) + { + return false; + } + for (int i = 0; i < keys.Length; i++) + { + char key[256]; + keys.GetString(i, key, sizeof(key)); + if (this.ContainsKey(key)) + { + return true; + } + } + return false; + } + + public bool ContainsAnyDifficultyKey(ArrayList keys, int specified = -1) + { + if (keys == null) + { + return false; + } + if (specified == -1) + { + for (int i = 0; i < keys.Length; i++) + { + char key[256]; + keys.GetString(i, key, sizeof(key)); + for (int i2 = 0; i2 < Difficulty_Max; i2++) + { + if (this.ContainsDifficultyKey(key, i2)) + { + return true; + } + } + } + } + else + { + for (int i = 0; i < keys.Length; i++) + { + char key[256]; + keys.GetString(i, key, sizeof(key)); + if (this.ContainsDifficultyKey(key, specified)) + { + return true; + } + } + } + return false; + } + + public void RemoveDifficultyKey(const char[] key, int difficulty) + { + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.RemoveKey(diffKey); + } + + public ProfileObject GetSection(const char[] key, ProfileObject def = null) + { + if (this == null) + { + return def; + } + + ProfileObject obj = view_as(this.Super.GetSection(key)); + if (obj == null) + { + return def; + } + + return obj; + } + + public bool GetRandomValues(const char[] key, float buffer[2]) + { + if (!this.ContainsKey(key)) + { + return false; + } + + char string[64]; + if (!view_as(this).GetString(key, string, sizeof(string))) + { + return false; + } + + int contains = StrContains(string, "(val)) != 0) == def) + { + val = this.Super.GetBool(key, def); + } + return val; + } + + public void SetBool(const char[] key, bool value, bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + this.Super.SetBool(key, value); + } + + public void GetString(const char[] key, char[] buffer, int bufferSize, const char[] def = "") + { + if (this == null) + { + strcopy(buffer, bufferSize, def); + return; + } + + this.Super.GetString(key, buffer, bufferSize, def); + } + + public bool SetString(const char[] key, const char[] value, bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + this.Super.SetString(key, value); + } + + public void GetVector(const char[] key, float buffer[3], const float def[3] = { 0.0, 0.0, 0.0 }) + { + if (this == null) + { + buffer = def; + return; + } + + if (!this.ContainsKey(key)) + { + buffer = def; + return; + } + + char vecString[64]; + this.GetString(key, vecString, sizeof(vecString)); + StringToVector(vecString, buffer); + } + + public void SetVector(const char[] key, float value[3], bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + char vecString[64]; + VectorToString(value, vecString, sizeof(vecString)); + this.SetString(key, vecString); + } + + public void GetColor(const char[] key, int buffer[4], const int def[4] = { 255, 255, 255, 255 }) + { + if (this == null) + { + buffer = def; + return; + } + + if (!this.ContainsKey(key)) + { + buffer = def; + return; + } + + char vecString[64]; + this.GetString(key, vecString, sizeof(vecString)); + StringToColor(vecString, buffer); + } + + public void SetColor(const char[] key, int value[4], bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + char vecString[64]; + ColorToString(value, vecString, sizeof(vecString)); + this.SetString(key, vecString); + } + + public KeyMap_Array GetArray(const char[] key, KeyMap_Array def = view_as(INVALID_HANDLE)) + { + if (this.GetType(key) != Key_Type_Section) + { + return def; + } + + KeyMap_Array value = view_as(this.Super.GetSection(key)); + if (value == null || !value.IsArray) + { + return def; + } + + return value; + } + + public ProfileObject GetDifficultySection(const char[] key, int difficulty, ProfileObject def = null) + { + if (this == null) + { + return def; + } + ProfileObject value = def; + + if (this.ContainsKey(key)) + { + value = this.GetSection(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetSection(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetSection(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetSection(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultySection(const char[] key, int difficulty, ProfileObject value) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetSection(diffKey, value); + } + + public int GetDifficultyInt(const char[] key, int difficulty, int def = 0) + { + if (this == null) + { + return def; + } + int value = def; + + if (this.ContainsKey(key)) + { + value = this.GetInt(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetInt(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetInt(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetInt(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultyInt(const char[] key, int difficulty, int value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetInt(diffKey, value, check); + } + + public float GetDifficultyFloat(const char[] key, int difficulty, float def = 0.0) + { + if (this == null) + { + return def; + } + float value = def; + + if (this.ContainsKey(key)) + { + value = this.GetFloat(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetFloat(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetFloat(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetFloat(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultyFloat(const char[] key, int difficulty, float value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetFloat(diffKey, value, check); + } + + public bool GetDifficultyBool(const char[] key, int difficulty, bool def = false) + { + if (this == null) + { + return def; + } + bool value = def; + + if (this.ContainsKey(key)) + { + value = this.GetBool(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetBool(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetBool(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetBool(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultyBool(const char[] key, int difficulty, bool value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetBool(diffKey, value, check); + } + + public void GetDifficultyVector(const char[] key, int difficulty, float buffer[3], const float def[3] = { 0.0, 0.0, 0.0 }) + { + buffer = def; + + if (this.ContainsKey(key)) + { + this.GetVector(key, buffer, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetVector(diffKey, buffer, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetVector(diffKey, buffer, def); + return; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetVector(diffKey, buffer, def); + return; + } + } + } + } + } + + public void SetDifficultyVector(const char[] key, int difficulty, float value[3], bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetVector(diffKey, value, check); + } + + public void GetDifficultyColor(const char[] key, int difficulty, int buffer[4], const int def[4] = { 255, 255, 255, 255 }) + { + buffer = def; + + if (this.ContainsKey(key)) + { + this.GetColor(key, buffer, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetColor(diffKey, buffer, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetColor(diffKey, buffer, def); + return; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetColor(diffKey, buffer, def); + return; + } + } + } + } + } + + public void SetDifficultyColor(const char[] key, int difficulty, int value[4], bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetColor(diffKey, value, check); + } + + public void GetDifficultyString(const char[] key, int difficulty, char[] buffer, int bufferSize, const char[] def = "") + { + strcopy(buffer, bufferSize, def); + + if (this.ContainsKey(key)) + { + this.GetString(key, buffer, bufferSize); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetString(diffKey, buffer, bufferSize, buffer); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetString(diffKey, buffer, bufferSize, buffer); + return; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetString(diffKey, buffer, bufferSize, buffer); + return; + } + } + } + } + } + + public void SetDifficultyString(const char[] key, int difficulty, const char[] value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetString(diffKey, value, check); + } + + public ProfileObject InsertNewSection(const char[] name) + { + if (this.GetSection(name) != null) + { + return this.GetSection(name); + } + ProfileObject newObj = view_as(new KeyMap()); + this.SetType(name, Key_Type_Section); + this.SetSection(name, newObj); + newObj.SetSectionName(name); + newObj.Parent = this; + this.SetKeyIndex(name, this.SectionLength); + this.SectionLength++; + return newObj; + } + + public void AddExistingSection(ProfileObject newObj) + { + if (newObj == null) + { + return; + } + char sectionName[512]; + newObj.GetSectionName(sectionName, sizeof(sectionName)); + this.SetSection(sectionName, newObj); + this.SetType(sectionName, Key_Type_Section); + newObj.Parent = this; + this.SetKeyIndex(sectionName, this.SectionLength); + this.SetIndexKey(sectionName, this.SectionLength); + this.SectionLength++; + } + + public void TransferKey(ProfileObject origin, const char[] oldKey, const char[] newKey) + { + char keyValue[2048]; + if (!origin.Super.Super.GetString(oldKey, keyValue, sizeof(keyValue))) + { + return; + } + this.SetKeyValue(newKey, keyValue); + } + + public void TransferDifficultyKey(ProfileObject target, const char[] oldKey, const char[] newKey, int difficulty) + { + int oldDiffKeySize = strlen(oldKey) + GetMaxProfileDifficultySuffixSize(); + char[] oldDiffKey = new char[oldDiffKeySize]; + GetProfileKeyWithDifficultySuffix(oldKey, difficulty, oldDiffKey, oldDiffKeySize); + + int newDiffKeySize = strlen(newKey) + GetMaxProfileDifficultySuffixSize(); + char[] newDiffKey = new char[newDiffKeySize]; + GetProfileKeyWithDifficultySuffix(newKey, difficulty, newDiffKey, newDiffKeySize); + + char keyValue[2048]; + if (!this.Super.GetString(oldDiffKey, keyValue, sizeof(keyValue))) + { + return; + } + target.SetKeyValue(newDiffKey, keyValue); + } +} + +methodmap ProfileSound < ProfileObject +{ + property KeyMap_Array Paths + { + public get() + { + return this.GetArray("paths"); + } + } + + public int GetChannel(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("channel", difficulty, this.Super.GetInt("__channel", SNDCHAN_AUTO)); + } + + public float GetVolume(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("volume", difficulty, SNDVOL_NORMAL); + } + + public int GetFlags(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("flags", difficulty, this.Super.GetInt("__flags", SND_NOFLAGS)); + } + + public int GetLevel(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("level", difficulty, SNDLEVEL_SCREAMING); + } + + public int GetPitch(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("pitch", difficulty, SNDPITCH_NORMAL); + } + + public float GetCooldownMin(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("cooldown_min", difficulty, this.Super.GetFloat("__cooldown_min", 1.5)); + } + + public float GetCooldownMax(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("cooldown_max", difficulty, this.Super.GetFloat("__cooldown_max", 1.5)); + } + + public float GetHurtCooldownMin(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("hurt_cooldown_min", difficulty, this.Super.GetFloat("__cooldown_min", 1.5)); + } + + public float GetHurtCooldownMax(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("hurt_cooldown_max", difficulty, this.Super.GetFloat("__cooldown_max", 1.5)); + } + + public int GetPitchRandomMin(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("pitch_random_min", difficulty, this.GetPitch(difficulty)); + } + + public int GetPitchRandomMax(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("pitch_random_max", difficulty, this.GetPitch(difficulty)); + } + + public float GetChance(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("chance", difficulty, 1.0); + } + + public void SetDefaultChannel(int channel) + { + this.SetInt("__channel", channel); + } + + public void SetDefaultFlags(int flags) + { + this.SetInt("__flags", flags); + } + + public void SetDefaultCooldownMin(float cooldown) + { + this.SetFloat("__cooldown_min", cooldown); + } + + public void SetDefaultCooldownMax(float cooldown) + { + this.SetFloat("__cooldown_max", cooldown); + } + + public void SetDefaultLevel(int level) + { + this.SetInt("__level", level); + } + + public void Precache() + { + KeyMap obj = this.GetSection("paths"); + if (obj == null) + { + obj = this; + } + bool doDelete = this.GetSection("paths") != null; + + KeyMap_Array paths = new KeyMap_Array(Key_Type_Value); + char path[PLATFORM_MAX_PATH]; + for (int i2 = 1;; i2++) + { + char num[16]; + IntToString(i2, num, sizeof(num)); + obj.GetString(num, path, sizeof(path)); + if (path[0] == '\0') + { + break; + } + + TryPrecacheBossProfileSoundPath(path, g_FileCheckConVar.BoolValue); + paths.PushString(path); + obj.RemoveKey(num); + } + if (doDelete) + { + this.RemoveKey("paths"); + } + this.SetType("paths", Key_Type_Section); + this.SetSection("paths", paths); + paths.SetSectionName("paths"); + paths.Parent = this; + + ProfileObject parent = view_as(this.Parent); + char section[64], formatter[90]; + this.GetSectionName(section, sizeof(section)); + for (int i = 0; i < Difficulty_Max; i++) + { + FormatEx(formatter, sizeof(formatter), "%s_channel", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("channel", i, parent.GetDifficultyInt(formatter, i, this.Super.GetInt("__channel", SNDCHAN_AUTO))); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_volume", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyFloat("volume", i, parent.GetDifficultyFloat(formatter, i, SNDVOL_NORMAL)); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_flags", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("flags", i, parent.GetDifficultyInt(formatter, i, this.Super.GetInt("__flags", SND_NOFLAGS))); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_level", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("level", i, parent.GetDifficultyInt(formatter, i, SNDLEVEL_SCREAMING)); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_pitch", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("pitch", i, parent.GetDifficultyInt(formatter, i, SNDPITCH_NORMAL)); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_cooldown_min", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyFloat("cooldown_min", i, parent.GetDifficultyFloat(formatter, i, this.Super.GetFloat("__cooldown_min", 1.5))); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_cooldown_max", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyFloat("cooldown_max", i, parent.GetDifficultyFloat(formatter, i, this.Super.GetFloat("__cooldown_max", 1.5))); + parent.RemoveDifficultyKey(formatter, i); + } + } + } + + public void StopAllSounds(int entity, int difficulty = Difficulty_Normal) + { + if (this == null) + { + return; + } + + KeyMap_Array paths = this.Paths; + if (paths == null) + { + return; + } + + char path[PLATFORM_MAX_PATH]; + for (int i = 0; i < paths.Length; i++) + { + paths.GetString(i, path, sizeof(path)); + if (path[0] == '\0') + { + continue; + } + + if (StrContains(path, ".mp3", true) == -1 && StrContains(path, ".wav", true) == -1) + { + continue; + } + + StopSound(entity, this.GetChannel(difficulty), path); + } + } + + public bool EmitSound(bool toClient = false, int entity = -1, int emitter = SOUND_FROM_PLAYER, float origin[3] = NULL_VECTOR, int addPitch = 0, char returnSound[PLATFORM_MAX_PATH] = "", int definedIndex = -1, int difficulty = Difficulty_Normal, float fixedChance = -1.0) + { + if (this == null) + { + return false; + } + + KeyMap_Array paths = this.Paths; + if (paths == null) + { + return false; + } + + char path[PLATFORM_MAX_PATH]; + if (definedIndex != -1) + { + paths.GetString(definedIndex, path, sizeof(path)); + } + else + { + paths.GetString(GetRandomInt(0, paths.Length - 1), path, sizeof(path)); + } + + returnSound = path; + + if (path[0] == '\0') + { + return false; + } + + float threshold = GetRandomFloat(0.0, 1.0); + if (fixedChance >= 0.0) + { + threshold = fixedChance; + } + if (threshold > this.GetChance(difficulty)) + { + return false; + } + + if (StrContains(path, ".mp3", true) == -1 && StrContains(path, ".wav", true) == -1) + { + if (!toClient) + { + EmitGameSoundToAll(path, entity, this.GetFlags(difficulty)); + } + else + { + EmitGameSoundToClient(entity, path, emitter, this.GetFlags(difficulty)); + } + return true; + } + + int pitch = GetRandomInt(this.GetPitchRandomMin(difficulty), this.GetPitchRandomMax(difficulty)); + int volumeCount = RoundToCeil(this.GetVolume(difficulty)); + for (int i = 0; i < volumeCount; i++) + { + float finalVolume = this.GetVolume(difficulty); + if (i + 1 != volumeCount) + { + if (!toClient) + { + EmitSoundToAll(path, entity, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), 1.0, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + else + { + EmitSoundToClient(entity, path, emitter, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), 1.0, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + } + else + { + if (finalVolume > 1.0) + { + while (finalVolume > 1.0) + { + finalVolume -= 1.0; + } + if (!toClient) + { + EmitSoundToAll(path, entity, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), finalVolume, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + else + { + EmitSoundToClient(entity, path, emitter, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), finalVolume, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + } + else + { + if (!toClient) + { + EmitSoundToAll(path, entity, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), this.GetVolume(difficulty), + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + else + { + EmitSoundToClient(entity, path, emitter, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), this.GetVolume(difficulty), + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + } + } + } + return true; + } + + public bool EmitToAllPlayers() + { + float threshold = GetRandomFloat(0.0, 1.0); + bool ret = true; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + bool value = this.EmitSound(true, i, .fixedChance = threshold); + if (!value) + { + ret = false; + } + } + return ret; + } +} + +methodmap ProfileAnimation < ProfileObject // This covers each animation index in each animation section +{ + public void GetAnimationName(int difficulty, char[] buffer, int bufferSize, bool legacy = false, const char[] legacyName = "") + { + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s", legacyName); + this.GetDifficultyString(formatter, difficulty, buffer, bufferSize); + if (!legacy) + { + this.GetDifficultyString("name", difficulty, buffer, bufferSize); + } + } + + public void GetGestureName(int difficulty, char[] buffer, int bufferSize, bool legacy = false) + { + if (legacy) + { + return; + } + this.GetDifficultyString("gesture_name", difficulty, buffer, bufferSize); + } + + public float GetAnimationPlaybackRate(int difficulty, bool legacy = false, const char[] legacyName = "") + { + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s_playbackrate", legacyName); + float val = this.GetDifficultyFloat(formatter, difficulty, 1.0); + if (!legacy) + { + val = this.GetDifficultyFloat("playbackrate", difficulty, val); + } + if (val < -12.0) + { + val = -12.0; + } + else if (val > 12.0) + { + val = 12.0; + } + + return val; + } + + public float GetGesturePlaybackRate(int difficulty, bool legacy = false) + { + if (legacy) + { + return 1.0; + } + float val = this.GetDifficultyFloat("gesture_playbackrate", difficulty, 1.0); + if (val < -12.0) + { + val = -12.0; + } + else if (val > 12.0) + { + val = 12.0; + } + + return val; + } + + public float GetDuration(int difficulty, bool legacy = false) + { + if (legacy) + { + return 0.0; + } + return this.GetDifficultyFloat("duration", difficulty); + } + + public float GetAnimationCycle(int difficulty, bool legacy = false) + { + if (legacy) + { + return 0.0; + } + return this.GetDifficultyFloat("cycle", difficulty); + } + + public float GetGestureCycle(int difficulty, bool legacy = false) + { + if (legacy) + { + return 0.0; + } + return this.GetDifficultyFloat("gesture_cycle", difficulty); + } + + public float GetFootstepInterval(int difficulty, bool legacy = false, const char[] legacyName = "") + { + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s_footstepinterval", legacyName); + float val = this.GetDifficultyFloat(formatter, difficulty, 0.0); + if (!legacy) + { + val = this.GetDifficultyFloat("footstepinterval", difficulty, val); + } + return val; + } + + public bool CanOverrideLoop(int difficulty, bool legacy = false) + { + if (legacy) + { + return false; + } + return this.GetDifficultyBool("override_loop", difficulty); + } + + public bool GetLoopState(int difficulty, bool legacy = false) + { + if (legacy) + { + return false; + } + return this.GetDifficultyBool("loop", difficulty); + } + + public bool ShouldSyncWithGround(int difficulty, bool legacy = false) + { + if (legacy) + { + return false; + } + return this.GetDifficultyBool("ground_sync", difficulty); + } + + public bool PlayAnimation(CBaseAnimating actor, int difficulty, bool loops = false) + { + int sequence; + char animation[64]; + this.GetAnimationName(difficulty, animation, sizeof(animation)); + + sequence = LookupProfileAnimation(actor.index, animation); + + if (sequence == -1) + { + return false; + } + + if (sequence != actor.GetProp(Prop_Send, "m_nSequence")) + { + actor.ResetSequence(sequence); + } + actor.SetPropFloat(Prop_Send, "m_flCycle", this.GetAnimationCycle(difficulty)); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", this.GetAnimationPlaybackRate(difficulty)); + actor.SetProp(Prop_Data, "m_bSequenceLoops", loops); + + return true; + } + + public bool PlayGesture(CBaseAnimatingOverlay actor, int difficulty, bool loops = false, int& layer = -1) + { + int sequence; + char animation[64]; + this.GetGestureName(difficulty, animation, sizeof(animation)); + + sequence = LookupProfileAnimation(actor.index, animation); + + if (sequence == -1) + { + return false; + } + + layer = actor.AddLayeredSequence(sequence, 1); + if (layer == -1) + { + return false; + } + + actor.SetLayerCycle(layer, this.GetGestureCycle(difficulty)); + actor.SetLayerPlaybackRate(layer, this.GetGesturePlaybackRate(difficulty)); + actor.SetLayerLooping(layer, loops); + actor.SetLayerAutokill(layer, true); + + return true; + } +} + +methodmap ProfileMasterAnimations < ProfileObject // This covers the whole "animations" section +{ + property bool IsLegacy + { + public get() + { + char section[64]; + this.GetSectionName(section, sizeof(section)); + return strcmp(section, "animations") != 0; + } + } + + public bool HasAnimationSection(const char[] animType, bool checkLegacy = false) + { + if (this.IsLegacy) + { + if (checkLegacy) + { + if (strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Smell]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Rage]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Spawn]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_FleeInitial]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Heal]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_DeathCam]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Death]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_TauntKill]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_AttackBegin]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_AttackEnd]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_ProjectileShoot]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Despawn]) == 0) + { + return false; + } + } + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s", animType); + return this.ContainsKey(formatter); + } + return this.GetSection(animType) != null; + } + + public ProfileAnimation GetAnimation(const char[] animType, int preDefinedIndex = -1, const char[] preDefinedName = "", int& index = -1) + { + if (this.IsLegacy) + { + if (!this.HasAnimationSection(animType)) + { + return null; + } + ProfileAnimation animObj = view_as(this); + return animObj; + } + + ProfileObject obj = this.GetSection(animType); + if (obj == null || obj.Size <= 0) + { + return null; + } + + ArrayList valids = new ArrayList(ByteCountToCells(64)); + for (int i = 0; i < obj.SectionLength; i++) + { + char key[64]; + obj.GetSectionNameFromIndex(i, key, sizeof(key)); + ProfileAnimation animObj = view_as(obj.GetSection(key)); + if (animObj != null) + { + valids.PushString(key); + } + } + + if (preDefinedIndex > -1) + { + if (preDefinedIndex >= valids.Length) + { + preDefinedIndex = valids.Length - 1; + } + index = preDefinedIndex; + } + else + { + index = GetRandomInt(0, valids.Length - 1); + } + + ProfileAnimation animObj; + if (preDefinedName[0] != '\0') + { + animObj = view_as(obj.GetSection(preDefinedName)); + } + else + { + char animName[64]; + valids.GetString(index, animName, sizeof(animName)); + animObj = view_as(obj.GetSection(animName)); + } + + delete valids; + + if (animObj == null) + { + return null; + } + + return animObj; + } +} + +methodmap ProfileEntityInputObject < ProfileObject +{ + public void GetInput(char[] buffer, int bufferSize) + { + this.GetString("input", buffer, bufferSize); + } + + public void GetParameter(char[] buffer, int bufferSize) + { + this.GetString("parameter", buffer, bufferSize); + } + + public int GetParameterSize() + { + return this.GetKeyValueLength("parameter"); + } + + public bool AcceptInput(int entity, int activator = -1, int caller = -1) + { + char input[64]; + this.GetInput(input, sizeof(input)); + + bool result = false; + + int size = this.GetParameterSize(); + char[] parameter = new char[size]; + this.GetParameter(parameter, size); + + SetVariantString(parameter); + result = AcceptEntityInput(entity, input, activator, caller); + + return result; + } +} + +methodmap ProfileEntityInputsArray < ProfileObject +{ + public void AcceptInputs(int entity, int activator = -1, int caller = -1) + { + for (int i = 0; i < this.SectionLength; i++) + { + char section[64]; + this.GetSectionNameFromIndex(i, section, sizeof(section)); + ProfileEntityInputObject output = view_as(this.GetSection(section)); + if (output == null) + { + continue; + } + + output.AcceptInput(entity, activator, caller); + } + } +} + +methodmap ProfileEntityOutputObject < ProfileObject +{ + public void GetTarget(char[] buffer, int bufferSize) + { + this.GetString("target", buffer, bufferSize); + } + + public int GetTargetSize() + { + return this.GetKeyValueLength("target"); + } + + public void GetInput(char[] buffer, int bufferSize) + { + this.GetString("input", buffer, bufferSize); + } + + public int GetInputSize() + { + return this.GetKeyValueLength("input"); + } + + public void GetParameter(char[] buffer, int bufferSize) + { + this.GetString("parameter", buffer, bufferSize); + } + + public int GetParameterSize() + { + return this.GetKeyValueLength("parameter"); + } + + public float GetDelay() + { + return this.GetFloat("delay"); + } + + public int GetTimesToFire() + { + return this.GetInt("times_to_fire", -1); + } + + public void AddOutput(int entity, const char[] output) + { + int size = this.GetTargetSize(); + char[] target = new char[size]; + this.GetTarget(target, size); + + size = this.GetInputSize(); + char[] input = new char[size]; + this.GetInput(input, size); + + size = this.GetParameterSize(); + char[] parameter = new char[size]; + this.GetParameter(parameter, size); + + size = 98 + strlen(output) + strlen(target) + strlen(input) + strlen(parameter); + char[] addOutput = new char[size]; + FormatEx(addOutput, size, "EntityOutputs.AddOutput(self, `%s`, `%s`, `%s`, `%s`, %0.3f, %d)", output, target, input, parameter, this.GetDelay(), this.GetTimesToFire()); + + PrintToChatAll("%s", addOutput); + + SetVariantString(addOutput); + AcceptEntityInput(entity, "RunScriptCode"); + } +} + +methodmap ProfileEntityOutputsArray < ProfileObject +{ + public void AddOutputs(int entity) + { + for (int i = 0; i < this.SectionLength; i++) + { + char section[64]; + this.GetSectionNameFromIndex(i, section, sizeof(section)); + ProfileObject obj = this.GetSection(section); + if (obj == null) + { + continue; + } + + for (int i2 = 0; i2 < obj.SectionLength; i2++) + { + char subSection[64]; + obj.GetSectionNameFromIndex(i2, subSection, sizeof(subSection)); + ProfileEntityOutputObject output = view_as(obj.GetSection(subSection)); + if (output == null) + { + continue; + } + + output.AddOutput(entity, section); + } + } + } +} + +int ColorToString(int buffer[4], char[] string, int maxlength) +{ + char value[32], num[4]; + for (int i = 0; i < 4; i++) + { + IntToString(buffer[i], num, sizeof(num)); + StrCat(value, sizeof(value), num); + if (i < 3) + { + StrCat(value, sizeof(value), " "); + } + } + return strcopy(string, maxlength, value); +} + +int VectorToString(float buffer[3], char[] string, int maxlength) +{ + char value[64], num[16]; + for (int i = 0; i < 3; i++) + { + FloatToString(buffer[i], num, sizeof(num)); + StrCat(value, sizeof(value), num); + if (i < 2) + { + StrCat(value, sizeof(value), " "); + } + } + return strcopy(string, maxlength, value); +} + +void StringToVector(const char[] string, float buffer[3]) +{ + buffer[0] = 0.0; + buffer[1] = 0.0; + buffer[2] = 0.0; + + char num[3][16]; + int numStrings = ExplodeString(string, " ", num, sizeof(num), sizeof(num[])); + + for (int i = 0; i < numStrings; i++) + { + buffer[i] = StringToFloat(num[i]); + } +} + +void StringToColor(const char[] string, int buffer[4]) +{ + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + + char num[4][16]; + int numStrings = ExplodeString(string, " ", num, sizeof(num), sizeof(num[])); + + for (int i = 0; i < numStrings; i++) + { + buffer[i] = StringToInt(num[i]); + } +} + +bool StringToBool(const char[] string) +{ + return strcmp(string, "1", false) == 0; +} + +int BoolToString(bool value, char[] buffer, int bufferSize) +{ + return FormatEx(buffer, bufferSize, "%b", value); +} + +void RunScriptCode(int entity, int activator, int caller, const char[] format, any ...) +{ + if (!IsValidEntity(entity)) + { + return; + } + + static char buffer[1024]; + VFormat(buffer, sizeof(buffer), format, 5); + + SetVariantString(buffer); + AcceptEntityInput(entity, "RunScriptCode", activator, caller); +} + +void SetupProfileObjectNatives() +{ + CreateNative("SF2_ProfileObject.Parent.get", Native_GetObjectParent); + CreateNative("SF2_ProfileObject.KeyLength.get", Native_GetObjectKeyLength); + CreateNative("SF2_ProfileObject.SectionLength.get", Native_GetObjectSectionLength); + CreateNative("SF2_ProfileObject.GetInt", Native_GetObjectInt); + CreateNative("SF2_ProfileObject.SetInt", Native_SetObjectInt); + CreateNative("SF2_ProfileObject.GetBool", Native_GetObjectBool); + CreateNative("SF2_ProfileObject.SetBool", Native_SetObjectBool); + CreateNative("SF2_ProfileObject.GetFloat", Native_GetObjectFloat); + CreateNative("SF2_ProfileObject.SetFloat", Native_SetObjectFloat); + CreateNative("SF2_ProfileObject.GetSection", Native_GetObjectSection); + CreateNative("SF2_ProfileObject.GetArray", Native_GetObjectArray); + CreateNative("SF2_ProfileObject.GetString", Native_GetObjectString); + CreateNative("SF2_ProfileObject.SetString", Native_SetObjectString); + CreateNative("SF2_ProfileObject.GetVector", Native_GetObjectVector); + CreateNative("SF2_ProfileObject.SetVector", Native_SetObjectVector); + CreateNative("SF2_ProfileObject.GetColor", Native_GetObjectColor); + CreateNative("SF2_ProfileObject.SetColor", Native_SetObjectColor); + CreateNative("SF2_ProfileObject.GetDifficultyInt", Native_GetObjectDifficultyInt); + CreateNative("SF2_ProfileObject.SetDifficultyInt", Native_SetObjectDifficultyInt); + CreateNative("SF2_ProfileObject.GetDifficultyBool", Native_GetObjectDifficultyBool); + CreateNative("SF2_ProfileObject.SetDifficultyBool", Native_SetObjectDifficultyBool); + CreateNative("SF2_ProfileObject.GetDifficultyFloat", Native_GetObjectDifficultyFloat); + CreateNative("SF2_ProfileObject.SetDifficultyFloat", Native_SetObjectDifficultyFloat); + CreateNative("SF2_ProfileObject.GetDifficultySection", Native_GetObjectDifficultySection); + CreateNative("SF2_ProfileObject.GetDifficultyString", Native_GetObjectDifficultyString); + CreateNative("SF2_ProfileObject.SetDifficultyString", Native_SetObjectDifficultyString); + CreateNative("SF2_ProfileObject.GetDifficultyVector", Native_GetObjectDifficultyVector); + CreateNative("SF2_ProfileObject.SetDifficultyVector", Native_SetObjectDifficultyVector); + CreateNative("SF2_ProfileObject.GetDifficultyColor", Native_GetObjectDifficultyColor); + CreateNative("SF2_ProfileObject.SetDifficultyColor", Native_SetObjectDifficultyColor); + CreateNative("SF2_ProfileObject.ConvertValuesSectionToArray", Native_ConvertValuesSectionToArray); + CreateNative("SF2_ProfileObject.ConvertSectionsSectionToArray", Native_ConvertSectionsSectionToArray); + + CreateNative("SF2_ProfileArray.Length.get", Native_GetArrayLength); + CreateNative("SF2_ProfileArray.GetInt", Native_GetArrayInt); + CreateNative("SF2_ProfileArray.GetBool", Native_GetArrayBool); + CreateNative("SF2_ProfileArray.GetFloat", Native_GetArrayFloat); + CreateNative("SF2_ProfileArray.GetSection", Native_GetArraySection); + CreateNative("SF2_ProfileArray.GetString", Native_GetArrayString); + + CreateNative("SF2_ProfileSound.Paths.get", Native_GetSoundPaths); + CreateNative("SF2_ProfileSound.SetDefaultChannel", Native_SetSoundDefaultChannel); + CreateNative("SF2_ProfileSound.SetDefaultLevel", Native_SetSoundDefaultLevel); + CreateNative("SF2_ProfileSound.SetDefaultFlags", Native_SetSoundDefaultFlags); + CreateNative("SF2_ProfileSound.SetDefaultCooldownMin", Native_SetSoundDefaultCooldownMin); + CreateNative("SF2_ProfileSound.SetDefaultCooldownMax", Native_SetSoundDefaultCooldownMax); + CreateNative("SF2_ProfileSound.GetChannel", Native_GetSoundChannel); + CreateNative("SF2_ProfileSound.GetVolume", Native_GetSoundVolume); + CreateNative("SF2_ProfileSound.GetFlags", Native_GetSoundFlags); + CreateNative("SF2_ProfileSound.GetLevel", Native_GetSoundLevel); + CreateNative("SF2_ProfileSound.GetPitch", Native_GetSoundPitch); + CreateNative("SF2_ProfileSound.GetCooldownMin", Native_GetSoundCooldownMin); + CreateNative("SF2_ProfileSound.GetCooldownMax", Native_GetSoundCooldownMax); + CreateNative("SF2_ProfileSound.Precache", Native_SoundPrecache); + CreateNative("SF2_ProfileSound.EmitToAll", Native_SoundEmitToAll); + CreateNative("SF2_ProfileSound.EmitToClient", Native_SoundEmitToClient); + CreateNative("SF2_ProfileSound.StopAllSounds", Native_SoundStopAllSounds); + + CreateNative("SF2_ProfileAnimation.GetAnimationName", Native_AnimationGetAnimationName); + CreateNative("SF2_ProfileAnimation.GetGestureName", Native_AnimationGetGestureName); + CreateNative("SF2_ProfileAnimation.GetAnimationPlaybackRate", Native_AnimationGetAnimationRate); + CreateNative("SF2_ProfileAnimation.GetGesturePlaybackRate", Native_AnimationGetGestureRate); + CreateNative("SF2_ProfileAnimation.GetDuration", Native_AnimationGetDuration); + CreateNative("SF2_ProfileAnimation.GetAnimationCycle", Native_AnimationGetAnimationCycle); + CreateNative("SF2_ProfileAnimation.GetGestureCycle", Native_AnimationGetGestureCycle); + CreateNative("SF2_ProfileAnimation.PlayAnimation", Native_AnimationPlayAnimation); + CreateNative("SF2_ProfileAnimation.PlayGesture", Native_AnimationPlayGesture); + + CreateNative("SF2_ProfileMasterAnimation.HasAnimationSection", Native_MasterAnimationHasSection); + CreateNative("SF2_ProfileMasterAnimation.GetAnimation", Native_MasterAnimationGetAnimation); +} + +static any Native_GetObjectParent(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.Parent; +} + +static any Native_GetObjectKeyLength(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.KeyLength; +} + +static any Native_GetObjectSectionLength(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.SectionLength; +} + +static any Native_GetObjectInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int def = GetNativeCell(3); + + return obj.GetInt(key, def); +} + +static any Native_SetObjectInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int def = GetNativeCell(3); + + obj.SetInt(key, def); + + return 0; +} + +static any Native_GetObjectBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + bool def = GetNativeCell(3); + + return obj.GetBool(key, def); +} + +static any Native_SetObjectBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + bool def = GetNativeCell(3); + + obj.SetBool(key, def); + + return 0; +} + +static any Native_GetObjectFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float def = GetNativeCell(3); + + return obj.GetFloat(key, def); +} + +static any Native_SetObjectFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float def = GetNativeCell(3); + + obj.SetFloat(key, def); + + return 0; +} + +static any Native_GetObjectSection(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + ProfileObject def = GetNativeCell(3); + + return obj.GetSection(key, def); +} + +static any Native_GetObjectArray(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + KeyMap_Array def = GetNativeCell(3); + + return obj.GetArray(key, def); +} + +static any Native_GetObjectString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + int defSize; + GetNativeStringLength(5, defSize); + defSize++; + char[] def = new char[defSize]; + GetNativeString(5, def, defSize); + + obj.GetString(key, buffer, bufferSize, def); + + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_SetObjectString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int valueSize; + GetNativeStringLength(3, valueSize); + valueSize++; + char[] value = new char[valueSize]; + GetNativeString(3, value, valueSize); + + obj.SetString(key, value); + + return 0; +} + +static any Native_GetObjectVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float def[3]; + GetNativeArray(4, def, 3); + float vec[3]; + + obj.GetVector(key, vec, def); + + SetNativeArray(3, vec, 3); + return 0; +} + +static any Native_SetObjectVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float value[3]; + GetNativeArray(3, value, 3); + + obj.SetVector(key, value); + return 0; +} + +static any Native_GetObjectColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int def[4]; + GetNativeArray(4, def, 4); + int vec[4]; + + obj.GetColor(key, vec, def); + + SetNativeArray(3, vec, 4); + return 0; +} + +static any Native_SetObjectColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int value[4]; + GetNativeArray(3, value, 4); + + obj.SetColor(key, value); + return 0; +} + +static any Native_GetObjectDifficultyInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + int def = GetNativeCell(4); + + return obj.GetDifficultyInt(key, difficulty, def); +} + +static any Native_SetObjectDifficultyInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + int def = GetNativeCell(4); + + obj.SetDifficultyInt(key, difficulty, def); + return 0; +} + +static any Native_GetObjectDifficultyBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + bool def = GetNativeCell(4); + + return obj.GetDifficultyBool(key, difficulty, def); +} + +static any Native_SetObjectDifficultyBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + bool def = GetNativeCell(4); + + obj.SetDifficultyBool(key, difficulty, def); + return 0; +} + +static any Native_GetObjectDifficultyFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + float def = GetNativeCell(4); + + return obj.GetDifficultyFloat(key, difficulty, def); +} + +static any Native_SetObjectDifficultyFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + float def = GetNativeCell(4); + + obj.SetDifficultyFloat(key, difficulty, def); + return 0; +} + +static any Native_GetObjectDifficultySection(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + ProfileObject def = GetNativeCell(4); + + return obj.GetDifficultySection(key, difficulty, def); +} + +static any Native_GetObjectDifficultyString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int bufferSize = GetNativeCell(5); + char[] buffer = new char[bufferSize]; + + int defSize; + GetNativeStringLength(6, defSize); + defSize++; + char[] def = new char[defSize]; + GetNativeString(6, def, defSize); + + obj.GetDifficultyString(key, difficulty, buffer, bufferSize, def); + SetNativeString(4, buffer, bufferSize); + return 0; +} + +static any Native_SetObjectDifficultyString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int valueSize; + GetNativeStringLength(4, valueSize); + valueSize++; + char[] value = new char[valueSize]; + GetNativeString(4, value, valueSize); + + obj.SetDifficultyString(key, difficulty, value); + return 0; +} + +static any Native_GetObjectDifficultyVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + float buffer[3]; + + float def[3]; + GetNativeArray(5, def, 3); + + obj.GetDifficultyVector(key, difficulty, buffer, def); + SetNativeArray(4, buffer, 3); + return 0; +} + +static any Native_SetObjectDifficultyVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + float value[3]; + GetNativeArray(4, value, 3); + + obj.SetDifficultyVector(key, difficulty, value); + return 0; +} + +static any Native_GetObjectDifficultyColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int buffer[4]; + + int def[4]; + GetNativeArray(5, def, 4); + + obj.GetDifficultyColor(key, difficulty, buffer, def); + SetNativeArray(4, buffer, 4); + return 0; +} + +static any Native_SetObjectDifficultyColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int value[4]; + GetNativeArray(4, value, 4); + + obj.SetDifficultyColor(key, difficulty, value); + return 0; +} + +static any Native_ConvertValuesSectionToArray(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + obj.ConvertValuesSectionToArray(key); + return 0; +} + +static any Native_ConvertSectionsSectionToArray(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + obj.ConvertSectionsSectionToArray(key); + return 0; +} + +static any Native_GetArrayLength(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.Length; +} + +static any Native_GetArrayInt(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetInt(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArrayBool(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetBool(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArrayFloat(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetFloat(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArraySection(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetSection(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArrayString(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + int index = GetNativeCell(2); + + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + int defaultValueSize; + GetNativeStringLength(5, defaultValueSize); + defaultValueSize++; + char[] defaultValue = new char[defaultValueSize]; + GetNativeString(5, defaultValue, defaultValueSize); + + obj.GetString(index, buffer, bufferSize, defaultValue); + + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_GetSoundPaths(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.Paths; +} + +static any Native_SetSoundDefaultChannel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int channel = GetNativeCell(2); + + obj.SetDefaultChannel(channel); + return 0; +} + +static any Native_SetSoundDefaultLevel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int level = GetNativeCell(2); + + obj.SetDefaultLevel(level); + return 0; +} + +static any Native_SetSoundDefaultFlags(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int flags = GetNativeCell(2); + + obj.SetDefaultFlags(flags); + return 0; +} + +static any Native_SetSoundDefaultCooldownMin(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + float cooldown = GetNativeCell(2); + + obj.SetDefaultCooldownMin(cooldown); + return 0; +} + +static any Native_SetSoundDefaultCooldownMax(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + float cooldown = GetNativeCell(2); + + obj.SetDefaultCooldownMax(cooldown); + return 0; +} + +static any Native_GetSoundChannel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetChannel(GetNativeCell(2)); +} + +static any Native_GetSoundVolume(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetVolume(GetNativeCell(2)); +} + +static any Native_GetSoundFlags(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetFlags(GetNativeCell(2)); +} + +static any Native_GetSoundLevel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetLevel(GetNativeCell(2)); +} + +static any Native_GetSoundPitch(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetPitch(GetNativeCell(2)); +} + +static any Native_GetSoundCooldownMin(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetCooldownMin(GetNativeCell(2)); +} + +static any Native_GetSoundCooldownMax(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetCooldownMax(GetNativeCell(2)); +} + +static any Native_SoundPrecache(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.Precache(); + return 0; +} + +static any Native_SoundEmitToAll(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.EmitSound(false, GetNativeCell(2), .difficulty = GetNativeCell(3)); + return 0; +} + +static any Native_SoundEmitToClient(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.EmitSound(true, GetNativeCell(2), GetNativeCell(3), .difficulty = GetNativeCell(4)); + return 0; +} + +static any Native_SoundStopAllSounds(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.StopAllSounds(.entity = GetNativeCell(2)); + return 0; +} + +static any Native_AnimationGetAnimationName(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int difficulty = GetNativeCell(2); + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + obj.GetAnimationName(difficulty, buffer, bufferSize); + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_AnimationGetGestureName(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int difficulty = GetNativeCell(2); + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + obj.GetGestureName(difficulty, buffer, bufferSize); + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_AnimationGetAnimationRate(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetAnimationPlaybackRate(GetNativeCell(2)); +} + +static any Native_AnimationGetGestureRate(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetGesturePlaybackRate(GetNativeCell(2)); +} + +static any Native_AnimationGetDuration(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetDuration(GetNativeCell(2)); +} + +static any Native_AnimationGetAnimationCycle(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetAnimationCycle(GetNativeCell(2)); +} + +static any Native_AnimationGetGestureCycle(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetGestureCycle(GetNativeCell(2)); +} + +static any Native_AnimationPlayAnimation(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + CBaseAnimating entity = CBaseAnimating(GetNativeCell(2)); + int difficulty = GetNativeCell(3); + + char name[64]; + obj.GetAnimationName(difficulty, name, sizeof(name)); + int sequence = LookupProfileAnimation(entity.index, name); + if (sequence == -1) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid animation name %s", name); + } + + float rate = obj.GetAnimationPlaybackRate(difficulty); + float cycle = obj.GetAnimationCycle(difficulty); + + entity.ResetSequence(sequence); + entity.SetPropFloat(Prop_Send, "m_flCycle", cycle); + entity.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + entity.SetProp(Prop_Data, "m_bSequenceLoops", GetNativeCell(4)); + + return 0; +} + +static any Native_AnimationPlayGesture(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + CBaseAnimatingOverlay entity = CBaseAnimatingOverlay(GetNativeCell(2)); + int difficulty = GetNativeCell(3); + bool loop = GetNativeCell(4); + + char name[64]; + obj.GetGestureName(difficulty, name, sizeof(name)); + int sequence = LookupProfileAnimation(entity.index, name); + if (sequence == -1) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid gesture name %s", name); + } + + float rate = obj.GetGesturePlaybackRate(difficulty); + float cycle = obj.GetGestureCycle(difficulty); + float duration = entity.SequenceDuration(sequence); + int layer = entity.AddLayeredSequence(sequence, 1); + entity.SetLayerDuration(layer, duration); + entity.SetLayerPlaybackRate(layer, rate); + entity.SetLayerCycle(layer, cycle); + entity.SetLayerLooping(layer, loop); + entity.SetLayerAutokill(layer, true); + SetNativeCellRef(5, layer); + return 0; +} + +static any Native_MasterAnimationHasSection(Handle plugin, int numParams) +{ + ProfileMasterAnimations obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + return obj.HasAnimationSection(key); +} + +static any Native_MasterAnimationGetAnimation(Handle plugin, int numParams) +{ + ProfileMasterAnimations obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize, nameSize; + GetNativeStringLength(2, keySize); + GetNativeStringLength(4, nameSize); + keySize++; + nameSize++; + char[] key = new char[keySize]; + char[] name = new char[nameSize]; + GetNativeString(2, key, keySize); + GetNativeString(4, name, nameSize); + int index = GetNativeCell(5); + + ProfileAnimation animation = obj.GetAnimation(key, GetNativeCell(3), name, index); + SetNativeCellRef(5, index); + return animation; +} diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp index 73df0d24..9dfcb168 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp @@ -8,7 +8,7 @@ StringMap g_ChaserBossProfileData; -#include "sf2/profiles/profile_chaser_precache.sp" +#include "profile_chaser_precache.sp" void InitializeChaserProfiles() { @@ -17,15 +17,12 @@ void InitializeChaserProfiles() void UnloadChaserBossProfile(const char[] profile) { - char tempProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - if (!g_ChaserBossProfileData.GetString(profile, tempProfile, sizeof(tempProfile)) || tempProfile[0] == '\0') + SF2ChaserBossProfileData chaserProfileData; + if (!g_ChaserBossProfileData.GetArray(profile, chaserProfileData, sizeof(chaserProfileData))) { return; } - SF2ChaserBossProfileData chaserProfileData; - g_ChaserBossProfileData.GetArray(profile, chaserProfileData, sizeof(chaserProfileData)); - chaserProfileData.Destroy(); g_ChaserBossProfileData.Remove(profile); @@ -33,2384 +30,16 @@ void UnloadChaserBossProfile(const char[] profile) static SF2ChaserBossProfileData g_CachedProfileData; -bool GetChaserProfileEndlessChasingState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasesEndlessly; -} - -bool GetChaserProfileAutoChaseState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseEnabled; -} - -bool GetChaserProfileAutoChaseSprinterState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseSprinters; -} - -int GetChaserProfileAutoChaseCount(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseCount[difficulty]; -} - -int GetChaserProfileAutoChaseAddGeneral(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAdd[difficulty]; -} - -int GetChaserProfileAutoChaseAddFootstep(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddFootstep[difficulty]; -} - -int GetChaserProfileAutoChaseAddLoudFootstep(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddLoudFootstep[difficulty]; -} - -int GetChaserProfileAutoChaseAddVoice(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddVoice[difficulty]; -} - -int GetChaserProfileAutoChaseAddWeapon(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddWeapon[difficulty]; -} - -float GetChaserProfileWalkSpeed(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WalkSpeed[difficulty]; -} - -float GetChaserProfileMaxWalkSpeed(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MaxWalkSpeed[difficulty]; -} - -float GetChaserProfileAlertGracetime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AlertGracetime[difficulty]; -} - -float GetChaserProfileAlertDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AlertDuration[difficulty]; -} - -float GetChaserProfileChaseDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDuration[difficulty]; -} - -float GetChaserProfileChaseDurationAddVisibleMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibleMin; -} - -float GetChaserProfileChaseDurationAddVisibleMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibleMax; -} - -float GetChaserProfileSoundPosDiscardTime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SoundPosDiscardTime[difficulty]; -} - -float GetChaserProfileSoundPosDistanceTolerance(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SoundPosDistanceTolerance[difficulty]; -} - -float GetChaserProfileChasePersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasePersistencyTimeInit; -} - -float GetChaserProfileChaseAttackPersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseAttackPersistencyTimeInit; -} - -float GetChaserProfileChaseAttackPersistencyTimeAdd(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseAttackPersistencyTimeAdd; -} - -float GetChaserProfileChaseNewTargetPersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseNewTargetPersistencyTimeInit; -} - -float GetChaserProfileChaseNewTargetPersistencyTimeAdd(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseNewTargetPersistencyTimeAdd; -} - -float GetChaserProfileChasePersistencyAddVisibleMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasePersistencyAddVisibleMin; -} - -float GetChaserProfileChasePersistencyAddVisibleMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasePersistencyAddVisibleMax; -} - -float GetChaserProfileChaseStunPersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseStunPersistencyTimeInit; -} - -float GetChaserProfileChaseStunPersistencyTimeAdd(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseStunPersistencyTimeAdd; -} - -float GetChaserProfileHearFootstepCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FootstepSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearLoudFootstepCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.LoudFootstepSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearQuietFootstepCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.QuietFootstepSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearVoiceCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.VoiceSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearWeaponCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WeaponSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearFlashlightCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightSenses.Cooldown[difficulty]; -} - -int GetChaserProfileFootstepAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FootstepSenses.AddCount[difficulty]; -} - -int GetChaserProfileLoudFootstepAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.LoudFootstepSenses.AddCount[difficulty]; -} - -int GetChaserProfileQuietFootstepAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.QuietFootstepSenses.AddCount[difficulty]; -} - -int GetChaserProfileVoiceAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.VoiceSenses.AddCount[difficulty]; -} - -int GetChaserProfileWeaponAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WeaponSenses.AddCount[difficulty]; -} - -int GetChaserProfileFlashlightAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightSenses.AddCount[difficulty]; -} - -float GetChaserProfileWanderRangeMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderRangeMin[difficulty]; -} - -float GetChaserProfileWanderRangeMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderRangeMax[difficulty]; -} - -float GetChaserProfileWanderTimeMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderTimeMin[difficulty]; -} - -float GetChaserProfileWanderTimeMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderTimeMax[difficulty]; -} - -float GetChaserProfileWanderEnterTimeMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderEnterTimeMin[difficulty]; -} - -float GetChaserProfileWanderEnterTimeMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderEnterTimeMax[difficulty]; -} - -float GetChaserProfileProjectileCooldownMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileCooldownMin[difficulty]; -} - -float GetChaserProfileProjectileCooldownMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileCooldownMax[difficulty]; -} - -float GetChaserProfileIceballSlowdownDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IceballSlowDuration[difficulty]; -} - -float GetChaserProfileIceballSlowdownPercent(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IceballSlowPercent[difficulty]; -} - -float GetChaserProfileProjectileSpeed(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileSpeed[difficulty]; -} - -float GetChaserProfileProjectileDamage(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileDamage[difficulty]; -} - -float GetChaserProfileProjectileRadius(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileRadius[difficulty]; -} - -float GetChaserProfileProjectileDeviation(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileDeviation[difficulty]; -} - -int GetChaserProfileProjectileCount(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileCount[difficulty]; -} - -float GetChaserProfileRandomEffectDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectDuration[difficulty]; -} - -float GetChaserProfileRandomEffectSlowdown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectSlowdown[difficulty]; -} - -float GetChaserProfileJaratePlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateDuration[difficulty]; -} - -float GetChaserProfileMilkPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkDuration[difficulty]; -} - -float GetChaserProfileGasPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasDuration[difficulty]; -} - -float GetChaserProfileMarkPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MarkDuration[difficulty]; -} - -float GetChaserProfileSilentMarkPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentMarkDuration[difficulty]; -} - -float GetChaserProfileIgnitePlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteDuration[difficulty]; -} - -float GetChaserProfileIgnitePlayerDelay(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteDelay[difficulty]; -} - -float GetChaserProfileStunPlayerDuration(const char[] profile, int difficulty) +float GetChaserProfileDeathHealth(const char[] profile, int difficulty) { g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectDuration[difficulty]; + return g_CachedProfileData.DeathData.Health[difficulty]; } -float GetChaserProfileStunPlayerSlowdown(const char[] profile, int difficulty) +bool GetChaserProfileBoxingState(const char[] profile) { g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectSlowdown[difficulty]; -} - -float GetChaserProfileBleedPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BleedDuration[difficulty]; -} - -float GetChaserProfileEletricPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricDuration[difficulty]; -} - -float GetChaserProfileEletricPlayerSlowdown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricSlowdown[difficulty]; -} - -float GetChaserProfileWakeRadius(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WakeRadius; -} - -int GetChaserProfileAttackCount(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Attacks.Length; -} - -ArrayList GetChaserProfileAttackPropModels(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AttackPropModels; -} - -int GetChaserProfileRandomAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectIndexes; -} - -int GetChaserProfileRandomAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RandomEffectIndexesString); -} - -int GetChaserProfileRandomStunType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectStunType; -} - -int GetChaserProfileJarateAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateIndexes; -} - -int GetChaserProfileJarateAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.JarateIndexesString); -} - -int GetChaserProfileMilkAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkIndexes; -} - -int GetChaserProfileMilkAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MilkIndexesString); -} - -int GetChaserProfileGasAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasIndexes; -} - -int GetChaserProfileGasAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GasIndexesString); -} - -int GetChaserProfileMarkAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MarkIndexes; -} - -int GetChaserProfileMarkAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MarkIndexesString); -} - -int GetChaserProfileSilentMarkAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentMarkIndexes; -} - -int GetChaserProfileSilentMarkAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SilentMarkIndexesString); -} - -int GetChaserProfileIgniteAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteIndexes; -} - -int GetChaserProfileIgniteAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.IgniteIndexesString); -} - -int GetChaserProfileStunAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectIndexes; -} - -int GetChaserProfileStunAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.StunEffectIndexesString); -} - -int GetChaserProfileStunDamageType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectType; -} - -int GetChaserProfileBleedAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BleedIndexes; -} - -int GetChaserProfileBleedAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.BleedIndexesString); -} - -int GetChaserProfileEletricAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricIndexes; -} - -int GetChaserProfileEletricAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ElectricIndexesString); -} - -int GetChaserProfileSmiteAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteIndexes; -} - -int GetChaserProfileSmiteAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SmiteIndexesString); -} - -int GetChaserProfileSmiteDamageType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteDamageType; -} - -void GetChaserProfileSmiteColor(const char[] profile, int buffer[4]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.SmiteColor; -} - -float GetChaserProfileSmiteDamage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteDamage; -} - -int GetChaserProfileSmiteHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SmiteHitSound); -} - -int GetChaserProfileAttackType(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::Type); -} - -void GetChaserProfileAttackPunchVelocity(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.PunchVelocity; -} - -bool GetChaserProfileAttackDisappear(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Disappear[difficulty]; -} - -int GetChaserProfileAttackRepeat(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::Repeat); -} - -int GetChaserProfileMaxAttackRepeats(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::MaxRepeats); -} - -ArrayList GetChaserProfileAttackRepeatTimers(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return null; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RepeatTimers; -} - -bool GetChaserProfileAttackIgnoreAlwaysLooking(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.IgnoreAlwaysLooking[difficulty]; -} - -int GetChaserProfileAttackWeaponTypeInt(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::WeaponInt); -} - -int GetChaserProfileAttackWeaponTypeString(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.WeaponString); -} - -float GetChaserProfileAttackDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Damage[difficulty]; -} - -float GetChaserProfileAttackLifeStealDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LifeStealDuration[difficulty]; -} - -float GetChaserProfileAttackProjectileDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileDamage[difficulty]; -} - -float GetChaserProfileAttackProjectileSpeed(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileSpeed[difficulty]; -} - -float GetChaserProfileAttackProjectileRadius(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileRadius[difficulty]; -} - -float GetChaserProfileAttackProjectileDeviation(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileDeviation[difficulty]; -} - -int GetChaserProfileAttackProjectileCount(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileCount[difficulty]; -} - -bool GetChaserProfileAttackCritProjectiles(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.CritProjectiles[difficulty]; -} - -int GetChaserProfileAttackProjectileType(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::ProjectileType); -} - -void GetChaserProfileAttackProjectileOffset(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.ProjectileOffset; -} - -float GetChaserProfileAttackProjectileIceSlowPercent(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.IceballSlowdownPercent[difficulty]; -} - -float GetChaserProfileAttackProjectileIceSlowDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.IceballSlowdownDuration[difficulty]; -} - -int GetChaserProfileAttackFireballTrail(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.FireballTrail); -} - -int GetChaserProfileAttackIceballTrail(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.IceballTrail); -} - -int GetChaserProfileAttackRocketModel(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.RocketModel); -} - -int GetChaserProfileAttackBulletCount(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BulletCount[difficulty]; -} - -float GetChaserProfileAttackBulletDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BulletDamage[difficulty]; -} - -float GetChaserProfileAttackBulletSpread(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BulletSpread[difficulty]; -} - -int GetChaserProfileAttackBulletTrace(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.BulletTrace); -} - -void GetChaserProfileAttackBulletOffset(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.BulletOffset; -} - -float GetChaserProfileAttackLaserDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LaserDamage[difficulty]; -} - -float GetChaserProfileAttackLaserSize(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::LaserSize); -} - -void GetChaserProfileAttackLaserColor(const char[] profile, int attackIndex, int buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.LaserColor; -} - -bool GetChaserProfileEnableLaserAttachment(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::LaserAttachment); -} - -float GetChaserProfileAttackLaserDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LaserDuration[difficulty]; -} - -float GetChaserProfileAttackLaserNoise(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::LaserNoise); -} - -int GetChaserProfileAttackLaserAttachmentName(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.LaserAttachmentName); -} - -void GetChaserProfileAttackLaserOffset(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.LaserOffset; -} - -bool GetChaserProfileAttackPullIn(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::PullIn); -} - -float GetChaserProfileAttackDamageVsProps(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::DamageVsProps); -} - -float GetChaserProfileAttackDamageForce(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DamageForce[difficulty]; -} - -int GetChaserProfileAttackDamageType(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DamageType[difficulty]; -} - -float GetChaserProfileAttackDamageDelay(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DamageDelay[difficulty]; -} - -float GetChaserProfileAttackRange(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Range[difficulty]; -} - -float GetChaserProfileAttackDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Duration[difficulty]; -} - -float GetChaserProfileAttackSpread(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Spread[difficulty]; -} - -float GetChaserProfileAttackBeginRange(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BeginRange[difficulty]; -} - -float GetChaserProfileAttackBeginFOV(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BeginFOV[difficulty]; -} - -float GetChaserProfileAttackCooldown(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Cooldown[difficulty]; -} - -bool GetChaserProfileAttackLifeStealState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LifeSteal[difficulty]; -} - -bool GetChaserProfileAttackRunWhileAttackingState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.AttackWhileRunning[difficulty]; -} - -float GetChaserProfileAttackRunSpeed(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RunSpeed[difficulty]; -} - -float GetChaserProfileAttackRunDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RunDuration[difficulty]; -} - -float GetChaserProfileAttackRunDelay(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RunDelay[difficulty]; -} - -int GetChaserProfileAttackUseOnDifficulty(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::UseOnDifficulty); -} - -int GetChaserProfileAttackBlockOnDifficulty(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::BlockOnDifficulty); -} - -int GetChaserProfileAttackExplosiveDanceRadius(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ExplosiveDanceRadius[difficulty]; -} - -bool GetChaserProfileAttackGesturesState(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::Gestures); -} - -bool GetChaserProfileAttackDeathCamLowHealth(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::DeathCamLowHealth); -} - -bool GetChaserProfileAttackChaseInitialInterruptState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DontInterruptChaseInitial[difficulty]; -} - -float GetChaserProfileAttackUseOnHealth(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::UseOnHealth); -} - -float GetChaserProfileAttackBlockOnHealth(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::BlockOnHealth); -} - -bool GetChaserProfileAttackCancelLOSState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.CancelLos[difficulty]; -} - -float GetChaserProfileAttackCancelDistance(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.CancelDistance[difficulty]; -} - -bool GetChaserProfileEnableAdvancedDamageEffects(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AdvancedDamageEffects; -} - -bool GetChaserProfileEnableAdvancedDamageEffectsRandom(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomAdvancedDamageEffects; -} - -bool GetChaserProfileEnableAdvancedDamageParticles(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AdvancedDamageEffectParticles; -} - -bool GetChaserProfileJarateState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateEffects; -} - -bool GetChaserProfileJarateBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateBeamParticle; -} - -int GetChaserProfileJarateParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.JarateParticle); -} - -int GetChaserProfileJarateHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.JarateHitSound); -} - -bool GetChaserProfileMilkState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkEffects; -} - -bool GetChaserProfileMilkBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkBeamParaticle; -} - -int GetChaserProfileMilkParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MilkParticle); -} - -int GetChaserProfileMilkHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MilkHitSound); -} - -bool GetChaserProfileGasState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasEffects; -} - -bool GetChaserProfileGasBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasBeamParticle; -} - -int GetChaserProfileGasParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GasParticle); -} - -int GetChaserProfileGasHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GasHitSound); -} - -bool GetChaserProfileMarkState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MarkEffects; -} - -bool GetChaserProfileSilentMarkState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentMarkEffects; -} - -bool GetChaserProfileIgniteState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteEffects; -} - -bool GetChaserProfileStunAttackState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffects; -} - -bool GetChaserProfileStunAttackBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectBeamParticle; -} - -int GetChaserProfileStunParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.StunParticle); -} - -int GetChaserProfileStunHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.StunHitSound); -} - -bool GetChaserProfileBleedState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BleedEffects; -} - -bool GetChaserProfileEletricAttackState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricEffects; -} - -bool GetChaserProfileEletricBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricBeamParticle; -} - -int GetChaserProfileElectricRedParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ElectricParticleRed); -} - -int GetChaserProfileElectricBlueParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ElectricParticleBlue); -} - -bool GetChaserProfileSmiteState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteEffects; -} - -bool GetChaserProfileSmiteMessage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteMessage; -} - -bool GetChaserProfileXenobladeCombo(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeCombo; -} - -float GetChaserProfileXenobladeBreakDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeDuration; -} - -float GetChaserProfileXenobladeToppleDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeToppleDuration; -} - -float GetChaserProfileXenobladeToppleSlowdown(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeToppleSlowdown; -} - -float GetChaserProfileXenobladeDazeDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeDazeDuration; -} - -bool GetChaserProfileCloakState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CanCloak; -} - -bool GetChaserProfileProjectileState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectilesEnabled; -} - -bool GetChaserProfileCriticalRockets(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CriticalProjectiles; -} - -int GetChaserProfileFireballShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.FireballShootSound); -} - -int GetChaserProfileFireballTrail(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.FireballTrail); -} - -int GetChaserProfileFireballExplodeSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.FireballExplodeSound); -} - -int GetChaserProfileIceballSlowSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.IceballSlowSound); -} - -int GetChaserProfileIceballTrail(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.IceballTrail); -} - -int GetChaserProfileRocketShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketShootSound); -} - -int GetChaserProfileRocketModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketModel); -} - -int GetChaserProfileRocketTrail(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketTrail); -} - -int GetChaserProfileRocketExplodeParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketExplodeParticle); -} - -int GetChaserProfileRocketExplodeSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketExplodeSound); -} - -int GetChaserProfileSentryRocketShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SentryRocketShootSound); -} - -int GetChaserProfileGrenadeShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GrenadeShootSound); -} - -int GetChaserProfileArrowShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ArrowShootSound); -} - -int GetChaserProfileManglerShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ManglerShootSound); -} - -int GetChaserProfileBaseballShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.BaseballShootSound); -} - -bool GetChaserProfileGestureShoot(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShootGestures; -} - -int GetChaserProfileShootGestureName(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShootGestureName); -} - -bool GetChaserProfileShootAnimationsState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShootAnimations; -} - -bool GetChaserProfileProjectileAmmoState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileClips; -} - -bool GetChaserProfileChargeUpProjectilesState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChargeUpProjectiles; -} - -int GetChaserProfileRandomProjectilePosMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileRandomPosMin; -} - -int GetChaserProfileRandomProjectilePosMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileRandomPosMax; -} - -int GetChaserProfileBaseballModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.BaseballModel); -} - -ArrayList GetChaserProfileProjectilePositionsArray(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectilePosOffsets; -} - -int GetChaserProfileProjectileType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileType; -} - -int GetChaserProfileProjectileLoadedAmmo(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileClipSize[difficulty]; -} - -float GetChaserProfileProjectileAmmoReloadTime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileReloadTime[difficulty]; -} - -float GetChaserProfileProjectileChargeUpTime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileChargeUp[difficulty]; -} - -bool GetChaserProfileDamageParticleState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DamageParticles; -} - -int GetChaserProfileDamageParticleName(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DamageParticleName); -} - -bool GetChaserProfileDamageParticleBeamState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DamageParticleBeam; -} - -int GetChaserProfileDamageParticleSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DamageParticleSound); -} - -bool GetChaserProfileStunState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEnabled; -} - -float GetChaserProfileStunCooldown(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunCooldown; -} - -bool GetChaserProfileStunFlashlightState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightStun; -} - -float GetChaserProfileStunFlashlightDamage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightDamage; -} - -bool GetChaserProfileStunOnChaseInitial(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseInitialOnStun; -} - -ArrayList GetChaserProfileDamageResistances(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DamageResistances; -} - -float GetChaserProfileStunHealth(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunHealth; -} - -float GetChaserProfileStunHealthPerPlayer(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunHealthPerPlayer; -} - -float GetChaserProfileStunHealthPerClass(const char[] profile, int index) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunHealthPerClass[index]; -} - -bool GetChaserProfileKeyDrop(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.KeyDrop; -} - -int GetChaserProfileKeyModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.KeyModel); -} - -int GetChaserProfileKeyTrigger(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.KeyTrigger); -} - -bool GetChaserProfileDisappearOnStun(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DisappearOnStun; -} - -bool GetChaserProfileStunItemDropState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ItemDropOnStun; -} - -int GetChaserProfileStunItemDropType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunItemDropType; -} - -bool GetChaserProfileBoxingState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BoxingBoss; -} - -bool GetChaserProfileNormalSoundHook(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.NormalSoundHook; -} - -bool GetChaserProfileChaseOnLook(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseOnLook; -} - -bool GetChaserProfileEarthquakeFootstepState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootsteps; -} - -float GetChaserProfileEarthquakeFootstepAmplitude(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepAmplitude; -} - -float GetChaserProfileEarthquakeFootstepFrequency(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepFrequency; -} - -float GetChaserProfileEarthquakeFootstepDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepDuration; -} - -float GetChaserProfileEarthquakeFootstepRadius(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepRadius; -} - -bool GetChaserProfileEarthquakeFootstepAirShake(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepAirShake; -} - -int GetChaserProfileSoundCountToAlert(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SoundCountToAlert[difficulty]; -} - -float GetChaserProfileCloakCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakCooldown[difficulty]; -} - -float GetChaserProfileCloakRange(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakRange[difficulty]; -} - -float GetChaserProfileDecloakRange(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DecloakRange[difficulty]; -} - -float GetChaserProfileCloakDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakDuration[difficulty]; -} - -float GetChaserProfileCloakSpeedMultiplier(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakSpeedMultiplier[difficulty]; -} - -bool GetChaserProfileCloakToHeal(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakToHeal; -} - -int GetChaserProfileCloakParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.CloakParticle); -} - -void GetChaserProfileCloakRenderColor(const char[] profile, int buffer[4]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.CloakRenderColor; -} - -int GetChaserProfileCloakRenderMode(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakRenderMode; -} - -int GetChaserProfileCloakOnSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.CloakOnSound); -} - -int GetChaserProfileCloakOffSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.CloakOffSound); -} - -bool GetChaserProfileChaseInitialAnimationState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseInitialAnimations; -} - -bool GetChaserProfileSpawnAnimationState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SpawnAnimationsEnabled; -} - -bool GetChaserProfileOldAnimState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OldAnimationAI; -} - -bool GetChaserProfileAlertWalkingState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AlertWalkingAnimation; -} - -bool GetChaserProfileUnnerfedVisibility(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.UnnerfedVisibility; -} - -bool GetChaserProfileClearLayersState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ClearLayersOnAnimUpdate; -} - -float GetChaserProfileHealAnimTimer(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealAnimationTimer; -} - -float GetChaserProfileHealFunctionTimer(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealFunctionTimer; -} - -float GetChaserProfileHealRangeMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealRangeMin; -} - -float GetChaserProfileHealRangeMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealRangeMax; -} - -float GetChaserProfileHealTimeMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealTimeMin; -} - -float GetChaserProfileHealTimeMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealTimeMax; -} - -float GetChaserProfileAfterburnMultiplier(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AfterburnMultiplier; -} - -float GetChaserProfileBackstabDamageScale(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BackstabDamageScale; -} - -bool GetChaserProfileShockwaveState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Shockwaves; -} - -float GetChaserProfileShockwaveHeight(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveHeight[difficulty]; -} - -float GetChaserProfileShockwaveRange(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveRange[difficulty]; -} - -float GetChaserProfileShockwaveDrain(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveDrain[difficulty]; -} - -float GetChaserProfileShockwaveForce(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveForce[difficulty]; -} - -bool GetChaserProfileShockwaveStunState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveStun; -} - -float GetChaserProfileShockwaveStunDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveStunDuration[difficulty]; -} - -float GetChaserProfileShockwaveStunSlowdown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveStunSlowdown[difficulty]; -} - -int GetChaserProfileShockwaveAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveIndexes; -} - -int GetChaserProfileShockwaveAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShockwaveIndexesString); -} - -float GetChaserProfileShockwaveWidth1(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveWidths[0]; -} - -float GetChaserProfileShockwaveWidth2(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveWidths[1]; -} - -float GetChaserProfileShockwaveAmplitude(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveAmplitude; -} - -void GetChaserProfileShockwaveColor1(const char[] profile, int buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.ShockwaveColor1; -} - -void GetChaserProfileShockwaveColor2(const char[] profile, int buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.ShockwaveColor2; -} - -int GetChaserProfileShockwaveAlpha1(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveAlpha1; -} - -int GetChaserProfileShockwaveAlpha2(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveAlpha2; -} - -int GetChaserProfileShockwaveBeamSprite(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShockwaveBeamSprite); -} - -int GetChaserProfileShockwaveHaloSprite(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShockwaveHaloSprite); -} - -int GetChaserProfileShockwaveBeamModel(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveBeamModel; -} - -int GetChaserProfileShockwaveHaloModel(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveHaloModel; -} - -bool GetChaserProfileTrapState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Traps; -} - -int GetChaserProfileTrapType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TrapType; -} - -float GetChaserProfileTrapSpawnCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TrapCooldown[difficulty]; -} - -int GetChaserProfileTrapModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapModel); -} - -int GetChaserProfileTrapDeploySound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapDeploySound); -} - -int GetChaserProfileTrapMissSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapMissSound); -} - -int GetChaserProfileTrapCatchSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapCatchSound); -} - -int GetChaserProfileTrapAnimIdle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapAnimIdle); -} - -int GetChaserProfileTrapAnimClose(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapAnimClose); -} - -int GetChaserProfileTrapAnimOpen(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapAnimOpen); -} - -bool GetChaserProfileCrawlState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Crawling; -} - -float GetChaserProfileCrawlSpeedMultiplier(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CrawlSpeedMultiplier[difficulty]; -} - -void GetChaserProfileCrawlMins(const char[] profile, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.CrawlDetectMins; -} - -void GetChaserProfileCrawlMaxs(const char[] profile, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.CrawlDetectMaxs; -} - -bool GetChaserProfileSelfHealState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHeal; -} - -float GetChaserProfileSelfHealStartPercentage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealPercentageStart; -} - -float GetChaserProfileSelfHealPercentageOne(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealRecover[0]; -} - -float GetChaserProfileSelfHealPercentageTwo(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealRecover[1]; -} - -float GetChaserProfileSelfHealPercentageThree(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealRecover[2]; -} - -void GetChaserProfileIdleSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.IdleSounds; -} - -void GetChaserProfileAlertSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AlertSounds; -} - -void GetChaserProfileChasingSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.ChasingSounds; -} - -void GetChaserProfileChaseInitialSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.ChaseInitialSounds; -} - -void GetChaserProfileStunSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.StunnedSounds; -} - -void GetChaserProfileAttackKilledSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AttackKilledSounds; -} - -void GetChaserProfileAttackKilledAllSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AttackKilledAllSounds; -} - -void GetChaserProfileAttackKilledClientSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AttackKilledClientSounds; -} - -void GetChaserProfileRageAllSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.RageAllSounds; -} - -void GetChaserProfileRageTwoSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.RageTwoSounds; -} - -void GetChaserProfileRageThreeSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.RageThreeSounds; -} - -void GetChaserProfileSelfHealSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.SelfHealSounds; -} - -void GetChaserProfileFootstepSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.FootstepSounds; -} - -ArrayList GetChaserProfileAttackSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AttackSounds; -} - -ArrayList GetChaserProfileHitSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HitSounds; -} - -ArrayList GetChaserProfileMissSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MissSounds; -} - -ArrayList GetChaserProfileBulletShootSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BulletShootSounds; -} - -ArrayList GetChaserProfileProjectileShootSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileShootSounds; + return g_CachedProfileData.BoxingBoss; } void GetChaserProfileChaseMusics(const char[] profile, SF2BossProfileSoundInfo params) diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index 048efa6d..5c7b17fe 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -10,22 +10,16 @@ * Parses and stores the unique values of a chaser profile from the current position in the profiles config. * Returns true if loading was successful, false if not. */ -bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen) +bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen, SF2BossProfileData baseData) { strcopy(loadFailReasonBuffer, loadFailReasonBufferLen, ""); SF2ChaserBossProfileData profileData; profileData.Init(); - SF2BossProfileData g_CachedProfileData; - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - - profileData.UnnerfedVisibility = !!kv.GetNum("old_boss_visibility", profileData.UnnerfedVisibility); - - profileData.ClearLayersOnAnimUpdate = !!kv.GetNum("animation_clear_layers_on_update", profileData.ClearLayersOnAnimUpdate); + profileData.ClearLayersOnAnimUpdate = kv.GetNum("animation_clear_layers_on_update", profileData.ClearLayersOnAnimUpdate) != 0; GetProfileDifficultyFloatValues(kv, "walkspeed", profileData.WalkSpeed, profileData.WalkSpeed); - GetProfileDifficultyFloatValues(kv, "walkspeed_max", profileData.MaxWalkSpeed, profileData.MaxWalkSpeed); profileData.WakeRadius = kv.GetFloat("wake_radius", profileData.WakeRadius); if (profileData.WakeRadius < 0.0) @@ -33,21 +27,72 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.WakeRadius = 0.0; } - GetProfileDifficultyFloatValues(kv, "alert_gracetime", profileData.AlertGracetime, profileData.AlertGracetime); - GetProfileDifficultyFloatValues(kv, "alert_duration", profileData.AlertDuration, profileData.AlertDuration); - GetProfileDifficultyFloatValues(kv, "chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); - profileData.ChaseDurationAddVisibleMin = kv.GetFloat("chase_duration_add_visible_min", profileData.ChaseDurationAddVisibleMin); - profileData.ChaseDurationAddVisibleMax = kv.GetFloat("chase_duration_add_visible_max", profileData.ChaseDurationAddVisibleMax); - - profileData.ChasePersistencyTimeInit = kv.GetFloat("chase_persistency_time_init", profileData.ChasePersistencyTimeInit); - profileData.ChaseAttackPersistencyTimeInit = kv.GetFloat("chase_persistency_time_init_attack", profileData.ChaseAttackPersistencyTimeInit); - profileData.ChaseAttackPersistencyTimeAdd = kv.GetFloat("chase_persistency_time_add_attack", profileData.ChaseAttackPersistencyTimeAdd); - profileData.ChaseNewTargetPersistencyTimeInit = kv.GetFloat("chase_persistency_time_init_newtarget", profileData.ChaseNewTargetPersistencyTimeInit); - profileData.ChaseNewTargetPersistencyTimeAdd = kv.GetFloat("chase_persistency_time_add_newtarget", profileData.ChaseNewTargetPersistencyTimeAdd); - profileData.ChasePersistencyAddVisibleMin = kv.GetFloat("chase_persistency_time_add_visible_min", profileData.ChasePersistencyAddVisibleMin); - profileData.ChasePersistencyAddVisibleMax = kv.GetFloat("chase_persistency_time_add_visible_max", profileData.ChasePersistencyAddVisibleMax); - profileData.ChaseStunPersistencyTimeInit = kv.GetFloat("chase_persistency_time_init_stun", profileData.ChaseStunPersistencyTimeInit); - profileData.ChaseStunPersistencyTimeAdd = kv.GetFloat("chase_persistency_time_add_stun", profileData.ChaseStunPersistencyTimeAdd); + if (kv.JumpToKey("idle")) + { + profileData.IdleData.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("alert")) + { + profileData.AlertData.Load(kv); + + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.AlertOnAlertInfo.Radius[i] = baseData.SearchRange[i]; + } + profileData.AlertOnAlertInfo.Load(kv); + + kv.GoBack(); + } + else + { + GetProfileDifficultyFloatValues(kv, "alert_gracetime", profileData.AlertData.GraceTime, profileData.AlertData.GraceTime); + GetProfileDifficultyFloatValues(kv, "search_alert_gracetime", profileData.AlertData.GraceTime, profileData.AlertData.GraceTime); + GetProfileDifficultyFloatValues(kv, "alert_duration", profileData.AlertData.Duration, profileData.AlertData.Duration); + GetProfileDifficultyFloatValues(kv, "search_alert_duration", profileData.AlertData.Duration, profileData.AlertData.Duration); + } + + if (kv.JumpToKey("chase")) + { + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.AlertOnChaseInfo.Radius[i] = baseData.SearchRange[i]; + } + profileData.AlertOnChaseInfo.Load(kv); + + if (kv.JumpToKey("duration")) + { + GetProfileDifficultyFloatValues(kv, "max", profileData.ChaseDuration, profileData.ChaseDuration); + + if (kv.JumpToKey("add")) + { + GetProfileDifficultyFloatValues(kv, "target_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); + GetProfileDifficultyFloatValues(kv, "visible_target_near", profileData.ChaseDurationAddVisibleMin, profileData.ChaseDurationAddVisibleMin); + GetProfileDifficultyFloatValues(kv, "visible_target_far", profileData.ChaseDurationAddVisibleMax, profileData.ChaseDurationAddVisibleMax); + GetProfileDifficultyFloatValues(kv, "attack", profileData.ChaseDurationAddOnAttack, profileData.ChaseDurationAddOnAttack); + GetProfileDifficultyFloatValues(kv, "stunned", profileData.ChaseDurationAddOnStun, profileData.ChaseDurationAddOnStun); + + kv.GoBack(); + } + + kv.GoBack(); + } + + kv.GoBack(); + } + else + { + GetProfileDifficultyFloatValues(kv, "chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); + GetProfileDifficultyFloatValues(kv, "search_chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.ChaseDurationAddMaxRange[i] = baseData.SearchRange[i]; + } + GetProfileDifficultyFloatValues(kv, "chase_duration_add_max_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); + GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_min", profileData.ChaseDurationAddVisibleMin, profileData.ChaseDurationAddVisibleMin); + GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_max", profileData.ChaseDurationAddVisibleMax, profileData.ChaseDurationAddVisibleMax); + } if (kv.JumpToKey("senses")) { @@ -81,9 +126,16 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } kv.GoBack(); } + if (kv.JumpToKey("smell")) + { + profileData.SmellData.Load(kv); + + kv.GoBack(); + } kv.GoBack(); } + GetProfileDifficultyBoolValues(kv, "wander_move", profileData.CanWander, profileData.CanWander); GetProfileDifficultyFloatValues(kv, "wander_range_min", profileData.WanderRangeMin, profileData.WanderRangeMin); GetProfileDifficultyFloatValues(kv, "wander_range_max", profileData.WanderRangeMax, profileData.WanderRangeMax); GetProfileDifficultyFloatValues(kv, "wander_time_min", profileData.WanderTimeMin, profileData.WanderTimeMin); @@ -91,56 +143,69 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe GetProfileDifficultyFloatValues(kv, "wander_enter_time_min", profileData.WanderEnterTimeMin, profileData.WanderEnterTimeMin); GetProfileDifficultyFloatValues(kv, "wander_enter_time_max", profileData.WanderEnterTimeMax, profileData.WanderEnterTimeMax); - profileData.StunEnabled = !!kv.GetNum("stun_enabled", profileData.StunEnabled); - if (profileData.StunEnabled) + if (kv.GetNum("stun_enabled", false) != 0) { - profileData.StunCooldown = kv.GetFloat("stun_cooldown", profileData.StunCooldown); - if (profileData.StunCooldown < 0.0) - { - profileData.StunCooldown = 0.0; - } - profileData.StunHealth = kv.GetFloat("stun_health", profileData.StunHealth); - if (profileData.StunHealth < 0.0) - { - profileData.StunHealth = 0.0; - } - profileData.StunHealthPerPlayer = kv.GetFloat("stun_health_per_player", profileData.StunHealthPerPlayer); - if (profileData.StunHealthPerPlayer < 0.0) + SF2ChaserBossProfileStunData stunData; + stunData = profileData.StunData; + for (int i = 0; i < Difficulty_Max; i++) { - profileData.StunHealthPerPlayer = 0.0; + stunData.Enabled[i] = true; } - profileData.StunHealthPerClass[1] = kv.GetFloat("stun_health_per_scout", profileData.StunHealthPerClass[1]); - profileData.StunHealthPerClass[2] = kv.GetFloat("stun_health_per_sniper", profileData.StunHealthPerClass[2]); - profileData.StunHealthPerClass[3] = kv.GetFloat("stun_health_per_soldier", profileData.StunHealthPerClass[3]); - profileData.StunHealthPerClass[4] = kv.GetFloat("stun_health_per_demoman", profileData.StunHealthPerClass[4]); - profileData.StunHealthPerClass[5] = kv.GetFloat("stun_health_per_medic", profileData.StunHealthPerClass[5]); - profileData.StunHealthPerClass[6] = kv.GetFloat("stun_health_per_heavyweapons", profileData.StunHealthPerClass[6]); - profileData.StunHealthPerClass[7] = kv.GetFloat("stun_health_per_pyro", profileData.StunHealthPerClass[7]); - profileData.StunHealthPerClass[8] = kv.GetFloat("stun_health_per_spy", profileData.StunHealthPerClass[8]); - profileData.StunHealthPerClass[9] = kv.GetFloat("stun_health_per_engineer", profileData.StunHealthPerClass[9]); - profileData.FlashlightStun = !!kv.GetNum("stun_damage_flashlight_enabled", profileData.FlashlightStun); - profileData.FlashlightDamage = kv.GetFloat("stun_damage_flashlight", profileData.FlashlightDamage); - profileData.ChaseInitialOnStun = !!kv.GetNum("chase_initial_on_stun", profileData.ChaseInitialOnStun); - - profileData.ItemDropOnStun = !!kv.GetNum("drop_item_on_stun", profileData.ItemDropOnStun); - if (profileData.ItemDropOnStun) + GetProfileDifficultyFloatValues(kv, "stun_cooldown", stunData.Cooldown, stunData.Cooldown); + GetProfileDifficultyFloatValues(kv, "stun_health", stunData.Health, stunData.Health); + + GetProfileDifficultyFloatValues(kv, "stun_health_per_player", stunData.AddHealthPerPlayer, stunData.AddHealthPerPlayer); + GetProfileDifficultyFloatValues(kv, "stun_health_per_scout", stunData.AddHealthPerScout, stunData.AddHealthPerScout); + GetProfileDifficultyFloatValues(kv, "stun_health_per_soldier", stunData.AddHealthPerSoldier, stunData.AddHealthPerSoldier); + GetProfileDifficultyFloatValues(kv, "stun_health_per_pyro", stunData.AddHealthPerPyro, stunData.AddHealthPerPyro); + GetProfileDifficultyFloatValues(kv, "stun_health_per_demoman", stunData.AddHealthPerDemoman, stunData.AddHealthPerDemoman); + GetProfileDifficultyFloatValues(kv, "stun_health_per_heavyweapons", stunData.AddHealthPerHeavy, stunData.AddHealthPerHeavy); + GetProfileDifficultyFloatValues(kv, "stun_health_per_engineer", stunData.AddHealthPerEngineer, stunData.AddHealthPerEngineer); + GetProfileDifficultyFloatValues(kv, "stun_health_per_medic", stunData.AddHealthPerMedic, stunData.AddHealthPerMedic); + GetProfileDifficultyFloatValues(kv, "stun_health_per_sniper", stunData.AddHealthPerSniper, stunData.AddHealthPerSniper); + GetProfileDifficultyFloatValues(kv, "stun_health_per_spy", stunData.AddHealthPerSpy, stunData.AddHealthPerSpy); + + GetProfileDifficultyBoolValues(kv, "stun_damage_flashlight_enabled", stunData.FlashlightStun, stunData.FlashlightStun); + GetProfileDifficultyFloatValues(kv, "stun_damage_flashlight", stunData.FlashlightStunDamage, stunData.FlashlightStunDamage); + + GetProfileDifficultyBoolValues(kv, "chase_initial_on_stun", stunData.ChaseInitialOnEnd, stunData.ChaseInitialOnEnd); + + GetProfileDifficultyBoolValues(kv, "drop_item_on_stun", stunData.ItemDrop, stunData.ItemDrop); + GetProfileDifficultyNumValues(kv, "drop_item_type", stunData.ItemDropType, stunData.ItemDropType); + + GetProfileDifficultyBoolValues(kv, "disappear_on_stun", stunData.Disappear, stunData.Disappear); + + stunData.KeyDrop = kv.GetNum("keydrop_enabled", stunData.KeyDrop) != 0; + kv.GetString("key_model", stunData.KeyModel, sizeof(stunData.KeyModel), stunData.KeyModel); + PrecacheModel2(stunData.KeyModel, _, _, g_FileCheckConVar.BoolValue); + kv.GetString("key_trigger", stunData.KeyTrigger, sizeof(stunData.KeyTrigger), stunData.KeyTrigger); + profileData.StunData = stunData; + } + else + { + if (kv.JumpToKey("stun")) { - profileData.StunItemDropType = kv.GetNum("drop_item_type", profileData.StunItemDropType); - if (profileData.StunItemDropType < 1) - { - profileData.StunItemDropType = 1; - } - if (profileData.StunItemDropType > 7) + for (int i = 0; i < Difficulty_Max; i++) { - profileData.StunItemDropType = 7; + profileData.StunData.Enabled[i] = true; } + profileData.StunData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); } + } - profileData.DisappearOnStun = !!kv.GetNum("disappear_on_stun", profileData.DisappearOnStun); - - if (kv.JumpToKey("resistances")) + if (kv.JumpToKey("resistances")) + { + profileData.DamageResistances = new ArrayList(sizeof(SF2ChaserBossProfileResistanceData)); + if (!kv.GotoFirstSubKey()) { - profileData.DamageResistances = new ArrayList(); + SF2ChaserBossProfileResistanceData resistanceData; + resistanceData.Init(); + for (int i = 0; i < Difficulty_Max; i++) + { + resistanceData.Multiplier[i] = 0.0; + } + resistanceData.DamageTypes = new ArrayList(); char key[64]; int resistance = -1; for (int i = 1;; i++) @@ -151,39 +216,118 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { break; } - profileData.DamageResistances.Push(resistance); + resistanceData.DamageTypes.Push(resistance); + } + profileData.DamageResistances.PushArray(resistanceData); + } + else + { + do + { + SF2ChaserBossProfileResistanceData resistance; + resistance.Init(); + resistance.Load(kv); + profileData.DamageResistances.PushArray(resistance); } + while (kv.GotoNextKey()); + kv.GoBack(); } + kv.GoBack(); + } - profileData.KeyDrop = !!kv.GetNum("keydrop_enabled", profileData.KeyDrop); - if (profileData.KeyDrop) + if (kv.JumpToKey("death")) + { + for (int i = 0; i < Difficulty_Max; i++) { - kv.GetString("key_model", profileData.KeyModel, sizeof(profileData.KeyModel), profileData.KeyModel); - PrecacheModel2(profileData.KeyModel, _, _, g_FileCheckConVar.BoolValue); - kv.GetString("key_trigger", profileData.KeyTrigger, sizeof(profileData.KeyTrigger), profileData.KeyTrigger); + profileData.DeathData.Enabled[i] = true; } + profileData.DeathData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); } - profileData.CanCloak = !!kv.GetNum("cloak_enable", profileData.CanCloak); - if (profileData.CanCloak) + profileData.BackstabDamageScale = kv.GetFloat("backstab_damage_scale", profileData.BackstabDamageScale); + + if (kv.JumpToKey("cloaking")) + { + profileData.CloakData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + else if (kv.GetNum("cloak_enable", false) != 0) { - GetProfileDifficultyFloatValues(kv, "cloak_cooldown", profileData.CloakCooldown, profileData.CloakCooldown); - GetProfileDifficultyFloatValues(kv, "cloak_range", profileData.CloakRange, profileData.CloakRange); - GetProfileDifficultyFloatValues(kv, "cloak_decloak_range", profileData.DecloakRange, profileData.DecloakRange); - GetProfileDifficultyFloatValues(kv, "cloak_duration", profileData.CloakDuration, profileData.CloakDuration); - GetProfileDifficultyFloatValues(kv, "cloak_speed_multiplier", profileData.CloakSpeedMultiplier, profileData.CloakSpeedMultiplier); - kv.GetString("cloak_particle", profileData.CloakParticle, sizeof(profileData.CloakParticle), profileData.CloakParticle); - - GetProfileColorNoBacks(kv, "cloak_rendercolor", profileData.CloakRenderColor[0], profileData.CloakRenderColor[1], profileData.CloakRenderColor[2], profileData.CloakRenderColor[3], - g_CachedProfileData.RenderColor[0], g_CachedProfileData.RenderColor[1], g_CachedProfileData.RenderColor[2], profileData.CloakRenderColor[3]); - profileData.CloakRenderMode = kv.GetNum("cloak_rendermode", profileData.CloakRenderMode); - - kv.GetString("cloak_on_sound", profileData.CloakOnSound, sizeof(profileData.CloakOnSound), profileData.CloakOnSound); - kv.GetString("cloak_off_sound", profileData.CloakOffSound, sizeof(profileData.CloakOffSound), profileData.CloakOffSound); - TryPrecacheBossProfileSoundPath(profileData.CloakOnSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.CloakOffSound, _, g_FileCheckConVar.BoolValue); + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.CloakData.Enabled[i] = true; + } + GetProfileDifficultyFloatValues(kv, "cloak_cooldown", profileData.CloakData.Cooldown, profileData.CloakData.Cooldown); + GetProfileDifficultyFloatValues(kv, "cloak_range", profileData.CloakData.CloakRange, profileData.CloakData.CloakRange); + GetProfileDifficultyFloatValues(kv, "cloak_decloak_range", profileData.CloakData.DecloakRange, profileData.CloakData.DecloakRange); + GetProfileDifficultyFloatValues(kv, "cloak_duration", profileData.CloakData.Duration, profileData.CloakData.Duration); + + GetProfileColorNoBacks(kv, "cloak_rendercolor", profileData.CloakData.CloakRenderColor[0], profileData.CloakData.CloakRenderColor[1], profileData.CloakData.CloakRenderColor[2], profileData.CloakData.CloakRenderColor[3], + profileData.CloakData.CloakRenderColor[0], profileData.CloakData.CloakRenderColor[1], profileData.CloakData.CloakRenderColor[2], profileData.CloakData.CloakRenderColor[3]); + + profileData.CloakData.CloakRenderMode = kv.GetNum("cloak_rendermode", profileData.CloakData.CloakRenderMode); + + profileData.CloakData.CloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + SF2BossProfileBaseEffectInfo particle1; + particle1.Init(); + particle1.Type = EffectType_Particle; + particle1.Event = EffectEvent_Constant; + kv.GetString("cloak_particle", particle1.ParticleName, sizeof(particle1.ParticleName), "drg_cow_explosioncore_charged_blue"); + particle1.LifeTime = 0.1; + particle1.Origin = {0.0, 0.0, 35.0}; + particle1.PostLoad(); + profileData.CloakData.CloakEffects.PushArray(particle1); + + char sound[PLATFORM_MAX_PATH]; + SF2BossProfileBaseEffectInfo sound1; + sound1.Init(); + sound1.Type = EffectType_Sound; + sound1.Event = EffectEvent_Constant; + kv.GetString("cloak_on_sound", sound, sizeof(sound), "weapons/medi_shield_deploy.wav"); + TryPrecacheBossProfileSoundPath(sound, g_FileCheckConVar.BoolValue); + sound1.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound1.SoundSounds.Paths.PushString(sound); + sound1.PostLoad(); + profileData.CloakData.CloakEffects.PushArray(sound1); + + profileData.CloakData.DecloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + profileData.CloakData.DecloakEffects.PushArray(particle1); + + SF2BossProfileBaseEffectInfo sound2; + sound2.Init(); + sound2.Type = EffectType_Sound; + sound2.Event = EffectEvent_Constant; + kv.GetString("cloak_off_sound", sound, sizeof(sound), "weapons/medi_shield_retract.wav"); + TryPrecacheBossProfileSoundPath(sound, g_FileCheckConVar.BoolValue); + sound2.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound2.SoundSounds.Paths.PushString(sound); + sound2.PostLoad(); + profileData.CloakData.DecloakEffects.PushArray(sound2); + + SF2ChaserBossProfilePostureInfo posture; + posture.Init(); + float multipliers[Difficulty_Max] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + GetProfileDifficultyFloatValues(kv, "cloak_speed_multiplier", multipliers, multipliers); + for (int i = 0; i < Difficulty_Max; i++) + { + posture.Speed[i] = baseData.RunSpeed[i] * multipliers[i]; + posture.WalkSpeed[i] = profileData.WalkSpeed[i] * multipliers[i]; + posture.Acceleration[i] = baseData.Acceleration[i] * multipliers[i]; + posture.CloakInfo.Enabled[i] = true; + } + posture.Name = "Legacy Cloak"; + + if (profileData.Postures == null) + { + profileData.Postures = new StringMap(); + } + profileData.Postures.SetArray(posture.Name, posture, sizeof(posture)); } + profileData.ProjectilesEnabled = !!kv.GetNum("projectile_enable", profileData.ProjectilesEnabled); if (profileData.ProjectilesEnabled) { @@ -198,21 +342,21 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe GetProfileDifficultyFloatValues(kv, "projectile_damageradius", profileData.ProjectileRadius, profileData.ProjectileRadius); GetProfileDifficultyFloatValues(kv, "projectile_deviation", profileData.ProjectileDeviation, profileData.ProjectileDeviation); GetProfileDifficultyNumValues(kv, "projectile_count", profileData.ProjectileCount, profileData.ProjectileCount); - profileData.CriticalProjectiles = !!kv.GetNum("enable_crit_rockets", profileData.CriticalProjectiles); - profileData.ShootGestures = !!kv.GetNum("use_gesture_shoot", profileData.ShootGestures); + profileData.CriticalProjectiles = kv.GetNum("enable_crit_rockets", profileData.CriticalProjectiles) != 0; + profileData.ShootGestures = kv.GetNum("use_gesture_shoot", profileData.ShootGestures) != 0; if (profileData.ShootGestures) { kv.GetString("gesture_shootprojectile", profileData.ShootGestureName, sizeof(profileData.ShootGestureName), profileData.ShootGestureName); } - profileData.ProjectileClips = !!kv.GetNum("projectile_clips_enable", profileData.ProjectileClips); + profileData.ProjectileClips = kv.GetNum("projectile_clips_enable", profileData.ProjectileClips) != 0; if (profileData.ProjectileClips) { GetProfileDifficultyNumValues(kv, "projectile_ammo_loaded", profileData.ProjectileClipSize, profileData.ProjectileClipSize); GetProfileDifficultyFloatValues(kv, "projectile_reload_time", profileData.ProjectileReloadTime, profileData.ProjectileReloadTime); } - profileData.ChargeUpProjectiles = !!kv.GetNum("projectile_chargeup_enable", profileData.ChargeUpProjectiles); + profileData.ChargeUpProjectiles = kv.GetNum("projectile_chargeup_enable", profileData.ChargeUpProjectiles) != 0; if (profileData.ChargeUpProjectiles) { GetProfileDifficultyFloatValues(kv, "projectile_chargeup_duration", profileData.ProjectileChargeUp, profileData.ProjectileChargeUp); @@ -237,8 +381,8 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("fire_shoot_sound", profileData.FireballShootSound, sizeof(profileData.FireballShootSound), profileData.FireballShootSound); kv.GetString("fire_trail", profileData.FireballTrail, sizeof(profileData.FireballTrail), profileData.FireballTrail); - TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Iceball: { @@ -247,9 +391,9 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("fire_iceball_slow_sound", profileData.IceballSlowSound, sizeof(profileData.IceballSlowSound), profileData.IceballSlowSound); kv.GetString("fire_iceball_trail", profileData.IceballTrail, sizeof(profileData.IceballTrail), profileData.IceballTrail); - TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.IceballSlowSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.IceballSlowSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Rocket: { @@ -259,8 +403,8 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("rocket_shoot_sound", profileData.RocketShootSound, sizeof(profileData.RocketShootSound), profileData.RocketShootSound); kv.GetString("rocket_model", profileData.RocketModel, sizeof(profileData.RocketModel), profileData.RocketModel); - TryPrecacheBossProfileSoundPath(profileData.RocketExplodeSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.RocketShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.RocketExplodeSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.RocketShootSound, g_FileCheckConVar.BoolValue); if (strcmp(profileData.RocketModel, ROCKET_MODEL, true) != 0) { @@ -278,32 +422,32 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { kv.GetString("grenade_shoot_sound", profileData.GrenadeShootSound, sizeof(profileData.GrenadeShootSound), profileData.GrenadeShootSound); - TryPrecacheBossProfileSoundPath(profileData.GrenadeShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.GrenadeShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_SentryRocket: { kv.GetString("sentryrocket_shoot_sound", profileData.SentryRocketShootSound, sizeof(profileData.SentryRocketShootSound), profileData.SentryRocketShootSound); - TryPrecacheBossProfileSoundPath(profileData.SentryRocketShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.SentryRocketShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Arrow: { kv.GetString("arrow_shoot_sound", profileData.ArrowShootSound, sizeof(profileData.ArrowShootSound), profileData.ArrowShootSound); - TryPrecacheBossProfileSoundPath(profileData.ArrowShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.ArrowShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Mangler: { kv.GetString("mangler_shoot_sound", profileData.ManglerShootSound, sizeof(profileData.ManglerShootSound), profileData.ManglerShootSound); - TryPrecacheBossProfileSoundPath(profileData.ManglerShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.ManglerShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Baseball: { kv.GetString("baseball_model", profileData.BaseballModel, sizeof(profileData.BaseballModel), profileData.BaseballModel); kv.GetString("baseball_shoot_sound", profileData.BaseballShootSound, sizeof(profileData.BaseballShootSound), profileData.BaseballShootSound); - TryPrecacheBossProfileSoundPath(profileData.BaseballShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.BaseballShootSound, g_FileCheckConVar.BoolValue); PrecacheModel2(profileData.BaseballModel, _, _, g_FileCheckConVar.BoolValue); } } @@ -321,7 +465,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe float position[3]; for (int i = 0; i <= profileData.ProjectileRandomPosMax; i++) { - position = view_as( { 0.0, 0.0, 0.0 }); + position = { 0.0, 0.0, 0.0 }; FormatEx(keyName, sizeof(keyName), "projectile_pos_offset_%i", i); kv.GetVector(keyName, position, position); profileData.ProjectilePosOffsets.PushArray(position); @@ -329,858 +473,1254 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } } - profileData.ShootAnimations = !!kv.GetNum("use_shoot_animations", profileData.ShootAnimations); + profileData.XenobladeCombo = kv.GetNum("xenoblade_chain_art_combo", profileData.XenobladeCombo) != 0; + if (profileData.XenobladeCombo) + { + profileData.XenobladeDuration = kv.GetFloat("xenoblade_break_duration", profileData.XenobladeDuration); + profileData.XenobladeToppleDuration = kv.GetFloat("xenoblade_topple_duration", profileData.XenobladeToppleDuration); + profileData.XenobladeToppleSlowdown = kv.GetFloat("xenoblade_topple_slowdown", profileData.XenobladeToppleSlowdown); + profileData.XenobladeDazeDuration = kv.GetFloat("xenoblade_daze_duration", profileData.XenobladeDazeDuration); + } - profileData.AdvancedDamageEffects = !!kv.GetNum("player_damage_effects", profileData.AdvancedDamageEffects); - if (profileData.AdvancedDamageEffects) + profileData.EarthquakeFootsteps = kv.GetNum("earthquake_footsteps", profileData.EarthquakeFootsteps) != 0; + if (profileData.EarthquakeFootsteps) { - profileData.AdvancedDamageEffectParticles = !!kv.GetNum("player_attach_particle", profileData.AdvancedDamageEffectParticles); + profileData.EarthquakeFootstepAmplitude = kv.GetFloat("earthquake_footsteps_amplitude", profileData.EarthquakeFootstepAmplitude); + profileData.EarthquakeFootstepFrequency = kv.GetFloat("earthquake_footsteps_frequency", profileData.EarthquakeFootstepFrequency); + profileData.EarthquakeFootstepDuration = kv.GetFloat("earthquake_footsteps_duration", profileData.EarthquakeFootstepDuration); + profileData.EarthquakeFootstepRadius = kv.GetFloat("earthquake_footsteps_radius", profileData.EarthquakeFootstepRadius); + profileData.EarthquakeFootstepAirShake = kv.GetNum("earthquake_footsteps_airshake", profileData.EarthquakeFootstepAirShake) != 0; + } - profileData.RandomAdvancedDamageEffects = !!kv.GetNum("player_damage_random_effects", profileData.RandomAdvancedDamageEffects); - if (profileData.RandomAdvancedDamageEffects) + GetProfileDifficultyBoolValues(kv, "traps_enabled", profileData.Traps, profileData.Traps); + GetProfileDifficultyNumValues(kv, "trap_type", profileData.TrapType, profileData.TrapType); + GetProfileDifficultyFloatValues(kv, "trap_spawn_cooldown", profileData.TrapCooldown, profileData.TrapCooldown); + kv.GetString("trap_model", profileData.TrapModel, sizeof(profileData.TrapModel), profileData.TrapModel); + kv.GetString("trap_deploy_sound", profileData.TrapDeploySound, sizeof(profileData.TrapDeploySound), profileData.TrapDeploySound); + kv.GetString("trap_miss_sound", profileData.TrapMissSound, sizeof(profileData.TrapMissSound), profileData.TrapMissSound); + kv.GetString("trap_catch_sound", profileData.TrapCatchSound, sizeof(profileData.TrapCatchSound), profileData.TrapCatchSound); + kv.GetString("trap_animation_idle", profileData.TrapAnimIdle, sizeof(profileData.TrapAnimIdle), profileData.TrapAnimIdle); + kv.GetString("trap_animation_closed", profileData.TrapAnimClose, sizeof(profileData.TrapAnimClose), profileData.TrapAnimClose); + kv.GetString("trap_animation_open", profileData.TrapAnimOpen, sizeof(profileData.TrapAnimOpen), profileData.TrapAnimOpen); + TryPrecacheBossProfileSoundPath(profileData.TrapDeploySound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.TrapMissSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.TrapCatchSound, g_FileCheckConVar.BoolValue); + + if (strcmp(profileData.TrapModel, TRAP_MODEL, true) != 0) + { + if (!PrecacheModel(profileData.TrapModel, true)) { - profileData.RandomEffectIndexes = kv.GetNum("player_random_attack_indexes", profileData.RandomEffectIndexes); - if (profileData.RandomEffectIndexes < 0) - { - profileData.RandomEffectIndexes = 1; - } - kv.GetString("player_random_attack_indexes", profileData.RandomEffectIndexesString, sizeof(profileData.RandomEffectIndexesString), profileData.RandomEffectIndexesString); - GetProfileDifficultyFloatValues(kv, "player_random_duration", profileData.RandomEffectDuration, profileData.RandomEffectDuration); - GetProfileDifficultyFloatValues(kv, "player_random_slowdown", profileData.RandomEffectSlowdown, profileData.RandomEffectSlowdown); - profileData.RandomEffectStunType = kv.GetNum("player_random_stun_type", profileData.RandomEffectStunType); - if (profileData.RandomEffectStunType < 0) - { - profileData.RandomEffectStunType = 0; - } - if (profileData.RandomEffectStunType > 3) - { - profileData.RandomEffectStunType = 3; - } + LogSF2Message("Trap model file %s failed to be loaded, likely does not exist. This will crash the server if not fixed.", profileData.TrapModel); } - - profileData.JarateEffects = !!kv.GetNum("player_jarate_on_hit", profileData.JarateEffects); - if (profileData.JarateEffects) + else { - profileData.JarateIndexes = kv.GetNum("player_jarate_attack_indexs", profileData.JarateIndexes); - if (profileData.JarateIndexes < 0) - { - profileData.JarateIndexes = 1; - } - kv.GetString("player_jarate_attack_indexs", profileData.JarateIndexesString, sizeof(profileData.JarateIndexesString), profileData.JarateIndexesString); - GetProfileDifficultyFloatValues(kv, "player_jarate_duration", profileData.JarateDuration, profileData.JarateDuration); - profileData.JarateBeamParticle = !!kv.GetNum("player_jarate_beam_particle", profileData.JarateBeamParticle); - kv.GetString("player_jarate_particle", profileData.JarateParticle, sizeof(profileData.JarateParticle), profileData.JarateParticle); - - kv.GetString("player_jarate_sound", profileData.JarateHitSound, sizeof(profileData.JarateHitSound), profileData.JarateHitSound); - TryPrecacheBossProfileSoundPath(profileData.JarateHitSound, _, g_FileCheckConVar.BoolValue); + PrecacheModel2(profileData.TrapModel, _, _, g_FileCheckConVar.BoolValue); } + } - profileData.MilkEffects = !!kv.GetNum("player_milk_on_hit", profileData.MilkEffects); - if (profileData.MilkEffects) + if (kv.JumpToKey("autochase")) + { + GetProfileDifficultyBoolValues(kv, "enabled", profileData.AutoChaseEnabled, profileData.AutoChaseEnabled); + GetProfileDifficultyNumValues(kv, "threshold", profileData.AutoChaseCount, profileData.AutoChaseCount); + GetProfileDifficultyFloatValues(kv, "cooldown_after_chase", profileData.AutoChaseAfterChaseCooldown, profileData.AutoChaseAfterChaseCooldown); + GetProfileDifficultyBoolValues(kv, "sprinters", profileData.AutoChaseSprinters, profileData.AutoChaseSprinters); + if (kv.JumpToKey("add")) { - profileData.MilkIndexes = kv.GetNum("player_milk_attack_indexs", profileData.MilkIndexes); - if (profileData.MilkIndexes < 0) - { - profileData.MilkIndexes = 1; - } - kv.GetString("player_milk_attack_indexs", profileData.MilkIndexesString, sizeof(profileData.MilkIndexesString), profileData.MilkIndexesString); - GetProfileDifficultyFloatValues(kv, "player_milk_duration", profileData.MilkDuration, profileData.MilkDuration); - profileData.MilkBeamParaticle = !!kv.GetNum("player_milk_beam_particle", profileData.MilkBeamParaticle); - kv.GetString("player_milk_particle", profileData.MilkParticle, sizeof(profileData.MilkParticle), profileData.MilkParticle); + GetProfileDifficultyNumValues(kv, "on_state_change", profileData.AutoChaseAdd, profileData.AutoChaseAdd); + GetProfileDifficultyNumValues(kv, "footsteps", profileData.AutoChaseAddFootstep, profileData.AutoChaseAddFootstep); + GetProfileDifficultyNumValues(kv, "footsteps_loud", profileData.AutoChaseAddLoudFootstep, profileData.AutoChaseAddLoudFootstep); + GetProfileDifficultyNumValues(kv, "footsteps_quiet", profileData.AutoChaseAddQuietFootstep, profileData.AutoChaseAddQuietFootstep); + GetProfileDifficultyNumValues(kv, "voice", profileData.AutoChaseAddVoice, profileData.AutoChaseAddVoice); + GetProfileDifficultyNumValues(kv, "weapon", profileData.AutoChaseAddWeapon, profileData.AutoChaseAddWeapon); + kv.GoBack(); + } + kv.GoBack(); + } + else + { + GetProfileDifficultyBoolValues(kv, "auto_chase_enabled", profileData.AutoChaseEnabled, profileData.AutoChaseEnabled); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_threshold", profileData.AutoChaseCount, profileData.AutoChaseCount); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add", profileData.AutoChaseAdd, profileData.AutoChaseAdd); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps", profileData.AutoChaseAddFootstep, profileData.AutoChaseAddFootstep); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps_loud", profileData.AutoChaseAddLoudFootstep, profileData.AutoChaseAddLoudFootstep); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps_quiet", profileData.AutoChaseAddQuietFootstep, profileData.AutoChaseAddQuietFootstep); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_voice", profileData.AutoChaseAddVoice, profileData.AutoChaseAddVoice); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_weapon", profileData.AutoChaseAddWeapon, profileData.AutoChaseAddWeapon); + GetProfileDifficultyBoolValues(kv, "auto_chase_sprinters", profileData.AutoChaseSprinters, profileData.AutoChaseSprinters); + GetProfileDifficultyFloatValues(kv, "auto_chase_cooldown_after_chase", profileData.AutoChaseAfterChaseCooldown, profileData.AutoChaseAfterChaseCooldown); + } + + profileData.ChasesEndlessly = KvGetNum(kv, "boss_chases_endlessly", profileData.ChasesEndlessly) != 0; - kv.GetString("player_milk_sound", profileData.MilkHitSound, sizeof(profileData.MilkHitSound), profileData.MilkHitSound); - TryPrecacheBossProfileSoundPath(profileData.MilkHitSound, _, g_FileCheckConVar.BoolValue); + if (kv.JumpToKey("chase_on_look")) + { + profileData.ChaseOnLookData.Load(kv); + kv.GoBack(); + } + else + { + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.ChaseOnLookData.Enabled[i] = kv.GetNum("chase_upon_look") != 0; + profileData.ChaseOnLookData.Enabled[i] = kv.GetNum("auto_chase_upon_look", profileData.ChaseOnLookData.Enabled[i]) != 0; } + } - profileData.GasEffects = !!kv.GetNum("player_gas_on_hit", profileData.GasEffects); - if (profileData.GasEffects) + profileData.BoxingBoss = kv.GetNum("boxing_boss", profileData.BoxingBoss) != 0; + + profileData.NormalSoundHook = kv.GetNum("normal_sound_hook", profileData.NormalSoundHook) != 0; + + profileData.OldAnimationAI = kv.GetNum("old_animation_ai", profileData.OldAnimationAI) != 0; + + if (kv.JumpToKey("rages")) + { + profileData.Rages = new ArrayList(sizeof(SF2ChaserRageInfo)); + if (kv.GotoFirstSubKey()) { - profileData.GasIndexes = kv.GetNum("player_gas_attack_indexs", profileData.GasIndexes); - if (profileData.GasIndexes < 0) + do { - profileData.GasIndexes = 1; + SF2ChaserRageInfo rage; + rage.Init(); + rage.Load(kv, g_FileCheckConVar.BoolValue); + profileData.Rages.PushArray(rage); } - kv.GetString("player_gas_attack_indexs", profileData.GasIndexesString, sizeof(profileData.GasIndexesString), profileData.GasIndexesString); - GetProfileDifficultyFloatValues(kv, "player_gas_duration", profileData.GasDuration, profileData.GasDuration); - profileData.GasBeamParticle = !!kv.GetNum("player_gas_beam_particle", profileData.GasBeamParticle); - kv.GetString("player_gas_particle", profileData.GasParticle, sizeof(profileData.GasParticle), profileData.GasParticle); + while (kv.GotoNextKey()); - kv.GetString("player_gas_sound", profileData.GasHitSound, sizeof(profileData.GasHitSound), profileData.GasHitSound); - TryPrecacheBossProfileSoundPath(profileData.GasHitSound, _, g_FileCheckConVar.BoolValue); + kv.GoBack(); } - - profileData.MarkEffects = !!kv.GetNum("player_mark_on_hit", profileData.MarkEffects); - if (profileData.MarkEffects) + kv.GoBack(); + } + else + { + if (profileData.BoxingBoss) { - profileData.MarkIndexes = kv.GetNum("player_mark_attack_indexs", profileData.MarkIndexes); - if (profileData.MarkIndexes < 0) + profileData.Rages = new ArrayList(sizeof(SF2ChaserRageInfo)); + SF2ChaserRageInfo rage1, rage2, rage3; + rage1.Init(); + rage2.Init(); + rage3.Init(); + rage2.PercentageThreshold = 0.5; + rage3.PercentageThreshold = 0.25; + + rage1.Name = "1"; + rage2.Name = "2"; + rage3.Name = "3"; + + rage1.Invincible = true; + rage2.Invincible = true; + rage3.Invincible = true; + + if (kv.GetNum("self_heal_enabled", false) != 0) { - profileData.MarkIndexes = 1; + rage1.Invincible = false; + rage2.Invincible = false; + rage3.Invincible = false; + + rage1.IsHealing = true; + rage2.IsHealing = true; + rage3.IsHealing = true; + + rage1.HealDelay = kv.GetFloat("heal_timer", 0.0); + rage2.HealDelay = rage1.HealDelay; + rage3.HealDelay = rage1.HealDelay; + + rage1.FleeRange[0] = kv.GetFloat("heal_range_min", 512.0); + rage1.FleeRange[1] = kv.GetFloat("heal_range_max", 1024.0); + rage2.FleeRange[0] = rage1.FleeRange[0]; + rage2.FleeRange[1] = rage1.FleeRange[1]; + rage3.FleeRange[0] = rage1.FleeRange[0]; + rage3.FleeRange[1] = rage1.FleeRange[1]; + + rage1.PercentageThreshold = kv.GetFloat("health_percentage_to_heal", 0.35); + rage2.PercentageThreshold = rage1.PercentageThreshold; + rage3.PercentageThreshold = rage1.PercentageThreshold; + + rage1.HealAmount = kv.GetFloat("heal_percentage_one", 0.5); + rage2.HealAmount = kv.GetFloat("heal_percentage_two", 0.5); + rage3.HealAmount = kv.GetFloat("heal_percentage_three", 0.5); + + rage1.HealCloak = kv.GetNum("cloak_to_heal", false) != 0; + rage2.HealCloak = rage1.HealCloak; + rage3.HealCloak = rage1.HealCloak; } - kv.GetString("player_mark_attack_indexs", profileData.MarkIndexesString, sizeof(profileData.MarkIndexesString), profileData.MarkIndexesString); - GetProfileDifficultyFloatValues(kv, "player_mark_duration", profileData.MarkDuration, profileData.MarkDuration); - } - profileData.SilentMarkEffects = !!kv.GetNum("player_silent_mark_on_hit", profileData.SilentMarkEffects); - if (profileData.SilentMarkEffects) - { - profileData.SilentMarkIndexes = kv.GetNum("player_silent_mark_attack_indexs", profileData.SilentMarkIndexes); - if (profileData.SilentMarkIndexes < 0) + if (kv.JumpToKey("sound_rage")) { - profileData.SilentMarkIndexes = 1; + rage1.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); } - kv.GetString("player_silent_mark_attack_indexs", profileData.SilentMarkIndexesString, sizeof(profileData.SilentMarkIndexesString), profileData.SilentMarkIndexesString); - GetProfileDifficultyFloatValues(kv, "player_silent_mark_duration", profileData.SilentMarkDuration, profileData.SilentMarkDuration); - } - profileData.IgniteEffects = !!kv.GetNum("player_ignite_on_hit", profileData.IgniteEffects); - if (profileData.IgniteEffects) - { - profileData.IgniteIndexes = kv.GetNum("player_ignite_attack_indexs", profileData.IgniteIndexes); - if (profileData.IgniteIndexes < 0) + if (kv.JumpToKey("sound_rage_2")) { - profileData.IgniteIndexes = 1; + rage2.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); } - kv.GetString("player_ignite_attack_indexs", profileData.IgniteIndexesString, sizeof(profileData.IgniteIndexesString), profileData.IgniteIndexesString); - GetProfileDifficultyFloatValues(kv, "player_ignite_duration", profileData.IgniteDuration, profileData.IgniteDuration); - GetProfileDifficultyFloatValues(kv, "player_ignite_delay", profileData.IgniteDelay, profileData.IgniteDelay); - } - - profileData.StunEffects = !!kv.GetNum("player_stun_on_hit", profileData.StunEffects); - if (profileData.StunEffects) - { - profileData.StunEffectType = kv.GetNum("player_stun_type", profileData.StunEffectType); - if (profileData.StunEffectType < 0) + else { - profileData.StunEffectType = 0; + if (kv.JumpToKey("sound_rage")) + { + rage2.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } } - if (profileData.StunEffectType > 3) + + if (kv.JumpToKey("sound_rage_3")) { - profileData.StunEffectType = 3; + rage3.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); } - profileData.StunEffectIndexes = kv.GetNum("player_stun_attack_indexs", profileData.StunEffectIndexes); - if (profileData.StunEffectIndexes < 0) + else { - profileData.StunEffectIndexes = 1; + if (kv.JumpToKey("sound_rage")) + { + rage3.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } } - kv.GetString("player_stun_attack_indexs", profileData.StunEffectIndexesString, sizeof(profileData.StunEffectIndexesString), profileData.StunEffectIndexesString); - GetProfileDifficultyFloatValues(kv, "player_stun_duration", profileData.StunEffectDuration, profileData.StunEffectDuration); - GetProfileDifficultyFloatValues(kv, "player_stun_slowdown", profileData.StunEffectSlowdown, profileData.StunEffectSlowdown); - profileData.StunEffectBeamParticle = !!kv.GetNum("player_stun_beam_particle", profileData.StunEffectBeamParticle); - kv.GetString("player_stun_particle", profileData.StunParticle, sizeof(profileData.StunParticle), profileData.StunParticle); - - kv.GetString("player_stun_sound", profileData.StunHitSound, sizeof(profileData.StunHitSound), profileData.StunHitSound); - TryPrecacheBossProfileSoundPath(profileData.StunHitSound, _, g_FileCheckConVar.BoolValue); - } - profileData.BleedEffects = !!kv.GetNum("player_bleed_on_hit", profileData.BleedEffects); - if (profileData.BleedEffects) - { - profileData.BleedIndexes = kv.GetNum("player_bleed_attack_indexs", profileData.BleedIndexes); - if (profileData.BleedIndexes < 0) + if (kv.JumpToKey("sound_heal_self")) { - profileData.BleedIndexes = 1; + rage1.HealSounds.Load(kv, g_FileCheckConVar.BoolValue); + rage2.HealSounds.Load(kv, g_FileCheckConVar.BoolValue); + rage3.HealSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); } - kv.GetString("player_bleed_attack_indexs", profileData.BleedIndexesString, sizeof(profileData.BleedIndexesString), profileData.BleedIndexesString); - GetProfileDifficultyFloatValues(kv, "player_bleed_duration", profileData.BleedDuration, profileData.BleedDuration); - } - profileData.ElectricEffects = !!kv.GetNum("player_electric_slow_on_hit", profileData.ElectricEffects); - if (profileData.ElectricEffects) - { - profileData.ElectricIndexes = kv.GetNum("player_electrocute_attack_indexs", profileData.ElectricIndexes); - if (profileData.ElectricIndexes < 0) + rage1.Animations.Init(); + rage2.Animations.Init(); + rage3.Animations.Init(); + rage1.Animations.Load(kv); + rage2.Animations.Load(kv); + rage3.Animations.Load(kv); + + StringMapSnapshot snapshot = rage1.Animations.Animations.Snapshot(); + SF2BossProfileAnimationSectionNameData animData; + char animType[64]; + ArrayList animDataArray; + for (int i = 0; i < snapshot.Length; i++) { - profileData.ElectricIndexes = 1; - } - kv.GetString("player_electrocute_attack_indexs", profileData.ElectricIndexesString, sizeof(profileData.ElectricIndexesString), profileData.ElectricIndexesString); - GetProfileDifficultyFloatValues(kv, "player_electric_slow_duration", profileData.ElectricDuration, profileData.ElectricDuration); - GetProfileDifficultyFloatValues(kv, "player_electric_slow_slowdown", profileData.ElectricSlowdown, profileData.ElectricSlowdown); - profileData.ElectricBeamParticle = !!kv.GetNum("player_electric_beam_particle", profileData.ElectricBeamParticle); - kv.GetString("player_electric_red_particle", profileData.ElectricParticleRed, sizeof(profileData.ElectricParticleRed), profileData.ElectricParticleRed); - kv.GetString("player_electric_blue_particle", profileData.ElectricParticleBlue, sizeof(profileData.ElectricParticleBlue), profileData.ElectricParticleBlue); - } + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "rage", false) == 0 || strcmp(animType, "heal", false) == 0 || strcmp(animType, "fleestart", false) == 0) + { + continue; + } - profileData.SmiteEffects = !!kv.GetNum("player_smite_on_hit", profileData.SmiteEffects); - if (profileData.SmiteEffects) - { - profileData.SmiteIndexes = kv.GetNum("player_smite_attack_indexs", profileData.SmiteIndexes); - if (profileData.SmiteIndexes < 0) + rage1.Animations.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + rage1.Animations.Animations.Remove(animType); + } + if (rage1.Animations.Animations.GetValue("rage", animDataArray) || rage1.Animations.Animations.GetValue("fleestart", animDataArray)) { - profileData.SmiteIndexes = 1; + rage1.Animations.Animations.SetValue("start", animDataArray); + rage1.Animations.Animations.Remove("rage"); + rage1.Animations.Animations.Remove("fleestart"); } - kv.GetString("player_smite_attack_indexs", profileData.SmiteIndexesString, sizeof(profileData.SmiteIndexesString), profileData.SmiteIndexesString); - profileData.SmiteDamage = kv.GetFloat("player_smite_damage", profileData.SmiteDamage); - profileData.SmiteDamageType = kv.GetNum("player_smite_damage_type", profileData.SmiteDamageType); - profileData.SmiteColor[0] = kv.GetNum("player_smite_color_r", profileData.SmiteColor[0]); - profileData.SmiteColor[1] = kv.GetNum("player_smite_color_g", profileData.SmiteColor[1]); - profileData.SmiteColor[2] = kv.GetNum("player_smite_color_b", profileData.SmiteColor[2]); - profileData.SmiteColor[3] = kv.GetNum("player_smite_transparency", profileData.SmiteColor[3]); - profileData.SmiteMessage = !!kv.GetNum("player_smite_message", profileData.SmiteMessage); - - kv.GetString("player_smite_sound", profileData.SmiteHitSound, sizeof(profileData.SmiteHitSound), profileData.SmiteHitSound); - } - } + if (rage1.Animations.Animations.GetValue("heal", animDataArray)) + { + rage1.Animations.Animations.SetValue("healing", animDataArray); + rage1.Animations.Animations.Remove("heal"); + } + delete snapshot; - profileData.XenobladeCombo = !!kv.GetNum("xenoblade_chain_art_combo", profileData.XenobladeCombo); - if (profileData.XenobladeCombo) - { - profileData.XenobladeDuration = kv.GetFloat("xenoblade_break_duration", profileData.XenobladeDuration); - profileData.XenobladeToppleDuration = kv.GetFloat("xenoblade_topple_duration", profileData.XenobladeToppleDuration); - profileData.XenobladeToppleSlowdown = kv.GetFloat("xenoblade_topple_slowdown", profileData.XenobladeToppleSlowdown); - profileData.XenobladeDazeDuration = kv.GetFloat("xenoblade_daze_duration", profileData.XenobladeDazeDuration); - } + snapshot = rage2.Animations.Animations.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "rage", false) == 0 || strcmp(animType, "heal", false) == 0 || strcmp(animType, "fleestart", false) == 0) + { + continue; + } - profileData.DamageParticles = !!kv.GetNum("damage_particle_effect_enabled", profileData.DamageParticles); - if (profileData.DamageParticles) - { - kv.GetString("damage_effect_particle", profileData.DamageParticleName, sizeof(profileData.DamageParticleName), profileData.DamageParticleName); - profileData.DamageParticleBeam = !!kv.GetNum("damage_effect_beam_particle", profileData.DamageParticleBeam); - kv.GetString("sound_damage_effect", profileData.DamageParticleSound, sizeof(profileData.DamageParticleSound), profileData.DamageParticleSound); - TryPrecacheBossProfileSoundPath(profileData.DamageParticleSound, _, g_FileCheckConVar.BoolValue); - } + rage2.Animations.Animations.GetValue(animType, animDataArray); - profileData.Shockwaves = !!kv.GetNum("shockwave", profileData.Shockwaves); - if (profileData.Shockwaves) - { - GetProfileDifficultyFloatValues(kv, "shockwave_height", profileData.ShockwaveHeight, profileData.ShockwaveHeight); - GetProfileDifficultyFloatValues(kv, "shockwave_range", profileData.ShockwaveRange, profileData.ShockwaveRange); - GetProfileDifficultyFloatValues(kv, "shockwave_drain", profileData.ShockwaveDrain, profileData.ShockwaveDrain); - GetProfileDifficultyFloatValues(kv, "shockwave_force", profileData.ShockwaveForce, profileData.ShockwaveForce); - - profileData.ShockwaveStun = !!kv.GetNum("shockwave_stun", profileData.ShockwaveStun); - GetProfileDifficultyFloatValues(kv, "shockwave_stun_duration", profileData.ShockwaveStunDuration, profileData.ShockwaveStunDuration); - GetProfileDifficultyFloatValues(kv, "shockwave_stun_slowdown", profileData.ShockwaveStunSlowdown, profileData.ShockwaveStunSlowdown); - - profileData.ShockwaveIndexes = kv.GetNum("shockwave_attack_index", profileData.ShockwaveIndexes); - if (profileData.ShockwaveIndexes < 1) - { - profileData.ShockwaveIndexes = 1; - } - kv.GetString("shockwave_attack_index", profileData.ShockwaveIndexesString, sizeof(profileData.ShockwaveIndexesString), profileData.ShockwaveIndexesString); - profileData.ShockwaveWidths[0] = kv.GetFloat("shockwave_width_1", profileData.ShockwaveWidths[0]); - profileData.ShockwaveWidths[1] = kv.GetFloat("shockwave_width_2", profileData.ShockwaveWidths[1]); - profileData.ShockwaveAmplitude = kv.GetFloat("shockwave_amplitude", profileData.ShockwaveAmplitude); - - float tempColor[3]; - tempColor[0] = float(profileData.ShockwaveColor1[0]); - tempColor[1] = float(profileData.ShockwaveColor1[1]); - tempColor[2] = float(profileData.ShockwaveColor1[2]); - kv.GetVector("shockwave_color_1", tempColor, tempColor); - profileData.ShockwaveColor1[0] = RoundToNearest(tempColor[0]); - profileData.ShockwaveColor1[1] = RoundToNearest(tempColor[1]); - profileData.ShockwaveColor1[2] = RoundToNearest(tempColor[2]); - - tempColor[0] = float(profileData.ShockwaveColor2[0]); - tempColor[1] = float(profileData.ShockwaveColor2[1]); - tempColor[2] = float(profileData.ShockwaveColor2[2]); - kv.GetVector("shockwave_color_2", tempColor, tempColor); - profileData.ShockwaveColor2[0] = RoundToNearest(tempColor[0]); - profileData.ShockwaveColor2[1] = RoundToNearest(tempColor[1]); - profileData.ShockwaveColor2[2] = RoundToNearest(tempColor[2]); - - profileData.ShockwaveAlpha1 = kv.GetNum("shockwave_alpha_1", profileData.ShockwaveAlpha1); - profileData.ShockwaveAlpha2 = kv.GetNum("shockwave_alpha_2", profileData.ShockwaveAlpha2); - - kv.GetString("shockwave_beam_sprite", profileData.ShockwaveBeamSprite, sizeof(profileData.ShockwaveBeamSprite), profileData.ShockwaveBeamSprite); - kv.GetString("shockwave_halo_sprite", profileData.ShockwaveHaloSprite, sizeof(profileData.ShockwaveHaloSprite), profileData.ShockwaveHaloSprite); - profileData.ShockwaveBeamModel = PrecacheModel(profileData.ShockwaveBeamSprite, true); - profileData.ShockwaveHaloModel = PrecacheModel(profileData.ShockwaveHaloSprite, true); - } + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + rage2.Animations.Animations.Remove(animType); + } + if (rage2.Animations.Animations.GetValue("rage", animDataArray) || rage1.Animations.Animations.GetValue("fleestart", animDataArray)) + { + rage2.Animations.Animations.SetValue("start", animDataArray); + rage2.Animations.Animations.Remove("rage"); + rage2.Animations.Animations.Remove("fleestart"); + } + if (rage2.Animations.Animations.GetValue("heal", animDataArray)) + { + rage2.Animations.Animations.SetValue("healing", animDataArray); + rage2.Animations.Animations.Remove("heal"); + } + delete snapshot; - profileData.EarthquakeFootsteps = !!kv.GetNum("earthquake_footsteps", profileData.EarthquakeFootsteps); - if (profileData.EarthquakeFootsteps) - { - profileData.EarthquakeFootstepAmplitude = kv.GetFloat("earthquake_footsteps_amplitude", profileData.EarthquakeFootstepAmplitude); - profileData.EarthquakeFootstepFrequency = kv.GetFloat("earthquake_footsteps_frequency", profileData.EarthquakeFootstepFrequency); - profileData.EarthquakeFootstepDuration = kv.GetFloat("earthquake_footsteps_duration", profileData.EarthquakeFootstepDuration); - profileData.EarthquakeFootstepRadius = kv.GetFloat("earthquake_footsteps_radius", profileData.EarthquakeFootstepRadius); - profileData.EarthquakeFootstepAirShake = !!kv.GetNum("earthquake_footsteps_airshake", profileData.EarthquakeFootstepAirShake); - } + snapshot = rage3.Animations.Animations.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "rage", false) == 0 || strcmp(animType, "heal", false) == 0 || strcmp(animType, "fleestart", false) == 0) + { + continue; + } - profileData.Traps = !!kv.GetNum("traps_enabled", profileData.Traps); - if (profileData.Traps) - { - profileData.TrapType = kv.GetNum("trap_type", profileData.TrapType); - GetProfileDifficultyFloatValues(kv, "trap_spawn_cooldown", profileData.TrapCooldown, profileData.TrapCooldown); - kv.GetString("trap_model", profileData.TrapModel, sizeof(profileData.TrapModel), profileData.TrapModel); - kv.GetString("trap_deploy_sound", profileData.TrapDeploySound, sizeof(profileData.TrapDeploySound), profileData.TrapDeploySound); - kv.GetString("trap_miss_sound", profileData.TrapMissSound, sizeof(profileData.TrapMissSound), profileData.TrapMissSound); - kv.GetString("trap_catch_sound", profileData.TrapCatchSound, sizeof(profileData.TrapCatchSound), profileData.TrapCatchSound); - kv.GetString("trap_animation_idle", profileData.TrapAnimIdle, sizeof(profileData.TrapAnimIdle), profileData.TrapAnimIdle); - kv.GetString("trap_animation_closed", profileData.TrapAnimClose, sizeof(profileData.TrapAnimClose), profileData.TrapAnimClose); - kv.GetString("trap_animation_open", profileData.TrapAnimOpen, sizeof(profileData.TrapAnimOpen), profileData.TrapAnimOpen); - TryPrecacheBossProfileSoundPath(profileData.TrapDeploySound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.TrapMissSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.TrapCatchSound, _, g_FileCheckConVar.BoolValue); + rage3.Animations.Animations.GetValue(animType, animDataArray); - if (strcmp(profileData.TrapModel, TRAP_MODEL, true) != 0) - { - if (!PrecacheModel(profileData.TrapModel, true)) + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + rage3.Animations.Animations.Remove(animType); + } + if (rage3.Animations.Animations.GetValue("rage", animDataArray) || rage1.Animations.Animations.GetValue("fleestart", animDataArray)) { - LogSF2Message("Trap model file %s failed to be loaded, likely does not exist. This will crash the server if not fixed.", profileData.TrapModel); + rage3.Animations.Animations.SetValue("start", animDataArray); + rage3.Animations.Animations.Remove("rage"); + rage3.Animations.Animations.Remove("fleestart"); } - else + if (rage3.Animations.Animations.GetValue("heal", animDataArray)) { - PrecacheModel2(profileData.TrapModel, _, _, g_FileCheckConVar.BoolValue); + rage3.Animations.Animations.SetValue("healing", animDataArray); + rage3.Animations.Animations.Remove("heal"); } + delete snapshot; + + profileData.Rages.PushArray(rage1, sizeof(rage1)); + profileData.Rages.PushArray(rage2, sizeof(rage2)); + profileData.Rages.PushArray(rage3, sizeof(rage3)); } } - profileData.AutoChaseEnabled = !!kv.GetNum("auto_chase_enabled", profileData.AutoChaseEnabled); - if (profileData.AutoChaseEnabled) - { - GetProfileDifficultyNumValues(kv, "auto_chase_sound_threshold", profileData.AutoChaseCount, profileData.AutoChaseCount); - GetProfileDifficultyNumValues(kv, "auto_chase_sound_add", profileData.AutoChaseAdd, profileData.AutoChaseAdd); - GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps", profileData.AutoChaseAddFootstep, profileData.AutoChaseAddFootstep); - GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps_loud", profileData.AutoChaseAddLoudFootstep, profileData.AutoChaseAddFootstep); - GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_voice", profileData.AutoChaseAddVoice, profileData.AutoChaseAddVoice); - GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_weapon", profileData.AutoChaseAddWeapon, profileData.AutoChaseAddWeapon); - } - profileData.AutoChaseSprinters = !!kv.GetNum("auto_chase_sprinters", profileData.AutoChaseSprinters); + profileData.Attacks = new ArrayList(sizeof(SF2ChaserBossProfileAttackData)); - profileData.ChasesEndlessly = !!KvGetNum(kv, "boss_chases_endlessly", profileData.ChasesEndlessly); + int attackNums = ParseChaserProfileAttacks(kv, profileData, baseData); - profileData.SelfHeal = !!kv.GetNum("self_heal_enabled", profileData.SelfHeal); - if (profileData.SelfHeal) + if (baseData.Flags & SFF_ATTACKPROPS) { - profileData.SelfHealPercentageStart = kv.GetFloat("health_percentage_to_heal", profileData.SelfHealPercentageStart); - if (profileData.SelfHealPercentageStart < 0.0) + profileData.AttackPropModels = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + if (kv.JumpToKey("attack_props_physics_models")) { - profileData.SelfHealPercentageStart = 0.0; + char key[64], model[PLATFORM_MAX_PATH]; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + kv.GetString(key, model, sizeof(model)); + if (model[0] == '\0') + { + break; + } + profileData.AttackPropModels.PushString(model); + } + kv.GoBack(); } - if (profileData.SelfHealPercentageStart > 0.999) + else { - profileData.SelfHealPercentageStart = 0.999; + delete profileData.AttackPropModels; } - profileData.SelfHealRecover[0] = kv.GetFloat("heal_percentage_one", profileData.SelfHealRecover[0]); - if (profileData.SelfHealRecover[0] < 0.0) + } + + if (profileData.NormalSoundHook) + { + profileData.IdleSounds.Channel = SNDCHAN_VOICE; + profileData.AlertSounds.Channel = SNDCHAN_VOICE; + profileData.ChasingSounds.Channel = SNDCHAN_VOICE; + profileData.ChaseInitialSounds.Channel = SNDCHAN_VOICE; + profileData.StunnedSounds.Channel = SNDCHAN_VOICE; + profileData.DeathSounds.Channel = SNDCHAN_VOICE; + profileData.TauntKillSounds.Channel = SNDCHAN_VOICE; + profileData.SmellSounds.Channel = SNDCHAN_VOICE; + profileData.SelfHealSounds.Channel = SNDCHAN_VOICE; + profileData.RageSounds1.Channel = SNDCHAN_VOICE; + profileData.RageSounds2.Channel = SNDCHAN_VOICE; + profileData.RageSounds3.Channel = SNDCHAN_VOICE; + } + + ArrayList validSections = new ArrayList(ByteCountToCells(128)); + + // We have to copy and paste here, the previous kv.GotoFirstSubKey() in profile_boss_functions.sp will somehow reset all values of these sound sections + if (kv.GotoFirstSubKey()) + { + char s2[64]; + + do { - profileData.SelfHealRecover[0] = 0.0; + kv.GetSectionName(s2, sizeof(s2)); + + if (validSections.FindString(s2) != -1) + { + continue; + } + + validSections.PushString(s2); + + if (!StrContains(s2, "sound_")) + { + profileData.SortSoundSections(kv, s2, g_FileCheckConVar.BoolValue); + } } - if (profileData.SelfHealRecover[0] > 1.0) + while (kv.GotoNextKey()); + + kv.GoBack(); + } + + delete validSections; + + if (attackNums > 0) + { + if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_Attack])) { - profileData.SelfHealRecover[0] = 1.0; + profileData.LoadNestedSoundSections(g_SlenderVoiceList[SF2BossSound_Attack], kv, g_FileCheckConVar.BoolValue, profileData.AttackSounds, profileData.NormalSoundHook, profileData); } - profileData.SelfHealRecover[1] = kv.GetFloat("heal_percentage_two", profileData.SelfHealRecover[1]); - if (profileData.SelfHealRecover[1] < 0.0) + if (kv.JumpToKey("sound_hitenemy")) { - profileData.SelfHealRecover[1] = 0.0; + profileData.LoadNestedSoundSections("sound_hitenemy", kv, g_FileCheckConVar.BoolValue, profileData.HitSounds, false, profileData); } - if (profileData.SelfHealRecover[1] > 1.0) + if (kv.JumpToKey("sound_missenemy")) { - profileData.SelfHealRecover[1] = 1.0; + profileData.LoadNestedSoundSections("sound_missenemy", kv, g_FileCheckConVar.BoolValue, profileData.MissSounds, false, profileData); } - profileData.SelfHealRecover[2] = kv.GetFloat("heal_percentage_three", profileData.SelfHealRecover[2]); - if (profileData.SelfHealRecover[2] < 0.0) + if (kv.JumpToKey("sound_bulletshoot")) { - profileData.SelfHealRecover[2] = 0.0; + profileData.LoadNestedSoundSections("sound_bulletshoot", kv, g_FileCheckConVar.BoolValue, profileData.BulletShootSounds, false, profileData); } - if (profileData.SelfHealRecover[2] > 1.0) + if (kv.JumpToKey("sound_attackshootprojectile")) { - profileData.SelfHealRecover[2] = 1.0; + profileData.LoadNestedSoundSections("sound_attackshootprojectile", kv, g_FileCheckConVar.BoolValue, profileData.ProjectileShootSounds, false, profileData); + } + if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_AttackBegin])) + { + profileData.LoadNestedSoundSections(g_SlenderVoiceList[SF2BossSound_AttackBegin], kv, g_FileCheckConVar.BoolValue, profileData.AttackBeginSounds, profileData.NormalSoundHook, profileData); + } + if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_AttackEnd])) + { + profileData.LoadNestedSoundSections(g_SlenderVoiceList[SF2BossSound_AttackEnd], kv, g_FileCheckConVar.BoolValue, profileData.AttackEndSounds, profileData.NormalSoundHook, profileData); + } + if (kv.JumpToKey("sound_attack_loop")) + { + profileData.LoadNestedSoundSections("sound_attack_loop", kv, g_FileCheckConVar.BoolValue, profileData.AttackLoopSounds, false, profileData); } - profileData.CloakToHeal = !!kv.GetNum("cloak_to_heal", profileData.CloakToHeal); - } - profileData.Crawling = !!kv.GetNum("crawling_enabled", profileData.Crawling); - if (profileData.Crawling) - { - GetProfileDifficultyFloatValues(kv, "crawl_multiplier", profileData.CrawlSpeedMultiplier, profileData.CrawlSpeedMultiplier); - kv.GetVector("crawl_detect_mins", profileData.CrawlDetectMins, profileData.CrawlDetectMins); - kv.GetVector("crawl_detect_maxs", profileData.CrawlDetectMaxs, profileData.CrawlDetectMaxs); - } + if (kv.GetNum("player_damage_effects", false) != 0) + { + if (kv.GetNum("player_damage_random_effects", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Random, _, attackNums); + } + + if (kv.GetNum("player_jarate_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Jarate, _, attackNums); + } + + if (kv.GetNum("player_milk_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Milk, _, attackNums); + } + + if (kv.GetNum("player_gas_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Gas, _, attackNums); + } + + if (kv.GetNum("player_mark_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Mark, _, attackNums); + } - profileData.ChaseOnLook = !!kv.GetNum("auto_chase_upon_look", profileData.ChaseOnLook); + if (kv.GetNum("player_silent_mark_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Mark, true, attackNums); + } - GetProfileDifficultyFloatValues(kv, "awareness_rate_increase", profileData.AwarenessIncreaseRate, profileData.AwarenessIncreaseRate); - GetProfileDifficultyFloatValues(kv, "awareness_rate_decrease", profileData.AwarenessDecreaseRate, profileData.AwarenessDecreaseRate); + if (kv.GetNum("player_ignite_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Ignite, _, attackNums); + } - profileData.BoxingBoss = !!kv.GetNum("boxing_boss", profileData.BoxingBoss); + if (kv.GetNum("player_stun_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Stun, _, attackNums); + } - profileData.NormalSoundHook = !!kv.GetNum("normal_sound_hook", profileData.NormalSoundHook); + if (kv.GetNum("player_bleed_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Bleed, _, attackNums); + } - profileData.ChaseInitialAnimations = !!kv.GetNum("use_chase_initial_animation", profileData.ChaseInitialAnimations); + if (kv.GetNum("player_electric_slow_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Stun, true, attackNums); + } - profileData.SpawnAnimationsEnabled = !!kv.GetNum("spawn_animation", profileData.SpawnAnimationsEnabled); + if (kv.GetNum("player_smite_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Smite, _, attackNums); + } + } - profileData.OldAnimationAI = !!kv.GetNum("old_animation_ai", profileData.OldAnimationAI); + if (kv.GetNum("shockwave", false) != 0) + { + char indexes[128], index[64][64], buffer[PLATFORM_MAX_PATH]; + kv.GetString("shockwave_attack_index", indexes, sizeof(indexes), "1"); + int nums = ExplodeString(indexes, " ", index, sizeof(index), sizeof(index)); - profileData.AlertWalkingAnimation = !!kv.GetNum("use_alert_walking_animation", profileData.AlertWalkingAnimation); + for (int i = 0; i < attackNums; i++) + { + SF2ChaserBossProfileAttackData attackData; + profileData.GetAttackFromIndex(i, attackData); - if (profileData.BoxingBoss) - { - profileData.HealAnimationTimer = kv.GetFloat("heal_timer_animation", profileData.HealAnimationTimer); - profileData.HealFunctionTimer = kv.GetFloat("heal_timer", profileData.HealFunctionTimer); - profileData.HealRangeMin = kv.GetFloat("heal_range_min", profileData.HealRangeMin); - profileData.HealRangeMax = kv.GetFloat("heal_range_max", profileData.HealRangeMax); - profileData.HealTimeMin = kv.GetFloat("heal_time_min", profileData.HealTimeMin); - profileData.HealTimeMax = kv.GetFloat("heal_range_max", profileData.HealTimeMax); - - profileData.AfterburnMultiplier = kv.GetFloat("fire_damage_multiplier", profileData.AfterburnMultiplier); - profileData.BackstabDamageScale = kv.GetFloat("backstab_damage_scale", profileData.BackstabDamageScale); - } + for (int i2 = 0; i2 < nums; i2++) + { + if (strcmp(attackData.Name, index[i2]) != 0) + { + continue; + } - profileData.Attacks = new ArrayList(sizeof(SF2ChaserBossProfileAttackData)); + attackData.Shockwave.Enabled = true; - int attackNums = ParseChaserProfileAttacks(kv, profileData); + GetProfileDifficultyFloatValues(kv, "shockwave_height", attackData.Shockwave.Height, attackData.Shockwave.Height); + GetProfileDifficultyFloatValues(kv, "shockwave_range", attackData.Shockwave.Radius, attackData.Shockwave.Radius); + GetProfileDifficultyFloatValues(kv, "shockwave_drain", attackData.Shockwave.BatteryDrainPercent, attackData.Shockwave.BatteryDrainPercent); + GetProfileDifficultyFloatValues(kv, "shockwave_force", attackData.Shockwave.Force, attackData.Shockwave.Force); - if (g_CachedProfileData.Flags & SFF_ATTACKPROPS) + if (kv.GetNum("shockwave_stun", false) != 0) + { + if (attackData.Shockwave.DamageEffects == null) + { + attackData.Shockwave.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + } + SF2ChaserBossProfileDamageEffectData damageData; + damageData.Init(); + damageData.SetType("stun"); + damageData.StunFlags = new ArrayList(ByteCountToCells(256)); + for (int i3 = 0; i3 < Difficulty_Max; i3++) + { + damageData.StunFlags.PushString("slow"); + } + + GetProfileDifficultyFloatValues(kv, "shockwave_stun_duration", damageData.Duration, damageData.Duration); + GetProfileDifficultyFloatValues(kv, "shockwave_stun_slowdown", damageData.StunSlowdown, damageData.StunSlowdown); + + attackData.Shockwave.DamageEffects.PushArray(damageData, sizeof(damageData)); + } + + if (attackData.Shockwave.Effects == null) + { + attackData.Shockwave.Effects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + } + SF2BossProfileBaseEffectInfo effect1; + effect1.Init(); + effect1.Type = EffectType_TempEntBeamRing; + effect1.Event = EffectEvent_Constant; + effect1.BeamRingWidth = kv.GetFloat("shockwave_width_1", 40.0); + effect1.BeamRingEndRadius = attackData.Shockwave.Radius[1]; + effect1.BeamRingSpeed = RoundToNearest(attackData.Shockwave.Radius[1]); + effect1.BeamRingAmplitude = kv.GetFloat("shockwave_amplitude", 5.0); + effect1.LifeTime = 0.3; + + kv.GetString("shockwave_beam_sprite", effect1.BeamRingBeamSprite, sizeof(effect1.BeamRingBeamSprite), effect1.BeamRingBeamSprite); + if (effect1.BeamRingBeamSprite[0] != '\0') + { + effect1.BeamRingBeamModel = PrecacheModel(effect1.BeamRingBeamSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", effect1.BeamRingBeamSprite); + AddFileToDownloadsTable(buffer); + } + + kv.GetString("shockwave_halo_sprite", effect1.BeamRingHaloSprite, sizeof(effect1.BeamRingHaloSprite), effect1.BeamRingHaloSprite); + if (effect1.BeamRingHaloSprite[0] != '\0') + { + effect1.BeamRingHaloModel = PrecacheModel(effect1.BeamRingHaloSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", effect1.BeamRingHaloSprite); + AddFileToDownloadsTable(buffer); + } + + char color[32]; + kv.GetString("shockwave_color_1", buffer, sizeof(buffer), "128 128 128"); + FormatEx(color, sizeof(color), "%s %i", buffer, kv.GetNum("shockwave_alpha_1", 255)); + strcopy(effect1.BeamRingColor, sizeof(effect1.BeamRingColor), color); + + effect1.BeamRingFrameRate = 30; + + attackData.Shockwave.Effects.PushArray(effect1, sizeof(effect1)); + + SF2BossProfileBaseEffectInfo effect2; + effect2.Init(); + effect2.Type = EffectType_TempEntBeamRing; + effect2.Event = EffectEvent_Constant; + effect2.BeamRingWidth = kv.GetFloat("shockwave_width_2", 20.0); + effect2.BeamRingEndRadius = attackData.Shockwave.Radius[1]; + effect2.BeamRingSpeed = RoundToNearest(attackData.Shockwave.Radius[1]); + effect2.BeamRingAmplitude = effect1.BeamRingAmplitude; + effect2.LifeTime = 0.2; + + strcopy(effect2.BeamRingBeamSprite, sizeof(effect2.BeamRingBeamSprite), effect1.BeamRingBeamSprite); + effect2.BeamRingBeamModel = effect1.BeamRingBeamModel; + + strcopy(effect2.BeamRingHaloSprite, sizeof(effect2.BeamRingHaloSprite), effect1.BeamRingHaloSprite); + effect2.BeamRingHaloModel = effect1.BeamRingHaloModel; + + kv.GetString("shockwave_color_2", buffer, sizeof(buffer), "255 255 255"); + FormatEx(color, sizeof(color), "%s %i", buffer, kv.GetNum("shockwave_alpha_2", 255)); + strcopy(effect2.BeamRingColor, sizeof(effect2.BeamRingColor), color); + + effect2.BeamRingFrameRate = 30; + + attackData.Shockwave.Effects.PushArray(effect2, sizeof(effect2)); + + profileData.Attacks.SetArray(i, attackData, sizeof(attackData)); + } + } + } + } + + if (kv.JumpToKey("postures")) { - profileData.AttackPropModels = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); - if (kv.JumpToKey("attack_props_physics_models")) + if (profileData.Postures == null) { - char key[64], model[PLATFORM_MAX_PATH]; - for (int i = 1;; i++) + profileData.Postures = new StringMap(); + } + + if (kv.GotoFirstSubKey()) + { + do { - FormatEx(key, sizeof(key), "%d", i); - kv.GetString(key, model, sizeof(model)); - if (model[0] == '\0') + SF2ChaserBossProfilePostureInfo posture; + posture.Init(); + for (int i = 0; i < Difficulty_Max; i++) { - break; + posture.Speed[i] = baseData.RunSpeed[i]; + posture.WalkSpeed[i] = profileData.WalkSpeed[i]; + posture.Acceleration[i] = baseData.Acceleration[i]; } - profileData.AttackPropModels.PushString(model); + posture.Load(kv); + profileData.Postures.SetArray(posture.Name, posture, sizeof(posture)); } + while (kv.GotoNextKey()); + kv.GoBack(); } - else + kv.GoBack(); + } + + if (kv.GetNum("use_alert_walking_animation", false) != 0) + { + if (profileData.Postures == null) { - delete profileData.AttackPropModels; + profileData.Postures = new StringMap(); + } + SF2ChaserBossProfilePostureInfo posture; + posture.Init(); + for (int i = 0; i < Difficulty_Max; i++) + { + posture.Speed[i] = baseData.RunSpeed[i]; + posture.WalkSpeed[i] = profileData.WalkSpeed[i]; + posture.Acceleration[i] = baseData.Acceleration[i]; + posture.AlertInfo.Enabled[i] = true; + } + posture.Name = "Legacy Walk Alert"; + posture.Animations.Load(kv); + StringMapSnapshot snapshot = posture.Animations.Animations.Snapshot(); + SF2BossProfileAnimationSectionNameData animData; + char animType[64]; + ArrayList animDataArray; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "walkalert", false) == 0) + { + continue; + } + posture.Animations.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + posture.Animations.Animations.Remove(animType); } + + if (posture.Animations.Animations.GetValue("walkalert", animDataArray)) + { + posture.Animations.Animations.SetValue("walk", animDataArray); + posture.Animations.Animations.Remove("walkalert"); + profileData.Postures.SetArray(posture.Name, posture, sizeof(posture)); + } + delete snapshot; } - if (profileData.NormalSoundHook) + profileData.PostLoad(); + + g_ChaserBossProfileData.SetArray(profile, profileData, sizeof(profileData)); + + return true; +} + +// #define THIS_IS_A_FUCKING_MESS +static void LoadLegacyEffects(KeyValues kv, SF2ChaserBossProfileData profileData, int type = SF2DamageType_Jarate, bool alt = false, int maxAttacks = 0) +{ + char key[64]; + + bool attach = kv.GetNum("player_attach_particle", true) != 0; + char flag[32]; + flag[0] = '\0'; + switch (kv.GetNum("player_stun_type", 0)) { - profileData.IdleSounds.Channel = SNDCHAN_VOICE; - profileData.AlertSounds.Channel = SNDCHAN_VOICE; - profileData.ChasingSounds.Channel = SNDCHAN_VOICE; - profileData.ChaseInitialSounds.Channel = SNDCHAN_VOICE; - profileData.StunnedSounds.Channel = SNDCHAN_VOICE; - profileData.RageAllSounds.Channel = SNDCHAN_VOICE; - profileData.RageTwoSounds.Channel = SNDCHAN_VOICE; - profileData.RageThreeSounds.Channel = SNDCHAN_VOICE; - profileData.SelfHealSounds.Channel = SNDCHAN_VOICE; + case 1: + { + flag = "slow"; + } + + case 2: + { + flag = "loser"; + } + + case 3: + { + flag = "no_fx stuck"; + } + + case 4: + { + flag = "boo"; + } } - // We have to copy and paste here, the previous kv.GotoFirstSubKey() in profile_boss_functions.sp will somehow reset all values of these sound sections - if (kv.GotoFirstSubKey()) + switch (type) { - char s2[64]; + case SF2DamageType_Bleed: + { + key = "player_bleed_attack_indexs"; + } - do + case SF2DamageType_Gas: { - kv.GetSectionName(s2, sizeof(s2)); + key = "player_gas_attack_indexs"; + } - if (!StrContains(s2, "sound_")) + case SF2DamageType_Ignite: + { + key = "player_ignite_attack_indexs"; + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_attack_indexs"; + } + + case SF2DamageType_Mark: + { + key = "player_mark_attack_indexs"; + if (alt) { - profileData.SortSoundSections(kv, s2, g_FileCheckConVar.BoolValue); + key = "player_silent_mark_attack_indexs"; } } - while (kv.GotoNextKey()); - kv.GoBack(); + case SF2DamageType_Milk: + { + key = "player_milk_attack_indexs"; + } + + case SF2DamageType_Random: + { + key = "player_random_attack_indexes"; + } + + case SF2DamageType_Smite: + { + key = "player_smite_attack_indexs"; + } + + case SF2DamageType_Stun: + { + key = "player_stun_attack_indexs"; + if (alt) + { + key = "player_electrocute_attack_indexs"; + } + } } - if (attackNums > 0) + char indexes[128], index[64][64]; + kv.GetString(key, indexes, sizeof(indexes), "1"); + int nums = ExplodeString(indexes, " ", index, sizeof(index), sizeof(index)); + + for (int i = 0; i < maxAttacks; i++) { - if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_Attack])) + SF2ChaserBossProfileAttackData attackData; + profileData.GetAttackFromIndex(i, attackData); + + for (int i2 = 0; i2 < nums; i2++) { - profileData.AttackSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) + if (strcmp(attackData.Name, index[i2]) != 0) { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) + continue; + } + + SF2ChaserBossProfileDamageEffectData damageEffect; + damageEffect.Init(); + switch (type) + { + case SF2DamageType_Bleed: { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - if (profileData.NormalSoundHook) + key = "player_bleed_attack_indexs"; + damageEffect.SetType("bleed"); + } + + case SF2DamageType_Gas: + { + key = "player_gas_attack_indexs"; + damageEffect.SetType("gas"); + } + + case SF2DamageType_Ignite: + { + key = "player_ignite_attack_indexs"; + damageEffect.SetType("ignite"); + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_attack_indexs"; + damageEffect.SetType("jarate"); + } + + case SF2DamageType_Mark: + { + key = "player_mark_attack_indexs"; + if (alt) { - soundInfo.Channel = SNDCHAN_VOICE; + key = "player_silent_mark_attack_indexs"; } - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) + damageEffect.SetType("mark"); + } + + case SF2DamageType_Milk: + { + key = "player_milk_attack_indexs"; + damageEffect.SetType("milk"); + } + + case SF2DamageType_Random: + { + key = "player_random_attack_indexes"; + damageEffect.SetType("random"); + damageEffect.Types.Push(SF2DamageType_Ignite); + damageEffect.Types.Push(SF2DamageType_Gas); + damageEffect.Types.Push(SF2DamageType_Bleed); + damageEffect.Types.Push(SF2DamageType_Mark); + damageEffect.Types.Push(SF2DamageType_Jarate); + damageEffect.Types.Push(SF2DamageType_Milk); + damageEffect.Types.Push(SF2DamageType_Stun); + } + + case SF2DamageType_Smite: + { + key = "player_smite_attack_indexs"; + damageEffect.SetType("smite"); + } + + case SF2DamageType_Stun: + { + key = "player_stun_attack_indexs"; + if (alt) { - profileData.AttackSounds.PushArray(soundInfo, sizeof(soundInfo)); + key = "player_electrocute_attack_indexs"; } + damageEffect.SetType("stun"); } - else + } + + if (attackData.DamageEffects == null) + { + attackData.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + } + + switch (damageEffect.Type) + { + case SF2DamageType_Bleed: + { + key = "player_bleed_duration"; + } + + case SF2DamageType_Gas: + { + key = "player_gas_duration"; + } + + case SF2DamageType_Ignite: + { + key = "player_ignite_duration"; + } + + case SF2DamageType_Jarate: { - profileData.AttackSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) + key = "player_jarate_duration"; + } + + case SF2DamageType_Mark: + { + key = "player_mark_duration"; + if (alt) { - SF2BossProfileSoundInfo tempInfo; - profileData.AttackSounds.SetArray(i, tempInfo, sizeof(tempInfo)); + key = "player_silent_mark_duration"; } - do + } + + case SF2DamageType_Milk: + { + key = "player_milk_duration"; + } + + case SF2DamageType_Random: + { + key = "player_random_duration"; + } + + case SF2DamageType_Stun: + { + key = "player_stun_duration"; + if (alt) { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - if (profileData.NormalSoundHook) - { - soundInfo.Channel = SNDCHAN_VOICE; - } - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.AttackSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } + key = "player_electric_slow_duration"; } - while (kv.GotoNextKey()); - kv.GoBack(); + } + + default: + { + key[0] = '\0'; } } - kv.GoBack(); - } - if (kv.JumpToKey("sound_hitenemy")) - { - profileData.HitSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) + + if (key[0] != '\0') { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) + GetProfileDifficultyFloatValues(kv, key, damageEffect.Duration, damageEffect.Duration); + } + + if (damageEffect.Type == SF2DamageType_Mark && alt) + { + for (int i3 = 0; i3 < Difficulty_Max; i3++) { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) + damageEffect.MarkSilent[i3] = true; + } + } + + if (damageEffect.Type == SF2DamageType_Stun) + { + if (alt) + { + key = "player_electric_slow_slowdown"; + } + else + { + key = "player_stun_slowdown"; + } + + if (damageEffect.StunFlags == null) + { + damageEffect.StunFlags = new ArrayList(ByteCountToCells(256)); + } + + GetProfileDifficultyFloatValues(kv, key, damageEffect.StunSlowdown, damageEffect.StunSlowdown); + + for (int i3 = 0; i3 < Difficulty_Max; i3++) + { + damageEffect.StunFlags.PushString(flag); + } + } + else if (damageEffect.Type == SF2DamageType_Random) + { + switch (kv.GetNum("player_random_stun_type", 0)) + { + case 1: + { + flag = "slow"; + } + + case 2: + { + flag = "loser"; + } + + case 3: + { + flag = "no_fx stuck"; + } + + case 4: { - profileData.HitSounds.PushArray(soundInfo, sizeof(soundInfo)); + flag = "boo"; } } - else + + if (damageEffect.StunFlags == null) + { + damageEffect.StunFlags = new ArrayList(ByteCountToCells(256)); + } + + GetProfileDifficultyFloatValues(kv, "player_random_slowdown", damageEffect.StunSlowdown, damageEffect.StunSlowdown); + + for (int i3 = 0; i3 < Difficulty_Max; i3++) { - profileData.HitSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) + damageEffect.StunFlags.PushString(flag); + } + } + + if (damageEffect.Type == SF2DamageType_Smite) + { + GetProfileDifficultyFloatValues(kv, "player_smite_damage", damageEffect.SmiteDamage, damageEffect.SmiteDamage); + GetProfileDifficultyNumValues(kv, "player_smite_damage_type", damageEffect.SmiteDamageType, damageEffect.SmiteDamageType); + + damageEffect.SmiteColor[0] = kv.GetNum("player_smite_color_r", 255); + damageEffect.SmiteColor[1] = kv.GetNum("player_smite_color_g", 255); + damageEffect.SmiteColor[2] = kv.GetNum("player_smite_color_b", 255); + damageEffect.SmiteColor[3] = kv.GetNum("player_smite_transparency", 255); + + damageEffect.SmiteMessage = kv.GetNum("player_smite_message", damageEffect.SmiteMessage) != 0; + + kv.GetString("hit_sound", damageEffect.SmiteHitSound, sizeof(damageEffect.SmiteHitSound), damageEffect.SmiteHitSound); + } + + bool create = false; + switch (damageEffect.Type) + { + case SF2DamageType_Gas, SF2DamageType_Jarate, SF2DamageType_Milk, SF2DamageType_Stun: + { + create = true; + } + } + + if (create) + { + switch (damageEffect.Type) + { + case SF2DamageType_Gas: + { + key = "player_gas_beam_particle"; + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_beam_particle"; + } + + case SF2DamageType_Milk: { - SF2BossProfileSoundInfo tempInfo; - profileData.HitSounds.SetArray(i, tempInfo, sizeof(tempInfo)); + key = "player_milk_beam_particle"; } - do + + case SF2DamageType_Stun: { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) + key = "player_stun_beam_particle"; + if (alt) { - profileData.HitSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); + key = "player_electric_beam_particle"; } } - while (kv.GotoNextKey()); - kv.GoBack(); } - } - kv.GoBack(); - } - if (kv.JumpToKey("sound_missenemy")) - { - profileData.MissSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) - { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) + + SF2ParticleData particle; + particle.Init(); + particle.BeamParticles = kv.GetNum(key, false) != 0; + particle.AttachParticles = attach; + + char particleName[128]; + switch (damageEffect.Type) { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) + case SF2DamageType_Gas: { - profileData.MissSounds.PushArray(soundInfo, sizeof(soundInfo)); + key = "player_gas_particle"; + particleName = "gas_can_impact_blue"; } - } - else - { - profileData.MissSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) + + case SF2DamageType_Jarate: { - SF2BossProfileSoundInfo tempInfo; - profileData.MissSounds.SetArray(i, tempInfo, sizeof(tempInfo)); + key = "player_jarate_particle"; + particleName = "peejar_impact"; } - do + + case SF2DamageType_Milk: + { + key = "player_milk_particle"; + particleName = "peejar_impact_milk"; + } + + case SF2DamageType_Stun: { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) + key = "player_stun_particle"; + particleName = "xms_icicle_melt"; + if (alt) { - profileData.MissSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); + key = "player_electric_red_particle"; + particleName = "electrocuted_gibbed_red"; } } - while (kv.GotoNextKey()); - kv.GoBack(); } - } - kv.GoBack(); - } - if (kv.JumpToKey("sound_bulletshoot")) - { - profileData.BulletShootSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) - { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) + + kv.GetString(key, particle.ParticleName, sizeof(particle.ParticleName), particleName); + + if (particleName[0] != '\0') { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) + if (damageEffect.Particles == null) { - profileData.BulletShootSounds.PushArray(soundInfo, sizeof(soundInfo)); + damageEffect.Particles = new ArrayList(sizeof(SF2ParticleData)); } + damageEffect.Particles.PushArray(particle, sizeof(particle)); } - else + + if (damageEffect.Sounds.Paths == null && !alt) + { + damageEffect.Sounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + } + + char sound[PLATFORM_MAX_PATH]; + switch (damageEffect.Type) { - profileData.BulletShootSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) + case SF2DamageType_Gas: { - SF2BossProfileSoundInfo tempInfo; - profileData.BulletShootSounds.SetArray(i, tempInfo, sizeof(tempInfo)); + key = "player_gas_sound"; + sound = "weapons/jar_single.wav"; } - do + + case SF2DamageType_Jarate: { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.BulletShootSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } + key = "player_jarate_sound"; + sound = "weapons/jar_single.wav"; } - while (kv.GotoNextKey()); - kv.GoBack(); - } - } - kv.GoBack(); - } - if (kv.JumpToKey("sound_attackshootprojectile")) - { - profileData.ProjectileShootSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) - { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) - { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) + + case SF2DamageType_Milk: { - profileData.ProjectileShootSounds.PushArray(soundInfo, sizeof(soundInfo)); + key = "player_milk_sound"; + sound = "weapons/jar_single.wav"; } - } - else - { - profileData.ProjectileShootSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) + + case SF2DamageType_Stun: { - SF2BossProfileSoundInfo tempInfo; - profileData.ProjectileShootSounds.SetArray(i, tempInfo, sizeof(tempInfo)); + key = "player_stun_sound"; + sound = "weapons/icicle_freeze_victim_01.wav"; } - do + } + + if (!alt) + { + kv.GetString(key, sound, sizeof(sound), sound); + if (sound[0] != '\0') { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.ProjectileShootSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } + TryPrecacheBossProfileSoundPath(sound, g_FileCheckConVar.BoolValue); + damageEffect.Sounds.Paths.PushString(sound); + damageEffect.Sounds.PostLoad(); } - while (kv.GotoNextKey()); - kv.GoBack(); } } - kv.GoBack(); - } - char sectionName[128]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - } - - profileData.PostLoad(); - g_ChaserBossProfileData.SetArray(profile, profileData, sizeof(profileData)); + attackData.DamageEffects.PushArray(damageEffect); - return true; + profileData.Attacks.SetArray(i, attackData, sizeof(attackData)); + } + } } -static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chaserProfileData) +static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chaserProfileData, SF2BossProfileData baseData) { // Create the array. ArrayList attacks = chaserProfileData.Attacks; - int maxAttacks = -1; - if (kv.JumpToKey("attacks")) + bool oldStuff = false; + if (!kv.JumpToKey("attacks")) { - maxAttacks = 0; - char num[3]; - for (int i = 1; i <= SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - FormatEx(num, sizeof(num), "%d", i); - if (kv.JumpToKey(num)) - { - maxAttacks++; - kv.GoBack(); - } - } - if (maxAttacks == 0) + oldStuff = true; + if (kv.GetDataType("attack_damage") == KvData_None) { - LogSF2Message("[SF2 PROFILES PARSER] Critical error, found \"attacks\" section with no attacks inside of it!"); + return -1; } } - if (maxAttacks <= -1) + + if (!oldStuff && !kv.GotoFirstSubKey()) { + LogSF2Message("[SF2 PROFILES PARSER] Critical error, found \"attacks\" section with no attacks inside of it!"); + kv.GoBack(); return -1; } - else - { - attacks.Resize(maxAttacks); - for (int i = 0; i < maxAttacks; i++) - { - attacks.Set(i, SF2BossAttackType_Invalid, SF2ChaserBossProfileAttackData::Type); - } - } - char num[3]; - for (int attackNum = -1; attackNum <=maxAttacks; attackNum++) + + char attackName[64]; + int index = 0; + do { - if (attackNum < 1) - { - attackNum = 1; - } - FormatEx(num, sizeof(num), "%d", attackNum); - kv.JumpToKey(num); + kv.GetSectionName(attackName, sizeof(attackName)); + SF2ChaserBossProfileAttackData attackData; attackData.Init(); - attackData.Type = kv.GetNum("type", attackData.Type); - - GetProfileDifficultyFloatValues(kv, "range", attackData.Range, attackData.Range); + attackData.Index = index; + attackData.Name = attackName; - switch (attackData.Type) + if (!oldStuff) { - case SF2BossAttackType_Melee, SF2BossAttackType_Custom: - { - GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); + attackData.Type = kv.GetNum("type", attackData.Type); + GetProfileDifficultyFloatValues(kv, "range", attackData.Range, attackData.Range); + GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); + GetProfileDifficultyFloatValues(kv, "damage_percent", attackData.DamagePercent, attackData.DamagePercent); + attackData.DamageVsProps = kv.GetFloat("damage_vs_props", attackData.DamageVsProps); + GetProfileDifficultyFloatValues(kv, "damageforce", attackData.DamageForce, attackData.DamageForce); + GetProfileDifficultyNumValues(kv, "damagetype", attackData.DamageType, attackData.DamageType); - attackData.DamageVsProps = kv.GetFloat("damage_vs_props", attackData.DamageVsProps); - GetProfileDifficultyFloatValues(kv, "damageforce", attackData.DamageForce, attackData.DamageForce); + attackData.CanUseAgainstProps = kv.GetNum("props", attackData.CanUseAgainstProps) != 0; - GetProfileDifficultyNumValues(kv, "damagetype", attackData.DamageType, attackData.DamageType); + GetProfileDifficultyFloatValues(kv, "delay", attackData.DamageDelay, attackData.DamageDelay); - attackData.CanUseAgainstProps = !!kv.GetNum("props", attackData.CanUseAgainstProps); + kv.GetVector("punchvel", attackData.PunchVelocity, attackData.PunchVelocity); - kv.GetVector("punchvel", attackData.PunchVelocity, attackData.PunchVelocity); + GetProfileDifficultyFloatValues(kv, "duration", attackData.Duration, attackData.Duration); - GetProfileDifficultyBoolValues(kv, "lifesteal", attackData.LifeSteal, attackData.LifeSteal); - GetProfileDifficultyFloatValues(kv, "lifesteal_duration", attackData.LifeStealDuration, attackData.LifeStealDuration); + GetProfileDifficultyFloatValues(kv, "fov", attackData.Spread, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "spread", attackData.Spread, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "begin_range", attackData.BeginRange, attackData.BeginRange); + GetProfileDifficultyFloatValues(kv, "begin_fov", attackData.BeginFOV, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "cooldown", attackData.Cooldown, attackData.Cooldown); - attackData.PullIn = !!kv.GetNum("pull_player_in", attackData.PullIn); + GetProfileDifficultyBoolValues(kv, "disappear", attackData.Disappear, attackData.Disappear); + GetProfileDifficultyBoolValues(kv, "disappear_upon_damaging", attackData.DisappearOnHit, attackData.DisappearOnHit); + } + attackData.Type = kv.GetNum("attack_type", attackData.Type); - attackData.DeathCamLowHealth = !!kv.GetNum("deathcam_on_low_health", attackData.DeathCamLowHealth); - } - case SF2BossAttackType_Ranged: - { - GetProfileDifficultyNumValues(kv, "bullet_count", attackData.BulletCount, attackData.BulletCount); - GetProfileDifficultyFloatValues(kv, "bullet_damage", attackData.BulletDamage, attackData.BulletDamage); - GetProfileDifficultyFloatValues(kv, "bullet_spread", attackData.BulletSpread, attackData.BulletSpread); - kv.GetVector("bullet_offset", attackData.BulletOffset, attackData.BulletOffset); - kv.GetString("bullet_tracer", attackData.BulletTrace, sizeof(attackData.BulletTrace), attackData.BulletTrace); - } - case SF2BossAttackType_Projectile: - { - GetProfileDifficultyFloatValues(kv, "projectile_damage", attackData.ProjectileDamage, attackData.ProjectileDamage); - GetProfileDifficultyFloatValues(kv, "projectile_speed", attackData.ProjectileSpeed, attackData.ProjectileSpeed); - GetProfileDifficultyFloatValues(kv, "projectile_radius", attackData.ProjectileRadius, attackData.ProjectileRadius); - GetProfileDifficultyFloatValues(kv, "projectile_deviation", attackData.ProjectileDeviation, attackData.ProjectileDeviation); - GetProfileDifficultyBoolValues(kv, "projectile_crits", attackData.CritProjectiles, attackData.CritProjectiles); - GetProfileDifficultyFloatValues(kv, "projectile_iceslow_percent", attackData.IceballSlowdownPercent, attackData.IceballSlowdownPercent); - GetProfileDifficultyFloatValues(kv, "projectile_iceslow_duration", attackData.IceballSlowdownDuration, attackData.IceballSlowdownDuration); - GetProfileDifficultyNumValues(kv, "projectile_count", attackData.ProjectileCount, attackData.ProjectileCount); - attackData.ProjectileType = kv.GetNum("projectiletype", attackData.ProjectileType); - kv.GetVector("projectile_offset", attackData.ProjectileOffset, attackData.ProjectileOffset); - kv.GetString("fire_trail", attackData.FireballTrail, sizeof(attackData.FireballTrail), attackData.FireballTrail); - kv.GetString("fire_iceball_trail", attackData.IceballTrail, sizeof(attackData.IceballTrail), attackData.IceballTrail); - kv.GetString("rocket_model", attackData.RocketModel, sizeof(attackData.RocketModel), attackData.RocketModel); + GetProfileDifficultyFloatValues(kv, "attack_range", attackData.Range, attackData.Range); - if (strcmp(attackData.RocketModel, ROCKET_MODEL, true) != 0) - { - if (!PrecacheModel(attackData.RocketModel, true)) - { - LogSF2Message("Attack rocket model file %s on attack index %i failed to be loaded, likely does not exist. This will crash the server if not fixed.", attackData.RocketModel, attackNum); - } - else - { - PrecacheModel2(attackData.RocketModel, _, _, g_FileCheckConVar.BoolValue); - } - } - } - case SF2BossAttackType_ExplosiveDance: - { - GetProfileDifficultyNumValues(kv, "explosivedance_radius", attackData.ExplosiveDanceRadius, attackData.ExplosiveDanceRadius); - } - case SF2BossAttackType_LaserBeam: - { - GetProfileDifficultyFloatValues(kv, "laser_damage", attackData.LaserDamage, attackData.LaserDamage); - attackData.LaserSize = kv.GetFloat("laser_size", attackData.LaserSize); - if (attackData.LaserSize < 0.0) - { - attackData.LaserSize = 0.0; - } - attackData.LaserColor[0] = kv.GetNum("laser_color_r", attackData.LaserColor[0]); - attackData.LaserColor[1] = kv.GetNum("laser_color_g", attackData.LaserColor[1]); - attackData.LaserColor[2] = kv.GetNum("laser_color_b", attackData.LaserColor[2]); - attackData.LaserAttachment = !!kv.GetNum("laser_attachment", attackData.LaserAttachment); - if (attackData.LaserAttachment) - { - kv.GetString("laser_attachment_name", attackData.LaserAttachmentName, sizeof(attackData.LaserAttachmentName), attackData.LaserAttachmentName); - } - else - { - kv.GetVector("laser_offset", attackData.LaserOffset, attackData.LaserOffset); - } - GetProfileDifficultyFloatValues(kv, "laser_duration", attackData.LaserDuration, attackData.LaserDuration); - attackData.LaserNoise = kv.GetFloat("laser_noise", attackData.LaserNoise); - } - } + GetProfileDifficultyFloatValues(kv, "attack_damage", attackData.Damage, attackData.Damage); - GetProfileDifficultyBoolValues(kv, "dont_interrupt_chaseinitial", attackData.DontInterruptChaseInitial, attackData.DontInterruptChaseInitial); + attackData.DamageVsProps = kv.GetFloat("attack_damage_vs_props", attackData.DamageVsProps); + GetProfileDifficultyFloatValues(kv, "attack_damageforce", attackData.DamageForce, attackData.DamageForce); + + GetProfileDifficultyNumValues(kv, "attack_damagetype", attackData.DamageType, attackData.DamageType); - GetProfileDifficultyFloatValues(kv, "delay", attackData.DamageDelay, attackData.DamageDelay); + attackData.CanUseAgainstProps = kv.GetNum("attack_props", attackData.CanUseAgainstProps) != 0; - GetProfileDifficultyFloatValues(kv, "duration", attackData.Duration, attackData.Duration); + kv.GetVector("attack_punchvel", attackData.PunchVelocity, attackData.PunchVelocity); + + GetProfileDifficultyFloatValues(kv, "attack_delay", attackData.DamageDelay, attackData.DamageDelay); + + GetProfileDifficultyFloatValues(kv, "attack_duration", attackData.Duration, attackData.Duration); bool backwardsCompatibility = true; for (int i = 0; i < Difficulty_Max; i++) { @@ -1192,23 +1732,131 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas if (backwardsCompatibility) // Backward compatibility { float endAfter[Difficulty_Max]; - GetProfileDifficultyFloatValues(kv, "endafter", endAfter, endAfter); + if (!oldStuff) + { + GetProfileDifficultyFloatValues(kv, "endafter", endAfter, endAfter); + } + GetProfileDifficultyFloatValues(kv, "attack_endafter", endAfter, endAfter); for (int i = 0; i < Difficulty_Max; i++) { attackData.Duration[i] = attackData.DamageDelay[i] + endAfter[i]; } } - GetProfileDifficultyFloatValues(kv, "fov", attackData.Spread, attackData.Spread); - GetProfileDifficultyFloatValues(kv, "spread", attackData.Spread, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "attack_fov", attackData.Spread, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "attack_spread", attackData.Spread, attackData.Spread); - GetProfileDifficultyFloatValues(kv, "begin_range", attackData.BeginRange, attackData.Range); + GetProfileDifficultyFloatValues(kv, "attack_begin_range", attackData.BeginRange, attackData.BeginRange); - GetProfileDifficultyFloatValues(kv, "begin_fov", attackData.BeginFOV, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "attack_begin_fov", attackData.BeginFOV, attackData.Spread); - GetProfileDifficultyFloatValues(kv, "cooldown", attackData.Cooldown, attackData.Cooldown); + GetProfileDifficultyFloatValues(kv, "attack_cooldown", attackData.Cooldown, attackData.Cooldown); + + GetProfileDifficultyBoolValues(kv, "attack_disappear_upon_damaging", attackData.Disappear, attackData.Disappear); + + if (oldStuff) + { + attacks.PushArray(attackData, sizeof(attackData)); + index++; + continue; + } + + GetProfileDifficultyBoolValues(kv, "lifesteal", attackData.LifeSteal, attackData.LifeSteal); + GetProfileDifficultyBoolValues(kv, "attack_lifesteal", attackData.LifeSteal, attackData.LifeSteal); + GetProfileDifficultyFloatValues(kv, "lifesteal_duration", attackData.LifeStealDuration, attackData.LifeStealDuration); + GetProfileDifficultyFloatValues(kv, "attack_lifesteal_duration", attackData.LifeStealDuration, attackData.LifeStealDuration); + + attackData.PullIn = kv.GetNum("pull_player_in", attackData.PullIn) != 0; + + attackData.DeathCamLowHealth = kv.GetNum("deathcam_on_low_health", attackData.DeathCamLowHealth) != 0; + attackData.DeathCamLowHealth = kv.GetNum("attack_deathcam_on_low_health", attackData.DeathCamLowHealth) != 0; + + GetProfileDifficultyNumValues(kv, "bullet_count", attackData.BulletCount, attackData.BulletCount); + GetProfileDifficultyNumValues(kv, "attack_bullet_count", attackData.BulletCount, attackData.BulletCount); + GetProfileDifficultyFloatValues(kv, "bullet_damage", attackData.BulletDamage, attackData.BulletDamage); + GetProfileDifficultyFloatValues(kv, "attack_bullet_damage", attackData.BulletDamage, attackData.BulletDamage); + GetProfileDifficultyFloatValues(kv, "bullet_spread", attackData.BulletSpread, attackData.BulletSpread); + GetProfileDifficultyFloatValues(kv, "attack_bullet_spread", attackData.BulletSpread, attackData.BulletSpread); + kv.GetVector("bullet_offset", attackData.BulletOffset, attackData.BulletOffset); + kv.GetVector("attack_bullet_offset", attackData.BulletOffset, attackData.BulletOffset); + kv.GetString("bullet_tracer", attackData.BulletTrace, sizeof(attackData.BulletTrace), attackData.BulletTrace); + kv.GetString("attack_bullet_tracer", attackData.BulletTrace, sizeof(attackData.BulletTrace), attackData.BulletTrace); + + GetProfileDifficultyFloatValues(kv, "projectile_damage", attackData.ProjectileDamage, attackData.ProjectileDamage); + GetProfileDifficultyFloatValues(kv, "attack_projectile_damage", attackData.ProjectileDamage, attackData.ProjectileDamage); + GetProfileDifficultyFloatValues(kv, "projectile_speed", attackData.ProjectileSpeed, attackData.ProjectileSpeed); + GetProfileDifficultyFloatValues(kv, "attack_projectile_speed", attackData.ProjectileSpeed, attackData.ProjectileSpeed); + GetProfileDifficultyFloatValues(kv, "projectile_radius", attackData.ProjectileRadius, attackData.ProjectileRadius); + GetProfileDifficultyFloatValues(kv, "attack_projectile_radius", attackData.ProjectileRadius, attackData.ProjectileRadius); + GetProfileDifficultyFloatValues(kv, "projectile_deviation", attackData.ProjectileDeviation, attackData.ProjectileDeviation); + GetProfileDifficultyFloatValues(kv, "attack_projectile_deviation", attackData.ProjectileDeviation, attackData.ProjectileDeviation); + GetProfileDifficultyBoolValues(kv, "projectile_crits", attackData.CritProjectiles, attackData.CritProjectiles); + GetProfileDifficultyBoolValues(kv, "attack_projectile_crits", attackData.CritProjectiles, attackData.CritProjectiles); + GetProfileDifficultyFloatValues(kv, "projectile_iceslow_percent", attackData.IceballSlowdownPercent, attackData.IceballSlowdownPercent); + GetProfileDifficultyFloatValues(kv, "attack_projectile_iceslow_percent", attackData.IceballSlowdownPercent, attackData.IceballSlowdownPercent); + GetProfileDifficultyFloatValues(kv, "projectile_iceslow_duration", attackData.IceballSlowdownDuration, attackData.IceballSlowdownDuration); + GetProfileDifficultyFloatValues(kv, "attack_projectile_iceslow_duration", attackData.IceballSlowdownDuration, attackData.IceballSlowdownDuration); + GetProfileDifficultyNumValues(kv, "projectile_count", attackData.ProjectileCount, attackData.ProjectileCount); + GetProfileDifficultyNumValues(kv, "attack_projectile_count", attackData.ProjectileCount, attackData.ProjectileCount); + attackData.ProjectileType = kv.GetNum("projectiletype", attackData.ProjectileType); + attackData.ProjectileType = kv.GetNum("attack_projectiletype", attackData.ProjectileType); + kv.GetVector("projectile_offset", attackData.ProjectileOffset, attackData.ProjectileOffset); + kv.GetVector("attack_projectile_offset", attackData.ProjectileOffset, attackData.ProjectileOffset); + kv.GetString("fire_trail", attackData.FireballTrail, sizeof(attackData.FireballTrail), attackData.FireballTrail); + kv.GetString("fire_iceball_trail", attackData.IceballTrail, sizeof(attackData.IceballTrail), attackData.IceballTrail); + kv.GetString("rocket_model", attackData.RocketModel, sizeof(attackData.RocketModel), attackData.RocketModel); + + if (strcmp(attackData.RocketModel, ROCKET_MODEL, true) != 0) + { + if (!PrecacheModel(attackData.RocketModel, true)) + { + LogSF2Message("Attack rocket model file %s on attack name %s failed to be loaded, likely does not exist. This will crash the server if not fixed.", attackData.RocketModel, attackName); + } + else + { + PrecacheModel2(attackData.RocketModel, _, _, g_FileCheckConVar.BoolValue); + } + } + + GetProfileDifficultyFloatValues(kv, "explosivedance_radius", attackData.ExplosiveDanceRadius, attackData.ExplosiveDanceRadius); + GetProfileDifficultyFloatValues(kv, "attack_explosivedance_radius", attackData.ExplosiveDanceRadius, attackData.ExplosiveDanceRadius); + + GetProfileDifficultyFloatValues(kv, "laser_damage", attackData.LaserDamage, attackData.LaserDamage); + GetProfileDifficultyFloatValues(kv, "attack_laser_damage", attackData.LaserDamage, attackData.LaserDamage); + attackData.LaserSize = kv.GetFloat("laser_size", attackData.LaserSize); + attackData.LaserSize = kv.GetFloat("attack_laser_size", attackData.LaserSize); + if (attackData.LaserSize < 0.0) + { + attackData.LaserSize = 0.0; + } + attackData.LaserColor[0] = kv.GetNum("laser_color_r", attackData.LaserColor[0]); + attackData.LaserColor[0] = kv.GetNum("attack_laser_color_r", attackData.LaserColor[0]); + attackData.LaserColor[1] = kv.GetNum("laser_color_g", attackData.LaserColor[1]); + attackData.LaserColor[1] = kv.GetNum("attack_laser_color_g", attackData.LaserColor[1]); + attackData.LaserColor[2] = kv.GetNum("laser_color_b", attackData.LaserColor[2]); + attackData.LaserColor[2] = kv.GetNum("attack_laser_color_b", attackData.LaserColor[2]); + attackData.LaserAttachment = kv.GetNum("laser_attachment", attackData.LaserAttachment) != 0; + attackData.LaserAttachment = kv.GetNum("attack_laser_attachment", attackData.LaserAttachment) != 0; + if (attackData.LaserAttachment) + { + kv.GetString("laser_attachment_name", attackData.LaserAttachmentName, sizeof(attackData.LaserAttachmentName), attackData.LaserAttachmentName); + kv.GetString("attack_laser_attachment_name", attackData.LaserAttachmentName, sizeof(attackData.LaserAttachmentName), attackData.LaserAttachmentName); + } + else + { + kv.GetVector("laser_offset", attackData.LaserOffset, attackData.LaserOffset); + kv.GetVector("attack_laser_offset", attackData.LaserOffset, attackData.LaserOffset); + } + GetProfileDifficultyFloatValues(kv, "laser_duration", attackData.LaserDuration, attackData.LaserDuration); + GetProfileDifficultyFloatValues(kv, "attack_laser_duration", attackData.LaserDuration, attackData.LaserDuration); + attackData.LaserNoise = kv.GetFloat("laser_noise", attackData.LaserNoise); + attackData.LaserNoise = kv.GetFloat("attack_laser_noise", attackData.LaserNoise); + GetProfileDifficultyFloatValues(kv, "laser_tick_delay", attackData.LaserTicks, attackData.LaserTicks); + + GetProfileDifficultyBoolValues(kv, "dont_interrupt_chaseinitial", attackData.DontInterruptChaseInitial, attackData.DontInterruptChaseInitial); attackData.Repeat = kv.GetNum("repeat", attackData.Repeat); + attackData.Repeat = kv.GetNum("attack_repeat", attackData.Repeat); if (attackData.Repeat < 0) { attackData.Repeat = 0; @@ -1218,6 +1866,7 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas attackData.Repeat = 2; } attackData.MaxRepeats = kv.GetNum("max_repeats", attackData.MaxRepeats); + attackData.MaxRepeats = kv.GetNum("attack_max_repeats", attackData.MaxRepeats); if (attackData.MaxRepeats < 0) { attackData.MaxRepeats = 0; @@ -1231,49 +1880,238 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas { FormatEx(attackBuffer, sizeof(attackBuffer), "repeat_%i_delay", repeats + 1); delay = kv.GetFloat(attackBuffer, delay); + FormatEx(attackBuffer, sizeof(attackBuffer), "attack_repeat_%i_delay", repeats + 1); + delay = kv.GetFloat(attackBuffer, delay); attackData.RepeatTimers.Push(delay); } } GetProfileDifficultyBoolValues(kv, "ignore_always_looking", attackData.IgnoreAlwaysLooking, attackData.IgnoreAlwaysLooking); - GetProfileDifficultyBoolValues(kv, "disappear_upon_damaging", attackData.Disappear, attackData.Disappear); + GetProfileDifficultyBoolValues(kv, "attack_ignore_always_looking", attackData.IgnoreAlwaysLooking, attackData.IgnoreAlwaysLooking); + + GetProfileDifficultyBoolValues(kv, "start_through_walls", attackData.StartThroughWalls, attackData.StartThroughWalls); + GetProfileDifficultyBoolValues(kv, "hit_through_walls", attackData.HitThroughWalls, attackData.HitThroughWalls); attackData.WeaponInt = kv.GetNum("weapontypeint", attackData.WeaponInt); + attackData.WeaponInt = kv.GetNum("attack_weapontypeint", attackData.WeaponInt); if (attackData.WeaponInt < 0) { attackData.WeaponInt = 0; } + kv.GetString("attack_weapontype", attackData.WeaponString, sizeof(attackData.WeaponString), attackData.WeaponString); kv.GetString("weapontype", attackData.WeaponString, sizeof(attackData.WeaponString), attackData.WeaponString); - GetProfileDifficultyBoolValues(kv, "run_enabled", attackData.AttackWhileRunning, attackData.AttackWhileRunning); GetProfileDifficultyFloatValues(kv, "run_speed", attackData.RunSpeed, attackData.RunSpeed); + GetProfileDifficultyFloatValues(kv, "attack_run_speed", attackData.RunSpeed, attackData.RunSpeed); GetProfileDifficultyFloatValues(kv, "run_duration", attackData.RunDuration, attackData.RunDuration); + GetProfileDifficultyFloatValues(kv, "attack_run_duration", attackData.RunDuration, attackData.RunDuration); GetProfileDifficultyFloatValues(kv, "run_delay", attackData.RunDelay, attackData.RunDelay); + GetProfileDifficultyFloatValues(kv, "attack_run_delay", attackData.RunDelay, attackData.RunDelay); + for (int i = 0; i < Difficulty_Max; i++) + { + attackData.RunAcceleration[i] = baseData.Acceleration[i]; + } + GetProfileDifficultyFloatValues(kv, "run_acceleration", attackData.RunAcceleration, attackData.RunAcceleration); + GetProfileDifficultyBoolValues(kv, "run_ground_speed", attackData.RunGroundSpeed, attackData.RunGroundSpeed); attackData.UseOnDifficulty = kv.GetNum("use_on_difficulty", attackData.UseOnDifficulty); + attackData.UseOnDifficulty = kv.GetNum("attack_use_on_difficulty", attackData.UseOnDifficulty); attackData.BlockOnDifficulty = kv.GetNum("block_on_difficulty", attackData.BlockOnDifficulty); + attackData.BlockOnDifficulty = kv.GetNum("attack_block_on_difficulty", attackData.BlockOnDifficulty); attackData.UseOnHealth = kv.GetFloat("use_on_health", attackData.UseOnHealth); + attackData.UseOnHealth = kv.GetFloat("attack_use_on_health", attackData.UseOnHealth); attackData.BlockOnHealth = kv.GetFloat("block_on_health", attackData.BlockOnHealth); + attackData.BlockOnHealth = kv.GetFloat("attack_block_on_health", attackData.BlockOnHealth); - attackData.Gestures = !!kv.GetNum("gestures", attackData.Gestures); + attackData.Gestures = kv.GetNum("gestures", attackData.Gestures) != 0; + attackData.Gestures = kv.GetNum("attack_gestures", attackData.Gestures) != 0; GetProfileDifficultyBoolValues(kv, "cancel_los", attackData.CancelLos, attackData.CancelLos); - GetProfileDifficultyFloatValues(kv, "cancel_distance", attackData.CancelDistance, attackData.CancelDistance); + GetProfileDifficultyFloatValues(kv, "cancel_distance_max", attackData.CancelDistance, attackData.CancelDistance); + GetProfileDifficultyFloatValues(kv, "cancel_distance_min", attackData.MinCancelDistance, attackData.MinCancelDistance); - attacks.SetArray(attackNum - 1, attackData, sizeof(attackData)); + attackData.EventNumber = kv.GetNum("event", attackData.EventNumber); - if (maxAttacks > 0) // Backward compatibility + kv.GetString("subtype", attackData.SubType, sizeof(attackData.SubType), attackData.SubType); + + if (kv.JumpToKey("shockwave")) { + attackData.Shockwave.Load(kv, g_FileCheckConVar.BoolValue); kv.GoBack(); } - else + + if (kv.JumpToKey("effects")) + { + if (kv.JumpToKey("start")) + { + attackData.StartEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.StartEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("hit")) + { + attackData.HitEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.HitEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("miss")) + { + attackData.MissEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.MissEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("on_kill")) + { + attackData.KillEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.KillEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + kv.GoBack(); + } + + if (kv.JumpToKey("apply_conditions")) + { + attackData.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + SF2ChaserBossProfileDamageEffectData damageData; + damageData.Init(); + kv.GetSectionName(section, sizeof(section)); + if (!damageData.SetType(section)) + { + continue; + } + damageData.Load(kv, g_FileCheckConVar.BoolValue); + attackData.DamageEffects.PushArray(damageData, sizeof(damageData)); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + + GetProfileDifficultyBoolValues(kv, "invulnerable", attackData.ImmuneToDamage, attackData.ImmuneToDamage); + + if (kv.JumpToKey("use_with_posture")) { - break; + attackData.PostureWhitelist = new ArrayList(ByteCountToCells(64)); + char key[64], posture[64]; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + kv.GetString(key, posture, sizeof(posture)); + if (posture[0] == '\0') + { + break; + } + attackData.PostureWhitelist.PushString(posture); + } + kv.GoBack(); + } + + if (kv.JumpToKey("tongue")) + { + GetProfileDifficultyFloatValues(kv, "speed", attackData.TongueSpeed, attackData.TongueSpeed); + GetProfileDifficultyFloatValues(kv, "pull_scale", attackData.TonguePullScale, attackData.TonguePullScale); + GetProfileDifficultyBoolValues(kv, "can_escape", attackData.TongueCanEscape, attackData.TongueCanEscape); + kv.GetString("material", attackData.TongueMaterial, sizeof(attackData.TongueMaterial), attackData.TongueMaterial); + PrecacheMaterial2(attackData.TongueMaterial, g_FileCheckConVar.BoolValue); + kv.GetString("attachment", attackData.TongueAttachment, sizeof(attackData.TongueAttachment), attackData.TongueAttachment); + kv.GetVector("offset", attackData.TongueOffset, attackData.TongueOffset); + + if (kv.JumpToKey("sounds")) + { + if (kv.JumpToKey("launch")) + { + attackData.TongueLaunchSound.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + + if (kv.JumpToKey("hit")) + { + attackData.TongueHitSound.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + + if (kv.JumpToKey("tied")) + { + attackData.TongueTiedSound.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + kv.GoBack(); + } + + attackData.Animations.Init(); + attackData.Animations.Load(kv, false); + + kv.GoBack(); } + + attacks.PushArray(attackData, sizeof(attackData)); + index++; } - if (maxAttacks > 0) + while (kv.GotoNextKey() && !oldStuff); + + if (!oldStuff) { kv.GoBack(); + kv.GoBack(); } - return maxAttacks; + + return index; } diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp b/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp index 40ae88cf..9f40351f 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp @@ -15,21 +15,18 @@ void InitializeStatueProfiles() void UnloadStatueBossProfile(const char[] profile) { - char tempProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - if (!g_StatueBossProfileData.GetString(profile, tempProfile, sizeof(tempProfile)) || tempProfile[0] == '\0') + SF2StatueBossProfileData statueProfileData; + if (!g_StatueBossProfileData.GetArray(profile, statueProfileData, sizeof(statueProfileData))) { return; } - SF2StatueBossProfileData statueProfileData; - g_StatueBossProfileData.GetArray(profile, statueProfileData, sizeof(statueProfileData)); - statueProfileData.Destroy(); g_StatueBossProfileData.Remove(profile); } -bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen) +bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen, SF2BossProfileData baseData) { SF2StatueBossProfileData profileData; profileData.Init(); @@ -59,12 +56,19 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } GetProfileDifficultyFloatValues(kv, "chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.ChaseDurationAddMaxRange[i] = baseData.SearchRange[i]; + } + GetProfileDifficultyFloatValues(kv, "chase_duration_add_max_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_min", profileData.ChaseDurationAddVisibilityMin, profileData.ChaseDurationAddVisibilityMin); GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_max", profileData.ChaseDurationAddVisibilityMax, profileData.ChaseDurationAddVisibilityMax); GetProfileDifficultyFloatValues(kv, "model_change_dist_max", profileData.ModelChangeDistanceMax, profileData.ModelChangeDistanceMax); GetProfileDifficultyFloatValues(kv, "idle_lifetime", profileData.IdleLifeTime, profileData.IdleLifeTime); + ArrayList validSections = new ArrayList(ByteCountToCells(128)); + if (kv.GotoFirstSubKey()) { char s2[64]; @@ -73,6 +77,13 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { kv.GetSectionName(s2, sizeof(s2)); + if (validSections.FindString(s2) != -1) + { + continue; + } + + validSections.PushString(s2); + if (!StrContains(s2, "sound_")) { profileData.SortSoundSections(kv, s2, g_FileCheckConVar.BoolValue); @@ -83,65 +94,11 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } + delete validSections; + profileData.PostLoad(); g_StatueBossProfileData.SetArray(profile, profileData, sizeof(profileData)); return true; } - -static SF2StatueBossProfileData g_CachedProfileData; - -ArrayList GetStatueProfileModelsAverageDist(const char[] profile) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ModelsAverageDist; -} - -ArrayList GetStatueProfileModelsCloseDist(const char[] profile) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ModelsCloseDist; -} - -float GetStatueProfileChaseDuration(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDuration[difficulty]; -} - -float GetStatueProfileChaseDurationAddVisibleMin(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibilityMin[difficulty]; -} - -float GetStatueProfileChaseDurationAddVisibleMax(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibilityMax[difficulty]; -} - -float GetStatueProfileIdleLifeTime(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IdleLifeTime[difficulty]; -} - -float GetStatueProfileModelChangeDistMax(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ModelChangeDistanceMax[difficulty]; -} - -void GetStatueProfileMoveSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.MoveSounds; -} - -void GetStatueProfileSingleMoveSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.SingleMoveSounds; -} diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 25ed93b3..83acedb4 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -8,8 +8,14 @@ /** * Loads a profile in the current KeyValues position in kv. */ -bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen) +bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen, bool lookIntoLoads = false, const char[] originalDir = "") { + if (IsProfileValid(profile)) + { + FormatEx(loadFailReasonBuffer, loadFailReasonBufferLen, "is already loaded or matches another boss's profile name!"); + return false; + } + SF2BossProfileData profileData; profileData.Init(); @@ -36,6 +42,59 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GoBack(); } + if (lookIntoLoads) + { + // In this, we're basically just gonna go look for companion bosses and skip them here + bool skip = true; + + if (kv.GetNum("enable_random_selection", true) != 0) + { + skip = false; + } + + if (kv.GetNum("admin_only", false) != 0) + { + skip = false; + } + + if (kv.GetNum("enable_random_selection_boxing", false) != 0) + { + skip = false; + } + + if (kv.GetNum("enable_random_selection_renevant", false) != 0) + { + skip = false; + } + + if (kv.GetNum("enable_random_selection_renevant_admin", false) != 0) + { + skip = false; + } + + if (kv.GetNum("is_pve", false) != 0 && kv.GetNum("pve_selectable", 1) != 0) + { + skip = false; + } + + if (kv.GetNum("always_load", false) != 0) + { + skip = false; + } + + if (kv.JumpToKey("pve") && kv.GetNum("selectable", 1) != 0) + { + kv.GoBack(); + skip = false; + } + + if (skip) + { + FormatEx(loadFailReasonBuffer, loadFailReasonBufferLen, "is not selectable, skipping!"); + return false; + } + } + profileData.Models = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); profileData.Names = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); SetProfileDifficultyStringArrayValues(kv, "model", profileData.Models, true); @@ -79,7 +138,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu return false; } - profileData.SkinDifficultiesOn = !!kv.GetNum("skin_difficulty", profileData.SkinDifficultiesOn); + profileData.SkinDifficultiesOn = kv.GetNum("skin_difficulty", profileData.SkinDifficultiesOn) != 0; profileData.Body[1] = kv.GetNum("body", profileData.Body[1]); if (profileData.Body[1] < 0) @@ -96,9 +155,9 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu return false; } - profileData.BodyDifficultiesOn = !!kv.GetNum("body_difficulty", profileData.BodyDifficultiesOn); + profileData.BodyDifficultiesOn = kv.GetNum("body_difficulty", profileData.BodyDifficultiesOn) != 0; - profileData.RaidHitbox = !!kv.GetNum("use_raid_hitbox", profileData.RaidHitbox); + profileData.RaidHitbox = kv.GetNum("use_raid_hitbox", profileData.RaidHitbox) != 0; profileData.InstantKillRadius = kv.GetFloat("kill_radius", profileData.InstantKillRadius); @@ -129,6 +188,26 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } profileData.TurnRate = kv.GetFloat("maxyawrate", profileData.TurnRate); + profileData.TurnRate = kv.GetFloat("turnrate", profileData.TurnRate); + + switch (profileData.Type) + { + case SF2BossType_Chaser: + { + profileData.Description.Type = "Chaser"; + } + + case SF2BossType_Statue: + { + profileData.Description.Type = "Statue"; + } + } + + if (kv.JumpToKey("description")) + { + profileData.Description.Load(kv); + kv.GoBack(); + } profileData.ScareCooldown = kv.GetFloat("scare_cooldown", profileData.ScareCooldown); if (profileData.ScareCooldown < 0.0) @@ -152,7 +231,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("kill_weapontype", profileData.WeaponString, sizeof(profileData.WeaponString), profileData.WeaponString); profileData.WeaponInt = kv.GetNum("kill_weapontype", profileData.WeaponInt); - profileData.DiscoMode = !!kv.GetNum("disco_mode", profileData.DiscoMode); + profileData.DiscoMode = kv.GetNum("disco_mode", profileData.DiscoMode) != 0; if (profileData.DiscoMode) { profileData.DiscoDistanceMin = kv.GetFloat("disco_mode_rng_distance_min", profileData.DiscoDistanceMin); @@ -160,7 +239,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetVector("disco_mode_pos", profileData.DiscoPos, profileData.DiscoPos); } - profileData.FestiveLights = !!kv.GetNum("festive_lights", profileData.FestiveLights); + profileData.FestiveLights = kv.GetNum("festive_lights", profileData.FestiveLights) != 0; if (profileData.FestiveLights) { profileData.FestiveLightBrightness = kv.GetNum("festive_light_brightness", profileData.FestiveLightBrightness); @@ -170,39 +249,86 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetVector("festive_lights_ang", profileData.FestiveLightAng, profileData.FestiveLightAng); } - profileData.EnableSpawnParticles = !!kv.GetNum("tp_effect_spawn", profileData.EnableSpawnParticles); - if (profileData.EnableSpawnParticles) - { - kv.GetString("tp_effect_spawn_particle", profileData.SpawnParticle, sizeof(profileData.SpawnParticle), profileData.SpawnParticle); - kv.GetString("tp_effect_spawn_sound", profileData.SpawnParticleSound, sizeof(profileData.SpawnParticleSound), profileData.SpawnParticleSound); - TryPrecacheBossProfileSoundPath(profileData.SpawnParticleSound, _, g_FileCheckConVar.BoolValue); - profileData.SpawnParticleSoundVolume = kv.GetFloat("tp_effect_spawn_sound_volume", profileData.SpawnParticleSoundVolume); - profileData.SpawnParticleSoundPitch = kv.GetNum("tp_effect_spawn_sound_pitch", profileData.SpawnParticleSoundPitch); - } - - profileData.EnableDespawnParticles = !!kv.GetNum("tp_effect_despawn", profileData.EnableDespawnParticles); - if (profileData.EnableDespawnParticles) + if (kv.GetNum("tp_effect_spawn", false) != 0) { - kv.GetString("tp_effect_despawn_particle", profileData.DespawnParticle, sizeof(profileData.DespawnParticle), profileData.DespawnParticle); - kv.GetString("tp_effect_despawn_sound", profileData.DespawnParticleSound, sizeof(profileData.DespawnParticleSound), profileData.DespawnParticleSound); - TryPrecacheBossProfileSoundPath(profileData.DespawnParticleSound, _, g_FileCheckConVar.BoolValue); - profileData.DespawnParticleSoundVolume = kv.GetFloat("tp_effect_despawn_sound_volume", profileData.DespawnParticleSoundVolume); - profileData.DespawnParticleSoundPitch = kv.GetNum("tp_effect_despawn_sound_pitch", profileData.DespawnParticleSoundPitch); + if (profileData.SpawnEffects == null) + { + profileData.SpawnEffects = new StringMap(); + } + ArrayList listEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + SF2BossProfileBaseEffectInfo particle; + SF2BossProfileBaseEffectInfo sound; + + particle.Init(); + particle.Type = EffectType_Particle; + particle.Event = EffectEvent_Constant; + kv.GetString("tp_effect_spawn_particle", particle.ParticleName, sizeof(particle.ParticleName), particle.ParticleName); + particle.LifeTime = 0.1; + kv.GetVector("tp_effect_origin", particle.Origin, particle.Origin); + particle.PostLoad(); + listEffects.PushArray(particle); + + char soundName[PLATFORM_MAX_PATH]; + sound.Init(); + sound.Type = EffectType_Sound; + sound.Event = EffectEvent_Constant; + kv.GetString("tp_effect_spawn_sound", soundName, sizeof(soundName), soundName); + TryPrecacheBossProfileSoundPath(soundName, g_FileCheckConVar.BoolValue); + sound.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound.SoundSounds.Paths.PushString(soundName); + sound.SoundSounds.Volume = kv.GetFloat("tp_effect_spawn_sound_volume", sound.SoundSounds.Volume); + sound.SoundSounds.Pitch = kv.GetNum("tp_effect_spawn_sound_pitch", sound.SoundSounds.Pitch); + sound.SoundSounds.PostLoad(); + listEffects.PushArray(sound); + + profileData.SpawnEffects.SetValue("TPEffectSpawnBackwards", listEffects); } - if (profileData.EnableSpawnParticles || profileData.EnableDespawnParticles) + if (kv.GetNum("tp_effect_despawn", false) != 0) { - kv.GetVector("tp_effect_origin", profileData.SpawnParticleOrigin, profileData.SpawnParticleOrigin); + if (profileData.DespawnEffects == null) + { + profileData.DespawnEffects = new StringMap(); + } + ArrayList listEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + SF2BossProfileBaseEffectInfo particle; + SF2BossProfileBaseEffectInfo sound; + + particle.Init(); + particle.Type = EffectType_Particle; + particle.Event = EffectEvent_Constant; + kv.GetString("tp_effect_despawn_particle", particle.ParticleName, sizeof(particle.ParticleName), particle.ParticleName); + particle.LifeTime = 0.1; + kv.GetVector("tp_effect_origin", particle.Origin, particle.Origin); + particle.PostLoad(); + listEffects.PushArray(particle); + + char soundName[PLATFORM_MAX_PATH]; + sound.Init(); + sound.Type = EffectType_Sound; + sound.Event = EffectEvent_Constant; + kv.GetString("tp_effect_despawn_sound", soundName, sizeof(soundName), soundName); + TryPrecacheBossProfileSoundPath(soundName, g_FileCheckConVar.BoolValue); + sound.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound.SoundSounds.Paths.PushString(soundName); + sound.SoundSounds.Volume = kv.GetFloat("tp_effect_despawn_sound_volume", sound.SoundSounds.Volume); + sound.SoundSounds.Pitch = kv.GetNum("tp_effect_despawn_sound_pitch", sound.SoundSounds.Pitch); + sound.SoundSounds.PostLoad(); + listEffects.PushArray(sound); + + profileData.DespawnEffects.SetValue("TPEffectDespawnBackwards", listEffects); } profileData.BlinkLookRate = kv.GetFloat("blink_look_rate_multiply", profileData.BlinkLookRate); profileData.BlinkStaticRate = kv.GetFloat("blink_static_rate_multiply", profileData.BlinkStaticRate); - profileData.DeathCam = !!kv.GetNum("death_cam", profileData.DeathCam); + profileData.DeathCam = kv.GetNum("death_cam", profileData.DeathCam) != 0; if (profileData.DeathCam) { - profileData.DeathCamScareSound = !!kv.GetNum("death_cam_play_scare_sound", profileData.DeathCamScareSound); - profileData.PublicDeathCam = !!kv.GetNum("death_cam_public", profileData.PublicDeathCam); + profileData.DeathCamScareSound = kv.GetNum("death_cam_play_scare_sound", profileData.DeathCamScareSound) != 0; + profileData.PublicDeathCam = kv.GetNum("death_cam_public", profileData.PublicDeathCam) != 0; if (profileData.PublicDeathCam) { profileData.PublicDeathCamSpeed = kv.GetFloat("death_cam_speed", profileData.PublicDeathCamSpeed); @@ -211,7 +337,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.PublicDeathCamBackwardOffset = kv.GetFloat("deathcam_death_backward_offset", profileData.PublicDeathCamBackwardOffset); profileData.PublicDeathCamDownwardOffset = kv.GetFloat("deathcam_death_downward_offset", profileData.PublicDeathCamDownwardOffset); } - profileData.DeathCamOverlay = !!kv.GetNum("death_cam_overlay", profileData.DeathCamOverlay); + profileData.DeathCamOverlay = kv.GetNum("death_cam_overlay", profileData.DeathCamOverlay) != 0; profileData.DeathCamOverlayStartTime = kv.GetFloat("death_cam_time_overlay_start", profileData.DeathCamOverlayStartTime); if (profileData.DeathCamOverlayStartTime < 0.0) { @@ -227,11 +353,19 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("death_cam_attachtment_point", profileData.PublicDeathCamAttachment, sizeof(profileData.PublicDeathCamAttachment), profileData.PublicDeathCamAttachment); } + if (kv.JumpToKey("public_death_cam")) + { + profileData.DeathCamData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + GetProfileDifficultyFloatValues(kv, "sound_music_loop", profileData.SoundMusicLoop, profileData.SoundMusicLoop); GetProfileDifficultyFloatValues(kv, "kill_cooldown", profileData.InstantKillCooldown, profileData.InstantKillCooldown); GetProfileDifficultyFloatValues(kv, "search_view_distance", profileData.SearchRange, profileData.SearchRange); + GetProfileDifficultyFloatValues(kv, "search_range", profileData.SearchRange, profileData.SearchRange); GetProfileDifficultyFloatValues(kv, "hearing_range", profileData.SearchSoundRange, profileData.SearchSoundRange); + GetProfileDifficultyFloatValues(kv, "search_sound_range", profileData.SearchSoundRange, profileData.SearchSoundRange); GetProfileDifficultyFloatValues(kv, "taunt_alert_range", profileData.TauntAlertRange, profileData.TauntAlertRange); GetProfileDifficultyBoolValues(kv, "teleport_allowed", profileData.TeleportAllowed, profileData.TeleportAllowed); @@ -243,22 +377,21 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu GetProfileDifficultyFloatValues(kv, "teleport_target_stress_min", profileData.TeleportStressMin, profileData.TeleportStressMin); GetProfileDifficultyFloatValues(kv, "teleport_target_stress_max", profileData.TeleportStressMax, profileData.TeleportStressMax); GetProfileDifficultyFloatValues(kv, "teleport_target_persistency_period", profileData.TeleportPersistencyPeriod, profileData.TeleportPersistencyPeriod); - profileData.TeleportIgnoreChases = !!kv.GetNum("teleport_target_ignore_chases", profileData.TeleportIgnoreChases); - profileData.TeleportIgnoreVis = !!kv.GetNum("teleport_target_ignore_visibility", profileData.TeleportIgnoreVis); + profileData.TeleportIgnoreChases = kv.GetNum("teleport_target_ignore_chases", profileData.TeleportIgnoreChases) != 0; + profileData.TeleportIgnoreVis = kv.GetNum("teleport_target_ignore_visibility", profileData.TeleportIgnoreVis) != 0; GetProfileDifficultyFloatValues(kv, "jumpscare_distance", profileData.JumpscareDistance, profileData.JumpscareDistance); GetProfileDifficultyFloatValues(kv, "jumpscare_duration", profileData.JumpscareDuration, profileData.JumpscareDuration); GetProfileDifficultyFloatValues(kv, "jumpscare_cooldown", profileData.JumpscareCooldown, profileData.JumpscareCooldown); - profileData.JumpscareOnScare = !!kv.GetNum("jumpscare_on_scare", profileData.JumpscareOnScare); - profileData.JumpscareNoSight = !!kv.GetNum("jumpscare_no_sight", profileData.JumpscareNoSight); + profileData.JumpscareOnScare = kv.GetNum("jumpscare_on_scare", profileData.JumpscareOnScare) != 0; + profileData.JumpscareNoSight = kv.GetNum("jumpscare_no_sight", profileData.JumpscareNoSight) != 0; GetProfileDifficultyFloatValues(kv, "speed", profileData.RunSpeed, profileData.RunSpeed); - GetProfileDifficultyFloatValues(kv, "speed_max", profileData.MaxRunSpeed, profileData.MaxRunSpeed); GetProfileDifficultyFloatValues(kv, "acceleration", profileData.Acceleration, profileData.Acceleration); GetProfileDifficultyFloatValues(kv, "idle_lifetime", profileData.IdleLifeTime, profileData.IdleLifeTime); - profileData.CustomOutlines = !!kv.GetNum("customizable_outlines", profileData.CustomOutlines); + profileData.CustomOutlines = kv.GetNum("customizable_outlines", profileData.CustomOutlines) != 0; if (profileData.CustomOutlines) { profileData.OutlineColor[0] = kv.GetNum("outline_color_r", profileData.OutlineColor[0]); @@ -297,10 +430,10 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("scare_player_reaction_response_custom", profileData.ScareReactionCustom, sizeof(profileData.ScareReactionCustom), profileData.ScareReactionCustom); } - profileData.ScareReplenishSprint = !!kv.GetNum("scare_player_replenish_sprint", profileData.ScareReplenishSprint); + profileData.ScareReplenishSprint = kv.GetNum("scare_player_replenish_sprint", profileData.ScareReplenishSprint) != 0; if (profileData.ScareReplenishSprint) { - profileData.ScareReplenishSprintAmount = kv.GetNum("scare_player_replenish_sprint_amount", profileData.ScareReplenishSprintAmount); + profileData.ScareReplenishSprintAmount = kv.GetFloat("scare_player_replenish_sprint_amount", profileData.ScareReplenishSprintAmount); } GetProfileDifficultyFloatValues(kv, "static_radius", profileData.StaticRadius, profileData.StaticRadius); @@ -338,50 +471,66 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } kv.GetString("chat_message_upon_death_prefix", profileData.DeathMessagePrefix, sizeof(profileData.DeathMessagePrefix), profileData.DeathMessagePrefix); - profileData.BurnRagdoll = !!kv.GetNum("burn_ragdoll_on_kill", profileData.BurnRagdoll); - profileData.CloakRagdoll = !!kv.GetNum("cloak_ragdoll_on_kill", profileData.CloakRagdoll); - profileData.DecapRagdoll = !!kv.GetNum("decap_ragdoll_on_kill", profileData.DecapRagdoll); - profileData.GibRagdoll = !!kv.GetNum("gib_ragdoll_on_kill", profileData.GibRagdoll); - profileData.IceRagdoll = !!kv.GetNum("ice_ragdoll_on_kill", profileData.IceRagdoll); - profileData.GoldRagdoll = !!kv.GetNum("gold_ragdoll_on_kill", profileData.GoldRagdoll); - profileData.ElectrocuteRagdoll = !!kv.GetNum("electrocute_ragdoll_on_kill", profileData.ElectrocuteRagdoll); - profileData.AshRagdoll = !!kv.GetNum("disintegrate_ragdoll_on_kill", profileData.AshRagdoll); - profileData.DeleteRagdoll = !!kv.GetNum("delete_ragdoll_on_kill", profileData.DeleteRagdoll); - profileData.PushRagdoll = !!kv.GetNum("push_ragdoll_on_kill", profileData.PushRagdoll); + profileData.BurnRagdoll = kv.GetNum("burn_ragdoll_on_kill", profileData.BurnRagdoll) != 0; + profileData.CloakRagdoll = kv.GetNum("cloak_ragdoll_on_kill", profileData.CloakRagdoll) != 0; + profileData.DecapRagdoll = kv.GetNum("decap_ragdoll_on_kill", profileData.DecapRagdoll) != 0; + profileData.GibRagdoll = kv.GetNum("gib_ragdoll_on_kill", profileData.GibRagdoll) != 0; + profileData.IceRagdoll = kv.GetNum("ice_ragdoll_on_kill", profileData.IceRagdoll) != 0; + profileData.GoldRagdoll = kv.GetNum("gold_ragdoll_on_kill", profileData.GoldRagdoll) != 0; + profileData.ElectrocuteRagdoll = kv.GetNum("electrocute_ragdoll_on_kill", profileData.ElectrocuteRagdoll) != 0; + profileData.AshRagdoll = kv.GetNum("disintegrate_ragdoll_on_kill", profileData.AshRagdoll) != 0; + profileData.DeleteRagdoll = kv.GetNum("delete_ragdoll_on_kill", profileData.DeleteRagdoll) != 0; + profileData.PushRagdoll = kv.GetNum("push_ragdoll_on_kill", profileData.PushRagdoll) != 0; if (profileData.PushRagdoll) { kv.GetVector("push_ragdoll_force", profileData.PushRagdollForce, profileData.PushRagdollForce); } - profileData.DissolveRagdoll = !!kv.GetNum("dissolve_ragdoll_on_kill", profileData.DissolveRagdoll); + profileData.DissolveRagdoll = kv.GetNum("dissolve_ragdoll_on_kill", profileData.DissolveRagdoll) != 0; if (profileData.DissolveRagdoll) { profileData.DissolveKillType = kv.GetNum("dissolve_ragdoll_type", profileData.DissolveKillType); } - profileData.PlasmaRagdoll = !!kv.GetNum("plasma_ragdoll_on_kill", profileData.PlasmaRagdoll); - profileData.ResizeRagdoll = !!kv.GetNum("resize_ragdoll_on_kill", profileData.ResizeRagdoll); + profileData.PlasmaRagdoll = kv.GetNum("plasma_ragdoll_on_kill", profileData.PlasmaRagdoll) != 0; + profileData.ResizeRagdoll = kv.GetNum("resize_ragdoll_on_kill", profileData.ResizeRagdoll) != 0; if (profileData.ResizeRagdoll) { profileData.ResizeRagdollHead = kv.GetFloat("resize_ragdoll_head", profileData.ResizeRagdollHead); profileData.ResizeRagdollHands = kv.GetFloat("resize_ragdoll_hands", profileData.ResizeRagdollHands); profileData.ResizeRagdollTorso = kv.GetFloat("resize_ragdoll_torso", profileData.ResizeRagdollTorso); } - profileData.DecapOrGibRagdoll = !!kv.GetNum("decap_or_gib_ragdoll_on_kill", profileData.DecapOrGibRagdoll); - profileData.SilentKill = !!kv.GetNum("silent_kill", profileData.SilentKill); - profileData.MultiEffectRagdoll = !!kv.GetNum("multieffect_ragdoll_on_kill", profileData.MultiEffectRagdoll); - profileData.CustomDeathFlag = !!kv.GetNum("attack_custom_deathflag_enabled", profileData.CustomDeathFlag); + profileData.DecapOrGibRagdoll = kv.GetNum("decap_or_gib_ragdoll_on_kill", profileData.DecapOrGibRagdoll) != 0; + profileData.SilentKill = kv.GetNum("silent_kill", profileData.SilentKill) != 0; + profileData.MultiEffectRagdoll = kv.GetNum("multieffect_ragdoll_on_kill", profileData.MultiEffectRagdoll) != 0; + profileData.CustomDeathFlag = kv.GetNum("attack_custom_deathflag_enabled", profileData.CustomDeathFlag) != 0; if (profileData.CustomDeathFlag) { profileData.CustomDeathFlagType = kv.GetNum("attack_custom_deathflag", profileData.CustomDeathFlagType); } - profileData.OutroMusic = !!kv.GetNum("sound_music_outro_enabled", profileData.OutroMusic); + profileData.OutroMusic = kv.GetNum("sound_music_outro_enabled", profileData.OutroMusic) != 0; profileData.EngineSoundLevel = kv.GetNum("constant_sound_level", profileData.EngineSoundLevel); + profileData.EngineSoundLevel = kv.GetNum("engine_sound_level", profileData.EngineSoundLevel); profileData.EngineSoundVolume = kv.GetFloat("constant_sound_volume", profileData.EngineSoundVolume); + profileData.EngineSoundVolume = kv.GetFloat("engine_sound_volume", profileData.EngineSoundVolume); kv.GetVector("eye_pos", profileData.EyePosOffset, profileData.EyePosOffset); kv.GetVector("eye_ang_offset", profileData.EyeAngOffset, profileData.EyeAngOffset); + if (kv.JumpToKey("eyes")) + { + profileData.EyeData.Load(kv); + kv.GoBack(); + profileData.EyePosOffset = profileData.EyeData.OffsetPos; + profileData.EyeAngOffset = profileData.EyeData.OffsetAng; + } + + if (kv.JumpToKey("slaughter_run")) + { + profileData.SlaughterRunData.Load(kv); + kv.GoBack(); + } + // Parse through flags. int bossFlags = 0; if (kv.GetNum("static_on_look")) @@ -400,10 +549,6 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { bossFlags |= SFF_HASJUMPSCARE; } - if (kv.GetNum("sound_sight_enabled")) - { - bossFlags |= SFF_HASSIGHTSOUNDS; - } if (kv.GetNum("sound_static_loop_local_enabled")) { bossFlags |= SFF_HASSTATICLOOPLOCALSOUND; @@ -412,10 +557,6 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { bossFlags |= SFF_HASVIEWSHAKE; } - if (kv.GetNum("copy")) - { - bossFlags |= SFF_COPIES; - } if (kv.GetNum("wander_move", 1)) { bossFlags |= SFF_WANDERMOVE; @@ -432,15 +573,13 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { bossFlags |= SFF_WEAPONKILLSONRADIUS; } - profileData.Flags = bossFlags; - - if (profileData.Flags & SFF_COPIES) + if (kv.GetNum("static_shake")) { - GetProfileDifficultyNumValues(kv, "copy_max", profileData.Copies, profileData.Copies); - profileData.TeleportCopyDistance = kv.GetFloat("copy_teleport_dist_from_others", profileData.TeleportCopyDistance); - profileData.TeleportCopyDistance = kv.GetFloat("teleport_distance_between_copies", profileData.TeleportCopyDistance); - profileData.FakeCopies = !!kv.GetNum("fake_copies", profileData.FakeCopies); + bossFlags |= SFF_HASSTATICSHAKE; } + profileData.Flags = bossFlags; + + profileData.CopiesInfo.Load(kv); if (profileData.Flags & SFF_PROXIES) { @@ -512,14 +651,15 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.ProxyWeaponSlots[i] = kv.GetNum(keyValue, profileData.ProxyWeaponSlots[i]); } } - profileData.ProxySpawnEffect = !!kv.GetNum("proxies_spawn_effect_enabled", profileData.ProxySpawnEffect); + profileData.ProxySpawnEffect = kv.GetNum("proxies_spawn_effect_enabled", profileData.ProxySpawnEffect) != 0; if (profileData.ProxySpawnEffect) { kv.GetString("proxies_spawn_effect", profileData.ProxySpawnEffectName, sizeof(profileData.ProxySpawnEffectName), profileData.ProxySpawnEffectName); profileData.ProxySpawnEffectZOffset = kv.GetFloat("proxies_spawn_effect_z_offset", profileData.ProxySpawnEffectZOffset); } - profileData.ProxyZombies = !!kv.GetNum("proxies_zombie", profileData.ProxyZombies); - profileData.ProxyDifficultyModels = !!kv.GetNum("proxy_difficulty_models", profileData.ProxyDifficultyModels); + profileData.ProxyZombies = kv.GetNum("proxies_zombie", profileData.ProxyZombies) != 0; + profileData.ProxyRobots = kv.GetNum("proxies_robot", profileData.ProxyRobots) != 0; + profileData.ProxyDifficultyModels = kv.GetNum("proxy_difficulty_models", profileData.ProxyDifficultyModels) != 0; char index[64], modelDirectory[PLATFORM_MAX_PATH]; if (profileData.ProxyDifficultyModels) @@ -690,36 +830,44 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } } + + profileData.ProxyOverrideMaxSpeed = !!kv.GetNum("proxies_override_max_speed", profileData.ProxyOverrideMaxSpeed); + if (profileData.ProxyOverrideMaxSpeed) + { + GetProfileDifficultyFloatValues(kv, "proxies_max_speed", profileData.ProxyMaxSpeed, profileData.ProxyMaxSpeed); + } } UnloadBossProfile(profile); + profileData.AnimationData.Load(kv, true); + switch (profileData.Type) { case SF2BossType_Statue: { - if (!LoadStatueBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen)) + if (!LoadStatueBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen, profileData)) { return false; } } case SF2BossType_Chaser: { - if (!LoadChaserBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen)) + if (!LoadChaserBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen, profileData)) { return false; } } } - // Add the section to our config. g_Config.Rewind(); g_Config.JumpToKey(profile, true); g_Config.Import(kv); kv.GetString("constant_sound", profileData.EngineSound, sizeof(profileData.EngineSound), profileData.EngineSound); + kv.GetString("engine_sound", profileData.EngineSound, sizeof(profileData.EngineSound), profileData.EngineSound); - TryPrecacheBossProfileSoundPath(profileData.EngineSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.EngineSound, g_FileCheckConVar.BoolValue); int index = g_BossProfileList.FindString(profile); if (index == -1) @@ -727,7 +875,13 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu g_BossProfileList.PushString(profile); } - if (!!kv.GetNum("enable_random_selection", 1)) + profileData.EnableRandomSelection = kv.GetNum("enable_random_selection", true) != 0; + profileData.AdminOnly = kv.GetNum("admin_only", false) != 0; + profileData.EnableRandomSelectionBoxing = kv.GetNum("enable_random_selection_boxing", false) != 0; + profileData.EnableRandomSelectionRenevant = kv.GetNum("enable_random_selection_renevant", false) != 0; + profileData.EnableRandomSelectionRenevantAdmin = kv.GetNum("enable_random_selection_renevant_admin", false) != 0; + + if (profileData.EnableRandomSelection) { if (GetSelectableBossProfileList().FindString(profile) == -1) { @@ -744,7 +898,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("admin_only", 0)) + if (profileData.AdminOnly) { if (GetSelectableAdminBossProfileList().FindString(profile) == -1) { @@ -761,7 +915,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("enable_random_selection_boxing", 0)) + if (profileData.EnableRandomSelectionBoxing) { if (GetSelectableBoxingBossProfileList().FindString(profile) == -1) { @@ -778,7 +932,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("enable_random_selection_renevant", 0)) + if (profileData.EnableRandomSelectionRenevant) { if (GetSelectableRenevantBossProfileList().FindString(profile) == -1) { @@ -795,7 +949,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("enable_random_selection_renevant_admin", 0)) + if (profileData.EnableRandomSelectionRenevantAdmin) { if (GetSelectableRenevantBossAdminProfileList().FindString(profile) == -1) { @@ -812,6 +966,107 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } + if (kv.JumpToKey("pve")) + { + profileData.IsPvEBoss = true; + kv.GoBack(); + } + else + { + profileData.IsPvEBoss = kv.GetNum("is_pve", profileData.IsPvEBoss) != 0; + } + + if (profileData.IsPvEBoss) + { + profileData.Flags = profileData.Flags & ~SFF_PROXIES; + if (kv.JumpToKey("pve")) + { + if (kv.JumpToKey("spawn_messages")) + { + profileData.PvESpawnMessagesArray = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char message[256], section[64]; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + kv.GetString(section, message, sizeof(message)); + if (message[0] == '\0') + { + break; + } + + profileData.PvESpawnMessagesArray.PushString(message); + } + kv.GoBack(); + } + kv.GetString("spawn_message_prefix", profileData.PvESpawnMessagePrefix, sizeof(profileData.PvESpawnMessagePrefix), profileData.PvESpawnMessagePrefix); + profileData.DisplayPvEHealth = kv.GetNum("health_bar", profileData.DisplayPvEHealth) != 0; + char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + strcopy(setProfile, sizeof(setProfile), profile); + if (kv.GetNum("selectable", 1) != 0) + { + RegisterPvESlenderBoss(setProfile); + } + profileData.PvETeleportEndTimer = kv.GetFloat("teleport_players_time", profileData.PvETeleportEndTimer); + kv.GoBack(); + } + else + { + if (kv.JumpToKey("pve_spawn_messages")) + { + profileData.PvESpawnMessagesArray = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char message[256], section[64]; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + kv.GetString(section, message, sizeof(message)); + if (message[0] == '\0') + { + break; + } + + profileData.PvESpawnMessagesArray.PushString(message); + } + kv.GoBack(); + } + kv.GetString("pve_spawn_message_prefix", profileData.PvESpawnMessagePrefix, sizeof(profileData.PvESpawnMessagePrefix), profileData.PvESpawnMessagePrefix); + profileData.DisplayPvEHealth = kv.GetNum("pve_health_bar", profileData.DisplayPvEHealth) != 0; + char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + strcopy(setProfile, sizeof(setProfile), profile); + if (kv.GetNum("pve_selectable", 1) != 0) + { + RegisterPvESlenderBoss(setProfile); + } + } + + index = GetSelectableBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableBossProfileList().Erase(index); + } + index = GetSelectableAdminBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableAdminBossProfileList().Erase(index); + } + index = GetSelectableBoxingBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableBoxingBossProfileList().Erase(index); + } + index = GetSelectableRenevantBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableRenevantBossProfileList().Erase(index); + } + index = GetSelectableRenevantBossAdminProfileList().FindString(profile); + if (index != -1) + { + GetSelectableRenevantBossAdminProfileList().Erase(index); + } + } + + ArrayList validSections = new ArrayList(ByteCountToCells(128)); + if (kv.GotoFirstSubKey()) //Special thanks to Fire for modifying the code for download errors. { char s2[64], s3[64], s4[PLATFORM_MAX_PATH], s5[PLATFORM_MAX_PATH]; @@ -820,7 +1075,14 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { kv.GetSectionName(s2, sizeof(s2)); - if (!StrContains(s2, "sound_")) + if (validSections.FindString(s2) != -1) + { + continue; + } + + validSections.PushString(s2); + + if (StrContains(s2, "sound_") != -1) { bool doBack = false; if (kv.JumpToKey("paths")) @@ -836,7 +1098,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu break; } - TryPrecacheBossProfileSoundPath(s4, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(s4, g_FileCheckConVar.BoolValue); // Here comes an if else mess, I'm very sorry if (strcmp(s2, "sound_jumpscare") == 0) @@ -981,7 +1243,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("1", s4, sizeof(s4)); profileData.OverlayJumpscare = s4; } - if (!StrContains(s2, "sound_footsteps_event_")) + if (StrContains(s2, "sound_footsteps_event_") != -1) { if (profileData.FootstepEventSounds == null) { @@ -1006,7 +1268,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.FootstepEventSounds.PushArray(soundInfo); } } - else if (!StrContains(s2, "sound_event_")) + else if (StrContains(s2, "sound_event_") != -1) { if (profileData.EventSounds == null) { @@ -1037,11 +1299,14 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GoBack(); } + delete validSections; + if (kv.JumpToKey("companions")) { profileData.CompanionsArray = new ArrayList(sizeof(SF2BossProfileCompanionsInfo)); kv.GetString("type", profileData.CompanionSpawnType, sizeof(profileData.CompanionSpawnType)); + ArrayList validProfiles = null; if (kv.GotoFirstSubKey()) { do @@ -1050,11 +1315,67 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu companions.Init(); companions.Load(kv); profileData.CompanionsArray.PushArray(companions); + if (lookIntoLoads) + { + char compProfile[SF2_MAX_PROFILE_NAME_LENGTH], otherProfile[SF2_MAX_PROFILE_NAME_LENGTH], dir[PLATFORM_MAX_PATH], file[PLATFORM_MAX_PATH]; + FileType fileType; + DirectoryListing directory = OpenDirectory(originalDir); + while (directory.GetNext(file, sizeof(file), fileType)) + { + if (fileType == FileType_Directory) + { + continue; + } + + FormatEx(dir, sizeof(dir), "%s/%s", originalDir, file); + + for (int i = 0; i < companions.Bosses.Length; i++) + { + companions.Bosses.GetString(i, compProfile, sizeof(compProfile)); + + KeyValues otherKeys = new KeyValues("root"); + if (!FileToKeyValues(otherKeys, dir)) + { + delete otherKeys; + continue; + } + + otherKeys.GetSectionName(otherProfile, sizeof(otherProfile)); + + if (strcmp(compProfile, otherProfile) == 0) + { + if (validProfiles == null) + { + validProfiles = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); + } + if (validProfiles.FindString(otherProfile) == -1) + { + if (!LoadBossProfile(otherKeys, otherProfile, loadFailReasonBuffer, loadFailReasonBufferLen)) + { + LogSF2Message("(COMPANION) %s...FAILED (reason: %s)", dir, loadFailReasonBuffer); + } + else + { + LogSF2Message("(COMPANION) %s...", otherProfile); + } + validProfiles.PushString(otherProfile); + } + } + + delete otherKeys; + } + } + delete directory; + } } while (kv.GotoNextKey()); kv.GoBack(); } kv.GoBack(); + if (validProfiles != null) + { + delete validProfiles; + } } if (kv.JumpToKey("attributes")) @@ -1062,8 +1383,6 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.AttributesInfo.Load(kv); } - profileData.AnimationData.Load(kv); - if (kv.JumpToKey("effects")) { profileData.EffectsArray = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); @@ -1075,7 +1394,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu SF2BossProfileBaseEffectInfo effects; effects.Init(); effects.ModelScale = profileData.ModelScale; - effects.Load(kv); + effects.Load(kv, g_FileCheckConVar.BoolValue); profileData.EffectsArray.PushArray(effects); } while (kv.GotoNextKey()); @@ -1084,6 +1403,93 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GoBack(); } + if (kv.JumpToKey("spawn_effects")) + { + if (profileData.SpawnEffects == null) + { + profileData.SpawnEffects = new StringMap(); + } + + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + kv.GetSectionName(section, sizeof(section)); + if (kv.JumpToKey("effects")) + { + ArrayList list = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effects; + effects.Init(); + effects.ModelScale = profileData.ModelScale; + effects.Load(kv, g_FileCheckConVar.BoolValue); + if (effects.Type == EffectType_Particle) + { + effects.LifeTime = 0.1; + } + list.PushArray(effects); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + profileData.SpawnEffects.SetValue(section, list); + } + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("despawn_effects")) + { + if (profileData.DespawnEffects == null) + { + profileData.DespawnEffects = new StringMap(); + } + profileData.HideDespawnEffectsOnDeath = kv.GetNum("hide_on_death", profileData.HideDespawnEffectsOnDeath) != 0; + + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + kv.GetSectionName(section, sizeof(section)); + if (kv.JumpToKey("effects")) + { + ArrayList list = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effects; + effects.Init(); + effects.ModelScale = profileData.ModelScale; + effects.Load(kv, g_FileCheckConVar.BoolValue); + if (effects.Type == EffectType_Particle) + { + effects.LifeTime = 0.1; + } + list.PushArray(effects); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + profileData.DespawnEffects.SetValue(section, list); + } + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + profileData.PostLoad(); g_BossProfileData.SetArray(profile, profileData, sizeof(profileData)); @@ -1146,7 +1552,7 @@ int GetBossProfileBodyGroupsMax(const char[] profile) return g_CachedProfileData.BodyMax; } -int GetBossProfileRaidHitbox(const char[] profile) +bool GetBossProfileRaidHitbox(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.RaidHitbox; @@ -1167,7 +1573,7 @@ float GetBossProfileSoundMusicLoop(const char[] profile, int difficulty) int GetBossProfileMaxCopies(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Copies[difficulty]; + return g_CachedProfileData.CopiesInfo.MaxCopies[difficulty]; } float GetBossProfileModelScale(const char[] profile) @@ -1290,72 +1696,6 @@ void GetBossProfileFestiveLightAngles(const char[] profile, float buffer[3]) buffer = g_CachedProfileData.FestiveLightAng; } -bool GetBossProfileSpawnParticleState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EnableSpawnParticles; -} - -int GetBossProfileSpawnParticleName(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SpawnParticle); -} - -int GetBossProfileSpawnParticleSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SpawnParticleSound); -} - -float GetBossProfileSpawnParticleSoundVolume(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SpawnParticleSoundVolume; -} - -int GetBossProfileSpawnParticleSoundPitch(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SpawnParticleSoundPitch; -} - -void GetBossProfileSpawnParticleOrigin(const char[] profile, float buffer[3]) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.SpawnParticleOrigin; -} - -bool GetBossProfileDespawnParticleState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EnableDespawnParticles; -} - -int GetBossProfileDespawnParticleName(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DespawnParticle); -} - -int GetBossProfileDespawnParticleSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DespawnParticleSound); -} - -float GetBossProfileDespawnParticleSoundVolume(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DespawnParticleSoundVolume; -} - -int GetBossProfileDespawnParticleSoundPitch(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DespawnParticleSoundPitch; -} - ArrayList GetBossProfileNames(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1440,18 +1780,6 @@ float GetBossProfilePublicDeathCamDownwardOffset(const char[] profile) return g_CachedProfileData.PublicDeathCamDownwardOffset; } -int GetBossProfilePublicDeathCamTargetAttachment(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.PublicDeathCamAttachmentTarget); -} - -int GetBossProfilePublicDeathCamAttachment(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.PublicDeathCamAttachment); -} - bool GetBossProfileDeathCamOverlayState(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1470,30 +1798,6 @@ float GetBossProfileDeathCamTime(const char[] profile) return g_CachedProfileData.DeathCamTime; } -void GetBossProfileDeathCamPosition(const char[] profile, float buffer[3]) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.DeathCamPos; -} - -float GetBossProfileSpeed(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RunSpeed[difficulty]; -} - -float GetBossProfileMaxSpeed(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MaxRunSpeed[difficulty]; -} - -float GetBossProfileAcceleration(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Acceleration[difficulty]; -} - float GetBossProfileIdleLifetime(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1548,30 +1852,12 @@ float GetBossProfileStaticShakeLocalVolumeMax(const char[] profile) return g_CachedProfileData.StaticShakeVolumeMax; } -float GetBossProfileSearchRadius(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SearchRange[difficulty]; -} - -float GetBossProfileHearRadius(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SearchSoundRange[difficulty]; -} - bool GetBossProfileTeleportAllowed(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.TeleportAllowed[difficulty]; } -float GetBossProfileTauntAlertRange(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TauntAlertRange[difficulty]; -} - float GetBossProfileTeleportTimeMin(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1632,10 +1918,10 @@ bool GetBossProfileTeleportIgnoreVis(const char[] profile) return g_CachedProfileData.TeleportIgnoreVis; } -float GetBossProfileTeleportCopyDistance(const char[] profile) +float GetBossProfileTeleportCopyDistance(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TeleportCopyDistance; + return g_CachedProfileData.CopiesInfo.TeleportDistanceSpacing[difficulty]; } float GetBossProfileJumpscareDistance(const char[] profile, int difficulty) @@ -1758,10 +2044,10 @@ int GetBossProfileMaxProxies(const char[] profile, int difficulty) return g_CachedProfileData.MaxProxies[difficulty]; } -bool GetBossProfileFakeCopies(const char[] profile) +bool GetBossProfileFakeCopies(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FakeCopies; + return g_CachedProfileData.CopiesInfo.Fakes[difficulty]; } bool GetBossProfileDrainCreditState(const char[] profile) @@ -1843,16 +2129,16 @@ ArrayList GetBossProfileProxyModels(const char[] profile, int index, int difficu return g_CachedProfileData.ProxyModels[index]; } -float GetBossProfileFOV(const char[] profile) +bool GetBossProfileProxyOverrideMaxSpeed(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FOV; + return g_CachedProfileData.ProxyOverrideMaxSpeed; } -float GetBossProfileTurnRate(const char[] profile) +float GetBossProfileProxyMaxSpeed(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TurnRate; + return g_CachedProfileData.ProxyMaxSpeed[difficulty]; } void GetBossProfileEyePositionOffset(const char[] profile, float buffer[3]) @@ -1867,18 +2153,6 @@ void GetBossProfileEyeAngleOffset(const char[] profile, float buffer[3]) buffer = g_CachedProfileData.EyeAngOffset; } -float GetBossProfileInstantKillRadius(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.InstantKillRadius; -} - -float GetBossProfileInstantKillCooldown(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.InstantKillCooldown[difficulty]; -} - float GetBossProfileScareRadius(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1939,7 +2213,7 @@ bool GetBossProfileScareReplenishState(const char[] profile) return g_CachedProfileData.ScareReplenishSprint; } -int GetBossProfileScareReplenishAmount(const char[] profile) +float GetBossProfileScareReplenishAmount(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.ScareReplenishSprintAmount; @@ -1957,42 +2231,12 @@ bool GetBossProfileCustomOutlinesState(const char[] profile) return g_CachedProfileData.CustomOutlines; } -int GetBossProfileOutlineColorR(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[0]; -} - -int GetBossProfileOutlineColorG(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[1]; -} - -int GetBossProfileOutlineColorB(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[2]; -} - -int GetBossProfileOutlineTransparency(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[3]; -} - bool GetBossProfileRainbowOutlineState(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.RainbowOutline; } -float GetBossProfileRainbowCycleRate(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RainbowOutlineCycle; -} - bool GetBossProfileProxyWeapons(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -2047,192 +2291,6 @@ int GetBossProfileChatDeathMessagePrefix(const char[] profile, char[] buffer, in return strcopy(buffer, bufferlen, g_CachedProfileData.DeathMessagePrefix); } -bool GetBossProfileHealthbarState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Healthbar; -} - -bool GetBossProfileBurnRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BurnRagdoll; -} - -bool GetBossProfileCloakRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakRagdoll; -} - -bool GetBossProfileDecapRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DecapRagdoll; -} - -bool GetBossProfileGibRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GibRagdoll; -} - -bool GetBossProfileGoldRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GoldRagdoll; -} - -bool GetBossProfileIceRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IceRagdoll; -} - -bool GetBossProfileElectrocuteRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectrocuteRagdoll; -} - -bool GetBossProfileAshRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AshRagdoll; -} - -bool GetBossProfileDeleteRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DeleteRagdoll; -} - -bool GetBossProfilePushRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.PushRagdoll; -} - -void GetBossProfilePushRagdollForce(const char[] profile, float buffer[3]) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.PushRagdollForce; -} - -bool GetBossProfileDissolveRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DissolveRagdoll; -} - -int GetBossProfileDissolveRagdollType(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DissolveKillType; -} - -bool GetBossProfilePlasmaRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.PlasmaRagdoll; -} - -bool GetBossProfileResizeRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdoll; -} - -float GetBossProfileResizeRagdollHead(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdollHead; -} - -float GetBossProfileResizeRagdollHands(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdollHands; -} - -float GetBossProfileResizeRagdollTorso(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdollTorso; -} - -bool GetBossProfileDecapOrGibRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DecapOrGibRagdoll; -} - -bool GetBossProfileSilentKill(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentKill; -} - -bool GetBossProfileMultieffectRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MultiEffectRagdoll; -} - -bool GetBossProfileCustomDeathFlag(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CustomDeathFlag; -} - -int GetBossProfileCustomDeathFlagType(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CustomDeathFlagType; -} - -bool GetBossProfileOutroMusicState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutroMusic; -} - -int GetBossProfileEngineSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.EngineSound); -} - -int GetBossProfileEngineSoundLevel(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EngineSoundLevel; -} - -float GetBossProfileEngineSoundVolume(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EngineSoundVolume; -} - -void GetBossProfileLocalDeathCamSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.LocalDeathCamSounds; -} - -void GetBossProfileClientDeathCamSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.ClientDeathCamSounds; -} - -void GetBossProfileGlobalDeathCamSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.GlobalDeathCamSounds; -} - void GetBossProfileMusicSounds(const char[] profile, SF2BossProfileSoundInfo params, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -2285,12 +2343,6 @@ void GetBossProfileSpawnLocalSounds(const char[] profile, SF2BossProfileSoundInf params = g_CachedProfileData.SpawnLocalSounds; } -void GetBossProfilePlayerDeathcamOverlaySounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.PlayerDeathCamOverlaySounds; -} - void GetBossProfileProxySpawnSounds(const char[] profile, SF2BossProfileSoundInfo params) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -2403,10 +2455,4 @@ void GetBossProfileAnimationsData(const char[] profile, SF2BossProfileMasterAnim { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); data = g_CachedProfileData.AnimationData; -} - -ArrayList GetBossProfileEffectsArray(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EffectsArray; -} +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/pve.sp b/addons/sourcemod/scripting/sf2/pve.sp new file mode 100644 index 00000000..00ef2e2b --- /dev/null +++ b/addons/sourcemod/scripting/sf2/pve.sp @@ -0,0 +1,1388 @@ +#pragma semicolon 1 + +static bool g_PlayerInPvE[MAXTF2PLAYERS]; +static bool g_PlayerIsLeavingPvE[MAXTF2PLAYERS]; +static bool g_PlayerShowBossHealthPvE[MAXTF2PLAYERS]; +static Handle g_PlayerPvETimer[MAXTF2PLAYERS]; +static Handle g_PlayerPvERespawnTimer[MAXTF2PLAYERS]; +static int g_PlayerPvETimerCount[MAXTF2PLAYERS]; +static ArrayList g_PlayerEnteredPvETriggers[MAXTF2PLAYERS] = { null, ... }; +static ArrayList g_SlenderBosses; +static ArrayList g_CustomBosses; +static ArrayList g_ActiveBosses; +static bool g_IsPvEActive; +static bool g_DoesPvEExist; +static char g_PlayerCurrentPvEMusic[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static bool g_PvEBoxingMode; + +static float g_TimeUntilBossSpawns; +static bool g_HasBossSpawned; + +#define MAX_MUSICS 14 +static Database g_MusicDataBase; +static bool g_IsMusicCached[MAXTF2PLAYERS]; +static char g_MusicFile[MAXTF2PLAYERS][MAX_MUSICS][PLATFORM_MAX_PATH]; +static char g_MenuMusic[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static float g_MusicVolume[MAXTF2PLAYERS][MAX_MUSICS]; +static float g_MusicLoopTime[MAXTF2PLAYERS][MAX_MUSICS]; +static char g_CustomMusicOverride[PLATFORM_MAX_PATH]; + +static GlobalForward g_OnSelectedPvEBoss; +static GlobalForward g_OnPlayerEnterPvE; +static GlobalForward g_OnPlayerExitPvE; + +static ArrayList g_PvETriggers; + +static Menu g_MenuSettingsPvE; + +void PvE_Initialize() +{ + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + g_OnGameFramePFwd.AddFunction(null, GameFrame); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnRoundStartPFwd.AddFunction(null, RoundStart); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerAverageUpdatePFwd.AddFunction(null, OnPlayerAverageUpdate); + + g_SlenderBosses = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); + g_CustomBosses = new ArrayList(ByteCountToCells(64)); + g_ActiveBosses = new ArrayList(); + + g_PvETriggers = new ArrayList(); + + RegConsoleCmd("sm_sf2_add_pve_music", Command_AddPvEMusic); + RegConsoleCmd("sm_sf2_pve_show_health", Command_ShowHealth); + RegAdminCmd("sm_sf2_do_pve", Command_DoPvE, ADMFLAG_SLAY); +} + +void PvE_SetupMenus() +{ + g_MenuSettingsPvE = new Menu(Menu_SettingsPvE); + g_MenuSettingsPvE.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Settings PvE Menu Title"); + char formatter[256]; + FormatEx(formatter, sizeof(formatter), "%t", "SF2 PvE Music Settings Title"); + g_MenuSettingsPvE.AddItem("0", formatter); + g_MenuSettingsPvE.ExitBackButton = true; +} + +Menu GetPvEMenu() +{ + return g_MenuSettingsPvE; +} + +static void OnGamemodeStart() +{ + Database base; + char error[512]; + if (SQL_CheckConfig("sf2_pve_music")) + { + base = SQL_Connect("sf2_pve_music", true, error, sizeof(error)); + if (base == null) + { + PrintToServer("%s", error); + return; + } + } + else + { + base = SQLite_UseDatabase("sf2_pve_music", error, sizeof(error)); + if (base == null) + { + PrintToServer("%s", error); + return; + } + } + + Transaction action = new Transaction(); + for (int i = 0; i < MAXTF2PLAYERS; i++) + { + for (int i2 = 0; i2 < MAX_MUSICS; i2++) + { + g_MusicFile[i][i2][0] = '\0'; + g_MusicVolume[i][i2] = 0.75; + g_MusicLoopTime[i][i2] = 0.0; + } + } + + char formatter[1024]; + for (int i = 0; i < MAX_MUSICS; i++) + { + FormatEx(formatter, sizeof(formatter), "CREATE TABLE IF NOT EXISTS music_selection_%i (" + ... "steamid INTEGER PRIMARY KEY, " + ... "music_file VARCHAR(256), " + ... "music_volume FLOAT NOT NULL DEFAULT 1.0, " + ... "music_loop_time FLOAT NOT NULL DEFAULT 0.0);", + i); + + action.AddQuery(formatter); + } + + base.Execute(action, Database_Success, Database_FailHandle, base); +} + +static void GameFrame() +{ + if (g_DoesPvEExist && g_TimeUntilBossSpawns < GetGameTime() && !g_HasBossSpawned && GetRoundState() != SF2RoundState_Waiting) + { + SpawnPvEBoss(); + } +} + +static void MapStart() +{ + g_DoesPvEExist = false; + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + CBaseEntity trigger = CBaseEntity(ent); + if (trigger.IsValid()) + { + char name[50]; + trigger.GetPropString(Prop_Data, "m_iName", name, sizeof(name)); + if (StrContains(name, "sf2_pve_trigger", false) == 0) + { + float mins[3], maxs[3]; + trigger.GetPropVector(Prop_Send, "m_vecMins", mins); + trigger.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + float range = ((maxs[0] + maxs[1]) / 2.0) + (FloatAbs(mins[0] + mins[1]) / 2.0); + if (range > 2000.0) + { + g_DoesPvEExist = true; + } + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "sf2_trigger_pve")) != -1) + { + SF2TriggerPvEEntity trigger = SF2TriggerPvEEntity(ent); + if (trigger.IsValid()) + { + if (trigger.IsBossPvE) + { + g_DoesPvEExist = true; + } + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } +} + +static void Database_Success(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData) +{ + g_MusicDataBase = data; + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientAuthorized(i)) + { + OnClientAuthorized(i, NULL_STRING); + } + } +} + +public void OnClientAuthorized(int client, const char[] auth) +{ + if (g_MusicDataBase == null || IsFakeClient(client)) + { + return; + } + + int id = GetSteamAccountID(client); + if (id == 0) + { + return; + } + + Transaction action = new Transaction(); + + char formatter[256]; + for (int i = 0; i < MAX_MUSICS; i++) + { + FormatEx(formatter, sizeof(formatter), "SELECT * FROM music_selection_%i WHERE steamid = %d;", i, id); + action.AddQuery(formatter); + } + + g_MusicDataBase.Execute(action, Database_SetupMusics, Database_RetryClient, GetClientUserId(client)); +} + +static void Database_SetupMusics(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData) +{ + int client = GetClientOfUserId(data); + if (!IsValidClient(client)) + { + return; + } + char formatter[256]; + Transaction action; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (results[i].FetchRow()) + { + results[i].FetchString(1, g_MusicFile[client][i], sizeof(g_MusicFile[][])); + g_MusicVolume[client][i] = results[i].FetchFloat(2); + g_MusicLoopTime[client][i] = results[i].FetchFloat(3); + if (g_MusicFile[client][i][0] != '\0') + { + PrecacheSound(g_MusicFile[client][i]); + } + } + else if (!results[i].MoreRows) + { + if (action == null) + { + action = new Transaction(); + } + + FormatEx(formatter, sizeof(formatter), "INSERT INTO music_selection_%i (steamid, music_file) VALUES ('%d', '%s')", i, GetSteamAccountID(client), ""); + action.AddQuery(formatter); + } + } + + if (action != null) + { + g_MusicDataBase.Execute(action, _, Database_Fail); + } + + g_IsMusicCached[client] = true; +} + +static void Database_Fail(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData) +{ + LogError(error); +} + +static void Database_FailHandle(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData) +{ + LogError(error); + CloseHandle(data); +} + +static void Database_RetryClient(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData) +{ + int client = GetClientOfUserId(data); + if (IsValidClient(client)) + { + OnClientAuthorized(client, error); + } +} + +bool IsPvEBoxing() +{ + return g_PvEBoxingMode; +} + +static void RoundStart() +{ + g_PvETriggers.Clear(); + g_TimeUntilBossSpawns = GetRandomFloat(10.0, 20.0) + GetGameTime(); + g_IsPvEActive = false; + g_HasBossSpawned = false; + g_ActiveBosses.Clear(); + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + CBaseEntity trigger = CBaseEntity(ent); + if (trigger.IsValid()) + { + char name[50]; + trigger.GetPropString(Prop_Data, "m_iName", name, sizeof(name)); + if (StrContains(name, "sf2_pve_trigger", false) == 0) + { + float mins[3], maxs[3]; + trigger.GetPropVector(Prop_Send, "m_vecMins", mins); + trigger.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + float range = ((maxs[0] + maxs[1]) / 2.0) + (FloatAbs(mins[0] + mins[1]) / 2.0); + if (range > 2000.0) + { + g_PvETriggers.Push(EntIndexToEntRef(ent)); + } + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "sf2_trigger_pve")) != -1) + { + SF2TriggerPvEEntity trigger = SF2TriggerPvEEntity(ent); + if (trigger.IsValid()) + { + if (trigger.IsBossPvE) + { + g_PvETriggers.Push(EntIndexToEntRef(ent)); + } + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + g_PlayerEnteredPvETriggers[client.index] = new ArrayList(); + + g_PlayerShowBossHealthPvE[client.index] = false; + + PvE_ForceResetPlayerPvEData(client.index); + + if (IsClientAuthorized(client.index)) + { + OnClientAuthorized(client.index, NULL_STRING); + } +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + PvE_SetPlayerPvEState(client.index, false, false); + + if (g_PlayerEnteredPvETriggers[client.index] != null) + { + delete g_PlayerEnteredPvETriggers[client.index]; + g_PlayerEnteredPvETriggers[client.index] = null; + } + + if (g_MusicDataBase != null && !IsFakeClient(client.index) && g_IsMusicCached[client.index]) + { + int id = GetSteamAccountID(client.index); + if (id != 0) + { + Transaction action = new Transaction(); + + char formatter[512]; + for (int i = 0; i < MAX_MUSICS; i++) + { + FormatEx(formatter, sizeof(formatter), "UPDATE music_selection_%i SET " + ... "music_file = '%s', " + ... "music_volume = %f, " + ... "music_loop_time = %f " + ... "WHERE steamid = %d;", + i, + g_MusicFile[client.index][i], + g_MusicVolume[client.index][i], + g_MusicLoopTime[client.index][i], + id); + + action.AddQuery(formatter); + } + + g_MusicDataBase.Execute(action, _, Database_Fail, _, DBPrio_High); + } + } + + g_IsMusicCached[client.index] = false; +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + PvE_SetPlayerPvEState(client.index, false, false); + + g_PlayerIsLeavingPvE[client.index] = false; +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + if (client.IsInPvE) + { + g_PlayerIsLeavingPvE[client.index] = false; + } + + PvE_SetPlayerPvEState(client.index, false, false); + } +} + +static void OnPlayerAverageUpdate(SF2_BasePlayer client) +{ + if (!client.IsInPvE && !g_PlayerShowBossHealthPvE[client.index]) + { + return; + } + + if (!client.IsEliminated && g_PlayerShowBossHealthPvE[client.index]) + { + return; + } + + static int hudColorBossBar[3] = { 43, 103, 255 }; + char buffer[256]; + + ArrayList bosses = GetActivePvEBosses(); + char buffer2[64]; + char formatter[128], name[SF2_MAX_NAME_LENGTH]; + for (int i2 = 0; i2 < bosses.Length; i2++) + { + if (i2 >= 4) + { + continue; + } + name[0] = '\0'; + SF2_ChaserEntity chaser = SF2_ChaserEntity(EntRefToEntIndex(bosses.Get(i2))); + if (chaser.IsValid() && chaser.Controller.IsValid()) + { + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); + data.Names.GetString(1, name, sizeof(name)); + if (!data.DisplayPvEHealth) + { + continue; + } + } + + if (bosses.Length > 1 && i2 > 0) + { + FormatEx(buffer2, sizeof(buffer2), "\n"); + } + + CBaseCombatCharacter boss = CBaseCombatCharacter(EntRefToEntIndex(bosses.Get(i2))); + int health = boss.GetProp(Prop_Data, "m_iHealth"); + if (name[0] != '\0') + { + FormatEx(formatter, sizeof(formatter), "%s: %d", name, health); + } + else + { + FormatEx(formatter, sizeof(formatter), "%d", health); + } + + StrCat(buffer2, sizeof(buffer2), formatter); + StrCat(buffer, sizeof(buffer), buffer2); + SetHudTextParams(-1.0, 0.15, 0.25, hudColorBossBar[0], hudColorBossBar[1], hudColorBossBar[2], 225, 0, 1.0, 0.07, 0.3); + ShowSyncHudText(client.index, g_HudSync2, buffer); + } +} + +static Action Timer_TeleportPlayerToPvE(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (g_PlayerProxy[client]) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvERespawnTimer[client]) + { + return Plugin_Stop; + } + g_PlayerPvERespawnTimer[client] = null; + + ArrayList spawnPointList = new ArrayList(); + + int ent = -1; + + { + float myPos[3]; + float mins[3], maxs[3]; + GetEntPropVector(client, Prop_Send, "m_vecMins", mins); + GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); + + char name[32]; + + ent = -1; + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); + + if (!StrContains(name, "spawn_loot", false)) + { + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", myPos); + + spawnPointList.Push(ent); + } + } + } + + int num; + if ((num = spawnPointList.Length) > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, num - 1)); + } + + delete spawnPointList; + + if (num > 0) + { + float pos[3], ang[3]; + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); + TeleportEntity(client, pos, ang, { 0.0, 0.0, 0.0 }); + + EmitAmbientSound(SF2_PVP_SPAWN_SOUND, pos, _, SNDLEVEL_NORMAL, _, 1.0); + } + return Plugin_Stop; +} + +void PvE_ForceResetPlayerPvEData(int client) +{ + g_PlayerInPvE[client] = false; + g_PlayerPvETimer[client] = null; + g_PlayerPvETimerCount[client] = 0; + g_PlayerPvERespawnTimer[client] = null; + g_PlayerIsLeavingPvE[client] = false; + + if (g_PlayerEnteredPvETriggers[client] != null) + { + g_PlayerEnteredPvETriggers[client].Clear(); + } +} + +void PvE_SetPlayerPvEState(int client, bool status, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool oldInPvE = g_PlayerInPvE[player.index]; + if (status == oldInPvE) + { + return; // no change + } + + g_PlayerInPvE[player.index] = status; + if (!status && g_PlayerCurrentPvEMusic[client][0] != '\0') + { + StopSound(client, SNDCHAN_AUTO, g_PlayerCurrentPvEMusic[client]); + g_PlayerCurrentPvEMusic[client][0] = '\0'; + } + + if (status && player.IsInPvP) + { + PvP_ForceResetPlayerPvPData(player.index); + PvP_SetPlayerPvPState(player.index, false, false, false); + } + g_PlayerPvETimer[player.index] = null; + g_PlayerPvERespawnTimer[player.index] = null; + g_PlayerPvETimerCount[player.index] = 0; + + if (status) + { + player.ChangeCondition(TFCond_UberchargedCanteen, true); + player.ChangeCondition(TFCond_UberchargedHidden, true); + player.ChangeCondition(TFCond_Ubercharged, true); + player.ChangeCondition(TFCond_UberchargedOnTakeDamage, true); + player.ChangeCondition(TFCond_Taunting, true); + player.ChangeCondition(TFCond_UberchargedCanteen, _, 1.0); + + Call_StartForward(g_OnPlayerEnterPvE); + Call_PushCell(player.index); + Call_Finish(); + } + else + { + Call_StartForward(g_OnPlayerExitPvE); + Call_PushCell(player.index); + Call_Finish(); + } + + if (regenerate) + { + // Regenerate player but keep health the same. + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +void PvE_OnTriggerStartTouch(int trigger, int other) +{ + char name[64]; + GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); + + // The reason I'm not searching for the PvE triggers array is because of maps like Cliffroad and Mountain Complex + // where the PvE arenas are too small but still serve as shooting ranges. + if (StrContains(name, "sf2_pve_trigger", false) == 0 || SF2TriggerPvEEntity(trigger).IsValid()) + { + if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + { + if (!g_PlayerEliminated[other] && !DidClientEscape(other)) + { + return; + } + //Use valve's kill code if the player is stuck. + if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) + { + TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); + } + //Resize the player. + SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); + + int entRef = EntIndexToEntRef(trigger); + if (g_PlayerEnteredPvETriggers[other].FindValue(entRef) == -1) + { + g_PlayerEnteredPvETriggers[other].Push(entRef); + } + + if (IsClientInPvE(other)) + { + if (g_PlayerIsLeavingPvE[other]) + { + // Player left and came back again, but is still in PvP mode. + g_PlayerPvETimerCount[other] = 0; + g_PlayerPvETimer[other] = null; + g_PlayerIsLeavingPvE[other] = false; + } + } + else + { + PvE_SetPlayerPvEState(other, true); + if (g_IsPvEActive && g_PvETriggers.FindValue(entRef) != -1) + { + if (g_CustomMusicOverride[0] != '\0') + { + EmitSoundToClient(other, g_CustomMusicOverride, _, _, _, _, 0.75); + strcopy(g_PlayerCurrentPvEMusic[other], sizeof(g_PlayerCurrentPvEMusic[]), g_CustomMusicOverride); + } + else + { + ArrayList musics = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + ArrayList volumes = new ArrayList(); + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[other][i][0] != '\0') + { + musics.PushString(g_MusicFile[other][i]); + volumes.Push(g_MusicVolume[other][i]); + } + } + if (musics.Length > 0) + { + char preferredMusic[PLATFORM_MAX_PATH]; + int random = GetRandomInt(0, musics.Length - 1); + musics.GetString(random, preferredMusic, sizeof(preferredMusic)); + EmitSoundToClient(other, preferredMusic, _, _, _, _, volumes.Get(random)); + strcopy(g_PlayerCurrentPvEMusic[other], sizeof(g_PlayerCurrentPvEMusic[]), preferredMusic); + } + delete volumes; + delete musics; + } + } + } + } + } +} + +void PvE_OnTriggerEndTouch(int trigger, int other) +{ + if (IsValidClient(other)) + { + if (g_PlayerEnteredPvETriggers[other] != null) + { + int triggerEntRef = EnsureEntRef(trigger); + for (int i = g_PlayerEnteredPvETriggers[other].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredPvETriggers[other].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredPvETriggers[other].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredPvETriggers[other].Erase(i); + } + } + } + + if (IsClientInPvE(other)) + { + if (g_PlayerEnteredPvETriggers[other].Length == 0) + { + g_PlayerPvETimerCount[other] = 4; + if (g_PlayerPvETimerCount[other] != 0) + { + g_PlayerPvETimer[other] = CreateTimer(1.0, Timer_PlayerPvELeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerIsLeavingPvE[other] = true; + } + else + { + g_PlayerPvETimer[other] = CreateTimer(0.1, Timer_PlayerPvELeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static Action Timer_PlayerPvELeaveCountdown(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvETimer[client]) + { + return Plugin_Stop; + } + + if (!IsClientInPvE(client)) + { + return Plugin_Stop; + } + + if (g_PlayerPvETimerCount[client] <= 0) + { + PvE_SetPlayerPvEState(client, false); + g_PlayerIsLeavingPvE[client] = false; + + // Force them to their melee weapon and stop taunting, to prevent tposing and what not. + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + TF2_RemoveCondition(client, TFCond_Taunting); + return Plugin_Stop; + } + + g_PlayerPvETimerCount[client]--; + + //if (!g_PlayerProxyAvailableInForce[client]) + { + SetHudTextParams(-1.0, 0.75, + 1.0, + 255, 255, 255, 255, + _, + _, + 0.25, 1.25); + + ShowSyncHudText(client, g_HudSync, "%T", "SF2 Exiting PvE Arena", client, g_PlayerPvETimerCount[client]); + } + + return Plugin_Continue; +} + +bool IsClientInPvE(int client) +{ + return g_PlayerInPvE[client]; +} + +static void SpawnPvEBoss(const char[] override = "") +{ + if (g_CustomBosses.Length == 0 && g_SlenderBosses.Length == 0) + { + return; + } + + int ent = -1; + char targetName[64]; + while ((ent = FindEntityByClassname(ent, "logic_relay")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (targetName[0] != '\0' && strcmp(targetName, "boss_start", false) == 0) + { + AcceptEntityInput(ent, "Trigger"); + break; + } + } + + ArrayList spawnPointList = new ArrayList(); + + ent = -1; + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (!StrContains(targetName, "minigames_boss_spawnpoint", false)) + { + spawnPointList.Push(ent); + } + } + + if (spawnPointList.Length > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, spawnPointList.Length - 1)); + } + + delete spawnPointList; + + if (!IsValidEntity(ent)) + { + return; + } + + g_IsPvEActive = true; + bool shouldDoCustom = false; + shouldDoCustom = g_CustomBosses.Length > 0 && GetRandomInt(0, 1) == 1; + if (override[0] != '\0') + { + shouldDoCustom = g_CustomBosses.FindString(override) != -1; + } + int bossChosen; + if (shouldDoCustom || g_SlenderBosses.Length == 0) + { + char boss[64]; + float spawnPos[3], spawnAng[3]; + CBaseEntity(ent).GetAbsOrigin(spawnPos); + CBaseEntity(ent).GetAbsAngles(spawnAng); + bossChosen = GetRandomInt(0, g_CustomBosses.Length - 1); + if (override[0] != '\0') + { + bossChosen = g_CustomBosses.FindString(override); + if (bossChosen == -1) + { + bossChosen = GetRandomInt(0, g_CustomBosses.Length - 1); + } + } + g_CustomBosses.GetString(bossChosen, boss, sizeof(boss)); + Call_StartForward(g_OnSelectedPvEBoss); + Call_PushString(boss); + Call_PushArray(spawnPos, sizeof(spawnPos)); + Call_PushArray(spawnAng, sizeof(spawnAng)); + Call_Finish(); + } + else + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + float spawnPos[3]; + CBaseEntity(ent).GetAbsOrigin(spawnPos); + bossChosen = GetRandomInt(0, g_SlenderBosses.Length - 1); + if (override[0] != '\0') + { + bossChosen = g_SlenderBosses.FindString(override); + if (bossChosen == -1) + { + bossChosen = GetRandomInt(0, g_SlenderBosses.Length - 1); + } + } + g_SlenderBosses.GetString(bossChosen, profile, sizeof(profile)); + SF2NPC_BaseNPC npc = AddProfile(profile); + if (npc.IsValid()) + { + npc.Spawn(spawnPos); + SF2BossProfileData data; + data = npc.GetProfileData(); + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.MusicSoundsNormal; + SF2ChaserBossProfileData chaserData; + chaserData = view_as(npc).GetProfileData(); + g_PvEBoxingMode = chaserData.BoxingBoss; + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), g_CustomMusicOverride, sizeof(g_CustomMusicOverride)); + } + g_ActiveBosses.Push(EntIndexToEntRef(npc.EntIndex)); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC testNPC = SF2NPC_BaseNPC(i); + if (!testNPC.IsValid()) + { + continue; + } + + if (testNPC.CompanionMaster == npc) + { + testNPC.Spawn(spawnPos); + g_ActiveBosses.Push(EntIndexToEntRef(testNPC.EntIndex)); + + SF2BossProfileData tempData; + tempData = testNPC.GetProfileData(); + + if (tempData.CopiesInfo.Enabled[1]) + { + char tempProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + testNPC.GetProfile(tempProfile, sizeof(tempProfile)); + for (int i2 = 0; i2 < tempData.CopiesInfo.MaxCopies[1]; i2++) + { + SF2NPC_BaseNPC copy = AddProfile(tempProfile, _, testNPC); + if (!copy.IsValid()) + { + continue; + } + copy.Spawn(spawnPos); + g_ActiveBosses.Push(EntIndexToEntRef(copy.EntIndex)); + } + } + } + } + + if (data.CopiesInfo.Enabled[1]) + { + for (int i = 0; i < data.CopiesInfo.MaxCopies[1]; i++) + { + SF2NPC_BaseNPC copy = AddProfile(profile, _, npc); + if (!copy.IsValid()) + { + continue; + } + copy.Spawn(spawnPos); + g_ActiveBosses.Push(EntIndexToEntRef(copy.EntIndex)); + } + } + } + } + + g_HasBossSpawned = true; +} + +static Action Command_DoPvE(int client, int args) +{ + if (!g_DoesPvEExist) + { + CPrintToChat(client, "{royalblue}%t {default}A PvE arena does not exist.", "SF2 Prefix"); + return Plugin_Handled; + } + + if (g_IsPvEActive) + { + CPrintToChat(client, "{royalblue}%t {default}You may not reactivate the PvE arena.", "SF2 Prefix"); + return Plugin_Handled; + } + + if (g_CustomBosses.Length == 0 && g_SlenderBosses.Length == 0) + { + return Plugin_Handled; + } + + char override[64]; + GetCmdArg(1, override, sizeof(override)); + SpawnPvEBoss(override); + + return Plugin_Handled; +} + +static Action Command_AddPvEMusic(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sf2_add_pve_music "); + return Plugin_Handled; + } + + char soundFile[PLATFORM_MAX_PATH]; + GetCmdArg(1, soundFile, sizeof(soundFile)); + + if (soundFile[0] == '\0') + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Invalid File", client); + return Plugin_Handled; + } + + bool isAvailable = false; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[client][i][0] == '\0') + { + isAvailable = true; + strcopy(g_MusicFile[client][i], sizeof(g_MusicFile[][]), soundFile); + break; + } + + if (strcmp(g_MusicFile[client][i], soundFile, false) == 0) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Add Unique Track", client); + return Plugin_Handled; + } + } + + if (!isAvailable) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Tracks Unavailable", client); + return Plugin_Handled; + } + + PrecacheSound(soundFile); + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Added Track", client, soundFile); + + ClientSaveCookies(client); + + return Plugin_Handled; +} + +static Action Command_ShowHealth(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + g_PlayerShowBossHealthPvE[client] = !g_PlayerShowBossHealthPvE[client]; + CPrintToChat(client, "{royalblue}%t {default}%s seeing the PvE boss health anywhere.", "SF2 Prefix", g_PlayerShowBossHealthPvE[client] ? "Enabled" : "Disabled"); + + return Plugin_Handled; +} + +static void DisplayMusicSelectionPvE(int client) +{ + Menu newMenu = new Menu(PvEMenu_MusicSelection); + ArrayList musics = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[client][i][0] != '\0') + { + musics.PushString(g_MusicFile[client][i]); + } + } + for (int i = 0; i < musics.Length; i++) + { + char music[PLATFORM_MAX_PATH]; + musics.GetString(i, music, sizeof(music)); + newMenu.AddItem(music, music); + } + delete musics; + newMenu.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 PvE Music Settings Title", client); + newMenu.ExitBackButton = true; + newMenu.Display(client, MENU_TIME_FOREVER); + g_MenuMusic[client][0] = '\0'; +} + +static int Menu_SettingsPvE(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack) + { + g_MenuSettings.Display(param1, 30); + } + } + case MenuAction_Select: + { + bool hasMusic = false; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[param1][i][0] != '\0') + { + hasMusic = true; + break; + } + } + if (!hasMusic) + { + g_MenuSettingsPvE.Display(param1, 30); + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE No Tracks 1-3", param1); + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE No Tracks 2-3", param1); + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE No Tracks 3-3", param1); + } + else + { + DisplayMusicSelectionPvE(param1); + } + } + } + return 0; +} + +static int PvEMenu_MusicSelection(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack) + { + g_MenuSettings.Display(param1, 30); + } + } + case MenuAction_Select: + { + char music[PLATFORM_MAX_PATH]; + float volume = 0.75; + menu.GetItem(param2, music, sizeof(music)); + Menu newMenu = new Menu(PvEMenu_SelectedMusic); + newMenu.SetTitle("%t %s\n \n", "SF2 Prefix", music); + newMenu.ExitBackButton = true; + + char formatter[256]; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (strcmp(g_MusicFile[param1][i], music, false) == 0) + { + volume = g_MusicVolume[param1][i]; + break; + } + } + FormatEx(formatter, sizeof(formatter), "%T", "SF2 PvE Set Volume", param1, RoundToNearest(volume * 100.0)); + newMenu.AddItem("0", formatter); + FormatEx(formatter, sizeof(formatter), "%T", "SF2 PvE Remove Music", param1); + newMenu.AddItem("1", formatter); + newMenu.Display(param1, MENU_TIME_FOREVER); + strcopy(g_MenuMusic[param1], sizeof(g_MenuMusic[]), music); + } + } + return 0; +} + +static int PvEMenu_SelectedMusic(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select) + { + switch (param2) + { + case 0: + { + char buffer[512]; + Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Music Volume Title", param1); + + Panel panel = new Panel(); + panel.SetTitle(buffer); + + panel.DrawItem("0%"); + panel.DrawItem("25%"); + panel.DrawItem("50%"); + panel.DrawItem("75% (Default)"); + panel.DrawItem("100%"); + + panel.Send(param1, Panel_SettingsMusicVolume, 30); + delete panel; + } + + case 1: + { + for (int i = 0; i < MAX_MUSICS; i++) + { + if (strcmp(g_MusicFile[param1][i], g_MenuMusic[param1], false) == 0) + { + g_MusicFile[param1][i][0] = '\0'; + g_MusicVolume[param1][i] = 0.75; + g_MusicLoopTime[param1][i] = 0.0; + break; + } + } + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Removed Music", param1, g_MenuMusic[param1]); + DisplayMusicSelectionPvE(param1); + } + } + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayMusicSelectionPvE(param1); + } + } + return 0; +} + +static int Panel_SettingsMusicVolume(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select) + { + float volume; + for (int i = 1; i < param2; i++) + { + volume += 0.25; + } + for (int i = 0; i < MAX_MUSICS; i++) + { + if (strcmp(g_MusicFile[param1][i], g_MenuMusic[param1], false) == 0) + { + g_MusicVolume[param1][i] = volume; + break; + } + } + CPrintToChat(param1, "%t", "SF2 Music Volum Changed", RoundToNearest(volume * 100.0)); + ClientSaveCookies(param1); + + DisplayMusicSelectionPvE(param1); + } + return 0; +} + +// These register boss entity names to choose from +void RegisterPvEBoss(char[] bossName) +{ + g_CustomBosses.PushString(bossName); +} + +void UnregisterPveBoss(char[] bossName) +{ + int index = g_CustomBosses.FindString(bossName); + if (index != -1) + { + g_CustomBosses.Erase(index); + } +} + +void RegisterPvESlenderBoss(char profile[SF2_MAX_PROFILE_NAME_LENGTH]) +{ + g_SlenderBosses.PushString(profile); +} + +void UnregisterPvESlenderBoss(char profile[SF2_MAX_PROFILE_NAME_LENGTH]) +{ + int index = g_SlenderBosses.FindString(profile); + if (index != -1) + { + g_SlenderBosses.Erase(index); + } +} + +void KillPvEBoss(int boss) +{ + int index = g_ActiveBosses.FindValue(EntIndexToEntRef(boss)); + if (index != -1) + { + g_ActiveBosses.Erase(index); + } + else + { + return; + } + if (g_ActiveBosses.Length == 0) + { + g_CustomMusicOverride[0] = '\0'; + g_IsPvEActive = false; + char targetName[64]; + int bossIndex = NPCGetFromEntIndex(boss); + float time = 5.0; + if (bossIndex != -1) + { + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); + time = data.PvETeleportEndTimer; + } + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || !client.IsInPvE) + { + continue; + } + + if (g_PlayerCurrentPvEMusic[client.index][0] != '\0') + { + StopSound(client.index, SNDCHAN_AUTO, g_PlayerCurrentPvEMusic[client.index]); + } + + g_PlayerPvERespawnTimer[client.index] = CreateTimer(time, Timer_TeleportPlayerToPvE, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "logic_relay")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (targetName[0] != '\0' && strcmp(targetName, "boss_end", false) == 0) + { + AcceptEntityInput(ent, "Trigger"); + break; + } + } + + CreateTimer(time, Timer_RemoveAllPvEBosses, _, TIMER_FLAG_NO_MAPCHANGE); + } +} + +ArrayList GetActivePvEBosses() +{ + return g_ActiveBosses; +} + +static Action Timer_RemoveAllPvEBosses(Handle timer) +{ + if (!g_Enabled) + { + return Plugin_Stop; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + SF2BossProfileData data; + data = npc.GetProfileData(); + if (!data.IsPvEBoss) + { + continue; + } + + npc.Remove(); + } + + return Plugin_Stop; +} + +void PvE_InitializeAPI() +{ + CreateNative("SF2_IsClientInPvE", Native_IsClientInPvE); + CreateNative("SF2_RegisterPvEBoss", Native_RegisterPvEBoss); + CreateNative("SF2_SetOverridePvEMusic", Native_SetOverridePvEMusic); + CreateNative("SF2_UnregisterPvEBoss", Native_UnregisterPvEBoss); + g_OnSelectedPvEBoss = new GlobalForward("SF2_OnSelectedPvEBoss", ET_Ignore, Param_String, Param_Array, Param_Array); + g_OnPlayerEnterPvE = new GlobalForward("SF2_OnClientEnterPvE", ET_Ignore, Param_Cell); + g_OnPlayerExitPvE = new GlobalForward("SF2_OnClientExitPvE", ET_Ignore, Param_Cell); + CreateNative("SF2_AddPvEBoss", Native_AddPvEBoss); + CreateNative("SF2_KillPvEBoss", Native_KillPvEBoss); + CreateNative("SF2_GetActivePvEBosses", Native_GetActivePvEBosses); +} + +static any Native_IsClientInPvE(Handle plugin, int numParams) +{ + return IsClientInPvE(GetNativeCell(1)); +} + +static any Native_RegisterPvEBoss(Handle plugin, int numParams) +{ + char boss[64]; + GetNativeString(1, boss, sizeof(boss)); + RegisterPvEBoss(boss); + return 0; +} + +static any Native_SetOverridePvEMusic(Handle plugin, int numParams) +{ + char music[PLATFORM_MAX_PATH]; + GetNativeString(1, music, sizeof(music)); + strcopy(g_CustomMusicOverride, sizeof(g_CustomMusicOverride), music); + return 0; +} + +static any Native_UnregisterPvEBoss(Handle plugin, int numParams) +{ + char boss[64]; + GetNativeString(1, boss, sizeof(boss)); + UnregisterPveBoss(boss); + return 0; +} + +static any Native_AddPvEBoss(Handle plugin, int numParams) +{ + g_ActiveBosses.Push(GetNativeCell(1)); + return 0; +} + +static any Native_KillPvEBoss(Handle plugin, int numParams) +{ + KillPvEBoss(GetNativeCell(1)); + return 0; +} + +static any Native_GetActivePvEBosses(Handle plugin, int numParams) +{ + return GetActivePvEBosses(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index c54c6824..0d359e80 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -6,10 +6,9 @@ #pragma semicolon 1 #define SF2_PVP_SPAWN_SOUND "items/pumpkin_drop.wav" -#define FLAME_HIT_DELAY 0.05 -ConVar g_PvPArenaLeaveTimeConVar = null; -ConVar g_PvPArenaProjectileZapConVar = null; +static ConVar g_PvPArenaLeaveTimeConVar = null; +static ConVar g_PvPArenaProjectileZapConVar = null; static const char g_PvPProjectileClasses[][] = { @@ -23,6 +22,7 @@ static const char g_PvPProjectileClasses[][] = "tf_projectile_balloffire", "tf_projectile_jar", "tf_projectile_jar_milk", + "tf_projectile_jar_gas", "tf_projectile_pipe", "tf_projectile_pipe_remote", "tf_projectile_stun_ball", @@ -35,17 +35,45 @@ static const char g_PvPProjectileClasses[][] = "tf_projectile_energy_ring", "tf_projectile_syringe" }; + static const char g_PvPProjectileClassesNoTouch[][] = { "tf_projectile_flare" }; +static const char g_PvPProjectileClassesNoHook[][] = +{ + "tf_projectile_pipe_remote", + "tf_projectile_balloffire" +}; + +static int g_SpectatorItemIDs[] = +{ + TF_WEAPON_BUFF_ITEM, // CTFPlayerShared::PulseRageBuff + TF_WEAPON_FLAMETHROWER, // CTFFlameThrower::SecondaryAttack + TF_WEAPON_FLAME_BALL, // CWeaponFlameBall::SecondaryAttack + TF_WEAPON_SNIPERRIFLE, // CTFPlayer::FireBullet + TF_WEAPON_KNIFE, // CTFKnife::BackstabVMThink + TF_WEAPON_RAYGUN_REVENGE, // CTFFlareGun_Revenge::ExtinguishPlayerInternal +}; + +static int g_EnemyItemIDs[] = +{ + TF_WEAPON_HANDGUN_SCOUT_PRIMARY, // CTFPistol_ScoutPrimary::Push + TF_WEAPON_GRAPPLINGHOOK, // CTFGrapplingHook::ActivateRune +}; + static bool g_PlayerInPvP[MAXTF2PLAYERS]; static bool g_PlayerIsLeavingPvP[MAXTF2PLAYERS]; Handle g_PlayerPvPTimer[MAXTF2PLAYERS]; Handle g_PlayerPvPRespawnTimer[MAXTF2PLAYERS]; static int g_PlayerPvPTimerCount[MAXTF2PLAYERS]; static ArrayList g_PlayerEnteredPvPTriggers[MAXTF2PLAYERS] = { null, ... }; +static float g_PlayerMedigunDrainTime[MAXTF2PLAYERS]; +static int g_PlayerOriginalTeam[MAXTF2PLAYERS]; + +static TFTeam g_PreHookTeam[2049]; +static TFTeam g_PreHookDisguiseTeam[MAXTF2PLAYERS]; //Blood static int g_PvPUserIdLastTrace; @@ -53,6 +81,45 @@ static float g_TimeLastTrace; static ArrayList g_PvPBallsOfFire; +static GlobalForward g_OnPlayerEnterPvP; +static GlobalForward g_OnPlayerExitPvP; + +#define TICK_NEVER_THINK -1.0 +static Handle g_SDKGetNextThink; +static Handle g_SDKGetGlobalTeam; +static Handle g_SDKChangeTeam; +static Handle g_SDKAddPlayer; +static Handle g_SDKRemovePlayer; +static Handle g_SDKAddObject; +static Handle g_SDKRemoveObject; +static Handle g_SDKGetPenetrationType; + +static DynamicHook g_DHookCTFBaseRocketExplode; +static DynamicHook g_DHookCBaseGrenadeExplode; +static DynamicHook g_DHookVPhysicsUpdate; + +static DynamicDetour g_DDetourPhysicsDispatchThink; +static DynamicDetour g_DDetourAllowedToHealTarget; + +enum ThinkFunction +{ + ThinkFunction_None, + ThinkFunction_DispenseThink, + ThinkFunction_SentryThink, + ThinkFunction_SapperThink, +} + +static ThinkFunction g_ThinkFunction = ThinkFunction_None; + +enum +{ + PostThinkType_None, + PostThinkType_Spectator, + PostThinkType_EnemyTeam, +} + +static int g_PostThinkType; + enum struct PvPProjectile_BallOfFire { int EntIndex; @@ -73,7 +140,12 @@ enum struct PvPProjectile_BallOfFire int ent = this.EntIndex; int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); - if (!IsValidClient(ownerEntity) || otherEntity == ownerEntity || !IsClientInPvP(ownerEntity)) + if (!IsValidClient(ownerEntity) || otherEntity == ownerEntity) + { + return; + } + + if (!IsClientInPvP(ownerEntity)) { return; } @@ -84,21 +156,26 @@ enum struct PvPProjectile_BallOfFire { this.TouchedEntities.Push(otherEntity); - if (IsValidClient(otherEntity) && IsClientInPvP(otherEntity) && GetEntProp(otherEntity, Prop_Send, "m_iTeamNum") == GetEntProp(ownerEntity, Prop_Send, "m_iTeamNum")) + if (IsValidClient(otherEntity) && (IsClientInPvP(otherEntity)) && GetEntProp(otherEntity, Prop_Send, "m_iTeamNum") == GetEntProp(ownerEntity, Prop_Send, "m_iTeamNum")) { float damage = GetEntDataFloat(ent, FindSendPropInfo("CTFProjectile_BallOfFire", "m_iDeflected") + 4); - float damageBonus = TF2_IsPlayerInCondition(otherEntity, TFCond_OnFire) ? g_DragonsFuryBurningBonus.FloatValue : 1.0; + float damageBonus = TF2_IsPlayerInCondition(otherEntity, TFCond_OnFire) ? g_DragonsFuryBurningBonusConVar.FloatValue : 1.0; float damagePos[3]; GetEntPropVector(ent, Prop_Data, "m_vecOrigin", damagePos); // int damageCustom = 79; if (TF2_GetPlayerClass(otherEntity) == TFClass_Pyro) { - TF2_AddCondition(otherEntity, TFCond_BurningPyro, g_DragonsFuryBurnDuration.FloatValue * 0.5, ownerEntity); + TF2_AddCondition(otherEntity, TFCond_BurningPyro, g_DragonsFuryBurnDurationConVar.FloatValue * 0.5, ownerEntity); } TF2_IgnitePlayer(otherEntity, ownerEntity); SDKHooks_TakeDamage(otherEntity, ownerEntity, ownerEntity, damage * damageBonus, 0x1220000, GetEntPropEnt(ownerEntity, Prop_Send, "m_hActiveWeapon"), NULL_VECTOR, damagePos); + CBaseEntity primary = CBaseEntity(GetPlayerWeaponSlot(ownerEntity, TFWeaponSlot_Primary)); + if (primary.IsValid()) + { + primary.SetPropFloat(Prop_Send, "m_flRechargeScale", 1.5); + } } } } @@ -117,21 +194,150 @@ void PvP_Initialize() g_PvPBallsOfFire = new ArrayList(sizeof(PvPProjectile_BallOfFire)); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnGameFramePFwd.AddFunction(null, GameFrame); + g_OnRoundStartPFwd.AddFunction(null, RoundStart); + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPrePFwd.AddFunction(null, OnPlayerDeathPre); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + + GameData gameData = new GameData("sf2"); + + g_DHookCTFBaseRocketExplode = DynamicHook.FromConf(gameData, "CTFBaseRocket::Explode"); + if (g_DHookCTFBaseRocketExplode == null) + { + SetFailState("Failed to create hook CTFBaseRocket::Explode from gamedata!"); + } + + g_DHookCBaseGrenadeExplode = DynamicHook.FromConf(gameData, "CBaseGrenade::Explode"); + if (g_DHookCBaseGrenadeExplode == null) + { + SetFailState("Failed to create hook CTFBaseRocket::Explode from gamedata!"); + } + + g_DHookVPhysicsUpdate = DynamicHook.FromConf(gameData, "CBaseEntity::VPhysicsUpdate"); + if (g_DHookVPhysicsUpdate == null) + { + SetFailState("Failed to create hook CBaseEntity::VPhysicsUpdate from gamedata!"); + } + + g_DDetourPhysicsDispatchThink = DynamicDetour.FromConf(gameData, "CBaseEntity::PhysicsDispatchThink"); + if (g_DDetourPhysicsDispatchThink == null) + { + SetFailState("Failed to create hook CBaseEntity::PhysicsDispatchThink from gamedata!"); + } + + g_DDetourPhysicsDispatchThink.Enable(Hook_Pre, PhysicsDispatchThinkPre); + g_DDetourPhysicsDispatchThink.Enable(Hook_Post, PhysicsDispatchThinkPost); + + g_DDetourAllowedToHealTarget = DynamicDetour.FromConf(gameData, "CWeaponMedigun::AllowedToHealTarget"); + if (g_DDetourAllowedToHealTarget == null) + { + SetFailState("Failed to create hook CWeaponMedigun::AllowedToHealTarget from gamedata!"); + } + + g_DDetourAllowedToHealTarget.Enable(Hook_Pre, AllowedToHealTargetPre); + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CBaseEntity::GetNextThink"); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer, VDECODE_FLAG_ALLOWNULL); + PrepSDKCall_SetReturnInfo(SDKType_Float, SDKPass_Plain); + g_SDKGetNextThink = EndPrepSDKCall(); + if (g_SDKGetNextThink == null) + { + SetFailState("Failed to retrieve CBaseEntity::GetNextThink offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Static); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "GetGlobalTeam"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + g_SDKGetGlobalTeam = EndPrepSDKCall(); + if (g_SDKGetGlobalTeam == null) + { + SetFailState("Failed to retrieve GetGlobalTeam offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CBaseEntity::ChangeTeam"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + g_SDKChangeTeam = EndPrepSDKCall(); + if (g_SDKChangeTeam == null) + { + SetFailState("Failed to retrieve CBaseEntity::ChangeTeam offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTeam::AddPlayer"); + PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer); + g_SDKAddPlayer = EndPrepSDKCall(); + if (g_SDKAddPlayer == null) + { + SetFailState("Failed to retrieve CTeam::AddPlayer offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTeam::RemovePlayer"); + PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer); + g_SDKRemovePlayer = EndPrepSDKCall(); + if (g_SDKRemovePlayer == null) + { + SetFailState("Failed to retrieve CTeam::RemovePlayer offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CTFTeam::AddObject"); + PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); + g_SDKAddObject = EndPrepSDKCall(); + if (g_SDKAddObject == null) + { + SetFailState("Failed to retrieve CTFTeam::AddObject offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CTFTeam::RemoveObject"); + PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); + g_SDKRemoveObject = EndPrepSDKCall(); + if (g_SDKRemoveObject == null) + { + SetFailState("Failed to retrieve CTFTeam::RemoveObject offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTFSniperRifle::GetPenetrateType"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + g_SDKGetPenetrationType = EndPrepSDKCall(); + if (g_SDKGetPenetrationType == null) + { + SetFailState("Failed to retrieve CTFSniperRifle::GetPenetrateType offset from gamedata!"); + } + + delete gameData; + AddTempEntHook("TFBlood", TempEntHook_PvPBlood); AddTempEntHook("World Decal", TempEntHook_PvPDecal); AddTempEntHook("Entity Decal", TempEntHook_PvPDecal); + + HookEvent("fish_notice", OnPlayerDeathEventPre, EventHookMode_Pre); + HookEvent("fish_notice__arm", OnPlayerDeathEventPre, EventHookMode_Pre); + HookEvent("slap_notice", OnPlayerDeathEventPre, EventHookMode_Pre); + HookEvent("player_death", OnPlayerDeathEventPre, EventHookMode_Pre); } void PvP_SetupMenus() { - g_MenuSettingsPvP = CreateMenu(Menu_SettingsPvP); - SetMenuTitle(g_MenuSettingsPvP, "%t%t\n \n", "SF2 Prefix", "SF2 Settings PvP Menu Title"); - AddMenuItem(g_MenuSettingsPvP, "0", "Toggle automatic spawning"); - AddMenuItem(g_MenuSettingsPvP, "1", "Toggle spawn protection"); - SetMenuExitBackButton(g_MenuSettingsPvP, true); + g_MenuSettingsPvP = new Menu(Menu_SettingsPvP); + g_MenuSettingsPvP.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Settings PvP Menu Title"); + g_MenuSettingsPvP.AddItem("0", "Toggle automatic spawning"); + g_MenuSettingsPvP.AddItem("1", "Toggle spawn protection"); + g_MenuSettingsPvP.ExitBackButton = true; } -void PvP_OnMapStart() +static void MapStart() { int ent = -1; while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) @@ -161,7 +367,7 @@ void PvP_OnMapStart() } } -void PvP_OnRoundStart() +static void RoundStart() { int ent = -1; while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) @@ -178,7 +384,7 @@ void PvP_OnRoundStart() //flags |= TRIGGER_PHYSICS_OBJECTS; flags |= TRIGGER_PHYSICS_DEBRIS; SetEntProp(ent, Prop_Data, "m_spawnflags", flags); - SDKHook( ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch ); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch); } } } @@ -205,25 +411,37 @@ void PvP_Precache() PrecacheSound2(SF2_PVP_SPAWN_SOUND, true); } -void PvP_OnClientPutInServer(int client) +static void OnPutInServer(SF2_BasePlayer client) { - g_PlayerEnteredPvPTriggers[client] = new ArrayList(); + if (!g_Enabled) + { + return; + } + g_PlayerEnteredPvPTriggers[client.index] = new ArrayList(); + + if (!client.IsSourceTV) + { + SDKHook(client.index, SDKHook_PreThink, ClientPreThink); + SDKHook(client.index, SDKHook_PreThinkPost, ClientPreThinkPost); + SDKHook(client.index, SDKHook_PostThink, ClientPostThink); + SDKHook(client.index, SDKHook_PostThinkPost, ClientPostThinkPost); + } - PvP_ForceResetPlayerPvPData(client); + PvP_ForceResetPlayerPvPData(client.index); } -void PvP_OnClientDisconnect(int client) +static void OnDisconnected(SF2_BasePlayer client) { - PvP_SetPlayerPvPState(client, false, false, false); + PvP_SetPlayerPvPState(client.index, false, false, false); - if (g_PlayerEnteredPvPTriggers[client] != null) + if (g_PlayerEnteredPvPTriggers[client.index] != null) { - delete g_PlayerEnteredPvPTriggers[client]; - g_PlayerEnteredPvPTriggers[client] = null; + delete g_PlayerEnteredPvPTriggers[client.index]; + g_PlayerEnteredPvPTriggers[client.index] = null; } } -void PvP_OnGameFrame() +static void GameFrame() { // Process through PvP projectiles. for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) @@ -248,67 +466,33 @@ void PvP_OnGameFrame() } } - if (changeProjectileTeam) - { - SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); - SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); - } - } - } - - // Process through PvP flame entities. - { - static float mins[3] = { -6.0, ... }; - static float maxs[3] = { 6.0, ... }; - - float flOrigin[3]; - - Handle trace = null; - int ent = -1; - int ownerEntity = INVALID_ENT_REFERENCE; - int hitEntity = INVALID_ENT_REFERENCE; - - while ((ent = FindEntityByClassname(ent, "tf_flame_manager")) != -1) - { - ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); - - if (IsValidEdict(ownerEntity)) + for (int i2 = 0; i2 < sizeof(g_PvPProjectileClassesNoHook); i2++) { - // tf_flame's initial owner SHOULD be the flamethrower that it originates from. - // If not, then something's completely bogus. - - ownerEntity = GetEntPropEnt(ownerEntity, Prop_Data, "m_hOwnerEntity"); + if (strcmp(g_PvPProjectileClasses[i], g_PvPProjectileClassesNoHook[i2], false) == 0) + { + changeProjectileTeam = false; + } } - if (IsValidClient(ownerEntity) && (IsRoundInWarmup() || IsClientInPvP(ownerEntity))) + if (changeProjectileTeam) { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", flOrigin); - - trace = TR_TraceHullFilterEx(flOrigin, flOrigin, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, ownerEntity); - hitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (IsValidEntity(hitEntity)) - { - PvP_OnFlameEntityStartTouchPost(ent, hitEntity); - } + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); } } - delete trace; } } -void PvP_OnEntityCreated(int ent, const char[] classname) +static void EntityCreated(CBaseEntity ent, const char[] classname) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x083EFF3EFF+ %i(%s)",ent,classname); + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x083EFF3EFF+ %i(%s)", ent.index, classname); #endif for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) { if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) { - SDKHook(ent, SDKHook_Spawn, Hook_PvPProjectileSpawn); - SDKHook(ent, SDKHook_SpawnPost, Hook_PvPProjectileSpawnPost); + SDKHook(ent.index, SDKHook_SpawnPost, Hook_PvPProjectileSpawnPost); break; } } @@ -317,21 +501,76 @@ void PvP_OnEntityCreated(int ent, const char[] classname) { if (strcmp(classname, g_PvPProjectileClassesNoTouch[i], false) == 0) { - SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + SDKHook(ent.index, SDKHook_Touch, Hook_PvPProjectile_OnTouch); break; } } + + if (strcmp(classname, "tf_flame_manager", false) == 0) + { + SDKHook(ent.index, SDKHook_Touch, FlameStartTouch); + SDKHook(ent.index, SDKHook_TouchPost, FlameStartTouchPost); + } + else if (strcmp(classname, "tf_gas_manager", false) == 0) + { + SDKHook(ent.index, SDKHook_Touch, GasStartTouch); + } + + if (strncmp(classname, "tf_projectile_", 14) == 0) + { + g_DHookProjectileCanCollideWithTeammates.HookEntity(Hook_Post, ent.index, Hook_PvPProjectileCanCollideWithTeammates); + + if (strcmp(classname, "tf_projectile_pipe_remote") == 0) + { + SDKHook(ent.index, SDKHook_OnTakeDamage, PipeOnTakeDamage); + SDKHook(ent.index, SDKHook_OnTakeDamagePost, PipeOnTakeDamagePost); + } + else if (strcmp(classname, "tf_projectile_flare") == 0) + { + g_DHookCTFBaseRocketExplode.HookEntity(Hook_Pre, ent.index, FlareExplodePre); + g_DHookCTFBaseRocketExplode.HookEntity(Hook_Post, ent.index, FlareExplodePost); + } + else if (strncmp(classname, "tf_projectile_jar", 17) == 0) + { + g_DHookCBaseGrenadeExplode.HookEntity(Hook_Pre, ent.index, JarExplodePre); + g_DHookCBaseGrenadeExplode.HookEntity(Hook_Post, ent.index, JarExplodePost); + } + else if (strncmp(classname, "tf_projectile_pipe", 18) == 0) + { + g_DHookVPhysicsUpdate.HookEntity(Hook_Pre, ent.index, VPhysicsUpdatePre); + g_DHookVPhysicsUpdate.HookEntity(Hook_Post, ent.index, VPhysicsUpdatePost); + } + else if (strcmp(classname, "tf_projectile_cleaver") == 0) + { + SDKHook(ent.index, SDKHook_Touch, CleaverTouch); + SDKHook(ent.index, SDKHook_TouchPost, CleaverTouchPost); + } + else if (strcmp(classname, "tf_projectile_pipe") == 0) + { + SDKHook(ent.index, SDKHook_Touch, GrenadeTouch); + SDKHook(ent.index, SDKHook_TouchPost, GrenadeTouchPost); + } + } + else if (IsEntityWeapon(ent.index)) + { + int id = GetWeaponID(ent.index); + if (id == TF_WEAPON_SNIPERRIFLE || id == TF_WEAPON_SNIPERRIFLE_DECAP || id == TF_WEAPON_SNIPERRIFLE_CLASSIC) + { + // Fixes Sniper Rifles dealing no damage to teammates + g_DHookWeaponGetCustomDamageType.HookEntity(Hook_Post, ent.index, GetCustomDamageTypePost); + } + } } -void PvP_OnEntityDestroyed(int ent, const char[] classname) +static void EntityDestroyed(CBaseEntity ent, const char[] classname) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x08FF4040FF- %i(%s)",ent,classname); + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x08FF4040FF- %i(%s)", ent.index, classname); #endif if (strcmp(classname, "tf_projectile_balloffire", false) == 0) { - int index = g_PvPBallsOfFire.FindValue( ent ); + int index = g_PvPBallsOfFire.FindValue(ent.index); if (index != -1) { PvPProjectile_BallOfFire projectileData; @@ -342,142 +581,304 @@ void PvP_OnEntityDestroyed(int ent, const char[] classname) } } -static Action Hook_PvPProjectile_OnTouch(int projectile, int client) +static float GetNextThink(int entity, const char[] context = "") { - // Check if the projectile hit a player outside of pvp area - // Without that, cannon balls can bounce players which should not happen because they are outside of pvp. - if (IsValidClient(client) && !IsClientInPvP(client)) + if (g_SDKGetNextThink != null) { - RemoveEntity(projectile); - return Plugin_Handled; + return SDKCall(g_SDKGetNextThink, entity, context); } - int throwerOffset = FindDataMapInfo(projectile, "m_hThrower"); - int ownerEntity = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); - if (ownerEntity != client && throwerOffset != -1) - { - ownerEntity = GetEntDataEnt2(projectile, throwerOffset); - } + return TICK_NEVER_THINK; +} - if (ownerEntity == client) +static Address SDKCall_GetGlobalTeam(TFTeam team) +{ + if (g_SDKGetGlobalTeam) { - RemoveEntity(projectile); - return Plugin_Handled; + return SDKCall(g_SDKGetGlobalTeam, team); } - return Plugin_Continue; + return Address_Null; } -static Action Hook_PvPProjectileSpawn(int ent) +static void CBaseEntity_ChangeTeam(int entity, TFTeam team) { - char class[64]; - GetEntityClassname(ent, class, sizeof(class)); + SDKCall(g_SDKChangeTeam, entity, team); +} - int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); - int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); +static void CTeam_AddPlayer(Address team, int client) +{ + SDKCall(g_SDKAddPlayer, team, client); +} - if (ownerEntity == -1 && throwerOffset != -1) +static void CTeam_RemovePlayer(Address team, int client) +{ + SDKCall(g_SDKRemovePlayer, team, client); +} + +static void CTeam_AddObject(Address team, int obj) +{ + SDKCall(g_SDKAddObject, team, obj); +} + +static void CTeam_RemoveObject(Address team, int obj) +{ + SDKCall(g_SDKRemoveObject, team, obj); +} + +static void ClientPreThink(int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) { - ownerEntity = GetEntDataEnt2(ent, throwerOffset); + return; } - if (IsValidClient(ownerEntity)) + if (player.Team == TFTeam_Red) { - if (IsClientInPvP(ownerEntity)) + return; + } + + float gameTime = GetGameTime(); + CBaseEntity secondary = CBaseEntity(GetPlayerWeaponSlot(player.index, TFWeaponSlot_Secondary)); + if (secondary.IsValid() && GetWeaponID(secondary.index) == TF_WEAPON_MEDIGUN && g_PlayerMedigunDrainTime[player.index] <= gameTime) + { + SF2_BasePlayer healTarget = SF2_BasePlayer(secondary.GetPropEnt(Prop_Send, "m_hHealingTarget")); + if (healTarget.IsValid && healTarget.IsAlive && (healTarget.IsInPvP)) { - SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); - SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + float damage = 6.0; + if (secondary.GetProp(Prop_Send, "m_iItemDefinitionIndex") == 411) // Quick-Fix + { + damage *= 1.4; + } + if (player.InCondition(TFCond_MegaHeal)) + { + damage *= 3.0; + } + + int damageType = DMG_ENERGYBEAM; + if (player.IsCritBoosted()) + { + damageType |= DMG_CRIT; + } + SDKHooks_TakeDamage(healTarget.index, player.index, player.index, damage, damageType, .bypassHooks = false); + g_PlayerMedigunDrainTime[player.index] = gameTime + 0.1; } } - return Plugin_Continue; + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); } -static void Hook_PvPProjectileSpawnPost(int ent) +static void ClientPreThinkPost(int client) { - if (!IsValidEntity(ent)) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) { return; } - char class[64]; - GetEntityClassname(ent, class, sizeof(class)); + if (player.Team != TFTeam_Spectator) + { + return; + } - int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); - int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); +} - if (ownerEntity == -1 && throwerOffset != -1) +static void ClientPostThink(int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) { - ownerEntity = GetEntDataEnt2(ent, throwerOffset); + return; } - if (IsValidClient(ownerEntity)) + if (player.Team == TFTeam_Red) { - if (IsClientInPvP(ownerEntity)) - { - static const char fixWeaponNotCollidingWithTeammates[][] = - { - "tf_projectile_rocket", - "tf_projectile_sentryrocket", - "tf_projectile_flare" - }; + return; + } - for (int i = 0; i < sizeof(fixWeaponNotCollidingWithTeammates); i++) - { - if (IsValidEntity(ent) && strcmp(class, fixWeaponNotCollidingWithTeammates[i], false) == 0) - { - DHookEntity(g_DHookProjectileCanCollideWithTeammates, false, ent, _, Hook_PvPProjectileCanCollideWithTeammates); - break; - } - } + // CTFPlayer::DoTauntAttack + if (TF2_IsPlayerInCondition(client, TFCond_Taunting)) + { + g_PostThinkType |= PostThinkType_Spectator; - if (strcmp(class, "tf_projectile_pipe", false) == 0 && GetEntProp(ent, Prop_Send, "m_iType") == 3) - { - /* - Loose Cannon's projectiles - KR: I'm assuming that stopping non-PvP players from getting bounced is the reason why this is implemented. - Despite hooking onto Touch, the knockback still happens half of the time. StartTouch yields the same result - so this is really the best that can be done. - */ - SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); - } - else if (strcmp(class, "tf_projectile_balloffire", false) == 0) - { - /* - Replicate projectile logic for Dragon's Fury projectiles. - KR: The projectile checks the team of its owner entity (the player), not itself. CBaseEntity::InSameTeam() - could be hooked to change this, but I think that would be too game-changing and not really worth doing for - a single case, so the enemy player logic is sort of replicated. - */ + // Allows taunt kill work on both teams + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + return; + } - PvPProjectile_BallOfFire projectileData; - projectileData.Init(ent); - g_PvPBallsOfFire.PushArray(projectileData, sizeof(projectileData)); + int activeWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); + if (!IsValidEntity(activeWeapon)) + { + return; + } - SDKHook(ent, SDKHook_TouchPost, Hook_PvPProjectileBallOfFireTouchPost); - } + // For functions that use GetEnemyTeam(), move everyone else to the enemy team + // Disabled for now but it doesn't matter since this only applies to the Shortstop + for (int i = 0; i < sizeof(g_EnemyItemIDs); i++) + { + if (GetWeaponID(activeWeapon) == g_EnemyItemIDs[i]) + { + g_PostThinkType |= PostThinkType_EnemyTeam; - if (g_PlayerPvPTimer[ownerEntity]==null) - { - SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); - SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); - } - else + TFTeam enemyTeam = GetEnemyTeam(TF2_GetClientTeam(client)); + + for (int other = 1; other <= MaxClients; other++) { - //Client is not in pvp, remove the projectile - if (g_PvPArenaProjectileZapConVar.BoolValue) + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + if (otherPlayer.IsValid && otherPlayer.index != player.index && (otherPlayer.IsInPvP)) { - PvP_ZapProjectile(ent, false); + otherPlayer.SetProp(Prop_Send, "m_iTeamNum", view_as(enemyTeam)); } } } } + + // For functions that do simple GetTeamNumber() checks, move ourselves to spectator team + for (int i = 0; i < sizeof(g_SpectatorItemIDs); i++) + { + // Don't let losing team attack with those weapons + if (GameRules_GetRoundState() == RoundState_TeamWin && TF2_GetClientTeam(client) != view_as(GameRules_GetProp("m_iWinningTeam"))) + { + break; + } + + if (GetWeaponID(activeWeapon) == g_SpectatorItemIDs[i]) + { + g_PostThinkType |= PostThinkType_Spectator; + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } } -static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntity) +static void ClientPostThinkPost(int client) { - int index = g_PvPBallsOfFire.FindValue(projectile); - if (index != -1) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) + { + return; + } + + // Change everything back to how it was accordingly + if ((g_PostThinkType & PostThinkType_Spectator) != 0) + { + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + + if ((g_PostThinkType & PostThinkType_EnemyTeam) != 0) + { + for (int other = 1; other <= MaxClients; other++) + { + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + if (otherPlayer.IsValid && otherPlayer.index != player.index && otherPlayer.IsInPvP) + { + otherPlayer.SetProp(Prop_Send, "m_iTeamNum", view_as(GetEnemyTeam(TF2_GetClientTeam(otherPlayer.index)))); + } + } + } + + g_PostThinkType = PostThinkType_None; +} + +static Action Hook_PvPProjectile_OnTouch(int projectile, int client) +{ + // Check if the projectile hit a player outside of pvp area + // Without that, cannon balls can bounce players which should not happen because they are outside of pvp. + bool remove = false; + if (IsValidClient(client) && !IsClientInPvP(client)) + { + remove = true; + } + + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (owner == client) + { + remove = false; + } + + if (remove) + { + float vel[3]; + CBaseEntity(client).GetAbsVelocity(vel); + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vel); + RemoveEntity(projectile); + return Plugin_Handled; + } + + return Plugin_Continue; +} + +static void Hook_PvPProjectileSpawnPost(int ent) +{ + if (!IsValidEntity(ent)) + { + return; + } + + char class[64]; + GetEntityClassname(ent, class, sizeof(class)); + + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (ownerEntity == -1 && throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + } + + if (IsValidClient(ownerEntity)) + { + if (IsClientInPvP(ownerEntity)) + { + if (strcmp(class, "tf_projectile_pipe", false) == 0 && GetEntProp(ent, Prop_Send, "m_iType") == 3) + { + /* + Loose Cannon's projectiles + KR: I'm assuming that stopping non-PvP players from getting bounced is the reason why this is implemented. + Despite hooking onto Touch, the knockback still happens half of the time. StartTouch yields the same result + so this is really the best that can be done. + */ + SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + } + else if (strcmp(class, "tf_projectile_balloffire", false) == 0) + { + /* + Replicate projectile logic for Dragon's Fury projectiles. + KR: The projectile checks the team of its owner entity (the player), not itself. CBaseEntity::InSameTeam() + could be hooked to change this, but I think that would be too game-changing and not really worth doing for + a single case, so the enemy player logic is sort of replicated. + */ + + PvPProjectile_BallOfFire projectileData; + projectileData.Init(ent); + g_PvPBallsOfFire.PushArray(projectileData, sizeof(projectileData)); + + SDKHook(ent, SDKHook_TouchPost, Hook_PvPProjectileBallOfFireTouchPost); + } + + if (g_PlayerPvPTimer[ownerEntity] == null) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + else + { + //Client is not in pvp, remove the projectile + if (g_PvPArenaProjectileZapConVar.BoolValue) + { + PvP_ZapProjectile(ent, false); + } + } + } + } +} + +static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntity) +{ + int index = g_PvPBallsOfFire.FindValue(projectile); + if (index != -1) { PvPProjectile_BallOfFire projectileData; g_PvPBallsOfFire.GetArray(index, projectileData, sizeof(projectileData)); @@ -485,323 +886,1015 @@ static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntit } } -void PvP_OnPlayerSpawn(int client) +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + PvP_SetPlayerPvPState(client.index, false, false, false); + + g_PlayerIsLeavingPvP[client.index] = false; + + g_PlayerOriginalTeam[client.index] = client.Team; + + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + if (client.IsAlive && client.IsParticipating) + { + if (!client.IsInGhostMode && !client.IsProxy) + { + if (client.IsEliminated || client.HasEscaped) + { + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + + if (autoSpawn) + { + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.12, Timer_TeleportPlayerToPvP, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + else + { + g_PlayerPvPRespawnTimer[client.index] = null; + } + } + } +} + +void PvP_ZapProjectile(int projectile, bool effects=true) +{ + if (!IsValidEntity(projectile)) + { + return; + } + + //Add zap effects + if (effects) + { + float pos[3]; + GetEntPropVector(projectile, Prop_Send, "m_vecOrigin", pos); + //Spawn the particle. + TE_Particle(g_Particles[ZapParticle], pos, pos); + TE_SendToAll(); + //Play zap sound. + EmitSoundToAll(ZAP_SOUND, projectile, SNDCHAN_AUTO, SNDLEVEL_CAR); + SetEntityRenderMode(projectile, RENDER_TRANSCOLOR); + SetEntityRenderColor(projectile, 0, 0, 0, 1); + } + RemoveEntity(projectile); +} + +static void OnPlayerDeathPre(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + if (client.IsInPvP) + { + client.SetProp(Prop_Send, "m_iTeamNum", g_PlayerOriginalTeam[client.index]); + } + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + if (client.IsInPvP) + { + g_PlayerIsLeavingPvP[client.index] = false; + } + + if (!client.IsInGhostMode && !client.IsProxy) + { + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + + if (autoSpawn) + { + if (client.IsEliminated || client.HasEscaped) + { + if (!IsRoundEnding()) + { + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.3, Timer_RespawnPlayer, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + + PvP_SetPlayerPvPState(client.index, false, false, false); + } +} + +void PvP_OnClientGhostModeEnable(int client) +{ + g_PlayerPvPRespawnTimer[client] = null; +} + +static bool Hook_ClientPvPShouldCollide(int ent,int collisiongroup,int contentsmask, bool originalResult) +{ + if (!g_Enabled || g_PlayerProxy[ent] || !IsClientInPvP(ent) || !g_PlayerEliminated[ent]) + { + SDKUnhook(ent, SDKHook_ShouldCollide, Hook_ClientPvPShouldCollide); + if (collisiongroup == 8) + { + return false; + } + return originalResult; + } + if (IsClientInPvP(ent) && GetClientTeam(ent) == TFTeam_Blue && collisiongroup == 8) + { + return true; + } + return originalResult; +} + +void PvP_OnTriggerStartTouch(int trigger, int other) +{ + char name[64]; + GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); + + if (StrContains(name, "sf2_pvp_trigger", false) == 0 || SF2TriggerPvPEntity(trigger).IsValid()) + { + if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + { + if (!g_PlayerEliminated[other] && !DidClientEscape(other)) + { + return; + } + //Use valve's kill code if the player is stuck. + if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) + { + TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); + } + //Resize the player. + SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); + + int entRef = EnsureEntRef(trigger); + if (g_PlayerEnteredPvPTriggers[other].FindValue(entRef) == -1) + { + g_PlayerEnteredPvPTriggers[other].Push(entRef); + } + + if (IsClientInPvP(other)) + { + if (g_PlayerIsLeavingPvP[other]) + { + // Player left and came back again, but is still in PvP mode. + g_PlayerPvPTimerCount[other] = 0; + g_PlayerPvPTimer[other] = null; + g_PlayerIsLeavingPvP[other] = false; + } + } + else + { + PvP_SetPlayerPvPState(other, true); + } + } + } +} + +void PvP_OnTriggerEndTouch(int trigger, int other) +{ + if (IsValidClient(other)) + { + if (g_PlayerEnteredPvPTriggers[other] != null) + { + int triggerEntRef = EnsureEntRef(trigger); + for (int i = g_PlayerEnteredPvPTriggers[other].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredPvPTriggers[other].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + } + } + + if (IsClientInPvP(other)) + { + if (g_PlayerEnteredPvPTriggers[other].Length == 0) + { + g_PlayerPvPTimerCount[other] = g_PvPArenaLeaveTimeConVar.IntValue; + if (g_PlayerPvPTimerCount[other] != 0) + { + g_PlayerPvPTimer[other] = CreateTimer(1.0, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerIsLeavingPvP[other] = true; + } + else + { + g_PlayerPvPTimer[other] = CreateTimer(0.1, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + + //A projectile went off pvp area. + if (other > MaxClients && IsValidEntity(other)) + { + //Get entity's classname. + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + //Yup it's a projectile zap it! + //But we have to wait to prevent some bugs. + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static void PvP_OnTriggerStartTouchBoxing(int trigger, int other) +{ + if (other > MaxClients && IsValidEntity(other) && !IsRoundInWarmup() && !IsRoundEnding()) + { + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static Action EntityStillAlive(Handle timer, int ref) +{ + int ent = EntRefToEntIndex(ref); + if (ent && ent != INVALID_ENT_REFERENCE) + { + PvP_ZapProjectile(ent); + } + return Plugin_Stop; +} +/** + * Enables/Disables PvP mode on the player. + */ +void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles = true, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool oldInPvP = g_PlayerInPvP[player.index]; + if (status == oldInPvP) + { + return; // no change + } + + if (status && player.IsInPvE) + { + PvE_ForceResetPlayerPvEData(player.index); + PvE_SetPlayerPvEState(player.index, false, false); + } + + g_PlayerInPvP[player.index] = status; + g_PlayerPvPTimer[player.index] = null; + g_PlayerPvPRespawnTimer[player.index] = null; + g_PlayerPvPTimerCount[player.index] = 0; + + if (removeProjectiles) + { + // Remove previous projectiles. + PvP_RemovePlayerProjectiles(player.index); + } + + if (status) + { + Call_StartForward(g_OnPlayerEnterPvP); + Call_PushCell(player.index); + Call_Finish(); + } + else + { + Call_StartForward(g_OnPlayerExitPvP); + Call_PushCell(player.index); + Call_Finish(); + } + + if (regenerate) + { + // Regenerate player but keep health the same. + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +static void FlameStartTouch(int flame, int other) +{ + SF2_BasePlayer client = SF2_BasePlayer(other); + if (client.IsValid) + { + if ((client.IsInPvP) && !IsRoundEnding()) + { + int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); + if (IsValidEdict(flamethrower)) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.index != client.index && ownerEntity.IsValid) + { + if (ownerEntity.IsInPvP) + { + if (client.Team == ownerEntity.Team && ownerEntity.Team != TFTeam_Red) + { + ownerEntity.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } + } + } + } + } +} + +static void FlameStartTouchPost(int flame, int other) +{ + int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); + if (IsValidEdict(flamethrower)) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.IsValid && ownerEntity.Team == TFTeam_Spectator) + { + ownerEntity.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + } +} + +static Action GasStartTouch(int entity, int other) +{ + SF2_BasePlayer client = SF2_BasePlayer(other); + if (client.IsValid) + { + if ((client.IsInPvP) && !IsRoundEnding()) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.index == client.index) + { + return Plugin_Handled; + } + + if (ownerEntity.IsValid) + { + if (!ownerEntity.IsInPvP) + { + return Plugin_Handled; + } + } + } + } + + return Plugin_Continue; +} + +static Action PipeOnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) +{ + if (attacker != -1) + { + int owner = GetEntPropEnt(victim, Prop_Data, "m_hOwnerEntity"); + if (owner == attacker) + { + return Plugin_Handled; + } + + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) + { + if (player.Team == TFTeam_Red) + { + return Plugin_Handled; + } + + if (!player.IsInPvP) + { + return Plugin_Handled; + } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } + + return Plugin_Continue; +} + +static void PipeOnTakeDamagePost(int victim, int attacker, int inflictor, float damage, int damagetype) +{ + if (attacker != -1) + { + int owner = GetEntPropEnt(victim, Prop_Data, "m_hOwnerEntity"); + if (owner == attacker) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) + { + if (player.Team == TFTeam_Red) + { + return; + } + + if (!player.IsInPvP) + { + return; + } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + } +} + +static MRESReturn PhysicsDispatchThinkPre(int entity) +{ + char classname[64]; + if (!GetEntityClassname(entity, classname, sizeof(classname))) + { + return MRES_Ignored; + } + + if (strcmp(classname, "obj_sentrygun") == 0) + { + // CObjectSentrygun::SentryThink + if (GetNextThink(entity, "SentryThink") != TICK_NEVER_THINK) + { + return MRES_Ignored; + } + + g_ThinkFunction = ThinkFunction_SentryThink; + + TFTeam myTeam = TF2_GetEntityTeam(entity); + TFTeam enemyTeam = GetEnemyTeam(myTeam); + Address globalTeam = SDKCall_GetGlobalTeam(enemyTeam); + + // CObjectSentrygun::FindTarget uses CTFTeamManager to collect valid players. + // Add all enemy players to the desired team. + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && (player.IsInPvP)) + { + TFTeam team = TF2_GetClientTeam(client); + g_PreHookTeam[client] = team; + bool friendly = IsObjectFriendly(entity, client); + + if (friendly && team == enemyTeam) + { + CTeam_RemovePlayer(globalTeam, client); + } + else if (!friendly && team != enemyTeam) + { + CTeam_AddPlayer(globalTeam, client); + } + + // Sentry Guns don't shoot spies disguised as the same team, spoof the disguise team + if (!friendly) + { + g_PreHookDisguiseTeam[client] = view_as(GetEntProp(client, Prop_Send, "m_nDisguiseTeam")); + SetEntProp(client, Prop_Send, "m_nDisguiseTeam", TFTeam_Spectator); + } + } + } + + // Buildings work in a similar way. + // NOTE: Previously, we would use CBaseObject::ChangeTeam, but we switched to AddObject/RemoveObject calls, + // due to ChangeTeam recreating the build points, causing issues with sapper placement. + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) + { + if (!GetEntProp(obj, Prop_Send, "m_bPlacing")) + { + TFTeam team = TF2_GetEntityTeam(obj); + g_PreHookTeam[obj] = team; + bool friendly = IsObjectFriendly(entity, obj); + + if (friendly && team == enemyTeam) + { + CTeam_RemoveObject(globalTeam, obj); + } + else if (!friendly && team != enemyTeam) + { + CTeam_AddObject(globalTeam, obj); + } + } + } + } + else if (strcmp(classname, "obj_dispenser") == 0) + { + // CObjectDispenser::DispenseThink + if (GetNextThink(entity, "DispenseThink") != TICK_NEVER_THINK) + { + return MRES_Ignored; + } + + if (!GetEntProp(entity, Prop_Send, "m_bPlacing") && !GetEntProp(entity, Prop_Send, "m_bBuilding")) + { + g_ThinkFunction = ThinkFunction_DispenseThink; + + // Disallow players able to be healed from dispenser + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && player.IsInPvP && player.Team != TFTeam_Red) + { + if (!IsObjectFriendly(entity, client)) + { + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } + } + } + } + else if (strcmp(classname, "obj_attachment_sapper") == 0) + { + // CBaseObject::BaseObjectThink + if (GetNextThink(entity, "BaseObjectThink") != TICK_NEVER_THINK) + { + return MRES_Ignored; + } + + g_ThinkFunction = ThinkFunction_SapperThink; + + // Always set team to spectator so we can place sappers on buildings of both teams + CBaseEntity_ChangeTeam(entity, view_as(TFTeam_Spectator)); + } + + return MRES_Ignored; +} + +static MRESReturn PhysicsDispatchThinkPost(int entity) +{ + switch (g_ThinkFunction) + { + case ThinkFunction_SentryThink: + { + TFTeam myTeam = TF2_GetEntityTeam(entity); + TFTeam enemyTeam = GetEnemyTeam(myTeam); + Address globalTeam = SDKCall_GetGlobalTeam(enemyTeam); + + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && (player.IsInPvP)) + { + TFTeam team = g_PreHookTeam[client]; + g_PreHookTeam[client] = TFTeam_Unassigned; + bool friendly = IsObjectFriendly(entity, client); + + if (friendly && team == enemyTeam) + { + CTeam_AddPlayer(globalTeam, client); + } + else if (!friendly && team != enemyTeam) + { + CTeam_RemovePlayer(globalTeam, client); + } + + if (!friendly) + { + SetEntProp(client, Prop_Send, "m_nDisguiseTeam", g_PreHookDisguiseTeam[client]); + g_PreHookDisguiseTeam[client] = TFTeam_Unassigned; + } + } + } + + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) + { + if (!GetEntProp(obj, Prop_Send, "m_bPlacing")) + { + TFTeam team = g_PreHookTeam[obj]; + bool friendly = IsObjectFriendly(entity, obj); + + if (friendly && team == enemyTeam) + { + CTeam_AddObject(globalTeam, obj); + } + else if (!friendly && team != enemyTeam) + { + CTeam_RemoveObject(globalTeam, obj); + } + + g_PreHookTeam[obj] = TFTeam_Unassigned; + } + } + } + case ThinkFunction_DispenseThink: + { + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && (player.IsInPvP) && player.Team != TFTeam_Red) + { + if (!IsObjectFriendly(entity, client)) + { + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + } + } + } + } + + g_ThinkFunction = ThinkFunction_None; + + return MRES_Ignored; +} + +static MRESReturn FlareExplodePre(int entity, DHookParam params) +{ + SF2_BasePlayer other = SF2_BasePlayer(params.Get(2)); + if (!other.IsValid) + { + return MRES_Ignored; + } + + if (!other.IsInPvP) + { + return MRES_Ignored; + } + + if (other.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + other.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + + return MRES_Ignored; +} + +static MRESReturn FlareExplodePost(int entity, DHookParam params) +{ + SF2_BasePlayer other = SF2_BasePlayer(params.Get(2)); + if (!other.IsValid) + { + return MRES_Ignored; + } + + if (!other.IsInPvP) + { + return MRES_Ignored; + } + + if (other.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + other.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + + return MRES_Ignored; +} + +static MRESReturn AllowedToHealTargetPre(int medigun, DHookReturn ret, DHookParam params) +{ + SF2_BasePlayer owner = SF2_BasePlayer(GetEntPropEnt(medigun, Prop_Send, "m_hOwnerEntity")); + if (!owner.IsValid) + { + owner = SF2_BasePlayer(GetEntPropEnt(medigun, Prop_Send, "m_hOwner")); + } + int entity = params.Get(1); + SF2_BasePlayer target = SF2_BasePlayer(entity); + if (owner.IsEliminated) + { + if (target.IsValid) + { + if (target.IsEliminated) + { + if (owner.IsInPvP && !target.IsInPvP) + { + ret.Value = false; + return MRES_Supercede; + } + + if (!owner.IsInPvP && target.IsInPvP) + { + ret.Value = false; + return MRES_Supercede; + } + } + else + { + ret.Value = true; + return MRES_Supercede; + } + } + else if (entity > MaxClients && NPCGetFromEntIndex(entity) != -1) + { + ret.Value = true; + return MRES_Supercede; + } + } + return MRES_Ignored; +} + +static MRESReturn JarExplodePre(int entity, DHookParam params) +{ + SF2_BasePlayer thrower = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Send, "m_hThrower")); + if (!thrower.IsValid) + { + return MRES_Ignored; + } + + if (!thrower.IsInPvP) + { + return MRES_Ignored; + } + + if (thrower.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + thrower.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + + return MRES_Ignored; +} + +static MRESReturn JarExplodePost(int entity, DHookParam params) +{ + SF2_BasePlayer thrower = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Send, "m_hThrower")); + if (!thrower.IsValid) + { + return MRES_Ignored; + } + + if (!thrower.IsInPvP) + { + return MRES_Ignored; + } + + if (thrower.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + thrower.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + + return MRES_Ignored; +} + +static MRESReturn VPhysicsUpdatePre(int entity, DHookParam params) +{ + SF2_BasePlayer thrower = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity")); + if (!thrower.IsValid) + { + return MRES_Ignored; + } + + if (!thrower.IsInPvP) + { + return MRES_Ignored; + } + + if (thrower.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + TFTeam enemyTeam = GetEnemyTeam(TF2_GetEntityTeam(entity)); + + // Not needed because of our CanCollideWithTeammates hook, but can't hurt + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.Team == TFTeam_Red) + { + continue; + } + + if (!client.IsInPvP) + { + continue; + } + + client.SetProp(Prop_Send, "m_iTeamNum", view_as(enemyTeam)); + } + + // Fix projectiles rarely bouncing off buildings + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) + { + if (!IsObjectFriendly(obj, thrower.index)) + { + continue; + } + + if (CBaseEntity(obj).GetProp(Prop_Send, "m_iTeamNum") == TFTeam_Red) + { + continue; + } + + CBaseEntity(obj).SetProp(Prop_Send, "m_iTeamNum", view_as(enemyTeam)); + } + + return MRES_Ignored; +} + +static MRESReturn VPhysicsUpdatePost(int entity, DHookParam params) { - if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + int thrower = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); + + for (int i = 1; i <= MaxClients; i++) { - return; - } + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.Team == TFTeam_Red) + { + continue; + } - PvP_SetPlayerPvPState(client, false, false, false); + if (!client.IsInPvP) + { + continue; + } - g_PlayerIsLeavingPvP[client] = false; + client.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } - if (IsPlayerAlive(client) && IsClientParticipating(client)) + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) { - if (!IsClientInGhostMode(client) && !g_PlayerProxy[client]) + if (!IsObjectFriendly(obj, thrower)) { - if (g_PlayerEliminated[client] || DidClientEscape(client)) - { - bool autoSpawn = g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn; - - if (autoSpawn) - { - g_PlayerPvPRespawnTimer[client] = CreateTimer(0.12, Timer_TeleportPlayerToPvP, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - } - else - { - g_PlayerPvPRespawnTimer[client] = null; - } + continue; } + + CBaseEntity(obj).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); } + + return MRES_Ignored; } -void PvP_ZapProjectile(int projectile,bool bEffects=true) +static Action CleaverTouch(int entity, int other) { - if (!IsValidEntity(projectile)) + if (other == 0) { - return; + return Plugin_Continue; } - //Add zap effects - if (bEffects) + int owner = GetEntPropEnt(entity, Prop_Data, "m_hThrower"); + if (owner == other) { - float pos[3]; - GetEntPropVector(projectile, Prop_Send, "m_vecOrigin", pos); - //Spawn the particle. - TE_Particle(g_Particles[ZapParticle], pos, pos); - TE_SendToAll(); - //Play zap sound. - EmitSoundToAll(ZAP_SOUND, projectile, SNDCHAN_AUTO, SNDLEVEL_CAR); - SetEntityRenderMode(projectile, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectile, 0, 0, 0, 1); + return Plugin_Continue; } - RemoveEntity(projectile); -} -void PvP_OnPlayerDeath(int client, bool fake) -{ - if (!fake) + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) { - if (IsClientInPvP(client)) + if (player.Team == TFTeam_Red) { - g_PlayerIsLeavingPvP[client] = false; + return Plugin_Continue; } - if (!IsClientInGhostMode(client) && !g_PlayerProxy[client]) + if (!player.IsInPvP) { - bool autoSpawn = g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn; - - if (autoSpawn) - { - if (g_PlayerEliminated[client] || DidClientEscape(client)) - { - if (!IsRoundEnding()) - { - g_PlayerPvPRespawnTimer[client] = CreateTimer(0.3, Timer_RespawnPlayer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - } - } + return Plugin_Continue; } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); } -} -void PvP_OnClientGhostModeEnable(int client) -{ - g_PlayerPvPRespawnTimer[client] = null; + return Plugin_Continue; } -static bool Hook_ClientPvPShouldCollide(int ent,int collisiongroup,int contentsmask, bool originalResult) +static void CleaverTouchPost(int entity, int other) { - if (!g_Enabled || g_PlayerProxy[ent] || !IsClientInPvP(ent) || !g_PlayerEliminated[ent]) + if (other == 0) { - SDKUnhook(ent, SDKHook_ShouldCollide, Hook_ClientPvPShouldCollide); - if (collisiongroup == 8) - { - return false; - } - return originalResult; + return; } - if (IsClientInPvP(ent) && GetClientTeam(ent) == TFTeam_Blue && collisiongroup == 8) + + int owner = GetEntPropEnt(entity, Prop_Data, "m_hThrower"); + if (owner == other) { - return true; + return; } - return originalResult; -} - -void PvP_OnTriggerStartTouch(int trigger,int other) -{ - char name[64]; - GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); - if (StrContains(name, "sf2_pvp_trigger", false) == 0 || SF2TriggerPvPEntity(trigger).IsValid()) + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) { - if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + if (player.Team == TFTeam_Red) { - if (!g_PlayerEliminated[other] && !DidClientEscape(other)) - { - return; - } - //Use valve's kill code if the player is stuck. - if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) - { - TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); - } - //Resize the player. - SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); - - int entRef = EnsureEntRef(trigger); - if (g_PlayerEnteredPvPTriggers[other].FindValue(entRef) == -1) - { - g_PlayerEnteredPvPTriggers[other].Push(entRef); - } + return; + } - if (IsClientInPvP(other)) - { - if (g_PlayerIsLeavingPvP[other]) - { - // Player left and came back again, but is still in PvP mode. - g_PlayerPvPTimerCount[other] = 0; - g_PlayerPvPTimer[other] = null; - g_PlayerIsLeavingPvP[other] = false; - } - } - else - { - PvP_SetPlayerPvPState(other, true); - } + if (!player.IsInPvP) + { + return; } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); } } -void PvP_OnTriggerEndTouch(int trigger,int other) +static Action GrenadeTouch(int entity, int other) { - if (IsValidClient(other)) + if (other == 0) { - if (g_PlayerEnteredPvPTriggers[other] != null) - { - int triggerEntRef = EnsureEntRef(trigger); - for (int i = g_PlayerEnteredPvPTriggers[other].Length - 1; i >= 0; i--) - { - int entRef = g_PlayerEnteredPvPTriggers[other].Get(i); - if (entRef == triggerEntRef) - { - g_PlayerEnteredPvPTriggers[other].Erase(i); - } - else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) - { - g_PlayerEnteredPvPTriggers[other].Erase(i); - } - } - } + return Plugin_Continue; + } - if (IsClientInPvP(other)) - { - if (g_PlayerEnteredPvPTriggers[other].Length == 0) - { - g_PlayerPvPTimerCount[other] = g_PvPArenaLeaveTimeConVar.IntValue; - if (g_PlayerPvPTimerCount[other] != 0) - { - g_PlayerPvPTimer[other] = CreateTimer(1.0, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - g_PlayerIsLeavingPvP[other] = true; - } - else - { - g_PlayerPvPTimer[other] = CreateTimer(0.1, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - } - } - } + int owner = GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity"); + if (owner == other) + { + return Plugin_Continue; } - //A projectile went off pvp area. (Experimental) - if (other > MaxClients && IsValidEntity(other)) + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) { - //Get entity's classname. - char classname[50]; - GetEntityClassname(other,classname,sizeof(classname)); - for (int i = 0; i < (sizeof(g_PvPProjectileClasses)-4); i++) + if (player.Team == TFTeam_Red) { - if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) - { - if (!GetEntProp(other, Prop_Send, "m_iDeflected")) - { - //Yup it's a projectile zap it! - //But we have to wait to prevent some bugs. - CreateTimer(0.1,EntityStillAlive,other,TIMER_FLAG_NO_MAPCHANGE); - } - } + return Plugin_Continue; } - } -} -static void PvP_OnTriggerStartTouchBoxing(int trigger,int other) -{ - //A projectile went in the area. (Experimental) - if (other > MaxClients && IsValidEntity(other) && !IsRoundInWarmup() && !IsRoundEnding()) - { - //Get entity's classname. - char classname[50]; - GetEntityClassname(other,classname,sizeof(classname)); - for (int i = 0; i < (sizeof(g_PvPProjectileClasses)-4); i++) + if (!player.IsInPvP) { - if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) - { - if (!GetEntProp(other, Prop_Send, "m_iDeflected")) - { - //Yup it's a projectile zap it! - //But we have to wait to prevent some bugs. - CreateTimer(0.1,EntityStillAlive,other,TIMER_FLAG_NO_MAPCHANGE); - } - } + return Plugin_Continue; } - } -} -static Action EntityStillAlive(Handle timer, int iRef) -{ - int ent = EntRefToEntIndex(iRef); - if (ent > MaxClients) - { - PvP_ZapProjectile(ent); + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); } - return Plugin_Stop; + + return Plugin_Continue; } -/** - * Enables/Disables PvP mode on the player. - */ -void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles=true, bool regenerate=true) + +static void GrenadeTouchPost(int entity, int other) { - if (!IsValidClient(client)) + if (other == 0) { return; } - bool oldInPvP = g_PlayerInPvP[client]; - if (status == oldInPvP) + int owner = GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity"); + if (owner == other) { - return; // no change + return; } - g_PlayerInPvP[client] = status; - g_PlayerPvPTimer[client] = null; - g_PlayerPvPRespawnTimer[client] = null; - g_PlayerPvPTimerCount[client] = 0; - - if (removeProjectiles) + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) { - // Remove previous projectiles. - PvP_RemovePlayerProjectiles(client); - } + if (player.Team == TFTeam_Red) + { + return; + } - if (regenerate) - { - // Regenerate player but keep health the same. - int health = GetEntProp(client, Prop_Send, "m_iHealth"); - TF2_RemoveWeaponSlot(client, TFWeaponSlot_Primary); - TF2_RemoveWeaponSlot(client, TFWeaponSlot_Secondary); - TF2_RegeneratePlayer(client); - SetEntProp(client, Prop_Data, "m_iHealth", health); - SetEntProp(client, Prop_Send, "m_iHealth", health); + if (!player.IsInPvP) + { + return; + } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); } } -static void PvP_OnFlameEntityStartTouchPost(int flame,int other) //Thanks Fire +static MRESReturn GetCustomDamageTypePost(int entity, DHookReturn ret) { - static float flasthit[MAXPLAYERS+1]; - - if (IsValidClient(other)) + // Allows Sniper Rifles to hit teammates, without breaking Machina penetration + int penetrateType = SDKCall(g_SDKGetPenetrationType, entity); + if (penetrateType == 0) { - float time = GetEngineTime(); - if (flasthit[other] < time) - { - flasthit[other] = time + FLAME_HIT_DELAY; - - if ((IsRoundInWarmup() || IsClientInPvP(other)) && !IsRoundEnding()) - { - int iFlamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); - if (IsValidEdict(iFlamethrower)) - { - int ownerEntity = GetEntPropEnt(iFlamethrower, Prop_Data, "m_hOwnerEntity"); - if (ownerEntity != other && IsValidClient(ownerEntity)) - { - if (IsRoundInWarmup() || IsClientInPvP(ownerEntity)) - { - if (GetClientTeam(other) == GetClientTeam(ownerEntity) && GetClientTeam(ownerEntity) != TFTeam_Red) - { - //TF2_MakeBleed(other, ownerEntity, 4.0); - TF2_IgnitePlayer(other, ownerEntity); - SDKHooks_TakeDamage(other, ownerEntity, ownerEntity, 10.0, IsClientCritBoosted(ownerEntity) ? (DMG_BURN | DMG_PREVENT_PHYSICS_FORCE | DMG_ACID) : DMG_BURN | DMG_PREVENT_PHYSICS_FORCE); - } - } - } - } - } - } + ret.Value = 0; + return MRES_Supercede; } + + return MRES_Ignored; } /** @@ -939,10 +2032,13 @@ static Action Timer_TeleportPlayerToPvP(Handle timer, any userid) float pos[3], ang[3]; GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); - TeleportEntity(client, pos, ang, view_as({ 0.0, 0.0, 0.0 })); + TeleportEntity(client, pos, ang, { 0.0, 0.0, 0.0 }); EmitAmbientSound(SF2_PVP_SPAWN_SOUND, pos, _, SNDLEVEL_NORMAL, _, 1.0); - if (g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection) TF2_AddCondition(client, TFCond_UberchargedCanteen, 1.5); + if (g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection) + { + TF2_AddCondition(client, TFCond_UberchargedCanteen, 1.5); + } SF2PlayerPvPSpawnEntity spawnPoint = SF2PlayerPvPSpawnEntity(ent); if (spawnPoint.IsValid()) @@ -1002,6 +2098,11 @@ bool IsClientInPvP(int client) { return g_PlayerInPvP[client]; } + +bool IsClientLeavingPvP(int client) +{ + return g_PlayerIsLeavingPvP[client]; +} //////////////// // Blood effects //////////////// @@ -1039,6 +2140,7 @@ Action Hook_PvPPlayerTraceAttack(int victim, int &attacker, int &inflictor, floa return Plugin_Continue; } + static Action TempEntHook_PvPDecal(const char[] te_name, int[] players, int numPlayers, float delay) { if (!g_Enabled) @@ -1061,61 +2163,47 @@ static Action TempEntHook_PvPDecal(const char[] te_name, int[] players, int numP return Plugin_Continue; } -MRESReturn PvP_GetWeaponCustomDamageType(int weapon, int client, int &customDamageType) +// For mediguns, eventually I'll make proper assistants +static Action OnPlayerDeathEventPre(Event event, const char[] name, bool dontBroadcast) { - if (IsValidClient(client) && IsClientInPvP(client) && IsValidEntity(weapon) && IsValidEdict(weapon) && IsValidEntity(client)) + if (!g_Enabled) { - static const char fixWeaponPenetrationClasses[][] = - { - "tf_weapon_sniperrifle", - "tf_weapon_sniperrifle_decap", - "tf_weapon_sniperrifle_classic" - }; + return Plugin_Continue; + } - char sWeaponName[256]; - GetEntityClassname(weapon, sWeaponName, sizeof(sWeaponName)); + SF2_BasePlayer attacker = SF2_BasePlayer(GetClientOfUserId(event.GetInt("attacker"))); - /* - * Fixes the sniper rifle not damaging teammates. - * - * WHY? For every other hitscan weapon in the game, simply enforcing lag compensation in CTFPlayer::WantsLagCompensationOnEntity() - * works. However, when it comes to weapons that penetrate teammates, the bullet trace will not iterate through teammates. This is - * the case with all sniper rifles, and is the reason why damage is never normally dealt to teammates despite having friendly fire - * on and lag compensation. - * - * In this case, the type of penetration is determined by CTFWeaponBase::GetCustomDamageType(). For Snipers, default value is - * TF_DMG_CUSTOM_PENETRATE_MY_TEAM (11) (piss rifle is TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE (14)). This value specifies - * penetration of the bullet through teammates without damaging them. The damage type is switched to 0, and for the Machina at - * full charge, TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS (12). - * - */ - for (int i = 0; i < sizeof(fixWeaponPenetrationClasses); i++) + if (attacker.IsValid && attacker.IsInPvP) + { + CBaseEntity weapon = CBaseEntity(attacker.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (weapon.IsValid() && GetWeaponID(weapon.index) == TF_WEAPON_MEDIGUN) { - if (strcmp(sWeaponName, fixWeaponPenetrationClasses[i], false) == 0) - { - customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS - int itemDefIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); - - if ((itemDefIndex == 526 || itemDefIndex == 30665) && GetEntPropFloat(weapon, Prop_Send, "m_flChargedDamage") >= 150.0 ) // The Machina, Shooting Star - { - customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS - } - else - { - customDamageType = 0; // no penetration behavior. - } + event.SetString("weapon_logclassname", "tf_weapon_deathgun"); + event.SetString("weapon", "merasmus_zap"); + } + } - return MRES_Supercede; + SF2_BasePlayer assister = SF2_BasePlayer(GetClientOfUserId(event.GetInt("assister"))); + if (assister.IsValid && assister.IsInPvP) + { + CBaseEntity weapon = CBaseEntity(assister.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (weapon.IsValid() && GetWeaponID(weapon.index) == TF_WEAPON_MEDIGUN) + { + CBaseEntity target = CBaseEntity(weapon.GetPropEnt(Prop_Send, "m_hHealingTarget")); + if (target.index == attacker.index) + { + event.SetInt("assister", -1); + event.SetString("assister_fallback", ""); } } } - return MRES_Ignored; + return Plugin_Changed; } static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHookReturn returnHandle, DHookParam params) { - if (!IsValidEdict(projectile) || !IsValidEntity(projectile)) + if (!g_Enabled) { return MRES_Ignored; } @@ -1129,6 +2217,8 @@ static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHoo void PvP_InitializeAPI() { CreateNative("SF2_IsClientInPvP", Native_IsClientInPvP); + g_OnPlayerEnterPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); + g_OnPlayerExitPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); } static int Native_IsClientInPvP(Handle plugin,int numParams) diff --git a/addons/sourcemod/scripting/sf2/pvp/menus.sp b/addons/sourcemod/scripting/sf2/pvp/menus.sp index 48c21a14..1994ccdb 100644 --- a/addons/sourcemod/scripting/sf2/pvp/menus.sp +++ b/addons/sourcemod/scripting/sf2/pvp/menus.sp @@ -6,9 +6,9 @@ #pragma semicolon 1 -Handle g_MenuSettingsPvP; +Menu g_MenuSettingsPvP; -int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) +int Menu_SettingsPvP(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -19,15 +19,15 @@ int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings PvP Spawn Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); Format(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); Format(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsPvPSpawn, 30); + panel.Send(param1, Panel_SettingsPvPSpawn, 30); delete panel; } case 1: @@ -35,13 +35,13 @@ int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings PvP Spawn Protection Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "On"); - DrawPanelItem(panel, "Off"); + panel.DrawItem("On"); + panel.DrawItem("Off"); - SendPanelToClient(panel, param1, Panel_SettingsPvPProtection, 30); + panel.Send(param1, Panel_SettingsPvPProtection, 30); delete panel; } } @@ -50,13 +50,13 @@ int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } } return 0; } -static int Panel_SettingsPvPSpawn(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsPvPSpawn(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -74,12 +74,12 @@ static int Panel_SettingsPvPSpawn(Handle menu, MenuAction action,int param1,int } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsPvPProtection(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsPvPProtection(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -97,7 +97,7 @@ static int Panel_SettingsPvPProtection(Handle menu, MenuAction action,int param } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index f898eced..67b1695d 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -11,6 +11,9 @@ #define SR_SOUND_SELECT "slender/specialroundselect.mp3" #define SR_SOUND_SELECT_BR "ambient/rottenburg/rottenburg_belltower.wav" #define SR_DUCK_MODEL "models/workshop/player/items/pyro/eotl_ducky/eotl_bonus_duck.mdl" +#define SR_BEATBOX_MUSIC "sf2m/specialround/beatbox/theme.wav" +#define SR_BEATBOX_BEEP "sf2m/specialround/beatbox/beep.wav" +#define SR_BEATBOX_FLICK "sf2m/specialround/beatbox/flick.mp3" #define FILE_SPECIALROUNDS "configs/sf2/specialrounds.cfg" #define FILE_SPECIALROUNDS_DATA "data/sf2/specialrounds.cfg" @@ -25,6 +28,497 @@ static bool g_Started = false; static int doublerouletteCount = 0; static int g_SpecialRoundType = 0; +static Handle g_BeatBoxMasterTime = null; +static Handle g_BeatBoxMusicTimer = null; +static int g_BeatBoxCueIndex = 0; + +static int g_OverrideDifficulty = -1; + +void SetupSpecialRounds() +{ + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + g_OnRoundStartPFwd.AddFunction(null, OnRoundStart); + g_OnRoundEndPFwd.AddFunction(null, OnRoundEnd); + g_OnAdminMenuCreateOptionsPFwd.AddFunction(null, OnAdminMenuCreateOptions); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerTeamPFwd.AddFunction(null, OnPlayerTeam); + g_OnPlayerClassPFwd.AddFunction(null, OnPlayerClass); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyVoteFinished); +} + +static void OnGamemodeStart() +{ + PrecacheSound2(SR_BEATBOX_MUSIC, true); + PrecacheSound2(SR_BEATBOX_BEEP, true); + PrecacheSound2(SR_BEATBOX_FLICK, true); +} + +static Action Timer_BeatBoxMasterTimer(Handle timer) +{ + if (timer != g_BeatBoxMasterTime) + { + return Plugin_Stop; + } + + if (!g_Enabled) + { + return Plugin_Stop; + } + + if (!SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + return Plugin_Stop; + } + + g_BeatBoxCueIndex++; + switch (g_BeatBoxCueIndex) + { + case 1: + { + DoBeatBoxBeat("Stop moving in: 3..."); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 2: + { + DoBeatBoxBeat("Stop moving in: 2..."); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 3: + { + DoBeatBoxBeat("Stop moving in: 1..."); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 4: + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || !client.IsAlive) + { + continue; + } + if (client.IsEliminated && !client.IsProxy) + { + continue; + } + if (client.HasEscaped) + { + continue; + } + if (!client.IsMoving()) + { + continue; + } + float damage = 20.0; + switch (g_DifficultyConVar.IntValue) + { + case Difficulty_Normal: + { + client.Stun(2.0, 0.25, TF_STUNFLAGS_SMALLBONK, client.index); + damage = 20.0; + } + case Difficulty_Hard: + { + client.Stun(3.0, 0.45, TF_STUNFLAGS_SMALLBONK, client.index); + damage = 30.0; + } + case Difficulty_Insane: + { + client.Stun(3.5, 0.6, TF_STUNFLAGS_SMALLBONK, client.index); + damage = 40.0; + } + case Difficulty_Nightmare: + { + client.Stun(5.5, 0.7, TF_STUNFLAGS_SMALLBONK, client.index); + damage = 50.0; + } + case Difficulty_Apollyon: + { + client.Stun(7.5, 0.8, TF_STUNFLAGS_SMALLBONK, client.index); + damage = 60.0; + } + } + if (client.Class == TFClass_Medic) + { + damage *= 1.5; + } + client.TakeDamage(true, _, _, damage, DMG_CLUB); + } + DoBeatBoxBeat("Stop moving!", true); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 5: + { + DoBeatBoxBeat("Start moving!", true, true); + g_BeatBoxMasterTime = CreateTimer(1.95, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + } + if (g_BeatBoxCueIndex >= 5) + { + g_BeatBoxCueIndex = 0; + } + return Plugin_Stop; +} + +static Action Timer_BeatBoxMusic(Handle timer) +{ + if (timer != g_BeatBoxMusicTimer) + { + StopBeatBoxMusicForAll(); + return Plugin_Stop; + } + + if (!g_Enabled) + { + StopBeatBoxMusicForAll(); + return Plugin_Stop; + } + + if (!SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + StopBeatBoxMusicForAll(); + return Plugin_Stop; + } + + EmitBeatBoxMusic(); + + return Plugin_Continue; +} + +static void DoBeatBoxBeat(const char[] message, bool end = false, bool messageOnly = false) +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.HasEscaped || client.IsEliminated || client.IsInDeathCam || !client.IsAlive) + { + continue; + } + if (!messageOnly) + { + if (!end) + { + EmitSoundToClient(client.index, SR_BEATBOX_BEEP, _, _, _, _, 0.35); + } + else + { + EmitSoundToClient(client.index, SR_BEATBOX_FLICK, _, _, _, _, 0.6); + } + } + PrintCenterText(client.index, message); + } +} + +static void EmitBeatBoxMusic() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + float volume = 1.0; + if (!client.IsValid) + { + continue; + } + if (client.HasEscaped) + { + volume = 0.0; + } + else if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + volume = 0.0; + } + else if (!client.IsAlive) + { + volume = 0.0; + } + EmitSoundToClient(client.index, SR_BEATBOX_MUSIC, _, MUSIC_CHAN, _, (1 << 0) | (1 << 8), volume, _, _, _, _, false); + } +} + +static void StopBeatBoxMusic(SF2_BasePlayer client) +{ + StopSound(client.index, SNDCHAN_AUTO, SR_BEATBOX_MUSIC); +} + +void StopBeatBoxMusicForAll() +{ + for (int i = 1; i < MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + StopBeatBoxMusic(client); + } +} + +bool IsBeatBoxBeating(int index = 0) +{ + return g_BeatBoxCueIndex > index && SF_SpecialRound(SPECIALROUND_BEATBOX); +} + +static void OnRoundStart() +{ + g_OverrideDifficulty = -1; +} + +static void OnRoundEnd() +{ + StopBeatBoxMusicForAll(); + g_Started = false; + SF_RemoveAllSpecialRound(); +} + +static void OnAdminMenuCreateOptions(TopMenu topMenu, TopMenuObject commands) +{ + //topMenu.AddItem("sf2_specialrounds_main", AdminTopMenu_SpecialsMain, commands, "sm_sf2_force_special_round", ADMFLAG_CHEATS); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (client.IsEliminated) + { + return; + } + + if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) + { + TF2_StripContrackerOnly(client.index); + } + + if (SF_SpecialRound(SPECIALROUND_1UP) && !g_PlayerIn1UpCondition[client.index] && !g_PlayerDied1Up[client.index]) + { + g_PlayerDied1Up[client.index] = false; + g_PlayerIn1UpCondition[client.index] = true; + g_PlayerFullyDied1Up[client.index] = false; + } + + if (SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) + { + ClientSetSpecialRoundTimer(client.index, 0.0, Timer_ClientPageDetector, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !client.HasEscaped) + { + ChangeThanatophobiaClass(client); + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (fake) + { + return; + } + + //StopBeatBoxMusic(client); + + if (SF_SpecialRound(SPECIALROUND_MULTIEFFECT)) + { + CreateTimer(0.1, Timer_ReplacePlayerRagdoll, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !client.HasEscaped && client.Team == TFTeam_Red) + { + for (int reds = 1; reds <= MaxClients; reds++) + { + SF2_BasePlayer player = SF2_BasePlayer(reds); + if (!player.IsValid || + player.IsEliminated || + player.HasEscaped || + !player.IsAlive) + { + continue; + } + int randomNegative = GetRandomInt(1, 5); + switch (randomNegative) + { + case 1: + { + player.Bleed(true, _, 4.0); + EmitSoundToClient(reds, BLEED_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + case 2: + { + player.ChangeCondition(TFCond_Jarated, _, 5.0); + EmitSoundToClient(reds, JARATE_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + case 3: + { + player.ChangeCondition(TFCond_Gas, _, 5.0); + EmitSoundToClient(reds, GAS_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + case 4: + { + int maxHealth = SDKCall(g_SDKGetMaxHealth, reds); + float damageToTake = float(maxHealth) / 10.0; + player.TakeDamage(true, _, _, damageToTake, 128); + } + case 5: + { + player.ChangeCondition(TFCond_MarkedForDeath, _, 5.0); + } + } + } + } +} + +static void OnPlayerTeam(SF2_BasePlayer client, int team) +{ + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !client.IsEliminated && team == TFTeam_Red && + !client.HasEscaped) + { + ChangeThanatophobiaClass(client); + } +} + +static void OnPlayerClass(SF2_BasePlayer client) +{ + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !client.IsEliminated && client.Team == TFTeam_Red && + !client.HasEscaped) + { + ChangeThanatophobiaClass(client); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + if (SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + StopBeatBoxMusic(client); + } +} + +static Action OnDifficultyVoteFinished(int difficulty, int& newDifficulty) +{ + if (g_OverrideDifficulty > -1 && difficulty < g_OverrideDifficulty) + { + newDifficulty = g_OverrideDifficulty; + return Plugin_Changed; + } + + return Plugin_Continue; +} + +static void ChangeThanatophobiaClass(SF2_BasePlayer client) +{ + TFClassType class = client.Class; + int classToInt = view_as(class); + if (!IsClassConfigsValid()) + { + if (class == TFClass_Medic) + { + ShowVGUIPanel(client.index, "class_red"); + EmitSoundToClient(client.index, THANATOPHOBIA_MEDICNO); + TFClassType newClass; + int random = GetRandomInt(1, 8); + switch (random) + { + case 1: + { + newClass = TFClass_Scout; + } + case 2: + { + newClass = TFClass_Soldier; + } + case 3: + { + newClass = TFClass_Pyro; + } + case 4: + { + newClass = TFClass_DemoMan; + } + case 5: + { + newClass = TFClass_Heavy; + } + case 6: + { + newClass = TFClass_Engineer; + } + case 7: + { + newClass = TFClass_Sniper; + } + case 8: + { + newClass = TFClass_Spy; + } + } + client.SetClass(newClass); + client.Regenerate(); + } + } + else + { + if (g_ClassBlockedOnThanatophobia[classToInt]) + { + ShowVGUIPanel(client.index, "class_red"); + switch (class) + { + case TFClass_Scout: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SCOUTNO); + } + case TFClass_Soldier: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SOLDIERNO); + } + case TFClass_Pyro: + { + EmitSoundToClient(client.index, THANATOPHOBIA_PYRONO); + } + case TFClass_DemoMan: + { + EmitSoundToClient(client.index, THANATOPHOBIA_DEMOMANNO); + } + case TFClass_Heavy: + { + EmitSoundToClient(client.index, THANATOPHOBIA_HEAVYNO); + } + case TFClass_Engineer: + { + EmitSoundToClient(client.index, THANATOPHOBIA_ENGINEERNO); + } + case TFClass_Medic: + { + EmitSoundToClient(client.index, THANATOPHOBIA_MEDICNO); + } + case TFClass_Sniper: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SNIPERNO); + } + case TFClass_Spy: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SPYNO); + } + } + ArrayList classArrays = new ArrayList(); + for (int i = 1; i < MAX_CLASSES + 1; i++) + { + if (!g_ClassBlockedOnThanatophobia[i]) + { + classArrays.Push(view_as(i)); + } + } + TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); + client.SetClass(newClass); + client.Regenerate(); + delete classArrays; + } + } +} + void ReloadSpecialRounds() { if (g_SpecialRoundCycleNames == null) @@ -178,7 +672,7 @@ static bool SpecialRoundCanBeSelected(int specialRound) return false; } - return !!g_SpecialRoundsConfig.GetNum("enabled", 1); + return g_SpecialRoundsConfig.GetNum("enabled", 1) != 0; } static bool IsSpecialRoundEnabled(int specialRound) @@ -276,7 +770,7 @@ static Action Timer_SpecialRoundFakeBosses(Handle timer) } for (int i = 0; i < MAX_BOSSES; i++) { - SF2NPC_BaseNPC Npc = view_as(i); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (!Npc.IsValid()) { continue; @@ -378,7 +872,7 @@ static ArrayList SpecialEnabledList() if (GetSelectableBossProfileList().Length > 0) { AddSpecialRoundToList(SPECIALROUND_DOUBLETROUBLE, enabledRounds); - AddSpecialRoundToList(SPECIALROUND_DOOMBOX, enabledRounds); + AddSpecialRoundToList(SPECIALROUND_SILENTSLENDER, enabledRounds); } } else @@ -386,11 +880,11 @@ static ArrayList SpecialEnabledList() if (GetSelectableBoxingBossProfileList().Length > 0) { AddSpecialRoundToList(SPECIALROUND_DOUBLETROUBLE, enabledRounds); - AddSpecialRoundToList(SPECIALROUND_DOOMBOX, enabledRounds); + AddSpecialRoundToList(SPECIALROUND_SILENTSLENDER, enabledRounds); } } - if (GetActivePlayerCount() <= g_MaxPlayersConVar.IntValue * 2 && g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) + if (GetActivePlayerCount() <= g_MaxPlayersConVar.IntValue * 2 && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_DOUBLEMAXPLAYERS, enabledRounds); } @@ -398,14 +892,7 @@ static ArrayList SpecialEnabledList() { if (GetSelectableBossProfileList().Length > 0 && GetActivePlayerCount() <= g_MaxPlayersConVar.IntValue * 2) { - if (g_DifficultyConVar.IntValue < 3) - { - AddSpecialRoundToList(SPECIALROUND_2DOUBLE, enabledRounds); - } - if (g_DifficultyConVar.IntValue < 2) - { - AddSpecialRoundToList(SPECIALROUND_2DOOM, enabledRounds); - } + AddSpecialRoundToList(SPECIALROUND_2DOUBLE, enabledRounds); } } else @@ -415,7 +902,7 @@ static ArrayList SpecialEnabledList() AddSpecialRoundToList(SPECIALROUND_2DOUBLE, enabledRounds); } } - if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && !SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) && !SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_2DOOM) && g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) + if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && !SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) && !SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_INSANEDIFFICULTY, enabledRounds); } @@ -536,7 +1023,7 @@ static ArrayList SpecialEnabledList() { AddSpecialRoundToList(SPECIALROUND_BOSSROULETTE, enabledRounds); } - if (!SF_SpecialRound(SPECIALROUND_WALLHAX) && !SF_IsRaidMap() && !SF_IsBoxingMap() && g_DifficultyConVar.IntValue < 3) + if (!SF_SpecialRound(SPECIALROUND_WALLHAX) && !SF_IsRaidMap() && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_WALLHAX, enabledRounds); } @@ -544,7 +1031,10 @@ static ArrayList SpecialEnabledList() { AddSpecialRoundToList(SPECIALROUND_SINGLEPLAYER, enabledRounds); } - //Always keep this special round push at the bottom, we need the array length. + if (!SF_SpecialRound(SPECIALROUND_BEATBOX) && !SF_IsRaidMap() && !SF_IsBoxingMap() && !SF_IsProxyMap() && !SF_SpecialRound(SPECIALROUND_DOUBLEROULETTE) && !SF_SpecialRound(SPECIALROUND_REVOLUTION)) + { + AddSpecialRoundToList(SPECIALROUND_BEATBOX, enabledRounds); + } if (!SF_SpecialRound(SPECIALROUND_VOTE) && !SF_SpecialRound(SPECIALROUND_DOUBLEROULETTE) && !SF_SpecialRound(SPECIALROUND_REVOLUTION) && !SF_SpecialRound(SPECIALROUND_SUPRISE) && enabledRounds.Length > 5 && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_VOTE, enabledRounds); @@ -576,8 +1066,8 @@ void SpecialRoundStart() case SPECIALROUND_DOUBLETROUBLE: { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); + ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList(); if (!SF_IsBoxingMap()) { @@ -595,39 +1085,38 @@ void SpecialRoundStart() AddProfile(buffer); } } - delete selectableBosses; - delete selectableBoxingBosses; SF_AddSpecialRound(SPECIALROUND_DOUBLETROUBLE); } - case SPECIALROUND_DOOMBOX: + case SPECIALROUND_SILENTSLENDER: { - char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList().Clone(); - - if (!SF_IsBoxingMap()) + ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); + if (g_DifficultyConVar.IntValue < 2) { - if (selectableBosses.Length > 0) - { - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - } + SetDifficulty(Difficulty_Hard); // Override difficulty to Hardcore. } - else + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 2) { - if (selectableBoxingBosses.Length > 0) - { - selectableBoxingBosses.GetString(GetRandomInt(0, selectableBoxingBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBoxingBosses.GetString(GetRandomInt(0, selectableBoxingBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - } + g_OverrideDifficulty = 2; } - delete selectableBosses; - delete selectableBoxingBosses; - SF_AddSpecialRound(SPECIALROUND_DOOMBOX); + + char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; + ArrayList selectableBosses = GetSelectableBossProfileList(); + + if (selectableBosses.Length > 0) + { + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); + + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); + + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); + + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); + } + SF_AddSpecialRound(SPECIALROUND_SILENTSLENDER); } case SPECIALROUND_THANATOPHOBIA: { @@ -742,7 +1231,11 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. + } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; } SF_AddSpecialRound(SPECIALROUND_INSANEDIFFICULTY); } @@ -750,7 +1243,11 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 2) { - g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. + SetDifficulty(Difficulty_Hard); // Override difficulty to Insane. + } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 2) + { + g_OverrideDifficulty = 2; } if (g_RoundGraceTimer != null) { @@ -762,7 +1259,11 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. + } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; } if (g_RoundGraceTimer != null) { @@ -775,11 +1276,15 @@ void SpecialRoundStart() ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); if (g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. + } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; } char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - ArrayList selectableBoxingBosses = GetSelectableBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); + ArrayList selectableBoxingBosses = GetSelectableBossProfileList(); if (!SF_IsBoxingMap()) { if (selectableBosses.Length > 0) @@ -796,8 +1301,6 @@ void SpecialRoundStart() AddProfile(buffer); } } - delete selectableBosses; - delete selectableBoxingBosses; SF_AddSpecialRound(SPECIALROUND_2DOUBLE); } case SPECIALROUND_SUPRISE: @@ -810,7 +1313,11 @@ void SpecialRoundStart() ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. + } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; } SF_AddSpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS); } @@ -828,13 +1335,13 @@ void SpecialRoundStart() } case SPECIALROUND_MODBOSSES: { - char buffer[SF2_MAX_PROFILE_NAME_LENGTH], sNightmareDisplay[256]; + char buffer[SF2_MAX_PROFILE_NAME_LENGTH], nightmareDisplay[256]; if (!SF_SpecialRound(SPECIALROUND_DOUBLEROULETTE) && !SF_SpecialRound(SPECIALROUND_REVOLUTION)) { NPCStopMusic(); NPCRemoveAll(); } - ArrayList selectableBosses = GetSelectableAdminBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableAdminBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -846,39 +1353,33 @@ void SpecialRoundStart() { case 1: { - g_DifficultyConVar.SetInt(Difficulty_Normal); + SetDifficulty(Difficulty_Normal); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {yellow}%t{default}.", "SF2 Prefix", "SF2 Normal Difficulty"); } case 2: { - g_DifficultyConVar.SetInt(Difficulty_Hard); + SetDifficulty(Difficulty_Hard); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {orange}%t{default}.", "SF2 Prefix", "SF2 Hard Difficulty"); } case 3: { - g_DifficultyConVar.SetInt(Difficulty_Insane); + SetDifficulty(Difficulty_Insane); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {red}%t{default}.", "SF2 Prefix", "SF2 Insane Difficulty"); } case 4: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + PlayNightmareSound(); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); + SetDifficulty(Difficulty_Nightmare); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {valve}%t!", "SF2 Prefix", "SF2 Nightmare Difficulty"); } case 5: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + PlayNightmareSound(); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); + SetDifficulty(Difficulty_Apollyon); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {darkgray}%t!", "SF2 Prefix", "SF2 Apollyon Difficulty"); int randomQuote = GetRandomInt(1, 8); switch (randomQuote) @@ -935,39 +1436,33 @@ void SpecialRoundStart() { case 1: { - g_DifficultyConVar.SetInt(Difficulty_Normal); + SetDifficulty(Difficulty_Normal); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {yellow}%t{default}.", "SF2 Prefix", "SF2 Normal Difficulty"); } case 2: { - g_DifficultyConVar.SetInt(Difficulty_Hard); + SetDifficulty(Difficulty_Hard); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {orange}%t{default}.", "SF2 Prefix", "SF2 Hard Difficulty"); } case 3: { - g_DifficultyConVar.SetInt(Difficulty_Insane); + SetDifficulty(Difficulty_Insane); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {red}%t{default}.", "SF2 Prefix", "SF2 Insane Difficulty"); } case 4: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + PlayNightmareSound(); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); + SetDifficulty(Difficulty_Nightmare); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {valve}%t!", "SF2 Prefix", "SF2 Nightmare Difficulty"); } case 5: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + PlayNightmareSound(); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); + SetDifficulty(Difficulty_Apollyon); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {darkgray}%t!", "SF2 Prefix", "SF2 Apollyon Difficulty"); int randomQuote = GetRandomInt(1, 8); switch (randomQuote) @@ -1017,18 +1512,22 @@ void SpecialRoundStart() } } } + + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < randomDifficulty) + { + g_OverrideDifficulty = randomDifficulty; + } } - delete selectableBosses; SF_AddSpecialRound(SPECIALROUND_MODBOSSES); } case SPECIALROUND_TRIPLEBOSSES: { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - int tripleBosses=0; + int tripleBosses = 0; for (int i = 0; i < MAX_BOSSES; i++) { NPCStopMusic(); - SF2NPC_BaseNPC Npc = view_as(i); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (!Npc.IsValid()) { continue; @@ -1044,7 +1543,7 @@ void SpecialRoundStart() } SF_AddSpecialRound(SPECIALROUND_TRIPLEBOSSES); } - case SPECIALROUND_LIGHTSOUT,SPECIALROUND_NIGHTVISION: + case SPECIALROUND_LIGHTSOUT, SPECIALROUND_NIGHTVISION: { if (g_SpecialRoundType == SPECIALROUND_LIGHTSOUT) { @@ -1060,7 +1559,7 @@ void SpecialRoundStart() } for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1077,99 +1576,24 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Hardcore. - } - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - if (!NPCGetRainbowOutlineState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - SlenderAddGlow(npcIndex, view_as({0, 0, 0, 0})); - } - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. } - for (int i = 1; i <= MaxClients; i++) + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } + g_OverrideDifficulty = 3; } + SF_AddSpecialRound(SPECIALROUND_WALLHAX); } case SPECIALROUND_INFINITEFLASHLIGHT: { SF_RemoveSpecialRound(SPECIALROUND_LIGHTSOUT); - bool nightVision = (g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)); - if (nightVision && g_NightvisionType != 1) + if (IsNightVisionEnabled() && g_NightVisionType != 1) { - g_NightvisionType = 1; + g_NightVisionType = 1; for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1193,7 +1617,7 @@ void SpecialRoundStart() { for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1212,7 +1636,7 @@ void SpecialRoundStart() SF_AddSpecialRound(SPECIALROUND_NOULTRAVISION); for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1268,7 +1692,7 @@ void SpecialRoundStart() { for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1280,38 +1704,25 @@ void SpecialRoundStart() } SF_AddSpecialRound(SPECIALROUND_PAGEDETECTOR); } - case SPECIALROUND_2DOOM: - { - ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); - if (g_DifficultyConVar.IntValue < 2) - { - g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. - } - char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - if (selectableBosses.Length > 0) - { - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - } - delete selectableBosses; - SF_AddSpecialRound(SPECIALROUND_2DOOM); - } case SPECIALROUND_SINGLEPLAYER: { for (int client = 1; client <= MaxClients; client++) { - if (IsValidClient(client) && IsClientInGame(client) && !g_PlayerEliminated[client] && !DidClientEscape(client)) + if (IsValidClient(client) && !g_PlayerEliminated[client] && !DidClientEscape(client)) { TF2_StripContrackerOnly(client); } } SF_AddSpecialRound(SPECIALROUND_SINGLEPLAYER); } + case SPECIALROUND_BEATBOX: + { + g_BeatBoxCueIndex = 0; + g_BeatBoxMasterTime = CreateTimer(2.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + g_BeatBoxMusicTimer = CreateTimer(0.1, Timer_BeatBoxMusic, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); + EmitBeatBoxMusic(); + SF_AddSpecialRound(SPECIALROUND_BEATBOX); + } default: { SF_AddSpecialRound(g_SpecialRoundType); @@ -1326,6 +1737,10 @@ void SpecialRoundStart() { SpecialRoundCycleStart(); } + + Call_StartForward(g_OnSpecialRoundStartPFwd); + Call_PushCell(g_SpecialRoundType); + Call_Finish(); } static Action Timer_SpecialRoundVoteLoop(Handle timer) @@ -1381,7 +1796,7 @@ static void SpecialCreateVote() NativeVotes_SetInitiator(voteMenu, NATIVEVOTES_SERVER_INDEX); char title[255]; - FormatEx(title,255,"%t%t","SF2 Prefix","SF2 Special Round Vote Menu Title"); + FormatEx(title, 255, "%t%t", "SF2 Prefix", "SF2 Special Round Vote Menu Title"); NativeVotes_SetDetails(voteMenu,title); ArrayList enabledRounds = SpecialEnabledList().Clone(); @@ -1436,9 +1851,9 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Bacon Spray"); } - case SPECIALROUND_DOOMBOX: + case SPECIALROUND_SILENTSLENDER: { - FormatEx(item, sizeof(item), "Doom Box"); + FormatEx(item, sizeof(item), "Silent Slender"); } case SPECIALROUND_NOGRACE: { @@ -1524,10 +1939,6 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Class Scramble"); } - case SPECIALROUND_2DOOM: - { - FormatEx(item, sizeof(item), "Silent Slender"); - } case SPECIALROUND_PAGEREWARDS: { FormatEx(item, sizeof(item), "Page Rewards"); @@ -1544,7 +1955,6 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Triple Bosses"); } - case SPECIALROUND_BOSSROULETTE: { FormatEx(item, sizeof(item), "Boss Roulette"); @@ -1553,14 +1963,22 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Wall Hax"); } + case SPECIALROUND_SINGLEPLAYER: + { + FormatEx(item, sizeof(item), "Single Player"); + } + case SPECIALROUND_BEATBOX: + { + FormatEx(item, sizeof(item), "Beat Box"); + } } - for (int iBit = 0; iBit < 30; iBit++) + for (int bit = 0; bit < 30; bit++) { - if (strcmp(item[iBit],"-") == 0 ||strcmp(item[iBit],":") == 0) + if (strcmp(item[bit], "-") == 0 || strcmp(item[bit], ":") == 0) { break; } - itemOutPut[iBit] = item[iBit]; + itemOutPut[bit] = item[bit]; } FormatEx(item, sizeof(item), "%d", round); NativeVotes_AddItem(voteMenu, item, itemOutPut); @@ -1573,7 +1991,7 @@ static void SpecialCreateVote() for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1614,7 +2032,7 @@ static int Menu_SpecialVote(Handle menu, MenuAction action,int param1,int param2 NativeVotes_GetItem(menu, param1, specialRound, sizeof(specialRound), specialRoundName, sizeof(specialRoundName)); CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Special Round Vote Successful", specialRoundName); - FormatEx(display,120,"%t","SF2 Special Round Vote Successful", specialRoundName); + FormatEx(display, 120, "%t", "SF2 Special Round Vote Successful", specialRoundName); g_SpecialRoundType = StringToInt(specialRound); g_SpecialRoundOverrideConVar.SetInt(g_SpecialRoundType); @@ -1630,12 +2048,6 @@ static int Menu_SpecialVote(Handle menu, MenuAction action,int param1,int param2 return 0; } -void SpecialRound_RoundEnd() -{ - g_Started = false; - SF_RemoveAllSpecialRound(); -} - void SpecialRoundReset() { g_SpecialRoundType = 0; @@ -1663,4 +2075,4 @@ static any Native_IsSpecialRoundRunning(Handle plugin,int numParams) static any Native_GetSpecialRoundType(Handle plugin,int numParams) { return g_SpecialRoundType; -} \ No newline at end of file +} diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index ad7923bb..71180d4d 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -25,11 +25,11 @@ #define HIDEHUD_INVEHICLE ( 1<<10 ) #define HIDEHUD_BONUS_PROGRESS ( 1<<11 ) // Hide bonus progress display (for bonus map challenges) -#define FFADE_IN 0x0001 // Just here so we don't pass 0 into the function -#define FFADE_OUT 0x0002 // Fade out (not in) -#define FFADE_MODULATE 0x0004 // Modulate (don't blend) -#define FFADE_STAYOUT 0x0008 // ignores the duration, stays faded out until new ScreenFade message received -#define FFADE_PURGE 0x0010 // Purges all other fades, replacing them with this one +#define FFADE_IN 0x0001 // Just here so we don't pass 0 into the function +#define FFADE_OUT 0x0002 // Fade out (not in) +#define FFADE_MODULATE 0x0004 // Modulate (don't blend) +#define FFADE_STAYOUT 0x0008 // ignores the duration, stays faded out until new ScreenFade message received +#define FFADE_PURGE 0x0010 // Purges all other fades, replacing them with this one #define SF_FADE_IN 0x0001 // Fade in, not out #define SF_FADE_MODULATE 0x0002 // Modulate, don't blend @@ -50,6 +50,7 @@ #define MAX_BUTTONS 26 +// m_nSolidType #define SOLID_NONE 0 // no solid model #define SOLID_BSP 1 // a BSP tree #define SOLID_BBOX 2 // an AABB @@ -69,14 +70,6 @@ #define TF_WEAPON_PHLOGISTINATOR 594 -// m_nSolidType -#define SOLID_NONE 0 // no solid model -#define SOLID_BSP 1 // a BSP tree -#define SOLID_BBOX 2 // an AABB -#define SOLID_OBB 3 // an OBB (not implemented yet) -#define SOLID_OBB_YAW 4 // an OBB, constrained so that it can only yaw -#define SOLID_CUSTOM 5 // Always call into the entity for tests -#define SOLID_VPHYSICS 6 // solid vphysics object, get vcollide from the model and collide with that // m_usSolidFlags #define FSOLID_CUSTOMRAYTEST 0x0001 // Ignore solid type + always call into the entity for ray tests #define FSOLID_CUSTOMBOXTEST 0x0002 // Ignore solid type + always call into the entity for swept box tests @@ -90,32 +83,21 @@ #define FSOLID_ROOT_PARENT_ALIGNED 0x0100 // Collisions are defined in root parent's local coordinate space #define FSOLID_TRIGGER_TOUCH_DEBRIS 0x0200 // This trigger will touch debris objects -#define DAMAGE_NO 0 -#define DAMAGE_EVENTS_ONLY 1 -#define DAMAGE_YES 2 -#define DAMAGE_AIM 3 - -#define EF_BONEMERGE 0x001 // Performs bone merge on client side -#define EF_BRIGHTLIGHT 0x002 // DLIGHT centered at entity origin -#define EF_DIMLIGHT 0x004 // player flashlight -#define EF_NOINTERP 0x008 // don't interpolate the next frame -#define EF_NOSHADOW 0x010 // Don't cast no shadow -#define EF_NODRAW 0x020 // don't draw entity -#define EF_NORECEIVESHADOW 0x040 // Don't receive no shadow -#define EF_BONEMERGE_FASTCULL 0x080 // For use with EF_BONEMERGE. If this is set, then it places this ent's origin at its - // parent and uses the parent's bbox + the max extents of the aiment. - // Otherwise, it sets up the parent's bones every frame to figure out where to place - // the aiment, which is inefficient because it'll setup the parent's bones even if - // the parent is not in the PVS. -#define EF_ITEM_BLINK 0x100 // blink an item so that the user notices it. -#define EF_PARENT_ANIMATES 0x200 // always assume that the parent entity is animating - // hull defines, mostly used for space checking. float HULL_HUMAN_MINS[3] = { -13.0, -13.0, 0.0 }; float HULL_HUMAN_MAXS[3] = { 13.0, 13.0, 72.0 }; float HULL_TF2PLAYER_MINS[3] = { -24.5, -24.5, 0.0 }; -float HULL_TF2PLAYER_MAXS[3] = { 24.5, 24.5, 83.0 }; +float HULL_TF2PLAYER_MAXS[3] = { 24.5, 24.5, 83.0 }; + +// ========================================================== +// Overrides +// ========================================================== +bool TF2_IsPlayerInConditionEx(int client, TFCond condition) +{ + return g_ClientInCondition[client][condition]; +} +#define TF2_IsPlayerInCondition TF2_IsPlayerInConditionEx // ========================================================== // Map Functions @@ -123,32 +105,32 @@ float HULL_TF2PLAYER_MAXS[3] = { 24.5, 24.5, 83.0 }; bool SF_IsSurvivalMap() { - return !!(g_IsSurvivalMap || (g_SurvivalMapConVar.IntValue == 1)); + return (g_IsSurvivalMap || (g_SurvivalMapConVar.IntValue == 1)); } bool SF_IsRaidMap() { - return !!(g_IsRaidMap || (g_RaidMapConVar.IntValue == 1)); + return (g_IsRaidMap || (g_RaidMapConVar.IntValue == 1)); } bool SF_IsProxyMap() { - return !!(g_IsProxyMap || (g_ProxyMapConVar.IntValue == 1)); + return (g_IsProxyMap || (g_ProxyMapConVar.IntValue == 1)); } bool SF_BossesChaseEndlessly() { - return !!(g_BossesChaseEndlessly || (g_BossChaseEndlesslyConVar.IntValue == 1)); + return (g_BossesChaseEndlessly || (g_BossChaseEndlesslyConVar.IntValue == 1)); } bool SF_IsBoxingMap() { - return !!(g_IsBoxingMap || (g_BoxingMapConVar.IntValue == 1)); + return (g_IsBoxingMap || (g_BoxingMapConVar.IntValue == 1)); } bool SF_IsSlaughterRunMap() { - return !!(g_IsSlaughterRunMap || (g_SlaughterRunMapConVar.IntValue == 1)); + return (g_IsSlaughterRunMap || (g_SlaughterRunMapConVar.IntValue == 1)); } /* int SDK_StartTouch(int entity, int iOther) @@ -173,7 +155,7 @@ bool SDK_PointIsWithin(int func, float pos[3]) { if (g_SDKPointIsWithin != null) { - return !!(SDKCall(g_SDKPointIsWithin, func, pos)); + return (SDKCall(g_SDKPointIsWithin, func, pos)) != 0; } return false; @@ -194,30 +176,17 @@ int EnsureEntRef(int entIndex) int SetEntityTransmitState(int entity, int newFlags) { - if (!IsValidEdict(entity)) - { - return 0; - } - - int flags = GetEdictFlags(entity); - flags &= ~(FL_EDICT_ALWAYS | FL_EDICT_PVSCHECK | FL_EDICT_DONTSEND); - flags |= newFlags; - SetEdictFlags(entity, flags); - - return flags; -} - -bool IsEntityClassname(int entIndex, const char[] classname, bool caseSensitive=true) -{ - if (!IsValidEntity(entIndex)) + if (!IsValidEdict(entity)) { - return false; + return 0; } - char buffer[256]; - GetEntityClassname(entIndex, buffer, sizeof(buffer)); + int flags = GetEdictFlags(entity); + flags &= ~(FL_EDICT_ALWAYS | FL_EDICT_PVSCHECK | FL_EDICT_DONTSEND); + flags |= newFlags; + SetEdictFlags(entity, flags); - return strcmp(buffer, classname, caseSensitive) == 0; + return flags; } int FindEntityByTargetname(const char[] targetName, const char[] className, bool caseSensitive=true) @@ -243,11 +212,11 @@ float GetVectorSquareMagnitude(const float vec1[3], const float vec2[3]) /*float GetVectorAnglesTwoPoints(const float startPos[3], const float endPos[3], float angles[3]) { - static float tmpVec[3]; - tmpVec[0] = endPos[0] - startPos[0]; - tmpVec[1] = endPos[1] - startPos[1]; - tmpVec[2] = endPos[2] - startPos[2]; - GetVectorAngles(tmpVec, angles); + static float tmpVec[3]; + tmpVec[0] = endPos[0] - startPos[0]; + tmpVec[1] = endPos[1] - startPos[1]; + tmpVec[2] = endPos[2] - startPos[2]; + GetVectorAngles(tmpVec, angles); }*/ float SquareFloat(const float value) @@ -262,90 +231,122 @@ float EntityDistanceFromEntity(int ent1, int ent2) return -1.0; } - float myPos[3],hisPos[3]; + float myPos[3], hisPos[3]; GetEntPropVector(ent1, Prop_Data, "m_vecAbsOrigin", myPos); GetEntPropVector(ent2, Prop_Data, "m_vecAbsOrigin", hisPos); return GetVectorSquareMagnitude(myPos, hisPos); } -bool IsSpaceOccupied(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) -{ - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; - return hit; -} - bool IsSpaceOccupiedIgnorePlayers(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitPlayersOrEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) + { + if (list.Get(i) == entity) + { + continue; + } + + check = true; + break; + } + if (check) + { + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitPlayersOrEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; + } + + delete list; return hit; } bool IsSpaceOccupiedIgnorePlayersAndEnts(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitCharactersOrEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; - return hit; -} + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) + { + if (list.Get(i) == entity) + { + continue; + } -bool IsSpaceOccupiedPlayer(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) -{ - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; - return hit; -} + check = true; + break; + } + if (check) + { + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitCharactersOrEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; + } -bool IsSpaceOccupiedNPC(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) -{ - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_NPCSOLID, TraceRayDontHitEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; + delete list; return hit; } -int EntitySetAnimation(int entity, const char[] name, float playbackRate = 1.0, int forceSequence = -1, float cycle = 0.0) +bool IsSpaceOccupiedPlayer(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { - CBaseCombatCharacter animationEntity = CBaseCombatCharacter(entity); - int sequence = forceSequence; - Activity activity = TranslateProfileActivityFromName(name); - if (activity != ACT_INVALID) + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) { - sequence = animationEntity.SelectWeightedSequence(activity); + if (list.Get(i) == entity) + { + continue; + } + + check = true; + break; } - else if (activity == ACT_INVALID && forceSequence == -1) + if (check) { - sequence = animationEntity.LookupSequence(name); + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; } - if (sequence != -1) + delete list; + return hit; +} + +bool IsSpaceOccupiedNPC(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) +{ + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) { - animationEntity.ResetSequence(sequence); - if (cycle > 0.0) + if (list.Get(i) == entity) { - animationEntity.SetPropFloat(Prop_Data, "m_flCycle", cycle); + continue; } - } - if (playbackRate < -12.0) - { - playbackRate = -12.0; + check = true; + break; } - if (playbackRate > 12.0) + if (check) { - playbackRate = 12.0; + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_NPCSOLID, TraceRayDontHitEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; } - animationEntity.SetPropFloat(Prop_Send, "m_flPlaybackRate", playbackRate); - return sequence; + + delete list; + return hit; } void CBaseNPC_RemoveAllLayers(int entity) @@ -399,6 +400,23 @@ bool NavHasFuncPrefer(CNavArea area) return false; } +int LookupBone(int entity, const char[] name) +{ + int bone = -1; + bone = SDKCall(g_SDKLookupBone, entity, name); + return bone; +} + +void GetBonePosition(int entity, int bone, float origin[3], float angles[3]) +{ + SDKCall(g_SDKGetBonePosition, entity, bone, origin, angles); +} + +bool CBaseAnimating_GetSequenceVelocity(Address studioHdr, int sequence, float cycle, Address poseParameter, float velocity[3]) +{ + return SDKCall(g_SDKSequenceVelocity, studioHdr, sequence, cycle, poseParameter, velocity); +} + // ========================================================= // GLOW FUNCTIONS // ========================================================= @@ -408,15 +426,15 @@ int TF2_CreateGlow(int entIndex) char oldEntName[64]; GetEntPropString(entIndex, Prop_Data, "m_iName", oldEntName, sizeof(oldEntName)); - char strName[126], strClass[64]; - GetEntityClassname(entIndex, strClass, sizeof(strClass)); - FormatEx(strName, sizeof(strName), "%s%i", strClass, entIndex); - DispatchKeyValue(entIndex, "targetname", strName); + char name[126], class[64]; + GetEntityClassname(entIndex, class, sizeof(class)); + FormatEx(name, sizeof(name), "%s%i", class, entIndex); + DispatchKeyValue(entIndex, "targetname", name); int ent = CreateEntityByName("tf_glow"); - DispatchKeyValue(ent, "target", strName); - FormatEx(strName, sizeof(strName), "tf_glow_%i", entIndex); - DispatchKeyValue(ent, "targetname", strName); + DispatchKeyValue(ent, "target", name); + FormatEx(name, sizeof(name), "tf_glow_%i", entIndex); + DispatchKeyValue(ent, "targetname", name); DispatchKeyValue(ent, "Mode", "0"); DispatchSpawn(ent); @@ -432,7 +450,7 @@ int TF2_CreateGlow(int entIndex) // ========================================================== //Credits to Linux_lover for this and signature. -void SDK_PlaySpecificSequence(int client, const char[] strSequence) +void SDK_PlaySpecificSequence(int client, const char[] sequence) { if (g_SDKPlaySpecificSequence != null) { @@ -440,11 +458,11 @@ void SDK_PlaySpecificSequence(int client, const char[] strSequence) static bool once = true; if (once) { - PrintToServer("(SDK_PlaySpecificSequence) Calling on player %N \"%s\"..", client, strSequence); + PrintToServer("(SDK_PlaySpecificSequence) Calling on player %N \"%s\"..", client, sequence); once = false; } #endif - SDKCall(g_SDKPlaySpecificSequence, client, strSequence); + SDKCall(g_SDKPlaySpecificSequence, client, sequence); } } @@ -460,26 +478,26 @@ void KillClient(int client) { if (client != -1) { - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); ForcePlayerSuicide(client); SetVariantInt(9001); AcceptEntityInput(client, "RemoveHealth"); } } -bool IsEntityAProjectile(int entity) +void Explode(float pos[3], float damage, float radius, int attacker, const char[] explosionParticle = "ExplosionCore_MidAir") { - char classname[64]; - if (IsValidEntity(entity) && GetEntityClassname(entity, classname, sizeof(classname)) && - (strcmp(classname, "env_explosion") == 0 || - strcmp(classname, "tf_projectile_sentryrocket") == 0 || - strcmp(classname, "tf_projectile_rocket") == 0 || - strcmp(classname, "tf_projectile_pipe") == 0 || - strcmp(classname, "tf_projectile_arrow") == 0)) - { - return true; - } - return false; + int bomb = CreateEntityByName("tf_generic_bomb"); + DispatchKeyValueVector(bomb, "origin", pos); + DispatchKeyValueFloat(bomb, "damage", damage); + DispatchKeyValueFloat(bomb, "radius", radius); + DispatchKeyValue(bomb, "health", "1"); + DispatchKeyValue(bomb, "friendlyfire", "1"); + DispatchKeyValue(bomb, "explode_particle", explosionParticle); + SetEntityOwner(bomb, attacker); + DispatchSpawn(bomb); + + SDKHooks_TakeDamage(bomb, attacker, attacker, 9001.0, DMG_BLAST); } void DestroyAllActiveWeapons(int client) @@ -495,9 +513,6 @@ void DestroyAllActiveWeapons(int client) } } -#define SF_IGNORE_LOS 0x0004 -#define SF_NO_DISGUISED_SPY_HEALING 0x0008 - int SDK_SwitchWeapon(int client, int weapon) { if (g_SDKWeaponSwitch != null) @@ -581,8 +596,8 @@ bool IsClientCritBoosted(int client) void TF2_StripWearables(int client) { - int entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_wearable")) > MaxClients) + int entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_wearable")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -590,8 +605,8 @@ void TF2_StripWearables(int client) } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_wearable_vm")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_wearable_vm")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -599,8 +614,8 @@ void TF2_StripWearables(int client) } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_powerup_bottle")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_powerup_bottle")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -608,8 +623,8 @@ void TF2_StripWearables(int client) } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_wearable_razorback")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_wearable_razorback")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -620,8 +635,8 @@ void TF2_StripWearables(int client) void TF2_DestroySpyWeapons() { - int entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_weapon_revolver")) > MaxClients) + int entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_weapon_revolver")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -629,8 +644,8 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_weapon_builder")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_weapon_builder")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -638,8 +653,8 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_weapon_knife")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_weapon_knife")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -647,8 +662,8 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "saxxy")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "saxxy")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -656,8 +671,8 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_weapon_pda_spy")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_weapon_pda_spy")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -665,8 +680,8 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_weapon_invis")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_weapon_invis")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -674,8 +689,8 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; - while((entity = FindEntityByClassname(entity, "tf_weapon_sapper")) > MaxClients) + entity = MaxClients + 1; + while ((entity = FindEntityByClassname(entity, "tf_weapon_sapper")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -684,7 +699,7 @@ void TF2_DestroySpyWeapons() } } -void ClientSwitchToWeaponSlot(int client,int slot) +void ClientSwitchToWeaponSlot(int client, int slot) { int weaponEnt = GetPlayerWeaponSlot(client, slot); if (weaponEnt <= MaxClients) @@ -695,9 +710,9 @@ void ClientSwitchToWeaponSlot(int client,int slot) SDK_SwitchWeapon(client, weaponEnt); } -void ChangeClientTeamNoSuicide(int client,int team, bool respawn=true) +void ChangeClientTeamNoSuicide(int client, int team, bool respawn = true) { - if (!IsClientInGame(client)) + if (!IsValidClient(client)) { return; } @@ -728,7 +743,7 @@ void UTIL_ClientScreenShake(int client, float amplitude, float duration, float f } } -void UTIL_ScreenFade(int client,int duration,int time,int flags,int r,int g,int b,int a) +void UTIL_ScreenFade(int client, int duration, int time, int flags, int r, int g, int b, int a) { int clients[1]; Handle bf; @@ -746,34 +761,61 @@ void UTIL_ScreenFade(int client,int duration,int time,int flags,int r,int g,int EndMessage(); } +bool IsClientInGameEx(int client) +{ + return g_ClientInGame[client]; +} + bool IsValidClient(int client) { - return client > 0 && client <= MaxClients && IsClientInGame(client); + return client > 0 && client <= MaxClients && IsClientInGameEx(client); } void PrintToSourceTV(const char[] message) { int client = GetClientOfUserId(g_SourceTVUserID); - if (MaxClients >= client > 0 && IsClientInGame(client) && IsClientSourceTV(client)) + if (MaxClients >= client > 0 && IsValidClient(client) && IsClientSourceTV(client)) { CPrintToChat(client, message); } } + +void ShowHealthRegen(int client, int amount, int weaponIndex = -1) +{ + Event event = CreateEvent("player_healonhit"); + if (event) + { + event.SetInt("amount", amount); + event.SetInt("entindex", client); + event.SetInt("weapon_def_index", weaponIndex); + event.Fire(); + } +} + // ========================================================== // TF2-SPECIFIC FUNCTIONS // ========================================================== bool TF2_IsMiniCritBuffed(int client) { return (TF2_IsPlayerInCondition(client, TFCond_CritCola) - || TF2_IsPlayerInCondition(client, TFCond_CritHype) - || TF2_IsPlayerInCondition(client, TFCond_Buffed) - ); + || TF2_IsPlayerInCondition(client, TFCond_Buffed)); +} + +bool TF2_IsInvisible(int client) +{ + return ((TF2_IsPlayerInCondition(client, TFCond_Cloaked) || + TF2_IsPlayerInCondition(client, TFCond_DeadRingered) || + TF2_IsPlayerInCondition(client, TFCond_Stealthed)) + && !TF2_IsPlayerInCondition(client, TFCond_StealthedUserBuffFade) + && !TF2_IsPlayerInCondition(client, TFCond_CloakFlicker)); } bool IsTauntWep(int weaponEnt) { int index = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (index==37 || index==304 || index==5 || index==195 || index==43 || index==239 || index==310 || index==331 || index==426 || index==587 || index==656 || index==1084 || index==1100 || index == 1143) + if (index == 37 || index == 304 || index == 5 || index == 195 || index == 43 || + index == 239 || index == 310 || index == 331 || index == 426 || index == 587 || + index == 656 || index == 1084 || index == 1100 || index == 1143) { return true; } @@ -808,7 +850,7 @@ void ForceTeamWin(int team) AcceptEntityInput(ent, "SetWinner"); } -Handle PrepareItemHandle(char[] classname,int index,int level,int quality, char[] att) +Handle PrepareItemHandle(char[] classname, int index, int level, int quality, char[] att) { Handle item = TF2Items_CreateItem(OVERRIDE_ALL | FORCE_GENERATION); TF2Items_SetClassname(item, classname); @@ -846,20 +888,24 @@ void SpeakResponseConcept(int client, const char[] concept) //Thanks The Gaben void SpecialRoundGameText(const char[] message, const char[] icon = "") { int entity = CreateEntityByName("game_text_tf"); - DispatchKeyValue(entity,"message", message); - DispatchKeyValue(entity,"display_to_team", "0"); - DispatchKeyValue(entity,"icon", icon); - DispatchKeyValue(entity,"targetname", "game_text1"); - DispatchKeyValue(entity,"background", "0"); + DispatchKeyValue(entity, "message", message); + DispatchKeyValue(entity, "display_to_team", "0"); + DispatchKeyValue(entity, "icon", icon); + DispatchKeyValue(entity, "targetname", "game_text1"); + DispatchKeyValue(entity, "background", "0"); DispatchSpawn(entity); AcceptEntityInput(entity, "Display", entity, entity); //The only time I keep this. CreateTimer(2.0, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); } + // Removes wearables such as botkillers from weapons. -void TF2_RemoveWeaponSlotAndWearables(int client,int slot) +void TF2_RemoveWeaponSlotAndWearables(int client, int slot) { int weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!IsValidEntity(weaponEnt)) return; + if (!IsValidEntity(weaponEnt)) + { + return; + } int wearable = INVALID_ENT_REFERENCE; while ((wearable = FindEntityByClassname(wearable, "tf_wearable")) != -1) @@ -907,43 +953,165 @@ void TF2_StripContrackerOnly(int client) } } -void TE_Particle(int particleIndex, float origin[3]=NULL_VECTOR, float start[3]=NULL_VECTOR, float angles[3]=NULL_VECTOR, int entindex=-1, int attachtype=-1, int attachpoint=-1, bool resetParticles=true) +void TE_Particle(int particleIndex, + float origin[3] = NULL_VECTOR, + float start[3] = NULL_VECTOR, + float angles[3] = NULL_VECTOR, + int entindex = -1, + int attachtype = -1, + int attachpoint = -1, + bool resetParticles = true, + bool controlPoint = false, + int controlPointAttachType = -1, + const float controlPointOffset[3] = NULL_VECTOR) { - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", origin[0]); - TE_WriteFloat("m_vecOrigin[1]", origin[1]); - TE_WriteFloat("m_vecOrigin[2]", origin[2]); - TE_WriteFloat("m_vecStart[0]", start[0]); - TE_WriteFloat("m_vecStart[1]", start[1]); - TE_WriteFloat("m_vecStart[2]", start[2]); - TE_WriteVector("m_vecAngles", angles); - TE_WriteNum("m_iParticleSystemIndex", particleIndex); - TE_WriteNum("entindex", entindex); + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", origin[0]); + TE_WriteFloat("m_vecOrigin[1]", origin[1]); + TE_WriteFloat("m_vecOrigin[2]", origin[2]); + TE_WriteFloat("m_vecStart[0]", start[0]); + TE_WriteFloat("m_vecStart[1]", start[1]); + TE_WriteFloat("m_vecStart[2]", start[2]); + TE_WriteVector("m_vecAngles", angles); + TE_WriteNum("m_iParticleSystemIndex", particleIndex); + TE_WriteNum("entindex", entindex); + + if (attachtype != -1) + { + TE_WriteNum("m_iAttachType", attachtype); + } + if (attachpoint != -1) + { + TE_WriteNum("m_iAttachmentPointIndex", attachpoint); + } + TE_WriteNum("m_bResetParticles", resetParticles ? 1 : 0); + + TE_WriteNum("m_bControlPoint1", controlPoint); + if (controlPoint) + { + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", controlPointAttachType); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", controlPointOffset[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", controlPointOffset[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", controlPointOffset[2]); + } +} + +/*void CreateParticle(char[] particle, float pos[3], float ang[3]) +{ + int tblidx = FindStringTable("ParticleEffectNames"); + char tmp[256]; + int count = GetStringTableNumStrings(tblidx); + int stridx = INVALID_STRING_INDEX; + + for (int i = 0; i < count; i++) + { + ReadStringTable(tblidx, i, tmp, sizeof(tmp)); + if (StrEqual(tmp, particle, false)) + { + stridx = i; + break; + } + } + + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", pos[0]); + TE_WriteFloat("m_vecOrigin[1]", pos[1]); + TE_WriteFloat("m_vecOrigin[2]", pos[2]); + TE_WriteVector("m_vecAngles", ang); + TE_WriteNum("m_iParticleSystemIndex", stridx); + TE_WriteNum("entindex", -1); + TE_WriteNum("m_iAttachType", 5); + TE_SendToAll(); +}*/ + +void TE_SetupEffectDispatch(int effectName, + const float origin[3] = NULL_VECTOR, + const float start[3] = NULL_VECTOR, + const float angles[3] = NULL_VECTOR, + const float normal[3] = NULL_VECTOR, + int flags = 0, + float magnitude = 0.0, + float scale = 1.0, + int attachmentIndex = 0, + int surfaceProp = 0, + int material = 0, + int damageType = 0, + int hitbox = 0, + int entindex = 0, + int color = 0, + float radius = 0.0, + bool customColors = false, + bool controlPoint1 = false, + int cp1AttachType = -1, + const float cp1Offset[3] = NULL_VECTOR) +{ + TE_Start("EffectDispatch"); + TE_WriteFloat("m_vOrigin[0]", origin[0]); + TE_WriteFloat("m_vOrigin[1]", origin[1]); + TE_WriteFloat("m_vOrigin[2]", origin[2]); + TE_WriteFloat("m_vStart[0]", start[0]); + TE_WriteFloat("m_vStart[1]", start[1]); + TE_WriteFloat("m_vStart[2]", start[2]); + TE_WriteVector("m_vAngles", angles); + TE_WriteVector("m_vNormal", normal); + TE_WriteNum("m_fFlags", flags); + TE_WriteFloat("m_flMagnitude", magnitude); + TE_WriteFloat("m_flScale", scale); + TE_WriteNum("m_nAttachmentIndex", attachmentIndex); + TE_WriteNum("m_nSurfaceProp", surfaceProp); + TE_WriteNum("m_iEffectName", effectName); + TE_WriteNum("m_nMaterial", material); + TE_WriteNum("m_nDamageType", damageType); + TE_WriteNum("m_nHitBox", hitbox); + TE_WriteNum("entindex", entindex); + TE_WriteNum("m_nColor", color); + TE_WriteFloat("m_flRadius", radius); + TE_WriteNum("m_bCustomColors", customColors); + TE_WriteNum("m_bControlPoint1", controlPoint1); + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", cp1AttachType); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", cp1Offset[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", cp1Offset[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", cp1Offset[2]); +} + +int GetEffectDispatchStringTableIndex(const char[] effectName) +{ + static int table = INVALID_STRING_TABLE; + if (table == INVALID_STRING_TABLE) + { + table = FindStringTable("EffectDispatch"); + } + + int index = FindStringIndex2(table, effectName); + if (index == INVALID_STRING_INDEX) + { + int numStrings = GetStringTableNumStrings(table); + if (numStrings >= GetStringTableMaxStrings(table)) + { + return INVALID_STRING_INDEX; + } - if (attachtype != -1) - { - TE_WriteNum("m_iAttachType", attachtype); - } - if (attachpoint != -1) - { - TE_WriteNum("m_iAttachmentPointIndex", attachpoint); - } - TE_WriteNum("m_bResetParticles", resetParticles ? 1 : 0); + AddToStringTable(table, effectName); + index = numStrings; + } + + return index; } void UTIL_ScreenShake(float center[3], float amplitude, float frequency, float duration, float radius, int command, bool airShake) { - for(int i=1; i<=MaxClients; i++) + for(int i = 1; i <= MaxClients; i++) { - if (IsClientInGame(i) && !IsFakeClient(i) && !IsClientInGhostMode(i)) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (player.IsValid && !player.IsBot && !player.IsInGhostMode) { - if (!airShake && command == 0 && !(GetEntityFlags(i) && FL_ONGROUND)) + if (!airShake && command == 0 && !(player.GetFlags() && FL_ONGROUND)) { continue; } float playerPos[3]; - GetClientAbsOrigin(i, playerPos); + player.GetAbsOrigin(playerPos); float localAmplitude = ComputeShakeAmplitude(center, playerPos, amplitude, radius); @@ -954,7 +1122,7 @@ void UTIL_ScreenShake(float center[3], float amplitude, float frequency, float d if (localAmplitude > 0 || command == 1) { - Handle msg = StartMessageOne("Shake", i, USERMSG_RELIABLE); + Handle msg = StartMessageOne("Shake", player.index, USERMSG_RELIABLE); if (msg != null) { BfWriteByte(msg, command); @@ -997,7 +1165,7 @@ float ComputeShakeAmplitude(float center[3], float playerPos[3], float amplitude /** * Converts a given timestamp into hours, minutes, and seconds. */ -void FloatToTimeHMS(float time,int &h=0,int &m=0,int &s=0) +void FloatToTimeHMS(float time, int &h=0, int &m=0, int &s=0) { s = RoundFloat(time); h = s / 3600; @@ -1006,7 +1174,7 @@ void FloatToTimeHMS(float time,int &h=0,int &m=0,int &s=0) s = s % 60; } -int FixedUnsigned16(float value,int scale) +int FixedUnsigned16(float value, int scale) { int output; @@ -1041,19 +1209,19 @@ float FloatClamp(float a, float min, float max) /** * Linearly interpolates between a and b by t. */ -/*float LerpFloats(const float a, const float b, float t) +float LerpFloats(const float a, const float b, float t) { - if (t < 0.0) + if (t < 0.0) { t = 0.0; } - if (t > 1.0) + if (t > 1.0) { t = 1.0; } - return a + (b - a) * t; -}*/ + return a + (b - a) * t; +} // ========================================================== // VECTOR FUNCTIONS @@ -1071,18 +1239,18 @@ void CopyVector(const float copy[3], float dest[3]) /*void LerpVectors(const float a[3] , const float b[3], float c[3], float t) { - if (t < 0.0) + if (t < 0.0) { t = 0.0; } - if (t > 1.0) + if (t > 1.0) { t = 1.0; } - c[0] = a[0] + (b[0] - a[0]) * t; - c[1] = a[1] + (b[1] - a[1]) * t; - c[2] = a[2] + (b[2] - a[2]) * t; + c[0] = a[0] + (b[0] - a[0]) * t; + c[1] = a[1] + (b[1] - a[1]) * t; + c[2] = a[2] + (b[2] - a[2]) * t; }*/ /** @@ -1111,12 +1279,26 @@ void GetPositionForward(float pos[3], float ang[3], float returnValue[3], float float dir[3]; GetAngleVectors(ang, dir, NULL_VECTOR, NULL_VECTOR); returnValue = pos; - for(int i=0; i<3; i++) + for(int i = 0; i < 3; i++) { returnValue[i] += dir[i] * distance; } } +bool IsEmptyVector(const float test[3]) +{ + int count = 0; + for (int i = 0; i < 3; i++) + { + if (test[i] == 0.0) + { + count++; + } + } + + return count == 3; +} + // ========================================================== // ANGLE FUNCTIONS // ========================================================== @@ -1146,126 +1328,127 @@ void GetPositionForward(float pos[3], float ang[3], float returnValue[3], float return AngleNormalize(value); }*/ -float AngleNormalize(float angle) -{ - while (angle > 180.0) - { - angle -= 360.0; - } - while (angle < -180.0) - { - angle += 360.0; - } - return angle; -} - -float AngleDiff(float firstAngle, float secondAngle) -{ - float diff = secondAngle - firstAngle; - return AngleNormalize(diff); -} //Credits to Boikinov for figuring out these calculations I don't understand the thought process of float GetAngleBetweenVectors(const float vector1[3], const float vector2[3], const float direction[3]) { - float vector1_n[3], vector2_n[3], direction_n[3], cross[3]; - NormalizeVector(direction, direction_n); - NormalizeVector(vector1, vector1_n); - NormalizeVector(vector2, vector2_n); - float degree = ArcCosine(GetVectorDotProduct( vector1_n, vector2_n )) * 57.29577951; - GetVectorCrossProduct(vector1_n, vector2_n, cross); + float vector1_n[3], vector2_n[3], direction_n[3], cross[3]; + NormalizeVector(direction, direction_n); + NormalizeVector(vector1, vector1_n); + NormalizeVector(vector2, vector2_n); + float degree = ArcCosine(GetVectorDotProduct(vector1_n, vector2_n)) * 57.29577951; + GetVectorCrossProduct(vector1_n, vector2_n, cross); - if (GetVectorDotProduct(cross, direction_n) < 0.0) - { - degree *= -1.0; - } + if (GetVectorDotProduct(cross, direction_n) < 0.0) + { + degree *= -1.0; + } - return degree; + return degree; } void RotateYaw(float angles[3], float degree) { - float direction[3], normal[3]; - GetAngleVectors(angles, direction, NULL_VECTOR, normal); + float direction[3], normal[3]; + GetAngleVectors(angles, direction, NULL_VECTOR, normal); - float sin = Sine(degree * 0.01745328); - float cos = Cosine(degree * 0.01745328); - float a = normal[0] * sin; - float b = normal[1] * sin; - float c = normal[2] * sin; - float x = direction[2] * b + direction[0] * cos - direction[1] * c; - float y = direction[0] * c + direction[1] * cos - direction[2] * a; - float z = direction[1] * a + direction[2] * cos - direction[0] * b; - direction[0] = x; - direction[1] = y; - direction[2] = z; + float sin = Sine(degree * 0.01745328); + float cos = Cosine(degree * 0.01745328); + float a = normal[0] * sin; + float b = normal[1] * sin; + float c = normal[2] * sin; + float x = direction[2] * b + direction[0] * cos - direction[1] * c; + float y = direction[0] * c + direction[1] * cos - direction[2] * a; + float z = direction[1] * a + direction[2] * cos - direction[0] * b; + direction[0] = x; + direction[1] = y; + direction[2] = z; - GetVectorAngles(direction, angles); + GetVectorAngles(direction, angles); - float up[3]; - GetVectorVectors(direction, NULL_VECTOR, up); + float up[3]; + GetVectorVectors(direction, NULL_VECTOR, up); - float roll = GetAngleBetweenVectors(up, normal, direction); - angles[2] += roll; + float roll = GetAngleBetweenVectors(up, normal, direction); + angles[2] += roll; } // ========================================================== // TRACE FUNCTIONS // ========================================================== -bool TraceRayDontHitEntity(int entity,int mask,any data) +bool TraceRayDontHitEntity(int entity, int mask, any data) { if (entity == data) { return false; } - if (IsValidEntity(entity)) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc")) - { - return false; - } + return false; } return true; } -bool TraceRayDontHitPlayers(int entity,int mask, any data) +bool TraceRayDontHitPlayers(int entity, int mask, any data) { - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { return false; } - if (IsValidEntity(entity)) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc")) - { - return false; - } + return false; } return true; } -bool TraceRayDontHitPlayersOrEntity(int entity,int mask,any data) +bool TraceRayDontHitPlayersOrEntity(int entity, int mask, any data) { if (entity == data) { return false; } - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { return false; } - if (IsValidEntity(entity)) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc")) - { - return false; - } + return false; + } + + return true; +} + +bool TraceRayDontHitPlayersOrEntityEx(int entity, int mask, any data) +{ + if (entity == data) + { + return false; + } + if (IsValidClient(entity)) + { + return false; + } + + return true; +} + +bool EnumerateEverything(int entIndex, ArrayList array) +{ + if (IsValidEntity(entIndex)) + { + array.Push(entIndex); + } + + return true; +} + +bool EnumerateLivingPlayers(int entIndex, ArrayList players) +{ + if (IsValidClient(entIndex) && IsPlayerAlive(entIndex) && !IsClientInGhostMode(entIndex)) + { + players.Push(entIndex); } return true; @@ -1277,11 +1460,7 @@ bool TraceRayDontHitPlayersOrEntity(int entity,int mask,any data) Action Timer_KillEntity(Handle timer, any entref) { int ent = EntRefToEntIndex(entref); - if (ent == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - if (!IsValidEntity(ent)) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } @@ -1294,7 +1473,7 @@ Action Timer_KillEntity(Handle timer, any entref) Action Timer_KillEdict(Handle timer, any entref) { int ent = EntRefToEntIndex(entref); - if (!IsValidEdict(ent)) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } @@ -1309,10 +1488,25 @@ Action Timer_KillEdict(Handle timer, any entref) // ========================================================== bool IsInfiniteFlashlightEnabled() { - return !!(g_RoundInfiniteFlashlight || (g_PlayerInfiniteFlashlightOverrideConVar.IntValue == 1) || SF_SpecialRound(SPECIALROUND_INFINITEFLASHLIGHT) || ((g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)) && g_NightvisionType == 1)); + return (g_RoundInfiniteFlashlight || (g_PlayerInfiniteFlashlightOverrideConVar.IntValue == 1) || SF_SpecialRound(SPECIALROUND_INFINITEFLASHLIGHT) || (IsNightVisionEnabled() && g_NightVisionType == 1)); +} + +bool IsInfiniteBlinkEnabled() +{ + return g_RoundInfiniteBlink || (g_PlayerInfiniteBlinkOverrideConVar.IntValue == 1); +} + +bool IsInfiniteSprintEnabled() +{ + return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1) || SF_IsSlaughterRunMap() || SF_IsBoxingMap() || SF_IsRaidMap(); } -int g_ArraySpecialRoundType[SPECIALROUND_MAXROUNDS]; +bool IsNightVisionEnabled() +{ + return g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION); +} + +static int g_ArraySpecialRoundType[SPECIALROUND_MAXROUNDS]; bool SF_SpecialRound(int specialRound) { @@ -1320,9 +1514,9 @@ bool SF_SpecialRound(int specialRound) { return false; } - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { - if (specialRound==g_ArraySpecialRoundType[array]) + if (specialRound == g_ArraySpecialRoundType[array]) { return true; } @@ -1332,7 +1526,7 @@ bool SF_SpecialRound(int specialRound) void SF_AddSpecialRound(int specialRound) { - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { if (g_ArraySpecialRoundType[array] == 0 || g_ArraySpecialRoundType[array] == specialRound) { @@ -1344,28 +1538,11 @@ void SF_AddSpecialRound(int specialRound) void SF_RemoveSpecialRound(int specialRound) { - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { if (g_ArraySpecialRoundType[array] == specialRound) { g_ArraySpecialRoundType[array] = 0; - //Useless - /*if (array != (SPECIALROUND_MAXROUNDS-1)) - { - for (int iArray2 = array;iArray2 < SPECIALROUND_MAXROUNDS; iArray2++) - { - if (g_ArraySpecialRoundType[iArray2+1] != 0) - { - g_ArraySpecialRoundType[iArray2] = g_ArraySpecialRoundType[iArray2+1]; - g_ArraySpecialRoundType[iArray2+1] = 0; - } - else - { - g_ArraySpecialRoundType[iArray2] = 0; - break; - } - } - }*/ break; } } @@ -1373,7 +1550,7 @@ void SF_RemoveSpecialRound(int specialRound) void SF_RemoveAllSpecialRound() { - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { g_ArraySpecialRoundType[array] = 0; } @@ -1384,7 +1561,19 @@ void SF_RemoveAllSpecialRound() // ========================================================== int GetLocalGlobalDifficulty(int npcIndex = -1) { - if (SF_IsBoxingMap()) + if (npcIndex == -1) + { + return g_DifficultyConVar.IntValue; + } + SF2BossProfileData data; + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(npcIndex); + data = controller.GetProfileData(); + SF2ChaserBossProfileData chaserData; + if (data.Type == SF2BossType_Chaser) + { + chaserData = view_as(controller).GetProfileData(); + } + if (data.IsPvEBoss || chaserData.BoxingBoss) { if (NPCGetUniqueID(npcIndex) != -1) { @@ -1398,120 +1587,38 @@ int GetLocalGlobalDifficulty(int npcIndex = -1) return g_DifficultyConVar.IntValue; } -bool DispatchParticleEffect(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], - int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) +float GetDifficultyModifier(int difficulty) { - char particleReal[PLATFORM_MAX_PATH]; - FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); - if (particle[0] != '\0') + switch (difficulty) { - int tblidx = FindStringTable("ParticleEffectNames"); - if (tblidx == INVALID_STRING_TABLE) + case Difficulty_Hard: { - LogError("Could not find string table: ParticleEffectNames"); - return false; + return DIFFICULTYMODIFIER_HARD; } - char tmp[256]; - int count = GetStringTableNumStrings(tblidx); - int stridx = INVALID_STRING_INDEX; - for (int i = 0; i < count; i++) - { - ReadStringTable(tblidx, i, tmp, sizeof(tmp)); - if (strcmp(tmp, particleReal, false) == 0) - { - stridx = i; - break; - } - } - if (stridx == INVALID_STRING_INDEX) - { - LogError("Could not find particle: %s", particleReal); - return false; - } - - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", startPos[0]); - TE_WriteFloat("m_vecOrigin[1]", startPos[1]); - TE_WriteFloat("m_vecOrigin[2]", startPos[2]); - TE_WriteVector("m_vecAngles", angles); - TE_WriteNum("m_iParticleSystemIndex", stridx); - TE_WriteNum("entindex", entity); - TE_WriteNum("m_iAttachType", view_as(attachType)); - TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); - TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); - TE_WriteNum("m_bControlPoint1", 0); - TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 0); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); - TE_SendToAll(); - } - else - { - //LogError("There is no valid particle to use for effects."); - return false; - } - return true; -} -bool DispatchParticleEffectBeam(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], - int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) -{ - char particleReal[PLATFORM_MAX_PATH]; - FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); - if (particle[0] != '\0') - { - int tblidx = FindStringTable("ParticleEffectNames"); - if (tblidx == INVALID_STRING_TABLE) + case Difficulty_Insane: { - LogError("Could not find string table: ParticleEffectNames"); - return false; + return DIFFICULTYMODIFIER_INSANE; } - char tmp[256]; - int count = GetStringTableNumStrings(tblidx); - int stridx = INVALID_STRING_INDEX; - for (int i = 0; i < count; i++) + + case Difficulty_Nightmare: { - ReadStringTable(tblidx, i, tmp, sizeof(tmp)); - if (strcmp(tmp, particleReal, false) == 0) - { - stridx = i; - break; - } + return DIFFICULTYMODIFIER_NIGHTMARE; } - if (stridx == INVALID_STRING_INDEX) + + case Difficulty_Apollyon: { - LogError("Could not find particle: %s", particleReal); - return false; + return DIFFICULTYMODIFIER_APOLLYON; } - - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", startPos[0]); - TE_WriteFloat("m_vecOrigin[1]", startPos[1]); - TE_WriteFloat("m_vecOrigin[2]", startPos[2]); - TE_WriteVector("m_vecAngles", angles); - TE_WriteNum("m_iParticleSystemIndex", stridx); - TE_WriteNum("entindex", entity); - TE_WriteNum("m_iAttachType", view_as(attachType)); - TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); - TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); - TE_WriteNum("m_bControlPoint1", 1); - TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); - TE_SendToAll(); } - else - { - //LogError("There is no valid particle to use for effects."); - return false; - } - return true; + + return DIFFICULTYMODIFIER_NORMAL; } -MRESReturn Hook_GlowUpdateTransmitState(int glow, DHookReturn returnHook) +void PlayNightmareSound() { - returnHook.Value = SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); - return MRES_Supercede; + for (int i = 0; i < sizeof(g_SoundNightmareMode); i++) + { + EmitSoundToAll(g_SoundNightmareMode[i]); + } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index 5fc9d841..56f246da 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -8,16 +8,39 @@ static float g_TrapDespawnTimer[2049]; static bool g_TrapClosed[2049]; static int g_TrapState[2049]; -static int g_TrapMaster[2049]; +static SF2NPC_Chaser g_TrapMaster[2049]; static bool g_TrapStartedOpenAnim[2049]; static bool g_TrapDoIdleAnim[2049]; static bool g_TrapAnimChange[2049]; static Handle g_TrapTimer[2049]; //State 0 = Idle, State 1 = Closed -void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) +void SetupTraps() { - int slender = NPCGetEntIndex(bossIndex); + g_OnPlayerJumpPFwd.AddFunction(null, OnJump); +} + +static void OnJump(SF2_BasePlayer client) +{ + if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped) + { + return; + } + + if (client.IsTrapped) + { + client.TrapCount -= 1; + } + if (client.IsTrapped && client.TrapCount <= 1) + { + client.IsTrapped = false; + client.TrapCount = 0; + } +} + +void Trap_SpawnTrap(float position[3], float direction[3], SF2NPC_Chaser controller) +{ + int slender = controller.EntIndex; if (!slender || slender == INVALID_ENT_REFERENCE) { return; @@ -41,7 +64,11 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) float yaw = GetAngleBetweenVectors(product2, cross, tempAngles); RotateYaw(newAngles, yaw - 90.0); - switch (NPCChaserGetTrapType(bossIndex)) + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + + switch (data.TrapType[difficulty]) { case SF2BossTrapType_BearTrap: { @@ -49,13 +76,13 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) if (trapEntity != -1) { TeleportEntity(trapEntity, newPos, newAngles, NULL_VECTOR); - SetEntityModel(trapEntity, g_SlenderTrapModel[bossIndex]); + SetEntityModel(trapEntity, data.TrapModel); DispatchSpawn(trapEntity); ActivateEntity(trapEntity); SetEntProp(trapEntity, Prop_Send, "m_usSolidFlags", FSOLID_TRIGGER_TOUCH_DEBRIS|FSOLID_TRIGGER|FSOLID_NOT_SOLID|FSOLID_CUSTOMBOXTEST); SetEntProp(trapEntity, Prop_Data, "m_nSolidType", SOLID_BBOX); - SetEntProp(trapEntity, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); // COLLISION_GROUP_DEBRIS + SetEntityCollisionGroup(trapEntity, COLLISION_GROUP_DEBRIS_TRIGGER); float mins[3], maxs[3]; mins[0] = -25.0; @@ -72,21 +99,21 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) AcceptEntityInput(trapEntity, "EnableCollision"); - g_TrapMaster[trapEntity] = bossIndex; + g_TrapMaster[trapEntity] = controller; g_TrapClosed[trapEntity] = false; g_TrapAnimChange[trapEntity] = true; SetEntProp(trapEntity, Prop_Data, "m_bSequenceLoops", false); - if (g_SlenderTrapAnimOpen[bossIndex][0] != '\0') + if (data.TrapAnimOpen[0] != '\0') { - SetVariantString(g_SlenderTrapAnimOpen[bossIndex]); + SetVariantString(data.TrapAnimOpen); AcceptEntityInput(trapEntity, "SetAnimation"); HookSingleEntityOutput(trapEntity, "OnAnimationDone", OnTrapOpenComplete, false); g_TrapStartedOpenAnim[trapEntity] = true; } else { - SetVariantString(g_SlenderTrapAnimIdle[bossIndex]); + SetVariantString(data.TrapAnimIdle); AcceptEntityInput(trapEntity, "SetAnimation"); } @@ -95,7 +122,7 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) g_TrapDespawnTimer[trapEntity] = GetGameTime() + GetRandomFloat(20.0, 40.0); g_TrapState[trapEntity] = 0; - EmitSoundToAll(g_SlenderTrapDeploySound[bossIndex], trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); + EmitSoundToAll(data.TrapDeploySound, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); SDKHook(trapEntity, SDKHook_OnTakeDamage, Hook_TrapOnTakeDamage); g_TrapTimer[trapEntity] = CreateTimer(0.1, Timer_TrapThink, EntIndexToEntRef(trapEntity), TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); @@ -106,11 +133,6 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) static Action Timer_TrapThink(Handle timer, any entref) { - if (!g_Enabled) - { - return Plugin_Stop; - } - int trapEntity = EntRefToEntIndex(entref); if (!trapEntity || trapEntity == INVALID_ENT_REFERENCE) { @@ -122,7 +144,8 @@ static Action Timer_TrapThink(Handle timer, any entref) return Plugin_Stop; } - if (GetGameTime() >= g_TrapDespawnTimer[trapEntity]) + SF2NPC_Chaser controller = g_TrapMaster[trapEntity]; + if (GetGameTime() >= g_TrapDespawnTimer[trapEntity] || !controller.IsValid()) { Trap_Despawn(trapEntity); } @@ -132,17 +155,31 @@ static Action Timer_TrapThink(Handle timer, any entref) for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || - !player.IsAlive || - player.IsEliminated || + if (!player.IsValid) + { + continue; + } + + if (!player.IsAlive || + player.IsInDeathCam || player.IsInGhostMode || - player.HasEscaped) + player.HasEscaped || + player.InCondition(view_as(130)) || + player.Team == TFTeam_Spectator) { continue; } + if (!g_Enabled) + { + if (player.GetProp(Prop_Data, "m_iTeamNum") == controller.DefaultTeam) + { + continue; + } + } + float entPos[3], otherPos[3]; - player.GetPropVector(Prop_Data, "m_vecAbsOrigin", otherPos); + player.GetAbsOrigin(otherPos); GetEntPropVector(trapEntity, Prop_Data, "m_vecAbsOrigin", entPos); float zPos = otherPos[2] - entPos[2]; float distance = GetVectorSquareMagnitude(otherPos, entPos); @@ -167,16 +204,17 @@ static Action Timer_TrapThink(Handle timer, any entref) player.TrapCount = GetRandomInt(2, 4); } } - if (!g_PlayerHints[player.index][PlayerHint_Trap]) + if (!player.HasHint(PlayerHint_Trap)) { player.ShowHint(PlayerHint_Trap); } - SDKHooks_TakeDamage(player.index, player.index, player.index, 10.0, 128); + player.TakeDamage(true, _, _, 10.0, 128); g_TrapState[trapEntity] = 1; g_TrapAnimChange[trapEntity] = true; - int bossIndex = g_TrapMaster[trapEntity]; - EmitSoundToAll(g_SlenderTrapHitSound[bossIndex], trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - SetVariantString(g_SlenderTrapAnimClose[bossIndex]); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + EmitSoundToAll(data.TrapCatchSound, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); + SetVariantString(data.TrapAnimClose); AcceptEntityInput(trapEntity, "SetAnimation"); AcceptEntityInput(trapEntity, "DisableCollision"); g_TrapClosed[trapEntity] = true; @@ -195,27 +233,33 @@ static void TrapUpdateAnimation(int trapEntity) return; } int state = g_TrapState[trapEntity]; - int bossIndex = g_TrapMaster[trapEntity]; + SF2NPC_Chaser controller = g_TrapMaster[trapEntity]; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); switch (state) { case 0: { - if (!g_TrapStartedOpenAnim[trapEntity] && g_SlenderTrapAnimOpen[bossIndex][0] != '\0') + if (!g_TrapStartedOpenAnim[trapEntity] && data.TrapAnimOpen[0] != '\0') { - SetVariantString(g_SlenderTrapAnimOpen[bossIndex]); + SetVariantString(data.TrapAnimOpen); AcceptEntityInput(trapEntity, "SetAnimation"); HookSingleEntityOutput(trapEntity, "OnAnimationDone", OnTrapOpenComplete, true); g_TrapStartedOpenAnim[trapEntity] = true; } else if (g_TrapDoIdleAnim[trapEntity]) { - SetVariantString(g_SlenderTrapAnimIdle[bossIndex]); + SetVariantString(data.TrapAnimIdle); AcceptEntityInput(trapEntity, "SetAnimation"); } } case 1: { - SetVariantString(g_SlenderTrapAnimClose[bossIndex]); + SetVariantString(data.TrapAnimClose); AcceptEntityInput(trapEntity, "SetAnimation"); AcceptEntityInput(trapEntity, "DisableCollision"); } @@ -232,31 +276,8 @@ static void OnTrapOpenComplete(const char[] output, int caller, int activator, f g_TrapDoIdleAnim[caller] = true; } } -/* -public Action Hook_TrapTouch(int trapEntity, int client) -{ - if (MaxClients >= client > 0 && IsClientInGame(client)) - { - if (!g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red && !g_TrapClosed[trapEntity]) - { - g_PlayerTrapped[client] = true; - if (!g_PlayerHints[client][PlayerHint_Trap]) - { - ClientShowHint(client, PlayerHint_Trap); - } - SDKHooks_TakeDamage(client, client, client, 10.0, 128); - g_PlayerTrapCount[client] = GetRandomInt(2, 4); - g_TrapClosed[trapEntity] = true; - g_TrapState[trapEntity] = 1; - EmitSoundToAll(TRAP_CLOSE, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - AcceptEntityInput(trapEntity, "DisableCollision"); - } - if (IsClientInGhostMode(client)) return Plugin_Handled; - } - return Plugin_Continue; -} -*/ -static Action Hook_TrapOnTakeDamage(int trapEntity,int &attacker,int &inflictor,float &damage,int &damagetype,int &weapon, float damageForce[3], float damagePosition[3],int damagecustom) + +static Action Hook_TrapOnTakeDamage(int trapEntity, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { if (!g_Enabled) { @@ -272,9 +293,11 @@ static Action Hook_TrapOnTakeDamage(int trapEntity,int &attacker,int &inflictor, g_TrapClosed[trapEntity] = true; g_TrapState[trapEntity] = 1; g_TrapAnimChange[trapEntity] = true; - int bossIndex = g_TrapMaster[trapEntity]; - EmitSoundToAll(g_SlenderTrapMissSound[bossIndex], trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - SetVariantString(g_SlenderTrapAnimClose[bossIndex]); + SF2NPC_Chaser controller = g_TrapMaster[trapEntity]; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + EmitSoundToAll(data.TrapMissSound, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); + SetVariantString(data.TrapAnimClose); AcceptEntityInput(trapEntity, "SetAnimation"); AcceptEntityInput(trapEntity, "DisableCollision"); if (g_TrapDespawnTimer[trapEntity] > 5.0) diff --git a/addons/sourcemod/translations/sf2.phrases.txt b/addons/sourcemod/translations/sf2.phrases.txt index fdbd0b31..02340da5 100644 --- a/addons/sourcemod/translations/sf2.phrases.txt +++ b/addons/sourcemod/translations/sf2.phrases.txt @@ -7,8 +7,44 @@ "fi" "Valitse vaikeustaso:" "chi" "选择难度:" "pt" "Selecione uma dificuldade:" + "ko" "난이도 선택:" + } + "SF2 Renevant Wall Hax" + { + "en" "You can now see bosses and players through walls." + } + "SF2 Renevant Marked" + { + "en" "Everyone is marked for death permanently." + } + "SF2 Renevant 90s" + { + "en" "You feel really nervous, now run faster." + } + "SF2 Renevant Bacon" + { + "en" "Bosses are now alerted on spawn." + } + "SF2 Renevant Multieffects" + { + "en" "Bosses can now inflict Multieffect." + } + "SF2 Renevant Difficulty" + { + "en" "Difficulty has been set to" + } + "SF2 Boss Added" + { + "en" "Next Boss" + } + "SF2 Bosses Added" + { + "en" "Next Bosses" + } + "SF2 Admin Boss" + { + "en" "Next Exclusive Boss" } - "SF2 Difficulty Vote Finished" { "en" "Vote successful! The difficulty has been changed to:" @@ -16,6 +52,19 @@ "fi" "Äänestys onnistui! Vaikeustaso on muutettu tasoon:" "chi" "投票成功!难度已被改为:" "pt" "Votação finalizada! A dificuldade foi alterada para:" + "ko" "투표 성공! 선택된 난이도:" + } + + "SF2 Difficulty Vote Finished Unsuccessful" + { + "#format" "{1:d}" + "en" "Vote for ({lightblue}Apollyon{default}/{lightblue}Nightmare{default}) failed, ({lightblue}{1}{default}%%) of the vote is required to pass. The difficulty has been changed to:" + } + + "SF2 Difficulty Vote Finished Unsuccessful Runoff" + { + "#format" "{1:d}" + "en" "Vote for ({lightblue}Apollyon{default}/{lightblue}Nightmare{default}) failed, ({lightblue}{1}{default}%%) of the vote is required to pass. Initiating a runoff vote..." } "SF2 Easy Difficulty" @@ -25,6 +74,7 @@ "fi" "Helppo" "chi" "新手" "pt" "Fácil" + "ko" "쉬움" } "SF2 Normal Difficulty" @@ -34,6 +84,7 @@ "fi" "Tavallinen" "chi" "普通" "pt" "Normal" + "ko" "보통" } "SF2 Hard Difficulty" @@ -43,6 +94,7 @@ "fi" "Vaikea" "chi" "困难" "pt" "Hardcore" + "ko" "하드코어" } "SF2 Insane Difficulty" @@ -52,6 +104,7 @@ "fi" "Hullu" "chi" "疯狂" "pt" "Insana" + "ko" "광기" } "SF2 Nightmare Difficulty" @@ -59,24 +112,28 @@ "en" "Nightmare" "fi" "Painajainen" "pt" "Pesadelo" + "ko" "악몽" } "SF2 Apollyon Difficulty" { "en" "Apollyon" "pt" "Apoliom" + "ko" "아폴리온" } "SF2 Calamity Difficulty" { "en" "Calamity" "pt" "Calamidade" + "ko" "대재앙" } - + "SF2 Random Difficulty" { "en" "Random" "pt" "Aleatória" + "ko" "랜덤" } "SF2 Default Intro Message Singular" @@ -87,6 +144,7 @@ "fi" "Kerää {1} lappu" "chi" "收集 {1} 纸张" "pt" "Colete {1} página" + "ko" "단서 {1} 개 수집" } "SF2 Default Intro Message Plural" @@ -97,6 +155,7 @@ "fi" "Kerää kaikki {1} lappua" "chi" "收集所有 {1} 纸张" "pt" "Colete todas as {1} páginas" + "ko" "단서 {1} 개 모두 수집" } "SF2 Default Escape Message" @@ -106,6 +165,7 @@ "fi" "Pakene!" "chi" "逃出去!" "pt" "Fuja!" + "ko" "탈출하세요!" } "SF2 Default Survive Message" @@ -115,6 +175,7 @@ "fi" "Selviydy!" "chi" "活下来!" "pt" "Sobreviva!" + "ko" "살아남으세요!" } "SF2 Default Boxing Message" @@ -124,6 +185,7 @@ "fi" "Tapa Mestari!" "chi" "击杀冠军!" "pt" "Mate o campeão!" + "ko" "챔피언을 쓰러뜨리세요!" } "SF2 Grace Period End" @@ -133,13 +195,15 @@ "fi" "Lisäaika loppui. Kuolema johtaa nyt eliminointiin." "chi" "宽限期结束。死亡将会导致出局。" "pt" "O tempo de preparação acabou. Mortes resultarão em eliminação." + "ko" "유예 기간이 끝났습니다. 이제 죽으면 탈락입니다." } - + "SF2 Grace Period Page" { "en" "Wait for Grace period to end to collect a page." "pt" "Aguardando o tempo de preparação acabar para coletar uma página." + "ko" "단서를 수집하기 위해선 유예 기간이 끝날 때까지 기다리세요." } "SF2 Player Escaped" @@ -150,6 +214,7 @@ "fi" "{dodgerblue}{1}{default} on paennut!" "chi" "{dodgerblue}{1}{default} 成功逃出了!" "pt" "{dodgerblue}{1}{default} escapou!" + "ko" "{dodgerblue}{1}{default}(이)가 탈출했습니다!" } "SF2 Player Killed Champion" @@ -160,16 +225,13 @@ "fi" "{red}{1}{default} on tappanut {valve}{2}!" "chi" "{red}{1}{default} 击杀了 {valve}{2}!" "pt" "{red}{1}{default} matou o {valve}{2}!" + "ko" "{red}{1}{default} (이)가 {valve}{2}{default}를 쓰러뜨렸습니다!" } "SF2 Camping System Warning" { "#format" "{1:d}" - "en" "WARNING: If you do not move from your spot in {1} seconds, you will be caught by the boss." - "ru" "ВНИМАНИЕ: Если в течение {1} сек. вы не начнёте двигаться, то вы будете схвачены!" - "fi" "VAROITUS: Jos et liiku paikaltasi {1} sekunttiin, vihollinen nappaa sinut." - "chi" "警告:如果你在 {1} 秒内不从当前位置移动的话,你会被Boss抓住。" - "pt" "AVISO: caso não se mova do seu lugar em {1} segundos, você será pego pelo chefão." + "en" "ALERT: If you do not move from your spot in {1} seconds, you will be eliminated from the game." } "SF2 Main Menu Title" @@ -179,6 +241,7 @@ "fi" "Päävalikko" "chi" "主菜单" "pt" "Menu principal" + "ko" "메인 메뉴" } "SF2 Queue Menu Title" @@ -188,6 +251,7 @@ "fi" "Jonolista" "chi" "队列清单" "pt" "Fila de espera" + "ko" "대기열" } "SF2 Reset Queue Points Option" @@ -198,6 +262,7 @@ "fi" "Sinun pisteesi: {1} (valitse tyhjentääksesi)" "chi" "你的点数: {1} (选择此项重置)" "pt" "Seus pontos: {1} (selecione para zerar)" + "ko" "현재 포인트: {1} (누를시 포인트 초기화)" } "SF2 Should Reset Queue Points" @@ -207,6 +272,7 @@ "fi" "Haluatko varmasti tyhjentää pisteesi 0:aan?" "chi" "你真的要重置点数为0吗?" "pt" "Tem certeza de que deseja zerar os seus pontos de espera?" + "ko" "정말 대기열 포인트를 0으로 초기화 하시겠습니까?" } "SF2 Queue Points Reset" @@ -216,6 +282,7 @@ "fi" "Olet tyhjentänyt pisteesi 0:aan" "chi" "你已重置点数为0。" "pt" "Os seus pontos de espera foram zerados." + "ko" "대기열 포인트가 0으로 초기화 되었습니다." } "SF2 Ghost Mode Menu Title" @@ -225,6 +292,7 @@ "fi" "Muuta Haamu Tilaa:" "chi" "开关幽灵模式:" "pt" "Alternar modo fantasma:" + "ko" "유령 모드 토글:" } "SF2 Ghost Mode Enabled" @@ -234,6 +302,7 @@ "fi" "Olet nyt Haamu Tilassa. Käytä äänikomentoja teleporttaaksesi RED pelaajaan. Hiivi napauttaaksesi itsesi maahan ilmasta." "chi" "你现在处于幽灵模式。使用语言命令来传送到一个红队玩家。在空中按蹲来降落。" "pt" "Você está no modo fantasma. Use um comando de voz para teletransportar-se a um jogador da equipe RED. Pressione \"Agachar\" para jogar-se ao chão." + "ko" "유령 모드가 되었습니다. 음성 명령을 사용하면 RED 팀 플레이어에게 텔레포트 됩니다. 공중에 있는 동안 앉기 키를 누르면 빠르게 바닥으로 내려갑니다." } "SF2 Ghost Mode Disabled" @@ -243,6 +312,7 @@ "fi" "Olet ottanut Haamu Tilan pois." "chi" "你已关闭幽灵模式。" "pt" "Modo fantasma desativado." + "ko" "유령 모드가 비활성화되었습니다." } "SF2 Ghost Mode Not Allowed" @@ -252,6 +322,7 @@ "fi" "Et voi mennä Haamu Tilaan juuri nyt." "chi" "你现在不可以切换幽灵模式。" "pt" "Alternação para o modo fantasma indisponível." + "ko" "지금은 유령 모드를 사용할 수 없습니다." } "SF2 Ghost Mode Bad Connection" @@ -262,12 +333,12 @@ "fi" "Sinut potkittiin ulos Haamu Tilasta ajoituksen vuoksi {1} sekunniksi." "chi" "你因为超时了 {1} 秒而被移出幽灵模式。" "pt" "Você foi removido do modo fantasma devido ao tempo-limite de {1} segundo(s) ter acabado." + "ko" "{1}초 간의 연결 지연시간 초과로 인해 유령 모드가 해제되었습니다." } "SF2 Prefix" { "en" "[SF2]" - "pt" "[SF2]" } "SF2 Help Menu Title" @@ -277,6 +348,7 @@ "fi" "Ohjevalikko" "chi" "帮助菜单" "pt" "Menu de ajuda" + "ko" "도움말" } "SF2 Help Objective Menu Title" @@ -286,6 +358,7 @@ "fi" "Tehtävä" "chi" "目标" "pt" "Objetivo" + "ko" "목표" } "SF2 Help Objective Description" @@ -295,6 +368,7 @@ "fi" "Slender Fortress 2 (SF2) on pelimuoto joka perustuu\n peliin \"Slender: The Eight Pages\" tehnyt Mark\nJ. Hadley. Tehtäväsi on kerätä kaikki tavarat\nkartalta ja paeta jäämättäsi kiinni\nviholliselle. Keräät lappuja lyömällä\nniitä lähitaistelu aseellasi." "chi" "暗鬼要塞2(SF2)是一个基于游戏\n \"瘦长鬼影:八页纸\" 作者Mark J.Hadley。\n游戏目标是收集地图内所有物品而不被敌人抓住\n使用近战物品攻击物品来收集。" "pt" "O Slender Fortress 2 (SF2) é um modo de jogo baseado no\njogo \"Slender: The Eight Pages\" feito por Mark J. Hadley.\nO objetivo é coletar todos os itens no mapa e escapar sem\nser pego pelos inimigos. Colete páginas ao bater nelas\ncom a sua arma corpo a corpo." + "ko" "슬렌더 포트리스 2 (SF2)는\nMark J. Hadley 가 만든 게임인\n\"Slender: The Eight Pages\"\n을 베이스로 한 게임모드입니다.\n적을 피해 단서를 모두 모으고 시설을 탈출 하세요.\n근접 공격을 통해 단서를 모을 수 있습니다." } "SF2 Help Objective Description 2" @@ -304,6 +378,7 @@ "fi" "Vain osa pelaajista valitaan RED:lle\npelaamaan peliä. Muut pelaajat joutuvat odottamaan\nvuoroaan BLU:lla kunnes on heidän vuoronsa.\nBLU pystyy katsomaan RED:iä reaaliaikaisesti\nkäyttäen komentoa !slghost." "chi" "只有一部分的蓝队玩家会被选入游戏\n剩下的玩家必须在蓝队等待直到轮到自己\n蓝队可以使用!slghost来实时观察红队\n而不用进入观察者队伍。" "pt" "Apenas uma certa quantidade de jogadores serão selecionados\nda equipe BLU para jogar. O resto dos jogadores terão\nde esperar na equipe BLU até que o seu turno chegue.\nJogadores da equipe BLU podem assistir os jogadores\nda equipe RED em tempo real com o comando \"!slghost\"\nsem ter que assisti-los no modo espectador." + "ko" "BLU 팀 플레이어 중에서 일정 인원 수만 선택되어 플레이하게 됩니다.\n나머지 플레이어는 대기실에서 다음 차례가 올 때까지 기다려야 합니다.\nBLU 팀은 관전자 팀에 들어갈 필요 없이\n!slghost 명령어를 통해 유령이 되어\n실시간으로 RED팀 상황을 볼 수 있습니다." } "SF2 Help Commands Menu Title" @@ -313,6 +388,7 @@ "fi" "Komentolista" "chi" "命令列表" "pt" "Lista de comandos" + "ko" "명령어 모음" } "SF2 Help Commands Description" @@ -322,6 +398,7 @@ "fi" "!slender - Näyttää päävalikon.\n!slnext - Näyttää listan seuraavista pelaajista.\n!slghost - Näyttää Haamu Tilan valikon.\n!slhelp - Näyttää ohjevalikon.\n!slcredits - Näyttää mahtavat henkilöt jotka auttoivat tämän pelimuodon kanssa." "chi" "!slender - 显示主菜单。\n!slnext - 显示列表谁是下一个玩家。\n!slghost - 显示幽灵模式菜单。\n!slhelp - 显示帮助菜单。\n!slcredits - 显示帮助制作此mod的人。" "pt" "!slender — exibe o menu principal.\n!slnext — exibe a fila de espera de quem vai jogar.\n!slghost — exibe o menu do modo fantasma.\n!slhelp — exibe o menu de ajuda.\n!slcredits — exibe os nomes de quem ajudou a desenvolver este modo." + "ko" "!slender - 게임모드의 메인메뉴를 보여줍니다.\n!slnext - 현재 대기열을 보여줍니다.\n!slghost - 유령 모드가 됩니다.\n!slhelp - 도움말을 보여줍니다.\n!slcredits - 어떤 개쩌는 녀셕들이 이 게임모드에 도움을 줬는지 알려줍니다." } "SF2 Help Ghost Mode Menu Title" @@ -331,6 +408,7 @@ "fi" "Mikä on Haamu Tila?" "chi" "什么是幽灵模式?" "pt" "O que é o modo fantasma?" + "ko" "유령 모드가 뭔가요?" } "SF2 Help Ghost Mode Description" @@ -340,6 +418,7 @@ "fi" "Haamu Tila on ominaisuus jolla BLU pelaajat\nvoivat katsoa RED pelaajia ilman että\ntarvitsee mennä Katsomoon. RED pelaajat eivät pysty\nnäkemään sinua, eivätkä pysty vaikuttaa\nsinuun. Sinä et myöskään pysty vaikuttamaan RED:iin." "chi" "幽灵模式是让蓝队可以实时观察红队但又不需要\n进入观察者的一项功能。红队无法看见你和交互。\n你也不能和红队进行交互。" "pt" "O modo fantasma é um recurso que jogadores da equipe BLU\npodem usar para assistir jogadores da RED sem ir para o\nmodo espectador. Jogadores da RED não poderão ver nem\ninteragir com você. Você também não pode interagir com\na equipe RED." + "ko" "유령 모드는 BLU 팀 플레이어가 관전자 팀으로 넘어가지 않고\nRED 팀 상황을 볼 수 있게 해줍니다.\nRED 팀 플레이어는 유령이 된 당신을 볼 수도 만질 수도 없습니다.\n또한 통상적으로는 얘기도 나눌 수 없습니다." } "SF2 Help Controls Menu Title" @@ -349,6 +428,7 @@ "fi" "Kontrollit" "chi" "控制" "pt" "Controles" + "ko" "조작법" } "SF2 Help Controls Description" @@ -358,6 +438,7 @@ "fi" "Ensisijainen hyökkäys - Kerää lappu\nToissijainen hyökkäys - Ottaa taskulampun käyttöön\nLataa - Räpäyttää\nErityis hyökkäys - Juokse" "chi" "主要攻击键 - 拾取物品\n次要攻击键 - 开关闪光灯\n装填弹药键 - 眨眼\n按住特殊攻击键 - 冲刺" "pt" "Ataque primário — coletar página\nAtaque secundário — alternar lanterna\nRecarregar — piscar\nSEGURAR \"ataque especial\" — correr" + "ko" "주 공격키 - 단서 수집\n보조 공격키 - 손전등 토글\n재장전키 - 눈 깜빡이기\n특수 공격키 누르기 - 달리기" } "SF2 Help Sprinting And Stamina Menu Title" @@ -367,6 +448,7 @@ "fi" "Juokseminen ja Kestävyys" "chi" "冲刺与耐力" "pt" "Corrida e energia" + "ko" "달리기와 스테미너" } "SF2 Help Sprinting And Stamina Description" @@ -376,6 +458,7 @@ "fi" "Sinulla on kyky juosta, jota pystyt käyttämään\npitämällä pojassa erikois hyökkäys nappiasi\n(oletuksena näppäin MOUSE3, jota voit vaihtaa asetuksista.\nKuitenkin, juokseminen ei kestä loputtomiin,\njoten on parasta säästää kestävyytesi hätätilanteisiin.\nMyös hyppiminen vähentää kestävyyttäsi\n \nLisäksi, juokseminen tekee enemmän ääntä\nkuin kävely tai kyykistely, joten juokseminen\nei ole vaihtoehto hiippailussa." "chi" "你可以通过按住特殊攻击键(默认鼠标中键)来冲刺。\n然而,冲刺需要耐力,因此最好在紧急情况下使用。\n跳跃也会消耗耐力。\n \n另外,冲刺比走路和蹲伏要发出更多的声音,\n因此假如你想隐蔽,那就不要冲刺。" "pt" "Você pode correr ao segurar o botão de ataque especial\n(MOUSE3 por padrão). (Use a bind \"+attack3\" para alterar\na vinculação.) Entretanto, a corrida não é infinita,\nentão é melhor conservar a sua energia para situações\nde emergência. Pular também diminui a sua energia.\n\nAlém disso, correr faz mais barulho do que andar ou\nagachar, então correr não é uma opção furtiva." + "ko" "달릴 수 있는 능력을 가지고 있으며, 특수 공격키를\n꾹 누르면 능력을 쓸 수 있습니다.\n(보통 마우스 휠키로 바인드 되있습니다)\n하지만 무한으로 달릴 수 없으며,\n때문에 아찔한 상황을 대비해 스테미너를 아껴야합니다.\n또한 점프를 해도 스테미너가 닳습니다.\n추가로 뛸 때는 걷거나 앉아서 갈 때보다\n더 큰 소음을 냅니다." } "SF2 Help Class Info Menu Title" @@ -385,6 +468,7 @@ "fi" "Luokkien tiedot" "chi" "兵种信息" "pt" "Informação das classes" + "ko" "클래스 정보" } "SF2 Help Class Info Description" @@ -394,168 +478,142 @@ "fi" "Valitse vaihtoehto nähdäksesi tietoja jokaisesta\npelaaja luokasta, mukaan lukien parannuksista ja heikennyksistä" "chi" "选择一个选项来查看对应兵种的信息,\n其中包括了增强与削弱。" "pt" "Selecione uma opção para ver as informações de\ncada classe, incluindo vantagens e desvantagens." + "ko" "버프와 너프를 포함한 각 클래스에 대한 정보를 보려면 옵션을 선택하세요." } "SF2 Help Scout Class Info Menu Title" { - "en" "[1] The Scout" - "ru" "[1] Разведчик" - "fi" "[1] Scout" - "chi" "[1] 侦察兵" - "pt" "[1] Scout" + "en" "The Scout" + "ru" "Разведчик" + "fi" "Scout" + "chi" "侦察兵" + "pt" "Scout" + "ko" "스카웃" } "SF2 Help Scout Class Info Description" { - "en" "[+] 15%% increase in sprint duration\n[+] Fastest base sprint speed\n[-] Low health, most affected by screen wobbling" - "ru" "[+] На 15%% увеличена продолжительность бега\n[-] Мало здоровья, наиболее подвержен внушению" - "fi" "[+] 15%% lisäys juoksun kestoon\n[-] Heikko terveys, ottaa eniten vaikustusta näytön keikkumisesta" - "chi" "[+] 增加 15%% 冲刺持续时间\n[-] 低生命值,更容易受到屏幕震动效果" - "pt" "[+] Aumento de 15%% na duração da corrida\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "en" "[+] 10%% increase in sprint duration\n[-] Low health, most affected by screen wobbling" } "SF2 Help Soldier Class Info Menu Title" { - "en" "[2] The Soldier" - "ru" "[2] Солдат" - "fi" "[2] Soldier" - "chi" "[2] 士兵" - "pt" "[2] Soldier" + "en" "The Soldier" + "ru" "Солдат" + "fi" "Soldier" + "chi" "士兵" + "pt" "Soldier" + "ko" "솔저" } "SF2 Help Soldier Class Info Description" { - "en" "[+] The Disciplinary Action increases both walking\nand sprinting speed\n[+] 5%% higher static resistance and recovery rate\n[+] Market Gardener crits in midair\n[+] Jumping while walking doesn't cost stamina\n[-] The Half-Zatoichi only restores 5 HP on kill\n[-] Slowest sprint speed along with Heavy and Demoman\n[-] The Escape Plan only affects sprinting speed" - "ru" "[+] Дисциплинарное взыскание увеличивает скорость бега и ходьбы\n[-] Полудзатоити восстанавливает только 5 ед. за убийство\n[-] План эвакуации влияет только на скорость бега" - "fi" "[+] Kuritoimenpide lisää kävelyn ja juoksemisen nopeutta\nPuolisokea Samurai antaa vain 5 terveyttä taposta\n[-] Pakosuunnitelma vaikuttaa vain ja ainostaan juoksu nopeuteen" - "chi" "[+] 指挥官的军鞭同时增加走路和冲刺速度\n[-] 座头市之刀击杀后只恢复5HP\n[-] 逃生计划镐只影响冲刺速度" - "pt" "[+] A Ação Disciplinar aumenta a velocidade de andar e de correr\n[-] A Quase-Zatoichi restaura apenas 5 de vida ao matar\n[-] O Plano de Fuga afeta somente a velocidade de correr" + "en" "[+] The Disciplinary Action increases both walking\nand sprinting speed\n[+] Cannot lose sprint points while non-sprint jumping\n[-] The Half-Zatoichi only restores 5 HP on kill\n[-] The Escape Plan only affects sprinting speed" } "SF2 Help Sniper Class Info Menu Title" { - "en" "[8] The Sniper" - "ru" "[8] Спайпер" - "fi" "[8] Sniper" - "chi" "[8] 狙击手" - "pt" "[8] Sniper" + "en" "The Sniper" + "ru" "Спайпер" + "fi" "Sniper" + "chi" "狙击手" + "pt" "Sniper" + "ko" "스나이퍼" } "SF2 Help Sniper Class Info Description" { - "en" "[+] Blink rate increased by 45%%\n[-] Low health, most affected by screen wobbling" - "ru" "[+] Моргает на 45%% реже\n[-] Мало здоровья, наиболее подвержен внушению" - "fi" "[+] Räpäyttelya lisätty 40%%\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" - "chi" "[+] 眨眼频率增益 45%%\n[-] 低生命值,更容易受到屏幕震动效果" - "pt" "[+] Aumento da taxa de piscada em 45%%\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "en" "[+] Blink rate increased by 100%%\n[-] Low health, most affected by screen wobbling" } "SF2 Help Demoman Class Info Menu Title" { - "en" "[4] The Demoman" - "ru" "[4] Подрывник" - "fi" "[4] Demomies" - "chi" "[4] 爆破手" - "pt" "[4] Demoman" + "en" "The Demoman" + "ru" "Подрывник" + "fi" "Demomies" + "chi" "爆破手" + "pt" "Demoman" + "ko" "데모맨" } "SF2 Help Demoman Class Info Description" { - "en" "[+] 66%% increase in scare sprint duration\n[-] Slowest base sprint speed along with Soldier and Heavy\n[-] The Half-Zatoichi only restores 5 HP on kill" - "ru" "[+] На 66%% увеличена продолжительность бега при испуге\n[-] Полудзатоити восстанавливает только 20 ед. за убийство" - "fi" "[+] 66%% lisäys pelottamisen juoksunopeuteen\n[-] Puolisokea Samurai palauttaa vain 20 terveyttä taposta" - "chi" "[+] 惊吓状态下增加 66%% 冲刺持续时间\n[-] 座头市之刀击杀后只恢复20HP" - "pt" "[+] Aumento de 66%% na duração da corrida quando assustado\n[-] A Quase-Zatoichi restaura apenas 20 de vida ao matar" + "en" "[+] 10%% increase in sprint duration\n[-] The Half-Zatoichi only restores 5 HP on kill" } "SF2 Help Heavy Class Info Menu Title" { - "en" "[5] The Heavy" - "ru" "[5] Пулемётчик" - "fi" "[5] Heavy" - "chi" "[5] 机枪手" - "pt" "[5] Heavy" + "en" "The Heavy" + "ru" "Пулемётчик" + "fi" "Heavy" + "chi" "机枪手" + "pt" "Heavy" + "ko" "헤비" } "SF2 Help Heavy Class Info Description" { - "en" "[+] Large health, least affected by screen wobbling\n [+] Unable to be trapped by bear traps\n [-] Slowest sprint speed along with Soldier and Demoman\n[-] The Gloves of Running Urgently only affects sprinting speed" - "ru" "[+] Много здоровья, мало подвержен внушению\n[-] ГРУ влияют только на скорость бега" - "fi" "[+] Suuri terveys, vähiten vaikutusta näytön keikkumisesta\n Pikajuoksuhanskat vaikuttaa vain juoksunopeuteen" - "chi" "[+] 很多生命值,不容易受到屏幕震动效果\n[-] 紧急逃跑手套只影响冲刺速度" - "pt" "[+] Mais vida e o balanço da tela afeta menos\n[-] As Geradoras de Rapidez Urgente afetam somente a velocidade de correr" + "en" "[+] Large health, least affected by screen wobbling\n[+] Bear traps only deal minimum damage\n[-] The Gloves of Running Urgently only affects sprinting speed" } "SF2 Help Medic Class Info Menu Title" { - "en" "[7] The Medic" - "ru" "[7] Медик" - "fi" "[7] Medic" - "chi" "[7] 医生" - "pt" "[7] Medic" + "en" "The Medic" + "ru" "Медик" + "fi" "Medic" + "chi" "医生" + "pt" "Medic" + "ko" "메딕" } "SF2 Help Medic Class Info Description" { - "en" "[+] Regenerates health over time\n[-] Highest priority to bosses\n[-] Unable to pick up health packs\n[-] Proxies deal 15%% more damage" - "ru" "[+] Со временем восстанавливает здоровье\n[-] Приоритетная цель" - "fi" "[+] Terveys palautuu ajan myötä\n[-] Ensisijainen kohde bosseille" - "chi" "[+] 随时间恢复生命值\n[-] Boss们的优先目标" - "pt" "[+] Regenera vida ao longo do tempo\n[-] Torna-se o alvo prioritário de chefões" + "en" "[+] Regenerates health over time\n[-] Prioritized target to bosses\n[-] Blocked on the special round Thanatophobia" } "SF2 Help Pyro Class Info Menu Title" { - "en" "[3] The Pyro" - "ru" "[3] Поджигатель" - "fi" "[3] Pyro" - "chi" "[3] 火焰兵" - "pt" "[3] Pyro" + "en" "The Pyro" + "ru" "Поджигатель" + "fi" "Pyro" + "chi" "火焰兵" + "pt" "Pyro" + "ko" "파이로" } "SF2 Help Pyro Class Info Description" { "en" "[+] Fireproof, duh!\n[-] The Powerjack only restores 20 HP on kill, and\nmelee damage taken increased to 33%%" - "ru" "[+] Огнеупорный, хех!\n[-] Разъединитель восстанавливает только 20 ед. за убийство, и\nполучаемый при ближнем бое урон увеличен на 33%%" - "fi" "[+] Tulenkestävä, duh!\n[-] Tunkki palauttaa vain 20 terveyttä taposta\nlähitaistelu vahingon ottoa lisätty 33%%" - "chi" "[+] 防火!哇!\n[-] 强力千斤顶击杀只恢复20HP,受到的近战伤害增加33%%" - "pt" "[+] Imune a fogo (ah, vá?)\n[-] A Ligação Direta restaura apenas 20 de vida e\no dano sofrido de armas corpo a corpo é 33%% maior" } "SF2 Help Spy Class Info Menu Title" { - "en" "[9] The Spy" - "ru" "[9] Шпион" - "fi" "[9] Spy" - "chi" "[9] 间谍" - "pt" "[9] Spy" + "en" "The Spy" + "ru" "Шпион" + "fi" "Spy" + "chi" "间谍" + "pt" "Spy" + "ko" "스파이" } "SF2 Help Spy Class Info Description" { - "en" "[+] All sounds you make are 30%% quieter to bosses\n[+] 5%% more sprint duration\n[-] Low health, most affected by screen wobbling\n[-] Higher boss priority\n[-] Least amount of scare sprint speed boost\n[-] Big Earner and Kunai only work against proxies" - "ru" "[+] Все издаваемые звуки на 30%% тише\n[-] Мало здоровья, наиболее подвержен внушению" - "fi" "[+] Kaikki äänet jota teet ovat 30%% hiljaisempia bosseille\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" - "chi" "[+] 你发出的声音对Boss来说有 30%% 安静加成\n[-] 低生命值,最容易受到屏幕震动效果" - "pt" "[+] Todos os sons que você faz são 30%% mais silenciosos\npara os chefões\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "en" "[+] All sounds you make are 30%% quieter to bosses\n[-] Low health, most affected by screen wobbling\n[-] Slightly prioritized target to bosses" } "SF2 Help Engineer Class Info Menu Title" { - "en" "[6] The Engineer" - "ru" "[6] Инженер" - "fi" "[6] Engineer" - "chi" "[6] 工程师" - "pt" "[6] Engineer" + "en" "The Engineer" + "ru" "Инженер" + "fi" "Engineer" + "chi" "工程师" + "pt" "Engineer" + "ko" "엔지니어" } "SF2 Help Engineer Class Info Description" { - "en" "[+] 75%% increase in Flashlight battery life\n[+] 60%% decrease in Flashlight recharge rate\n[-] Low health, most affected by screen wobbling" - "ru" "[+] Увеличение батареи фонарика на 75%%\n[+] На 60%% снижена скорость перезарядки фонарика\n[-] Низкое здоровье, наиболее подвержен внушению" - "fi" "[+] 75%% lisäys taskulampun patterin elämään\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" - "chi" "[+] 增加 75%% 手电筒电池容量\n[+] 增益 60%% 手电筒充能速率\n[-] 低生命值,最容易受到屏幕震动效果" - "pt" "[+] Aumento de 75%% da duração bateria da lanterna\n[+] Aumento de 60%% da taxa de recarga da lanterna\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "en" "[+] 50%% increase in Flashlight battery life\n[+] 20%% decrease in Flashlight recharge rate\n[+] Flashlight is overall brighter\n[-] Low health, most affected by screen wobbling" } "SF2 Settings Menu Title" @@ -565,6 +623,7 @@ "fi" "Asetukset" "chi" "设置" "pt" "Configurações" + "ko" "설정" } "SF2 Settings PvP Menu Title" @@ -574,6 +633,7 @@ "fi" "PVP Areenan Asetukset" "chi" "PvP 区域设置" "pt" "Configurações da arena JxJ" + "ko" "PvP 구역 설정" } "SF2 Settings PvP Spawn Menu Title" @@ -583,6 +643,7 @@ "fi" "Ota käyttöön/Poista käytöstä neuvot" "chi" "开启/关闭自动重生" "pt" "Ativar/desativar renascimento automático" + "ko" "PvP 구역에서 자동 부활 활성화/비활성화" } "SF2 Settings PvP Spawn Protection Title" @@ -590,6 +651,7 @@ "en" "Enable/Disable spawn protection" "fi" "Ota käyttöön/Poista käytöstä spawn suojaus" "pt" "Ativar/desativar proteção de renascimento" + "ko" "스폰 보호 활성화/비활성화" } "SF2 Settings Hints Menu Title" @@ -599,6 +661,7 @@ "fi" "Ota käyttöön/Poista käytöstä neuvot" "chi" "开关提示" "pt" "Alternar dicas" + "ko" "힌트 표시 토글" } "SF2 Settings Mute Mode Menu Title" @@ -608,6 +671,7 @@ "fi" "Aseta mykistystila" "chi" "设置禁言模式" "pt" "Definir modo de emudecimento" + "ko" "음소거 모드 설정" } "SF2 Settings Film Grain Menu Title" @@ -617,6 +681,7 @@ "fi" "Ota käyttöön/Poista käytöstä näytön rakeisuus tehoste" "chi" "开关膜颗粒效应" "pt" "Alternar granulação de filme" + "ko" "필름 그래인 토글" } "SF2 Settings Proxy Menu Title" @@ -626,6 +691,7 @@ "fi" "Salli valitsemisesi avustajaksi" "chi" "允许被选为小弟" "pt" "Permitir ser selecionado como proxy" + "ko" "프록시로 선택 여부 설정" } "SF2 Settings Proxy Ask Menu Title" @@ -633,13 +699,7 @@ "en" "Enable/Disable the proxy ask message" "fi" "Ota käyttöön/Poista käytöstä avustaja kysymys viesti" "pt" "Ativar/desativar mensagem de \"proxy ou não\"" - } - - "SF2 Settings Hud Version Title" - { - "en" "Switch HUD version between legacy and new" - "fi" "Vaihda HUD versio vanhan ja uuden välillä" - "pt" "Alternar versão da interface entre antiga e nova" + "ko" "프록시 참가 요청 메시지 활성화/비활성화" } "SF2 Settings Ghost Overlay Menu Title" @@ -649,27 +709,46 @@ "fi" "Vaihda Haamu Tilan overlay" "chi" "开关幽灵模式覆盖" "pt" "Alternar sobreposição do modo fantasma" + "ko" "유령 모드 오버레이 토글" } - + "SF2 Settings Ghost Mode Teleport Menu Title" { "en" "Set ghost mode teleport state" "fi" "Aseta haamu tila teleportointi tila" "pt" "Definir estado do teletransporte do modo fantasma" + "ko" "유령 모드 텔레포트 상태 설정" } - + "SF2 Settings Ghost Mode Toggle State Menu Title" { "en" "Set ghost mode toggle state" "fi" "Aseta haamu tilan toggle tila" "pt" "Definir estado da alternação do modo fantasma" + "ko" "유령 모드 돌입 토글 설정" + } + + "SF2 Settings View Bobbing Title" + { + "en" "Toggle camera view bobbing" } - + "SF2 Settings Hud Version Title" { "en" "Set HUD version" "fi" "Aseta HUD versio" "pt" "Definir versão da interface" + "ko" "HUD 버전 설정" + } + + "SF2 Settings Hud New" + { + "en" "New" + } + + "SF2 Settings Hud Legacy" + { + "en" "Legacy" } "SF2 Settings Music Volume Title" @@ -679,6 +758,12 @@ "fi" "Vaihda musiikin äänenvoimakkuutta" "chi" "设置音乐音量" "pt" "Definir volume da música" + "ko" "음악 볼륨 설정" + } + + "SF2 Settings PvE Menu Title" + { + "en" "PvE Arena Settings" } "SF2 Music Volum Changed" @@ -689,6 +774,12 @@ "fi" "{lightblue}Musiikin äänenvoimakkuus vaihdettu {1}%%." "chi" "{lightblue}音乐音量已设为 {1}%% 。" "pt" "{lightblue}Volume da música definido para {1}%%." + "ko" "{lightblue}음악 볼륨이 {1}%% 으로 설정 되었습니다." + } + + "SF2 Settings Projected Flashlight Title" + { + "en" "Projected flashlight" } "SF2 Settings Flashlight Temperature Title" @@ -698,16 +789,13 @@ "fi" "Aseta taskulampun lämpötila" "chi" "设置手电筒温度" "pt" "Definir temperatura da lanterna" + "ko" "손전등 색감 설정" } "SF2 Flashlight Temperature Changed" { "#format" "{1:d}" - "en" "{lightblue}Flashlight temperature set to {1}%%." - "ru" "{lightblue}Температура фонарика установлена на {1}%%." - "fi" "{lightblue}Taskulampun lämpötila vaihdettu {1}%%." - "chi" "{lightblue}手电筒温度设为 {1}%% 。" - "pt" "{lightblue}Temperatura da lanterna definida para {1}%%." + "en" "{lightblue}Flashlight temperature set to {1}k kelvin." } "SF2 Ad Message 1" @@ -716,6 +804,7 @@ "ru" "{dodgerblue}Официальный Discord сервер Slender Fortress Modified: {lightblue}https://discord.gg/7Zz7RYTCC4{default}" "chi" "{dodgerblue}Slender Fortress Modified官方Discord服务器:{lightblue}https://discord.gg/7Zz7RYTCC4{default}" "pt" "{dodgerblue}Servidor oficial do Slender Fortress Modified no Discord: {lightblue}https://discord.gg/7Zz7RYTCC4{default}" + "ko" "{dodgerblue}슬렌더 포트리스 모디파이드 공식 디스코드 서버: {lightblue}https://discord.gg/7Zz7RYTCC4{default}" } "SF2 Ad Message 2" @@ -725,6 +814,7 @@ "fi" "{dodgerblue}Uusi tähän pelitilaan? Kirjoita {lightblue}!slender{dodgerblue} chattiin avataksesi päävalikon." "chi" "{dodgerblue}新玩这个模式?在聊天键入{lightblue}!slender{dodgerblue}来打开主菜单。" "pt" "{dodgerblue}Não sabe como este modo funciona? Digite {lightblue}!slender{dodgerblue} na janela de conversa para abrir o menu principal." + "ko" "{dodgerblue}이 게임모드가 처음 이신가요? {lightblue}!slender{dodgerblue}를 채팅에 입력하여 메인 메뉴를 확인하세요." } "SF2 Welcome Message" @@ -734,6 +824,7 @@ "fi" "{dodgerblue}Tervetuloa {lightblue}Slender Fortress:iin{dodgerblue}! Jos olet uusi tähän pelitilaan, pääset päävalikkoon kirjoittamalla {lightblue}!slender{dodgerblue} chattiin." "chi" "{dodgerblue}欢迎来到{lightblue}暗鬼要塞{dodgerblue}!假如你新玩这个模式,在聊天键入{lightblue}!slender{dodgerblue}来进入主菜单。" "pt" "{dodgerblue}Bem-vindo(a) ao {lightblue}Slender Fortress{dodgerblue}! Caso não saiba como este modo funciona, acesse o menu principal com o comando \"{lightblue}!slender{dodgerblue}\"." + "ko" "{lightblue}슬렌더 포트리스{dodgerblue}에 오신 것을 환영합니다! 이 게임모드가 처음 이시면, {lightblue}!slender{dodgerblue}를 채팅에 입력하여 메인 메뉴를 확인하세요." } "SF2 No Queue Points To Spectator" @@ -743,6 +834,7 @@ "fi" "{dodgerblue}Sinulle ei annettu pisteitä, koska olit Katsoja tiimillä." "chi" "{dodgerblue}因为你在观察者队伍,你没有获得任何队列点数。" "pt" "{dodgerblue}Você não recebeu pontos de espera porque você estava no modo espectador." + "ko" "{dodgerblue}관전 팀에 머물러 있어서 대기열 포인트를 받지 못했습니다." } "SF2 PvP Spawn Accept" @@ -752,6 +844,7 @@ "fi" "{lightblue}Otettiin käyttöön automaattinen PvP areenalla syntyminen" "chi" "{lightblue}已开启PvP区域自动重生。" "pt" "{lightblue}Renascimento automático na arena JxJ ativado." + "ko" "{lightblue}PvP 구역 자동 부활이 활성화되었습니다." } "SF2 PvP Spawn Decline" @@ -761,6 +854,7 @@ "fi" "{lightblue}Poistettiin käytöstä automaattinen PvP areenalla syntyminen." "chi" "{lightblue}已关闭PvP区域自动重生。" "pt" "{lightblue}Renascimento automático na arena JxJ desativado." + "ko" "{lightblue}PvP 구역 자동 부활이 비활성화 되었습니다." } "SF2 PvP Protection On" @@ -768,6 +862,7 @@ "en" "{lightblue}Enabled PvP Spawn Protection." "fi" "{lightblue}PvP Spawn Suojaus otettiin käyttöön." "pt" "{lightblue}Proteção na arena JxJ ativada." + "ko" "{lightblue}PvP 구역 부활 보호가 활성화되었습니다." } "SF2 PvP Protection Off" @@ -775,6 +870,7 @@ "en" "{lightblue}Disabled PvP Spawn Protection." "fi" "{lightblue}PvP Spawn Suojaus poistettu käytöstä." "pt" "{lightblue}Proteção na arena JxJ desativada." + "ko" "{lightblue}PvP 구역 부활 보호가 비활성화되었습니다." } "SF2 Exiting PvP Arena" @@ -785,13 +881,15 @@ "fi" "Olet lähtemässä PvP Areenalta!\nMenetät aseesi {1} sekunnin päästä jos et palaa!" "chi" "你正离开PvP区域!\n假如你不回去,你会在 {1} 秒后失去你的武器!" "pt" "Você está saindo da arena JxJ! Você perderá as suas armas em {1} segundo(s) caso não retorne!" + "ko" "PvP 구역을 벗어났습니다!\n무기를 잃기 싫다면 {1} 초 안에 돌아오세요!" } - + "SF2 AFK On" { "en" "{royalblue}Disabled going to RED naturally." "fi" "{royalblue}Poistettiin pääsy RED:iin normaalisti." "pt" "{royalblue}Ida natural à RED desativada." + "ko" "{royalblue}AFK로 인해 RED 팀 자동 참가가 비활성화되었습니다." } "SF2 AFK Off" @@ -799,6 +897,7 @@ "en" "{royalblue}Enabled going to RED naturally." "fi" "{royalblue}Otettiin käyttöön pääsy RED:iin normaalisti." "pt" "{royalblue}Ida natural à RED ativada." + "ko" "{royalblue}RED팀 자동 참가가 다시 활성화되었습니다." } "SF2 AFK Status" @@ -806,6 +905,7 @@ "en" "You are currently AFK, press a button to wake up." "fi" "Olet nyt AFK, paina jotain nappia herätäksesi." "pt" "Você está ausente. Pressione um botão para acordar." + "ko" "현재 잠수 상태입니다. 아무 키나 눌러 깨어나세요." } "SF2 Enabled Hints" @@ -815,6 +915,7 @@ "fi" "{lightblue}Vihjeet otettiin käyttöön." "chi" "{lightblue}已开启提示。" "pt" "{lightblue}Dicas ativadas." + "ko" "{lightblue}힌트가 활성화되었습니다." } "SF2 Disabled Hints" @@ -824,6 +925,7 @@ "fi" "{lightblue}Vihjeet poistettiin käytöstä." "chi" "{lightblue}已关闭提示。" "pt" "{lightblue}Dicas desativadas." + "ko" "{lightblue}힌트가 비활성화되었습니다." } "SF2 Enabled Film Grain" @@ -833,6 +935,7 @@ "fi" "{lightblue}Otettiin käyttöön näytön rakeisuus tehoste." "chi" "{lightblue}已开启膜颗粒效应" "pt" "{lightblue}Granulação de filme ativada." + "ko" "{lightblue}필름 그래인이 활성화되었습니다." } "SF2 Disabled Film Grain" @@ -842,6 +945,7 @@ "fi" "{lightblue}Poistettiin näytön rakeisuus tehoste." "chi" "{lightblue}已关闭膜颗粒效应" "pt" "{lightblue}Granulação de filme desativada." + "ko" "{lightblue}필름 그래인이 비활성화되었습니다." } "SF2 Enabled Proxy" @@ -851,6 +955,7 @@ "fi" "{lightblue}Olet nyt valittavissa avustajaksi." "chi" "{lightblue}你现在有随机可能被选为小弟。" "pt" "{lightblue}Agora, você pode ser selecionado como um proxy." + "ko" "{lightblue}이제 프록시로 선택될 수 있습니다." } "SF2 Disabled Proxy" @@ -860,6 +965,7 @@ "fi" "{lightblue}Sinua ei enään valita avustajaksi." "chi" "{lightblue}你不再会被选为小弟。" "pt" "{lightblue}Você não poderá mais ser selecionado como um proxy." + "ko" "{lightblue}더 이상 프록시로 선택되지 않습니다." } "SF2 New Hud Use" @@ -867,6 +973,7 @@ "en" "{lightblue}Enabled the new HUD." "fi" "{lightblue}Otettiin käyttöön uusi HUD." "pt" "{lightblue}Interface nova ativada." + "ko" "{lightblue}신 버전 HUD를 활성화했습니다." } "SF2 Legacy Hud Use" @@ -874,6 +981,7 @@ "en" "{lightblue}Enabled the legacy HUD." "fi" "{lightblue}Otettiin käyttöön vanha HUD." "pt" "{lightblue}Interface antiga ativada." + "ko" "{lightblue}구 버전 HUD를 활성화했습니다." } "SF2 Enabled Ask Message Proxy" @@ -881,6 +989,7 @@ "en" "{lightblue}You will now be prompted and asked when you can become a proxy." "fi" "{lightblue}Olet nyt valittavissa avustajaksi." "pt" "{lightblue}Agora, o jogo perguntará se você deseja ser um proxy quando possível." + "ko" "{lightblue}이제 프록시 참가 요청 메시지가 표시됩니다." } "SF2 Disabled Ask Message Proxy" @@ -888,26 +997,19 @@ "en" "{lightblue}You will no longer be prompted and asked when you can become a proxy." "fi" "{lightblue}Sinua ei enään valita avustajaksi." "pt" "{lightblue}Agora, o jogo não perguntará se você deseja ser um proxy quando possível." + "ko" "{lightblue}프록시가 될 수 있어도 더 이상 참가 요청 여부를 묻지 않습니다." } "SF2 Give Queue Points" { "#format" "{1:d}" - "en" "{dodgerblue}You have been given {1} queue points for playing." - "ru" "{dodgerblue}Вами были получены {1} очков очереди." - "fi" "{dodgerblue}Sinulle on annettu {1} jonopistettä pelaamisesta." - "chi" "{dodgerblue}你因为游玩获得了 {1} 队列点数。" - "pt" "{dodgerblue}Você recebeu {1} ponto(s) de espera por jogar." + "en" "{dodgerblue}You have been given {lightblue}{1} {dodgerblue}queue points for playing. Type {lightblue}!slnext {dodgerblue}to see when you will get to play on {red}RED{dodgerblue}." } "SF2 Give Group Queue Points" { "#format" "{1:d}" - "en" "{dodgerblue}Your group has been given {1} queue points for playing." - "ru" "{dodgerblue}Вашей группой были получены {1} очков очереди." - "fi" "{dodgerblue}Sinun ryhmällesi on annettu {1} jonopistettä pelaamisesta." - "chi" "{dodgerblue}你的组因为游玩获得了 {1} 队列点数。" - "pt" "{dodgerblue}O seu grupo recebeu {1} ponto(s) de esepra por jogar." + "en" "{dodgerblue}Your group has been given {1} queue points for playing. Type {lightblue}!slnext {dodgerblue}to see when you will get to play on {red}RED{dodgerblue}." } "SF2 Force Play" @@ -917,6 +1019,7 @@ "fi" "{lightblue}Sinut on pakotettu peliin, koska RED:in pelaaja katkaisi yhteyden tai meni katsomoon. Pidä hauskaa!" "chi" "{lightblue}因为一名红队玩家断开连接或进入观察者,你被强制加入游戏。玩的开心!" "pt" "{lightblue}Você foi movido para a equipe RED devido à saída de outro jogador. Divirta-se!" + "ko" "{lightblue}RED 팀에 있는 플레이어가 서버와 연결이 끊겼거나 관전 팀으로 합류해 당신이 대신해서 들어갔습니다. 재밌게 하세요!" } "SF2 Special Round Announce Chat" @@ -927,15 +1030,17 @@ "fi" "Valittu erikois erä: {lightblue}{1}" "chi" "选择了特殊回合:{lightblue}{1}" "pt" "Selecionar rodada especial: {lightblue}{1}" + "ko" "선택된 스페셜 라운드: {lightblue}{1}" } "SF2 Hint Sprint" { - "en" "Sprint - HOLD Special Attack" + "en" "Sprint - HOLD Special Attack (Middle Mouse Button) or %%sprint%%" "ru" "Бег - Удерж. кнопку \"Специальная атака\"" "fi" "Juokse - Pidä Pohjassa Erityis Hyökkäystäsi" "chi" "冲刺 - 按住特殊攻击键" "pt" "Correr — SEGURAR \"ataque especial\"" + "ko" "달리기 - 특수 공격 키 누르기" } "SF2 Hint Flashlight" @@ -945,6 +1050,7 @@ "fi" "Taskulamppu - Toissijainen Hyökkäys" "chi" "闪光灯 - 次要攻击键" "pt" "Lanterna — ataque secundário" + "ko" "손전등 - 보조 공격 키" } "SF2 Hint Blink" @@ -954,6 +1060,7 @@ "fi" "Räpäytys - Lataa" "chi" "眨眼 - 装填弹药键" "pt" "Piscar — recarregar" + "ko" "눈 깜빡임 - 재장전 키" } "SF2 Hint Main Menu" @@ -963,8 +1070,9 @@ "fi" "Päävalikko - Kirjoita !sf2 tai !slender chattiin" "chi" "主菜单 - 在聊天键入!sf2。" "pt" "Menu principal — digite !sf2 ou !slender na janela de conversa" + "ko" "메인 메뉴 - 채팅창에 !sf2 또는 !slender 입력" } - + "SF2 Hint Trap" { "en" "Get Out of Trap - Press the jump key a few times." @@ -972,6 +1080,22 @@ "fi" "Poistu Ansasta - Paina hyppy nappia pari kertaa." "chi" "跳出陷阱 - 按几次跳跃键。" "pt" "Sair da armadilha — pressione \"Pular\" algumas vezes." + "ko" "함정 벗어나기 - 점프 키를 연타하세요." + } + + "SF2 Hint Noises" + { + "en" "WARNING - Voice commands, weapon sounds, flashlight sounds, and footstep sounds can alert bosses." + } + + "SF2 Hint Crouch" + { + "en" "Crouch - %%duck%%\nSneak around quietly" + } + + "SF2 Admin Menu Title" + { + "en" "SF2 Commands" } "SF2 Admin Menu Boss Main" @@ -981,6 +1105,7 @@ "fi" "Bossien Hallinta" "chi" "Boss管理" "pt" "Administração de chefão" + "ko" "현재 보스 관리" } "SF2 Admin Menu Add Boss" @@ -990,6 +1115,7 @@ "fi" "Lisää bossi" "chi" "增加一个Boss" "pt" "Adicionar um chefão" + "ko" "보스 추가" } "SF2 Admin Menu Add Fake Boss" @@ -999,6 +1125,7 @@ "fi" "Lisää teko bossi" "chi" "增加一个假Boss" "pt" "Adicionar um chefão falso" + "ko" "가짜 보스 추가" } "SF2 Admin Menu Remove Boss" @@ -1008,6 +1135,7 @@ "fi" "Poista bossi" "chi" "移除一个Boss" "pt" "Remover um chefão" + "ko" "보스 제거" } "SF2 Admin Menu Spawn Boss" @@ -1015,8 +1143,9 @@ "en" "Spawn an active boss" "ru" "Создать активного босса" "fi" "Spawnaa aktiivinen bossi" - "chi" "生成一个活跃Boss" + "chi" "生成一个活跃Boss" "pt" "Criar um chefão ativo" + "ko" "활동 중인 보스 소환" } "SF2 Admin Menu Boss Attack Waiters" @@ -1026,6 +1155,7 @@ "fi" "Kytke bossi hyökkäämään odottajiin (BLU)" "chi" "开关Boss攻击等待者(蓝队)" "pt" "Alternar \"Chefão ataca aguardantes\" (BLU)" + "ko" "보스의 대기자(BLU 팀)를 향한 공격 여부 토글" } "SF2 Admin Menu Boss Attack Waiters Confirm" @@ -1036,6 +1166,7 @@ "fi" "Pitäisikö {1} antaa hyökätä odottajiin (BLU)?" "chi" "应该让 {1} 攻击等待者(蓝队)吗?" "pt" "{1} deve atacar os aguardantes (BLU)?" + "ko" "{1} 이(가) 대기자(BLU팀)을 공격하게 하겠습니까?" } "SF2 Admin Menu Boss Teleport" @@ -1045,6 +1176,7 @@ "fi" "Kytke bossin teleporttaaminen RED:iin" "chi" "开关Boss传送到红队" "pt" "Alternar \"Chefão teletransporta-se à RED\"" + "ko" "보스의 RED 팀을 향한 텔레포트 여부 토글" } "SF2 Admin Menu Boss Teleport Confirm" @@ -1055,6 +1187,7 @@ "fi" "Pitäisikö {1} pystyä teleporttaamaan RED:in lähelle?" "chi" "应该让 {1} 能够传送到红队附近吗?" "pt" "{1] deve poder teletransportar-se próximo à RED?" + "ko" "{1} 이(가) RED 팀을 향해 텔레포트하게끔 만들겠습니까?" } "SF2 Admin Menu Override Boss" @@ -1064,6 +1197,7 @@ "fi" "Aseta seuraavan bossin ohitus" "chi" "选择下一个Boss覆盖" "pt" "Defina a substituição do próx. chefão" + "ko" "다음 보스 재정의" } "SF2 Admin Menu Current Boss Override" @@ -1074,6 +1208,24 @@ "fi" "Nykyisen Bossin Ohitus: {1}" "chi" "当前Boss覆盖:{1}" "pt" "Substituição de chefão atual: {1}" + "ko" "현재 보스 재정의 : {1}" + } + + "SF2 Disabled Boss Pack" + { + "en" "{red}Boss Pack vote is disabled on this server." + } + + "SF2 Boss Pack Next" + { + "#format" "{1:s}" + "en" "{dodgerblue}Next boss pack: {lightblue}{1}{default}." + } + + "SF2 Current Boss Pack" + { + "#format" "{1:s}" + "en" "{dodgerblue}Current boss pack: {lightblue}{1}{default}." } "SF2 Admin Menu Player Set Play State" @@ -1083,6 +1235,7 @@ "fi" "Pakota pelaaja sisään/ulos pelistä" "chi" "强制玩家进入/离开游戏" "pt" "Forçar jogador a entrar/sair da rodada" + "ko" "플레이어 게임 강제 입장/퇴장" } "SF2 Admin Menu Player Set Play State Confirm" @@ -1093,6 +1246,7 @@ "fi" "Pakotetaanko {1} sisään tai ulos pelistä?" "chi" "强制 {1} 进入或离开游戏?" "pt" "Forçar {1} a entrar/sair da rodada?" + "ko" "{1} 을(를) 게임에 강제 입장/퇴장 시키겠습니까?" } "SF2 Admin Menu Player Force Proxy" @@ -1102,6 +1256,7 @@ "fi" "Pakota pelaaja olemaan avustaja" "chi" "强制玩家成为小弟" "pt" "Forçar jogador a ser um proxy" + "ko" "플레이어를 프록시로 강제 설정" } "SF2 Admin Menu Player Force Proxy Boss" @@ -1111,6 +1266,7 @@ "fi" "Minkä bossin avustaja pelaaja olisi?" "chi" "让玩家成为哪个Boss的小弟?" "pt" "Para qual chefão o jogador deverá ser um proxy?" + "ko" "어떤 보스를 위해 플레이어가 프록시가 되어야 합니까?" } "SF2 Boss Does Not Exist" @@ -1120,6 +1276,7 @@ "fi" "Tuota bossia ei ole enään olemassa!" "chi" "Boss已不存在!" "pt" "Este chefão não existe mais!" + "ko" "해당 보스는 더 이상 존재하지 않습니다!" } "SF2 Player Does Not Exist" @@ -1129,6 +1286,7 @@ "fi" "Tuota pelaajaa ei ole olemassa!" "chi" "玩家不存在!" "pt" "Este jogador não existe!" + "ko" "해당 플레이어는 더 이상 존재하지 않습니다!" } "SF2 No Active Bosses" @@ -1138,6 +1296,7 @@ "fi" "Aktiivisia bosseja ei ole saatavilla!" "chi" "没有可用的活跃Boss!" "pt" "Nenhum chefão ativo disponível!" + "ko" "활동 중인 보스 중 사용 가능한 보스가 없습니다!" } "SF2 Spawned Boss" @@ -1147,6 +1306,7 @@ "fi" "Bossi syntyi sijaintiisi!" "chi" "在你的位置生成了Boss!" "pt" "Chefão criado na sua posição!" + "ko" "바라보고 있는 위치에 보스를 소환했습니다!" } "SF2 Removed Boss" @@ -1156,6 +1316,7 @@ "fi" "Poistettiin bossi!" "chi" "移除了Boss!" "pt" "Chefão removido!" + "ko" "보스가 제거되었습니다!" } "SF2 Boss Not Allowed To Have Proxies" @@ -1165,6 +1326,7 @@ "fi" "Tuolle bossille ei ole sallittu avustajia!" "chi" "这个Boss不允许获得小弟!" "pt" "Este chefão não pode ter proxies!" + "ko" "해당 보스는 프록시를 가질 수 없는 보스입니다!" } "SF2 Boss Should Teleport" @@ -1174,6 +1336,7 @@ "fi" "Bossi pystyy nyt teleporttaamaan!" "chi" "Boss现在可以传送了!" "pt" "Agora, o chefão pode teletransportar-se!" + "ko" "이제 보스가 텔레포트할 수 있습니다!" } "SF2 Boss Should Not Teleport" @@ -1183,6 +1346,7 @@ "fi" "Bossi ei pysty enään teleporttaamaan!" "chi" "Boss现在不可以传送了!" "pt" "O chefão não pode mais teletransportar-se!" + "ko" "보스는 더 이상 텔레포트할 수 없습니다!" } "SF2 Boss Attack Waiters" @@ -1192,6 +1356,7 @@ "fi" "Bossi hyökkää nyt odottajiin!" "chi" "Boss现在会攻击等待者!" "pt" "Agora, o chefão atacará os aguardantes!" + "ko" "이제 보스는 대기자를 공격 할 것입니다!" } "SF2 Boss Do Not Attack Waiters" @@ -1201,6 +1366,7 @@ "fi" "Bossi ei enään hyökkää odottajiin!" "chi" "Boss不会攻击等待者!" "pt" "O chefão não atacará mais os aguardantes." + "ko" "더 이상 보스는 대기자를 공격하지 않습니다!" } "SF2 Player Already A Proxy" @@ -1210,6 +1376,7 @@ "fi" "Tuo pelaaja on jo avustaja!" "chi" "这个玩家已经是小弟了!" "pt" "Este jogador já é um proxy!" + "ko" "해당 플레이어는 이미 프록시입니다!" } "SF2 Unable To Perform Action On Player In Round" @@ -1220,6 +1387,7 @@ "fi" "Ei pysty suorittamaan toimintoa {red}{1}{default} koska hän on erässä pelaamassa." "chi" "无法对 {red}{1}{default} 执行操作,因为他/她已在当前回合游玩中。" "pt" "Não foi possível realizar a ação em {red}{1}{default} porque tal jogador está jogando na rodada atual." + "ko" "해당 플레이어는 현재 라운드에서 플레이 중이므로 {red}{1}{default}에 대해 작업을 수행할 수 없습니다." } "SF2 Player Forced In Game" @@ -1230,6 +1398,7 @@ "fi" "Pakotettiin {blue}{1}{default} peliin." "chi" "强制 {blue}{1}{default} 进入游戏。" "pt" "{blue}{1}{default} foi forçado a entrar na rodada atual." + "ko" "{blue}{1}{default}을(를) 게임에 강제 입장시켰습니다." } "SF2 Player Forced Out Of Game" @@ -1240,13 +1409,15 @@ "fi" "Pakotettiin {red}{1}{default} ulos pelistä." "chi" "强制 {red}{1}{default} 离开游戏。" "pt" "{red}{1}{default} foi forçado a sair da rodada atual." + "ko" "{red}{1}{default}을(를) 게임에 강제 퇴장시켰습니다." } - + "SF2 Set Queue Points" { "#format" "{1:s}" "en" "Changed {blue}{1}{default} queue points." "pt" "Os pontos de espera de {blue}{1}{default} foram alterados." + "ko" "{blue}{1}{default}의 대기열 포인트를 변경했습니다." } "SF2 Cannot Use Command" @@ -1256,6 +1427,7 @@ "fi" "Tätä komentoa ei juuri nyt pysty käyttämään." "chi" "这个命令现在不可以使用。" "pt" "Este comando está indisponível no momento." + "ko" "현재 해당 명령어를 사용할 수 없습니다." } "SF2 Player No Place For Proxy" @@ -1266,6 +1438,7 @@ "fi" "Ei pystytty löytämään hyvää paikkaa avustajalle {blue}{1}!" "chi" "无法为 {blue}{1} 找到好的小弟位置。" "pt" "Não foi possível obter uma boa localização de proxy para {blue}{1}{default}!" + "ko" "{blue}{1}에 적합한 프록시 위치를 찾을 수 없습니다." } "SF2 In" @@ -1275,6 +1448,7 @@ "fi" "Sisällä" "chi" "进入" "pt" "Dentro" + "ko" "입장" } "SF2 Out" @@ -1284,6 +1458,7 @@ "fi" "Ulkona" "chi" "离开" "pt" "Fora" + "ko" "퇴장" } "SF2 Group Main Menu Title" @@ -1293,6 +1468,7 @@ "fi" "Ryhmä Valikko" "chi" "组菜单" "pt" "Menu do grupo" + "ko" "그룹 메뉴" } "SF2 Group Main Menu Description" @@ -1302,6 +1478,7 @@ "fi" "Hallinoi, katso tietoja, luo, tai lähde ryhmästä." "chi" "管理,查看信息,创造或退出一个组。" "pt" "Administrar, ver informações, criar ou sair de um grupo." + "ko" "그룹을 관리, 정보 보기, 생성 또는 탈퇴합니다." } "SF2 Admin Group Menu Title" @@ -1311,6 +1488,7 @@ "fi" "Hallinnoi/Katso tietoja nykyisestä ryhmästä" "chi" "管理/查看信息于当前组" "pt" "Administrar/ver informações do grupo atual" + "ko" "현재 그룹의 관리/정보 보기" } "SF2 Admin Group Menu Description" @@ -1321,6 +1499,7 @@ "fi" "Ryhmän Nimi: {1}\nRyhmän Johtaja: {2}\nJäsenet: {3}/{4}\nJonopisteet: {5}" "chi" "组名称:{1}\n组领袖:{2}\n成员:{3}/{4}\n队列点数:{5}" "pt" "Nome do grupo: {1}\nLíder do grupo: {2}\nMembros: {3}/{4}\nPontos de espera: {5}" + "ko" "그룹 이름: {1}\n그룹 리더: {2}\n멤버: {3}/{4}\n대기열 포인트: {5}" } "SF2 View Current Group Info Menu Title" @@ -1330,6 +1509,7 @@ "fi" "Näytä nykyisen ryhmän tiedot" "chi" "查看当前组信息" "pt" "Ver informações do grupo atual" + "ko" "현재 그룹의 정보 보기" } "SF2 View Group Members Menu Title" @@ -1339,6 +1519,7 @@ "fi" "Näytä ryhmän jäsenet" "chi" "查看组成员" "pt" "Ver membros do grupo" + "ko" "그룹 구성원 보기" } "SF2 View Group Members Menu Description" @@ -1348,6 +1529,7 @@ "fi" "Lista kaikista ryhmäsi jäsenistä:" "chi" "你的组的全部成员列表:" "pt" "Lista de todos os membros no seu grupo:" + "ko" "그룹의 모든 구성원 목록:" } "SF2 Set Group Name Menu Title" @@ -1357,6 +1539,7 @@ "fi" "Anna ryhmälle nimi" "chi" "设置组名称" "pt" "Definir nome do grupo" + "ko" "그룹 이름 설정" } "SF2 Set Group Name Menu Description" @@ -1366,6 +1549,7 @@ "fi" "Voit antaa tekemällesi ryhmälle mukautetun nimen\nselvästi tunnistettavissa muista. Voit antaa\nnykyisen ryhmäsi nimen kirjoittamalla seuraavasti\nchattiin:\n \n /slgroupname \n \nMaksimi nimen pituus on 64 merkkiä." "chi" "你可以给你的组设置独特的名称。\n你可以把你要设置的组名在聊天区这样键入:\n \n /slgroupname <名字>\n \n名称不能超过64个字符。" "pt" "Você pode definir um nome personalizado para o\nseu grupo para torná-lo único. Você pode alterar o\nnome do seu grupo atual\nao digitar o seguinte na ja-\nnela de conversa:\n\n/slgroupname \n\nAté 64 caracteres podem ser usados no nome." + "ko" "다른 그룹과의 구별을 위해서 그룹의 이름을 설정할 수 있습니다.\n채팅창에 입력:\n \n /slgroupname <이름>.\n\n이름의 최대 길이는 영문 기준 64자 입니다." } "SF2 Invalid Group Name" @@ -1375,6 +1559,7 @@ "fi" "Epäsopiva ryhmän nimi!" "chi" "无效的组名!" "pt" "Nome do grupo inválido!" + "ko" "잘못된 그룹 이름입니다!" } "SF2 Group Name Set" @@ -1385,6 +1570,7 @@ "fi" "Ryhmäsi nimi on vaihdettu tästä: {1} tähän: {2}." "chi" "你的组名已从 {1} 改为 {2}。" "pt" "O nome do seu grupo foi alterado de {1} para {2}." + "ko" "그룹 이름이 {1}에서 {2}(으)로 변경되었습니다." } "SF2 Set Group Leader Menu Title" @@ -1394,6 +1580,7 @@ "fi" "Aseta ryhmän johtaja" "chi" "设置组领袖" "pt" "Definir líder do grupo" + "ko" "그룹 리더 설정" } "SF2 Set Group Leader Menu Description" @@ -1403,6 +1590,7 @@ "fi" "Kenen pitäisi olla uusi ryhmän johtaja?" "chi" "谁应是新的组领袖?" "pt" "Quem deve ser o novo líder do grupo?" + "ko" "누구를 새로운 리더로 하시겠습니까?" } "SF2 Group Has Too Many Members" @@ -1412,6 +1600,7 @@ "fi" "Ryhmässäsi on liian monta jäsentä! Jotkin ryhmäsi jäsenistäsi potkitaan tilan hankkimiseksi." "chi" "你的组成员太多了!将踢出一些成员来制造空间。" "pt" "O seu grupo tem muitos membros! Alguns serão expulsos para liberar novas vagas." + "ko" "당신의 그룹에는 멤버가 너무 많습니다! 멤버들 중 일부는 자리를 마련하기 위해 그룹에서 쫓아내야 합니다." } "SF2 Invite To Group Menu Title" @@ -1421,6 +1610,7 @@ "fi" "Kutsu pelaajia ryhmääsi" "chi" "邀请玩家进入组" "pt" "Convidar jogadores para o grupo" + "ko" "그룹에 플레이어 초대" } "SF2 Invite To Group Menu Description" @@ -1430,6 +1620,7 @@ "fi" "Kutsu pelaajia liittymään ryhmääsi!\nHUOMAUTUS: Et voi kutsua pelaajia jotka ovat\njo ryhmässä. Tai ovat pelaamassa RED:llä" "chi" "邀请玩家进入你的组!\n \n注意:你不能邀请已有组的玩家,\n或正在以红队身份游玩的玩家。" "pt" "Convide jogadores para o seu grupo!\n\nAVISO: você não pode convidar jogadores que\njá estão em um grupo ou que estão na equipe RED." + "ko" "플레이어를 그룹에 초대하십시오!\n \n참고: 이미 그룹에 있거나 RED 팀으로 플레이 중인 플레이어는 초대할 수 없습니다." } "SF2 Group Invitation Sent" @@ -1440,6 +1631,7 @@ "fi" "Kutsu ryhmääsi lähetettiin pelaajalle: {1}." "chi" "已发送组邀请给 {1} 。" "pt" "Convite de grupo enviado para {1}." + "ko" "{1}에게 그룹 초대를 보냈습니다." } "SF2 Group Invite Menu Description" @@ -1450,6 +1642,7 @@ "fi" "Sait ryhmäkutsun pelaajalta {1} liity\n{2} ryhmään.\n \nVAROITUS: Liittymällä ryhmään, menetät kaikki jonopisteesi!\nSinut laitetaan jonoon vasta, kun ryhmäsi valitaan.\n \nHyväksytkö kutsun?" "chi" "你被 {1} 邀请加入 {2} 组。\n \n警告:加入组后,你的队列点数会被重置为0!\n你只能在你的组被选上时加入队列。\n \n要接受邀请吗?" "pt" "{1} convidou você para juntar-se ao grupo\n{2}.\n\nAVISO: ao entrar em um grupo, todos os seus pontos de espera\nserão zerados! Você apenas poderá jogar quando o seu\ngrupo for selecionado.\n\nDeseja aceitar o convite?" + "ko" "{1}로부터\n{2} 그룹에 가입하도록 초대받았습니다.\n \n경고: 그룹에 가입하면 대기열 포인트가 0으로 초기화됩니다.\n그룹을 선택할 때만 대기열에 들어갑니다.\n \n초대에 응하시겠습니까?" } "SF2 No Group Invite Spam" @@ -1460,6 +1653,7 @@ "fi" "Sinun pitää odottaa {1} sekunttia ennen kuin voit lähettää uutta kutsua pelaajalle {2}." "chi" "你必须等待 {1} 秒才能再次发送邀请给 {2} 。" "pt" "Aguarde {1} antes de enviar um novo convite para {2}." + "ko" "{2}에 다른 초대를 보내기 전에 {1}초를 기다려야 합니다." } "SF2 Kick From Group Menu Title" @@ -1469,6 +1663,7 @@ "fi" "Potki pelaaja ryhmästä" "chi" "将玩家踢出组" "pt" "Expulsar jogadores do grupo" + "ko" "그룹에서 플레이어 쫓아내기" } "SF2 Kick From Group Menu Description" @@ -1478,6 +1673,7 @@ "fi" "Potki ei halutut pelaajat ryhmästä." "chi" "将不想要的玩家踢出组。" "pt" "Expulse jogadores indesejáveis do seu grupo." + "ko" "그룹에서 원치 않는 플레이어를 쫓아냈습니다." } "SF2 Kicked From Group" @@ -1488,6 +1684,7 @@ "fi" "Sinut on potkittu ryhmästä {1}." "chi" "你被 {1} 组踢出。" "pt" "Você foi expulso do grupo {1}." + "ko" "{1} 그룹에서 쫓겨났습니다." } "SF2 Player Kicked From Group" @@ -1498,6 +1695,7 @@ "fi" "{1} Potkittiin ryhmästä." "chi" "已从组踢出 {1} 。" "pt" "{1} foi expulso do grupo." + "ko" "그룹에서 {1}을(를) 쫓아냈습니다." } "SF2 Reset Group Queue Points Menu Title" @@ -1507,6 +1705,7 @@ "fi" "Tyhjää ryhmäsi pisteet" "chi" "重置组的队列点数" "pt" "Redfinir os pontos de espera do grupo" + "ko" "그룹 대기열 포인트 초기화" } "SF2 Reset Group Queue Points Menu Description" @@ -1516,6 +1715,7 @@ "fi" "Oletko varma, että haluat tyhjätä\nryhmäsi pisteet takaisin 0?" "chi" "你确定要重置组的队列点数为0?" "pt" "Tem certeza de que deseja zerar\nos pontos de espera do grupo?" + "ko" "그룹의 대기열 포인트를 0으로 초기화하시겠습니까?" } "SF2 Group Queue Points Reset" @@ -1525,6 +1725,7 @@ "fi" "Ryhmäsi jonopisteet tyhjennettiin takaisin 0!" "chi" "你的组的队列点数被重置为0!" "pt" "Os pontos de espera do seu grupo foram zerados!" + "ko" "그룹의 대기열 포인트가 0으로 초기화되었습니다!" } "SF2 Group Is Full" @@ -1534,6 +1735,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska ryhmäsi on täynnä!" "chi" "因为组已满,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o grupo está cheio!" + "ko" "그룹이 꽉 찼기 때문에 이 명령어는 수행할 수 없습니다!" } "SF2 Not Group Leader" @@ -1543,6 +1745,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska et ole ryhmänjohtaja." "chi" "因为你不是组领袖,所以你不可以使用此命令!" "pt" "Apenas o líder do grupo pode usar este comando." + "ko" "그룹 리더가 아니므로 이 명령어는 수행할 수 없습니다." } "SF2 No Players Available" @@ -1552,6 +1755,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska yhtään pelaajia ei ole käytettävissä!" "chi" "因为已经没有可用玩家,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque não há jogadores disponíveis!" + "ko" "플레이어가 그룹에 없으므로 이 명령어는 수행할 수 없습니다." } "SF2 Player Not In Group" @@ -1561,6 +1765,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska pelaaja ei ole ryhmässäsi." "chi" "因为此玩家不在你的组,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o jogador não está no seu grupo." + "ko" "플레이어가 이미 다른 그룹에 있으므로 이 명령어는 수행할 수 없습니다." } "SF2 Player In Another Group" @@ -1570,6 +1775,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska pelaaja on jo toisessa ryhmässä." "chi" "因为玩家已在其他组,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o jogador já está em outro grupo." + "ko" "플레이어가 이미 그룹에 있으므로 이 명령어는 수행할 수 없습니다." } "SF2 Player In Group" @@ -1580,11 +1786,12 @@ "chi" "因为玩家已在你的组,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o jogador já está no seu grupo." } - + "SF2 Player In Game" { "en" "You cannot perform this command because the player is currently playing on RED." "pt" "Não é possível usar este comando porque o jogador está jogando na equipe RED." + "ko" "플레이어가 이미 RED 팀에 있으므로 이 명령어는 수행할 수 없습니다." } "SF2 Create Group Menu Title" @@ -1594,6 +1801,7 @@ "fi" "Luo uusi ryhmä" "chi" "创建新的组" "pt" "Criar novo grupo" + "ko" "새 그룹 만들기" } "SF2 Create Group Menu Description" @@ -1604,6 +1812,7 @@ "fi" "Ryhmän luominen antaa sinun pelaata kavereidesi\nkanssa! Mutta, voit ainoastaan kutsua {1} pelaajaa\nryhmääsi. Ja kun jonotatte, koko ryhmän jonotuspisteet\nasetetaan pisteidesi mukaisesti,\njotka ovat {2}. Mutta, henkilökohtaiset jonopisteesi\nTYHJÄTÄÄN TAKAISIN 0!\n \n Luo uusi ryhmä?" "chi" "创建组可以让你和你的朋友一起玩!\n然而你只可以邀请 {1} 玩家进入你的组。\n关于队列,整个组的队列点数都被设为你的,即是{2}。\n但是,你的个人队列点数会被重置为0!\n \n创建新的组吗?" "pt" "Um grupo permite que você jogue junto com os seus\namigos! Entretanto, só é possível convidar {1} joga-\ndor(es) ao seu grupo. Na fila de espera, os pontos\nde todo o grupo será definido para os\nseus, que são {2}. Todavia, os seus pontos de espera\npessoais serão zerados!\n\nCriar novo grupo?" + "ko" "그룹을 만들면\n여러분과 친구들이 함께 플레이할 수 있습니다!\n하지만 최대 {1}명만 그룹에 초대할 수 있습니다.\n대기열의 경우, 전체 그룹의 대기열 포인트가 {2}으로 설정됩니다.\n그러나 자신의 개인 대기열 포인트는 0으로 초기화 됩니다!\n그룹을 만드시겠습니까?" } "SF2 Player New Group Leader" @@ -1614,6 +1823,7 @@ "fi" "{1} on uusi ryhmän johtaja." "chi" "{1} 现在是你的组的新的领袖。" "pt" "Agora, {1} é o novo líder do seu grupo." + "ko" "{1}님이 이제 그룹의 새 리더입니다." } "SF2 New Group Leader" @@ -1624,6 +1834,7 @@ "fi" "Olet nyt ryhmän johtaja {1} ryhmässä." "chi" "你现在是 {1} 组的领袖。" "pt" "Agora, você é o líder do grupo {1}." + "ko" "이제 당신이 {1} 그룹의 리더입니다." } "SF2 In Group" @@ -1633,6 +1844,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska olet jo ryhmässä!" "chi" "因为你已经在组内了,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque você já está no grupo!" + "ko" "그룹에 이미 속해 있으므로 이 명령어는 수행할 수 없습니다!" } "SF2 In Another Group" @@ -1642,6 +1854,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska olet jo toisessa ryhmässä!" "chi" "因为你已经在另一个组内了,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque você já está em outro grupo!" + "ko" "이미 다른 그룹에 속해 있으므로 이 명령어는 수행할 수 없습니다!" } "SF2 Joined Group" @@ -1652,6 +1865,7 @@ "fi" "Olet nyt ryhmässä {1}. Tervetuloa!" "chi" "你现在在 {1} 组。欢迎!" "pt" "Você se juntou ao grupo {1}. Seja bem-vindo(a)!" + "ko" "{1} 그룹에 가입했습니다. 환영합니다!" } "SF2 Left Group" @@ -1662,6 +1876,7 @@ "fi" "Olet lähtenyt ryhmästä {1}." "chi" "你已离开 {1} 组。" "pt" "Você saiu do grupo {1}." + "ko" "{1} 그룹을 탈퇴했습니다." } "SF2 Player Joined Group" @@ -1672,6 +1887,7 @@ "fi" "Pelaaja {1} on liittynyt ryhmääsi." "chi" "玩家 {1} 加入了你的组。" "pt" "{1} entrou no seu grupo." + "ko" "{1} 플레이어가 그룹에 가입했습니다." } "SF2 Player Left Group" @@ -1682,6 +1898,7 @@ "fi" "Pelaaja {1} on lähtenyt ryhmästäsi." "chi" "玩家 {1} 离开了你的组。" "pt" "{1} saiu do seu grupo." + "ko" "{1} 플레이어가 그룹에서 탈퇴했습니다." } "SF2 Created Group" @@ -1692,6 +1909,7 @@ "fi" "Ryhmän luonti onnistui! Uuden ryhmäsi nimi on: {1}" "chi" "创建组成功!你的新的组名称是:{1}" "pt" "Grupo criado com sucesso! O novo nome do seu grupo é: {1}" + "ko" "그룹을 만들었습니다! 새 그룹의 이름은 {1}입니다." } "SF2 Max Groups Reached" @@ -1701,6 +1919,7 @@ "fi" "Uutta ryhmää ei voi luoda, koska ryhmien enimmäismäärä on saavutettu!" "chi" "因为组的最大数量已达到,所以无法创建新的组!" "pt" "Não é possível criar um novo grupo porque o número máximo de grupos foi atingido!" + "ko" "최대 그룹 수에 도달했기 때문에 새 그룹을 만들 수 없습니다!" } "SF2 Group Does Not Exist" @@ -1710,6 +1929,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska ryhmääsi ei enään ole olemassa!" "chi" "因为你的组已不存在,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o seu grupo não existe mais!" + "ko" "그룹이 더 이상 존재하지 않으므로 이 명령어는 수행할 수 없습니다!" } "SF2 Leave Group Menu Title" @@ -1719,6 +1939,7 @@ "fi" "Poistu nykyisestä ryhmästä" "chi" "离开当前组" "pt" "Sair do grupo atual" + "ko" "현재 그룹 나가기" } "SF2 Leave Group Menu Description" @@ -1729,6 +1950,7 @@ "fi" "Oletko varma että haluat lähteä ryhmästä {1}?" "chi" "你确定要离开 {1} 组吗?" "pt" "Tem certeza de que deseja sair do grupo {1}?" + "ko" "정말 {1} 그룹에서 나가겠습니까?" } "SF2 Proxy Ask Menu Title" @@ -1738,6 +1960,7 @@ "fi" "Sinut on valittu olemaan Apulainen!" "chi" "你被选成为了小弟!" "pt" "Você foi selecionado para ser um proxy!" + "ko" "프록시로 선택되었습니다!" } "SF2 Proxy Ask Menu Description" @@ -1747,6 +1970,7 @@ "fi" "Apulaisena, tehtävänäsi on tappaa RED tiimi. Sinut\nteleportataan lähelle RED pelaajien sijaintia\nkun hyväksyt. Lisäksi, Apulaiset näkevät RED tiimin\nääriviivat seinien läpi, jotta voit tehdä yllätyshyökkäyksiä.\nMutta, elämäsi on riippuvainen ohjausmittaristasi,\neli se ei säily loputtomiin.\n \nHyväksytkö?" "chi" "作为小弟,你的目标是击杀红队。\n你在同意后会被传送到一个红队玩家附近的位置\n另外,小弟可以透过墙看见红队的轮廓,\n你可以按需来偷袭。但是你的生命值和控制能力挂钩,\n因此你不能一直成为小弟。\n \n你接受吗?" "pt" "Como um proxy, o seu objetivo é matar a equipe RED.\nVocê será teletransportado próximo de um jogador\nda equipe RED assim que aceitar. Em adição, proxies\npodem ver as silhuetas da equipe RED através das\nparedes, então você pode atacá-los de surpresa. En-\ntretanto, a sua vida depende do seu medidor de con-\ntrole, que não durará para sempre.\n\nDeseja aceitar?" + "ko" "프록시의 목표는 RED 팀을 죽이는 것입니다.\n프록시가 되는 것을 수락하면 RED 팀 근처에서 텔레포트 됩니다.\n또한 프록시는 벽 너머로 RED팀 플레이어의 외곽선을 볼 수 있으며, 그런 점을 이용하여 기습을 시도하세요.\n그러나 화면 하단 수명 미터는 RED 팀을 타격하지 않는 한 계속 감소합니다.\n \n프록시가 되시겠습니까?" } "SF2 Too Much Time" @@ -1756,6 +1980,7 @@ "fi" "{dodgerblue}[SF2] {lightblue}Valitettavasti olet viettänyt liian paljon aikaa, aloituspisteesi on näkyvillä pelaajalle." "chi" "{dodgerblue}[SF2] {lightblue}抱歉你花了太多时间,你的重生点其实对一名玩家可见。" "pt" "{dodgerblue}[SF2] {lightblue}Você demorou demais e o seu ponto de nascimento está visível para um jogador." + "ko" "{dodgerblue}[SF2] {lightblue}시간을 너무 많이 빼앗아 죄송합니다. 당신의 스폰 위치가 플레이어에게 사실상 보이게 됩니다." } "SF2 Too Many Proxies" @@ -1765,6 +1990,7 @@ "fi" "Anteeksi, mutta tällä hetkellä pelissä on liian monta Avustajaa." "chi" "抱歉,现在游戏中太多小弟了。" "pt" "Erro: há muitos proxies na rodada atual." + "ko" "죄송합니다. 현재 게임에 프록시가 너무 많습니다." } "SF2 Proxy Force Message" @@ -1775,6 +2001,7 @@ "fi" "Sinut on valittu olemaan Avustaja!\nSinut teleportataan RED tiimin alueelle {1} sekunnin päästä." "chi" "你被选为了小弟!\n你将在 {1} 秒后传送到红队区域。" "pt" "Você foi selecionado para ser um\nproxy! Você será teletransportado à área da RED em {1} segundo(s)." + "ko" "프록시로 선택되었습니다!\n{1}초 후에 RED 팀 플레이 구역으로 텔레포트됩니다." } "SF2 Boss Pack Menu Title" @@ -1783,27 +2010,30 @@ "ru" "Показать текущий набор, если он есть:" "fi" "Näytä nykyinen paketti, jos sellainen on:" "chi" "如果有的话,显示当前包:" - "pt" "Exibir o pacote atual (caso haja um):" + "pt" "Exibir o pacote atual (caso haja um):" + "ko" "현재 보스팩이 있는 경우, 해당 보스팩을 표시:" } - + "SF2 Boss Next Pack Menu Title" { "en" "Show the next boss pack, if there is one:" "ru" "Показать следующий набор боссов, если он есть:" "fi" "Näytä seuraava boss paketti, jos sellainen on:" "chi" "如果有的话,显示下一个boss包:" - "pt" "Exibir o próximo pacote de chefões (caso haja um):" + "pt" "Exibir o próximo pacote de chefões (caso haja um):" + "ko" "다음 보스팩이 있는 경우, 해당 보스팩을 표시:" } - + "SF2 Boss View On List Title" { "en" "Show the bosses on the current pack:" "ru" "Показать боссов в текущем наборе:" "fi" "Näytä nykyisen paketin bossit:" "chi" "显示当前包的Boss:" - "pt" "Exibir os chefões do pacote atual:" + "pt" "Exibir os chefões do pacote atual:" + "ko" "현재 보스팩에 있는 보스 표시:" } - + "SF2 Boss List Menu Title" { "en" "Current Bosses" @@ -1811,6 +2041,7 @@ "fi" "Nykyiset Bossit" "chi" "当前Boss" "pt" "Chefões atuais" + "ko" "현재 보스 목록" } "SF2 Boss Pack Vote Menu Title" @@ -1820,6 +2051,7 @@ "fi" "Valitse boss paketti seuraavalle kartalle!" "chi" "选择下一个地图的Boss包" "pt" "Selecione o pacote de chefões para o próx. mapa!" + "ko" "다음 맵의 보스팩을 선택하세요!" } "SF2 Boss Pack Vote Successful" @@ -1830,6 +2062,7 @@ "fi" "Äänestys onnistui! Seuraava boss paketti, joka ladataan seuraavalle kartalle on: {1}" "chi" "投票成功!下一个地图的Boss包已设为:{1}" "pt" "Votação finalizada! O próximo pacote de chefões a ser carregado no próx. mapa é: {1}" + "ko" "투표 성공! 다음 맵에 로드할 보스 팩은 {1}입니다." } "SF2 Boss Pack No Vote" @@ -1839,6 +2072,7 @@ "fi" "Kukaan ei äänestänyt. Seuraava kartalle ladattava boss paketti on oletuspaketti." "chi" "无人投票。下一个地图的Boss包将为默认。" "pt" "Ninguém votou. O próximo pacote de chefões a ser carregado no próx. mapa será o padrão." + "ko" "아무도 투표를 안 했어요. 다음 맵의 보스팩은 기본값으로 로드됩니다." } "SF2 Special Round Vote Menu Title" @@ -1848,7 +2082,8 @@ "fi" "Valitse erikoiskierros!" "chi" "选择特殊回合!" "pt" "Selecione a rodada especial!" - } + "ko" "스페셜 라운드를 고르세요!" + } "SF2 Special Round Vote Successful" { @@ -1858,6 +2093,7 @@ "fi" "Äänestys onnistui! Aktivoidaan erikoiskierros: {1}" "chi" "投票成功!启动特殊回合:{1}" "pt" "Votação finalizada! Ativando rodada especial: {1}" + "ko" "투표 성공! 스페셜 라운드 활성화: {1}" } "SF2 Projected Flashlight" @@ -1867,6 +2103,7 @@ "fi" "{dodgerblue}Taskulamppusi on asetettu tilaan {lightblue}Projected{dodgerblue}" "chi" "{dodgerblue}你的手电筒模式已设为{lightblue}投影{dodgerblue}。" "pt" "{dodgerblue}O modo da sua lanterna foi alterado para \"{lightblue}Projetado{dodgerblue}\"." + "ko" "{dodgerblue}손전등 조명 모드가 {lightblue}Projected{doggerblue}으로 설정되었습니다." } "SF2 Normal Flashlight" @@ -1876,6 +2113,7 @@ "fi" "{dodgerblue}Taskulamppusi on asetettu tilaan {lightblue}Normaali{dodgerblue}." "chi" "{dodgerblue}你的手电筒模式已设为{lightblue}普通{dodgerblue}。" "pt" "{dodgerblue}O modo da sua lanterna foi alterado para \"{lightblue}Normal{dodgerblue}\"." + "ko" "{dodgerblue}손전등 조명 모드가 {lightblue}기본{dodgerblue}으로 설정되었습니다." } "SF2 Mute Mode Normal" @@ -1885,6 +2123,7 @@ "fi" "{lightblue}Mykistystila asetettu normaaliksi." "chi" "{lightblue}禁言模式设为普通。" "pt" "{lightblue}Modo de emudecimento alterado para \"Normal\"." + "ko" "{lightblue}음소거 모드가 정상으로 설정되었습니다." } "SF2 Mute Mode Opposing" @@ -1894,6 +2133,7 @@ "fi" "{lightblue}Vastatiimi vaimennettiin." "chi" "{lightblue}禁言敌方队伍。" "pt" "{lightblue}Equipe inimiga emudecida." + "ko" "{lightblue}상대 팀을 음소거했습니다." } "SF2 Mute Mode Proxy" @@ -1903,6 +2143,7 @@ "fi" "{lightblue}Vastatiimi hiljennettiin, mutta asetukset asetetaan automaattisesti normaaliksi, jos olet avustaja." "chi" "{lightblue}禁言敌方队伍,但将在成为小弟后自动设为普通。" "pt" "{lightblue}Equipe inimiga emudecida. Entretanto, as configurações voltarão ao normal quando você for um proxy." + "ko" "{lightblue}상대 팀을 음소거하지만 프록시가 되는 경우 설정이 자동으로 기본값으로 설정됩니다." } "SF2 Settings Ghost Mode Teleport Title" @@ -1910,6 +2151,7 @@ "en" "Change ghost mode teleport state" "fi" "Muuta haamu tilan teleportin tilaa" "pt" "Alterar estado de teletransporte do modo fantasma" + "ko" "유령 모드 텔레포트 상태 변경" } "SF2 Teleport Ghost Players" @@ -1917,12 +2159,14 @@ "en" "{lightblue}You can now teleport to only players during ghost mode." "fi" "{lightblue}Voit nyt teleportata pelaajiin vain haamu tilassa." "pt" "{lightblue}Agora, você pode teletransportar-se apenas a jogadores no modo fantasma." + "ko" "{lightblue}이제 플레이어를 향해서만 텔레포트 할 수 있습니다." } "SF2 Teleport Ghost Bosses" { "en" "{lightblue}You can now teleport to only bosses during ghost mode." "pt" "{lightblue}Agora, você pode teletransportar-se apenas ao chefão no modo fantasma." + "ko" "{lightblue}이제 보스를 향해서만 텔레포트 할 수 있습니다." } "SF2 Settings Ghost Mode Toggle State Title" @@ -1930,6 +2174,7 @@ "en" "Set ghost mode toggle state" "fi" "Aseta haamu muodon tila" "pt" "Definir estado da alternação do modo fantasma" + "ko" "유령 모드 돌입 설정 토글" } "SF2 Toggle Ghost Default" @@ -1937,6 +2182,7 @@ "en" "{lightblue}Ghost mode will now be enabled any time you want." "fi" "{lightblue}Haamu tila otetaan nyt käyttöön koska tahansa, kun haluat." "pt" "{lightblue}Agora, o modo fantasma será ativado ao seu comando." + "ko" "{lightblue}유령 모드는 이제 언제든지 사용할 수 있습니다." } "SF2 Toggle Ghost On Grace" @@ -1944,6 +2190,7 @@ "en" "{lightblue}Ghost mode will now be enabled whenever grace period ends." "fi" "{lightblue}Haamu tila otetaan nyt käyttöön, kun armonaika loppuu." "pt" "{lightblue}Agora, o modo fantasma apenas será ativado após o tempo de preparação acabar." + "ko" "{lightblue}이제 유예 기간이 끝날 때마다 유령 모드가 활성화됩니다." } "SF2 Toggle Ghost On Death" @@ -1951,24 +2198,28 @@ "en" "{lightblue}Ghost mode will now be enabled upon dying in RED." "fi" "{lightblue}Haamu tila otetaan nyt käyttöön, kun kuolet RED:llä." "pt" "{lightblue}Agora, o modo fantasma será ativado após morrer estando na RED." + "ko" "{lightblue}이제 RED 팀으로 플레이 도중 사망 시 유령 모드가 활성화됩니다." } - + "SF2 Settings View Bobbing Toggle Title" { "en" "Set view bobbing toggle" "pt" "Definir alternação da arma balançando ao andar" + "ko" "시야 기울어짐 설정" } "SF2 Toggle View Bobbing On" { "en" "{lightblue}View bobbing is now enabled." "pt" "{lightblue}Agora, a arma balança ao andar." + "ko" "{lightblue}시야 기울어짐이 활성화되었습니다." } "SF2 Toggle View Bobbing Off" { "en" "{lightblue}View bobbing is now disabled." "pt" "{lightblue}Agora, a arma não balança ao andar." + "ko" "{lightblue}시야 기울어짐이 비활성화되었습니다." } "SF2 Recent Changes" @@ -1978,12 +2229,7 @@ "fi" "Viimeaikaiset muutokset:" "chi" "最近改动:" "pt" "Alterações recentes:" - } - - "SF2 Change Log" - { - "en" "-Precached literally every boss key value\n-Removed certain backwards compatibility" - "pt" "- Pré-carregamento de todos os valores-chave\nde chefões (literalmente)\n- Remoção de certas retrocompatibilidades" + "ko" "최근 변경 사항:" } "SF2 Smite player" @@ -1993,6 +2239,7 @@ "fi" "Iske pelaajaa" "chi" "惩戒玩家" "pt" "Castigar jogador" + "ko" "플레이어에게 천벌내리기" } "SF2 Smote target" @@ -2003,6 +2250,7 @@ "fi" "{valve}{1}{default} kutsui Zeuksen voiman ja iski {red}{2}!" "chi" "{valve}{1}{default} 召唤了宙斯之力并惩罚了 {red}{2} !" "pt" "{valve}{1}{default} evocou o poder de Zeus e castigou {red}{2}{default}!" + "ko" "{valve}{1}{default}이(가) 제우스의 힘을 호출해 {red}{2}을(를) 박살냈습니다." } "SF2 Boxing Initiate" @@ -2012,6 +2260,7 @@ "fi" "Vaikeustaso asetettu tasoon: {yellow}Normaali{default}.\nVaikeustaso pahenee, kun mestaristamme tulee heikompi." "chi" "难度已设为{yellow}普通{default}。\n我们的冠军越虚弱,难度就越大。" "pt" "A dificuldade foi alterada para {yellow}Normal{default}.\nA dificuldade será aumentada de acordo com o enfraquecimento do nosso campeão." + "ko" "난이도가 {yellow}보통{default}(으)로 설정되었습니다.\n챔피언이 약해질수록 난이도는 높아질 것입니다." } "SF2 Boxing Win Message" @@ -2022,5 +2271,72 @@ "fi" "{red}{1}{default} on kukistanut {valve}{2}." "chi" "{red}{1}{default} 击败了 {valve}{2}。" "pt" "{red}{1}{default} derrotou {valve}{2}{default}." + "ko" "{red}{1}{default}이(가) {default}{2}을(를) 쓰러뜨렸습니다!" + } + + "SF2 Exiting PvE Arena" + { + "#format" "{1:d}" + "en" "You are exiting the PvE Arena!\nYou will lose your weapons in {1} second(s) if you don't return!" + } + + "SF2 PvE Invalid File" + { + "en" "That is an invalid sound file." + } + + "SF2 PvE Add Unique Track" + { + "en" "You already added that soundtrack, add a unique soundtrack." + } + + "SF2 PvE Tracks Unavailable" + { + "en" "You don't have any more music sounds available, please remove at least one music file." + } + + "SF2 PvE Added Track" + { + "#format" "{1:s}" + "en" "Successfully added sound file {1}." + } + + "SF2 PvE Music Settings Title" + { + "en" "Adjust music settings" + } + + "SF2 PvE No Tracks 1-3" + { + "en" "It looks like you don't have any music themes added, use !sf2_add_pve_music or sm_sf2_add_pve_music (for console users) to add a sound file." + } + + "SF2 PvE No Tracks 2-3" + { + "en" "Example: !sf2_add_pve_music \"slender_boxing_ring/music/metal_gear_rising_revengeance_it_has_to_be_this_way.wav\"" + } + + "SF2 PvE No Tracks 3-3" + { + "en" "Make sure the sound file you want to add is found in your TF2 game files otherwise it will not play for you and print out a missing sound error." + } + + "SF2 PvE Set Volume" + { + "#format" "{1:d}" + "en" "Set volume ({1}%%)" + } + + "SF2 PvE Remove Music" + { + "en" "Remove" + } + + "SF2 PvE Removed Music" + { + "#format" "{1:s}" + "en" "Removed music {1}." } } + + diff --git a/materials/editor/sf2/sf2_boss_maker.vmt b/materials/editor/sf2/sf2_boss_maker.vmt new file mode 100644 index 00000000..4c82a259 --- /dev/null +++ b/materials/editor/sf2/sf2_boss_maker.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_boss_maker" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_boss_maker.vtf b/materials/editor/sf2/sf2_boss_maker.vtf new file mode 100644 index 00000000..c47a9036 Binary files /dev/null and b/materials/editor/sf2/sf2_boss_maker.vtf differ diff --git a/materials/editor/sf2/sf2_game_text.vmt b/materials/editor/sf2/sf2_game_text.vmt new file mode 100644 index 00000000..2e561aad --- /dev/null +++ b/materials/editor/sf2/sf2_game_text.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_game_text" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_game_text.vtf b/materials/editor/sf2/sf2_game_text.vtf new file mode 100644 index 00000000..522665f0 Binary files /dev/null and b/materials/editor/sf2/sf2_game_text.vtf differ diff --git a/materials/editor/sf2/sf2_gamerules.vmt b/materials/editor/sf2/sf2_gamerules.vmt new file mode 100644 index 00000000..682cb499 --- /dev/null +++ b/materials/editor/sf2/sf2_gamerules.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_gamerules" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_gamerules.vtf b/materials/editor/sf2/sf2_gamerules.vtf new file mode 100644 index 00000000..8a122172 Binary files /dev/null and b/materials/editor/sf2/sf2_gamerules.vtf differ diff --git a/materials/editor/sf2/sf2_info_jumprestrict.vmt b/materials/editor/sf2/sf2_info_jumprestrict.vmt new file mode 100644 index 00000000..eea88ca9 --- /dev/null +++ b/materials/editor/sf2/sf2_info_jumprestrict.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_info_jumprestrict" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_info_jumprestrict.vtf b/materials/editor/sf2/sf2_info_jumprestrict.vtf new file mode 100644 index 00000000..4f69fdeb Binary files /dev/null and b/materials/editor/sf2/sf2_info_jumprestrict.vtf differ diff --git a/materials/editor/sf2/sf2_info_page_music.vmt b/materials/editor/sf2/sf2_info_page_music.vmt new file mode 100644 index 00000000..0c24a0a9 --- /dev/null +++ b/materials/editor/sf2/sf2_info_page_music.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_info_page_music" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_info_page_music.vtf b/materials/editor/sf2/sf2_info_page_music.vtf new file mode 100644 index 00000000..abaea1be Binary files /dev/null and b/materials/editor/sf2/sf2_info_page_music.vtf differ diff --git a/materials/editor/sf2/sf2_logic_arena.vmt b/materials/editor/sf2/sf2_logic_arena.vmt new file mode 100644 index 00000000..98e45181 --- /dev/null +++ b/materials/editor/sf2/sf2_logic_arena.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_arena" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_arena.vtf b/materials/editor/sf2/sf2_logic_arena.vtf new file mode 100644 index 00000000..4614ffdc Binary files /dev/null and b/materials/editor/sf2/sf2_logic_arena.vtf differ diff --git a/materials/editor/sf2/sf2_logic_boxing.vmt b/materials/editor/sf2/sf2_logic_boxing.vmt new file mode 100644 index 00000000..c279b3c8 --- /dev/null +++ b/materials/editor/sf2/sf2_logic_boxing.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_boxing" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_boxing.vtf b/materials/editor/sf2/sf2_logic_boxing.vtf new file mode 100644 index 00000000..8cd28caf Binary files /dev/null and b/materials/editor/sf2/sf2_logic_boxing.vtf differ diff --git a/materials/editor/sf2/sf2_logic_elimination.vmt b/materials/editor/sf2/sf2_logic_elimination.vmt new file mode 100644 index 00000000..97dcf7b9 --- /dev/null +++ b/materials/editor/sf2/sf2_logic_elimination.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_elimination" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_elimination.vtf b/materials/editor/sf2/sf2_logic_elimination.vtf new file mode 100644 index 00000000..b1059ce0 Binary files /dev/null and b/materials/editor/sf2/sf2_logic_elimination.vtf differ diff --git a/materials/editor/sf2/sf2_logic_proxy.vmt b/materials/editor/sf2/sf2_logic_proxy.vmt new file mode 100644 index 00000000..73876d09 --- /dev/null +++ b/materials/editor/sf2/sf2_logic_proxy.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_proxy" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_proxy.vtf b/materials/editor/sf2/sf2_logic_proxy.vtf new file mode 100644 index 00000000..b8476bd0 Binary files /dev/null and b/materials/editor/sf2/sf2_logic_proxy.vtf differ diff --git a/materials/editor/sf2/sf2_logic_raid.vmt b/materials/editor/sf2/sf2_logic_raid.vmt new file mode 100644 index 00000000..df6550e0 --- /dev/null +++ b/materials/editor/sf2/sf2_logic_raid.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_raid" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_raid.vtf b/materials/editor/sf2/sf2_logic_raid.vtf new file mode 100644 index 00000000..669c28f3 Binary files /dev/null and b/materials/editor/sf2/sf2_logic_raid.vtf differ diff --git a/materials/editor/sf2/sf2_logic_slaughter.vmt b/materials/editor/sf2/sf2_logic_slaughter.vmt new file mode 100644 index 00000000..d3e0d0eb --- /dev/null +++ b/materials/editor/sf2/sf2_logic_slaughter.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_slaughter" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_slaughter.vtf b/materials/editor/sf2/sf2_logic_slaughter.vtf new file mode 100644 index 00000000..316b88bc Binary files /dev/null and b/materials/editor/sf2/sf2_logic_slaughter.vtf differ diff --git a/materials/models/editor/sf2/dev_engy_body_blu.vmt b/materials/models/editor/sf2/dev_engy_body_blu.vmt new file mode 100644 index 00000000..0fcba12c --- /dev/null +++ b/materials/models/editor/sf2/dev_engy_body_blu.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$basetexture" "models/editor/sf2/dev_engy_body_blu" +} \ No newline at end of file diff --git a/materials/models/editor/sf2/dev_engy_body_blu.vtf b/materials/models/editor/sf2/dev_engy_body_blu.vtf new file mode 100644 index 00000000..b9cdb1bb Binary files /dev/null and b/materials/models/editor/sf2/dev_engy_body_blu.vtf differ diff --git a/materials/models/editor/sf2/dev_engy_body_red.vmt b/materials/models/editor/sf2/dev_engy_body_red.vmt new file mode 100644 index 00000000..5f02bca2 --- /dev/null +++ b/materials/models/editor/sf2/dev_engy_body_red.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$basetexture" "models/editor/sf2/dev_engy_body_red" +} \ No newline at end of file diff --git a/materials/models/editor/sf2/dev_engy_body_red.vtf b/materials/models/editor/sf2/dev_engy_body_red.vtf new file mode 100644 index 00000000..6c02f7cf Binary files /dev/null and b/materials/models/editor/sf2/dev_engy_body_red.vtf differ diff --git a/materials/models/editor/sf2/dev_engy_head.vmt b/materials/models/editor/sf2/dev_engy_head.vmt new file mode 100644 index 00000000..08528bee --- /dev/null +++ b/materials/models/editor/sf2/dev_engy_head.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$basetexture" "models/editor/sf2/dev_engy_head" +} \ No newline at end of file diff --git a/materials/models/editor/sf2/dev_engy_head.vtf b/materials/models/editor/sf2/dev_engy_head.vtf new file mode 100644 index 00000000..857e0f54 Binary files /dev/null and b/materials/models/editor/sf2/dev_engy_head.vtf differ diff --git a/materials/models/editor/sf2/masky.vmt b/materials/models/editor/sf2/masky.vmt new file mode 100644 index 00000000..3aad48e6 --- /dev/null +++ b/materials/models/editor/sf2/masky.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$basetexture" "models/editor/sf2/masky" +} \ No newline at end of file diff --git a/materials/models/editor/sf2/masky.vtf b/materials/models/editor/sf2/masky.vtf new file mode 100644 index 00000000..7265d2b9 Binary files /dev/null and b/materials/models/editor/sf2/masky.vtf differ diff --git a/models/editor/sf2/escapespawn.dx80.vtx b/models/editor/sf2/escapespawn.dx80.vtx new file mode 100644 index 00000000..7f6bac64 Binary files /dev/null and b/models/editor/sf2/escapespawn.dx80.vtx differ diff --git a/models/editor/sf2/escapespawn.dx90.vtx b/models/editor/sf2/escapespawn.dx90.vtx new file mode 100644 index 00000000..88ff2897 Binary files /dev/null and b/models/editor/sf2/escapespawn.dx90.vtx differ diff --git a/models/editor/sf2/escapespawn.mdl b/models/editor/sf2/escapespawn.mdl new file mode 100644 index 00000000..e1a6f752 Binary files /dev/null and b/models/editor/sf2/escapespawn.mdl differ diff --git a/models/editor/sf2/escapespawn.sw.vtx b/models/editor/sf2/escapespawn.sw.vtx new file mode 100644 index 00000000..3bc1766c Binary files /dev/null and b/models/editor/sf2/escapespawn.sw.vtx differ diff --git a/models/editor/sf2/escapespawn.vvd b/models/editor/sf2/escapespawn.vvd new file mode 100644 index 00000000..07d41be5 Binary files /dev/null and b/models/editor/sf2/escapespawn.vvd differ diff --git a/models/editor/sf2/proxyspawn.dx80.vtx b/models/editor/sf2/proxyspawn.dx80.vtx new file mode 100644 index 00000000..62533f31 Binary files /dev/null and b/models/editor/sf2/proxyspawn.dx80.vtx differ diff --git a/models/editor/sf2/proxyspawn.dx90.vtx b/models/editor/sf2/proxyspawn.dx90.vtx new file mode 100644 index 00000000..da58a8bc Binary files /dev/null and b/models/editor/sf2/proxyspawn.dx90.vtx differ diff --git a/models/editor/sf2/proxyspawn.mdl b/models/editor/sf2/proxyspawn.mdl new file mode 100644 index 00000000..e1d841c2 Binary files /dev/null and b/models/editor/sf2/proxyspawn.mdl differ diff --git a/models/editor/sf2/proxyspawn.sw.vtx b/models/editor/sf2/proxyspawn.sw.vtx new file mode 100644 index 00000000..e28eadec Binary files /dev/null and b/models/editor/sf2/proxyspawn.sw.vtx differ diff --git a/models/editor/sf2/proxyspawn.vvd b/models/editor/sf2/proxyspawn.vvd new file mode 100644 index 00000000..b488e229 Binary files /dev/null and b/models/editor/sf2/proxyspawn.vvd differ diff --git a/models/editor/sf2/pvpspawn.dx80.vtx b/models/editor/sf2/pvpspawn.dx80.vtx new file mode 100644 index 00000000..c07bb5a9 Binary files /dev/null and b/models/editor/sf2/pvpspawn.dx80.vtx differ diff --git a/models/editor/sf2/pvpspawn.dx90.vtx b/models/editor/sf2/pvpspawn.dx90.vtx new file mode 100644 index 00000000..4949fa06 Binary files /dev/null and b/models/editor/sf2/pvpspawn.dx90.vtx differ diff --git a/models/editor/sf2/pvpspawn.mdl b/models/editor/sf2/pvpspawn.mdl new file mode 100644 index 00000000..97ee2156 Binary files /dev/null and b/models/editor/sf2/pvpspawn.mdl differ diff --git a/models/editor/sf2/pvpspawn.sw.vtx b/models/editor/sf2/pvpspawn.sw.vtx new file mode 100644 index 00000000..7193f495 Binary files /dev/null and b/models/editor/sf2/pvpspawn.sw.vtx differ diff --git a/models/editor/sf2/pvpspawn.vvd b/models/editor/sf2/pvpspawn.vvd new file mode 100644 index 00000000..df114334 Binary files /dev/null and b/models/editor/sf2/pvpspawn.vvd differ diff --git a/sf2.fgd b/sf2.fgd index 304045dd..dd96573b 100644 --- a/sf2.fgd +++ b/sf2.fgd @@ -398,6 +398,15 @@ input DisableStaminaJumpRestriction(void) : "Disable the stamina jump restriction." ] -@SolidClass base(trigger_multiple) = sf2_trigger_pve : "(Private only) A trigger brush that when touched by a player will enter PvE mode." +@SolidClass base(trigger_multiple) = sf2_trigger_pve : "A trigger brush that when touched by a player will enter PvE mode." +[ + bosspve(choices) : "Is Boss Arena" : 1 : "(Modified only) Determines if the PvE arena can spawn bosses and play soundtracks to players. Useful if you want areas where players can wield weapons but shouldn't damage each other." = + [ + 0 : "Disabled" + 1 : "Enabled" + ] +] + +@SolidClass base(trigger_multiple) = sf2_trigger_weapons : "(Modified only) A trigger brush that when touched by a player will automatically give them all weapons but cannot damage other players. The substitude for sf2_trigger_pve" [ ]