commit 055f5cd1680b2a2ab0cf446c3e49718bc6b9acf7 Author: Scott Ehlert Date: Mon Sep 15 01:07:45 2008 -0500 Added most recent version of unmodified HL2 SDK for Orange Box engine diff --git a/Everything_SDK-2005.sln b/Everything_SDK-2005.sln new file mode 100644 index 00000000..b111649f --- /dev/null +++ b/Everything_SDK-2005.sln @@ -0,0 +1,202 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Glview", "utils\glview\glview-2005.vcproj", "{BD1604CA-F401-4C4B-821C-251F5AE157FE}" + ProjectSection(ProjectDependencies) = postProject + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Height2normal", "utils\height2normal\height2normal-2005.vcproj", "{0FDD99E4-130F-493C-8202-4C0236CC47EA}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Serverplugin_empty", "utils\serverplugin_sample\serverplugin_empty-2005.vcproj", "{B6572CBE-7C7F-4FFF-94DE-AFBBBAB11C64}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tgadiff", "utils\tgadiff\tgadiff-2005.vcproj", "{0CE0AF8A-A977-4538-9D63-BCB76DE1BAC6}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vbsp", "utils\vbsp\vbsp-2005.vcproj", "{B78B6271-B19A-4CF6-926E-40B643548E23}" + ProjectSection(ProjectDependencies) = postProject + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vice", "utils\vice\vice-2005.vcproj", "{3CE6E7A9-89EC-4304-8D72-5B602B4DBD09}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vrad_launcher", "utils\vrad_launcher\vrad_launcher-2005.vcproj", "{914F19DF-64EC-4E7D-8B01-76477BF06479}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vtf2tga", "utils\vtf2tga\vtf2tga-2005.vcproj", "{2A1F656C-053C-46A4-AE33-304C1D865E0C}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vtfdiff", "utils\vtfdiff\vtfdiff-2005.vcproj", "{0A368DE7-D34A-48D3-B517-996BFF2D0D5D}" + ProjectSection(ProjectDependencies) = postProject + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vvis_launcher", "utils\vvis_launcher\vvis_launcher-2005.vcproj", "{4C0B9915-E8FF-4089-8927-FC934BFC1E4A}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xwad", "utils\xwad\xwad-2005.vcproj", "{B850012C-98A2-42F7-B023-9F65C448D938}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "demoinfo", "utils\demoinfo\demoinfo-2005.vcproj", "{4FE3FDCA-9571-44B3-A521-C81448434490}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Motionmapper", "utils\motionmapper\motionmapper-2005.vcproj", "{A882FC08-8B92-4D4F-89BF-75BCEC2BAE11}" + ProjectSection(ProjectDependencies) = postProject + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QC_Eyes", "utils\qc_eyes\QC_Eyes-2005.vcproj", "{D373436F-7DBF-468B-A3E4-601FB8556544}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mathlib", "mathlib\mathlib-2005.vcproj", "{884C66F2-7F84-4570-AE6C-B634C1113D69}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tier1", "tier1\tier1-2005.vcproj", "{E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Phonemeextractor", "utils\phonemeextractor\phonemeextractor-2005.vcproj", "{8A35F55B-B5C2-47A0-8C4A-5857A8E20385}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vgui_controls", "vgui2\vgui_controls\vgui_controls-2005.vcproj", "{BF3EDBF5-ED65-4567-B348-504C1310A1BB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vrad_dll", "utils\vrad\vrad_dll-2005.vcproj", "{0DA02E11-F553-4DD1-83D1-F760F2D96862}" + ProjectSection(ProjectDependencies) = postProject + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Vvis_dll", "utils\vvis\vvis_dll-2005.vcproj", "{CB353257-04B0-4EC8-9E47-F2F17B2675C9}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {884C66F2-7F84-4570-AE6C-B634C1113D69} = {884C66F2-7F84-4570-AE6C-B634C1113D69} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vprojtomake", "utils\vprojtomake\vprojtomake-2005.vcproj", "{EA55446E-BC04-491C-A9F0-605DFCBB213A}" + ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BD1604CA-F401-4C4B-821C-251F5AE157FE}.Debug|Win32.ActiveCfg = Debug|Win32 + {BD1604CA-F401-4C4B-821C-251F5AE157FE}.Debug|Win32.Build.0 = Debug|Win32 + {BD1604CA-F401-4C4B-821C-251F5AE157FE}.Release|Win32.ActiveCfg = Release|Win32 + {BD1604CA-F401-4C4B-821C-251F5AE157FE}.Release|Win32.Build.0 = Release|Win32 + {0FDD99E4-130F-493C-8202-4C0236CC47EA}.Debug|Win32.ActiveCfg = Debug|Win32 + {0FDD99E4-130F-493C-8202-4C0236CC47EA}.Debug|Win32.Build.0 = Debug|Win32 + {0FDD99E4-130F-493C-8202-4C0236CC47EA}.Release|Win32.ActiveCfg = Release|Win32 + {0FDD99E4-130F-493C-8202-4C0236CC47EA}.Release|Win32.Build.0 = Release|Win32 + {B6572CBE-7C7F-4FFF-94DE-AFBBBAB11C64}.Debug|Win32.ActiveCfg = Debug|Win32 + {B6572CBE-7C7F-4FFF-94DE-AFBBBAB11C64}.Debug|Win32.Build.0 = Debug|Win32 + {B6572CBE-7C7F-4FFF-94DE-AFBBBAB11C64}.Release|Win32.ActiveCfg = Release|Win32 + {B6572CBE-7C7F-4FFF-94DE-AFBBBAB11C64}.Release|Win32.Build.0 = Release|Win32 + {0CE0AF8A-A977-4538-9D63-BCB76DE1BAC6}.Debug|Win32.ActiveCfg = Debug|Win32 + {0CE0AF8A-A977-4538-9D63-BCB76DE1BAC6}.Debug|Win32.Build.0 = Debug|Win32 + {0CE0AF8A-A977-4538-9D63-BCB76DE1BAC6}.Release|Win32.ActiveCfg = Release|Win32 + {0CE0AF8A-A977-4538-9D63-BCB76DE1BAC6}.Release|Win32.Build.0 = Release|Win32 + {B78B6271-B19A-4CF6-926E-40B643548E23}.Debug|Win32.ActiveCfg = Debug|Win32 + {B78B6271-B19A-4CF6-926E-40B643548E23}.Debug|Win32.Build.0 = Debug|Win32 + {B78B6271-B19A-4CF6-926E-40B643548E23}.Release|Win32.ActiveCfg = Release|Win32 + {B78B6271-B19A-4CF6-926E-40B643548E23}.Release|Win32.Build.0 = Release|Win32 + {3CE6E7A9-89EC-4304-8D72-5B602B4DBD09}.Debug|Win32.ActiveCfg = Debug|Win32 + {3CE6E7A9-89EC-4304-8D72-5B602B4DBD09}.Debug|Win32.Build.0 = Debug|Win32 + {3CE6E7A9-89EC-4304-8D72-5B602B4DBD09}.Release|Win32.ActiveCfg = Release|Win32 + {3CE6E7A9-89EC-4304-8D72-5B602B4DBD09}.Release|Win32.Build.0 = Release|Win32 + {914F19DF-64EC-4E7D-8B01-76477BF06479}.Debug|Win32.ActiveCfg = Debug|Win32 + {914F19DF-64EC-4E7D-8B01-76477BF06479}.Debug|Win32.Build.0 = Debug|Win32 + {914F19DF-64EC-4E7D-8B01-76477BF06479}.Release|Win32.ActiveCfg = Release|Win32 + {914F19DF-64EC-4E7D-8B01-76477BF06479}.Release|Win32.Build.0 = Release|Win32 + {2A1F656C-053C-46A4-AE33-304C1D865E0C}.Debug|Win32.ActiveCfg = Debug|Win32 + {2A1F656C-053C-46A4-AE33-304C1D865E0C}.Debug|Win32.Build.0 = Debug|Win32 + {2A1F656C-053C-46A4-AE33-304C1D865E0C}.Release|Win32.ActiveCfg = Release|Win32 + {2A1F656C-053C-46A4-AE33-304C1D865E0C}.Release|Win32.Build.0 = Release|Win32 + {0A368DE7-D34A-48D3-B517-996BFF2D0D5D}.Debug|Win32.ActiveCfg = Debug|Win32 + {0A368DE7-D34A-48D3-B517-996BFF2D0D5D}.Debug|Win32.Build.0 = Debug|Win32 + {0A368DE7-D34A-48D3-B517-996BFF2D0D5D}.Release|Win32.ActiveCfg = Release|Win32 + {0A368DE7-D34A-48D3-B517-996BFF2D0D5D}.Release|Win32.Build.0 = Release|Win32 + {4C0B9915-E8FF-4089-8927-FC934BFC1E4A}.Debug|Win32.ActiveCfg = Debug|Win32 + {4C0B9915-E8FF-4089-8927-FC934BFC1E4A}.Debug|Win32.Build.0 = Debug|Win32 + {4C0B9915-E8FF-4089-8927-FC934BFC1E4A}.Release|Win32.ActiveCfg = Release|Win32 + {4C0B9915-E8FF-4089-8927-FC934BFC1E4A}.Release|Win32.Build.0 = Release|Win32 + {B850012C-98A2-42F7-B023-9F65C448D938}.Debug|Win32.ActiveCfg = Debug|Win32 + {B850012C-98A2-42F7-B023-9F65C448D938}.Debug|Win32.Build.0 = Debug|Win32 + {B850012C-98A2-42F7-B023-9F65C448D938}.Release|Win32.ActiveCfg = Release|Win32 + {B850012C-98A2-42F7-B023-9F65C448D938}.Release|Win32.Build.0 = Release|Win32 + {4FE3FDCA-9571-44B3-A521-C81448434490}.Debug|Win32.ActiveCfg = Debug|Win32 + {4FE3FDCA-9571-44B3-A521-C81448434490}.Debug|Win32.Build.0 = Debug|Win32 + {4FE3FDCA-9571-44B3-A521-C81448434490}.Release|Win32.ActiveCfg = Release|Win32 + {4FE3FDCA-9571-44B3-A521-C81448434490}.Release|Win32.Build.0 = Release|Win32 + {A882FC08-8B92-4D4F-89BF-75BCEC2BAE11}.Debug|Win32.ActiveCfg = Debug|Win32 + {A882FC08-8B92-4D4F-89BF-75BCEC2BAE11}.Debug|Win32.Build.0 = Debug|Win32 + {A882FC08-8B92-4D4F-89BF-75BCEC2BAE11}.Release|Win32.ActiveCfg = Release|Win32 + {A882FC08-8B92-4D4F-89BF-75BCEC2BAE11}.Release|Win32.Build.0 = Release|Win32 + {D373436F-7DBF-468B-A3E4-601FB8556544}.Debug|Win32.ActiveCfg = Debug|Win32 + {D373436F-7DBF-468B-A3E4-601FB8556544}.Debug|Win32.Build.0 = Debug|Win32 + {D373436F-7DBF-468B-A3E4-601FB8556544}.Release|Win32.ActiveCfg = Release|Win32 + {D373436F-7DBF-468B-A3E4-601FB8556544}.Release|Win32.Build.0 = Release|Win32 + {884C66F2-7F84-4570-AE6C-B634C1113D69}.Debug|Win32.ActiveCfg = Debug|Win32 + {884C66F2-7F84-4570-AE6C-B634C1113D69}.Debug|Win32.Build.0 = Debug|Win32 + {884C66F2-7F84-4570-AE6C-B634C1113D69}.Release|Win32.ActiveCfg = Release|Win32 + {884C66F2-7F84-4570-AE6C-B634C1113D69}.Release|Win32.Build.0 = Release|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Debug|Win32.ActiveCfg = Debug|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Debug|Win32.Build.0 = Debug|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release|Win32.ActiveCfg = Release|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release|Win32.Build.0 = Release|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug|Win32.ActiveCfg = Debug|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug|Win32.Build.0 = Debug|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release|Win32.ActiveCfg = Release|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release|Win32.Build.0 = Release|Win32 + {BF3EDBF5-ED65-4567-B348-504C1310A1BB}.Debug|Win32.ActiveCfg = Debug|Win32 + {BF3EDBF5-ED65-4567-B348-504C1310A1BB}.Debug|Win32.Build.0 = Debug|Win32 + {BF3EDBF5-ED65-4567-B348-504C1310A1BB}.Release|Win32.ActiveCfg = Release|Win32 + {BF3EDBF5-ED65-4567-B348-504C1310A1BB}.Release|Win32.Build.0 = Release|Win32 + {0DA02E11-F553-4DD1-83D1-F760F2D96862}.Debug|Win32.ActiveCfg = Debug|Win32 + {0DA02E11-F553-4DD1-83D1-F760F2D96862}.Debug|Win32.Build.0 = Debug|Win32 + {0DA02E11-F553-4DD1-83D1-F760F2D96862}.Release|Win32.ActiveCfg = Release|Win32 + {0DA02E11-F553-4DD1-83D1-F760F2D96862}.Release|Win32.Build.0 = Release|Win32 + {CB353257-04B0-4EC8-9E47-F2F17B2675C9}.Debug|Win32.ActiveCfg = Debug|Win32 + {CB353257-04B0-4EC8-9E47-F2F17B2675C9}.Debug|Win32.Build.0 = Debug|Win32 + {CB353257-04B0-4EC8-9E47-F2F17B2675C9}.Release|Win32.ActiveCfg = Release|Win32 + {CB353257-04B0-4EC8-9E47-F2F17B2675C9}.Release|Win32.Build.0 = Release|Win32 + {EA55446E-BC04-491C-A9F0-605DFCBB213A}.Debug|Win32.ActiveCfg = Debug|Win32 + {EA55446E-BC04-491C-A9F0-605DFCBB213A}.Debug|Win32.Build.0 = Debug|Win32 + {EA55446E-BC04-491C-A9F0-605DFCBB213A}.Release|Win32.ActiveCfg = Release|Win32 + {EA55446E-BC04-491C-A9F0-605DFCBB213A}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Game_Episodic-2005.sln b/Game_Episodic-2005.sln new file mode 100644 index 00000000..390aef1d --- /dev/null +++ b/Game_Episodic-2005.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Client (Episodic)", "game\client\client_episodic-2005.vcproj", "{306BEF7B-8A1F-F569-3761-3E05CFD6D683}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Server (Episodic)", "game\server\server_episodic-2005.vcproj", "{31C796EE-3EE9-54FC-9EF2-AC09ED9C18F5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {306BEF7B-8A1F-F569-3761-3E05CFD6D683}.Debug|Win32.ActiveCfg = Debug|Win32 + {306BEF7B-8A1F-F569-3761-3E05CFD6D683}.Debug|Win32.Build.0 = Debug|Win32 + {306BEF7B-8A1F-F569-3761-3E05CFD6D683}.Release|Win32.ActiveCfg = Release|Win32 + {306BEF7B-8A1F-F569-3761-3E05CFD6D683}.Release|Win32.Build.0 = Release|Win32 + {31C796EE-3EE9-54FC-9EF2-AC09ED9C18F5}.Debug|Win32.ActiveCfg = Debug|Win32 + {31C796EE-3EE9-54FC-9EF2-AC09ED9C18F5}.Debug|Win32.Build.0 = Debug|Win32 + {31C796EE-3EE9-54FC-9EF2-AC09ED9C18F5}.Release|Win32.ActiveCfg = Release|Win32 + {31C796EE-3EE9-54FC-9EF2-AC09ED9C18F5}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Game_HL2-2005.sln b/Game_HL2-2005.sln new file mode 100644 index 00000000..a22672d6 --- /dev/null +++ b/Game_HL2-2005.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Client (HL2)", "game\client\client_hl2-2005.vcproj", "{F2D9D6B0-DEE5-4217-9336-A4FEBA24C790}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Server (HL2)", "game\server\server_hl2-2005.vcproj", "{EB864878-8530-446B-B8E4-BE97D3F608F7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F2D9D6B0-DEE5-4217-9336-A4FEBA24C790}.Debug|Win32.ActiveCfg = Debug|Win32 + {F2D9D6B0-DEE5-4217-9336-A4FEBA24C790}.Debug|Win32.Build.0 = Debug|Win32 + {F2D9D6B0-DEE5-4217-9336-A4FEBA24C790}.Release|Win32.ActiveCfg = Release|Win32 + {F2D9D6B0-DEE5-4217-9336-A4FEBA24C790}.Release|Win32.Build.0 = Release|Win32 + {EB864878-8530-446B-B8E4-BE97D3F608F7}.Debug|Win32.ActiveCfg = Debug|Win32 + {EB864878-8530-446B-B8E4-BE97D3F608F7}.Debug|Win32.Build.0 = Debug|Win32 + {EB864878-8530-446B-B8E4-BE97D3F608F7}.Release|Win32.ActiveCfg = Release|Win32 + {EB864878-8530-446B-B8E4-BE97D3F608F7}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Game_HL2MP-2005.sln b/Game_HL2MP-2005.sln new file mode 100644 index 00000000..b1a4cdee --- /dev/null +++ b/Game_HL2MP-2005.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Client (HL2MP)", "game\client\client_hl2mp-2005.vcproj", "{5FE91DC8-B6DF-4061-984E-36FD36623E72}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Server (HL2MP)", "game\server\server_hl2mp-2005.vcproj", "{4254C2D9-2C38-4911-BD4A-EB034CBD48D1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5FE91DC8-B6DF-4061-984E-36FD36623E72}.Debug|Win32.ActiveCfg = Debug|Win32 + {5FE91DC8-B6DF-4061-984E-36FD36623E72}.Debug|Win32.Build.0 = Debug|Win32 + {5FE91DC8-B6DF-4061-984E-36FD36623E72}.Release|Win32.ActiveCfg = Release|Win32 + {5FE91DC8-B6DF-4061-984E-36FD36623E72}.Release|Win32.Build.0 = Release|Win32 + {4254C2D9-2C38-4911-BD4A-EB034CBD48D1}.Debug|Win32.ActiveCfg = Debug|Win32 + {4254C2D9-2C38-4911-BD4A-EB034CBD48D1}.Debug|Win32.Build.0 = Debug|Win32 + {4254C2D9-2C38-4911-BD4A-EB034CBD48D1}.Release|Win32.ActiveCfg = Release|Win32 + {4254C2D9-2C38-4911-BD4A-EB034CBD48D1}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Game_Scratch-2005.sln b/Game_Scratch-2005.sln new file mode 100644 index 00000000..b1ae39b1 --- /dev/null +++ b/Game_Scratch-2005.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Client (SDK)", "game\client\client_scratch-2005.vcproj", "{09603CC0-928D-4939-83E5-C255291B9466}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Server (SDK)", "game\server\server_scratch-2005.vcproj", "{E8C0FE86-D8E1-4B44-8F76-F2A9A21E9C4F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {09603CC0-928D-4939-83E5-C255291B9466}.Debug|Win32.ActiveCfg = Debug|Win32 + {09603CC0-928D-4939-83E5-C255291B9466}.Debug|Win32.Build.0 = Debug|Win32 + {09603CC0-928D-4939-83E5-C255291B9466}.Release|Win32.ActiveCfg = Release|Win32 + {09603CC0-928D-4939-83E5-C255291B9466}.Release|Win32.Build.0 = Release|Win32 + {E8C0FE86-D8E1-4B44-8F76-F2A9A21E9C4F}.Debug|Win32.ActiveCfg = Debug|Win32 + {E8C0FE86-D8E1-4B44-8F76-F2A9A21E9C4F}.Debug|Win32.Build.0 = Debug|Win32 + {E8C0FE86-D8E1-4B44-8F76-F2A9A21E9C4F}.Release|Win32.ActiveCfg = Release|Win32 + {E8C0FE86-D8E1-4B44-8F76-F2A9A21E9C4F}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/common/GameUI/IGameUI.h b/common/GameUI/IGameUI.h new file mode 100644 index 00000000..ec7a7a53 --- /dev/null +++ b/common/GameUI/IGameUI.h @@ -0,0 +1,113 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef IGAMEUI_H +#define IGAMEUI_H +#ifdef _WIN32 +#pragma once +#endif + +#include "interface.h" +#include "vgui/IPanel.h" + +#if !defined( _X360 ) +#include "xbox/xboxstubs.h" +#endif + +// reasons why the user can't connect to a game server +enum ESteamLoginFailure +{ + STEAMLOGINFAILURE_NONE, + STEAMLOGINFAILURE_BADTICKET, + STEAMLOGINFAILURE_NOSTEAMLOGIN, + STEAMLOGINFAILURE_VACBANNED, + STEAMLOGINFAILURE_LOGGED_IN_ELSEWHERE +}; + +enum ESystemNotify +{ + SYSTEMNOTIFY_STORAGEDEVICES_CHANGED, + SYSTEMNOTIFY_USER_SIGNEDIN, + SYSTEMNOTIFY_USER_SIGNEDOUT, + SYSTEMNOTIFY_XUIOPENING, + SYSTEMNOTIFY_XUICLOSED, + SYSTEMNOTIFY_INVITE_SHUTDOWN, // Cross-game invite is causing us to shutdown +}; + +//----------------------------------------------------------------------------- +// Purpose: contains all the functions that the GameUI dll exports +//----------------------------------------------------------------------------- +abstract_class IGameUI +{ +public: + // initialization/shutdown + virtual void Initialize( CreateInterfaceFn appFactory ) = 0; + virtual void PostInit() = 0; + + // connect to other interfaces at the same level (gameui.dll/server.dll/client.dll) + virtual void Connect( CreateInterfaceFn gameFactory ) = 0; + + virtual void Start() = 0; + virtual void Shutdown() = 0; + virtual void RunFrame() = 0; + + // notifications + virtual void OnGameUIActivated() = 0; + virtual void OnGameUIHidden() = 0; + + // OLD: Use OnConnectToServer2 + virtual void OLD_OnConnectToServer(const char *game, int IP, int port) = 0; + + virtual void OnDisconnectFromServer_OLD( uint8 eSteamLoginFailure, const char *username ) = 0; + virtual void OnLevelLoadingStarted(bool bShowProgressDialog) = 0; + virtual void OnLevelLoadingFinished(bool bError, const char *failureReason, const char *extendedReason) = 0; + + // level loading progress, returns true if the screen needs updating + virtual bool UpdateProgressBar(float progress, const char *statusText) = 0; + // Shows progress desc, returns previous setting... (used with custom progress bars ) + virtual bool SetShowProgressText( bool show ) = 0; + + // !!!!!!!!!members added after "GameUI011" initial release!!!!!!!!!!!!!!!!!!! + virtual void ShowNewGameDialog( int chapter ) = 0; + + // Xbox 360 + virtual void SessionNotification( const int notification, const int param = 0 ) = 0; + virtual void SystemNotification( const int notification ) = 0; + virtual void ShowMessageDialog( const uint nType, vgui::Panel *pOwner ) = 0; + virtual void UpdatePlayerInfo( uint64 nPlayerId, const char *pName, int nTeam, byte cVoiceState, int nPlayersNeeded, bool bHost ) = 0; + virtual void SessionSearchResult( int searchIdx, void *pHostData, XSESSION_SEARCHRESULT *pResult, int ping ) = 0; + virtual void OnCreditsFinished( void ) = 0; + + // inserts specified panel as background for level load dialog + virtual void SetLoadingBackgroundDialog( vgui::VPANEL panel ) = 0; + + // Bonus maps interfaces + virtual void BonusMapUnlock( const char *pchFileName = NULL, const char *pchMapName = NULL ) = 0; + virtual void BonusMapComplete( const char *pchFileName = NULL, const char *pchMapName = NULL ) = 0; + virtual void BonusMapChallengeUpdate( const char *pchFileName, const char *pchMapName, const char *pchChallengeName, int iBest ) = 0; + virtual void BonusMapChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) = 0; + virtual void BonusMapChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) = 0; + virtual void BonusMapDatabaseSave( void ) = 0; + virtual int BonusMapNumAdvancedCompleted( void ) = 0; + virtual void BonusMapNumMedals( int piNumMedals[ 3 ] ) = 0; + + virtual void OnConnectToServer2(const char *game, int IP, int connectionPort, int queryPort) = 0; + + // X360 Storage device validation: + // returns true right away if storage device has been previously selected. + // otherwise returns false and will set the variable pointed by pStorageDeviceValidated to 1 + // once the storage device is selected by user. + virtual bool ValidateStorageDevice( int *pStorageDeviceValidated ) = 0; + + virtual void SetProgressOnStart() = 0; + virtual void OnDisconnectFromServer( uint8 eSteamLoginFailure ) = 0; + +}; + +#define GAMEUI_INTERFACE_VERSION "GameUI011" + +#endif // IGAMEUI_H diff --git a/common/compiledcaptionswap.cpp b/common/compiledcaptionswap.cpp new file mode 100644 index 00000000..b3763161 --- /dev/null +++ b/common/compiledcaptionswap.cpp @@ -0,0 +1,103 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Swap a compiled caption file. +// +// $NoKeywords: $ +//=============================================================================// + +#include "utlbuffer.h" +#include "byteswap.h" +#include "filesystem.h" +#include "tier2/fileutils.h" +#include "captioncompiler.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +BEGIN_BYTESWAP_DATADESC( CompiledCaptionHeader_t ) + DEFINE_FIELD( magic, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_FIELD( numblocks, FIELD_INTEGER ), + DEFINE_FIELD( blocksize, FIELD_INTEGER ), + DEFINE_FIELD( directorysize, FIELD_INTEGER ), + DEFINE_FIELD( dataoffset, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CaptionLookup_t ) + DEFINE_FIELD( hash, FIELD_INTEGER ), + DEFINE_FIELD( blockNum, FIELD_INTEGER ), + DEFINE_FIELD( offset, FIELD_SHORT ), + DEFINE_FIELD( length, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +//----------------------------------------------------------------------------- +// Swap a compiled closecaption file +//----------------------------------------------------------------------------- +bool SwapClosecaptionFile( void *pData ) +{ + CByteswap swap; + swap.ActivateByteSwapping( true ); + + CompiledCaptionHeader_t *pHdr = (CompiledCaptionHeader_t*)pData; + + if ( IsX360() ) + { + // pre-swap file header + swap.SwapFieldsToTargetEndian( pHdr ); + } + + if ( pHdr->magic != COMPILED_CAPTION_FILEID || pHdr->version != COMPILED_CAPTION_VERSION ) + { + // bad data + return false; + } + + // lookup headers + pData = (byte*)pData + sizeof(CompiledCaptionHeader_t); + swap.SwapFieldsToTargetEndian( (CaptionLookup_t*)pData, pHdr->directorysize ); + + // unicode data + pData = (byte*)pHdr + pHdr->dataoffset; + swap.SwapBufferToTargetEndian( (wchar_t*)pData, (wchar_t*)pData, pHdr->numblocks * pHdr->blocksize / sizeof(wchar_t) ); + + if ( IsPC() ) + { + // post-swap file header + swap.SwapFieldsToTargetEndian( pHdr ); + } + + return true; +} + +#if defined( CLIENT_DLL ) +//----------------------------------------------------------------------------- +// Callback for UpdateOrCreate - generates .360 file +//----------------------------------------------------------------------------- +static bool CaptionCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pExtraData ) +{ + // Generate the file + CUtlBuffer buf; + bool bOk = g_pFullFileSystem->ReadFile( pSourceName, pPathID, buf ); + if ( bOk ) + { + bOk = SwapClosecaptionFile( buf.Base() ); + if ( bOk ) + { + bOk = g_pFullFileSystem->WriteFile( pTargetName, pPathID, buf ); + } + else + { + Warning( "Failed to create %s\n", pTargetName ); + } + } + return bOk; +} + +//----------------------------------------------------------------------------- +// Calls utility function UpdateOrCreate +//----------------------------------------------------------------------------- +int UpdateOrCreateCaptionFile( const char *pSourceName, char *pTargetName, int maxLen, bool bForce ) +{ + return ::UpdateOrCreate( pSourceName, pTargetName, maxLen, "GAME", CaptionCreateCallback, bForce ); +} +#endif \ No newline at end of file diff --git a/common/hl2orange.spa.h b/common/hl2orange.spa.h new file mode 100644 index 00000000..0775e352 --- /dev/null +++ b/common/hl2orange.spa.h @@ -0,0 +1,363 @@ +//////////////////////////////////////////////////////////////////// +// +// hl2orange.spa.h +// +// Auto-generated on Thursday, 13 September 2007 at 16:59:17 +// XLAST project version 1.0.402.0 +// SPA Compiler version 2.0.6274.0 +// +//////////////////////////////////////////////////////////////////// + +#ifndef __THE_ORANGE_BOX_SPA_H__ +#define __THE_ORANGE_BOX_SPA_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +// +// Title info +// + +#define TITLEID_THE_ORANGE_BOX 0x4541080F + +// +// Context ids +// +// These values are passed as the dwContextId to XUserSetContext. +// + +#define CONTEXT_CHAPTER_HL2 0 +#define CONTEXT_SCENARIO 1 +#define CONTEXT_GAME 2 +#define CONTEXT_CHAPTER_EP1 3 +#define CONTEXT_CHAPTER_EP2 4 +#define CONTEXT_CHAPTER_PORTAL 5 + +// +// Context values +// +// These values are passed as the dwContextValue to XUserSetContext. +// + +// Values for CONTEXT_CHAPTER_HL2 + +#define CONTEXT_CHAPTER_HL2_POINT_INSERTION 0 +#define CONTEXT_CHAPTER_HL2_A_RED_LETTER_DAY 1 +#define CONTEXT_CHAPTER_HL2_ROUTE_KANAL 2 +#define CONTEXT_CHAPTER_HL2_WATER_HAZARD 3 +#define CONTEXT_CHAPTER_HL2_BLACK_MESA_EAST 4 +#define CONTEXT_CHAPTER_HL2_RAVENHOLM 5 +#define CONTEXT_CHAPTER_HL2_HIGHWAY_17 6 +#define CONTEXT_CHAPTER_HL2_SANDTRAPS 7 +#define CONTEXT_CHAPTER_HL2_NOVA_PROSPEKT 8 +#define CONTEXT_CHAPTER_HL2_ENTANGLEMENT 9 +#define CONTEXT_CHAPTER_HL2_ANTICITIZEN_ONE 10 +#define CONTEXT_CHAPTER_HL2_FOLLOW_FREEMAN 11 +#define CONTEXT_CHAPTER_HL2_OUR_BENEFACTORS 12 +#define CONTEXT_CHAPTER_HL2_DARK_ENERGY 13 + +// Values for CONTEXT_SCENARIO + +#define CONTEXT_SCENARIO_CTF_2FORT 0 +#define CONTEXT_SCENARIO_CP_DUSTBOWL 1 +#define CONTEXT_SCENARIO_CP_GRANARY 2 +#define CONTEXT_SCENARIO_CP_WELL 3 +#define CONTEXT_SCENARIO_CP_GRAVELPIT 4 +#define CONTEXT_SCENARIO_TC_HYDRO 5 +#define CONTEXT_SCENARIO_CTF_CLOAK 6 +#define CONTEXT_SCENARIO_CP_CLOAK 7 + +// Values for CONTEXT_GAME + +#define CONTEXT_GAME_GAME_HALF_LIFE_2 0 +#define CONTEXT_GAME_GAME_EPISODE_ONE 1 +#define CONTEXT_GAME_GAME_EPISODE_TWO 2 +#define CONTEXT_GAME_GAME_PORTAL 3 +#define CONTEXT_GAME_GAME_TEAM_FORTRESS 4 + +// Values for CONTEXT_CHAPTER_EP1 + +#define CONTEXT_CHAPTER_EP1_UNDUE_ALARM 0 +#define CONTEXT_CHAPTER_EP1_DIRECT_INTERVENTION 1 +#define CONTEXT_CHAPTER_EP1_LOWLIFE 2 +#define CONTEXT_CHAPTER_EP1_URBAN_FLIGHT 3 +#define CONTEXT_CHAPTER_EP1_EXIT_17 4 + +// Values for CONTEXT_CHAPTER_EP2 + +#define CONTEXT_CHAPTER_EP2_TO_THE_WHITE_FOREST 0 +#define CONTEXT_CHAPTER_EP2_THIS_VORTAL_COIL 1 +#define CONTEXT_CHAPTER_EP2_FREEMAN_PONTIFEX 2 +#define CONTEXT_CHAPTER_EP2_RIDING_SHOTGUN 3 +#define CONTEXT_CHAPTER_EP2_UNDER_THE_RADAR 4 +#define CONTEXT_CHAPTER_EP2_OUR_MUTUAL_FIEND 5 +#define CONTEXT_CHAPTER_EP2_T_MINUS_ONE 6 + +// Values for CONTEXT_CHAPTER_PORTAL + +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_00 0 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_04 1 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_08 2 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_10 3 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_13 4 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_14 5 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_15 6 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_16 7 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_17 8 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_18 9 +#define CONTEXT_CHAPTER_PORTAL_TESTCHAMBER_19 10 + +// Values for X_CONTEXT_PRESENCE + +#define CONTEXT_PRESENCE_TF_CP 0 +#define CONTEXT_PRESENCE_TF_CTF_LOSING 1 +#define CONTEXT_PRESENCE_TF_CTF_TIED 2 +#define CONTEXT_PRESENCE_TF_CTF_WINNING 3 +#define CONTEXT_PRESENCE_APPCHOOSER 4 +#define CONTEXT_PRESENCE_MENU 5 +#define CONTEXT_PRESENCE_EP1_INGAME 6 +#define CONTEXT_PRESENCE_HL2_INGAME 7 +#define CONTEXT_PRESENCE_EP2_INGAME 8 +#define CONTEXT_PRESENCE_PORTAL_INGAME 9 +#define CONTEXT_PRESENCE_COMMENTARY 10 +#define CONTEXT_PRESENCE_IDLE 11 + +// Values for X_CONTEXT_GAME_MODE + +#define CONTEXT_GAME_MODE_MULTIPLAYER 0 +#define CONTEXT_GAME_MODE_SINGLEPLAYER 1 + +// +// Property ids +// +// These values are passed as the dwPropertyId value to XUserSetProperty +// and as the dwPropertyId value in the XUSER_PROPERTY structure. +// + +#define PROPERTY_CAPS_OWNED 0x10000000 +#define PROPERTY_CAPS_TOTAL 0x10000001 +#define PROPERTY_PLAYER_TEAM_SCORE 0x10000002 +#define PROPERTY_OPPONENT_TEAM_SCORE 0x10000003 +#define PROPERTY_FLAG_CAPTURE_LIMIT 0x1000000B +#define PROPERTY_NUMBER_OF_ROUNDS 0x1000000C +#define PROPERTY_GAME_SIZE 0x1000000D +#define PROPERTY_AUTOBALANCE 0x1000000E +#define PROPERTY_PRIVATE_SLOTS 0x1000000F +#define PROPERTY_MAX_GAME_TIME 0x10000010 +#define PROPERTY_NUMBER_OF_KILLS 0x10000011 +#define PROPERTY_DAMAGE_DEALT 0x10000012 +#define PROPERTY_PLAY_TIME 0x10000013 +#define PROPERTY_POINT_CAPTURES 0x10000014 +#define PROPERTY_POINT_DEFENSES 0x10000015 +#define PROPERTY_DOMINATIONS 0x10000016 +#define PROPERTY_REVENGE 0x10000017 +#define PROPERTY_BUILDINGS_DESTROYED 0x10000019 +#define PROPERTY_HEADSHOTS 0x1000001A +#define PROPERTY_HEALTH_POINTS_HEALED 0x1000001B +#define PROPERTY_INVULNS 0x1000001C +#define PROPERTY_KILL_ASSISTS 0x1000001D +#define PROPERTY_BACKSTABS 0x1000001E +#define PROPERTY_HEALTH_POINTS_LEACHED 0x1000001F +#define PROPERTY_BUILDINGS_BUILT 0x10000020 +#define PROPERTY_SENTRY_KILLS 0x10000021 +#define PROPERTY_TELEPORTS 0x10000022 +#define PROPERTY_KILLS 0x10000023 +#define PROPERTY_NUMBER_OF_TEAMS 0x10000025 +#define PROPERTY_TEAM_RED 0x10000026 +#define PROPERTY_TEAM_BLUE 0x10000027 +#define PROPERTY_TEAM_SPECTATOR 0x10000028 +#define PROPERTY_TEAM 0x10000029 +#define PROPERTY_WIN_LIMIT 0x1000002A +#define PROPERTY_RANKING_TEST 0x2000000A +#define PROPERTY_POINTS_SCORED 0x20000018 + +// +// Achievement ids +// +// These values are used in the dwAchievementId member of the +// XUSER_ACHIEVEMENT structure that is used with +// XUserWriteAchievements and XUserCreateAchievementEnumerator. +// + +#define ACHIEVEMENT_HLX_KILL_ENEMIES_WITHPHYSICS 43 +#define ACHIEVEMENT_HLX_KILL_ENEMY_WITHHOPPERMINE 44 +#define ACHIEVEMENT_HLX_KILL_ENEMIES_WITHMANHACK 45 +#define ACHIEVEMENT_HLX_KILL_SOLDIER_WITHHISGRENADE 46 +#define ACHIEVEMENT_HLX_KILL_ENEMIES_WITHONEENERGYBALL 47 +#define ACHIEVEMENT_HLX_KILL_ELITESOLDIER_WITHHISENERGYBALL 48 +#define ACHIEVEMENT_EPX_GET_ZOMBINEGRENADE 50 +#define ACHIEVEMENT_EPX_KILL_ZOMBIES_WITHFLARES 51 +#define ACHIEVEMENT_HL2_HIT_CANCOP_WITHCAN 52 +#define ACHIEVEMENT_HL2_PUT_CANINTRASH 53 +#define ACHIEVEMENT_HL2_ESCAPE_APARTMENTRAID 54 +#define ACHIEVEMENT_HL2_BREAK_MINITELEPORTER 55 +#define ACHIEVEMENT_HL2_GET_CROWBAR 56 +#define ACHIEVEMENT_HL2_KILL_BARNACLESWITHBARREL 57 +#define ACHIEVEMENT_HL2_GET_AIRBOAT 58 +#define ACHIEVEMENT_HL2_GET_AIRBOATGUN 60 +#define ACHIEVEMENT_HL2_FIND_VORTIGAUNTCAVE 61 +#define ACHIEVEMENT_HL2_KILL_CHOPPER 62 +#define ACHIEVEMENT_HL2_FIND_HEVFACEPLATE 63 +#define ACHIEVEMENT_HL2_GET_GRAVITYGUN 64 +#define ACHIEVEMENT_HL2_MAKEABASKET 65 +#define ACHIEVEMENT_HL2_BEAT_RAVENHOLM_NOWEAPONS 66 +#define ACHIEVEMENT_HL2_BEAT_CEMETERY 67 +#define ACHIEVEMENT_HL2_KILL_ENEMIES_WITHCRANE 68 +#define ACHIEVEMENT_HL2_PIN_SOLDIER_TOBILLBOARD 69 +#define ACHIEVEMENT_HL2_KILL_ODESSAGUNSHIP 70 +#define ACHIEVEMENT_HL2_KILL_THREEGUNSHIPS 71 +#define ACHIEVEMENT_HL2_BEAT_DONTTOUCHSAND 72 +#define ACHIEVEMENT_HL2_KILL_ENEMIES_WITHANTLIONS 74 +#define ACHIEVEMENT_HL2_KILL_ENEMY_WITHTOILET 75 +#define ACHIEVEMENT_HL2_BEAT_TURRETSTANDOFF2 76 +#define ACHIEVEMENT_HL2_BEAT_TOXICTUNNEL 78 +#define ACHIEVEMENT_HL2_BEAT_PLAZASTANDOFF 79 +#define ACHIEVEMENT_HL2_KILL_ALLC1709SNIPERS 80 +#define ACHIEVEMENT_HL2_BEAT_SUPRESSIONDEVICE 81 +#define ACHIEVEMENT_HL2_BEAT_C1713STRIDERSTANDOFF 82 +#define ACHIEVEMENT_HL2_BEAT_GAME 84 +#define ACHIEVEMENT_HL2_FIND_ALLLAMBDAS 86 +#define ACHIEVEMENT_EP1_BEAT_MAINELEVATOR 87 +#define ACHIEVEMENT_EP1_BEAT_CITADELCORE 88 +#define ACHIEVEMENT_EP1_BEAT_CITADELCORE_NOSTALKERKILLS 89 +#define ACHIEVEMENT_EP1_KILL_ANTLIONS_WITHCARS 90 +#define ACHIEVEMENT_EP1_BEAT_GARAGEELEVATORSTANDOFF 91 +#define ACHIEVEMENT_EP1_KILL_ENEMIES_WITHSNIPERALYX 92 +#define ACHIEVEMENT_EP1_BEAT_HOSPITALATTICGUNSHIP 93 +#define ACHIEVEMENT_EP1_BEAT_CITIZENESCORT_NOCITIZENDEATHS 94 +#define ACHIEVEMENT_EP1_BEAT_GAME 95 +#define ACHIEVEMENT_EP1_BEAT_GAME_ONEBULLET 96 +#define ACHIEVEMENT_EP2_KILL_POISONANTLION 97 +#define ACHIEVEMENT_EP2_KILL_ALLGRUBS 98 +#define ACHIEVEMENT_EP2_BREAK_ALLWEBS 99 +#define ACHIEVEMENT_EP2_BEAT_ANTLIONINVASION 100 +#define ACHIEVEMENT_EP2_BEAT_ANTLIONGUARDS 101 +#define ACHIEVEMENT_EP2_KILL_ENEMIES_WITHCAR 102 +#define ACHIEVEMENT_EP2_BEAT_HUNTERAMBUSH 103 +#define ACHIEVEMENT_EP2_KILL_CHOPPER_NOMISSES 104 +#define ACHIEVEMENT_EP2_KILL_COMBINECANNON 105 +#define ACHIEVEMENT_EP2_FIND_ALLRADARCACHES 106 +#define ACHIEVEMENT_EP2_BEAT_ROCKETCACHEPUZZLE 107 +#define ACHIEVEMENT_EP2_BEAT_RACEWITHDOG 108 +#define ACHIEVEMENT_EP2_BEAT_WHITEFORESTINN 109 +#define ACHIEVEMENT_EP2_PUT_ITEMINROCKET 110 +#define ACHIEVEMENT_EP2_BEAT_MISSILESILO2 111 +#define ACHIEVEMENT_EP2_BEAT_OUTLAND12_NOBUILDINGSDESTROYED 112 +#define ACHIEVEMENT_EP2_BEAT_GAME 113 +#define ACHIEVEMENT_EP2_KILL_HUNTER_WITHFLECHETTES 114 +#define ACHIEVEMENT_PORTAL_GET_PORTALGUNS 115 +#define ACHIEVEMENT_PORTAL_KILL_COMPANIONCUBE 116 +#define ACHIEVEMENT_PORTAL_ESCAPE_TESTCHAMBERS 117 +#define ACHIEVEMENT_PORTAL_BEAT_GAME 118 +#define ACHIEVEMENT_PORTAL_INFINITEFALL 119 +#define ACHIEVEMENT_PORTAL_LONGJUMP 120 +#define ACHIEVEMENT_PORTAL_BEAT_2ADVANCEDMAPS 121 +#define ACHIEVEMENT_PORTAL_BEAT_4ADVANCEDMAPS 122 +#define ACHIEVEMENT_PORTAL_BEAT_6ADVANCEDMAPS 123 +#define ACHIEVEMENT_PORTAL_GET_ALLBRONZE 124 +#define ACHIEVEMENT_PORTAL_GET_ALLSILVER 125 +#define ACHIEVEMENT_PORTAL_GET_ALLGOLD 126 +#define ACHIEVEMENT_TF_GET_TURRETKILLS 127 +#define ACHIEVEMENT_TF_KILL_NEMESIS 128 +#define ACHIEVEMENT_TF_GET_CONSECUTIVEKILLS_NODEATHS 129 +#define ACHIEVEMENT_TF_GET_HEALED_BYENEMY 130 +#define ACHIEVEMENT_TF_PLAY_GAME_FRIENDSONLY 131 +#define ACHIEVEMENT_TF_WIN_MULTIPLEGAMES 132 +#define ACHIEVEMENT_TF_GET_MULTIPLEKILLS 133 +#define ACHIEVEMENT_TF_WIN_2FORT_NOENEMYCAPS 134 +#define ACHIEVEMENT_TF_WIN_WELL_MINIMUMTIME 135 +#define ACHIEVEMENT_TF_WIN_HYDRO_NOENEMYCAPS 136 +#define ACHIEVEMENT_TF_WIN_DUSTBOWL_NOENEMYCAPS 137 +#define ACHIEVEMENT_TF_WIN_GRAVELPIT_NOENEMYCAPS 138 +#define ACHIEVEMENT_TF_PLAY_GAME_EVERYCLASS 139 +#define ACHIEVEMENT_TF_PLAY_GAME_EVERYMAP 140 +#define ACHIEVEMENT_TF_GET_HEALPOINTS 141 +#define ACHIEVEMENT_TF_BURN_PLAYERSINMINIMIMTIME 142 +#define ACHIEVEMENT_HL2_DISINTEGRATE_SOLDIERSINFIELD 143 +#define ACHIEVEMENT_HL2_FOLLOW_FREEMAN 144 +#define ACHIEVEMENT_TF_GET_HEADSHOTS 145 +#define ACHIEVEMENT_PORTAL_DETACH_ALL_CAMERAS 146 +#define ACHIEVEMENT_PORTAL_HIT_TURRET_WITH_TURRET 148 + +// +// Stats view ids +// +// These are used in the dwViewId member of the XUSER_STATS_SPEC structure +// passed to the XUserReadStats* and XUserCreateStatsEnumerator* functions. +// + +// Skill leaderboards for ranked game modes + +#define STATS_VIEW_SKILL_RANKED_MULTIPLAYER 0xFFFF0000 +#define STATS_VIEW_SKILL_RANKED_SINGLEPLAYER 0xFFFF0001 + +// Skill leaderboards for unranked (standard) game modes + +#define STATS_VIEW_SKILL_STANDARD_MULTIPLAYER 0xFFFE0000 +#define STATS_VIEW_SKILL_STANDARD_SINGLEPLAYER 0xFFFE0001 + +// Title defined leaderboards + +#define STATS_VIEW_PLAYER_MAX_UNRANKED 1 +#define STATS_VIEW_PLAYER_MAX_RANKED 2 + +// +// Stats view column ids +// +// These ids are used to read columns of stats views. They are specified in +// the rgwColumnIds array of the XUSER_STATS_SPEC structure. Rank, rating +// and gamertag are not retrieved as custom columns and so are not included +// in the following definitions. They can be retrieved from each row's +// header (e.g., pStatsResults->pViews[x].pRows[y].dwRank, etc.). +// + +// Column ids for PLAYER_MAX_UNRANKED + +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_POINTS_SCORED 2 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_KILLS 3 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_POINTS_CAPPED 1 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_DAMAGE_DEALT 4 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_PLAY_TIME 5 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_POINT_DEFENSES 6 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_DOMINATIONS 7 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_REVENGE 8 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_BUILDINGS_DESTROYED 9 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_HEADSHOTS 10 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_HEALTH_POINTS_HEALED 11 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_INVULNS 12 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_KILL_ASSISTS 13 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_BACKSTABS 14 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_HEALTH_POINTS_LEACHED 15 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_BUILDINGS_BUILT 16 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_SENTRY_KILLS 17 +#define STATS_COLUMN_PLAYER_MAX_UNRANKED_TELEPORTS 18 + +// Column ids for PLAYER_MAX_RANKED + +#define STATS_COLUMN_PLAYER_MAX_RANKED_POINTS_SCORED 2 + +// +// Matchmaking queries +// +// These values are passed as the dwProcedureIndex parameter to +// XSessionSearch to indicate which matchmaking query to run. +// + +#define SESSION_MATCH_QUERY_PLAYER_MATCH 0 + +// +// Gamer pictures +// +// These ids are passed as the dwPictureId parameter to XUserAwardGamerTile. +// + + + +#ifdef __cplusplus +} +#endif + +#endif // __THE_ORANGE_BOX_SPA_H__ + + diff --git a/common/ifilesystemopendialog.h b/common/ifilesystemopendialog.h new file mode 100644 index 00000000..f99fca81 --- /dev/null +++ b/common/ifilesystemopendialog.h @@ -0,0 +1,45 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef IFILESYSTEMOPENDIALOG_H +#define IFILESYSTEMOPENDIALOG_H +#ifdef _WIN32 +#pragma once +#endif + + + +#define FILESYSTEMOPENDIALOG_VERSION "FileSystemOpenDlg003" + + +class IFileSystem; + + +abstract_class IFileSystemOpenDialog +{ +public: + // You must call this first to set the hwnd. + virtual void Init( CreateInterfaceFn factory, void *parentHwnd ) = 0; + + // Call this to free the dialog. + virtual void Release() = 0; + + // Use these to configure the dialog. + virtual void AddFileMask( const char *pMask ) = 0; + virtual void SetInitialDir( const char *pDir, const char *pPathID = NULL ) = 0; + virtual void SetFilterMdlAndJpgFiles( bool bFilter ) = 0; + virtual void GetFilename( char *pOut, int outLen ) const = 0; // Get the filename they chose. + + // Call this to make the dialog itself. Returns true if they clicked OK and false + // if they canceled it. + virtual bool DoModal() = 0; + + // This uses the standard windows file open dialog. + virtual bool DoModal_WindowsDialog() = 0; +}; + + +#endif // IFILESYSTEMOPENDIALOG_H diff --git a/common/randoverride.cpp b/common/randoverride.cpp new file mode 100644 index 00000000..eeb4da8c --- /dev/null +++ b/common/randoverride.cpp @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + +#include "stdlib.h" +#include "vstdlib/random.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef _LINUX +#define __cdecl +#endif + +void __cdecl srand(unsigned int) +{ +} + +int __cdecl rand() +{ + return RandomInt( 0, 0x7fff ); +} + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/common/studiobyteswap.cpp b/common/studiobyteswap.cpp new file mode 100644 index 00000000..fe178566 --- /dev/null +++ b/common/studiobyteswap.cpp @@ -0,0 +1,3133 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: Swaps the bytes in all file types generated by studiomdl +// (.vvd, .vtx, .mdl, .phy, .ani) so the files can be loaded +// on a big-endian machine, specifically the Xbox360. A new file is generated +// with an extra extension in the form of .360. +// +//=============================================================================// + +#include "studio.h" +#include "optimize.h" +#include "phyfile.h" +#include "studiobyteswap.h" +#include "vphysics_interface.h" + +#undef ALIGN16 +#undef ALIGN32 +#define ALIGN4( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3) +#define ALIGN16( a ) a = (byte *)((int)((byte *)a + 15) & ~ 15) +#define ALIGN32( a ) a = (byte *)((int)((byte *)a + 31) & ~ 31) +#define ALIGN64( a ) a = (byte *)((int)((byte *)a + 63) & ~ 63) + +// Fixup macros create variables that may not be referenced +#pragma warning( push ) +#pragma warning( disable:4189 ) // local variable is initialized but not referenced +#pragma warning( disable:4366 ) // The result of the unary '&' operator may be unaligned + +namespace StudioByteSwap +{ + +static bool g_bVerbose = true; +static bool g_bNativeSrc; +static CByteswap g_Swap; +static IPhysicsCollision *pCollision; +static CompressFunc_t g_pCompressFunc; + +void ActivateByteSwapping( bool activate ) +{ + g_Swap.ActivateByteSwapping( activate ); + SourceIsNative( IsPC() ); +} + +void SourceIsNative( bool bNative ) +{ + g_bNativeSrc = bNative; +} + +void SetCollisionInterface( IPhysicsCollision *pPhysicsCollision ) +{ + pCollision = pPhysicsCollision; +} + +void SetVerbose( bool bVerbose ) +{ + g_bVerbose = bVerbose; +} + +//---------------------------------------------------------------------- +// Helper to write a chunk of objects of the same type, and increment the buffer pointers. +//---------------------------------------------------------------------- +template inline void WriteObjects( byte **pOutputBuffer, byte **pBaseData, int objectCount = 1 ) +{ + T tempObject; + for ( int i = 0; i < objectCount; ++i ) + { + Q_memcpy( &tempObject, *pBaseData, sizeof(T) ); + g_Swap.SwapFieldsToTargetEndian( &tempObject, &tempObject ); + Q_memcpy( *pOutputBuffer, &tempObject, sizeof(T) ); + *pOutputBuffer += sizeof(T); + *pBaseData += sizeof(T); + } +} + +//---------------------------------------------------------------------- +// Helper to write a chunk of objects of the same type, and increment the buffer pointers. +//---------------------------------------------------------------------- +template inline void WriteObjects( T **pOutputBuffer, T **pBaseData, int objectCount = 1 ) +{ + T tempObject; + for ( int i = 0; i < objectCount; ++i ) + { + Q_memcpy( &tempObject, *pBaseData, sizeof(T) ); + g_Swap.SwapFieldsToTargetEndian( &tempObject, &tempObject ); + Q_memcpy( *pOutputBuffer, &tempObject, sizeof(T) ); + ++*pOutputBuffer; + ++*pBaseData; + } +} + +//---------------------------------------------------------------------- +// Helper to write a chunk of objects of the same type. +//---------------------------------------------------------------------- +template inline void WriteObjects( byte *pOutputBuffer, byte *pBaseData, int objectCount = 1 ) +{ + T tempObject; + for ( int i = 0; i < objectCount; ++i ) + { + Q_memcpy( &tempObject, pBaseData, sizeof(T) ); + g_Swap.SwapFieldsToTargetEndian( &tempObject, &tempObject ); + Q_memcpy( pOutputBuffer, &tempObject, sizeof(T) ); + pOutputBuffer += sizeof(T); + pBaseData += sizeof(T); + } +} + +//---------------------------------------------------------------------- +// Helper to write a chunk of objects of the same type. +//---------------------------------------------------------------------- +template inline void WriteObjects( T *pOutputBuffer, T *pBaseData, int objectCount = 1 ) +{ + T tempObject; + for ( int i = 0; i < objectCount; ++i ) + { + Q_memcpy( &tempObject, pBaseData, sizeof(T) ); + g_Swap.SwapFieldsToTargetEndian( &tempObject, &tempObject ); + Q_memcpy( pOutputBuffer, &tempObject, sizeof(T) ); + ++pOutputBuffer; + ++pBaseData; + } +} + +//---------------------------------------------------------------------- +// Helper to write a buffer of some integral type, and increment the buffer pointers. +//---------------------------------------------------------------------- +template inline void WriteBuffer( byte **pOutputBuffer, byte **pBaseData, int objectCount = 1 ) +{ + T tempObject; + for ( int i = 0; i < objectCount; ++i ) + { + Q_memcpy( &tempObject, *pBaseData, sizeof(T) ); + g_Swap.SwapBufferToTargetEndian( &tempObject, &tempObject ); + Q_memcpy( *pOutputBuffer, &tempObject, sizeof(T) ); + *pOutputBuffer += sizeof(T); + *pBaseData += sizeof(T); + } +} + +//---------------------------------------------------------------------- +// Helper to write a buffer of some integral type +//---------------------------------------------------------------------- +template inline void WriteBuffer( byte *pOutputBuffer, byte *pBaseData, int objectCount = 1 ) +{ + T tempObject; + for ( int i = 0; i < objectCount; ++i ) + { + Q_memcpy( &tempObject, pBaseData, sizeof(T) ); + g_Swap.SwapBufferToTargetEndian( &tempObject, &tempObject ); + Q_memcpy( pOutputBuffer, &tempObject, sizeof(T) ); + pOutputBuffer += sizeof(T); + pBaseData += sizeof(T); + } +} + +//---------------------------------------------------------------------- +// For getting values in the correct source/dest endian format +//---------------------------------------------------------------------- +template< class T > +T SrcNative( T *idx ) +{ + T ret = *idx; + if ( !g_bNativeSrc ) + { + g_Swap.SwapBuffer( &ret, idx ); + } + return ret; +} + +template< class T > +T DestNative( T *idx ) +{ + T ret = *idx; + if ( g_bNativeSrc ) + { + g_Swap.SwapBuffer( &ret, idx ); + } + return ret; +} + +//---------------------------------------------------------------------- +// Declares objects pointers for src/dest buffer +//---------------------------------------------------------------------- +#define DECLARE_OBJECT_POINTERS( objPtr, base, type ) \ + type* objPtr##Src = (type*)base##Src; \ + type* objPtr##Dest = (type*)base##Dest; \ + type* objPtr = objPtr##Src; + +//---------------------------------------------------------------------- +// Declares src/dest byte pointers and sets them to some index offset in the buffers. +//---------------------------------------------------------------------- +#define DECLARE_INDEX_POINTERS( ptr, base, index ) \ + byte *ptr##Src = (byte*)base##Src + SrcNative( &base##Src->index ); \ + byte *ptr##Dest = (byte*)base##Dest + SrcNative( &base##Src->index ); + +//---------------------------------------------------------------------- +// Declares src/dest byte pointers and sets them to some index offset in the buffers. +// If src pointer is misaligned, the fixup method is called. +//---------------------------------------------------------------------- +#define DECLARE_INDEX_POINTERS_FIXUP( ptr, base, index ) \ + byte *ptr##Src = (byte*)base##Src + SrcNative( &base##Src->index ); \ + byte *ptr##Dest = (byte*)base##Dest + SrcNative( &base##Src->index ); \ + FIXUP_OFFSETS( ptr, base, index ) + +//---------------------------------------------------------------------- +// Same as DECLARE_OBJECT_POINTERS, but reuses existing type pointers. +//---------------------------------------------------------------------- +#define SET_OBJECT_POINTERS( objPtr, base, type ) \ + objPtr##Src = (type*)base##Src; \ + objPtr##Dest = (type*)base##Dest; \ + objPtr = objPtr##Src; + +//---------------------------------------------------------------------- +// Same as DECLARE_INDEX_POINTERS, but reuses existing byte pointers. +//---------------------------------------------------------------------- +#define SET_INDEX_POINTERS( ptr, base, index ) \ + ptr##Src = (byte*)base##Src + SrcNative( &base##Src->index ); \ + ptr##Dest = (byte*)base##Dest + SrcNative( &base##Src->index ); + +//---------------------------------------------------------------------- +// Same as DECLARE_INDEX_POINTERS, but reuses existing byte pointers. +// If src pointer is misaligned, the fixup method is called. +//---------------------------------------------------------------------- +#define SET_INDEX_POINTERS_FIXUP( ptr, base, index ) \ + ptr##Src = (byte*)base##Src + SrcNative( &base##Src->index ); \ + ptr##Dest = (byte*)base##Dest + SrcNative( &base##Src->index ); \ + FIXUP_OFFSETS( ptr, base, index ) + +//---------------------------------------------------------------------- +// for() loop header, updates all three object pointers (src,dest,native) +//---------------------------------------------------------------------- +#define ITERATE_BLOCK( objPtr, count ) \ + for ( int objPtr##_idx = 0; objPtr##_idx < SrcNative( &count ); ++objPtr##_idx, ++objPtr, ++objPtr##Src, ++objPtr##Dest ) + +//---------------------------------------------------------------------- +// Checks for misaligned source pointer, then calculates the necessary fixup, +// calls the fixup function, and sets the src pointer to the new position. +//---------------------------------------------------------------------- +#define FIXUP_OFFSETS( ptr, base, index ) \ + { \ + byte *ptr##Fixup = ptr##Src; \ + ALIGN4( ptr##Fixup ); \ + if ( ptr##Fixup != ptr##Src ) \ + { \ + int nShiftBytes = ptr##Fixup - ptr##Src; \ + if ( g_bVerbose ) \ + Warning( "Shifting misaligned data block by %d bytes at " #base "->" #index "\n", nShiftBytes ); \ + int prevBytes = (byte*)ptr##Src - (byte*)g_pDataSrcBase; \ + Q_memmove( (byte*)ptr##Src + nShiftBytes, ptr##Src, fixedFileSize - prevBytes ); \ + g_pFixPoint = ptr##Src; \ + g_nFixupBytes = nShiftBytes; \ + fixedFileSize += nShiftBytes; \ + if ( fixedFileSize > fileSize + BYTESWAP_ALIGNMENT_PADDING ) \ + { \ + Error( "Byteswap buffer overrun - increase BYTESWAP_ALIGNMENT_PADDING!\n" ); \ + return 0; \ + } \ + g_pfnFileProcessFunc( pHdrSrc, UpdateSrcIndexFields ); \ + g_pFixPoint = NULL; \ + g_nFixupBytes = 0; \ + ptr##Src = ptr##Fixup; \ + } \ + } + + +typedef void ( *datadescProcessFunc_t)( void *pBase, void *pData, typedescription_t *pFields ); +typedef void ( *pfnFixupFunc_t )( void *pDestBase, datadescProcessFunc_t ); + +static pfnFixupFunc_t g_pfnFileProcessFunc; +static studiohdr_t *g_pHdr; +static const void *g_pDataSrcBase; +static void *g_pFixPoint; +static int g_nFixupBytes; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool UpdateIndex( void *pBase, int *indexMember ) +{ + bool bUpdateIndex = false; + int idx = *indexMember; + + // Update the index fields + if ( pBase < g_pFixPoint ) + { + if ( (byte*)pBase + idx >= g_pFixPoint ) + { + bUpdateIndex = true; + } + } + else + { + if ( (byte*)pBase + idx < g_pFixPoint ) + { + bUpdateIndex = true; + } + } + + // Update the member offset by the global fixup + if ( bUpdateIndex && *indexMember ) + { + *indexMember = idx + g_nFixupBytes * Sign(idx); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int GetIntegerFromField( void *pData, int fieldType ) +{ + if ( fieldType == FIELD_INTEGER ) + { + return SrcNative( (int*)pData ); + } + else if ( fieldType == FIELD_SHORT ) + { + return SrcNative( (short*)pData ); + } + Error( "Byteswap macro DEFINE_INDEX using unsupported fieldType %d\n", fieldType ); + return 0; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void PutIntegerInField( void *pData, int index, int fieldType ) +{ + if ( fieldType == FIELD_INTEGER ) + { + *(int*)pData = SrcNative( &index ); + } + else if ( fieldType == FIELD_SHORT ) + { + *(short*)pData = SrcNative( &index ); + } + else + { + Error( "Byteswap macro DEFINE_INDEX using unsupported fieldType %d\n", fieldType ); + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void UpdateSrcIndexFields( void *pBase, void *pData, typedescription_t *pField ) +{ + if ( pField->flags & FTYPEDESC_INDEX ) + { + int index = GetIntegerFromField( pData, pField->fieldType ); + if ( UpdateIndex( pBase, &index ) ) + { + PutIntegerInField( pData, index, pField->fieldType ); + } + } +} + +//----------------------------------------------------------------------------- +// Pass a datadesc field to a processing function +//----------------------------------------------------------------------------- +void ProcessField( void *pBase, void *pData, typedescription_t *pField, datadescProcessFunc_t pfn ) +{ + if ( pfn ) + { + pfn( pBase, pData, pField ); + } +} + +//----------------------------------------------------------------------------- +// Process the fields of a datadesc. +//----------------------------------------------------------------------------- +void ProcessFields( void *pBaseAddress, void *pData, datamap_t *pDataMap, datadescProcessFunc_t pfnProcessFunc ) +{ + // deal with base class first + if ( pDataMap->baseMap ) + { + ProcessFields( pBaseAddress, pData, pDataMap->baseMap, pfnProcessFunc ); + } + + typedescription_t *pFields = pDataMap->dataDesc; + int fieldCount = pDataMap->dataNumFields; + for ( int i = 0; i < fieldCount; ++i ) + { + typedescription_t *pField = &pFields[i]; + ProcessField( pBaseAddress, (BYTE*)pData + pField->fieldOffset[ TD_OFFSET_NORMAL ], pField, pfnProcessFunc ); + } +} + +//----------------------------------------------------------------------------- +// Process the fields of a datadesc. +//----------------------------------------------------------------------------- +void ProcessFields( void *pData, datamap_t *pDataMap, datadescProcessFunc_t pfnProcessFunc ) +{ + ProcessFields( pData, pData, pDataMap, pfnProcessFunc ); +} + +//----------------------------------------------------------------------------- +// Process a datadesc field by name +//----------------------------------------------------------------------------- +void ProcessFieldByName( void *pBaseAddress, void *pData, datamap_t *pDataMap, const char *pName, datadescProcessFunc_t pfnProcessFunc ) +{ + // deal with base class first + if ( pDataMap->baseMap ) + { + ProcessFieldByName( pBaseAddress, pData, pDataMap->baseMap, pName, pfnProcessFunc ); + } + + typedescription_t *pFields = pDataMap->dataDesc; + int fieldCount = pDataMap->dataNumFields; + for ( int i = 0; i < fieldCount; ++i ) + { + typedescription_t *pField = &pFields[i]; + if ( !Q_stricmp( pField->fieldName, pName ) ) + { + ProcessField( pBaseAddress, (BYTE*)pData + pField->fieldOffset[ TD_OFFSET_NORMAL ], pField, pfnProcessFunc ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Process a datadesc field by name. +//----------------------------------------------------------------------------- +void ProcessFieldByName( void *pData, datamap_t *pDataMap, const char *pName, datadescProcessFunc_t pfnProcessFunc ) +{ + ProcessFieldByName( pData, pData, pDataMap, pName, pfnProcessFunc ); +} + +void ProcessANIFields( void *pDataBase, datadescProcessFunc_t pfnProcessFunc ); +void ProcessMDLFields( void *pDataBase, datadescProcessFunc_t pfnProcessFunc ); + +// Fake header declaration for easier phy swapping +struct swapcompactsurfaceheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int size; + int vphysicsID; + short version; + short modelType; + int surfaceSize; + Vector dragAxisAreas; + int axisMapSize; +}; + +BEGIN_BYTESWAP_DATADESC( swapcompactsurfaceheader_t ) + DEFINE_FIELD( size, FIELD_INTEGER ), + DEFINE_FIELD( vphysicsID, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( modelType, FIELD_SHORT ), + DEFINE_FIELD( surfaceSize, FIELD_INTEGER ), + DEFINE_FIELD( dragAxisAreas, FIELD_VECTOR ), + DEFINE_FIELD( axisMapSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +// Fake header declaration for old style phy format +#if defined( _X360 ) +#pragma bitfield_order( push, lsb_to_msb ) +#endif +struct legacysurfaceheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int size; + float mass_center[3]; + float rotation_inertia[3]; + float upper_limit_radius; + BEGIN_BITFIELD( bf ) + int max_deviation : 8; + int byte_size : 24; + END_BITFIELD() + int offset_ledgetree_root; + int dummy[3]; +}; +#if defined( _X360 ) +#pragma bitfield_order( pop ) +#endif + +BEGIN_BYTESWAP_DATADESC( legacysurfaceheader_t ) + DEFINE_FIELD( size, FIELD_INTEGER ), + DEFINE_ARRAY( mass_center, FIELD_FLOAT, 3 ), + DEFINE_ARRAY( rotation_inertia, FIELD_FLOAT, 3 ), + DEFINE_FIELD( upper_limit_radius, FIELD_FLOAT ), + DEFINE_BITFIELD( bf, FIELD_INTEGER, 32 ), + DEFINE_FIELD( offset_ledgetree_root, FIELD_INTEGER ), + DEFINE_ARRAY( dummy, FIELD_INTEGER, 3 ), +END_BYTESWAP_DATADESC() + +//---------------------------------------------------------------------- +// Swap a .phy file +// Fixes alignment errors +//---------------------------------------------------------------------- +int ByteswapPHY( void *pDestBase, const void *pSrcBase, const int fileSize ) +{ + Assert( pCollision ); + if ( !pCollision ) + return 0; + + Q_memset( pDestBase, 0, fileSize ); + + byte *pSrc = (byte*)pSrcBase; + byte *pDest = (byte*)pDestBase; + vcollide_t collide = {0}; + + // file header + phyheader_t *pHdr = (phyheader_t*)( g_bNativeSrc ? pSrc : pDest ); + WriteObjects( &pDest, &pSrc ); + + if ( g_bNativeSrc ) + { + // Reset the pointers and let ivp swap the binary physics data + pSrc = (byte*)pSrcBase + pHdr->size; + pDest = (byte*)pDestBase + pHdr->size; + + int bufSize = fileSize - pHdr->size; + pCollision->VCollideLoad( &collide, pHdr->solidCount, (const char *)pSrc, bufSize, false ); + } + + // Swap the collision data headers + for ( int i = 0; i < pHdr->solidCount; ++i ) + { + swapcompactsurfaceheader_t *baseHdr = (swapcompactsurfaceheader_t*)( g_bNativeSrc ? pSrc : pDest ); + WriteObjects( pDest, pSrc ); + + int srcIncrement = baseHdr->surfaceSize + sizeof(swapcompactsurfaceheader_t); + int destIncrement = srcIncrement; + bool bCopyToSrc = !g_bNativeSrc; + + if ( baseHdr->vphysicsID != MAKEID('V','P','H','Y') ) + { + // May be old phy format + legacysurfaceheader_t *legacyHdr = (legacysurfaceheader_t*)( g_bNativeSrc ? pSrc : pDest ); + WriteObjects( pDest, pSrc ); + if ( legacyHdr->dummy[2] == MAKEID('I','V','P','S') || legacyHdr->dummy[2] == 0 ) + { + srcIncrement = legacyHdr->byte_size + sizeof(int); + destIncrement = legacyHdr->byte_size + sizeof(swapcompactsurfaceheader_t); + bCopyToSrc = false; + + if ( !g_bNativeSrc ) + { + // src needs the size member to be native to load vcollides + Q_memcpy( pSrc, pDest, sizeof(int) ); + } + } + else + { + // Not recognized + Assert(0); + return 0; + } + } + + if ( bCopyToSrc ) + { + // src needs the native header data to load the vcollides + Q_memcpy( pSrc, pDest, sizeof(swapcompactsurfaceheader_t) ); + } + + pSrc += srcIncrement; + pDest += destIncrement; + } + + // the rest of the file is text + int currPos = pSrc - (byte*)pSrcBase; + int remainingBytes = fileSize - currPos; + WriteBuffer( &pDest, &pSrc, remainingBytes ); + + if ( !g_bNativeSrc ) + { + // let ivp swap the ledge tree + pSrc = (byte*)pSrcBase + pHdr->size; + int bufSize = fileSize - pHdr->size; + pCollision->VCollideLoad( &collide, pHdr->solidCount, (const char *)pSrc, bufSize, true ); + } + + // Write out the ledge tree data + pDest = (byte*)pDestBase + pHdr->size; + for ( int i = 0; i < collide.solidCount; ++i ) + { + // skip over the size + pDest += sizeof(int); + int offset = pCollision->CollideWrite( (char*)pDest, collide.solids[i], g_bNativeSrc ); + int destSize = g_bNativeSrc ? SwapLong( offset ) : offset; + Q_memcpy( pDest - sizeof(int), &destSize, sizeof(int) ); + pDest += offset; + } + + // Free the memory + pCollision->VCollideUnload( &collide ); + + int newFileSize = pDest - (byte*)pDestBase + remainingBytes; + + if ( g_pCompressFunc ) + { + // compress entire swapped PHY + void *pInput = pDestBase; + int inputSize = newFileSize; + void *pOutput; + int outputSize; + if ( g_pCompressFunc( pInput, inputSize, &pOutput, &outputSize ) ) + { + // put the compressed version in its place + V_memcpy( pDestBase, pOutput, outputSize ); + free( pOutput ); + newFileSize = outputSize; + } + } + + return newFileSize; +} + +//---------------------------------------------------------------------- +// Swap a .vvd file +// Doesn't do any alignment fixups +//---------------------------------------------------------------------- +int ByteswapVVD( void *pDestBase, const void *pSrcBase, const int fileSize ) +{ + Q_memset( pDestBase, 0, fileSize ); + + byte *pDataSrc = (byte*)pSrcBase; + byte *pDataDest = (byte*)pDestBase; + + /** FILE HEADER **/ + + DECLARE_OBJECT_POINTERS( pHdr, pData, vertexFileHeader_t ) + WriteObjects( &pDataDest, &pDataSrc ); + + /** FIXUP TABLE **/ + + SET_INDEX_POINTERS( pData, pHdr, fixupTableStart ) + WriteObjects( &pDataDest, &pDataSrc, SrcNative( &pHdr->numFixups ) ); + + /** VERTEX DATA **/ + + SET_INDEX_POINTERS( pData, pHdr, vertexDataStart ) + WriteObjects( &pDataDest, &pDataSrc, SrcNative( &pHdr->numLODVertexes[0] ) ); + + /** TANGENT DATA **/ + + if ( pHdr->tangentDataStart != 0 ) + { + SET_INDEX_POINTERS( pData, pHdr, tangentDataStart ) + WriteBuffer( &pDataDest, &pDataSrc, 4 * SrcNative( &pHdr->numLODVertexes[0] ) ); + } + + int newFileSize = pDataDest - (byte*)pDestBase; + + if ( g_pCompressFunc ) + { + void *pInput = (byte*)pDestBase + sizeof( vertexFileHeader_t ); + int inputSize = newFileSize - sizeof( vertexFileHeader_t ); + void *pOutput; + int outputSize; + if ( g_pCompressFunc( pInput, inputSize, &pOutput, &outputSize ) ) + { + // place the compressed data after the header + V_memcpy( pInput, pOutput, outputSize ); + free( pOutput ); + newFileSize = sizeof( vertexFileHeader_t ) + outputSize; + } + } + + return newFileSize; +} + + +//---------------------------------------------------------------------- +// Swap a .vtx file +// Doesn't do any alignment fixups +//---------------------------------------------------------------------- +int ByteswapVTX( void *pDestBase, const void *pSrcBase, const int fileSize ) +{ + Q_memset( pDestBase, 0, fileSize ); + + // Do a straight copy first so the string table is transferred + memcpy( pDestBase, pSrcBase, fileSize ); + + // Start writing the file + byte *pDataSrc = (byte*)pSrcBase; + byte *pDataDest = (byte*)pDestBase; + + DECLARE_OBJECT_POINTERS( pVtxHeader, pData, OptimizedModel::FileHeader_t ) + WriteObjects( pVtxHeaderDest, pVtxHeaderSrc ); + + /** BODY PARTS **/ + + SET_INDEX_POINTERS( pData, pVtxHeader, bodyPartOffset ) + DECLARE_OBJECT_POINTERS( pBodyPartHeader, pData, OptimizedModel::BodyPartHeader_t ) + ITERATE_BLOCK( pBodyPartHeader, pVtxHeader->numBodyParts ) + { + WriteObjects( pBodyPartHeaderDest, pBodyPartHeaderSrc ); + + /** MODELS **/ + + SET_INDEX_POINTERS( pData, pBodyPartHeader, modelOffset ) + DECLARE_OBJECT_POINTERS( pModelHeader, pData, OptimizedModel::ModelHeader_t ) + ITERATE_BLOCK( pModelHeader, pBodyPartHeader->numModels ) + { + WriteObjects( pModelHeaderDest, pModelHeaderSrc ); + + /** MODEL LODS **/ + + unsigned int meshOffset = 0; + SET_INDEX_POINTERS( pData, pModelHeader, lodOffset ) + DECLARE_OBJECT_POINTERS( pModelLODHeader, pData, OptimizedModel::ModelLODHeader_t ) + ITERATE_BLOCK( pModelLODHeader, pModelHeader->numLODs ) + { + WriteObjects( pModelLODHeaderDest, pModelLODHeaderSrc ); + + /** MESHES **/ + + unsigned int prevOffset = meshOffset; + meshOffset = SrcNative( &pModelLODHeader->meshOffset ); + if ( prevOffset - sizeof(OptimizedModel::ModelLODHeader_t) == meshOffset ) + { + // This LOD shares data with the previous LOD - don't reswap. + continue; + } + + SET_INDEX_POINTERS( pData, pModelLODHeader, meshOffset ) + DECLARE_OBJECT_POINTERS( pMeshHeader, pData, OptimizedModel::MeshHeader_t ) + ITERATE_BLOCK( pMeshHeader, pModelLODHeader->numMeshes ) + { + WriteObjects( pMeshHeaderDest, pMeshHeaderSrc ); + + /** STRIP GROUPS **/ + + SET_INDEX_POINTERS( pData, pMeshHeader, stripGroupHeaderOffset ) + DECLARE_OBJECT_POINTERS( pStripGroupHeader, pData, OptimizedModel::StripGroupHeader_t ) + ITERATE_BLOCK( pStripGroupHeader, pMeshHeader->numStripGroups ) + { + WriteObjects( pStripGroupHeaderDest, pStripGroupHeaderSrc ); + + /** STRIP VERTS **/ + + SET_INDEX_POINTERS( pData, pStripGroupHeader, vertOffset ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pStripGroupHeader->numVerts ) ); + + /** VERT INDICES **/ + + SET_INDEX_POINTERS( pData, pStripGroupHeader, indexOffset ) + WriteBuffer( pDataDest, pDataSrc, SrcNative( &pStripGroupHeader->numIndices ) ); + + /** STRIPS **/ + + SET_INDEX_POINTERS( pData, pStripGroupHeader, stripOffset ) + DECLARE_OBJECT_POINTERS( pStripHeader, pData, OptimizedModel::StripHeader_t ) + ITERATE_BLOCK( pStripHeader, pStripGroupHeader->numStrips ) + { + WriteObjects( pStripHeaderDest, pStripHeaderSrc ); + + /** BONE STATE CHANGES **/ + + SET_INDEX_POINTERS( pData, pStripHeader, boneStateChangeOffset ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pStripHeader->numBoneStateChanges ) ); + } + } + } + } + } + } + + /** MATERIAL REPLACEMENT HEADERS **/ + + SET_INDEX_POINTERS( pData, pVtxHeader, materialReplacementListOffset ) + DECLARE_OBJECT_POINTERS( pMatRepListHeader, pData, OptimizedModel::MaterialReplacementListHeader_t ) + ITERATE_BLOCK( pMatRepListHeader, pVtxHeader->numLODs ) + { + WriteObjects( pMatRepListHeaderDest, pMatRepListHeaderSrc ); + + /** MATERIAL REPLACEMENTS **/ + + SET_INDEX_POINTERS( pData, pMatRepListHeader, replacementOffset ) + WriteObjects( &pDataDest, &pDataSrc, SrcNative( &pMatRepListHeader->numReplacements ) ); + } + + int newFileSize = fileSize; + + if ( g_pCompressFunc ) + { + void *pInput = (byte*)pDestBase + sizeof( OptimizedModel::FileHeader_t ); + int inputSize = fileSize - sizeof( OptimizedModel::FileHeader_t ); + void *pOutput; + int outputSize; + if ( g_pCompressFunc( pInput, inputSize, &pOutput, &outputSize ) ) + { + // place the compressed data after the header + V_memcpy( pInput, pOutput, outputSize ); + free( pOutput ); + newFileSize = sizeof( OptimizedModel::FileHeader_t ) + outputSize; + } + } + + return newFileSize; +} + +//---------------------------------------------------------------------- +// Swap animation data +// Fixes alignment errors +//---------------------------------------------------------------------- + +void ByteswapAnimData( mstudioanimdesc_t *pAnimDesc, int section, byte *&pDataSrc, byte *&pDataDest ) +{ + /** ANIMATIONS **/ + DECLARE_OBJECT_POINTERS( pAnimation, pData, mstudioanim_t ) + WriteObjects( pAnimationDest, pAnimationSrc ); + if ( pAnimation->bone == 255 ) + { + // No animation data + pAnimation = 0; + } + + while( pAnimation ) + { + if ( pAnimation->flags & ( STUDIO_ANIM_RAWROT | STUDIO_ANIM_RAWPOS | STUDIO_ANIM_RAWROT2 ) ) + { + if ( pAnimation->flags & STUDIO_ANIM_RAWROT ) + { + int offset = (byte*)pAnimation->pQuat48() - (byte*)pAnimation; + pDataSrc = (byte*)pAnimationSrc + offset; + pDataDest = (byte*)pAnimationDest + offset; + + // Write the quaternion (bit fields contained in 3 unsigned shorts) + WriteBuffer( &pDataDest, &pDataSrc, 3 ); + } + + if ( pAnimation->flags & STUDIO_ANIM_RAWROT2 ) + { + int offset = (byte*)pAnimation->pQuat64() - (byte*)pAnimation; + pDataSrc = (byte*)pAnimationSrc + offset; + pDataDest = (byte*)pAnimationDest + offset; + + // Write the quaternion (bit fields contained in 1 64 bit int + WriteBuffer( &pDataDest, &pDataSrc, 1 ); + } + + if ( pAnimation->flags & STUDIO_ANIM_RAWPOS ) + { + int offset = (byte*)pAnimation->pPos() - (byte*)pAnimation; + pDataSrc = (byte*)pAnimationSrc + offset; + pDataDest = (byte*)pAnimationDest + offset; + + // Write the vector (3 float16) + WriteBuffer( &pDataDest, &pDataSrc, 3 ); + } + } + else + { + int offset = (byte*)pAnimation->pRotV() - (byte*)pAnimation; + pDataSrc = (byte*)pAnimationSrc + offset; + pDataDest = (byte*)pAnimationDest + offset; + + mstudioanim_valueptr_t *rotvptr = (mstudioanim_valueptr_t*)pDataSrc; + WriteObjects( &pDataDest, &pDataSrc ); + + int animValueCt = 0; + for ( int idx = 0; idx < 3; ++idx ) + { + animValueCt += rotvptr->offset[idx] ? 1 : 0; + } + + if ( pAnimation->flags & STUDIO_ANIM_ANIMPOS ) + { + int offset = (byte*)pAnimation->pPosV() - (byte*)pAnimation; + pDataSrc = (byte*)pAnimationSrc + offset; + pDataDest = (byte*)pAnimationDest + offset; + + mstudioanim_valueptr_t *posvptr = (mstudioanim_valueptr_t*)pDataSrc; + WriteObjects( &pDataDest, &pDataSrc ); + + for ( int idx = 0; idx < 3; ++idx ) + { + animValueCt += posvptr->offset[idx] ? 1 : 0; + } + } + + // Write position and rotation animations + + // Note: destanimvalue_t is a union that can be either two bytes or a short. + // This structure is used to compress animation data using RLE. + // The first object of a chunk acts as the header, and uses the two bytes to + // store how many objects follow, and how many frames are encoded by them. + // The objects that follow use the short to store a value. + // The total number of chunks has been determined by counting the number of valid (non-zero) offsets. + for ( int animValue = 0; animValue < animValueCt; ++animValue ) + { + int encodedFrames = 0; + int totalFrames = SrcNative( &pAnimDesc->numframes ); + int sectionFrames = SrcNative( &pAnimDesc->sectionframes ); + if ( sectionFrames ) + { + int iStartFrame = section * sectionFrames; + int iEndFrame = (section + 1) * sectionFrames; + + iStartFrame = min( iStartFrame, totalFrames - 1 ); + iEndFrame = min( iEndFrame, totalFrames - 1 ); + + totalFrames = iEndFrame - iStartFrame + 1; + } + + while ( encodedFrames < totalFrames ) + { + // Write the first animation value (struct of 2 bytes) + mstudioanimvalue_t *pDestAnimvalue = (mstudioanimvalue_t*)( g_bNativeSrc ? pDataSrc : pDataDest ); + WriteBuffer( &pDataDest, &pDataSrc, 2 ); + + // Write the remaining animation values from this group (shorts) + WriteBuffer( &pDataDest, &pDataSrc, pDestAnimvalue->num.valid ); + + encodedFrames += pDestAnimvalue->num.total; + } + } + } + + // TODOKD: Could add a fixup here with some more work, hasn't been necessary yet + if ( pAnimation->nextoffset ) + { + // Set pointers to the next animation + pAnimationSrc = (mstudioanim_t*)( (byte*)pAnimationSrc + SrcNative( &pAnimation->nextoffset ) ); + pAnimationDest = (mstudioanim_t*)( (byte*)pAnimationDest + SrcNative( &pAnimation->nextoffset ) ); + pAnimation = pAnimationSrc; + + // Swap the next animation + WriteObjects( pAnimationDest, pAnimationSrc ); + } + else + { + pAnimation = 0; + pDataSrc += sizeof( mstudioanim_t ); + pDataDest += sizeof( mstudioanim_t ); + } + } + + ALIGN4( pDataSrc ); + ALIGN4( pDataDest ); +} + + +int ByteswapIKRules( studiohdr_t *&pHdrSrc, int numikrules, int numFrames, byte *&pDataSrc, byte *&pDataDest, int &fixedFileSize, const int fileSize ) +{ + DECLARE_OBJECT_POINTERS( pIKRule, pData, mstudioikrule_t ) + + ITERATE_BLOCK( pIKRule, numikrules ) + { + WriteObjects( pIKRuleDest, pIKRuleSrc ); + + /** IK ERROR KEYS **/ + + // Calculate the number of ikerrors by converting the ikerror start and end float values to + // frame numbers. (See the generation of these values in simplify.cpp: ProcessIKRules()). + float start = floorf( SrcNative( &pIKRule->start ) * (numFrames - 1) + 0.5f ); + float end = floorf( SrcNative( &pIKRule->end ) * (numFrames - 1) + 0.5f ); + int totalerror = (int)( end - start + 1 ); + if ( end >= numFrames ) + totalerror += 2; + + // Uncompressed - only found in some older models (shipped hl2) + if ( pIKRule->ikerrorindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pIKRule, ikerrorindex ) + WriteObjects( pDataDest, pDataSrc, totalerror ); + } + + // Compressed - all models since hl2 + if ( pIKRule->compressedikerrorindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pIKRule, compressedikerrorindex ) + WriteObjects( pDataDest, pDataSrc ); + + mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pDataSrc; + + // Write the animvalues. + for ( int idx = 0; idx < 6; ++idx ) + { + if ( pCompressed->offset[idx] ) + { + byte *pAnimvalueSrc = pDataSrc + SrcNative( &pCompressed->offset[idx] ); + byte *pAnimvalueDest = pDataDest + SrcNative( &pCompressed->offset[idx] ); + + int numerror = 0; + while ( numerror < totalerror ) + { + // Write the first animation value (struct of 2 bytes) + mstudioanimvalue_t *pDestAnimvalue = (mstudioanimvalue_t*)( g_bNativeSrc ? pAnimvalueSrc : pAnimvalueDest ); + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, 2 ); + + // Write the remaining animation values from this group (shorts) + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, pDestAnimvalue->num.valid ); + + numerror += pDestAnimvalue->num.total; + } + } + } + + if ( pIKRule->szattachmentindex ) + { + SET_INDEX_POINTERS( pData, pIKRule, szattachmentindex ) + int size = strlen( (char*)pDataSrc ) + 1; + WriteBuffer( pDataDest, pDataSrc, size ); + } + } + } + return fixedFileSize; +} + + + +//---------------------------------------------------------------------- +// Swap an .ani file +// Fixes alignment errors +//---------------------------------------------------------------------- +int ByteswapANIFile( studiohdr_t* pHdr, void *pDestBase, const void *pSrcBase, const int fileSize ) +{ + // Note, pHdr came from a native .mdl - + // so the header, animdescs and animblocks are already in native format. + Assert( pHdr ); + if ( !pHdr ) + return false; + + Q_memset( pDestBase, 0, fileSize ); + + // swap file header + { + byte *pHeaderSrc = (byte *)pSrcBase; + byte *pHeaderDest = (byte *)pDestBase; + DECLARE_OBJECT_POINTERS( pAniHeader, pHeader, studiohdr_t ) + WriteObjects( pAniHeaderDest, pAniHeaderSrc ); + } + + // for fixup functions + int fixedFileSize = fileSize; + g_pfnFileProcessFunc = ProcessANIFields; + g_pDataSrcBase = pSrcBase; + g_pHdr = pHdr; + studiohdr_t *pHdrSrc = pHdr; + + // The animdesc_t header is always contained in the mdl file, but its data may be in + // the mdl or the ani. When the data is contained in the mdl, the animdesc index fields + // represent offsets from the location of the animdesc header. When the data is in the ani, + // the index fields contain offsets from the start of the animblock in which the animdesc data is contained. + + mstudioanimdesc_t *pAnimDesc = pHdr->pLocalAnimdesc( 0 ); + for ( int i = 0; i < pHdr->numlocalanim; ++i, ++pAnimDesc ) + { + // printf("anim %d : %d : %d\n", i, pAnimDesc->animblock, pAnimDesc->sectionframes ); + if ( pAnimDesc->animblock == -1) + { + // out of date model format + continue; + } + + if ( pAnimDesc->animblock == 0 && pAnimDesc->sectionframes == 0) + { + // already saved out + continue; + } + + if ( pAnimDesc->sectionframes == 0 ) + { + mstudioanimblock_t *pAnimBlock = pHdr->pAnimBlock( pAnimDesc->animblock ); + + // printf("block %d : start %d + %d\n", pAnimDesc->animblock, pAnimBlock->datastart, pAnimDesc->animindex ); + // Base address of the animblock + byte *pBlockBaseSrc = (byte*)pSrcBase + pAnimBlock->datastart; + byte *pBlockBaseDest = (byte*)pDestBase + pAnimBlock->datastart; + + // Base address of the animation in the animblock + byte *pDataSrc = pBlockBaseSrc + pAnimDesc->animindex; + byte *pDataDest = pBlockBaseDest + pAnimDesc->animindex; + + ByteswapAnimData( pAnimDesc, 0, pDataSrc, pDataDest ); + } + else + { + int numsections = pAnimDesc->numframes / pAnimDesc->sectionframes + 2; + + for ( int i = 0; i < numsections; ++i ) + { + int block = pAnimDesc->pSection( i )->animblock; + int index = pAnimDesc->pSection( i )->animindex; + + if ( block != 0 ) + { + // printf("%s %d %d\n", pAnimDesc->pszName(), block, index ); + + mstudioanimblock_t *pAnimBlock = pHdr->pAnimBlock( block ); + + // Base address of the animblock + byte *pBlockBaseSrc = (byte*)pSrcBase + pAnimBlock->datastart; + byte *pBlockBaseDest = (byte*)pDestBase + pAnimBlock->datastart; + FIXUP_OFFSETS( pBlockBase, pAnimBlock, datastart ) + + // Base address of the animation in the animblock + byte *pDataSrc = pBlockBaseSrc + index; + byte *pDataDest = pBlockBaseDest + index; + + ByteswapAnimData( pAnimDesc, i, pDataSrc, pDataDest ); + } + } + } + + if ( pAnimDesc->animblock == 0) + { + // already saved out + continue; + } + + mstudioanimblock_t *pAnimBlock = pHdr->pAnimBlock( pAnimDesc->animblock ); + // Base address of the animblock + byte *pBlockBaseSrc = (byte*)pSrcBase + pAnimBlock->datastart; + byte *pBlockBaseDest = (byte*)pDestBase + pAnimBlock->datastart; + FIXUP_OFFSETS( pBlockBase, pAnimBlock, datastart ) + + // Base address of the animation in the animblock + byte *pDataSrc = pBlockBaseSrc + pAnimDesc->animindex; + byte *pDataDest = pBlockBaseDest + pAnimDesc->animindex; + FIXUP_OFFSETS( pData, pAnimDesc, animindex ) + + /** IK RULES **/ + + if ( pAnimDesc->animblockikruleindex ) + { + pDataSrc = (byte*)pBlockBaseSrc + pAnimDesc->animblockikruleindex; + pDataDest = (byte*)pBlockBaseDest + pAnimDesc->animblockikruleindex; + FIXUP_OFFSETS( pData, pAnimDesc, animblockikruleindex ) + + int numikrules = SrcNative( &pAnimDesc->numikrules ); + ByteswapIKRules( pHdrSrc, pAnimDesc->numikrules, pAnimDesc->numframes, pDataSrc, pDataDest, fixedFileSize, fileSize ); + } + + /** LOCAL HIERARCHY **/ + + if ( pAnimDesc->localhierarchyindex ) + { + pDataSrc = (byte*)pBlockBaseSrc + pAnimDesc->localhierarchyindex; + pDataDest = (byte*)pBlockBaseDest + pAnimDesc->localhierarchyindex; + DECLARE_OBJECT_POINTERS( pLocalHierarchy, pData, mstudiolocalhierarchy_t ) + + // HACK: Since animdescs are already native, pre-swap pAnimDesc->numlocalhierarchy + // here so the automatic swap inside ITERATE_BLOCK will restore it + int numlocalhierarchy = SrcNative( &pAnimDesc->numlocalhierarchy ); + ITERATE_BLOCK( pLocalHierarchy, numlocalhierarchy ) + { + WriteObjects( pLocalHierarchyDest, pLocalHierarchySrc, pAnimDesc->numlocalhierarchy ); + + /** COMPRESSED IK ERRORS **/ + + if ( pLocalHierarchy->localanimindex != 0 ) + { + // Calculate the number of ikerrors by converting the ikerror start and end float values to + // frame numbers. (See the generation of these values in simplify.cpp: ProcessIKRules()). + int numFrames = pAnimDesc->numframes; + float start = floorf( SrcNative( &pLocalHierarchy->start ) * (numFrames - 1) + 0.5f ); + float end = floorf( SrcNative( &pLocalHierarchy->end ) * (numFrames - 1) + 0.5f ); + int totalerror = (int)( end - start + 1 ); + if ( end >= numFrames ) + totalerror += 2; + + SET_INDEX_POINTERS( pData, pLocalHierarchy, localanimindex ) + WriteObjects( pDataDest, pDataSrc ); + + mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pDataSrc; + + // Write the animvalues. + for ( int idx = 0; idx < 6; ++idx ) + { + if ( pCompressed->offset[idx] ) + { + byte *pAnimvalueSrc = pDataSrc + SrcNative( &pCompressed->offset[idx] ); + byte *pAnimvalueDest = pDataDest + SrcNative( &pCompressed->offset[idx] ); + + int numerror = 0; + while ( numerror < totalerror ) + { + // Write the first animation value (struct of 2 bytes) + mstudioanimvalue_t *pDestAnimvalue = (mstudioanimvalue_t*)( g_bNativeSrc ? pAnimvalueSrc : pAnimvalueDest ); + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, 2 ); + + // Write the remaining animation values from this group (shorts) + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, pDestAnimvalue->num.valid ); + + numerror += pDestAnimvalue->num.total; + } + } + } + } + } // Local Hierarchy block + } + } + + // printf("returning %d\n", fixedFileSize ); + + return fixedFileSize; +} + +int ByteswapANI( studiohdr_t* pHdr, void *pDestBase, const void *pSrcBase, const int fileSize ) +{ + // Make a working copy of the source to allow for alignment fixups + void *pNewSrcBase = malloc( fileSize + BYTESWAP_ALIGNMENT_PADDING ); + Q_memcpy( pNewSrcBase, pSrcBase, fileSize ); + + int fixedFileSize = ByteswapANIFile( pHdr, pDestBase, pNewSrcBase, fileSize ); + if ( fixedFileSize != fileSize ) + { + int finalSize = ByteswapANIFile( pHdr, pDestBase, pNewSrcBase, fixedFileSize ); + if ( finalSize != fixedFileSize ) + { + if ( g_bVerbose ) + Warning( "Alignment fixups failed on ANI swap!\n" ); + fixedFileSize = 0; + } + } + + free( pNewSrcBase ); + + // the compression needs to happen on the final or "fixed" pass + if ( g_pCompressFunc && pHdr->numanimblocks >= 2 && fixedFileSize ) + { + // assemble a new anim of compressed anim blocks + // start with original size, with room for alignment padding + fixedFileSize += (pHdr->numanimblocks + 1) * 2048; + byte *pNewDestBase = (byte *)malloc( fixedFileSize ); + Q_memset( pNewDestBase, 0, fixedFileSize ); + byte *pNewDest = pNewDestBase; + + // get the header payload as is + // assuming the header is up to the first anim block + mstudioanimblock_t *pAnimBlock = pHdr->pAnimBlock( 1 ); + V_memcpy( pNewDest, pDestBase, pAnimBlock->datastart ); + pNewDest += pAnimBlock->datastart; + + int padding = AlignValue( (unsigned int)pNewDest - (unsigned int)pNewDestBase, 2048 ); + padding -= (unsigned int)pNewDest - (unsigned int)pNewDestBase; + pNewDest += padding; + + // iterate and compress anim blocks + for ( int i = 1; i < pHdr->numanimblocks; ++i ) + { + pAnimBlock = pHdr->pAnimBlock( i ); + + void *pInput = (byte *)pDestBase + pAnimBlock->datastart; + int inputSize = pAnimBlock->dataend - pAnimBlock->datastart; + + pAnimBlock->datastart = (unsigned int)pNewDest - (unsigned int)pNewDestBase; + + void *pOutput; + int outputSize; + if ( g_pCompressFunc( pInput, inputSize, &pOutput, &outputSize ) ) + { + V_memcpy( pNewDest, pOutput, outputSize ); + pNewDest += outputSize; + free( pOutput ); + } + else + { + // as is + V_memcpy( pNewDest, pInput, inputSize ); + pNewDest += inputSize; + } + + padding = AlignValue( (unsigned int)pNewDest - (unsigned int)pNewDestBase, 2048 ); + padding -= (unsigned int)pNewDest - (unsigned int)pNewDestBase; + pNewDest += padding; + + pAnimBlock->dataend = (unsigned int)pNewDest - (unsigned int)pNewDestBase; + } + + fixedFileSize = pNewDest - pNewDestBase; + V_memcpy( pDestBase, pNewDestBase, fixedFileSize ); + free( pNewDestBase ); + } + + return fixedFileSize; +} + +//---------------------------------------------------------------------- +// Write a .mdl file in big-endian format +//---------------------------------------------------------------------- +int ByteswapMDLFile( void *pDestBase, void *pSrcBase, const int fileSize ) +{ + // Needed by fixup functions + g_pDataSrcBase = pSrcBase; + g_pfnFileProcessFunc = ProcessMDLFields; + int fixedFileSize = fileSize; + + Q_memset( pDestBase, 0, fileSize ); + + byte *pDataSrc = (byte*)pSrcBase; + byte *pDataDest = (byte*)pDestBase; + + /** FILE HEADER **/ + + DECLARE_OBJECT_POINTERS( pHdr, pData, studiohdr_t ) + WriteObjects( pHdrDest, pHdrSrc ); + + /** BONES **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, boneindex ) + DECLARE_OBJECT_POINTERS( pStudioBone, pData, mstudiobone_t ) + ITERATE_BLOCK( pStudioBone, pHdr->numbones ) + { + WriteObjects( pStudioBoneDest, pStudioBoneSrc ); + + if ( pStudioBone->procindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pStudioBone, procindex ) + + unsigned int index = SrcNative( &pStudioBone->proctype ); + switch( index ) + { + case STUDIO_PROC_AXISINTERP: + { + /** AXIS-INTERP BONES **/ + DECLARE_OBJECT_POINTERS( pAxisInterpBone, pData, mstudioaxisinterpbone_t ) + WriteObjects( pAxisInterpBoneDest, pAxisInterpBoneSrc ); + break; + } + case STUDIO_PROC_QUATINTERP: + { + /** QUAT-INTERP BONES **/ + DECLARE_OBJECT_POINTERS( pQuatInterpBone, pData, mstudioquatinterpbone_t ) + WriteObjects( pQuatInterpBoneDest, pQuatInterpBoneSrc ); + + /** QUAT-INTERP TRIGGERS **/ + SET_INDEX_POINTERS_FIXUP( pData, pQuatInterpBone, triggerindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pQuatInterpBone->numtriggers ) ); + break; + } + case STUDIO_PROC_JIGGLE: + { + /** JIGGLE BONES **/ + DECLARE_OBJECT_POINTERS( pJiggleBone, pData, mstudiojigglebone_t ) + WriteObjects( pJiggleBoneDest, pJiggleBoneSrc ); + break; + } + case STUDIO_PROC_AIMATBONE: + case STUDIO_PROC_AIMATATTACH: + { + /** AIM AT BONES **/ + DECLARE_OBJECT_POINTERS( pAimAtBone, pData, mstudioaimatbone_t ) + WriteObjects( pAimAtBoneDest, pAimAtBoneSrc ); + break; + } + default: + Assert( 0 ); + Warning( "Unknown bone type %d found!\n", index ); + } + } + } + + /** BONE CONTROLLERS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, bonecontrollerindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numbonecontrollers ) ); + + /** ATTACHMENTS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, localattachmentindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numlocalattachments ) ); + + /** HITBOX SETS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, hitboxsetindex ) + DECLARE_OBJECT_POINTERS( pHitboxSet, pData, mstudiohitboxset_t ) + ITERATE_BLOCK( pHitboxSet, pHdr->numhitboxsets ) + { + WriteObjects( pHitboxSetDest, pHitboxSetSrc ); + + /** HITBOXES **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHitboxSet, hitboxindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHitboxSet->numhitboxes ) ); + } + + /** BONE TABLE **/ + + SET_INDEX_POINTERS( pData, pHdr, bonetablebynameindex ) + WriteBuffer( pDataDest, pDataSrc, SrcNative( &pHdr->numbones ) ); + + /** ANIMATION DESCRIPTIONS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, localanimindex ) + DECLARE_OBJECT_POINTERS( pAnimDesc, pData, mstudioanimdesc_t ) + ITERATE_BLOCK( pAnimDesc, pHdr->numlocalanim ) + { + WriteObjects( pAnimDescDest, pAnimDescSrc ); + + if ( pAnimDesc->animblock == -1 ) + { + // out of date model format + continue; + } + + // section data can point to both internal and external blocks + int numsections = 0; + if ( pAnimDesc->sectionframes != 0 ) + { + numsections = pAnimDesc->numframes / pAnimDesc->sectionframes + 2; + + SET_INDEX_POINTERS( pData, pAnimDesc, sectionindex ) + DECLARE_OBJECT_POINTERS( pSection, pData, mstudioanimsections_t ) + + WriteObjects( pSectionDest, pSectionSrc, numsections ); + } + + if ( pAnimDesc->animblock == 0 ) + { + if ( numsections == 0 ) + { + SET_INDEX_POINTERS( pData, pAnimDesc, animindex ) + ByteswapAnimData( pAnimDesc, 0, pDataSrc, pDataDest ); + } + else + { + for ( int i = 0; i < numsections; ++i ) + { + if ( pAnimDesc->pSection( i )->animblock == 0 ) + { + int index = pAnimDesc->pSection( i )->animindex; + + // Base address of the animation in the animblock + byte *pDataSrc = (byte *)pAnimDescSrc + index; + byte *pDataDest = (byte *)pAnimDescDest + index; + + ByteswapAnimData( pAnimDesc, i, pDataSrc, pDataDest ); + } + } + } + + /** IK RULES **/ + + if ( pAnimDesc->ikruleindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pAnimDesc, ikruleindex ) + DECLARE_OBJECT_POINTERS( pIKRule, pData, mstudioikrule_t ) + + int numframes = SrcNative( &pAnimDesc->numframes ); + ByteswapIKRules( pHdrSrc, pAnimDesc->numikrules, numframes, pDataSrc, pDataDest, fixedFileSize, fileSize ); + } + + /** LOCAL HIERARCHY **/ + + if ( pAnimDesc->localhierarchyindex ) + { + SET_INDEX_POINTERS( pData, pAnimDesc, localhierarchyindex ) + DECLARE_OBJECT_POINTERS( pLocalHierarchy, pData, mstudiolocalhierarchy_t ) + ITERATE_BLOCK( pLocalHierarchy, pAnimDesc->numlocalhierarchy ) + { + WriteObjects( pLocalHierarchyDest, pLocalHierarchySrc, SrcNative( &pAnimDesc->numlocalhierarchy ) ); + + /** COMPRESSED IK ERRORS **/ + + if ( pLocalHierarchy->localanimindex != 0 ) + { + // Calculate the number of ikerrors by converting the ikerror start and end float values to + // frame numbers. (See the generation of these values in simplify.cpp: ProcessIKRules()). + int numFrames = SrcNative( &pAnimDesc->numframes ); + float start = floorf( SrcNative( &pLocalHierarchy->start ) * (numFrames - 1) + 0.5f ); + float end = floorf( SrcNative( &pLocalHierarchy->end ) * (numFrames - 1) + 0.5f ); + int totalerror = (int)( end - start + 1 ); + if ( end >= numFrames ) + totalerror += 2; + + SET_INDEX_POINTERS( pData, pLocalHierarchy, localanimindex ) + WriteObjects( pDataDest, pDataSrc ); + + mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pDataSrc; + + // Write the animvalues. + for ( int idx = 0; idx < 6; ++idx ) + { + if ( pCompressed->offset[idx] ) + { + byte *pAnimvalueSrc = pDataSrc + SrcNative( &pCompressed->offset[idx] ); + byte *pAnimvalueDest = pDataDest + SrcNative( &pCompressed->offset[idx] ); + + int numerror = 0; + while ( numerror < totalerror ) + { + // Write the first animation value (struct of 2 bytes) + mstudioanimvalue_t *pDestAnimvalue = (mstudioanimvalue_t*)( g_bNativeSrc ? pAnimvalueSrc : pAnimvalueDest ); + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, 2 ); + + // Write the remaining animation values from this group (shorts) + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, pDestAnimvalue->num.valid ); + + numerror += pDestAnimvalue->num.total; + } + } + } + } + } + } // Local Hierarchy block + } + } // Animdesc block + + /** MOVEMENTS **/ + + // Separate loop required by format of mstudioanimdesc_t data + SET_INDEX_POINTERS( pData, pHdr, localanimindex ) + SET_OBJECT_POINTERS( pAnimDesc, pData, mstudioanimdesc_t ) + ITERATE_BLOCK( pAnimDesc, pHdr->numlocalanim ) + { + if ( pAnimDesc->nummovements ) + { + SET_INDEX_POINTERS_FIXUP( pData, pAnimDesc, movementindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pAnimDesc->nummovements ) ); + } + } + + /** IK RULES (2nd pass) **/ + + // This is required to support older models that had these lumps in a different + // order - the model version didn't change, so there's no reliable way to detect it. + SET_INDEX_POINTERS( pData, pHdr, localanimindex ) + SET_OBJECT_POINTERS( pAnimDesc, pData, mstudioanimdesc_t ) + ITERATE_BLOCK( pAnimDesc, pHdr->numlocalanim ) + { + if ( pAnimDesc->ikruleindex ) + { + // Only need to write the data again if a fixup happens + byte *pTest = (byte*)pAnimDesc + SrcNative( &pAnimDesc->ikruleindex ); + SET_INDEX_POINTERS_FIXUP( pData, pAnimDesc, ikruleindex ) + if ( pTest == pDataSrc ) + continue; + + DECLARE_OBJECT_POINTERS( pIKRule, pData, mstudioikrule_t ) + ITERATE_BLOCK( pIKRule, pAnimDesc->numikrules ) + { + WriteObjects( pIKRuleDest, pIKRuleSrc ); + + /** IK ERROR KEYS **/ + + // Calculate the number of ikerrors by converting the ikerror start and end float values to + // frame numbers. (See the generation of these values in simplify.cpp: ProcessIKRules()). + int numFrames = SrcNative( &pAnimDesc->numframes ); + float start = floorf( SrcNative( &pIKRule->start ) * (numFrames - 1) + 0.5f ); + float end = floorf( SrcNative( &pIKRule->end ) * (numFrames - 1) + 0.5f ); + int totalerror = (int)( end - start + 1 ); + if ( end >= numFrames ) + totalerror += 2; + + // Uncompressed - only found in some older models (shipped hl2) + if ( pIKRule->ikerrorindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pIKRule, ikerrorindex ) + WriteObjects( pDataDest, pDataSrc, totalerror ); + } + + // Compressed - all models since hl2 + if ( pIKRule->compressedikerrorindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pIKRule, compressedikerrorindex ) + WriteObjects( pDataDest, pDataSrc ); + + mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pDataSrc; + + // Write the animvalues. + for ( int idx = 0; idx < 6; ++idx ) + { + if ( pCompressed->offset[idx] ) + { + byte *pAnimvalueSrc = pDataSrc + SrcNative( &pCompressed->offset[idx] ); + byte *pAnimvalueDest = pDataDest + SrcNative( &pCompressed->offset[idx] ); + + int numerror = 0; + while ( numerror < totalerror ) + { + // Write the first animation value (struct of 2 bytes) + mstudioanimvalue_t *pDestAnimvalue = (mstudioanimvalue_t*)( g_bNativeSrc ? pAnimvalueSrc : pAnimvalueDest ); + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, 2 ); + + // Write the remaining animation values from this group (shorts) + WriteBuffer( &pAnimvalueDest, &pAnimvalueSrc, pDestAnimvalue->num.valid ); + + numerror += pDestAnimvalue->num.total; + } + } + } + + if ( pIKRule->szattachmentindex ) + { + SET_INDEX_POINTERS( pData, pIKRule, szattachmentindex ) + int size = strlen( (char*)pDataSrc ) + 1; + WriteBuffer( pDataDest, pDataSrc, size ); + } + } + } + } + + // Local hierarchy keys don't exist in older models + } + + /** ZERO FRAMES **/ + + SET_INDEX_POINTERS( pData, pHdr, localanimindex ) + SET_OBJECT_POINTERS( pAnimDesc, pData, mstudioanimdesc_t ) + ITERATE_BLOCK( pAnimDesc, pHdr->numlocalanim ) + { + if ( pAnimDesc->pZeroFrameData( ) != NULL ) + { + int offset = pAnimDesc->pZeroFrameData( ) - (byte *)pAnimDesc; + + // Base address of the animation in the animblock + byte *pZeroFrameSrc = (byte *)pAnimDescSrc + offset; + byte *pZeroFrameDest = (byte *)pAnimDescDest + offset; + + SET_INDEX_POINTERS( pData, pHdr, boneindex ) + SET_OBJECT_POINTERS( pStudioBone, pData, mstudiobone_t ) + ITERATE_BLOCK( pStudioBone, pHdr->numbones ) + { + if ( pStudioBone->flags & BONE_HAS_SAVEFRAME_POS ) + { + for ( int j = 0; j < pAnimDesc->zeroframecount; j++) + { + WriteBuffer( &pZeroFrameDest, &pZeroFrameSrc, 3 ); + } + } + if ( pStudioBone->flags & BONE_HAS_SAVEFRAME_ROT ) + { + for ( int j = 0; j < pAnimDesc->zeroframecount; j++) + { + WriteBuffer( &pZeroFrameDest, &pZeroFrameSrc, 1 ); + } + } + } + } + } + + /** SEQUENCE INFO **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, localseqindex ) + DECLARE_OBJECT_POINTERS( pSequence, pData, mstudioseqdesc_t ) + ITERATE_BLOCK( pSequence, pHdr->numlocalseq ) + { + WriteObjects( pSequenceDest, pSequenceSrc ); + + /** POSE KEYS **/ + + if ( pSequence->posekeyindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pSequence, posekeyindex ) + WriteBuffer( pDataDest, pDataSrc, SrcNative( &pSequence->groupsize[0] ) + SrcNative( &pSequence->groupsize[1] ) ); + } + + /** STUDIO EVENTS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pSequence, eventindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pSequence->numevents ) ); + + /** AUTOLAYERS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pSequence, autolayerindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pSequence->numautolayers ) ); + + /** BONE WEIGHTS **/ + + // Data may be shared across sequences + DECLARE_INDEX_POINTERS_FIXUP( pWeight, pSequence, weightlistindex ) + if ( pWeightSrc >= pDataSrc ) + { + int numBoneWeights = ( SrcNative( &pSequence->iklockindex ) - SrcNative( &pSequence->weightlistindex ) ) / sizeof(float); + WriteBuffer( pWeightDest, pWeightSrc, numBoneWeights ); + } + + /** IK LOCKS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pSequence, iklockindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pSequence->numiklocks ) ); + + /** ANIMATION INDICES **/ + + if ( pSequence->animindexindex ) + { + SET_INDEX_POINTERS( pData, pSequence, animindexindex ) + WriteBuffer( pDataDest, pDataSrc, SrcNative( &pSequence->groupsize[0] ) * SrcNative( &pSequence->groupsize[1] ) ); + } + + /** KEYVALUES **/ + + SET_INDEX_POINTERS( pData, pSequence, keyvalueindex ) + WriteBuffer( pDataDest, pDataSrc, SrcNative( &pSequence->keyvaluesize ) ); + } + + /** TRANSITION GRAPH **/ + + int numLocalNodes = SrcNative( &pHdr->numlocalnodes ); + SET_INDEX_POINTERS_FIXUP( pData, pHdr, localnodenameindex ) + WriteBuffer( pDataDest, pDataSrc, numLocalNodes ); + + /** LOCAL NODES **/ + + SET_INDEX_POINTERS( pData, pHdr, localnodeindex ) + WriteBuffer( pDataDest, pDataSrc, numLocalNodes * numLocalNodes ); + + /** BODYPART INFO **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, bodypartindex ) + DECLARE_OBJECT_POINTERS( pBodypart, pData, mstudiobodyparts_t ) + ITERATE_BLOCK( pBodypart, pHdr->numbodyparts ) + { + WriteObjects( pBodypartDest, pBodypartSrc ); + + /** MODEL INFO **/ + + SET_INDEX_POINTERS_FIXUP( pData, pBodypart, modelindex ) + DECLARE_OBJECT_POINTERS( pModel, pData, mstudiomodel_t ) + ITERATE_BLOCK( pModel, pBodypart->nummodels ) + { + WriteObjects( pModelDest, pModelSrc ); + + /** MESHES **/ + + SET_INDEX_POINTERS_FIXUP( pData, pModel, meshindex ) + DECLARE_OBJECT_POINTERS( pMesh, pData, mstudiomesh_t ) + ITERATE_BLOCK( pMesh, pModel->nummeshes ) + { + WriteObjects( pMeshDest, pMeshSrc ); + + if ( !pMesh->numflexes ) + continue; + + /** FLEXES **/ + + SET_INDEX_POINTERS_FIXUP( pData, pMesh, flexindex ) + DECLARE_OBJECT_POINTERS( pFlex, pData, mstudioflex_t ) + ITERATE_BLOCK( pFlex, pMesh->numflexes ) + { + WriteObjects( pFlexDest, pFlexSrc ); + + /** VERT ANIMS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pFlex, vertindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pFlex->numverts ) ); + } + } + + /** EYEBALLS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pModel, eyeballindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pModel->numeyeballs ) ); + } + } + + /** GLOBAL FLEX NAMES **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, flexdescindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numflexdesc ) ); + + /** GLOBAL FLEX CONTROLLERS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, flexcontrollerindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numflexcontrollers ) ); + + /** GLOBAL FLEX CONTROLLER REMAPS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, flexcontrolleruiindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numflexcontrollerui ) ); + + // TODOKD: The remap indices after the flex controller remap headers need to be swapped as well? + + /** FLEX RULES **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, flexruleindex ) + DECLARE_OBJECT_POINTERS( pFlexRule, pData, mstudioflexrule_t ) + ITERATE_BLOCK( pFlexRule, pHdr->numflexrules ) + { + WriteObjects( pFlexRuleDest, pFlexRuleSrc ); + + /** FLEX OPS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pFlexRule, opindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pFlexRule->numops ) ); + } + + /** IK CHAINS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, ikchainindex ) + DECLARE_OBJECT_POINTERS( pIKChain, pData, mstudioikchain_t ) + ITERATE_BLOCK( pIKChain, pHdr->numikchains ) + { + WriteObjects( pIKChainDest, pIKChainSrc ); + + /** IK LINKS **/ + + SET_INDEX_POINTERS( pData, pIKChain, linkindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pIKChain->numlinks ) ); + } + + /** IK AUTOPLAY LOCKS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, localikautoplaylockindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numlocalikautoplaylocks ) ); + + /** MOUTH INFO **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, mouthindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->nummouths ) ); + + /** POSE PARAMATERS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, localposeparamindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numlocalposeparameters ) ); + + /** MODEL GROUPS **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, includemodelindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numincludemodels ) ); + + /** ANIMBLOCK GROUP INFO **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, animblockindex ) + WriteObjects( pDataDest, pDataSrc, SrcNative( &pHdr->numanimblocks ) ); + + /** TEXTURE INFO **/ + + // While swapping, kill off unwanted textures by name + SET_INDEX_POINTERS_FIXUP( pData, pHdr, textureindex ) + DECLARE_OBJECT_POINTERS( pTexture, pData, mstudiotexture_t ) + int textureCt = SrcNative( &pHdr->numtextures ); + int nameOffset = 0; + for ( int i = 0; i < SrcNative( &pHdr->numtextures ); ++i, ++pTexture, ++pTextureSrc ) + { + WriteObjects( pTextureDest, pTextureSrc ); + + int destnameindex = SrcNative( &pTexture->sznameindex ) + nameOffset; + pTextureDest->sznameindex = DestNative( &destnameindex ); + char *pName = (char*)pTexture + SrcNative( &pTexture->sznameindex ); +#if 0 // Undone: Killing textures here can cause crashes at runtime. + // Don't need pupil textures + if ( Q_stristr( pName, "pupil_" ) || !Q_stricmp( pName, "pupil" ) ) + { + --textureCt; + nameOffset += sizeof(mstudiotexture_t); + } + else +#endif + { + ++pTextureDest; + } + } + pHdrDest->numtextures = DestNative( &textureCt ); + + /** TEXTURE INDICES **/ + + SET_INDEX_POINTERS_FIXUP( pData, pHdr, cdtextureindex ) + WriteBuffer( &pDataDest, &pDataSrc, SrcNative( &pHdr->numcdtextures ) ); + + /** TEXTURE DICTIONARY **/ + + SET_INDEX_POINTERS( pData, pHdr, skinindex ) + WriteBuffer( &pDataDest, &pDataSrc, SrcNative( &pHdr->numskinfamilies ) * SrcNative( &pHdr->numskinref ) ); + + /** KEYVALUES **/ + + SET_INDEX_POINTERS( pData, pHdr, keyvalueindex ) + WriteBuffer( &pDataDest, &pDataSrc, SrcNative( &pHdr->keyvaluesize ) ); + + /** STUDIOHDR2 **/ + + if ( pHdr->studiohdr2index ) + { + DECLARE_INDEX_POINTERS_FIXUP( pLocalData, pHdr, studiohdr2index ) + DECLARE_OBJECT_POINTERS( pStudioHdr2, pLocalData, studiohdr2_t ) + + // HACK: Pre-swap the constant "1" here so the automatic swap inside ITERATE_BLOCK will restore it + int studiohdr2ct = 1; + studiohdr2ct = SrcNative( &studiohdr2ct ); + ITERATE_BLOCK( pStudioHdr2, studiohdr2ct ) + { + WriteObjects( pStudioHdr2Dest, pStudioHdr2Src ); + + /** SRC BONE TRANSFORMS **/ + + if ( pStudioHdr2->numsrcbonetransform ) + { + // Note, srcbonetransformindex is an offset from the start of the file, not the start of the studiohdr2 + // as is the convention. That's why the macros can't be used here. + pDataSrc = (byte*)pHdrSrc + SrcNative( &pStudioHdr2->srcbonetransformindex ); + pDataDest = (byte*)pHdrDest + SrcNative( &pStudioHdr2->srcbonetransformindex ); + WriteObjects( &pDataDest, &pDataSrc, SrcNative( &pStudioHdr2->numsrcbonetransform ) ); + } + + if ( pStudioHdr2->linearboneindex ) + { + SET_INDEX_POINTERS_FIXUP( pData, pStudioHdr2, linearboneindex ) + DECLARE_OBJECT_POINTERS( pLinearBone, pData, mstudiolinearbone_t ) + + WriteObjects( pLinearBoneDest, pLinearBoneSrc ); + + int numBones = SrcNative( &pLinearBone->numbones ); + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, flagsindex ) + WriteBuffer( &pDataDest, &pDataSrc, numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, parentindex ) + WriteBuffer( &pDataDest, &pDataSrc, numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, posindex ) + WriteBuffer( &pDataDest, &pDataSrc, 3*numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, quatindex ) + WriteBuffer( &pDataDest, &pDataSrc, 4*numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, rotindex ) + WriteBuffer( &pDataDest, &pDataSrc, 3*numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, posetoboneindex ) + WriteBuffer( &pDataDest, &pDataSrc, 12*numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, posscaleindex ) + WriteBuffer( &pDataDest, &pDataSrc, 3*numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, rotscaleindex ) + WriteBuffer( &pDataDest, &pDataSrc, 3*numBones ); + + SET_INDEX_POINTERS_FIXUP( pData, pLinearBone, qalignmentindex ) + WriteBuffer( &pDataDest, &pDataSrc, 4*numBones ); + } + } + } + + /** STRING TABLE **/ + + // NOTE: The block of data (above) swapped immediately before the string table MUST update the + // pDataSrc pointer position, in order for this string table offset calculation to work correctly. + // To update the pointer position, pass the pointer address to WriteObjects(). + int offset = pDataSrc - (byte*)pSrcBase; + int stringTableBytes = fixedFileSize - offset; + WriteBuffer( pDataDest, pDataSrc, stringTableBytes ); + + pHdrDest->length = DestNative( &fixedFileSize ); + + // Cleanup texture paths + // Some older MDL's have double terminal slashes + SET_INDEX_POINTERS( pData, pHdr, cdtextureindex ) + int numCdTextures = SrcNative( &pHdr->numcdtextures ); + for ( int i = 0; i < numCdTextures; ++i ) + { + char *pPath = (char*)pHdrDest + SrcNative( &((int *)pDataSrc)[i] ); + int len = strlen( pPath ); + if ( len >= 2 && ( pPath[len-1] == '\\' || pPath[len-1] == '/' ) && ( pPath[len-2] == '\\' || pPath[len-2] == '/' ) ) + { + pPath[len-1] = '\0'; + } + } + + return fixedFileSize; +} + +//---------------------------------------------------------------------- +// Swap a .mdl in two passes - first pass fixes alignment errors by shifting +// the data and updating offsets, then the second pass does the final swap. +//---------------------------------------------------------------------- +int ByteswapMDL( void *pDestBase, const void *pSrcBase, const int fileSize ) +{ + // Make a working copy of the source to allow for alignment fixups + void *pNewSrcBase = malloc( fileSize + BYTESWAP_ALIGNMENT_PADDING ); + Q_memcpy( pNewSrcBase, pSrcBase, fileSize ); + + int fixedFileSize = ByteswapMDLFile( pDestBase, pNewSrcBase, fileSize ); + if ( fixedFileSize != fileSize ) + { + int finalSize = ByteswapMDLFile( pDestBase, pNewSrcBase, fixedFileSize ); + if ( finalSize != fixedFileSize ) + { + Warning( "Alignment fixups failed on MDL swap!\n" ); + fixedFileSize = 0; + } + } + + free( pNewSrcBase ); + + // the compression needs to happen on the final or "fixed" pass + if ( g_pCompressFunc && fixedFileSize ) + { + void *pInput = pDestBase; + int inputSize = fixedFileSize; + void *pOutput; + int outputSize; + if ( g_pCompressFunc( pInput, inputSize, &pOutput, &outputSize ) ) + { + V_memcpy( pDestBase, pOutput, outputSize ); + free( pOutput ); + fixedFileSize = outputSize; + } + } + + return fixedFileSize; +} + +//---------------------------------------------------------------------- +// Determines what kind of file this is and calls the correct swap function +//---------------------------------------------------------------------- +int ByteswapStudioFile( const char *pFilename, void *pOutBase, const void *pFileBase, int fileSize, studiohdr_t *pHdr, CompressFunc_t pCompressFunc ) +{ + assert( pFilename ); + assert( pOutBase != pFileBase ); + + g_pCompressFunc = pCompressFunc; + + int retVal = 0; + + if ( Q_stristr( pFilename, ".mdl" ) ) + { + retVal = ByteswapMDL( pOutBase, pFileBase, fileSize ); + } + else if ( Q_stristr( pFilename, ".vvd" ) ) + { + retVal = ByteswapVVD( pOutBase, pFileBase, fileSize ); + } + else if ( Q_stristr( pFilename, ".vtx" ) ) + { + retVal = ByteswapVTX( pOutBase, pFileBase, fileSize ); + } + else if ( Q_stristr( pFilename, ".phy" ) ) + { + retVal = ByteswapPHY( pOutBase, pFileBase, fileSize ); + } + else if ( Q_stristr( pFilename, ".ani" ) ) + { + // some dead .ani files exist in the tree + // only process valid .ani files properly hooked to their .mdl + if ( pHdr && pHdr->numanimblocks != 0 ) + { + retVal = ByteswapANI( pHdr, pOutBase, pFileBase, fileSize ); + } + } + + g_pCompressFunc = NULL; + + return retVal; +} + + +//---------------------------------------------------------------------- +// LEGACY ANI FIXUPS - No need to update this function +// +// If model data needs to be shifted to fix a misalignment, this function is +// called with the process func "UpdateSrcIndexFields" to update every datadesc field +// that was marked as FIELD_INDEX. Any index that "points" across +// the shifted data location will have its value incremented or decremented +// by the appropriate number of bytes. This misalignment issue only +// applies to some pre-EP2 models, as all newly compiled models are guaranteed +// to be aligned correctly. Therefore, this function should not need to change +// when model formats are updated, as any model compiled with a new format will +// naturally not require this fixup function to be called. +//---------------------------------------------------------------------- +void ProcessANIFields( void *pDataBase, datadescProcessFunc_t pfnProcessFunc ) +{ + studiohdr_t *pHdr = g_pHdr; + byte *pData = (byte*)pDataBase; + byte *pSrcBase = (byte*)g_pDataSrcBase; + + // Since the animblocks and animdescs are native data, trick the system + // into not swapping their index values during processing. + bool bNativeSrc = g_bNativeSrc; + g_bNativeSrc = true; + + // Update the .mdl's animblock offsets into the .ani file + mstudioanimblock_t *pAnimBlock = pHdr->pAnimBlock( 1 ); + for ( int i = 1; i < pHdr->numanimblocks; ++i, ++pAnimBlock ) + { + ProcessFields( pSrcBase, pAnimBlock, &mstudioanimblock_t::m_DataMap, pfnProcessFunc ); + } + + // Update the .mdl's animindex offsets into the .ani file + // Don't bother with local hierarchy keys - they only exist in newer, properly aligned models + mstudioanimdesc_t *pAnimDesc = pHdr->pLocalAnimdesc( 0 ); + for ( int i = 0; i < pHdr->numlocalanim; ++i, ++pAnimDesc ) + { + mstudioanimblock_t *pAnimBlock = pHdr->pAnimBlock( pAnimDesc->animblock ); + byte *pBlockBase = (byte*)pSrcBase + pAnimBlock->datastart; + + ProcessFieldByName( pBlockBase, pAnimDesc, &mstudioanimdesc_t::m_DataMap, "animindex", pfnProcessFunc ); + ProcessFieldByName( pBlockBase, pAnimDesc, &mstudioanimdesc_t::m_DataMap, "animblockikruleindex", pfnProcessFunc ); + } + + // Restore the correct native setting + g_bNativeSrc = bNativeSrc; + + // Update the .ani file's internal offsets + pAnimDesc = pHdr->pLocalAnimdesc( 0 ); + for ( int i = 0; i < pHdr->numlocalanim; ++i, ++pAnimDesc ) + { + pAnimBlock = pHdr->pAnimBlock( pAnimDesc->animblock ); + byte *pBlockBase = (byte*)pSrcBase + pAnimBlock->datastart; + + byte *pData = pBlockBase + pAnimDesc->animindex; + mstudioanim_t* pAnimation = (mstudioanim_t*)pData; + if ( pAnimation->bone == 255 ) + { + // No animation data + pAnimation = 0; + } + + while( pAnimation ) + { + ProcessFields( pAnimation, &mstudioanim_t::m_DataMap, pfnProcessFunc ); + + if ( pAnimation->nextoffset ) + { + pData = (byte*)pAnimation + SrcNative( &pAnimation->nextoffset ); + pAnimation = (mstudioanim_t*)pData; + } + else + { + pAnimation = NULL; + } + } + + if ( pAnimDesc->animblockikruleindex ) + { + pData = (byte*)pBlockBase + pAnimDesc->animblockikruleindex; + + mstudioikrule_t *pIKRule = (mstudioikrule_t *)pData; + for ( int i = 0; i < pAnimDesc->numikrules; ++i, ++pIKRule ) + { + ProcessFields( pIKRule, &mstudioikrule_t::m_DataMap, pfnProcessFunc ); + } + } + } +} + +//---------------------------------------------------------------------- +// LEGACY MDL FIXUPS - No need to update this function +// +// If model data needs to be shifted to fix a misalignment, this function is +// called with the process func "UpdateSrcIndexFields" to update every datadesc field +// that was marked as FIELD_INDEX. Any index that "points" across +// the shifted data location will have its value incremented or decremented +// by the appropriate number of bytes. This misalignment issue only +// applies to some pre-EP2 models, as all newly compiled models are guaranteed +// to be aligned correctly. Therefore, this function should not need to change +// when model formats are updated, as any model compiled with a new format will +// naturally not require this fixup function to be called. +//---------------------------------------------------------------------- +void ProcessMDLFields( void *pDataBase, datadescProcessFunc_t pfnProcessFunc ) +{ + byte *pData = (byte*)pDataBase; + + /** FILE HEADER **/ + + studiohdr_t *pHdr = (studiohdr_t*)pData; + ProcessFields( pHdr, &studiohdr_t::m_DataMap, pfnProcessFunc ); + + /** BONES **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->boneindex ); + mstudiobone_t *pBone = (mstudiobone_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numbones ); ++i, ++pBone ) + { + ProcessFields( pBone, &mstudiobone_t::m_DataMap, pfnProcessFunc ); + + if ( pBone->procindex ) + { + pData = (byte*)pBone + SrcNative( &pBone->procindex ); + + unsigned int proctype = SrcNative( &pBone->proctype ); + switch( proctype ) + { + case STUDIO_PROC_AXISINTERP: + + /** AXIS-INTERP BONES **/ + ProcessFields( pData, &mstudioaxisinterpbone_t::m_DataMap, pfnProcessFunc ); + break; + + case STUDIO_PROC_QUATINTERP: + + /** QUAT-INTERP BONES **/ + ProcessFields( pData, &mstudioquatinterpbone_t::m_DataMap, pfnProcessFunc ); + break; + + case STUDIO_PROC_JIGGLE: + + /** JIGGLE BONES **/ + ProcessFields( pData, &mstudiojigglebone_t::m_DataMap, pfnProcessFunc ); + break; + + case STUDIO_PROC_AIMATBONE: + case STUDIO_PROC_AIMATATTACH: + + /** AIM AT BONES **/ + ProcessFields( pData, &mstudioaimatbone_t::m_DataMap, pfnProcessFunc ); + break; + + } + } + } + + /** ATTACHMENTS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->localattachmentindex ); + mstudioattachment_t *pAttachment = (mstudioattachment_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numlocalattachments ); ++i, ++pAttachment ) + { + ProcessFields( pAttachment, &mstudioattachment_t::m_DataMap, pfnProcessFunc ); + } + + /** HITBOX SETS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->hitboxsetindex ); + mstudiohitboxset_t* pHitboxSet = (mstudiohitboxset_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numhitboxsets ); ++i, ++pHitboxSet ) + { + ProcessFields( pHitboxSet, &mstudiohitboxset_t::m_DataMap, pfnProcessFunc ); + + /** HITBOXES **/ + pData = (byte*)pHitboxSet + SrcNative( &pHitboxSet->hitboxindex ); + mstudiobbox_t *pBBox = (mstudiobbox_t*)pData; + for ( int i = 0; i < SrcNative( &pHitboxSet->numhitboxes ); ++i, ++pBBox ) + { + ProcessFields( pBBox, &mstudiobbox_t::m_DataMap, pfnProcessFunc ); + } + } + + /** ANIMATION DESCRIPTIONS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->localanimindex ); + mstudioanimdesc_t* pAnimDesc = (mstudioanimdesc_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numlocalanim ); ++i, ++pAnimDesc ) + { + // Can't update animindex or animblockindex for animations that are in a separate .ani file + ProcessFieldByName( pAnimDesc, &mstudioanimdesc_t::m_DataMap, "baseptr", pfnProcessFunc ); + ProcessFieldByName( pAnimDesc, &mstudioanimdesc_t::m_DataMap, "sznameindex", pfnProcessFunc ); + ProcessFieldByName( pAnimDesc, &mstudioanimdesc_t::m_DataMap, "movementindex", pfnProcessFunc ); + ProcessFieldByName( pAnimDesc, &mstudioanimdesc_t::m_DataMap, "ikruleindex", pfnProcessFunc ); + + /** ANIMATIONS **/ + + if ( pAnimDesc->animblock == 0 ) + { + // Now it's safe to update the animindex + ProcessFieldByName( pAnimDesc, &mstudioanimdesc_t::m_DataMap, "animindex", pfnProcessFunc ); + + pData = (byte*)pAnimDesc + SrcNative( &pAnimDesc->animindex ); + mstudioanim_t* pAnimation = (mstudioanim_t*)pData; + while( pAnimation ) + { + ProcessFields( pAnimation, &mstudioanim_t::m_DataMap, pfnProcessFunc ); + + if ( pAnimation->nextoffset ) + { + pData = (byte*)pAnimation + SrcNative( &pAnimation->nextoffset ); + pAnimation = (mstudioanim_t*)pData; + } + else + { + pAnimation = NULL; + } + } + } + + if ( pAnimDesc->ikruleindex ) + { + pData = (byte*)pAnimDesc + SrcNative( &pAnimDesc->ikruleindex ); + + mstudioikrule_t *pIKRule = (mstudioikrule_t *)pData; + for ( int i = 0; i < SrcNative( &pAnimDesc->numikrules ); ++i, ++pIKRule ) + { + ProcessFields( pIKRule, &mstudioikrule_t::m_DataMap, pfnProcessFunc ); + } + } + } + + /** SEQUENCE INFO **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->localseqindex ); + mstudioseqdesc_t *pSequenceHdr = (mstudioseqdesc_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numlocalseq ); ++i, ++pSequenceHdr ) + { + ProcessFields( pSequenceHdr, &mstudioseqdesc_t::m_DataMap, pfnProcessFunc ); + + /** STUDIO EVENTS **/ + + pData = (byte*)pHdr + SrcNative( &pSequenceHdr->eventindex ); + mstudioevent_t *pEvent = (mstudioevent_t*)pData; + for ( int i = 0; i < SrcNative( &pSequenceHdr->numevents ); ++i, ++pEvent ) + { + ProcessFields( pSequenceHdr, &mstudioevent_t::m_DataMap, pfnProcessFunc ); + } + } + + /** BODYPART INFO **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->bodypartindex ); + mstudiobodyparts_t *pBodypart = (mstudiobodyparts_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numbodyparts ); ++i, ++pBodypart ) + { + ProcessFields( pBodypart, &mstudiobodyparts_t::m_DataMap, pfnProcessFunc ); + + /** MODEL INFO **/ + + byte *pData = (byte*)pBodypart + SrcNative( &pBodypart->modelindex ); + mstudiomodel_t *pModel = (mstudiomodel_t*)pData; + for ( int i = 0; i < SrcNative( &pBodypart->nummodels ); ++i, ++pModel ) + { + ProcessFields( pModel, &mstudiomodel_t::m_DataMap, pfnProcessFunc ); + + /** MESHES **/ + + pData = (byte*)pModel + SrcNative( &pModel->meshindex ); + mstudiomesh_t *pMesh = (mstudiomesh_t*)pData; + for ( int i = 0; i < SrcNative( &pModel->nummeshes ); ++i, ++pMesh ) + { + ProcessFields( pMesh, &mstudiomesh_t::m_DataMap, pfnProcessFunc ); + + if ( !pMesh->numflexes ) + continue; + + /** FLEXES **/ + + pData = (byte*)pMesh + SrcNative( &pMesh->flexindex ); + mstudioflex_t *pFlex = (mstudioflex_t*)pData; + for ( int i = 0; i < SrcNative( &pMesh->numflexes ); ++i, ++pFlex ) + { + ProcessFields( pFlex, &mstudioflex_t::m_DataMap, pfnProcessFunc ); + } + } + + /** EYEBALLS **/ + + pData= (byte*)pModel + SrcNative( &pModel->eyeballindex ); + mstudioeyeball_t *pEyeball = (mstudioeyeball_t*)pData; + for ( int i = 0; i < SrcNative( &pModel->numeyeballs ); ++i, ++pEyeball ) + { + ProcessFields( pEyeball, &mstudioeyeball_t::m_DataMap, pfnProcessFunc ); + } + } + } + + /** GLOBAL FLEX NAMES **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->flexdescindex ); + mstudioflexdesc_t *pFlexDesc = (mstudioflexdesc_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numflexdesc ); ++i, ++pFlexDesc ) + { + ProcessFields( pFlexDesc, &mstudioflexdesc_t::m_DataMap, pfnProcessFunc ); + } + + /** GLOBAL FLEX CONTROLLERS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->flexcontrollerindex ); + mstudioflexcontroller_t *pFlexController = (mstudioflexcontroller_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numflexcontrollers ); ++i, ++pFlexController ) + { + ProcessFields( pFlexController, &mstudioflexcontroller_t::m_DataMap, pfnProcessFunc ); + } + + /** GLOBAL FLEX CONTROLLER REMAPS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->flexcontrolleruiindex ); + mstudioflexcontrollerui_t *pFlexControllerRemap = (mstudioflexcontrollerui_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numflexcontrollerui ); ++i, ++pFlexControllerRemap ) + { + ProcessFields( pFlexControllerRemap, &mstudioflexcontrollerui_t::m_DataMap, pfnProcessFunc ); + } + + /** FLEX RULES **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->flexruleindex ); + mstudioflexrule_t *pFlexRule = (mstudioflexrule_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numflexrules ); ++i, ++pFlexRule ) + { + ProcessFields( pFlexRule, &mstudioflexrule_t::m_DataMap, pfnProcessFunc ); + } + + /** IK CHAINS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->ikchainindex ); + mstudioikchain_t *pIKChain = (mstudioikchain_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numikchains ); ++i, ++pIKChain ) + { + ProcessFields( pIKChain, &mstudioikchain_t::m_DataMap, pfnProcessFunc ); + } + + /** POSE PARAMATERS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->localposeparamindex ); + mstudioposeparamdesc_t *pPoseParam = (mstudioposeparamdesc_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numlocalposeparameters ); ++i, ++pPoseParam ) + { + ProcessFields( pPoseParam, &mstudioposeparamdesc_t::m_DataMap, pfnProcessFunc ); + } + + /** MODEL GROUPS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->includemodelindex ); + mstudiomodelgroup_t *pMdl = (mstudiomodelgroup_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numincludemodels ); ++i, ++pMdl ) + { + ProcessFields( pMdl, &mstudiomodelgroup_t::m_DataMap, pfnProcessFunc ); + } + + /** TEXTURE INFO **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->textureindex ); + mstudiotexture_t *pTexture = (mstudiotexture_t*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numtextures ); ++i, ++pTexture ) + { + ProcessFields( pTexture, &mstudiotexture_t::m_DataMap, pfnProcessFunc ); + } + + /** CDTEXTURE OFFSETS **/ + + pData = (byte*)pHdr + SrcNative( &pHdr->cdtextureindex ); + int *pIdx = (int*)pData; + for ( int i = 0; i < SrcNative( &pHdr->numcdtextures ); ++i, ++pIdx ) + { + int idx = SrcNative( pIdx ); + UpdateIndex( pHdr, &idx ); + *pIdx = SrcNative( &idx ); + } + + /** STUDIOHDR2 **/ + + if ( pHdr->studiohdr2index ) + { + pData = (byte*)pHdr + SrcNative( &pHdr->studiohdr2index ); + studiohdr2_t *pStudioHdr2 = (studiohdr2_t*)pData; + for ( int i = 0; i < 1; ++i, ++pStudioHdr2 ) + { + ProcessFields( pStudioHdr2, &studiohdr2_t::m_DataMap, pfnProcessFunc ); + + /** SRC BONE TRANSFORMS **/ + + pData = (byte*)pHdr + SrcNative( &pStudioHdr2->srcbonetransformindex ); + mstudiosrcbonetransform_t *pSrcBoneTransform = (mstudiosrcbonetransform_t*)pData; + for ( int i = 0; i < SrcNative( &pStudioHdr2->numsrcbonetransform ); ++i, ++pSrcBoneTransform ) + { + ProcessFields( pSrcBoneTransform, &mstudiosrcbonetransform_t::m_DataMap, pfnProcessFunc ); + } + } + } +} + + +#pragma warning( pop ) // local variable is initialized but not referenced + +} // namespace StudioByteSwap + +// Data descriptions for byte swapping - only needed +// for structures that are written to file for use by the game. +// For any fields that reference other data in the file, use the +// DEFINE_INDEX macro to identify them as such. +BEGIN_BYTESWAP_DATADESC( studiohdr_t ) + DEFINE_FIELD( id, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_FIELD( checksum, FIELD_INTEGER ), + DEFINE_ARRAY( name, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( length, FIELD_INTEGER ), + DEFINE_FIELD( eyeposition, FIELD_VECTOR ), + DEFINE_FIELD( illumposition, FIELD_VECTOR ), + DEFINE_FIELD( hull_min, FIELD_VECTOR ), + DEFINE_FIELD( hull_max, FIELD_VECTOR ), + DEFINE_FIELD( view_bbmin, FIELD_VECTOR ), + DEFINE_FIELD( view_bbmax, FIELD_VECTOR ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( numbones, FIELD_INTEGER ), // bones + DEFINE_INDEX( boneindex, FIELD_INTEGER ), + DEFINE_FIELD( numbonecontrollers, FIELD_INTEGER ), // bone controllers + DEFINE_INDEX( bonecontrollerindex, FIELD_INTEGER ), + DEFINE_FIELD( numhitboxsets, FIELD_INTEGER ), + DEFINE_INDEX( hitboxsetindex, FIELD_INTEGER ), + DEFINE_FIELD( numlocalanim, FIELD_INTEGER ), // animations/poses + DEFINE_INDEX( localanimindex, FIELD_INTEGER ), // animation descriptions + DEFINE_FIELD( numlocalseq, FIELD_INTEGER ), // sequences + DEFINE_INDEX( localseqindex, FIELD_INTEGER ), + DEFINE_FIELD( activitylistversion, FIELD_INTEGER ), // initialization flag - have the sequences been indexed? + DEFINE_FIELD( eventsindexed, FIELD_INTEGER ), + DEFINE_FIELD( numtextures, FIELD_INTEGER ), + DEFINE_INDEX( textureindex, FIELD_INTEGER ), + DEFINE_FIELD( numcdtextures, FIELD_INTEGER ), + DEFINE_INDEX( cdtextureindex, FIELD_INTEGER ), + DEFINE_FIELD( numskinref, FIELD_INTEGER ), + DEFINE_FIELD( numskinfamilies, FIELD_INTEGER ), + DEFINE_INDEX( skinindex, FIELD_INTEGER ), + DEFINE_FIELD( numbodyparts, FIELD_INTEGER ), + DEFINE_INDEX( bodypartindex, FIELD_INTEGER ), + DEFINE_FIELD( numlocalattachments, FIELD_INTEGER ), + DEFINE_INDEX( localattachmentindex, FIELD_INTEGER ), + DEFINE_FIELD( numlocalnodes, FIELD_INTEGER ), + DEFINE_INDEX( localnodeindex, FIELD_INTEGER ), + DEFINE_INDEX( localnodenameindex, FIELD_INTEGER ), + DEFINE_FIELD( numflexdesc, FIELD_INTEGER ), + DEFINE_INDEX( flexdescindex, FIELD_INTEGER ), + DEFINE_FIELD( numflexcontrollers, FIELD_INTEGER ), + DEFINE_INDEX( flexcontrollerindex, FIELD_INTEGER ), + DEFINE_FIELD( numflexrules, FIELD_INTEGER ), + DEFINE_INDEX( flexruleindex, FIELD_INTEGER ), + DEFINE_FIELD( numikchains, FIELD_INTEGER ), + DEFINE_INDEX( ikchainindex, FIELD_INTEGER ), + DEFINE_FIELD( nummouths, FIELD_INTEGER ), + DEFINE_INDEX( mouthindex, FIELD_INTEGER ), + DEFINE_FIELD( numlocalposeparameters, FIELD_INTEGER ), + DEFINE_INDEX( localposeparamindex, FIELD_INTEGER ), + DEFINE_INDEX( surfacepropindex, FIELD_INTEGER ), + DEFINE_INDEX( keyvalueindex, FIELD_INTEGER ), + DEFINE_FIELD( keyvaluesize, FIELD_INTEGER ), + DEFINE_FIELD( numlocalikautoplaylocks, FIELD_INTEGER ), + DEFINE_INDEX( localikautoplaylockindex, FIELD_INTEGER ), + DEFINE_FIELD( mass, FIELD_FLOAT ), + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( numincludemodels, FIELD_INTEGER ), + DEFINE_INDEX( includemodelindex, FIELD_INTEGER ), + DEFINE_FIELD( virtualModel, FIELD_INTEGER ), // void* + DEFINE_INDEX( szanimblocknameindex, FIELD_INTEGER ), + DEFINE_FIELD( numanimblocks, FIELD_INTEGER ), + DEFINE_INDEX( animblockindex, FIELD_INTEGER ), + DEFINE_FIELD( animblockModel, FIELD_INTEGER ), // void* + DEFINE_INDEX( bonetablebynameindex, FIELD_INTEGER ), + DEFINE_FIELD( pVertexBase, FIELD_INTEGER ), // void* + DEFINE_FIELD( pIndexBase, FIELD_INTEGER ), // void* + DEFINE_FIELD( constdirectionallightdot, FIELD_CHARACTER ), // byte + DEFINE_FIELD( rootLOD, FIELD_CHARACTER ), // byte + DEFINE_FIELD( numAllowedRootLODs, FIELD_CHARACTER ), // byte + DEFINE_ARRAY( unused, FIELD_CHARACTER, 1 ), // byte + DEFINE_INDEX( unused4, FIELD_INTEGER ), + DEFINE_FIELD( numflexcontrollerui, FIELD_INTEGER ), + DEFINE_INDEX( flexcontrolleruiindex, FIELD_INTEGER ), + DEFINE_ARRAY( unused3, FIELD_INTEGER, 2 ), + DEFINE_INDEX( studiohdr2index, FIELD_INTEGER ), + DEFINE_ARRAY( unused2, FIELD_INTEGER, 1 ), +END_BYTESWAP_DATADESC() + +// NOTE! Next time we up the .mdl file format, remove studiohdr2_t +// and insert all fields in this structure into studiohdr_t. +BEGIN_BYTESWAP_DATADESC( studiohdr2_t ) + DEFINE_FIELD( numsrcbonetransform, FIELD_INTEGER ), + DEFINE_INDEX( srcbonetransformindex, FIELD_INTEGER ), + DEFINE_FIELD( illumpositionattachmentindex, FIELD_INTEGER ), + DEFINE_FIELD( flMaxEyeDeflection, FIELD_FLOAT ), + DEFINE_INDEX( linearboneindex, FIELD_INTEGER ), + DEFINE_ARRAY( reserved, FIELD_INTEGER, 59 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiobone_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( parent, FIELD_INTEGER ), + DEFINE_ARRAY( bonecontroller, FIELD_INTEGER, 6 ), + DEFINE_FIELD( pos, FIELD_VECTOR ), + DEFINE_FIELD( quat, FIELD_QUATERNION ), + DEFINE_ARRAY( rot, FIELD_FLOAT, 3 ), // RadianEuler + DEFINE_FIELD( posscale, FIELD_VECTOR ), + DEFINE_FIELD( rotscale, FIELD_VECTOR ), + DEFINE_ARRAY( poseToBone, FIELD_FLOAT, 12 ), // matrix3x4_t + DEFINE_FIELD( qAlignment, FIELD_QUATERNION ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( proctype, FIELD_INTEGER ), + DEFINE_INDEX( procindex, FIELD_INTEGER ), + DEFINE_INDEX( physicsbone, FIELD_INTEGER ), + DEFINE_INDEX( surfacepropidx, FIELD_INTEGER ), + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 8 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiolinearbone_t ) + DEFINE_FIELD( numbones, FIELD_INTEGER ), + DEFINE_INDEX( flagsindex, FIELD_INTEGER ), + DEFINE_INDEX( parentindex, FIELD_INTEGER ), + DEFINE_INDEX( posindex, FIELD_INTEGER ), + DEFINE_INDEX( quatindex, FIELD_INTEGER ), + DEFINE_INDEX( rotindex, FIELD_INTEGER ), + DEFINE_INDEX( posetoboneindex, FIELD_INTEGER ), + DEFINE_INDEX( posscaleindex, FIELD_INTEGER ), + DEFINE_INDEX( rotscaleindex, FIELD_INTEGER ), + DEFINE_INDEX( qalignmentindex, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 6 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioaxisinterpbone_t ) + DEFINE_FIELD( control, FIELD_INTEGER ), + DEFINE_FIELD( axis, FIELD_INTEGER ), + DEFINE_ARRAY( pos, FIELD_VECTOR, 6 ), + DEFINE_ARRAY( quat, FIELD_QUATERNION, 6 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioquatinterpbone_t ) + DEFINE_FIELD( control, FIELD_INTEGER ), + DEFINE_FIELD( numtriggers, FIELD_INTEGER ), + DEFINE_INDEX( triggerindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiojigglebone_t ) + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( length, FIELD_FLOAT ), + DEFINE_FIELD( tipMass, FIELD_FLOAT ), + DEFINE_FIELD( yawStiffness, FIELD_FLOAT ), + DEFINE_FIELD( yawDamping, FIELD_FLOAT ), + DEFINE_FIELD( pitchStiffness, FIELD_FLOAT ), + DEFINE_FIELD( pitchDamping, FIELD_FLOAT ), + DEFINE_FIELD( alongStiffness, FIELD_FLOAT ), + DEFINE_FIELD( alongDamping, FIELD_FLOAT ), + DEFINE_FIELD( angleLimit, FIELD_FLOAT ), + DEFINE_FIELD( minYaw, FIELD_FLOAT ), + DEFINE_FIELD( maxYaw, FIELD_FLOAT ), + DEFINE_FIELD( yawFriction, FIELD_FLOAT ), + DEFINE_FIELD( yawBounce, FIELD_FLOAT ), + DEFINE_FIELD( minPitch, FIELD_FLOAT ), + DEFINE_FIELD( maxPitch, FIELD_FLOAT ), + DEFINE_FIELD( pitchFriction, FIELD_FLOAT ), + DEFINE_FIELD( pitchBounce, FIELD_FLOAT ), + DEFINE_FIELD( baseMass, FIELD_FLOAT ), + DEFINE_FIELD( baseStiffness, FIELD_FLOAT ), + DEFINE_FIELD( baseDamping, FIELD_FLOAT ), + DEFINE_FIELD( baseMinLeft, FIELD_FLOAT ), + DEFINE_FIELD( baseMaxLeft, FIELD_FLOAT ), + DEFINE_FIELD( baseLeftFriction, FIELD_FLOAT ), + DEFINE_FIELD( baseMinUp, FIELD_FLOAT ), + DEFINE_FIELD( baseMaxUp, FIELD_FLOAT ), + DEFINE_FIELD( baseUpFriction, FIELD_FLOAT ), + DEFINE_FIELD( baseMinForward, FIELD_FLOAT ), + DEFINE_FIELD( baseMaxForward, FIELD_FLOAT ), + DEFINE_FIELD( baseForwardFriction, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioaimatbone_t ) + DEFINE_FIELD( parent, FIELD_INTEGER ), + DEFINE_FIELD( aim, FIELD_INTEGER ), + DEFINE_ARRAY( aimvector, FIELD_FLOAT, 3 ), + DEFINE_ARRAY( upvector, FIELD_FLOAT, 3 ), + DEFINE_ARRAY( basepos, FIELD_FLOAT, 3 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioquatinterpinfo_t ) + DEFINE_FIELD( inv_tolerance, FIELD_FLOAT ), + DEFINE_FIELD( trigger, FIELD_QUATERNION ), + DEFINE_FIELD( pos, FIELD_VECTOR ), + DEFINE_FIELD( quat, FIELD_QUATERNION ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiobonecontroller_t ) + DEFINE_FIELD( bone, FIELD_INTEGER ), + DEFINE_FIELD( type, FIELD_INTEGER ), + DEFINE_FIELD( start, FIELD_FLOAT ), + DEFINE_FIELD( end, FIELD_FLOAT ), + DEFINE_FIELD( rest, FIELD_INTEGER ), + DEFINE_FIELD( inputfield, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 8 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioattachment_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( localbone, FIELD_INTEGER ), + DEFINE_ARRAY( local, FIELD_FLOAT, 12 ), // matrix3x4_t + DEFINE_ARRAY( unused, FIELD_INTEGER, 8 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiohitboxset_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( numhitboxes, FIELD_INTEGER ), + DEFINE_INDEX( hitboxindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiosrcbonetransform_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_ARRAY( pretransform, FIELD_FLOAT, 12 ), // matrix3x4_t + DEFINE_ARRAY( posttransform, FIELD_FLOAT, 12 ), // matrix3x4_t +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiobbox_t ) + DEFINE_FIELD( bone, FIELD_INTEGER ), + DEFINE_FIELD( group, FIELD_INTEGER ), + DEFINE_FIELD( bbmin, FIELD_VECTOR ), + DEFINE_FIELD( bbmax, FIELD_VECTOR ), + DEFINE_INDEX( szhitboxnameindex, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 8 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioanim_valueptr_t ) + DEFINE_ARRAY( offset, FIELD_SHORT, 3 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiolocalhierarchy_t ) + DEFINE_FIELD( iBone, FIELD_INTEGER ), + DEFINE_FIELD( iNewParent, FIELD_INTEGER ), + DEFINE_FIELD( start, FIELD_FLOAT ), + DEFINE_FIELD( peak, FIELD_FLOAT ), + DEFINE_FIELD( tail, FIELD_FLOAT ), + DEFINE_FIELD( end, FIELD_FLOAT ), + DEFINE_FIELD( iStart, FIELD_INTEGER ), + DEFINE_INDEX( localanimindex, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioanimsections_t ) + DEFINE_FIELD( animblock, FIELD_INTEGER ), + DEFINE_INDEX( animindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioanimdesc_t ) + DEFINE_INDEX( baseptr, FIELD_INTEGER ), + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( fps, FIELD_FLOAT ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( numframes, FIELD_INTEGER ), + DEFINE_FIELD( nummovements, FIELD_INTEGER ), + DEFINE_INDEX( movementindex, FIELD_INTEGER ), + DEFINE_ARRAY( unused1, FIELD_INTEGER, 6 ), + DEFINE_FIELD( animblock, FIELD_INTEGER ), + DEFINE_INDEX( animindex, FIELD_INTEGER ), + DEFINE_FIELD( numikrules, FIELD_INTEGER ), + DEFINE_INDEX( ikruleindex, FIELD_INTEGER ), + DEFINE_INDEX( animblockikruleindex, FIELD_INTEGER ), + DEFINE_FIELD( numlocalhierarchy, FIELD_INTEGER ), + DEFINE_INDEX( localhierarchyindex, FIELD_INTEGER ), + DEFINE_INDEX( sectionindex, FIELD_INTEGER ), + DEFINE_FIELD( sectionframes, FIELD_INTEGER ), + DEFINE_FIELD( zeroframespan, FIELD_SHORT ), + DEFINE_FIELD( zeroframecount, FIELD_SHORT ), + DEFINE_INDEX( zeroframeindex, FIELD_INTEGER ), + DEFINE_FIELD( zeroframestalltime, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioanim_t ) + DEFINE_FIELD( bone, FIELD_CHARACTER ), + DEFINE_FIELD( flags, FIELD_CHARACTER ), + DEFINE_INDEX( nextoffset, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioikerror_t ) + DEFINE_FIELD( pos, FIELD_VECTOR ), + DEFINE_FIELD( q, FIELD_QUATERNION ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiocompressedikerror_t ) + DEFINE_ARRAY( scale, FIELD_FLOAT, 6 ), + DEFINE_ARRAY( offset, FIELD_SHORT, 6 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioikrule_t ) + DEFINE_FIELD( index, FIELD_INTEGER ), + DEFINE_FIELD( type, FIELD_INTEGER ), + DEFINE_FIELD( chain, FIELD_INTEGER ), + DEFINE_FIELD( bone, FIELD_INTEGER ), + DEFINE_FIELD( slot, FIELD_INTEGER ), + DEFINE_FIELD( height, FIELD_FLOAT ), + DEFINE_FIELD( radius, FIELD_FLOAT ), + DEFINE_FIELD( floor, FIELD_FLOAT ), + DEFINE_FIELD( pos, FIELD_VECTOR ), + DEFINE_FIELD( q, FIELD_QUATERNION ), + DEFINE_INDEX( compressedikerrorindex, FIELD_INTEGER ), + DEFINE_FIELD( unused2, FIELD_INTEGER ), + DEFINE_FIELD( iStart, FIELD_INTEGER ), + DEFINE_INDEX( ikerrorindex, FIELD_INTEGER ), + DEFINE_FIELD( start, FIELD_FLOAT ), + DEFINE_FIELD( peak, FIELD_FLOAT ), + DEFINE_FIELD( tail, FIELD_FLOAT ), + DEFINE_FIELD( end, FIELD_FLOAT ), + DEFINE_FIELD( unused3, FIELD_FLOAT ), + DEFINE_FIELD( contact, FIELD_FLOAT ), + DEFINE_FIELD( drop, FIELD_FLOAT ), + DEFINE_FIELD( top, FIELD_FLOAT ), + DEFINE_FIELD( unused6, FIELD_INTEGER ), + DEFINE_FIELD( unused7, FIELD_INTEGER ), + DEFINE_FIELD( unused8, FIELD_INTEGER ), + DEFINE_INDEX( szattachmentindex, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 7 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiomovement_t ) + DEFINE_FIELD( endframe, FIELD_INTEGER ), + DEFINE_FIELD( motionflags, FIELD_INTEGER ), + DEFINE_FIELD( v0, FIELD_FLOAT ), + DEFINE_FIELD( v1, FIELD_FLOAT ), + DEFINE_FIELD( angle, FIELD_FLOAT ), + DEFINE_FIELD( vector, FIELD_VECTOR ), + DEFINE_FIELD( position, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioseqdesc_t ) + DEFINE_INDEX( baseptr, FIELD_INTEGER ), + DEFINE_INDEX( szlabelindex, FIELD_INTEGER ), + DEFINE_INDEX( szactivitynameindex, FIELD_INTEGER ), + DEFINE_FIELD( flags, FIELD_INTEGER ), // looping/non-looping flags + DEFINE_FIELD( activity, FIELD_INTEGER ), // initialized at loadtime to game DLL values + DEFINE_FIELD( actweight, FIELD_INTEGER ), + DEFINE_FIELD( numevents, FIELD_INTEGER ), + DEFINE_INDEX( eventindex, FIELD_INTEGER ), + DEFINE_FIELD( bbmin, FIELD_VECTOR ), + DEFINE_FIELD( bbmax, FIELD_VECTOR ), + DEFINE_FIELD( numblends, FIELD_INTEGER ), + DEFINE_INDEX( animindexindex, FIELD_INTEGER ), + DEFINE_INDEX( movementindex, FIELD_INTEGER ), // [blend] float array for blended movement + DEFINE_ARRAY( groupsize, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( paramindex, FIELD_INTEGER, 2 ), // X, Y, Z, XR, YR, ZR + DEFINE_ARRAY( paramstart, FIELD_FLOAT, 2 ), // local (0..1) starting value + DEFINE_ARRAY( paramend, FIELD_FLOAT, 2 ), // local (0..1) ending value + DEFINE_FIELD( paramparent, FIELD_INTEGER ), + DEFINE_FIELD( fadeintime, FIELD_FLOAT ), // ideal cross fate in time (0.2 default) + DEFINE_FIELD( fadeouttime, FIELD_FLOAT ), // ideal cross fade out time (0.2 default) + DEFINE_FIELD( localentrynode, FIELD_INTEGER ), // transition node at entry + DEFINE_FIELD( localexitnode, FIELD_INTEGER ), // transition node at exit + DEFINE_FIELD( nodeflags, FIELD_INTEGER ), // transition rules + DEFINE_FIELD( entryphase, FIELD_FLOAT ), // used to match entry gait + DEFINE_FIELD( exitphase, FIELD_FLOAT ), // used to match exit gait + DEFINE_FIELD( lastframe, FIELD_FLOAT ), // frame that should generation EndOfSequence + DEFINE_FIELD( nextseq, FIELD_INTEGER ), // auto advancing sequences + DEFINE_FIELD( pose, FIELD_INTEGER ), // index of delta animation between end and nextseq + DEFINE_FIELD( numikrules, FIELD_INTEGER ), + DEFINE_FIELD( numautolayers, FIELD_INTEGER ), + DEFINE_INDEX( autolayerindex, FIELD_INTEGER ), + DEFINE_INDEX( weightlistindex, FIELD_INTEGER ), + DEFINE_INDEX( posekeyindex, FIELD_INTEGER ), + DEFINE_FIELD( numiklocks, FIELD_INTEGER ), + DEFINE_INDEX( iklockindex, FIELD_INTEGER ), + DEFINE_INDEX( keyvalueindex, FIELD_INTEGER ), + DEFINE_FIELD( keyvaluesize, FIELD_INTEGER ), + DEFINE_INDEX( cycleposeindex, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 7 ), // remove/add as appropriate (grow back to 8 ints on version change!) +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioevent_t ) + DEFINE_FIELD( cycle, FIELD_FLOAT ), + DEFINE_FIELD( event, FIELD_INTEGER ), + DEFINE_FIELD( type, FIELD_INTEGER ), + DEFINE_ARRAY( options, FIELD_CHARACTER, 64 ), + DEFINE_INDEX( szeventindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioautolayer_t ) + DEFINE_FIELD( iSequence, FIELD_SHORT ), + DEFINE_FIELD( iPose, FIELD_SHORT ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( start, FIELD_FLOAT ), + DEFINE_FIELD( peak, FIELD_FLOAT ), + DEFINE_FIELD( tail, FIELD_FLOAT ), + DEFINE_FIELD( end, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioiklock_t ) + DEFINE_FIELD( chain, FIELD_INTEGER ), + DEFINE_FIELD( flPosWeight, FIELD_FLOAT ), + DEFINE_FIELD( flLocalQWeight, FIELD_FLOAT ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiobodyparts_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( nummodels, FIELD_INTEGER ), + DEFINE_FIELD( base, FIELD_INTEGER ), + DEFINE_INDEX( modelindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiomodel_t ) + DEFINE_ARRAY( name, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( type, FIELD_INTEGER ), + DEFINE_FIELD( boundingradius, FIELD_FLOAT ), + DEFINE_FIELD( nummeshes, FIELD_INTEGER ), + DEFINE_INDEX( meshindex, FIELD_INTEGER ), + DEFINE_FIELD( numvertices, FIELD_INTEGER ), + DEFINE_INDEX( vertexindex, FIELD_INTEGER ), + DEFINE_INDEX( tangentsindex, FIELD_INTEGER ), + DEFINE_FIELD( numattachments, FIELD_INTEGER ), + DEFINE_INDEX( attachmentindex, FIELD_INTEGER ), + DEFINE_FIELD( numeyeballs, FIELD_INTEGER ), + DEFINE_INDEX( eyeballindex, FIELD_INTEGER ), + DEFINE_EMBEDDED( vertexdata ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 8 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudio_modelvertexdata_t ) + DEFINE_FIELD( pVertexData, FIELD_INTEGER ), // void* + DEFINE_FIELD( pTangentData, FIELD_INTEGER ), // void* +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioflexdesc_t ) + DEFINE_INDEX( szFACSindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioflexcontroller_t ) + DEFINE_INDEX( sztypeindex, FIELD_INTEGER ), + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( localToGlobal, FIELD_INTEGER ), + DEFINE_FIELD( min, FIELD_FLOAT ), + DEFINE_FIELD( max, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioflexcontrollerui_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_INDEX( szindex0, FIELD_INTEGER ), + DEFINE_INDEX( szindex1, FIELD_INTEGER ), + DEFINE_INDEX( szindex2, FIELD_INTEGER ), + DEFINE_FIELD( remaptype, FIELD_CHARACTER ), + DEFINE_FIELD( stereo, FIELD_BOOLEAN ), + DEFINE_ARRAY( unused, FIELD_CHARACTER, 2 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioflexrule_t ) + DEFINE_FIELD( flex, FIELD_INTEGER ), + DEFINE_FIELD( numops, FIELD_INTEGER ), + DEFINE_INDEX( opindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioflexop_t ) + DEFINE_FIELD( op, FIELD_INTEGER ), + DEFINE_FIELD( d, FIELD_INTEGER ), // int/float union +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioikchain_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( linktype, FIELD_INTEGER ), + DEFINE_FIELD( numlinks, FIELD_INTEGER ), + DEFINE_INDEX( linkindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioiklink_t ) + DEFINE_FIELD( bone, FIELD_INTEGER ), + DEFINE_FIELD( kneeDir, FIELD_VECTOR ), + DEFINE_FIELD( unused0, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiomouth_t ) + DEFINE_FIELD( bone, FIELD_INTEGER ), + DEFINE_FIELD( forward, FIELD_VECTOR ), + DEFINE_FIELD( flexdesc, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioposeparamdesc_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( start, FIELD_FLOAT ), + DEFINE_FIELD( end, FIELD_FLOAT ), + DEFINE_FIELD( loop, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiomesh_t ) + DEFINE_FIELD( material, FIELD_INTEGER ), + DEFINE_INDEX( modelindex, FIELD_INTEGER ), + DEFINE_FIELD( numvertices, FIELD_INTEGER ), + DEFINE_FIELD( vertexoffset, FIELD_INTEGER ), + DEFINE_FIELD( numflexes, FIELD_INTEGER ), + DEFINE_INDEX( flexindex, FIELD_INTEGER ), + DEFINE_FIELD( materialtype, FIELD_INTEGER ), + DEFINE_FIELD( materialparam, FIELD_INTEGER ), + DEFINE_FIELD( meshid, FIELD_INTEGER ), + DEFINE_FIELD( center, FIELD_VECTOR ), + DEFINE_EMBEDDED( vertexdata ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 8 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudio_meshvertexdata_t ) + DEFINE_FIELD( modelvertexdata, FIELD_INTEGER ), // mstudio_modelvertexdata_t* + DEFINE_ARRAY( numLODVertexes, FIELD_INTEGER, MAX_NUM_LODS ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioeyeball_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( bone, FIELD_INTEGER ), + DEFINE_FIELD( org, FIELD_VECTOR ), + DEFINE_FIELD( zoffset, FIELD_FLOAT ), + DEFINE_FIELD( radius, FIELD_FLOAT ), + DEFINE_FIELD( up, FIELD_VECTOR ), + DEFINE_FIELD( forward, FIELD_VECTOR ), + DEFINE_FIELD( texture, FIELD_INTEGER ), + DEFINE_FIELD( unused1, FIELD_INTEGER ), + DEFINE_FIELD( iris_scale, FIELD_FLOAT ), + DEFINE_FIELD( unused2, FIELD_INTEGER ), + DEFINE_ARRAY( upperflexdesc, FIELD_INTEGER, 3 ), + DEFINE_ARRAY( lowerflexdesc, FIELD_INTEGER, 3 ), + DEFINE_ARRAY( uppertarget, FIELD_FLOAT, 3 ), + DEFINE_ARRAY( lowertarget, FIELD_FLOAT, 3 ), + DEFINE_FIELD( upperlidflexdesc, FIELD_INTEGER ), + DEFINE_FIELD( lowerlidflexdesc, FIELD_INTEGER ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 4 ), + DEFINE_FIELD( m_bNonFACS, FIELD_BOOLEAN ), + DEFINE_ARRAY( unused3, FIELD_CHARACTER, 3 ), + DEFINE_ARRAY( unused4, FIELD_INTEGER, 7 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioflex_t ) + DEFINE_FIELD( flexdesc, FIELD_INTEGER ), + DEFINE_FIELD( target0, FIELD_FLOAT ), + DEFINE_FIELD( target1, FIELD_FLOAT ), + DEFINE_FIELD( target2, FIELD_FLOAT ), + DEFINE_FIELD( target3, FIELD_FLOAT ), + DEFINE_FIELD( numverts, FIELD_INTEGER ), + DEFINE_INDEX( vertindex, FIELD_INTEGER ), + DEFINE_FIELD( flexpair, FIELD_INTEGER ), + DEFINE_FIELD( vertanimtype, FIELD_CHARACTER ), + DEFINE_ARRAY( unusedchar, FIELD_CHARACTER, 3 ), + DEFINE_ARRAY( unused, FIELD_INTEGER, 6 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiovertanim_t ) + DEFINE_FIELD( index, FIELD_SHORT ), + DEFINE_FIELD( speed, FIELD_CHARACTER ), + DEFINE_FIELD( side, FIELD_CHARACTER ), + DEFINE_ARRAY( delta, FIELD_SHORT, 3 ), // short[3]/float16[3]union + DEFINE_ARRAY( ndelta, FIELD_SHORT, 3 ), // short[3]/float16[3] union +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiomodelgroup_t ) + DEFINE_INDEX( szlabelindex, FIELD_INTEGER ), + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioanimblock_t ) + DEFINE_INDEX( datastart, FIELD_INTEGER ), + DEFINE_INDEX( dataend, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiotexture_t ) + DEFINE_INDEX( sznameindex, FIELD_INTEGER ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( used, FIELD_INTEGER ), + DEFINE_FIELD( unused1, FIELD_INTEGER ), + DEFINE_FIELD( material, FIELD_INTEGER ), // IMaterial* + DEFINE_FIELD( clientmaterial, FIELD_INTEGER ), // void* + DEFINE_ARRAY( unused, FIELD_INTEGER, 10 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( vertexFileHeader_t ) + DEFINE_FIELD( id, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_FIELD( checksum, FIELD_INTEGER ), + DEFINE_FIELD( numLODs, FIELD_INTEGER ), + DEFINE_ARRAY( numLODVertexes, FIELD_INTEGER, MAX_NUM_LODS ), + DEFINE_FIELD( numFixups, FIELD_INTEGER ), + DEFINE_FIELD( fixupTableStart, FIELD_INTEGER ), + DEFINE_FIELD( vertexDataStart, FIELD_INTEGER ), + DEFINE_FIELD( tangentDataStart, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( vertexFileFixup_t ) + DEFINE_FIELD( lod, FIELD_INTEGER ), + DEFINE_FIELD( sourceVertexID, FIELD_INTEGER ), + DEFINE_FIELD( numVertexes, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudioboneweight_t ) + DEFINE_ARRAY( weight, FIELD_FLOAT, MAX_NUM_BONES_PER_VERT ), + DEFINE_ARRAY( bone, FIELD_CHARACTER, MAX_NUM_BONES_PER_VERT ), + DEFINE_FIELD( numbones, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( mstudiovertex_t ) + DEFINE_EMBEDDED( m_BoneWeights ), + DEFINE_FIELD( m_vecPosition, FIELD_VECTOR ), + DEFINE_FIELD( m_vecNormal, FIELD_VECTOR ), + DEFINE_ARRAY( m_vecTexCoord, FIELD_FLOAT, 2 ), +END_BYTESWAP_DATADESC() + +// Data descriptions from OptimizedModel.h +namespace OptimizedModel +{ + +BEGIN_BYTESWAP_DATADESC( BoneStateChangeHeader_t ) + DEFINE_FIELD( hardwareID, FIELD_INTEGER ), + DEFINE_FIELD( newBoneID, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( Vertex_t ) + DEFINE_ARRAY( boneWeightIndex, FIELD_CHARACTER, MAX_NUM_BONES_PER_VERT ), + DEFINE_FIELD( numBones, FIELD_CHARACTER ), + DEFINE_FIELD( origMeshVertID, FIELD_SHORT ), + DEFINE_ARRAY( boneID, FIELD_CHARACTER, MAX_NUM_BONES_PER_VERT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StripHeader_t ) + DEFINE_FIELD( numIndices, FIELD_INTEGER ), + DEFINE_FIELD( indexOffset, FIELD_INTEGER ), + DEFINE_FIELD( numVerts, FIELD_INTEGER ), + DEFINE_FIELD( vertOffset, FIELD_INTEGER ), + DEFINE_FIELD( numBones, FIELD_SHORT ), + DEFINE_FIELD( flags, FIELD_CHARACTER ), + DEFINE_FIELD( numBoneStateChanges, FIELD_INTEGER ), + DEFINE_FIELD( boneStateChangeOffset, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StripGroupHeader_t ) + DEFINE_FIELD( numVerts, FIELD_INTEGER ), + DEFINE_FIELD( vertOffset, FIELD_INTEGER ), + DEFINE_FIELD( numIndices, FIELD_INTEGER ), + DEFINE_FIELD( indexOffset, FIELD_INTEGER ), + DEFINE_FIELD( numStrips, FIELD_INTEGER ), + DEFINE_FIELD( stripOffset, FIELD_INTEGER ), + DEFINE_FIELD( flags, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( MeshHeader_t ) + DEFINE_FIELD( numStripGroups, FIELD_INTEGER ), + DEFINE_FIELD( stripGroupHeaderOffset, FIELD_INTEGER ), + DEFINE_FIELD( flags, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( ModelLODHeader_t ) + DEFINE_FIELD( numMeshes, FIELD_INTEGER ), + DEFINE_FIELD( meshOffset, FIELD_INTEGER ), + DEFINE_FIELD( switchPoint, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( ModelHeader_t ) + DEFINE_FIELD( numLODs, FIELD_INTEGER ), + DEFINE_FIELD( lodOffset, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( BodyPartHeader_t ) + DEFINE_FIELD( numModels, FIELD_INTEGER ), + DEFINE_FIELD( modelOffset, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( MaterialReplacementHeader_t ) + DEFINE_FIELD( materialID, FIELD_SHORT ), + DEFINE_FIELD( replacementMaterialNameOffset, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( MaterialReplacementListHeader_t ) + DEFINE_FIELD( numReplacements, FIELD_INTEGER ), + DEFINE_FIELD( replacementOffset, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( FileHeader_t ) + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_FIELD( vertCacheSize, FIELD_INTEGER ), + DEFINE_FIELD( maxBonesPerStrip, FIELD_SHORT ), + DEFINE_FIELD( maxBonesPerTri, FIELD_SHORT ), + DEFINE_FIELD( maxBonesPerVert, FIELD_INTEGER ), + DEFINE_FIELD( checkSum, FIELD_INTEGER ), + DEFINE_FIELD( numLODs, FIELD_INTEGER ), + DEFINE_FIELD( materialReplacementListOffset, FIELD_INTEGER ), + DEFINE_FIELD( numBodyParts, FIELD_INTEGER ), + DEFINE_FIELD( bodyPartOffset, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +} // namespace OptimizedModel + +// Data descriptions from phyfile.h +BEGIN_BYTESWAP_DATADESC( phyheader_t ) + DEFINE_FIELD( size, FIELD_INTEGER ), + DEFINE_FIELD( id, FIELD_INTEGER ), + DEFINE_FIELD( solidCount, FIELD_INTEGER ), + DEFINE_FIELD( checkSum, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() diff --git a/common/studiobyteswap.h b/common/studiobyteswap.h new file mode 100644 index 00000000..7f51f458 --- /dev/null +++ b/common/studiobyteswap.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2006, Valve LLC, All rights reserved. ============ +// +// Purpose: StudioMDL byteswapping functions. +// +// $NoKeywords: $ +//============================================================================= +#ifndef STUDIOBYTESWAP_H +#define STUDIOBYTESWAP_H + +#if defined(_WIN32) +#pragma once +#endif + +#include "byteswap.h" +struct studiohdr_t; +class IPhysicsCollision; + +namespace StudioByteSwap +{ +typedef bool (*CompressFunc_t)( const void *pInput, int inputSize, void **pOutput, int *pOutputSize ); + +//void SetTargetBigEndian( bool bigEndian ); +void ActivateByteSwapping( bool bActivate ); +void SourceIsNative( bool bActivate ); +void SetVerbose( bool bVerbose ); +void SetCollisionInterface( IPhysicsCollision *pPhysicsCollision ); + +int ByteswapStudioFile( const char *pFilename, void *pOutBase, const void *pFileBase, int fileSize, studiohdr_t *pHdr, CompressFunc_t pCompressFunc = NULL ); +int ByteswapPHY( void *pOutBase, const void *pFileBase, int fileSize ); +int ByteswapANI( studiohdr_t* pHdr, void *pOutBase, const void *pFileBase, int filesize ); +int ByteswapVVD( void *pOutBase, const void *pFileBase, int fileSize ); +int ByteswapVTX( void *pOutBase, const void *pFileBase, int fileSize ); +int ByteswapMDL( void *pOutBase, const void *pFileBase, int fileSize ); + +#define BYTESWAP_ALIGNMENT_PADDING 4096 +} + +#endif // STUDIOBYTESWAP_H \ No newline at end of file diff --git a/common/xbox/xboxstubs.h b/common/xbox/xboxstubs.h new file mode 100644 index 00000000..29f31902 --- /dev/null +++ b/common/xbox/xboxstubs.h @@ -0,0 +1,236 @@ +//========= Copyright © 1996-2004, Valve LLC, All rights reserved. ============ +// +// Purpose: Win32 replacements for XBox. +// +//============================================================================= + +#if !defined( XBOXSTUBS_H ) && !defined( _X360 ) +#define XBOXSTUBS_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/platform.h" + +// Content creation/open flags +#define XCONTENTFLAG_NONE 0x00 +#define XCONTENTFLAG_CREATENEW 0x00 +#define XCONTENTFLAG_CREATEALWAYS 0x00 +#define XCONTENTFLAG_OPENEXISTING 0x00 +#define XCONTENTFLAG_OPENALWAYS 0x00 +#define XCONTENTFLAG_TRUNCATEEXISTING 0x00 + +// Content attributes +#define XCONTENTFLAG_NOPROFILE_TRANSFER 0x00 +#define XCONTENTFLAG_NODEVICE_TRANSFER 0x00 +#define XCONTENTFLAG_STRONG_SIGNED 0x00 +#define XCONTENTFLAG_ALLOWPROFILE_TRANSFER 0x00 +#define XCONTENTFLAG_MOVEONLY_TRANSFER 0x00 + +// Console device ports +#define XDEVICE_PORT0 0 +#define XDEVICE_PORT1 1 +#define XDEVICE_PORT2 2 +#define XDEVICE_PORT3 3 +#define XUSER_MAX_COUNT 4 +#define XUSER_INDEX_NONE 0x000000FE + +#define XBX_CLR_DEFAULT 0xFF000000 +#define XBX_CLR_WARNING 0x0000FFFF +#define XBX_CLR_ERROR 0x000000FF + +#define XBOX_MINBORDERSAFE 0 +#define XBOX_MAXBORDERSAFE 0 + +typedef enum +{ + XK_NULL, + XK_BUTTON_UP, + XK_BUTTON_DOWN, + XK_BUTTON_LEFT, + XK_BUTTON_RIGHT, + XK_BUTTON_START, + XK_BUTTON_BACK, + XK_BUTTON_STICK1, + XK_BUTTON_STICK2, + XK_BUTTON_A, + XK_BUTTON_B, + XK_BUTTON_X, + XK_BUTTON_Y, + XK_BUTTON_LEFT_SHOULDER, + XK_BUTTON_RIGHT_SHOULDER, + XK_BUTTON_LTRIGGER, + XK_BUTTON_RTRIGGER, + XK_STICK1_UP, + XK_STICK1_DOWN, + XK_STICK1_LEFT, + XK_STICK1_RIGHT, + XK_STICK2_UP, + XK_STICK2_DOWN, + XK_STICK2_LEFT, + XK_STICK2_RIGHT, + XK_MAX_KEYS, +} xKey_t; + +//typedef enum +//{ +// XVRB_NONE, // off +// XVRB_ERROR, // fatal error +// XVRB_ALWAYS, // no matter what +// XVRB_WARNING, // non-fatal warnings +// XVRB_STATUS, // status reports +// XVRB_ALL, +//} xverbose_e; + +typedef unsigned short WORD; +#ifndef _LINUX +typedef unsigned long DWORD; +typedef void* HANDLE; +#endif + +#ifdef _LINUX +typedef int32 COLORREF; +#endif + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE ((HANDLE)-1) +#endif + +// typedef struct { +// IN_ADDR ina; // IP address (zero if not static/DHCP) +// IN_ADDR inaOnline; // Online IP address (zero if not online) +// WORD wPortOnline; // Online port +// BYTE abEnet[6]; // Ethernet MAC address +// BYTE abOnline[20]; // Online identification +// } XNADDR; + +typedef int XNADDR; +typedef uint64 XUID; + +typedef struct { + BYTE ab[8]; // xbox to xbox key identifier +} XNKID; + +typedef struct { + BYTE ab[16]; // xbox to xbox key exchange key +} XNKEY; + +typedef struct _XSESSION_INFO +{ + XNKID sessionID; // 8 bytes + XNADDR hostAddress; // 36 bytes + XNKEY keyExchangeKey; // 16 bytes +} XSESSION_INFO, *PXSESSION_INFO; + +typedef struct _XUSER_DATA +{ + BYTE type; + + union + { + int nData; // XUSER_DATA_TYPE_INT32 + int64 i64Data; // XUSER_DATA_TYPE_INT64 + double dblData; // XUSER_DATA_TYPE_DOUBLE + struct // XUSER_DATA_TYPE_UNICODE + { + uint cbData; // Includes null-terminator + char * pwszData; + } string; + float fData; // XUSER_DATA_TYPE_FLOAT + struct // XUSER_DATA_TYPE_BINARY + { + uint cbData; + char * pbData; + } binary; + }; +} XUSER_DATA, *PXUSER_DATA; + +typedef struct _XUSER_PROPERTY +{ + DWORD dwPropertyId; + XUSER_DATA value; +} XUSER_PROPERTY, *PXUSER_PROPERTY; + +typedef struct _XUSER_CONTEXT +{ + DWORD dwContextId; + DWORD dwValue; +} XUSER_CONTEXT, *PXUSER_CONTEXT; + +typedef struct _XSESSION_SEARCHRESULT +{ + XSESSION_INFO info; + DWORD dwOpenPublicSlots; + DWORD dwOpenPrivateSlots; + DWORD dwFilledPublicSlots; + DWORD dwFilledPrivateSlots; + DWORD cProperties; + DWORD cContexts; + PXUSER_PROPERTY pProperties; + PXUSER_CONTEXT pContexts; +} XSESSION_SEARCHRESULT, *PXSESSION_SEARCHRESULT; + +typedef struct _XSESSION_SEARCHRESULT_HEADER +{ + DWORD dwSearchResults; + XSESSION_SEARCHRESULT *pResults; +} XSESSION_SEARCHRESULT_HEADER, *PXSESSION_SEARCHRESULT_HEADER; + +typedef struct _XSESSION_REGISTRANT +{ + uint64 qwMachineID; + DWORD bTrustworthiness; + DWORD bNumUsers; + XUID *rgUsers; + +} XSESSION_REGISTRANT; + +typedef struct _XSESSION_REGISTRATION_RESULTS +{ + DWORD wNumRegistrants; + XSESSION_REGISTRANT *rgRegistrants; +} XSESSION_REGISTRATION_RESULTS, *PXSESSION_REGISTRATION_RESULTS; + +typedef struct { + BYTE bFlags; + BYTE bReserved; + WORD cProbesXmit; + WORD cProbesRecv; + WORD cbData; + BYTE * pbData; + WORD wRttMinInMsecs; + WORD wRttMedInMsecs; + DWORD dwUpBitsPerSec; + DWORD dwDnBitsPerSec; +} XNQOSINFO; + +typedef struct { + uint cxnqos; + uint cxnqosPending; + XNQOSINFO axnqosinfo[1]; +} XNQOS; + +#define XSESSION_CREATE_HOST 0 +#define XUSER_DATA_TYPE_INT32 0 +#define XSESSION_CREATE_USES_ARBITRATION 0 +#define XNET_QOS_LISTEN_ENABLE 0 +#define XNET_QOS_LISTEN_DISABLE 0 +#define XNET_QOS_LISTEN_SET_DATA 0 + +FORCEINLINE void XBX_ProcessEvents() {} +FORCEINLINE unsigned int XBX_GetSystemTime() { return 0; } +FORCEINLINE int XBX_GetPrimaryUserId() { return 0; } +FORCEINLINE void XBX_SetPrimaryUserId( DWORD idx ) {} +FORCEINLINE int XBX_GetStorageDeviceId() { return 0; } +FORCEINLINE void XBX_SetStorageDeviceId( DWORD idx ) {} +FORCEINLINE const char *XBX_GetLanguageString() { return ""; } +FORCEINLINE bool XBX_IsLocalized() { return false; } + +#define XCONTENT_MAX_DISPLAYNAME_LENGTH 128 +#define XCONTENT_MAX_FILENAME_LENGTH 42 + +#define XBX_INVALID_STORAGE_ID ((DWORD) -1) +#define XBX_STORAGE_DECLINED ((DWORD) -2) + +#endif // XBOXSTUBS_H diff --git a/devtools/bin/fxc_prep.pl b/devtools/bin/fxc_prep.pl new file mode 100644 index 00000000..adc51ad8 --- /dev/null +++ b/devtools/bin/fxc_prep.pl @@ -0,0 +1,949 @@ +BEGIN {use File::Basename; push @INC, dirname($0); } +require "valve_perl_helpers.pl"; + +sub ReadInputFile +{ + local( $filename ) = shift; + local( *INPUT ); + local( @output ); + open INPUT, "<$filename" || die; + + local( $line ); + local( $linenum ) = 1; + while( $line = ) + { +# print "LINE: $line"; +# $line =~ s/\n//g; +# local( $postfix ) = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; +# $postfix .= "; LINEINFO($filename)($linenum)\n"; + if( $line =~ m/\#include\s+\"(.*)\"/i ) + { + push @output, &ReadInputFile( $1 ); + } + else + { +# push @output, $line . $postfix; + push @output, $line; + } + $linenum++; + } + + close INPUT; +# print "-----------------\n"; +# print @output; +# print "-----------------\n"; + return @output; +} + +$dynamic_compile = defined $ENV{"dynamic_shaders"} && $ENV{"dynamic_shaders"} != 0; +$generateListingFile = 0; +$spewCombos = 0; + +@startTimes = times; +$startTime = time; + +$g_produceCppClasses = 1; +$g_produceCompiledVcs = 1; + +while( 1 ) +{ + $fxc_filename = shift; + if( $fxc_filename =~ m/-source/ ) + { + shift; + } + elsif( $fxc_filename =~ m/-nv3x/i ) + { + $nvidia = 1; + } + elsif( $fxc_filename =~ m/-ps20a/i ) + { + $ps2a = 1; + } + elsif( $fxc_filename =~ m/-x360/i ) + { + # enable x360 + $g_x360 = 1; + } + elsif( $fxc_filename =~ m/-novcs/i ) + { + $g_produceCompiledVcs = 0; + } + elsif( $fxc_filename =~ m/-nocpp/i ) + { + $g_produceCppClasses = 0; + } + else + { + last; + } +} + +$argstring = $fxc_filename; +$fxc_basename = $fxc_filename; +$fxc_basename =~ s/^.*-----//; +$fxc_filename =~ s/-----.*$//; + +$debug = 0; +$forcehalf = 0; + +sub ToUpper +{ + local( $in ) = shift; + $in =~ tr/a-z/A-Z/; + return $in; +} + +sub CreateCCodeToSpewDynamicCombo +{ + local( $out ) = ""; + + $out .= "\t\tOutputDebugString( \"src:$fxc_filename vcs:$fxc_basename dynamic index\" );\n"; + $out .= "\t\tchar tmp[128];\n"; + $out .= "\t\tint shaderID = "; + local( $scale ) = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + local( $name ) = @dynamicDefineNames[$i]; + local( $varname ) = "m_n" . $name; + $out .= "( $scale * $varname ) + "; + $scale *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + $out .= "0;\n"; + if( scalar( @dynamicDefineNames ) + scalar( @staticDefineNames ) > 0 ) + { + $out .= "\t\tint nCombo = shaderID;\n"; + } + + my $type = GetShaderType( $fxc_filename ); + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $out .= "\t\tint n$dynamicDefineNames[$i] = nCombo % "; + $out .= ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) + $dynamicDefineMin[$i]; + $out .= ";\n"; + + $out .= "\t\tsprintf( tmp, \"\%d\", n$dynamicDefineNames[$i] );\n"; + $out .= "\t\tOutputDebugString( \" $dynamicDefineNames[$i]"; + $out .= "=\" );\n"; + $out .= "\t\tOutputDebugString( tmp );\n"; + + $out .= "\t\tnCombo = nCombo / " . ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) . ";\n"; + $out .= "\n"; + } + $out .= "\t\tOutputDebugString( \"\\n\" );\n"; + return $out; +} + +sub CreateCCodeToSpewStaticCombo +{ + local( $out ) = ""; + + $out .= "\t\tOutputDebugString( \"src:$fxc_filename vcs:$fxc_basename static index\" );\n"; + $out .= "\t\tchar tmp[128];\n"; + $out .= "\t\tint shaderID = "; + + local( $scale ) = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $scale *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + local( $name ) = @staticDefineNames[$i]; + local( $varname ) = "m_n" . $name; + $out .= "( $scale * $varname ) + "; + $scale *= $staticDefineMax[$i] - $staticDefineMin[$i] + 1; + } + $out .= "0;\n"; + +# $out .= "\t\tsprintf( tmp, \"\%d\\n\", shaderID );\n"; +# $out .= "\t\tOutputDebugString( tmp );\n\n"; + if( scalar( @staticDefineNames ) + scalar( @staticDefineNames ) > 0 ) + { + $out .= "\t\tint nCombo = shaderID;\n"; + } + + my $type = GetShaderType( $fxc_filename ); + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $out .= "\t\tnCombo = nCombo / " . ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) . ";\n"; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $out .= "\t\tint n$staticDefineNames[$i] = nCombo % "; + $out .= ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) + $staticDefineMin[$i]; + $out .= ";\n"; + + $out .= "\t\tsprintf( tmp, \"\%d\", n$staticDefineNames[$i] );\n"; + $out .= "\t\tOutputDebugString( \" $staticDefineNames[$i]"; + $out .= "=\" );\n"; + $out .= "\t\tOutputDebugString( tmp );\n"; + + $out .= "\t\tnCombo = nCombo / " . ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) . ";\n"; + $out .= "\n"; + } + $out .= "\t\tOutputDebugString( \"\\n\" );\n"; + return $out; +} + +sub WriteHelperVar +{ + local( $name ) = shift; + local( $min ) = shift; + local( $max ) = shift; + local( $varname ) = "m_n" . $name; + local( $boolname ) = "m_b" . $name; + push @outputHeader, "private:\n"; + push @outputHeader, "\tint $varname;\n"; + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\tbool $boolname;\n"; + push @outputHeader, "#endif\n"; + push @outputHeader, "public:\n"; + # int version of set function + push @outputHeader, "\tvoid Set" . $name . "( int i )\n"; + push @outputHeader, "\t{\n"; + push @outputHeader, "\t\tAssert( i >= $min && i <= $max );\n"; + push @outputHeader, "\t\t$varname = i;\n"; + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\t\t$boolname = true;\n"; + push @outputHeader, "#endif\n"; + push @outputHeader, "\t}\n"; + # bool version of set function + push @outputHeader, "\tvoid Set" . $name . "( bool i )\n"; + push @outputHeader, "\t{\n"; +# push @outputHeader, "\t\tAssert( i >= $min && i <= $max );\n"; + push @outputHeader, "\t\t$varname = i ? 1 : 0;\n"; + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\t\t$boolname = true;\n"; + push @outputHeader, "#endif\n"; + push @outputHeader, "\t}\n"; +} + +sub WriteStaticBoolExpression +{ + local( $prefix ) = shift; + local( $operator ) = shift; + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + if( $i ) + { + push @outputHeader, " $operator "; + } + local( $name ) = @staticDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + push @outputHeader, "$prefix$boolname"; + } + push @outputHeader, ";\n"; +} + +sub WriteDynamicBoolExpression +{ + local( $prefix ) = shift; + local( $operator ) = shift; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + if( $i ) + { + push @outputHeader, " $operator "; + } + local( $name ) = @dynamicDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + push @outputHeader, "$prefix$boolname"; + } + push @outputHeader, ";\n"; +} + +sub WriteDynamicHelperClasses +{ + local( $basename ) = $fxc_basename; + $basename =~ tr/A-Z/a-z/; + local( $classname ) = $basename . "_Dynamic_Index"; + push @outputHeader, "class $classname\n"; + push @outputHeader, "{\n"; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $name = $dynamicDefineNames[$i]; + $min = $dynamicDefineMin[$i]; + $max = $dynamicDefineMax[$i]; + &WriteHelperVar( $name, $min, $max ); + } + push @outputHeader, "public:\n"; +# push @outputHeader, "void SetPixelShaderIndex( IShaderAPI *pShaderAPI ) { pShaderAPI->SetPixelShaderIndex( GetIndex() ); }\n"; + push @outputHeader, "\t$classname()\n"; + push @outputHeader, "\t{\n"; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + local( $name ) = @dynamicDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + local( $varname ) = "m_n" . $name; + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\t\t$boolname = false;\n"; + push @outputHeader, "#endif // _DEBUG\n"; + push @outputHeader, "\t\t$varname = 0;\n"; + } + push @outputHeader, "\t}\n"; + push @outputHeader, "\tint GetIndex()\n"; + push @outputHeader, "\t{\n"; + push @outputHeader, "\t\t// Asserts to make sure that we aren't using any skipped combinations.\n"; + foreach $skip (@perlskipcodeindividual) + { + # can't do this static and dynamic can see each other. +# $skip =~ s/\$/m_n/g; +# $skip =~ s/defined//g; +# push @outputHeader, "\t\tAssert( !( $skip ) );\n"; +# print "\t\tAssert( !( $skip ) );\n"; + } + push @outputHeader, "\t\t// Asserts to make sure that we are setting all of the combination vars.\n"; + + push @outputHeader, "#ifdef _DEBUG\n"; + if( scalar( @dynamicDefineNames ) > 0 ) + { + push @outputHeader, "\t\tbool bAllDynamicVarsDefined = "; + WriteDynamicBoolExpression( "", "&&" ); + } + if( scalar( @dynamicDefineNames ) > 0 ) + { + push @outputHeader, "\t\tAssert( bAllDynamicVarsDefined );\n"; + } + push @outputHeader, "#endif // _DEBUG\n"; + + if( $spewCombos && scalar( @dynamicDefineNames ) ) + { + push @outputHeader, &CreateCCodeToSpewDynamicCombo(); + } + push @outputHeader, "\t\treturn "; + local( $scale ) = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + local( $name ) = @dynamicDefineNames[$i]; + local( $varname ) = "m_n" . $name; + push @outputHeader, "( $scale * $varname ) + "; + $scale *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + push @outputHeader, "0;\n"; + push @outputHeader, "\t}\n"; + push @outputHeader, "};\n"; + push @outputHeader, "\#define shaderDynamicTest_" . $basename . " "; + my $prefix; + my $shaderType = &GetShaderType( $fxc_filename ); + if( $shaderType =~ m/^vs/i ) + { + $prefix = "vsh_"; + } + else + { + $prefix = "psh_"; + } + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + local( $name ) = @dynamicDefineNames[$i]; + push @outputHeader, $prefix . "forgot_to_set_dynamic_" . $name . " + "; + } + push @outputHeader, "0\n"; +} + +sub WriteStaticHelperClasses +{ + local( $basename ) = $fxc_basename; + $basename =~ tr/A-Z/a-z/; + local( $classname ) = $basename . "_Static_Index"; + push @outputHeader, "#include \"shaderlib/cshader.h\"\n"; + push @outputHeader, "class $classname\n"; + push @outputHeader, "{\n"; + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $name = $staticDefineNames[$i]; + $min = $staticDefineMin[$i]; + $max = $staticDefineMax[$i]; + &WriteHelperVar( $name, $min, $max ); + } + push @outputHeader, "public:\n"; +# push @outputHeader, "void SetShaderIndex( IShaderShadow *pShaderShadow ) { pShaderShadow->SetPixelShaderIndex( GetIndex() ); }\n"; + push @outputHeader, "\t$classname( )\n"; + push @outputHeader, "\t{\n"; + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + local( $name ) = @staticDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + local( $varname ) = "m_n" . $name; + if ( length( $staticDefineInit{$name} ) ) + { + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\t\t$boolname = true;\n"; + push @outputHeader, "#endif // _DEBUG\n"; + push @outputHeader, "\t\t$varname = $staticDefineInit{$name};\n"; + } + else + { + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\t\t$boolname = false;\n"; + push @outputHeader, "#endif // _DEBUG\n"; + push @outputHeader, "\t\t$varname = 0;\n"; + } + } + push @outputHeader, "\t}\n"; + push @outputHeader, "\tint GetIndex()\n"; + push @outputHeader, "\t{\n"; + push @outputHeader, "\t\t// Asserts to make sure that we aren't using any skipped combinations.\n"; + foreach $skip (@perlskipcodeindividual) + { + $skip =~ s/\$/m_n/g; +# push @outputHeader, "\t\tAssert( !( $skip ) );\n"; + } + push @outputHeader, "\t\t// Asserts to make sure that we are setting all of the combination vars.\n"; + + push @outputHeader, "#ifdef _DEBUG\n"; + if( scalar( @staticDefineNames ) > 0 ) + { + push @outputHeader, "\t\tbool bAllStaticVarsDefined = "; + WriteStaticBoolExpression( "", "&&" ); + + } + if( scalar( @staticDefineNames ) > 0 ) + { + push @outputHeader, "\t\tAssert( bAllStaticVarsDefined );\n"; + } + push @outputHeader, "#endif // _DEBUG\n"; + + if( $spewCombos && scalar( @staticDefineNames ) ) + { + push @outputHeader, &CreateCCodeToSpewStaticCombo(); + } + push @outputHeader, "\t\treturn "; + local( $scale ) = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $scale *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + local( $name ) = @staticDefineNames[$i]; + local( $varname ) = "m_n" . $name; + push @outputHeader, "( $scale * $varname ) + "; + $scale *= $staticDefineMax[$i] - $staticDefineMin[$i] + 1; + } + push @outputHeader, "0;\n"; + push @outputHeader, "\t}\n"; + push @outputHeader, "};\n"; + push @outputHeader, "\#define shaderStaticTest_" . $basename . " "; + my $prefix; + my $shaderType = &GetShaderType( $fxc_filename ); + if( $shaderType =~ m/^vs/i ) + { + $prefix = "vsh_"; + } + else + { + $prefix = "psh_"; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + local( $name ) = @staticDefineNames[$i]; + push @outputHeader, $prefix . "forgot_to_set_static_" . $name . " + " unless (length($staticDefineInit{$name} )); + } + push @outputHeader, "0\n"; +} + +sub GetNewMainName +{ + local( $shadername ) = shift; + local( $combo ) = shift; + local( $i ); + $shadername =~ s/\./_/g; + local( $name ) = $shadername; + for( $i = 0; $i < scalar( @defineNames ); $i++ ) + { + local( $val ) = ( $combo % ( $defineMax[$i] - $defineMin[$i] + 1 ) ) + $defineMin[$i]; + $name .= "_" . $defineNames[$i] . "_" . $val; + $combo = $combo / ( $defineMax[$i] - $defineMin[$i] + 1 ); + } +# return $name; + return "main"; +} + +sub RenameMain +{ + local( $shadername ) = shift; + local( $combo ) = shift; + local( $name ) = &GetNewMainName( $shadername, $combo ); + return "/Dmain=$name /E$name "; +} + +sub GetShaderType +{ + local( $shadername ) = shift; # hack - use global variables + $shadername = $fxc_basename; + if( $shadername =~ m/ps30/i ) + { + if( $debug ) + { + return "ps_3_sw"; + } + else + { + return "ps_3_0"; + } + } + elsif( $shadername =~ m/ps20b/i ) + { + return "ps_2_b"; + } + elsif( $shadername =~ m/ps20/i ) + { + if( $debug ) + { + return "ps_2_sw"; + } + else + { + if( $ps2a ) + { + return "ps_2_a"; + } + else + { + return "ps_2_0"; + } + } + } + elsif( $shadername =~ m/ps14/i ) + { + return "ps_1_4"; + } + elsif( $shadername =~ m/ps11/i ) + { + return "ps_1_1"; + } + elsif( $shadername =~ m/vs30/i ) + { + if( $debug ) + { + return "vs_3_sw"; + } + else + { + return "vs_3_0"; + } + } + elsif( $shadername =~ m/vs20/i ) + { + if( $debug ) + { + return "vs_2_sw"; + } + else + { + return "vs_2_0"; + } + } + elsif( $shadername =~ m/vs14/i ) + { + return "vs_1_1"; + } + elsif( $shadername =~ m/vs11/i ) + { + return "vs_1_1"; + } + else + { + die "\n\nSHADERNAME = $shadername\n\n"; + } +} + +sub CalcNumCombos +{ + local( $i, $numCombos ); + $numCombos = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $numCombos *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $numCombos *= $staticDefineMax[$i] - $staticDefineMin[$i] + 1; + } + return $numCombos; +} + +sub CalcNumDynamicCombos +{ + local( $i, $numCombos ); + $numCombos = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $numCombos *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + return $numCombos; +} + +sub CreateCFuncToCreateCompileCommandLine +{ + local( $out ) = ""; + + $out .= "\t\tOutputDebugString( \"compiling src:$fxc_filename vcs:$fxc_basename \" );\n"; + $out .= "\t\tchar tmp[128];\n"; + $out .= "\t\tsprintf( tmp, \"\%d\\n\", shaderID );\n"; + $out .= "\t\tOutputDebugString( tmp );\n"; + $out .= "\t\tstatic PrecompiledShaderByteCode_t byteCode;\n"; + if( scalar( @dynamicDefineNames ) + scalar( @staticDefineNames ) > 0 ) + { + $out .= "\t\tint nCombo = shaderID;\n"; + } + +# $out .= "\tvoid BuildCompileCommandLine( int nCombo, char *pResult, int maxLength )\n"; +# $out .= "\t{\n"; + $out .= "\t\tD3DXMACRO "; + $out .= "defineMacros"; + $out .= "["; + $out .= scalar( @dynamicDefineNames ) + scalar( @staticDefineNames ) + 1; # add 1 for null termination + $out .= "];\n"; + if( scalar( @dynamicDefineNames ) + scalar( @staticDefineNames ) > 0 ) + { + $out .= "\t\tchar tmpStringBuf[1024];\n"; + $out .= "\t\tchar *pTmpString = tmpStringBuf;\n\n"; + } + + local( $i ); + my $type = GetShaderType( $fxc_filename ); + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $out .= "\t\tsprintf( pTmpString, \"%d\", nCombo % "; + $out .= ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) + $dynamicDefineMin[$i]; + $out .= " );\n"; + $out .= "\t\tdefineMacros"; + $out .= "["; + $out .= $i; + $out .= "]"; + $out .= "\.Name = "; + $out .= "\"$dynamicDefineNames[$i]\";\n"; + + $out .= "\t\tint n$dynamicDefineNames[$i] = nCombo % "; + $out .= ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) + $dynamicDefineMin[$i]; + $out .= ";\n"; + $out .= "\t\tUNUSED( n$dynamicDefineNames[$i] );\n"; + + $out .= "\t\tdefineMacros"; + $out .= "["; + $out .= $i; + $out .= "]"; + $out .= "\.Definition = "; + $out .= "pTmpString;\n"; + $out .= "\t\tpTmpString += strlen( pTmpString ) + 1;\n"; + + $out .= "\t\tsprintf( tmp, \"\%d\", n$dynamicDefineNames[$i] );\n"; + $out .= "\t\tOutputDebugString( \" $dynamicDefineNames[$i]"; + $out .= "=\" );\n"; + $out .= "\t\tOutputDebugString( tmp );\n"; + + $out .= "\t\tnCombo = nCombo / " . ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) . ";\n"; + $out .= "\n"; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $out .= "\t\tsprintf( pTmpString, \"%d\", nCombo % "; + $out .= ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) + $staticDefineMin[$i]; + $out .= " );\n"; + $out .= "\t\tdefineMacros"; + $out .= "["; + $out .= $i + scalar( @dynamicDefineNames ); + $out .= "]"; + $out .= "\.Name = "; + $out .= "\"$staticDefineNames[$i]\";\n"; + + $out .= "\t\tint n$staticDefineNames[$i] = nCombo % "; + $out .= ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) + $staticDefineMin[$i]; + $out .= ";\n"; + $out .= "\t\tUNUSED( n$staticDefineNames[$i] );\n"; + + $out .= "\t\tdefineMacros"; + $out .= "["; + $out .= $i + scalar( @dynamicDefineNames ); + $out .= "]"; + $out .= "\.Definition = "; + $out .= "pTmpString;\n"; + $out .= "\t\tpTmpString += strlen( pTmpString ) + 1;\n"; + + $out .= "\t\tsprintf( tmp, \"\%d\", n$staticDefineNames[$i] );\n"; + $out .= "\t\tOutputDebugString( \" $staticDefineNames[$i]"; + $out .= "=\" );\n"; + $out .= "\t\tOutputDebugString( tmp );\n"; + + $out .= "\t\tnCombo = nCombo / " . ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) . ";\n"; + $out .= "\n"; + } + + $out .= "\t\tOutputDebugString( \"\\n\" );\n"; + + $cskipcode = $perlskipcode; + $cskipcode =~ s/\$/n/g; + $out .= "\t\tif( $cskipcode )\n\t\t{\n"; + $out .= "\t\t\tstatic char blah[4] = { 0, 0, 0, 0 };\n"; + $out .= "\t\t\tbyteCode.m_pRawData = blah;\n"; + $out .= "\t\t\tbyteCode.m_nSizeInBytes = 4;\n"; + $out .= "\t\t\treturn byteCode;\n"; + $out .= "\t\t}\n"; + + + + $out .= "\t\t// Must null terminate macros.\n"; + $out .= "\t\tdefineMacros["; + $out .= scalar( @dynamicDefineNames ) + scalar( @staticDefineNames ); + $out .= "]"; + $out .= ".Name = NULL;\n"; + $out .= "\t\tdefineMacros["; + $out .= scalar( @dynamicDefineNames ) + scalar( @staticDefineNames ); + $out .= "]"; + $out .= ".Definition = NULL;\n\n"; + + + $out .= "\t\tLPD3DXBUFFER pShader; // NOTE: THESE LEAK!!!\n"; + $out .= "\t\tLPD3DXBUFFER pErrorMessages; // NOTE: THESE LEAK!!!\n"; + $out .= "\t\tHRESULT hr = D3DXCompileShaderFromFile( \"u:\\\\hl2_e3_2004\\\\src_e3_2004\\\\materialsystem\\\\stdshaders\\\\$fxc_filename\",\n\t\t\tdefineMacros,\n\t\t\tNULL, // LPD3DXINCLUDE \n\t\t\t\"main\",\n\t\t\t\"$type\",\n\t\t\t0, // DWORD Flags\n\t\t\t&pShader,\n\t\t\t&pErrorMessages,\n\t\t\tNULL // LPD3DXCONSTANTTABLE *ppConstantTable\n\t\t\t );\n"; + $out .= "\t\tif( hr != D3D_OK )\n"; + $out .= "\t\t{\n"; + $out .= "\t\t\tconst char *pErrorMessageString = ( const char * )pErrorMessages->GetBufferPointer();\n"; + $out .= "\t\t\tOutputDebugString( pErrorMessageString );\n"; + $out .= "\t\t\tOutputDebugString( \"\\n\" );\n"; + $out .= "\t\t\tAssert( 0 );\n"; + $out .= "\t\t\tstatic char blah[4] = { 0, 0, 0, 0 };\n"; + $out .= "\t\t\tbyteCode.m_pRawData = blah;\n"; + $out .= "\t\t\tbyteCode.m_nSizeInBytes = 4;\n"; + $out .= "\t\t}\n"; + $out .= "\t\telse\n"; + $out .= "\t\t{\n"; + $out .= "\t\t\tbyteCode.m_pRawData = pShader->GetBufferPointer();\n"; + $out .= "\t\t\tbyteCode.m_nSizeInBytes = pShader->GetBufferSize();\n"; + $out .= "\t\t}\n"; + $out .= "\t\treturn byteCode;\n"; + return $out; +} + +#print "--------\n"; + +if ( $g_x360 ) +{ + $fxctmp = "fxctmp9_360_tmp"; +} +else +{ + $fxctmp = "fxctmp9_tmp"; +} + +if( !stat $fxctmp ) +{ + mkdir $fxctmp, 0777 || die $!; +} + +# suck in an input file (using includes) +#print "$fxc_filename..."; +@fxc = ReadInputFile( $fxc_filename ); + +# READ THE TOP OF THE FILE TO FIND SHADER COMBOS +foreach $line ( @fxc ) +{ + $line="" if ($g_x360 && ($line=~/\[PC\]/)); # line marked as [PC] when building for x360 + $line="" if (($g_x360 == 0) && ($line=~/\[XBOX\]/)); # line marked as [XBOX] when building for pc + + if ( $fxc_basename =~ m/_ps(\d+\w?)$/i ) + { + my $psver = $1; + $line="" if (($line =~/\[ps\d+\w?\]/i) && ($line!~/\[ps$psver\]/i)); # line marked for a version of compiler and not what we build + } + if ( $fxc_basename =~ m/_vs(\d+\w?)$/i ) + { + my $vsver = $1; + $line="" if (($line =~/\[vs\d+\w?\]/i) && ($line!~/\[vs$vsver\]/i)); # line marked for a version of compiler and not what we build + } + + my $init_expr; + + $init_expr = $1 if ( $line=~/\[\=([^\]]+)\]/); # parse default init expression for combos + + $line=~s/\[[^\[\]]*\]//; # cut out all occurrences of + # square brackets and whatever is + # inside all these modifications + # to the line are seen later when + # processing skips and centroids + + next if( $line =~ m/^\s*$/ ); + + if( $line =~ m/^\s*\/\/\s*STATIC\s*\:\s*\"(.*)\"\s+\"(\d+)\.\.(\d+)\"/ ) + { + local( $name, $min, $max ); + $name = $1; + $min = $2; + $max = $3; + # print STDERR "STATIC: \"$name\" \"$min..$max\"\n"; + push @staticDefineNames, $name; + push @staticDefineMin, $min; + push @staticDefineMax, $max; + $staticDefineInit{$name}=$init_expr; + } + elsif( $line =~ m/^\s*\/\/\s*DYNAMIC\s*\:\s*\"(.*)\"\s+\"(\d+)\.\.(\d+)\"/ ) + { + local( $name, $min, $max ); + $name = $1; + $min = $2; + $max = $3; + # print STDERR "DYNAMIC: \"$name\" \"$min..$max\"\n"; + push @dynamicDefineNames, $name; + push @dynamicDefineMin, $min; + push @dynamicDefineMax, $max; + } +} +# READ THE WHOLE FILE AND FIND SKIP STATEMENTS +foreach $line ( @fxc ) +{ + if( $line =~ m/^\s*\/\/\s*SKIP\s*\s*\:\s*(.*)$/ ) + { + # print $1 . "\n"; + $perlskipcode .= "(" . $1 . ")||"; + push @perlskipcodeindividual, $1; + } +} + +if( defined $perlskipcode ) +{ + $perlskipcode .= "0"; + $perlskipcode =~ s/\n//g; +} +else +{ + $perlskipcode = "0"; +} + +# READ THE WHOLE FILE AND FIND CENTROID STATEMENTS +foreach $line ( @fxc ) +{ + if( $line =~ m/^\s*\/\/\s*CENTROID\s*\:\s*TEXCOORD(\d+)\s*$/ ) + { + $centroidEnable{$1} = 1; +# print "CENTROID: $1\n"; + } +} + +if( $spewCombos ) +{ + push @outputHeader, "#include \"windows.h\"\n"; +} + +#push @outputHeader, "\#include \"shaderlib\\baseshader.h\"\n"; +#push @outputHeader, "IShaderDynamicAPI *CBaseShader::s_pShaderAPI;\n"; + +# Go ahead an compute the mask of samplers that need to be centroid sampled +$centroidMask = 0; +foreach $centroidRegNum ( keys( %centroidEnable ) ) +{ +# print "THING: $samplerName $centroidRegNum\n"; + $centroidMask += 1 << $centroidRegNum; +} + +#printf "0x%x\n", $centroidMask; + +$numCombos = &CalcNumCombos(); +#print "$numCombos combos\n"; + + +if( $g_produceCompiledVcs && !$dynamic_compile ) +{ + open FOUT, ">>filelistgen.txt" || die "can't open filelistgen.txt"; + + print FOUT "**** generated by fxc_prep.pl ****\n"; + print FOUT "#BEGIN " . $fxc_basename . "\n"; + print FOUT "$fxc_filename" . "\n"; + print FOUT "#DEFINES-D:\n"; + for( $i = 0; $i < scalar( @dynamicDefineNames ); \$i++ ) + { + print FOUT "$dynamicDefineNames[$i]="; + print FOUT $dynamicDefineMin[$i]; + print FOUT ".."; + print FOUT $dynamicDefineMax[$i]; + print FOUT "\n"; + } + print FOUT "#DEFINES-S:\n"; + for( $i = 0; $i < scalar( @staticDefineNames ); \$i++ ) + { + print FOUT "$staticDefineNames[$i]="; + print FOUT $staticDefineMin[$i]; + print FOUT ".."; + print FOUT $staticDefineMax[$i]; + print FOUT "\n"; + } + print FOUT "#SKIP:\n"; + print FOUT "$perlskipcode\n"; + print FOUT "#COMMAND:\n"; + # first line + print FOUT "fxc.exe "; + print FOUT "/DTOTALSHADERCOMBOS=$numCombos "; + print FOUT "/DCENTROIDMASK=$centroidMask "; + print FOUT "/DNUMDYNAMICCOMBOS=" . &CalcNumDynamicCombos() . " "; + print FOUT "/DFLAGS=0x0 "; # Nothing here for now. + print FOUT "\n"; +#defines go here +# second line + print FOUT &RenameMain( $fxc_filename, $i ); + print FOUT "/T" . &GetShaderType( $fxc_filename ) . " "; + print FOUT "/DSHADER_MODEL_" . &ToUpper( &GetShaderType( $fxc_filename ) ) . "=1 "; + if( $nvidia ) + { + print FOUT "/DNV3X=1 "; # enable NV3X codepath + } + if ( $g_x360 ) + { + print FOUT "/D_X360=1 "; # shaders can identify X360 centric code + # print FOUT "/Xbe:2- "; # use the less-broken old back end + } + if( $debug ) + { + print FOUT "/Od "; # disable optimizations + print FOUT "/Zi "; # enable debug info + } +# print FOUT "/Zi "; # enable debug info + print FOUT "/nologo "; +# print FOUT "/Fhtmpshader.h "; + print FOUT "/Foshader.o "; + print FOUT "$fxc_filename"; + print FOUT ">output.txt 2>&1"; + print FOUT "\n"; + #end of command line + print FOUT "#END\n"; + print FOUT "**** end ****\n"; + + close FOUT; +} + +if ( $g_produceCppClasses ) +{ + # Write out the C++ helper class for picking shader combos + &WriteStaticHelperClasses(); + &WriteDynamicHelperClasses(); + my $incfilename = "$fxctmp\\$fxc_basename" . ".inc"; + &WriteFile( $incfilename, join( "", @outputHeader ) ); +} + + + +if( $generateListingFile ) +{ + my $listFileName = "$fxctmp/$fxc_basename" . ".lst"; + print "writing $listFileName\n"; + if( !open FILE, ">$listFileName" ) + { + die; + } + print FILE @listingOutput; + close FILE; +} + + +@endTimes = times; + +$endTime = time; + +#printf "Elapsed user time: %.2f seconds!\n", $endTimes[0] - $startTimes[0]; +#printf "Elapsed system time: %.2f seconds!\n", $endTimes[1] - $startTimes[1]; +#printf "Elapsed child user time: %.2f seconds!\n", $endTimes[2] - $startTimes[2]; +#printf "Elapsed child system time: %.2f seconds!\n", $endTimes[3] - $startTimes[3]; + +#printf "Elapsed total time: %.2f seconds!\n", $endTime - $startTime; + diff --git a/devtools/bin/psh_prep.pl b/devtools/bin/psh_prep.pl new file mode 100644 index 00000000..5aa49c11 --- /dev/null +++ b/devtools/bin/psh_prep.pl @@ -0,0 +1,333 @@ +use String::CRC32; +BEGIN {use File::Basename; push @INC, dirname($0); } +require "valve_perl_helpers.pl"; + +sub BuildDefineOptions +{ + local( $output ); + local( $combo ) = shift; + local( $i ); + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + local( $val ) = ( $combo % ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) ) + $dynamicDefineMin[$i]; + $output .= "/D$dynamicDefineNames[$i]=$val "; + $combo = $combo / ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ); + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + local( $val ) = ( $combo % ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) ) + $staticDefineMin[$i]; + $output .= "/D$staticDefineNames[$i]=$val "; + $combo = $combo / ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ); + } + return $output; +} + +sub CalcNumCombos +{ + local( $i, $numCombos ); + $numCombos = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $numCombos *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $numCombos *= $staticDefineMax[$i] - $staticDefineMin[$i] + 1; + } + return $numCombos; +} + +sub CalcNumDynamicCombos +{ + local( $i, $numCombos ); + $numCombos = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $numCombos *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + return $numCombos; +} + +$g_dx9 = 1; + +while( 1 ) +{ + $psh_filename = shift; + + if( $psh_filename =~ m/-source/ ) + { + $g_SourceDir = shift; + } + elsif( $psh_filename =~ m/-x360/ ) + { + $g_x360 = 1; + } + else + { + last; + } +} + +$psh_filename =~ s/-----.*$//; + + +# Get the shader binary version number from a header file. +open FILE, "<$g_SourceDir\\public\\materialsystem\\shader_vcs_version.h" || die; +while( $line = ) +{ + if( $line =~ m/^\#define\s+SHADER_VCS_VERSION_NUMBER\s+(\d+)\s*$/ ) + { + $shaderVersion = $1; + last; + } +} +if( !defined $shaderVersion ) +{ + die "couldn't get shader version from shader_vcs_version.h"; +} +close FILE; + + + +local( @staticDefineNames ); +local( @staticDefineMin ); +local( @staticDefineMax ); +local( @dynamicDefineNames ); +local( @dynamicDefineMin ); +local( @dynamicDefineMax ); + +# Parse the combos. +open PSH, "<$psh_filename"; +while( ) +{ + last if( !m,^;, ); + s,^;\s*,,; + if( m/\s*STATIC\s*\:\s*\"(.*)\"\s+\"(\d+)\.\.(\d+)\"/ ) + { + local( $name, $min, $max ); + $name = $1; + $min = $2; + $max = $3; +# print "\"STATIC: $name\" \"$min..$max\"\n"; + if (/\[(.*)\]/) + { + $platforms=$1; + next if ( ($g_x360) && (!($platforms=~/XBOX/i)) ); + next if ( (!$g_x360) && (!($platforms=~/PC/i)) ); + } + push @staticDefineNames, $name; + push @staticDefineMin, $min; + push @staticDefineMax, $max; + } + elsif( m/\s*DYNAMIC\s*\:\s*\"(.*)\"\s+\"(\d+)\.\.(\d+)\"/ ) + { + local( $name, $min, $max ); + $name = $1; + $min = $2; + $max = $3; +# print "\"DYNAMIC: $name\" \"$min..$max\"\n"; + if (/\[(.*)\]/) + { + $platforms=$1; + next if ( ($g_x360) && (!($platforms=~/XBOX/i)) ); + next if ( (!$g_x360) && (!($platforms=~/PC/i)) ); + } + push @dynamicDefineNames, $name; + push @dynamicDefineMin, $min; + push @dynamicDefineMax, $max; + } +} +close PSH; + +$numCombos = &CalcNumCombos(); +$numDynamicCombos = &CalcNumDynamicCombos(); +print "$psh_filename\n"; +#print "$numCombos combos\n"; +#print "$numDynamicCombos dynamic combos\n"; + +if( $g_x360 ) +{ + $pshtmp = "pshtmp9_360"; +} +elsif( $g_dx9 ) +{ + $pshtmp = "pshtmp9"; +} +else +{ + $pshtmp = "pshtmp8"; +} +$basename = $psh_filename; +$basename =~ s/\.psh$//i; + +for( $shaderCombo = 0; $shaderCombo < $numCombos; $shaderCombo++ ) +{ + my $tempFilename = "shader$shaderCombo.o"; + unlink $tempFilename; + + if( $g_x360 ) + { + $cmd = "$g_SourceDir\\x360xdk\\bin\\win32\\psa /D_X360=1 /Foshader$shaderCombo.o /nologo " . &BuildDefineOptions( $shaderCombo ) . "$psh_filename > NIL"; + } + else + { + $cmd = "$g_SourceDir\\dx9sdk\\utilities\\psa /Foshader$shaderCombo.o /nologo " . &BuildDefineOptions( $shaderCombo ) . "$psh_filename > NIL"; + } + + if( !stat $pshtmp ) + { + mkdir $pshtmp, 0777 || die $!; + } + +# print $cmd . "\n"; + system $cmd || die $!; + + # Make sure a file got generated because sometimes the die above won't happen on compile errors. + my $filesize = (stat $tempFilename)[7]; + if ( !$filesize ) + { + die "Error compiling shader$shaderCombo.o"; + } + + push @outputHeader, @hdr; +} + +$basename =~ s/\.fxc//gi; +push @outputHeader, "static PrecompiledShaderByteCode_t " . $basename . "_pixel_shaders[" . $numCombos . "] = \n"; +push @outputHeader, "{\n"; +local( $j ); +for( $j = 0; $j < $numCombos; $j++ ) +{ + local( $thing ) = "pixelShader_" . $basename . "_" . $j; + push @outputHeader, "\t{ " . "$thing, sizeof( $thing ) },\n"; +} +push @outputHeader, "};\n"; + +push @outputHeader, "struct $basename" . "PixelShader_t : public PrecompiledShader_t\n"; +push @outputHeader, "{\n"; +push @outputHeader, "\t$basename" . "PixelShader_t()\n"; +push @outputHeader, "\t{\n"; +push @outputHeader, "\t\tm_nFlags = 0;\n"; +push @outputHeader, "\t\tm_pByteCode = " . $basename . "_pixel_shaders;\n"; +push @outputHeader, "\t\tm_nShaderCount = $numCombos;\n"; +#push @outputHeader, "\t\tm_nDynamicCombos = m_nShaderCount;\n"; +push @outputHeader, "\t\t// NOTE!!! psh_prep.pl shaders are always static combos!\n"; +push @outputHeader, "\t\tm_nDynamicCombos = 1;\n"; +push @outputHeader, "\t\tm_pName = \"$basename\";\n"; +if( $basename =~ /vs\d\d/ ) # hack +{ + push @outputHeader, "\t\tGetShaderDLL()->InsertPrecompiledShader( PRECOMPILED_VERTEX_SHADER, this );\n"; +} +else +{ + push @outputHeader, "\t\tGetShaderDLL()->InsertPrecompiledShader( PRECOMPILED_PIXEL_SHADER, this );\n"; +} +push @outputHeader, "\t}\n"; +push @outputHeader, "\tvirtual const PrecompiledShaderByteCode_t &GetByteCode( int shaderID )\n"; +push @outputHeader, "\t{\n"; +push @outputHeader, "\t\treturn m_pByteCode[shaderID];\n"; +push @outputHeader, "\t}\n"; +push @outputHeader, "};\n"; + +push @outputHeader, "static $basename" . "PixelShader_t $basename" . "_PixelShaderInstance;\n"; + + +&MakeDirHier( "shaders/psh" ); + +my $vcsName = ""; +if( $g_x360 ) +{ + $vcsName = $basename . ".360.vcs"; +} +else +{ + $vcsName = $basename . ".vcs"; +} + +open COMPILEDSHADER, ">shaders/psh/$vcsName" || die; +binmode( COMPILEDSHADER ); + +# +# Write out the part of the header that we know. . we'll write the rest after writing the object code. +# + +#print $numCombos . "\n"; + +# Pack arguments +my $sInt = "i"; +my $uInt = "I"; +if ( $g_x360 ) +{ + # Change arguments to "big endian long" + $sInt = "N"; + $uInt = "N"; +} + +open PSH, "<$psh_filename"; +my $crc = crc32( *PSH ); +close PSH; +#print STDERR "crc for $psh_filename: $crc\n"; + +# version +print COMPILEDSHADER pack $sInt, 4; +# totalCombos +print COMPILEDSHADER pack $sInt, $numCombos; +# dynamic combos +print COMPILEDSHADER pack $sInt, $numDynamicCombos; +# flags +print COMPILEDSHADER pack $uInt, 0x0; # nothing here for now. +# centroid mask +print COMPILEDSHADER pack $uInt, 0; +# reference size for diffs +print COMPILEDSHADER pack $uInt, 0; +# crc32 of the source code +print COMPILEDSHADER pack $uInt, $crc; + +my $beginningOfDir = tell COMPILEDSHADER; + +# Write out a blank directionary. . we'll fill it in later. +for( $i = 0; $i < $numCombos; $i++ ) +{ + # offset from beginning of file. + print COMPILEDSHADER pack $sInt, 0; + # size + print COMPILEDSHADER pack $sInt, 0; +} + +my $startByteCode = tell COMPILEDSHADER; +my @byteCodeStart; +my @byteCodeSize; + +# Write out the shader object code. +for( $shaderCombo = 0; $shaderCombo < $numCombos; $shaderCombo++ ) +{ + my $filename = "shader$shaderCombo\.o"; + my $filesize = (stat $filename)[7]; + + $byteCodeStart[$shaderCombo] = tell COMPILEDSHADER; + $byteCodeSize[$shaderCombo] = $filesize; + open SHADERBYTECODE, "<$filename"; + binmode SHADERBYTECODE; + + my $bin; + my $numread = read SHADERBYTECODE, $bin, $filesize; +# print "filename: $filename numread: $numread filesize: $filesize\n"; + close SHADERBYTECODE; + unlink $filename; + + print COMPILEDSHADER $bin; +} + +# Seek back to the directory and write it out. +seek COMPILEDSHADER, $beginningOfDir, 0; +for( $i = 0; $i < $numCombos; $i++ ) +{ + # offset from beginning of file. + print COMPILEDSHADER pack $sInt, $byteCodeStart[$i]; + # size + print COMPILEDSHADER pack $sInt, $byteCodeSize[$i]; +} + +close COMPILEDSHADER; + + diff --git a/devtools/bin/updateshaders.pl b/devtools/bin/updateshaders.pl new file mode 100644 index 00000000..005e96a0 --- /dev/null +++ b/devtools/bin/updateshaders.pl @@ -0,0 +1,305 @@ +use String::CRC32; +BEGIN {use File::Basename; push @INC, dirname($0); } +require "valve_perl_helpers.pl"; + +$dynamic_compile = defined $ENV{"dynamic_shaders"} && $ENV{"dynamic_shaders"} != 0; + +$depnum = 0; +$baseSourceDir = "."; + +my %dep; + +sub GetAsmShaderDependencies_R +{ + local( $shadername ) = shift; + local( *SHADER ); + + open SHADER, "<$shadername"; + while( ) + { + if( m/^\s*\#\s*include\s+\"(.*)\"/ ) + { + # make sure it isn't in there already. + if( !defined( $dep{$1} ) ) + { + $dep{$1} = 1; + GetAsmShaderDependencies_R( $1 ); + } + } + } + close SHADER; +} + +sub GetAsmShaderDependencies +{ + local( $shadername ) = shift; + undef %dep; + GetAsmShaderDependencies_R( $shadername ); +# local( $i ); +# foreach $i ( keys( %dep ) ) +# { +# print "$shadername depends on $i\n"; +# } + return keys( %dep ); +} + +sub GetShaderType +{ + my $shadername = shift; + my $shadertype; + if( $shadername =~ m/\.vsh/i ) + { + $shadertype = "vsh"; + } + elsif( $shadername =~ m/\.psh/i ) + { + $shadertype = "psh"; + } + elsif( $shadername =~ m/\.fxc/i ) + { + $shadertype = "fxc"; + } + else + { + die; + } + return $shadertype; +} + +sub GetShaderSrc +{ + my $shadername = shift; + if ( $shadername =~ m/^(.*)-----/i ) + { + return $1; + } + else + { + return $shadername; + } +} + +sub GetShaderBase +{ + my $shadername = shift; + if ( $shadername =~ m/-----(.*)$/i ) + { + return $1; + } + else + { + my $shadertype = &GetShaderType( $shadername ); + $shadername =~ s/\.$shadertype//i; + return $shadername; + } +} + +sub DoAsmShader +{ + my $argstring = shift; + my $shadername = &GetShaderSrc( $argstring ); + my $shaderbase = &GetShaderBase( $argstring ); + my $shadertype = &GetShaderType( $argstring ); + my $incfile = ""; + if( $shadertype eq "fxc" || $shadertype eq "vsh" ) + { + $incfile = $shadertype . "tmp9" . $g_tmpfolder . "\\$shaderbase.inc "; + } + + my $vcsfile = $shaderbase . $g_vcsext; + my $bWillCompileVcs = 1; + if( ( $shadertype eq "fxc") && $dynamic_compile ) + { + $bWillCompileVcs = 0; + } + if( $shadercrcpass{$argstring} ) + { + $bWillCompileVcs = 0; + } + + if( $bWillCompileVcs ) + { + &output_makefile_line( $incfile . "shaders\\$shadertype\\$vcsfile: $shadername @dep\n") ; + } + else + { + # psh files don't need a rule at this point since they don't have inc files and we aren't compiling a vcs. + if( $shadertype eq "fxc" || $shadertype eq "vsh" ) + { + &output_makefile_line( $incfile . ": $shadername @dep\n") ; + } + } + + + my $x360switch = ""; + my $moreswitches = ""; + if( !$bWillCompileVcs && $shadertype eq "fxc" ) + { + $moreswitches .= "-novcs "; + } + if( $g_x360 ) + { + $x360switch = "-x360"; + + if( $bWillCompileVcs && ( $shaderbase =~ m/_ps20$/i ) ) + { + $moreswitches .= "-novcs "; + $bWillCompileVcs = 0; + } + } + + # if we are psh and we are compiling the vcs, we don't need this rule. + if( !( $shadertype eq "psh" && !$bWillCompileVcs ) ) + { + &output_makefile_line( "\tperl $g_SourceDir\\devtools\\bin\\" . $shadertype . "_prep.pl $moreswitches $x360switch -source \"$g_SourceDir\" $argstring\n") ; + } + + if( $bWillCompileVcs ) + { + &output_makefile_line( "\techo $shadername>> filestocopy.txt\n") ; + my $dep; + foreach $dep( @dep ) + { + &output_makefile_line( "\techo $dep>> filestocopy.txt\n") ; + } + } + &output_makefile_line( "\n") ; +} + +if( scalar( @ARGV ) == 0 ) +{ + die "Usage updateshaders.pl shaderprojectbasename\n\tie: updateshaders.pl stdshaders_dx6\n"; +} + +$g_x360 = 0; +$g_tmpfolder = "_tmp"; +$g_vcsext = ".vcs"; + +while( 1 ) +{ + $inputbase = shift; + + if( $inputbase =~ m/-source/ ) + { + $g_SourceDir = shift; + } + elsif( $inputbase =~ m/-x360/ ) + { + $g_x360 = 1; + $g_tmpfolder = "_360_tmp"; + $g_vcsext = ".360.vcs"; + } + elsif( $inputbase =~ m/-execute/ ) + { + $g_execute = 1; + } + elsif( $inputbase =~ m/-nv3x/ ) + { + $nv3x = 1; + } + else + { + last; + } +} + +my @srcfiles = &LoadShaderListFile( $inputbase ); + +open MAKEFILE, ">makefile\.$inputbase"; +open COPYFILE, ">makefile\.$inputbase\.copy"; +open INCLIST, ">inclist.txt"; +open VCSLIST, ">vcslist.txt"; + +# make a default dependency that depends on all of the shaders. +&output_makefile_line( "default: ") ; +foreach $shader ( @srcfiles ) +{ + my $shadertype = &GetShaderType( $shader ); + my $shaderbase = &GetShaderBase( $shader ); + my $shadersrc = &GetShaderSrc( $shader ); + if( $shadertype eq "fxc" || $shadertype eq "vsh" ) + { + # We only generate inc files for fxc and vsh files. + my $incFileName = "$shadertype" . "tmp9" . $g_tmpfolder . "\\" . $shaderbase . "\.inc"; + &output_makefile_line( " $incFileName" ); + &output_inclist_line( "$incFileName\n" ); + } + + my $vcsfile = $shaderbase . $g_vcsext; + + my $compilevcs = 1; + if( $shadertype eq "fxc" && $dynamic_compile ) + { + $compilevcs = 0; + } + if( $g_x360 && ( $shaderbase =~ m/_ps20$/i ) ) + { + $compilevcs = 0; + } + if( $compilevcs ) + { + my $vcsFileName = "..\\..\\..\\game\\hl2\\shaders\\$shadertype\\$shaderbase" . $g_vcsext; + # We want to check for perforce operations even if the crc matches in the event that a file has been manually reverted and needs to be checked out again. + &output_vcslist_line( "$vcsFileName\n" ); + $shadercrcpass{$shader} = &CheckCRCAgainstTarget( $shadersrc, $vcsFileName, 0 ); + if( $shadercrcpass{$shader} ) + { + $compilevcs = 0; + } + } + if( $compilevcs ) + { + &output_makefile_line( " shaders\\$shadertype\\$vcsfile" ); + # emit a list of vcs files to copy to the target since we want to build them. + &output_copyfile_line( GetShaderSrc($shader) . "-----" . GetShaderBase($shader) . "\n" ); + } +} +&output_makefile_line( "\n\n") ; + +# Insert all of our vertex shaders and depencencies +$lastshader = ""; +foreach $shader ( @srcfiles ) +{ + my $currentshader = &GetShaderSrc( $shader ); + if ( $lastshader ne $currentshader ) + { + $lastshader = $currentshader; + @dep = &GetAsmShaderDependencies( $lastshader ); + } + &DoAsmShader( $shader ); +} +close VCSLIST; +close INCLIST; +close COPYFILE; +close MAKEFILE; + +# nuke the copyfile if it is zero length +if( ( stat "makefile\.$inputbase\.copy" )[7] == 0 ) +{ + unlink "makefile\.$inputbase\.copy"; +} + +sub output_makefile_line +{ + local ($_)=@_; + print MAKEFILE $_; +} + +sub output_copyfile_line +{ + local ($_)=@_; + print COPYFILE $_; +} + +sub output_vcslist_line +{ + local ($_)=@_; + print VCSLIST $_; +} + +sub output_inclist_line +{ + local ($_)=@_; + print INCLIST $_; +} + diff --git a/devtools/bin/vsh_prep.pl b/devtools/bin/vsh_prep.pl new file mode 100644 index 00000000..1ba8b6f1 --- /dev/null +++ b/devtools/bin/vsh_prep.pl @@ -0,0 +1,1106 @@ +use String::CRC32; +BEGIN {use File::Basename; push @INC, dirname($0); } +require "valve_perl_helpers.pl"; + +sub WriteHelperVar +{ + local( $name ) = shift; + local( $min ) = shift; + local( $max ) = shift; + local( $varname ) = "m_n" . $name; + local( $boolname ) = "m_b" . $name; + push @outputHeader, "private:\n"; + push @outputHeader, "\tint $varname;\n"; + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\tbool $boolname;\n"; + push @outputHeader, "#endif\n"; + push @outputHeader, "public:\n"; + # int version of set function + push @outputHeader, "\tvoid Set" . $name . "( int i )\n"; + push @outputHeader, "\t{\n"; + if ( $min != $max ) + { + push @outputHeader, "\t\tAssert( i >= $min && i <= $max );\n"; + push @outputHeader, "\t\t$varname = i;\n"; + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\t\t$boolname = true;\n"; + push @outputHeader, "#endif\n"; + } + push @outputHeader, "\t}\n"; + # bool version of set function + push @outputHeader, "\tvoid Set" . $name . "( bool i )\n"; + push @outputHeader, "\t{\n"; + if ( $min != $max ) + { +# push @outputHeader, "\t\tAssert( i >= $min && i <= $max );\n"; + push @outputHeader, "\t\t$varname = i ? 1 : 0;\n"; + push @outputHeader, "#ifdef _DEBUG\n"; + push @outputHeader, "\t\t$boolname = true;\n"; + push @outputHeader, "#endif\n"; + } + push @outputHeader, "\t}\n"; +} + +sub WriteStaticBoolExpression +{ + local( $prefix ) = shift; + local( $operator ) = shift; + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + if( $i ) + { + push @outputHeader, " $operator "; + } + local( $name ) = @staticDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + push @outputHeader, "$prefix$boolname"; + } + push @outputHeader, ";\n"; +} + +sub WriteDynamicBoolExpression +{ + local( $prefix ) = shift; + local( $operator ) = shift; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + if( $i ) + { + push @outputHeader, " $operator "; + } + local( $name ) = @dynamicDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + push @outputHeader, "$prefix$boolname"; + } + push @outputHeader, ";\n"; +} + +sub WriteDynamicHelperClasses +{ + local( $basename ) = $fxc_filename; + $basename =~ s/\.fxc//i; + $basename =~ tr/A-Z/a-z/; + local( $classname ) = $basename . "_Dynamic_Index"; + push @outputHeader, "class $classname\n"; + push @outputHeader, "{\n"; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $name = $dynamicDefineNames[$i]; + $min = $dynamicDefineMin[$i]; + $max = $dynamicDefineMax[$i]; + &WriteHelperVar( $name, $min, $max ); + } + push @outputHeader, "public:\n"; + push @outputHeader, "\t$classname()\n"; + push @outputHeader, "\t{\n"; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $min = $dynamicDefineMin[$i]; + $max = $dynamicDefineMax[$i]; + + local( $name ) = @dynamicDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + local( $varname ) = "m_n" . $name; + push @outputHeader, "#ifdef _DEBUG\n"; + if ( $min != $max ) + { + push @outputHeader, "\t\t$boolname = false;\n"; + } + else + { + push @outputHeader, "\t\t$boolname = true;\n"; + } + push @outputHeader, "#endif // _DEBUG\n"; + push @outputHeader, "\t\t$varname = 0;\n"; + } + push @outputHeader, "\t}\n"; + push @outputHeader, "\tint GetIndex()\n"; + push @outputHeader, "\t{\n"; + push @outputHeader, "\t\t// Asserts to make sure that we aren't using any skipped combinations.\n"; + foreach $skip (@perlskipcodeindividual) + { + $skip =~ s/\$/m_n/g; +# push @outputHeader, "\t\tAssert( !( $skip ) );\n"; + } + push @outputHeader, "\t\t// Asserts to make sure that we are setting all of the combination vars.\n"; + + push @outputHeader, "#ifdef _DEBUG\n"; + if( scalar( @dynamicDefineNames ) > 0 ) + { + push @outputHeader, "\t\tbool bAllDynamicVarsDefined = "; + WriteDynamicBoolExpression( "", "&&" ); + } + if( scalar( @dynamicDefineNames ) > 0 ) + { + push @outputHeader, "\t\tAssert( bAllDynamicVarsDefined );\n"; + } + push @outputHeader, "#endif // _DEBUG\n"; + + if( $spewCombos && scalar( @dynamicDefineNames ) ) + { + push @outputHeader, &CreateCCodeToSpewDynamicCombo(); + } + push @outputHeader, "\t\treturn "; + local( $scale ) = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + local( $name ) = @dynamicDefineNames[$i]; + local( $varname ) = "m_n" . $name; + push @outputHeader, "( $scale * $varname ) + "; + $scale *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + push @outputHeader, "0;\n"; + push @outputHeader, "\t}\n"; + push @outputHeader, "};\n"; +} + +sub WriteStaticHelperClasses +{ + local( $basename ) = $fxc_filename; + $basename =~ s/\.fxc//i; + $basename =~ tr/A-Z/a-z/; + local( $classname ) = $basename . "_Static_Index"; + push @outputHeader, "class $classname\n"; + push @outputHeader, "{\n"; + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $name = $staticDefineNames[$i]; + $min = $staticDefineMin[$i]; + $max = $staticDefineMax[$i]; + &WriteHelperVar( $name, $min, $max ); + } + push @outputHeader, "public:\n"; + push @outputHeader, "\t$classname()\n"; + push @outputHeader, "\t{\n"; + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $min = $staticDefineMin[$i]; + $max = $staticDefineMax[$i]; + + local( $name ) = @staticDefineNames[$i]; + local( $boolname ) = "m_b" . $name; + local( $varname ) = "m_n" . $name; + + push @outputHeader, "#ifdef _DEBUG\n"; + if ( $min != $max ) + { + push @outputHeader, "\t\t$boolname = false;\n"; + } + else + { + push @outputHeader, "\t\t$boolname = true;\n"; + } + push @outputHeader, "#endif // _DEBUG\n"; + push @outputHeader, "\t\t$varname = 0;\n"; + } + push @outputHeader, "\t}\n"; + push @outputHeader, "\tint GetIndex()\n"; + push @outputHeader, "\t{\n"; + push @outputHeader, "\t\t// Asserts to make sure that we aren't using any skipped combinations.\n"; + foreach $skip (@perlskipcodeindividual) + { + $skip =~ s/\$/m_n/g; +# push @outputHeader, "\t\tAssert( !( $skip ) );\n"; + } + push @outputHeader, "\t\t// Asserts to make sure that we are setting all of the combination vars.\n"; + + push @outputHeader, "#ifdef _DEBUG\n"; + if( scalar( @staticDefineNames ) > 0 ) + { + push @outputHeader, "\t\tbool bAllStaticVarsDefined = "; + WriteStaticBoolExpression( "", "&&" ); + + } + if( scalar( @staticDefineNames ) > 0 ) + { + push @outputHeader, "\t\tAssert( bAllStaticVarsDefined );\n"; + } + push @outputHeader, "#endif // _DEBUG\n"; + + if( $spewCombos && scalar( @staticDefineNames ) ) + { + push @outputHeader, &CreateCCodeToSpewStaticCombo(); + } + push @outputHeader, "\t\treturn "; + local( $scale ) = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $scale *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + local( $name ) = @staticDefineNames[$i]; + local( $varname ) = "m_n" . $name; + push @outputHeader, "( $scale * $varname ) + "; + $scale *= $staticDefineMax[$i] - $staticDefineMin[$i] + 1; + } + push @outputHeader, "0;\n"; + push @outputHeader, "\t}\n"; + push @outputHeader, "};\n"; +} + +sub CreateFuncToSetPerlVars +{ + local( $out ) = ""; + + $out .= "sub SetPerlVarsFunc\n"; + $out .= "{\n"; + $out .= " local( \$combo ) = shift;\n"; + $out .= " local( \$i );\n"; + local( $i ); + for( $i = 0; $i < scalar( @dynamicDefineNames ); \$i++ ) + { + $out .= " \$$dynamicDefineNames[$i] = \$combo % "; + $out .= ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) + $dynamicDefineMin[$i]; + $out .= ";\n"; + $out .= " \$combo = \$combo / " . ( $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1 ) . ";\n"; + } + for( $i = 0; $i < scalar( @staticDefineNames ); \$i++ ) + { + $out .= " \$$staticDefineNames[$i] = \$combo % "; + $out .= ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) + $staticDefineMin[$i]; + $out .= ";\n"; + $out .= " \$combo = \$combo / " . ( $staticDefineMax[$i] - $staticDefineMin[$i] + 1 ) . ";\n"; + } + $out .= "}\n"; + +# print $out; + eval $out; +} + +# These sections can be interchanged to enable profiling. +#$ShowTimers = 1; +#use Time::HiRes; +#sub SampleTime() +#{ +# return Time::HiRes::time(); +#} + +$ShowTimers = 0; +sub SampleTime() { return 0; } + +$total_start_time = SampleTime(); + +# NOTE: These must match the same values in macros.vsh! +$vPos = "v0"; +$vBoneWeights = "v1"; +$vBoneIndices = "v2"; +$vNormal = "v3"; +if( $g_x360 ) +{ + $vPosFlex = "v4"; + $vNormalFlex = "v13"; +} +$vColor = "v5"; +$vSpecular = "v6"; +$vTexCoord0 = "v7"; +$vTexCoord1 = "v8"; +$vTexCoord2 = "v9"; +$vTexCoord3 = "v10"; +$vTangentS = "v11"; +$vTangentT = "v12"; +$vUserData = "v14"; + +sub ReadInputFileWithLineInfo +{ + local( $base_filename ) = shift; + + local( *INPUT ); + local( @output ); + + # Look in the stdshaders directory, followed by the current directory. + # (This is for the SDK, since some of its files are under stdshaders). + local( $filename ) = $base_filename; + if ( !-e $filename ) + { + $filename = "$g_SourceDir\\materialsystem\\stdshaders\\$base_filename"; + if ( !-e $filename ) + { + die "\nvsh_prep.pl ERROR: missing include file: $filename.\n\n"; + } + } + + open INPUT, "<$filename" || die; + + local( $line ); + local( $linenum ) = 1; + while( $line = ) + { + $line =~ s/\n//g; + local( $postfix ) = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + $postfix .= "; LINEINFO($filename)($linenum)\n"; + if( $line =~ m/\#include\s+\"(.*)\"/i ) + { + push @output, &ReadInputFileWithLineInfo( $1 ); + } + else + { + push @output, $line . $postfix; + } + $linenum++; + } + + close INPUT; + return @output; +} + +sub ReadInputFileWithoutLineInfo +{ + local( $base_filename ) = shift; + + local( *INPUT ); + local( @output ); + + # Look in the stdshaders directory, followed by the current directory. + # (This is for the SDK, since some of its files are under stdshaders). + local( $filename ) = $base_filename; + if ( !-e $filename ) + { + $filename = "$g_SourceDir\\materialsystem\\stdshaders\\$base_filename"; + if ( !-e $filename ) + { + die "\nERROR: missing include file: $filename.\n\n"; + } + } + + open INPUT, "<$filename" || die; + + local( $line ); + while( $line = ) + { + if( $line =~ m/\#include\s+\"(.*)\"/i ) + { + push @output, &ReadInputFileWithoutLineInfo( $1 ); + } + else + { + push @output, $line; + } + } + + close INPUT; + return @output; +} + +sub IsPerl +{ + local( $line ) = shift; + if( $line =~ m/^\s*sub.*\,/ ) + { + return 0; + } + if( $line =~ m/^\#include/ || + $line =~ m/^\#define/ || + $line =~ m/^\#undef/ || + $line =~ m/^\#ifdef/ || + $line =~ m/^\#ifndef/ || + $line =~ m/^\#else/ || + $line =~ m/^\#endif/ || + $line =~ m/^\#error/ + ) + { + return 0; + } + if( $line =~ m/^\s*if\s*\(/ || + $line =~ m/^\s*else/ || + $line =~ m/^\s*elsif/ || + $line =~ m/^\s*for\s*\(/ || + $line =~ m/^\s*\{/ || + $line =~ m/^sub\s*/ || + $line =~ m/^\s*\}/ || + $line =~ m/^\s*\&/ || + $line =~ m/^\s*\#/ || + $line =~ m/^\s*\$/ || + $line =~ m/^\s*print/ || + $line =~ m/^\s*return/ || + $line =~ m/^\s*exit/ || + $line =~ m/^\s*die/ || + $line =~ m/^\s*eval/ || + $line =~ m/^\s*local/ || + $line =~ m/^\s*my\s+/ || + $line =~ m/^\s*@/ || + $line =~ m/^\s*alloc\s+/ || + $line =~ m/^\s*free\s+/ + ) + { + return 1; + } + return 0; +} + +# translate the output into something that takes us back to the source line +# that we care about in msdev +sub TranslateErrorMessages +{ + local( $origline ); + while( $origline = shift ) + { + if( $origline =~ m/(.*)\((\d+)\)\s*:\s*(.*)$/i ) + { + local( $filename ) = $1; + local( $linenum ) = $2; + local( $error ) = $3; + local( *FILE ); + open FILE, "<$filename" || die; + local( $i ); + local( $line ); + for( $i = 1; $i < $linenum; $i++ ) + { + $line = ; + } + if( $line =~ m/LINEINFO\((.*)\)\((.*)\)/ ) + { + print "$1\($2\) : $error\n"; + my $num = $linenum - 1; + print "$filename\($num\) : original error location\n"; + } + close FILE; + } + else + { + $origline =~ s/successful compile\!.*//gi; + if( !( $origline =~ m/^\s*$/ ) ) + { +# print "WTF: $origline\n"; + } + } + } +} + + +sub CountInstructions +{ + local( $line ); + local( $count ) = 0; + while( $line = shift ) + { + # get rid of comments + $line =~ s/;.*//gi; + $line =~ s/\/\/.*//gi; + # skip the vs1.1 statement + $line =~ s/^\s*vs.*//gi; + # if there's any text left, it's an instruction + if( $line =~ /\S/gi ) + { + $count++; + } + } + return $count; +} + + +%compiled = (); + +sub UsesRegister +{ + my $registerName = shift; + my $str = shift; + + # Cache a compiled RE for each register name. This makes UsesRegister about 2.5x faster. + if ( !$compiled{$registerName} ) + { + $compiled{$registerName} = qr/\b$registerName\b/; + } + + $ret = 0; + if( $str =~ /$compiled{$registerName}/gi ) + { + $ret = 1; + } + + return $ret; +} + +sub PadString +{ + local( $str ) = shift; + local( $desiredLen ) = shift; + local( $len ) = length $str; + while( $len < $desiredLen ) + { + $str .= " "; + $len++; + } + return $str; +} + +sub FixupAllocateFree +{ + local( $line ) = shift; + $line =~ s/\&AllocateRegister\s*\(\s*\\(\S+)\s*\)/&AllocateRegister( \\$1, \"\\$1\" )/g; + $line =~ s/\&FreeRegister\s*\(\s*\\(\S+)\s*\)/&FreeRegister( \\$1, \"\\$1\" )/g; + $line =~ s/alloc\s+(\S+)\s*/local( $1 ); &AllocateRegister( \\$1, \"\\$1\" );/g; + $line =~ s/free\s+(\S+)\s*/&FreeRegister( \\$1, \"\\$1\" );/g; + return $line; +} + +sub TranslateDXKeywords +{ + local( $line ) = shift; + $line =~ s/\bENDIF\b/endif/g; + $line =~ s/\bIF\b/if/g; + $line =~ s/\bELSE\b/else/g; + + return $line; +} + +# This is used to make the generated pl files all pretty. +sub GetLeadingWhiteSpace +{ + local( $str ) = shift; + if( $str =~ m/^;\S/ || $str =~ m/^; \S/ ) + { + return ""; + } + $str =~ s/^;/ /g; # count a leading ";" as whitespace as far as this is concerned. + $str =~ m/^(\s*)/; + return $1; +} + +$g_dx9 = 1; +$g_SourceDir = "..\\.."; + +while( 1 ) +{ + $filename = shift; + + if ( $filename =~ m/-source/i ) + { + $g_SourceDir = shift; + } + elsif( $filename =~ m/-x360/i ) + { + $g_x360 = 1; + } + else + { + last; + } +} + +$filename =~ s/-----.*$//; + + +# +# Get the shader binary version number from a header file. +# +open FILE, "<$g_SourceDir\\public\\materialsystem\\shader_vcs_version.h" || die; +while( $line = ) +{ + if( $line =~ m/^\#define\s+SHADER_VCS_VERSION_NUMBER\s+(\d+)\s*$/ ) + { + $shaderVersion = $1; + last; + } +} +if( !defined $shaderVersion ) +{ + die "couldn't get shader version from shader_vcs_version.h"; +} +close FILE; + + +if( $g_x360 ) +{ + $vshtmp = "vshtmp9_360_tmp"; +} +else +{ + $vshtmp = "vshtmp9_tmp"; +} + +if( !stat $vshtmp ) +{ + mkdir $vshtmp, 0777 || die $!; +} + +# suck in all files, including $include files. +@input = &ReadInputFileWithLineInfo( $filename ); + +sub CalcNumCombos +{ + local( $i, $numCombos ); + $numCombos = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $numCombos *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + for( $i = 0; $i < scalar( @staticDefineNames ); $i++ ) + { + $numCombos *= $staticDefineMax[$i] - $staticDefineMin[$i] + 1; + } + return $numCombos; +} + +sub CalcNumDynamicCombos +{ + local( $i, $numCombos ); + $numCombos = 1; + for( $i = 0; $i < scalar( @dynamicDefineNames ); $i++ ) + { + $numCombos *= $dynamicDefineMax[$i] - $dynamicDefineMin[$i] + 1; + } + return $numCombos; +} + +# READ THE TOP OF THE FILE TO FIND SHADER COMBOS +foreach $_ ( @input ) +{ + next if( m/^\s*$/ ); +# last if( !m,^//, ); + s,^//\s*,,; + if( m/\s*STATIC\s*\:\s*\"(.*)\"\s+\"(\d+)\.\.(\d+)\"/ ) + { + local( $name, $min, $max ); + $name = $1; + $min = $2; + $max = $3; +# print "\"$name\" \"$min..$max\"\n"; + if (/\[(.*)\]/) + { + $platforms=$1; + next if ( ($g_x360) && (!($platforms=~/XBOX/i)) ); + next if ( (!$g_x360) && (!($platforms=~/PC/i)) ); + } + push @staticDefineNames, $name; + push @staticDefineMin, $min; + push @staticDefineMax, $max; + } + elsif( m/\s*DYNAMIC\s*\:\s*\"(.*)\"\s+\"(\d+)\.\.(\d+)\"/ ) + { + local( $name, $min, $max ); + $name = $1; + $min = $2; + $max = $3; + if (/\[(.*)\]/) + { + $platforms=$1; + next if ( ($g_x360) && (!($platforms=~/XBOX/i)) ); + next if ( (!$g_x360) && (!($platforms=~/PC/i)) ); + } +# print "\"$name\" \"$min..$max\"\n"; + push @dynamicDefineNames, $name; + push @dynamicDefineMin, $min; + push @dynamicDefineMax, $max; + } +} + +# READ THE WHOLE FILE AND FIND SKIP STATEMENTS +foreach $_ ( @input ) +{ + if( m/^\s*\#\s*SKIP\s*\:\s*(.*\S+)\s*\; LINEINFO.*$/ ) + { + $perlskipcode .= "(" . $1 . ")||"; + push @perlskipcodeindividual, $1; + } +} +if( defined $perlskipcode ) +{ + $perlskipcode .= "0"; + $perlskipcode =~ s/\n//g; +} +else +{ + $perlskipcode = "0"; +} + +#print $perlskipcode . "\n"; + + +# Translate the input into a perl program that'll unroll everything and +# substitute variables. +while( $inputLine = shift @input ) +{ + $inputLine =~ s/\n//g; + # leave out lines that are only whitespace. + if( $inputLine =~ m/^\s*; LINEINFO.*$/ ) + { + next; + } + local( $inputLineNoLineNum ) = $inputLine; + $inputLineNoLineNum =~ s/; LINEINFO.*//gi; + if( &IsPerl( $inputLineNoLineNum ) ) + { + $inputLineNoLineNum = &FixupAllocateFree( $inputLineNoLineNum ); + push @outputProgram, $inputLineNoLineNum . "\n"; + } + else + { + # make asm lines that have quotes in them not barf. + $inputLine =~ s/\"/\\\"/g; + $inputLine = &TranslateDXKeywords( $inputLine ); + push @outputProgram, &GetLeadingWhiteSpace( $inputLine ) . "push \@output, \"" . + $inputLine . "\\n\";\n"; + } +} + +$outputProgram = join "", @outputProgram; + +$filename_base = $filename; +$filename_base =~ s/\.vsh//i; + +open DEBUGOUT, ">$vshtmp" . "/$filename_base.pl" || die; +print DEBUGOUT $outputProgram; +close DEBUGOUT; + +# Make a function called OutputProgram() +$bigProg = "sub OutputProgram { " . $outputProgram . "}"; +eval( $bigProg ); + + +#print $outputProgram; + +#push @finalheader, "// hack to force dependency checking\n"; +#push @finalheader, "\#ifdef NEVER\n"; +#push @finalheader, "\#include \"" . $filename_base . "\.vsh\"\n"; +#push @finalheader, "\#include \"..\\..\\devtools\\bin\\vsh_prep.pl\"\n"; +#push @finalheader, "\#endif\n"; + +%g_TimingBlocks = (); +$main_start_time = SampleTime(); + +$numCombos = &CalcNumCombos(); +$numDynamicCombos = &CalcNumDynamicCombos(); +#print "$numCombos total combos\n"; +#print "$numDynamicCombos dynamic combos\n"; +#print $numCombos / $numDynamicCombos . " static combos\n"; + +# Write out the C++ helper class for picking shader combos +$fxc_filename = $filename_base; +&WriteStaticHelperClasses(); +&WriteDynamicHelperClasses(); + +# Create a subroutine out of $perlskipcode +$perlskipfunc = "sub SkipCombo { return $perlskipcode; }\n"; +#print $perlskipfunc; + +eval $perlskipfunc; +&CreateFuncToSetPerlVars(); + +my $incfilename = "$vshtmp/$filename_base" . ".inc"; + +# Write the inc file that has indexing helpers, etc. +&WriteFile( $incfilename, join( "", @outputHeader ) ); + + +# Run the output program for all the combinations of bones and lights. +print "$filename_base.vsh\n"; +for( $i = 0; $i < $numCombos; $i++ ) +{ +# print "combo $i\n"; + &SetPerlVarsFunc( $i ); + local( $compileFailed ); + $ret = &SkipCombo; + if( !defined $ret ) + { + die "$@\n"; + } + if( $ret ) + { + # skip this combo! + $compileFailed = 1; + $numSkipped++; + next; + } + + $start = SampleTime(); + + $g_usesPos = 0; + $g_usesPosFlex = 0; + $g_usesBoneWeights = 0; + $g_usesBoneIndices = 0; + $g_usesNormal = 0; + $g_usesNormalFlex = 0; + $g_usesColor = 0; + $g_usesSpecular = 0; + $g_usesTexCoord0 = 0; + $g_usesTexCoord1 = 0; + $g_usesTexCoord2 = 0; + $g_usesTexCoord3 = 0; + $g_usesTangentS = 0; + $g_usesTangentT = 0; + $g_usesUserData = 0; + + undef @output; + + $g_TimingBlocks{"inner1"} += SampleTime() - $start; + + $eval_start_time = SampleTime(); + &OutputProgram(); + $eval_total_time += (SampleTime() - $eval_start_time); + + $start = SampleTime(); + + # Strip out comments once so we don't have to do it in all the UsesRegister calls. + @stripped = @output; + map + { + $_ =~ s/;.*//gi; + $_ =~ s/\/\/.*//gi; + } @stripped; + my $strippedStr = join( "", @stripped ); + + $g_TimingBlocks{"inner2"} += SampleTime() - $start; + + $start = SampleTime(); + + # Have to make another pass through after we know which v registers are used. . yuck. + $g_usesPos = &UsesRegister( $vPos, $strippedStr ); + if( $g_x360 ) + { + $g_usesPosFlex = &UsesRegister( $vPosFlex, $strippedStr ); + $g_usesNormalFlex = &UsesRegister( $vNormalFlex, $strippedStr ); + } + $g_usesBoneWeights = &UsesRegister( $vBoneWeights, $strippedStr ); + $g_usesBoneIndices = &UsesRegister( $vBoneIndices, $strippedStr ); + $g_usesNormal = &UsesRegister( $vNormal, $strippedStr ); + $g_usesColor = &UsesRegister( $vColor, $strippedStr ); + $g_usesSpecular = &UsesRegister( $vSpecular, $strippedStr ); + $g_usesTexCoord0 = &UsesRegister( $vTexCoord0, $strippedStr ); + $g_usesTexCoord1 = &UsesRegister( $vTexCoord1, $strippedStr ); + $g_usesTexCoord2 = &UsesRegister( $vTexCoord2, $strippedStr ); + $g_usesTexCoord3 = &UsesRegister( $vTexCoord3, $strippedStr ); + $g_usesTangentS = &UsesRegister( $vTangentS, $strippedStr ); + $g_usesTangentT = &UsesRegister( $vTangentT, $strippedStr ); + $g_usesUserData = &UsesRegister( $vUserData, $strippedStr ); + undef @output; + + $g_TimingBlocks{"inner2"} += SampleTime() - $start; + + $eval_start_time = SampleTime(); + # Running OutputProgram generates $outfilename + &OutputProgram(); + $eval_total_time += (SampleTime() - $eval_start_time); + + $start = SampleTime(); + + &CheckUnfreedRegisters(); + + for( $j = 0; $j < scalar( @output ); $j++ ) + { + # remove whitespace from the beginning of each line. + $output[$j] =~ s/^\s+//; + # remove LINEINFO from empty lines. + $output[$j] =~ s/^; LINEINFO.*//; + } + + $g_TimingBlocks{"inner3"} += SampleTime() - $start; + $start = SampleTime(); + + + $outfilename_base = $filename_base . "_" . $i; + + # $outfilename is the name of the file generated from executing the perl code + # for this shader. This file is generated once per combo. + # We will assemble this shader with vsa.exe. + $outfilename = "$vshtmp\\" . $outfilename_base . ".tmp"; + +# $outhdrfilename = "$vshtmp\\" . $outfilename_base . ".h"; +# unlink $outhdrfilename; + + open OUTPUT, ">$outfilename" || die; + print OUTPUT @output; + close OUTPUT; + + $g_TimingBlocks{"inner4"} += SampleTime() - $start; + $start = SampleTime(); + + local( $instructionCount ) = &CountInstructions( @output ); + $g_TimingBlocks{"inner5"} += SampleTime() - $start; + + local( $debug ); + + $debug = 1; +# for( $debug = 1; $debug >= 0; $debug-- ) + { + # assemble the vertex shader + unlink "shader$i.o"; + if( $g_x360 ) + { + $vsa = "..\\..\\x360xdk\\bin\\win32\\vsa"; + } + else + { + $vsa = "..\\..\\dx9sdk\\utilities\\vsa"; + } + $vsadebug = "$vsa /nologo /Foshader$i.o $outfilename"; + $vsanodebug = "$vsa /nologo /Foshader$i.o $outfilename"; + + $vsa_start_time = SampleTime(); + + if( $debug ) + { +# print $vsadebug . "\n"; + @vsaoutput = `$vsadebug 2>&1`; +# print @vsaoutput; + } + else + { + @vsaoutput = `$vsanodebug 2>&1`; + } + + $vsa_total_time += SampleTime() - $vsa_start_time; + + $start = SampleTime(); + + &TranslateErrorMessages( @vsaoutput ); + + $g_TimingBlocks{"inner6"} += SampleTime() - $start; + + push @finalheader, @hdr; + } +} + + +$main_total_time = SampleTime() - $main_start_time; + +# stick info about the shaders at the end of the inc file. +push @finalheader, "static PrecompiledShaderByteCode_t $filename_base" . "_vertex_shaders[] = {\n"; +for( $i = 0; $i < $numCombos; $i++ ) +{ + $outfilename_base = $filename_base . "_" . $i; + push @finalheader, "{ $outfilename_base, sizeof( $outfilename_base ) },\n"; +} +push @finalheader, "};\n"; + + +push @finalheader, "struct $filename_base" . "_VertexShader_t : public PrecompiledShader_t\n"; +push @finalheader, "{\n"; +push @finalheader, "\t$filename_base" . "_VertexShader_t()\n"; +push @finalheader, "\t{\n"; +push @finalheader, "\t\tm_nFlags = 0;\n"; + +$flags = 0; +#push @finalheader, "\t\tppVertexShaders = $filename_base" . "_vertex_shaders;\n"; +push @finalheader, "\t\tm_pByteCode = $filename_base" . "_vertex_shaders;\n"; +push @finalheader, "\t\tm_pName = \"$filename_base\";\n"; +push @finalheader, "\t\tm_nShaderCount = " . ( $maxNumBones + 1 ) * $totalFogCombos * $totalLightCombos . ";\n"; +push @finalheader, "\t\tm_nDynamicCombos = m_nShaderCount;\n"; +push @finalheader, "\t\tGetShaderDLL()->InsertPrecompiledShader( PRECOMPILED_VERTEX_SHADER, this );\n"; +push @finalheader, "\t}\n"; +push @finalheader, "\tvirtual const PrecompiledShaderByteCode_t &GetByteCode( int shaderID )\n"; +push @finalheader, "\t{\n"; +push @finalheader, "\t\treturn m_pByteCode[shaderID];\n"; +push @finalheader, "\t}\n"; +push @finalheader, "};\n"; +push @finalheader, "static $filename_base" . "_VertexShader_t $filename_base" . "_VertexShaderInstance;\n"; + +# Write the final header file with the compiled vertex shader programs. +$finalheadername = "$vshtmp\\" . $filename_base . ".inc"; +#print "writing $finalheadername\n"; +#open FINALHEADER, ">$finalheadername" || die; +#print FINALHEADER @finalheader; +#close FINALHEADER; + +&MakeDirHier( "shaders/vsh" ); + +my $vcsName = ""; +if( $g_x360 ) +{ + $vcsName = $filename_base . ".360.vcs"; +} +else +{ + $vcsName = $filename_base . ".vcs"; +} +open COMPILEDSHADER, ">shaders/vsh/$vcsName" || die; +binmode( COMPILEDSHADER ); + +# +# Write out the part of the header that we know. . we'll write the rest after writing the object code. +# + +# Pack arguments +my $sInt = "i"; +my $uInt = "I"; +if ( $g_x360 ) +{ + # Change arguments to "big endian long" + $sInt = "N"; + $uInt = "N"; +} + +my $undecoratedinput = join "", &ReadInputFileWithoutLineInfo( $filename ); +#print STDERR "undecoratedinput: $undecoratedinput\n"; +my $crc = crc32( $undecoratedinput ); +#print STDERR "crc for $filename: $crc\n"; + +# version +print COMPILEDSHADER pack $sInt, 4; +# totalCombos +print COMPILEDSHADER pack $sInt, $numCombos; +# dynamic combos +print COMPILEDSHADER pack $sInt, $numDynamicCombos; +# flags +print COMPILEDSHADER pack $uInt, $flags; +# centroid mask +print COMPILEDSHADER pack $uInt, 0; +# reference size +print COMPILEDSHADER pack $uInt, 0; +# crc32 of the source code +print COMPILEDSHADER pack $uInt, $crc; + +my $beginningOfDir = tell COMPILEDSHADER; + +# Write out a blank directionary. . we'll fill it in later. +for( $i = 0; $i < $numCombos; $i++ ) +{ + # offset from beginning of file. + print COMPILEDSHADER pack $sInt, 0; + # size + print COMPILEDSHADER pack $sInt, 0; +} + +my $startByteCode = tell COMPILEDSHADER; +my @byteCodeStart; +my @byteCodeSize; + +# Write out the shader object code. +for( $shaderCombo = 0; $shaderCombo < $numCombos; $shaderCombo++ ) +{ + my $filename = "shader$shaderCombo\.o"; + my $filesize = (stat $filename)[7]; + $byteCodeStart[$shaderCombo] = tell COMPILEDSHADER; + $byteCodeSize[$shaderCombo] = $filesize; + open SHADERBYTECODE, "<$filename" || die; + binmode SHADERBYTECODE; + my $bin; + my $numread = read SHADERBYTECODE, $bin, $filesize; +# print "filename: $filename numread: $numread filesize: $filesize\n"; + close SHADERBYTECODE; + unlink $filename; + + print COMPILEDSHADER $bin; +} + +# Seek back to the directory and write it out. +seek COMPILEDSHADER, $beginningOfDir, 0; +for( $i = 0; $i < $numCombos; $i++ ) +{ + # offset from beginning of file. + print COMPILEDSHADER pack $sInt, $byteCodeStart[$i]; + # size + print COMPILEDSHADER pack $sInt, $byteCodeSize[$i]; +} + +close COMPILEDSHADER; + +$total_time = SampleTime() - $total_start_time; + +if ( $ShowTimers ) +{ + print "\n\n"; + print sprintf( "Main loop time : %0.4f sec, (%0.2f%%)\n", $main_total_time, 100*$main_total_time / $total_time ); + print sprintf( "Inner1 time : %0.4f sec, (%0.2f%%)\n", $inner1_total_time, 100*$inner1_total_time / $total_time ); + print sprintf( "VSA time : %0.4f sec, (%0.2f%%)\n", $vsa_total_time, 100*$vsa_total_time / $total_time ); + print sprintf( "eval() time : %0.4f sec, (%0.2f%%)\n", $eval_total_time, 100*$eval_total_time / $total_time ); + print sprintf( "UsesRegister time: %0.4f sec, (%0.2f%%)\n", $usesr_total_time, 100*$usesr_total_time / $total_time ); + + foreach $key ( keys %g_TimingBlocks ) + { + print sprintf( "$key time: %0.4f sec, (%0.2f%%)\n", $g_TimingBlocks{$key}, 100*$g_TimingBlocks{$key} / $total_time ); + } + + print sprintf( "Total time : %0.4f sec\n", $total_time ); +} + diff --git a/game/client/AnimateSpecificTextureProxy.cpp b/game/client/AnimateSpecificTextureProxy.cpp new file mode 100644 index 00000000..cef92423 --- /dev/null +++ b/game/client/AnimateSpecificTextureProxy.cpp @@ -0,0 +1,53 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Acts exactly like "AnimatedTexture", but ONLY if the texture +// it's working on matches the desired texture to work on. +// +// This assumes that some other proxy will be switching out the textures. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "materialsystem/IMaterialProxy.h" +#include "materialsystem/IMaterialVar.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/ITexture.h" +#include "BaseAnimatedTextureProxy.h" +#include "utlstring.h" +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CAnimateSpecificTexture : public CBaseAnimatedTextureProxy +{ +private: + CUtlString m_OnlyAnimateOnTexture; +public: + virtual float GetAnimationStartTime( void* pBaseEntity ) { return 0; } + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pC_BaseEntity ); + virtual void Release( void ) { delete this; } +}; + +bool CAnimateSpecificTexture::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + char const* pszAnimateOnTexture = pKeyValues->GetString( "onlyAnimateOnTexture" ); + if( !pszAnimateOnTexture ) + return false; + + m_OnlyAnimateOnTexture.Set( pszAnimateOnTexture ); + + return CBaseAnimatedTextureProxy::Init( pMaterial, pKeyValues ); +} + +void CAnimateSpecificTexture::OnBind( void *pC_BaseEntity ) +{ + if( FStrEq( m_AnimatedTextureVar->GetTextureValue()->GetName(), m_OnlyAnimateOnTexture ) ) + { + CBaseAnimatedTextureProxy::OnBind( pC_BaseEntity ); + } + //else do nothing +} + +EXPOSE_INTERFACE( CAnimateSpecificTexture, IMaterialProxy, "AnimateSpecificTexture" IMATERIAL_PROXY_INTERFACE_VERSION ); \ No newline at end of file diff --git a/game/client/C_Env_Projected_Texture.h b/game/client/C_Env_Projected_Texture.h new file mode 100644 index 00000000..0cdb1063 --- /dev/null +++ b/game/client/C_Env_Projected_Texture.h @@ -0,0 +1,65 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_ENVPROJECTEDTEXTURE_H +#define C_ENVPROJECTEDTEXTURE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" +#include "basetypes.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_EnvProjectedTexture : public C_BaseEntity +{ + DECLARE_CLASS( C_EnvProjectedTexture, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_EnvProjectedTexture(); + ~C_EnvProjectedTexture(); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + void ShutDownLightHandle( void ); + + virtual void Simulate(); + + void UpdateLight( bool bForceUpdate ); + + bool ShadowsEnabled(); + + float GetFOV(); + +private: + + ClientShadowHandle_t m_LightHandle; + + EHANDLE m_hTargetEntity; + + bool m_bState; + float m_flLightFOV; + bool m_bEnableShadows; + bool m_bLightOnlyTarget; + bool m_bLightWorld; + bool m_bCameraSpace; + color32 m_cLightColor; + float m_flAmbient; + char m_SpotlightTextureName[ MAX_PATH ]; + int m_nSpotlightTextureFrame; + int m_nShadowQuality; + bool m_bCurrentShadow; + +public: + C_EnvProjectedTexture *m_pNext; +}; + +C_EnvProjectedTexture* GetEnvProjectedTextureList(); + +#endif // C_ENVPROJECTEDTEXTURE_H diff --git a/game/client/C_MaterialModifyControl.cpp b/game/client/C_MaterialModifyControl.cpp new file mode 100644 index 00000000..0566d74b --- /dev/null +++ b/game/client/C_MaterialModifyControl.cpp @@ -0,0 +1,798 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Material Modify control entity. +// +//=============================================================================// + +#include "cbase.h" +#include "ProxyEntity.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" +#include "materialsystem/ITexture.h" +#include "iviewrender.h" +#include "texture_group_names.h" +#include "BaseAnimatedTextureProxy.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MATERIAL_MODIFY_STRING_SIZE 255 +#define MATERIAL_MODIFY_ANIMATION_UNSET -1 + +// Must match MaterialModifyControl.cpp +enum MaterialModifyMode_t +{ + MATERIAL_MODIFY_MODE_NONE = 0, + MATERIAL_MODIFY_MODE_SETVAR = 1, + MATERIAL_MODIFY_MODE_ANIM_SEQUENCE = 2, + MATERIAL_MODIFY_MODE_FLOAT_LERP = 3, +}; + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + +ConVar debug_materialmodifycontrol_client( "debug_materialmodifycontrol_client", "0" ); + +struct materialanimcommands_t +{ + int iFrameStart; + int iFrameEnd; + bool bWrap; + float flFrameRate; +}; + +struct materialfloatlerpcommands_t +{ + int flStartValue; + int flEndValue; + float flTransitionTime; +}; + +//------------------------------------------------------------------------------ +// FIXME: This really should inherit from something more lightweight +//------------------------------------------------------------------------------ + +class C_MaterialModifyControl : public C_BaseEntity +{ +public: + + DECLARE_CLASS( C_MaterialModifyControl, C_BaseEntity ); + + C_MaterialModifyControl(); + + void OnPreDataChanged( DataUpdateType_t updateType ); + void OnDataChanged( DataUpdateType_t updateType ); + bool ShouldDraw(); + + IMaterial *GetMaterial( void ) { return m_pMaterial; } + const char *GetMaterialVariableName( void ) { return m_szMaterialVar; } + const char *GetMaterialVariableValue( void ) { return m_szMaterialVarValue; } + + DECLARE_CLIENTCLASS(); + + // Animated texture and Float Lerp usage + bool HasNewAnimationCommands( void ) { return m_bHasNewAnimationCommands; } + void ClearAnimationCommands( void ) { m_bHasNewAnimationCommands = false; } + + // Animated texture usage + void GetAnimationCommands( materialanimcommands_t *pCommands ); + + // FloatLerp usage + void GetFloatLerpCommands( materialfloatlerpcommands_t *pCommands ); + + void SetAnimationStartTime( float flTime ) + { + m_flAnimationStartTime = flTime; + } + float GetAnimationStartTime( void ) const + { + return m_flAnimationStartTime; + } + + MaterialModifyMode_t GetModifyMode( void ) const + { + return ( MaterialModifyMode_t)m_nModifyMode; + } +private: + + char m_szMaterialName[MATERIAL_MODIFY_STRING_SIZE]; + char m_szMaterialVar[MATERIAL_MODIFY_STRING_SIZE]; + char m_szMaterialVarValue[MATERIAL_MODIFY_STRING_SIZE]; + IMaterial *m_pMaterial; + + bool m_bHasNewAnimationCommands; + + // Animation commands from the server + int m_iFrameStart; + int m_iFrameEnd; + bool m_bWrap; + float m_flFramerate; + bool m_bNewAnimCommandsSemaphore; + bool m_bOldAnimCommandsSemaphore; + + // Float lerp commands from the server + float m_flFloatLerpStartValue; + float m_flFloatLerpEndValue; + float m_flFloatLerpTransitionTime; + bool m_bFloatLerpWrap; + float m_flAnimationStartTime; + + int m_nModifyMode; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_MaterialModifyControl, DT_MaterialModifyControl, CMaterialModifyControl) + RecvPropString( RECVINFO( m_szMaterialName ) ), + RecvPropString( RECVINFO( m_szMaterialVar ) ), + RecvPropString( RECVINFO( m_szMaterialVarValue ) ), + RecvPropInt( RECVINFO(m_iFrameStart) ), + RecvPropInt( RECVINFO(m_iFrameEnd) ), + RecvPropInt( RECVINFO(m_bWrap) ), + RecvPropFloat( RECVINFO(m_flFramerate) ), + RecvPropInt( RECVINFO(m_bNewAnimCommandsSemaphore) ), + RecvPropFloat( RECVINFO(m_flFloatLerpStartValue) ), + RecvPropFloat( RECVINFO(m_flFloatLerpEndValue) ), + RecvPropFloat( RECVINFO(m_flFloatLerpTransitionTime) ), + RecvPropInt( RECVINFO(m_bFloatLerpWrap) ), + RecvPropInt( RECVINFO(m_nModifyMode) ), +END_RECV_TABLE() + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +C_MaterialModifyControl::C_MaterialModifyControl() +{ + m_pMaterial = NULL; + m_bOldAnimCommandsSemaphore = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_MaterialModifyControl::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + m_bOldAnimCommandsSemaphore = m_bNewAnimCommandsSemaphore; +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void C_MaterialModifyControl::OnDataChanged( DataUpdateType_t updateType ) +{ + if( updateType == DATA_UPDATE_CREATED ) + { + m_pMaterial = materials->FindMaterial( m_szMaterialName, TEXTURE_GROUP_OTHER ); + + // Clear out our variables + m_bHasNewAnimationCommands = true; + } + + // Detect changes in the anim commands + if ( m_bNewAnimCommandsSemaphore != m_bOldAnimCommandsSemaphore ) + { + m_bHasNewAnimationCommands = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_MaterialModifyControl::GetAnimationCommands( materialanimcommands_t *pCommands ) +{ + pCommands->iFrameStart = m_iFrameStart; + pCommands->iFrameEnd = m_iFrameEnd; + pCommands->bWrap = m_bWrap; + pCommands->flFrameRate = m_flFramerate; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_MaterialModifyControl::GetFloatLerpCommands( materialfloatlerpcommands_t *pCommands ) +{ + pCommands->flStartValue = m_flFloatLerpStartValue; + pCommands->flEndValue = m_flFloatLerpEndValue; + pCommands->flTransitionTime = m_flFloatLerpTransitionTime; +} + +//------------------------------------------------------------------------------ +// Purpose: We don't draw. +//------------------------------------------------------------------------------ +bool C_MaterialModifyControl::ShouldDraw() +{ + return false; +} + +//============================================================================= +// +// THE MATERIALMODIFYPROXY ITSELF +// +class CMaterialModifyProxy : public CBaseAnimatedTextureProxy +{ +public: + CMaterialModifyProxy(); + virtual ~CMaterialModifyProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pEntity ); + virtual IMaterial *GetMaterial(); + +private: + void OnBindSetVar( C_MaterialModifyControl *pControl ); + void OnBindAnimatedTexture( C_MaterialModifyControl *pControl ); + void OnBindFloatLerp( C_MaterialModifyControl *pControl ); + float GetAnimationStartTime( void* pArg ); + void AnimationWrapped( void* pArg ); + + IMaterial *m_pMaterial; + + // texture animation stuff + int m_iFrameStart; + int m_iFrameEnd; + bool m_bReachedEnd; + bool m_bCustomWrap; + float m_flCustomFramerate; + + // float lerp stuff + IMaterialVar *m_pMaterialVar; + int m_flStartValue; + int m_flEndValue; + float m_flStartTime; + float m_flTransitionTime; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMaterialModifyProxy::CMaterialModifyProxy() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMaterialModifyProxy::~CMaterialModifyProxy() +{ +} + +bool CMaterialModifyProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + // set var stuff + m_pMaterial = pMaterial; + + // float lerp stuff + m_flStartValue = MATERIAL_MODIFY_ANIMATION_UNSET; + m_flEndValue = MATERIAL_MODIFY_ANIMATION_UNSET; + + // animated stuff +// m_pMaterial = pMaterial; +// m_iFrameStart = MATERIAL_MODIFY_ANIMATION_UNSET; +// m_iFrameEnd = MATERIAL_MODIFY_ANIMATION_UNSET; +// m_bReachedEnd = false; +// return CBaseAnimatedTextureProxy::Init( pMaterial, pKeyValues ); + + return true; +} + +void CMaterialModifyProxy::OnBind( void *pEntity ) +{ + // Get the modified material vars from the entity input + IClientRenderable *pRend = (IClientRenderable *)pEntity; + if ( pRend ) + { + C_BaseEntity *pBaseEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + + if ( pBaseEntity ) + { + if( debug_materialmodifycontrol_client.GetBool() ) + { +// DevMsg( 1, "%s\n", pBaseEntity->GetDebugName() ); + } + int numChildren = 0; + bool gotOne = false; + for ( C_BaseEntity *pChild = pBaseEntity->FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() ) + { + numChildren++; + C_MaterialModifyControl *pControl = dynamic_cast( pChild ); + if ( !pControl ) + continue; + + if( debug_materialmodifycontrol_client.GetBool() ) + { +// DevMsg( 1, "pControl: 0x%p\n", pControl ); + } + + switch( pControl->GetModifyMode() ) + { + case MATERIAL_MODIFY_MODE_NONE: + break; + case MATERIAL_MODIFY_MODE_SETVAR: + gotOne = true; + OnBindSetVar( pControl ); + break; + case MATERIAL_MODIFY_MODE_ANIM_SEQUENCE: + OnBindAnimatedTexture( pControl ); + break; + case MATERIAL_MODIFY_MODE_FLOAT_LERP: + OnBindFloatLerp( pControl ); + break; + default: + Assert( 0 ); + break; + } + } + if( gotOne ) + { +// DevMsg( 1, "numChildren: %d\n", numChildren ); + } + } + } + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CMaterialModifyProxy::GetMaterial() +{ + return m_pMaterial; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaterialModifyProxy::OnBindSetVar( C_MaterialModifyControl *pControl ) +{ + IMaterial *pMaterial = pControl->GetMaterial(); + if( !pMaterial ) + { + Assert( 0 ); + return; + } + + if ( pMaterial != m_pMaterial ) + { +// Warning( "\t%s!=%s\n", pMaterial->GetName(), m_pMaterial->GetName() ); + return; + } + + bool bFound; + IMaterialVar *pMaterialVar = pMaterial->FindVar( pControl->GetMaterialVariableName(), &bFound, false ); + if ( !bFound ) + return; + + if( Q_strcmp( pControl->GetMaterialVariableValue(), "" ) ) + { +// const char *pMaterialName = m_pMaterial->GetName(); +// const char *pMaterialVarName = pMaterialVar->GetName(); +// const char *pMaterialVarValue = pControl->GetMaterialVariableValue(); +// if( debug_materialmodifycontrol_client.GetBool() +// && Q_stristr( m_pMaterial->GetName(), "faceandhair" ) +// && Q_stristr( pMaterialVar->GetName(), "self" ) +// ) +// { +// static int count = 0; +// DevMsg( 1, "CMaterialModifyProxy::OnBindSetVar \"%s\" %s=%s %d pControl=0x%p\n", +// m_pMaterial->GetName(), pMaterialVar->GetName(), pControl->GetMaterialVariableValue(), count++, pControl ); +// } + pMaterialVar->SetValueAutodetectType( pControl->GetMaterialVariableValue() ); + } +} + + +//----------------------------------------------------------------------------- +// Does the dirty deed +//----------------------------------------------------------------------------- +void CMaterialModifyProxy::OnBindAnimatedTexture( C_MaterialModifyControl *pControl ) +{ + assert ( m_AnimatedTextureVar ); + if( m_AnimatedTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE ) + return; + + ITexture *pTexture; + pTexture = m_AnimatedTextureVar->GetTextureValue(); + + if ( !pControl ) + return; + + if ( pControl->HasNewAnimationCommands() ) + { + // Read the data from the modify entity + materialanimcommands_t sCommands; + pControl->GetAnimationCommands( &sCommands ); + + m_iFrameStart = sCommands.iFrameStart; + m_iFrameEnd = sCommands.iFrameEnd; + m_bCustomWrap = sCommands.bWrap; + m_flCustomFramerate = sCommands.flFrameRate; + m_bReachedEnd = false; + + m_flStartTime = gpGlobals->curtime; + + pControl->ClearAnimationCommands(); + } + + // Init all the vars based on whether we're using the base material settings, + // or the custom ones from the entity input. + int numFrames; + bool bWrapAnimation; + float flFrameRate; + int iLastFrame; + + // Do we have a custom frame section from the server? + if ( m_iFrameStart != MATERIAL_MODIFY_ANIMATION_UNSET ) + { + if ( m_iFrameEnd == MATERIAL_MODIFY_ANIMATION_UNSET ) + { + m_iFrameEnd = pTexture->GetNumAnimationFrames(); + } + + numFrames = (m_iFrameEnd - m_iFrameStart) + 1; + bWrapAnimation = m_bCustomWrap; + flFrameRate = m_flCustomFramerate; + iLastFrame = (m_iFrameEnd - 1); + } + else + { + numFrames = pTexture->GetNumAnimationFrames(); + bWrapAnimation = m_WrapAnimation; + flFrameRate = m_FrameRate; + iLastFrame = (numFrames - 1); + } + + // Have we already reached the end? If so, stay there. + if ( m_bReachedEnd && !bWrapAnimation ) + { + m_AnimatedTextureFrameNumVar->SetIntValue( iLastFrame ); + return; + } + + // NOTE: Must not use relative time based methods here + // because the bind proxy can be called many times per frame. + // Prevent multiple Wrap callbacks to be sent for no wrap mode + float startTime; + if ( m_iFrameStart != MATERIAL_MODIFY_ANIMATION_UNSET ) + { + startTime = m_flStartTime; + } + else + { + startTime = GetAnimationStartTime(pControl); + } + float deltaTime = gpGlobals->curtime - startTime; + float prevTime = deltaTime - gpGlobals->frametime; + + // Clamp.. + if (deltaTime < 0.0f) + deltaTime = 0.0f; + if (prevTime < 0.0f) + prevTime = 0.0f; + + float frame = flFrameRate * deltaTime; + float prevFrame = flFrameRate * prevTime; + + int intFrame = ((int)frame) % numFrames; + int intPrevFrame = ((int)prevFrame) % numFrames; + + if ( m_iFrameStart != MATERIAL_MODIFY_ANIMATION_UNSET ) + { + intFrame += m_iFrameStart; + intPrevFrame += m_iFrameStart; + } + + // Report wrap situation... + if (intPrevFrame > intFrame) + { + m_bReachedEnd = true; + + if (bWrapAnimation) + { + AnimationWrapped( pControl ); + } + else + { + // Only sent the wrapped message once. + // when we're in non-wrapping mode + if (prevFrame < numFrames) + AnimationWrapped( pControl ); + intFrame = numFrames - 1; + } + } + + m_AnimatedTextureFrameNumVar->SetIntValue( intFrame ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CMaterialModifyProxy::GetAnimationStartTime( void* pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + if (!pRend) + return 0.0f; + + C_BaseEntity* pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if (pEntity) + { + return pEntity->GetTextureAnimationStartTime(); + } + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaterialModifyProxy::AnimationWrapped( void* pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + if (!pRend) + return; + + C_BaseEntity* pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if (pEntity) + { + pEntity->TextureAnimationWrapped(); + } +} + +//----------------------------------------------------------------------------- +// Does the dirty deed +//----------------------------------------------------------------------------- +void CMaterialModifyProxy::OnBindFloatLerp( C_MaterialModifyControl *pControl ) +{ + if ( !pControl ) + return; + + if ( pControl->HasNewAnimationCommands() ) + { + pControl->SetAnimationStartTime( gpGlobals->curtime ); + pControl->ClearAnimationCommands(); + } + + // Read the data from the modify entity + materialfloatlerpcommands_t sCommands; + pControl->GetFloatLerpCommands( &sCommands ); + + m_flStartValue = sCommands.flStartValue; + m_flEndValue = sCommands.flEndValue; + m_flTransitionTime = sCommands.flTransitionTime; + m_flStartTime = pControl->GetAnimationStartTime(); + bool bFound; + m_pMaterialVar = m_pMaterial->FindVar( pControl->GetMaterialVariableName(), &bFound, false ); + + if( bFound ) + { + float currentValue; + if( m_flTransitionTime > 0.0f ) + { + currentValue = m_flStartValue + ( m_flEndValue - m_flStartValue ) * clamp( ( ( gpGlobals->curtime - m_flStartTime ) / m_flTransitionTime ), 0.0f, 1.0f ); + } + else + { + currentValue = m_flEndValue; + } + + if( debug_materialmodifycontrol_client.GetBool() && Q_stristr( m_pMaterial->GetName(), "faceandhair" ) && Q_stristr( m_pMaterialVar->GetName(), "warp" ) ) + { + static int count = 0; + DevMsg( 1, "CMaterialFloatLerpProxy::OnBind \"%s\" %s=%f %d\n", m_pMaterial->GetName(), m_pMaterialVar->GetName(), currentValue, count++ ); + } + m_pMaterialVar->SetFloatValue( currentValue ); + } +} + +//============================================================================= +// +// MATERIALMODIFYANIMATED PROXY +// +class CMaterialModifyAnimatedProxy : public CBaseAnimatedTextureProxy +{ +public: + CMaterialModifyAnimatedProxy() {}; + virtual ~CMaterialModifyAnimatedProxy() {}; + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pEntity ); + + virtual float GetAnimationStartTime( void* pBaseEntity ); + virtual void AnimationWrapped( void* pC_BaseEntity ); + +private: + IMaterial *m_pMaterial; + int m_iFrameStart; + int m_iFrameEnd; + bool m_bReachedEnd; + float m_flStartTime; + bool m_bCustomWrap; + float m_flCustomFramerate; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CMaterialModifyAnimatedProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + m_pMaterial = pMaterial; + m_iFrameStart = MATERIAL_MODIFY_ANIMATION_UNSET; + m_iFrameEnd = MATERIAL_MODIFY_ANIMATION_UNSET; + m_bReachedEnd = false; + return CBaseAnimatedTextureProxy::Init( pMaterial, pKeyValues ); +} + +//----------------------------------------------------------------------------- +// Does the dirty deed +//----------------------------------------------------------------------------- +void CMaterialModifyAnimatedProxy::OnBind( void *pEntity ) +{ + assert ( m_AnimatedTextureVar ); + if( m_AnimatedTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE ) + return; + + ITexture *pTexture; + pTexture = m_AnimatedTextureVar->GetTextureValue(); + + // Get the modified material vars from the entity input + IClientRenderable *pRend = (IClientRenderable *)pEntity; + if ( pRend ) + { + C_BaseEntity *pBaseEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if ( pBaseEntity ) + { + for ( C_BaseEntity *pChild = pBaseEntity->FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() ) + { + C_MaterialModifyControl *pControl = dynamic_cast( pChild ); + if ( !pControl ) + continue; + + if ( !pControl->HasNewAnimationCommands() ) + continue; + + // Read the data from the modify entity + materialanimcommands_t sCommands; + pControl->GetAnimationCommands( &sCommands ); + + m_iFrameStart = sCommands.iFrameStart; + m_iFrameEnd = sCommands.iFrameEnd; + m_bCustomWrap = sCommands.bWrap; + m_flCustomFramerate = sCommands.flFrameRate; + m_bReachedEnd = false; + + m_flStartTime = gpGlobals->curtime; + + pControl->ClearAnimationCommands(); + } + } + } + + // Init all the vars based on whether we're using the base material settings, + // or the custom ones from the entity input. + int numFrames; + bool bWrapAnimation; + float flFrameRate; + int iLastFrame; + + // Do we have a custom frame section from the server? + if ( m_iFrameStart != MATERIAL_MODIFY_ANIMATION_UNSET ) + { + if ( m_iFrameEnd == MATERIAL_MODIFY_ANIMATION_UNSET ) + { + m_iFrameEnd = pTexture->GetNumAnimationFrames(); + } + + numFrames = (m_iFrameEnd - m_iFrameStart) + 1; + bWrapAnimation = m_bCustomWrap; + flFrameRate = m_flCustomFramerate; + iLastFrame = (m_iFrameEnd - 1); + } + else + { + numFrames = pTexture->GetNumAnimationFrames(); + bWrapAnimation = m_WrapAnimation; + flFrameRate = m_FrameRate; + iLastFrame = (numFrames - 1); + } + + // Have we already reached the end? If so, stay there. + if ( m_bReachedEnd && !bWrapAnimation ) + { + m_AnimatedTextureFrameNumVar->SetIntValue( iLastFrame ); + return; + } + + // NOTE: Must not use relative time based methods here + // because the bind proxy can be called many times per frame. + // Prevent multiple Wrap callbacks to be sent for no wrap mode + float startTime; + if ( m_iFrameStart != MATERIAL_MODIFY_ANIMATION_UNSET ) + { + startTime = m_flStartTime; + } + else + { + startTime = GetAnimationStartTime(pEntity); + } + float deltaTime = gpGlobals->curtime - startTime; + float prevTime = deltaTime - gpGlobals->frametime; + + // Clamp.. + if (deltaTime < 0.0f) + deltaTime = 0.0f; + if (prevTime < 0.0f) + prevTime = 0.0f; + + float frame = flFrameRate * deltaTime; + float prevFrame = flFrameRate * prevTime; + + int intFrame = ((int)frame) % numFrames; + int intPrevFrame = ((int)prevFrame) % numFrames; + + if ( m_iFrameStart != MATERIAL_MODIFY_ANIMATION_UNSET ) + { + intFrame += m_iFrameStart; + intPrevFrame += m_iFrameStart; + } + + // Report wrap situation... + if (intPrevFrame > intFrame) + { + m_bReachedEnd = true; + + if (bWrapAnimation) + { + AnimationWrapped( pEntity ); + } + else + { + // Only sent the wrapped message once. + // when we're in non-wrapping mode + if (prevFrame < numFrames) + AnimationWrapped( pEntity ); + intFrame = numFrames - 1; + } + } + + m_AnimatedTextureFrameNumVar->SetIntValue( intFrame ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CMaterialModifyAnimatedProxy::GetAnimationStartTime( void* pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + if (!pRend) + return 0.0f; + + C_BaseEntity* pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if (pEntity) + { + return pEntity->GetTextureAnimationStartTime(); + } + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMaterialModifyAnimatedProxy::AnimationWrapped( void* pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + if (!pRend) + return; + + C_BaseEntity* pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if (pEntity) + { + pEntity->TextureAnimationWrapped(); + } +} + + +EXPOSE_INTERFACE( CMaterialModifyProxy, IMaterialProxy, "MaterialModify" IMATERIAL_PROXY_INTERFACE_VERSION ); +EXPOSE_INTERFACE( CMaterialModifyAnimatedProxy, IMaterialProxy, "MaterialModifyAnimated" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/C_WaterLODControl.cpp b/game/client/C_WaterLODControl.cpp new file mode 100644 index 00000000..67fb77d1 --- /dev/null +++ b/game/client/C_WaterLODControl.cpp @@ -0,0 +1,60 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Water LOD control entity. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "iviewrender.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------ +// FIXME: This really should inherit from something more lightweight +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Purpose : Water LOD control entity +//------------------------------------------------------------------------------ +class C_WaterLODControl : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_WaterLODControl, C_BaseEntity ); + + DECLARE_CLIENTCLASS(); + + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + +private: + float m_flCheapWaterStartDistance; + float m_flCheapWaterEndDistance; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_WaterLODControl, DT_WaterLODControl, CWaterLODControl) + RecvPropFloat(RECVINFO(m_flCheapWaterStartDistance)), + RecvPropFloat(RECVINFO(m_flCheapWaterEndDistance)), +END_RECV_TABLE() + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_WaterLODControl::OnDataChanged(DataUpdateType_t updateType) +{ + view->SetCheapWaterStartDistance( m_flCheapWaterStartDistance ); + view->SetCheapWaterEndDistance( m_flCheapWaterEndDistance ); +} + +//------------------------------------------------------------------------------ +// We don't draw... +//------------------------------------------------------------------------------ +bool C_WaterLODControl::ShouldDraw() +{ + return false; +} + diff --git a/game/client/EffectsClient.cpp b/game/client/EffectsClient.cpp new file mode 100644 index 00000000..0fad835a --- /dev/null +++ b/game/client/EffectsClient.cpp @@ -0,0 +1,231 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Utility code. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "IEffects.h" +#include "fx.h" +#include "c_te_legacytempents.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Client-server neutral effects interface +//----------------------------------------------------------------------------- +class CEffectsClient : public IEffects +{ +public: + CEffectsClient(); + virtual ~CEffectsClient(); + + // Members of the IEffect interface + virtual void Beam( const Vector &Start, const Vector &End, int nModelIndex, + int nHaloIndex, unsigned char frameStart, unsigned char frameRate, + float flLife, unsigned char width, unsigned char endWidth, unsigned char fadeLength, + unsigned char noise, unsigned char red, unsigned char green, + unsigned char blue, unsigned char brightness, unsigned char speed); + virtual void Smoke( const Vector &origin, int modelIndex, float scale, float framerate ); + virtual void Sparks( const Vector &position, int nMagnitude = 1, int nTrailLength = 1, const Vector *pvecDir = NULL ); + virtual void Dust( const Vector &pos, const Vector &dir, float size, float speed ); + virtual void MuzzleFlash( const Vector &origin, const QAngle &angles, float fScale, int type ); + virtual void MetalSparks( const Vector &position, const Vector &direction ); + virtual void EnergySplash( const Vector &position, const Vector &direction, bool bExplosive = false ); + virtual void Ricochet( const Vector &position, const Vector &direction ); + + // FIXME: Should these methods remain in this interface? Or go in some + // other client-server neutral interface? + virtual float Time(); + virtual bool IsServer(); + virtual void SuppressEffectsSounds( bool bSuppress ); + +private: + //----------------------------------------------------------------------------- + // Purpose: Returning true means don't even call TE func + // Input : filter - + // *suppress_host - + // Output : static bool + //----------------------------------------------------------------------------- + bool SuppressTE( C_RecipientFilter& filter ) + { + if ( !CanPredict() ) + return true; + + if ( !filter.GetRecipientCount() ) + { + // Suppress it + return true; + } + + // There's at least one recipient + return false; + } + + bool m_bSuppressSound; +}; + + +//----------------------------------------------------------------------------- +// Client-server neutral effects interface accessor +//----------------------------------------------------------------------------- +static CEffectsClient s_EffectClient; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEffectsClient, IEffects, IEFFECTS_INTERFACE_VERSION, s_EffectClient); +IEffects *g_pEffects = &s_EffectClient; + +ConVar r_decals( "r_decals", "2048" ); + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +CEffectsClient::CEffectsClient() +{ + m_bSuppressSound = false; +} + +CEffectsClient::~CEffectsClient() +{ +} + + +//----------------------------------------------------------------------------- +// Suppress sound on effects +//----------------------------------------------------------------------------- +void CEffectsClient::SuppressEffectsSounds( bool bSuppress ) +{ + m_bSuppressSound = bSuppress; +} + + +//----------------------------------------------------------------------------- +// Generates a beam +//----------------------------------------------------------------------------- +void CEffectsClient::Beam( const Vector &vecStartPoint, const Vector &vecEndPoint, + int nModelIndex, int nHaloIndex, unsigned char frameStart, unsigned char nFrameRate, + float flLife, unsigned char nWidth, unsigned char nEndWidth, unsigned char nFadeLength, + unsigned char noise, unsigned char r, unsigned char g, + unsigned char b, unsigned char brightness, unsigned char nSpeed) +{ + Assert(0); +// CBroadcastRecipientFilter filter; +// if ( !SuppressTE( filter ) ) +// { +// beams->CreateBeamPoints( vecStartPoint, vecEndPoint, nModelIndex, nHaloIndex, +// m_fHaloScale, +// flLife, 0.1 * nWidth, 0.1 * nEndWidth, nFadeLength, 0.01 * nAmplitude, a, 0.1 * nSpeed, +// m_nStartFrame, 0.1 * nFrameRate, r, g, b ); +// } +} + + +//----------------------------------------------------------------------------- +// Generates various tempent effects +//----------------------------------------------------------------------------- +void CEffectsClient::Smoke( const Vector &vecOrigin, int modelIndex, float scale, float framerate ) +{ + CPVSFilter filter( vecOrigin ); + if ( !SuppressTE( filter ) ) + { + int iColor = random->RandomInt(20,35); + color32 color; + color.r = iColor; + color.g = iColor; + color.b = iColor; + color.a = iColor; + QAngle angles; + VectorAngles( Vector(0,0,1), angles ); + FX_Smoke( vecOrigin, angles, scale * 0.1f, 4, (unsigned char *)&color, 255 ); + } +} + +void CEffectsClient::Sparks( const Vector &position, int nMagnitude, int nTrailLength, const Vector *pVecDir ) +{ + CPVSFilter filter( position ); + if ( !SuppressTE( filter ) ) + { + FX_ElectricSpark( position, nMagnitude, nTrailLength, pVecDir ); + } +} + +void CEffectsClient::Dust( const Vector &pos, const Vector &dir, float size, float speed ) +{ + CPVSFilter filter( pos ); + if ( !SuppressTE( filter ) ) + { + FX_Dust( pos, dir, size, speed ); + } +} + +void CEffectsClient::MuzzleFlash( const Vector &vecOrigin, const QAngle &vecAngles, float flScale, int iType ) +{ + CPVSFilter filter( vecOrigin ); + if ( !SuppressTE( filter ) ) + { + switch( iType ) + { + case MUZZLEFLASH_TYPE_DEFAULT: + FX_MuzzleEffect( vecOrigin, vecAngles, flScale, INVALID_EHANDLE_INDEX ); + break; + + case MUZZLEFLASH_TYPE_GUNSHIP: + FX_GunshipMuzzleEffect( vecOrigin, vecAngles, flScale, INVALID_EHANDLE_INDEX ); + break; + + case MUZZLEFLASH_TYPE_STRIDER: + FX_StriderMuzzleEffect( vecOrigin, vecAngles, flScale, INVALID_EHANDLE_INDEX ); + break; + + default: + Msg("No case for Muzzleflash type: %d\n", iType ); + break; + } + } +} + +void CEffectsClient::MetalSparks( const Vector &position, const Vector &direction ) +{ + CPVSFilter filter( position ); + if ( !SuppressTE( filter ) ) + { + FX_MetalSpark( position, direction, direction ); + } +} + +void CEffectsClient::EnergySplash( const Vector &position, const Vector &direction, bool bExplosive ) +{ + CPVSFilter filter( position ); + if ( !SuppressTE( filter ) ) + { + FX_EnergySplash( position, direction, bExplosive ); + } +} + +void CEffectsClient::Ricochet( const Vector &position, const Vector &direction ) +{ + CPVSFilter filter( position ); + if ( !SuppressTE( filter ) ) + { + FX_MetalSpark( position, direction, direction ); + + if ( !m_bSuppressSound ) + { + FX_RicochetSound( position ); + } + } +} + +// FIXME: Should these methods remain in this interface? Or go in some +// other client-server neutral interface? +float CEffectsClient::Time() +{ + return gpGlobals->curtime; +} + + +bool CEffectsClient::IsServer() +{ + return false; +} + + diff --git a/game/client/IsNPCProxy.cpp b/game/client/IsNPCProxy.cpp new file mode 100644 index 00000000..0d213cf3 --- /dev/null +++ b/game/client/IsNPCProxy.cpp @@ -0,0 +1,63 @@ +//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "FunctionProxy.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + +//----------------------------------------------------------------------------- +// Returns the player health (from 0 to 1) +//----------------------------------------------------------------------------- +class CProxyIsNPC : public CResultProxy +{ +public: + bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + void OnBind( void *pC_BaseEntity ); + +private: + CFloatInput m_Factor; +}; + +bool CProxyIsNPC::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if (!CResultProxy::Init( pMaterial, pKeyValues )) + return false; + + if (!m_Factor.Init( pMaterial, pKeyValues, "scale", 1 )) + return false; + + return true; +} + +void CProxyIsNPC::OnBind( void *pC_BaseEntity ) +{ + if ( !pC_BaseEntity ) + return; + + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + if ( pEntity && pEntity->IsNPC() ) + { + SetFloatResult( m_Factor.GetFloat() ); + } + else + { + SetFloatResult( 0.0f ); + } + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CProxyIsNPC, IMaterialProxy, "IsNPC" IMATERIAL_PROXY_INTERFACE_VERSION ); + + diff --git a/game/client/MonitorMaterialProxy.cpp b/game/client/MonitorMaterialProxy.cpp new file mode 100644 index 00000000..a515a567 --- /dev/null +++ b/game/client/MonitorMaterialProxy.cpp @@ -0,0 +1,60 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "materialsystem/IMaterialProxy.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" + +// $monitorTextureVar +class CMonitorMaterialProxy : public IMaterialProxy +{ +public: + CMonitorMaterialProxy(); + virtual ~CMonitorMaterialProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pC_BaseEntity ); + virtual void Release( void ) { delete this; } +private: + IMaterialVar *m_pMonitorTextureVar; +}; + +CMonitorMaterialProxy::CMonitorMaterialProxy() +{ + m_pMonitorTextureVar = NULL; +} + +CMonitorMaterialProxy::~CMonitorMaterialProxy() +{ + m_pMonitorTextureVar = NULL; +} + + +bool CMonitorMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + char const* pMonitorTextureVarName = pKeyValues->getString( "$monitorTextureVar" ); + if( !pMonitorTextureVarName ) + return false; + + bool foundVar; + m_pMonitorTextureVar = pMaterial->FindVar( pMonitorTextureVarName, &foundVar, false ); + if( !foundVar ) + { + m_pMonitorTextureVar = NULL; + return false; + } + return true; +} + +void CMonitorMaterialProxy::OnBind( void *pC_BaseEntity ) +{ + if( !m_pMonitorTextureVar ) + { + return; + } +} + +EXPOSE_INTERFACE( CMonitorMaterialProxy, IMaterialProxy, "Monitor" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/ProxyHealth.cpp b/game/client/ProxyHealth.cpp new file mode 100644 index 00000000..08ce3b5d --- /dev/null +++ b/game/client/ProxyHealth.cpp @@ -0,0 +1,59 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "FunctionProxy.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + +//----------------------------------------------------------------------------- +// Returns the player health (from 0 to 1) +//----------------------------------------------------------------------------- +class CProxyHealth : public CResultProxy +{ +public: + bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + void OnBind( void *pC_BaseEntity ); + +private: + CFloatInput m_Factor; +}; + +bool CProxyHealth::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if (!CResultProxy::Init( pMaterial, pKeyValues )) + return false; + + if (!m_Factor.Init( pMaterial, pKeyValues, "scale", 1 )) + return false; + + return true; +} + +void CProxyHealth::OnBind( void *pC_BaseEntity ) +{ + if (!pC_BaseEntity) + return; + + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + + Assert( m_pResult ); + SetFloatResult( pEntity->HealthFraction() * m_Factor.GetFloat() ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CProxyHealth, IMaterialProxy, "Health" IMATERIAL_PROXY_INTERFACE_VERSION ); + + diff --git a/game/client/ScreenSpaceEffects.cpp b/game/client/ScreenSpaceEffects.cpp new file mode 100644 index 00000000..d5cab6fe --- /dev/null +++ b/game/client/ScreenSpaceEffects.cpp @@ -0,0 +1,316 @@ +#include "cbase.h" + +#include "keyvalues.h" +#include "cdll_client_int.h" +#include "view_scene.h" +#include "viewrender.h" +#include "tier0/icommandline.h" +#include "materialsystem/IMesh.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialSystemHardwareConfig.h" +#include "materialsystem/IMaterialVar.h" + +#include "ScreenSpaceEffects.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------ +// CScreenSpaceEffectRegistration code +// Used to register and effect with the IScreenSpaceEffectManager +//------------------------------------------------------------------------------ +CScreenSpaceEffectRegistration *CScreenSpaceEffectRegistration::s_pHead = NULL; + +CScreenSpaceEffectRegistration::CScreenSpaceEffectRegistration( const char *pName, IScreenSpaceEffect *pEffect ) +{ + m_pEffectName = pName; + m_pEffect = pEffect; + m_pNext = s_pHead; + s_pHead = this; +} + + +//------------------------------------------------------------------------------ +// CScreenSpaceEffectManager - Implementation of IScreenSpaceEffectManager +//------------------------------------------------------------------------------ +class CScreenSpaceEffectManager : public IScreenSpaceEffectManager +{ +public: + + virtual void InitScreenSpaceEffects( ); + virtual void ShutdownScreenSpaceEffects( ); + + virtual IScreenSpaceEffect *GetScreenSpaceEffect( const char *pEffectName ); + + virtual void SetScreenSpaceEffectParams( const char *pEffectName, KeyValues *params ); + virtual void SetScreenSpaceEffectParams( IScreenSpaceEffect *pEffect, KeyValues *params ); + + virtual void EnableScreenSpaceEffect( const char *pEffectName ); + virtual void EnableScreenSpaceEffect( IScreenSpaceEffect *pEffect ); + + virtual void DisableScreenSpaceEffect( const char *pEffectName ); + virtual void DisableScreenSpaceEffect( IScreenSpaceEffect *pEffect ); + + virtual void DisableAllScreenSpaceEffects( ); + + virtual void RenderEffects( int x, int y, int w, int h ); +}; + +CScreenSpaceEffectManager g_ScreenSpaceEffectManager; +IScreenSpaceEffectManager *g_pScreenSpaceEffects = &g_ScreenSpaceEffectManager; + +//--------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::InitScreenSpaceEffects - Initialise all registered effects +//--------------------------------------------------------------------------------------- +void CScreenSpaceEffectManager::InitScreenSpaceEffects( ) +{ + if ( CommandLine()->FindParm( "-filmgrain" ) ) + { + GetScreenSpaceEffect( "filmgrain" )->Enable( true ); + } + + for( CScreenSpaceEffectRegistration *pReg=CScreenSpaceEffectRegistration::s_pHead; pReg!=NULL; pReg=pReg->m_pNext ) + { + IScreenSpaceEffect *pEffect = pReg->m_pEffect; + if( pEffect ) + { + bool bIsEnabled = pEffect->IsEnabled( ); + pEffect->Init( ); + pEffect->Enable( bIsEnabled ); + } + } +} + + +//---------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::ShutdownScreenSpaceEffects - Shutdown all registered effects +//---------------------------------------------------------------------------------------- +void CScreenSpaceEffectManager::ShutdownScreenSpaceEffects( ) +{ + for( CScreenSpaceEffectRegistration *pReg=CScreenSpaceEffectRegistration::s_pHead; pReg!=NULL; pReg=pReg->m_pNext ) + { + IScreenSpaceEffect *pEffect = pReg->m_pEffect; + if( pEffect ) + { + pEffect->Shutdown( ); + } + } +} + + +//--------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::GetScreenSpaceEffect - Returns a point to the named effect +//--------------------------------------------------------------------------------------- +IScreenSpaceEffect *CScreenSpaceEffectManager::GetScreenSpaceEffect( const char *pEffectName ) +{ + for( CScreenSpaceEffectRegistration *pReg=CScreenSpaceEffectRegistration::s_pHead; pReg!=NULL; pReg=pReg->m_pNext ) + { + if( !Q_stricmp( pReg->m_pEffectName, pEffectName ) ) + { + IScreenSpaceEffect *pEffect = pReg->m_pEffect; + return pEffect; + } + } + + Warning( "Could not find screen space effect %s\n", pEffectName ); + + return NULL; +} + + +//--------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::SetScreenSpaceEffectParams +// - Assign parameters to the specified effect +//--------------------------------------------------------------------------------------- +void CScreenSpaceEffectManager::SetScreenSpaceEffectParams( const char *pEffectName, KeyValues *params ) +{ + IScreenSpaceEffect *pEffect = GetScreenSpaceEffect( pEffectName ); + if( pEffect ) + SetScreenSpaceEffectParams( pEffect, params ); +} + +void CScreenSpaceEffectManager::SetScreenSpaceEffectParams( IScreenSpaceEffect *pEffect, KeyValues *params ) +{ + if( pEffect ) + pEffect->SetParameters( params ); +} + + +//--------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::EnableScreenSpaceEffect +// - Enables the specified effect +//--------------------------------------------------------------------------------------- +void CScreenSpaceEffectManager::EnableScreenSpaceEffect( const char *pEffectName ) +{ + IScreenSpaceEffect *pEffect = GetScreenSpaceEffect( pEffectName ); + if( pEffect ) + EnableScreenSpaceEffect( pEffect ); +} + +void CScreenSpaceEffectManager::EnableScreenSpaceEffect( IScreenSpaceEffect *pEffect ) +{ + if( pEffect ) + pEffect->Enable( true ); +} + + +//--------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::DisableScreenSpaceEffect +// - Disables the specified effect +//--------------------------------------------------------------------------------------- +void CScreenSpaceEffectManager::DisableScreenSpaceEffect( const char *pEffectName ) +{ + IScreenSpaceEffect *pEffect = GetScreenSpaceEffect( pEffectName ); + if( pEffect ) + DisableScreenSpaceEffect( pEffect ); +} + +void CScreenSpaceEffectManager::DisableScreenSpaceEffect( IScreenSpaceEffect *pEffect ) +{ + if( pEffect ) + pEffect->Enable( false ); +} + + +//--------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::DisableAllScreenSpaceEffects +// - Disables all registered screen space effects +//--------------------------------------------------------------------------------------- +void CScreenSpaceEffectManager::DisableAllScreenSpaceEffects( ) +{ + for( CScreenSpaceEffectRegistration *pReg=CScreenSpaceEffectRegistration::s_pHead; pReg!=NULL; pReg=pReg->m_pNext ) + { + IScreenSpaceEffect *pEffect = pReg->m_pEffect; + if( pEffect ) + { + pEffect->Enable( false ); + } + } +} + + +//--------------------------------------------------------------------------------------- +// CScreenSpaceEffectManager::RenderEffects +// - Renders all registered screen space effects +//--------------------------------------------------------------------------------------- +void CScreenSpaceEffectManager::RenderEffects( int x, int y, int w, int h ) +{ + for( CScreenSpaceEffectRegistration *pReg=CScreenSpaceEffectRegistration::s_pHead; pReg!=NULL; pReg=pReg->m_pNext ) + { + IScreenSpaceEffect *pEffect = pReg->m_pEffect; + if( pEffect ) + { + pEffect->Render( x, y, w, h ); + } + } +} + +//------------------------------------------------------------------------------ +// Example post-processing effect +//------------------------------------------------------------------------------ +class CExampleEffect : public IScreenSpaceEffect +{ +public: + CExampleEffect( ); + ~CExampleEffect( ); + + void Init( ); + void Shutdown( ); + + void SetParameters( KeyValues *params ); + + void Render( int x, int y, int w, int h ); + + void Enable( bool bEnable ); + bool IsEnabled( ); + +private: + + bool m_bEnable; + + CMaterialReference m_Material; +}; + +ADD_SCREENSPACE_EFFECT( CExampleEffect, exampleeffect ); + +//------------------------------------------------------------------------------ +// CExampleEffect constructor +//------------------------------------------------------------------------------ +CExampleEffect::CExampleEffect( ) +{ + m_bEnable = false; +} + + +//------------------------------------------------------------------------------ +// CExampleEffect destructor +//------------------------------------------------------------------------------ +CExampleEffect::~CExampleEffect( ) +{ +} + + +//------------------------------------------------------------------------------ +// CExampleEffect init +//------------------------------------------------------------------------------ +void CExampleEffect::Init( ) +{ + // This is just example code, init your effect material here + //m_Material.Init( "engine/exampleeffect", TEXTURE_GROUP_OTHER ); + + m_bEnable = false; +} + + +//------------------------------------------------------------------------------ +// CExampleEffect shutdown +//------------------------------------------------------------------------------ +void CExampleEffect::Shutdown( ) +{ + m_Material.Shutdown(); +} + +//------------------------------------------------------------------------------ +// CExampleEffect enable +//------------------------------------------------------------------------------ +void CExampleEffect::Enable( bool bEnable ) +{ + // This is just example code, don't enable it + // m_bEnable = bEnable; +} + +bool CExampleEffect::IsEnabled( ) +{ + return m_bEnable; +} + +//------------------------------------------------------------------------------ +// CExampleEffect SetParameters +//------------------------------------------------------------------------------ +void CExampleEffect::SetParameters( KeyValues *params ) +{ + if( params->GetDataType( "example_param" ) == KeyValues::TYPE_STRING ) + { + // ... + } +} + +//------------------------------------------------------------------------------ +// CExampleEffect render +//------------------------------------------------------------------------------ +void CExampleEffect::Render( int x, int y, int w, int h ) +{ + if ( !IsEnabled() ) + return; + + // Render Effect + Rect_t actualRect; + UpdateScreenEffectTexture( 0, x, y, w, h, false, &actualRect ); + ITexture *pTexture = GetFullFrameFrameBufferTexture( 0 ); + + CMatRenderContextPtr pRenderContext( materials ); + + pRenderContext->DrawScreenSpaceRectangle( m_Material, x, y, w, h, + actualRect.x, actualRect.y, actualRect.x+actualRect.width-1, actualRect.y+actualRect.height-1, + pTexture->GetActualWidth(), pTexture->GetActualHeight() ); +} diff --git a/game/client/ScreenSpaceEffects.h b/game/client/ScreenSpaceEffects.h new file mode 100644 index 00000000..8b8e64ad --- /dev/null +++ b/game/client/ScreenSpaceEffects.h @@ -0,0 +1,88 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=====================================================================================// + +#ifndef SCREENSPACEEFFECTS_H +#define SCREENSPACEEFFECTS_H + +#ifdef _WIN32 +#pragma once +#endif + +class KeyValues; + + +//------------------------------------------------------------------------------ +// Simple base class for screen space post-processing effects +//------------------------------------------------------------------------------ +abstract_class IScreenSpaceEffect +{ +public: + + virtual void Init( ) = 0; + virtual void Shutdown( ) = 0; + + virtual void SetParameters( KeyValues *params ) = 0; + + virtual void Render( int x, int y, int w, int h ) = 0; + + virtual void Enable( bool bEnable ) = 0; + virtual bool IsEnabled( ) = 0; +}; + + +//------------------------------------------------------------------------------ +// Interface class for managing screen space post-processing effects +//------------------------------------------------------------------------------ +abstract_class IScreenSpaceEffectManager +{ +public: + + virtual void InitScreenSpaceEffects( ) = 0; + virtual void ShutdownScreenSpaceEffects( ) = 0; + + virtual IScreenSpaceEffect *GetScreenSpaceEffect( const char *pEffectName ) = 0; + + virtual void SetScreenSpaceEffectParams( const char *pEffectName, KeyValues *params ) = 0; + virtual void SetScreenSpaceEffectParams( IScreenSpaceEffect *pEffect, KeyValues *params ) = 0; + + virtual void EnableScreenSpaceEffect( const char *pEffectName ) = 0; + virtual void EnableScreenSpaceEffect( IScreenSpaceEffect *pEffect ) = 0; + + virtual void DisableScreenSpaceEffect( const char *pEffectName ) = 0; + virtual void DisableScreenSpaceEffect( IScreenSpaceEffect *pEffect ) = 0; + + virtual void DisableAllScreenSpaceEffects( ) = 0; + + virtual void RenderEffects( int x, int y, int w, int h ) = 0; +}; + +extern IScreenSpaceEffectManager *g_pScreenSpaceEffects; + + +//------------------------------------------------------------------------------------- +// Registration class for adding screen space effects to the IScreenSpaceEffectManager +//------------------------------------------------------------------------------------- +class CScreenSpaceEffectRegistration +{ +public: + CScreenSpaceEffectRegistration( const char *pName, IScreenSpaceEffect *pEffect ); + + const char *m_pEffectName; + IScreenSpaceEffect *m_pEffect; + + CScreenSpaceEffectRegistration *m_pNext; + + static CScreenSpaceEffectRegistration *s_pHead; +}; + +#define ADD_SCREENSPACE_EFFECT( CEffect, pEffectName ) CEffect pEffectName##_effect; \ + CScreenSpaceEffectRegistration pEffectName##_reg( #pEffectName, &pEffectName##_effect ); + + + +#endif \ No newline at end of file diff --git a/game/client/TeamBitmapImage.cpp b/game/client/TeamBitmapImage.cpp new file mode 100644 index 00000000..9b7a80c8 --- /dev/null +++ b/game/client/TeamBitmapImage.cpp @@ -0,0 +1,172 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: This is a panel which is rendered image on top of an entity +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "teambitmapimage.h" +#include +#include "vgui_BitmapImage.h" +#include "PanelMetaClassMgr.h" +#include "vguimatsurface/IMatSystemSurface.h" +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// A multiplexer bitmap that chooses a bitmap based on team +//----------------------------------------------------------------------------- +CTeamBitmapImage::CTeamBitmapImage() : m_Alpha(1.0f) +{ + memset( m_ppImage, 0, BITMAP_COUNT * sizeof(BitmapImage*) ); + m_pEntity = NULL; + m_bRelativeTeams = 0; +} + +CTeamBitmapImage::~CTeamBitmapImage() +{ + int i; + for ( i = 0; i < BITMAP_COUNT; ++i ) + { + if (m_ppImage[i]) + delete m_ppImage[i]; + } +} + + +//----------------------------------------------------------------------------- +// initialization +//----------------------------------------------------------------------------- +bool CTeamBitmapImage::Init( vgui::Panel *pParent, KeyValues* pInitData, C_BaseEntity* pEntity ) +{ + static char *pRelativeTeamNames[BITMAP_COUNT] = + { + "NoTeam", + "MyTeam", + "EnemyTeam", + }; + + static char *pAbsoluteTeamNames[BITMAP_COUNT] = + { + "Team0", + "Team1", + "Team2", + }; + + m_pEntity = pEntity; + m_bRelativeTeams = (pInitData->GetInt( "relativeteam" ) != 0); + + char **ppTeamNames = m_bRelativeTeams ? pRelativeTeamNames : pAbsoluteTeamNames; + + int i; + for ( i = 0 ; i < BITMAP_COUNT; ++i ) + { + // Default to null + m_ppImage[i] = NULL; + + // Look for team section + KeyValues *pTeamKV = pInitData->FindKey( ppTeamNames[i] ); + if ( !pTeamKV ) + continue; + + char const* pClassImage = pTeamKV->GetString( "material" ); + if ( !pClassImage || !pClassImage[ 0 ] ) + return false; + + // modulation color + Color color; + if (!ParseRGBA( pTeamKV, "color", color )) + color.SetColor( 255, 255, 255, 255 ); + + // hook in the bitmap + m_ppImage[i] = new BitmapImage( pParent->GetVPanel(), pClassImage ); + m_ppImage[i]->SetColor( color ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Alpha modulate... +//----------------------------------------------------------------------------- +void CTeamBitmapImage::SetAlpha( float alpha ) +{ + m_Alpha = clamp( alpha, 0.0f, 1.0f ); +} + + +//----------------------------------------------------------------------------- +// draw +//----------------------------------------------------------------------------- +void CTeamBitmapImage::Paint( float yaw /*= 0.0f*/ ) +{ + if (m_Alpha == 0.0f) + return; + + int team = 0; + if (m_bRelativeTeams) + { + if (GetEntity()) + { + if (GetEntity()->GetTeamNumber() != 0) + { + team = GetEntity()->InLocalTeam() ? 1 : 2; + } + } + } + else + { + if (GetEntity()) + team = GetEntity()->GetTeamNumber(); + } + + // Paint the image for the current team + if (m_ppImage[team]) + { + // Modulate the color based on the alpha.... + Color color = m_ppImage[team]->GetColor(); + int alpha = color[3]; + color[3] = (alpha * m_Alpha); + m_ppImage[team]->SetColor( color ); + + if ( yaw != 0.0f ) + { + g_pMatSystemSurface->DisableClipping( true ); + + m_ppImage[team]->DoPaint( m_ppImage[team]->GetRenderSizePanel(), yaw ); + + g_pMatSystemSurface->DisableClipping( false ); + } + else + { + // Paint + m_ppImage[team]->Paint(); + } + + // restore previous color + color[3] = alpha; + m_ppImage[team]->SetColor( color ); + } +} + + +//----------------------------------------------------------------------------- +// Helper method to initialize a team image from KeyValues data.. +//----------------------------------------------------------------------------- +bool InitializeTeamImage( KeyValues *pInitData, const char* pSectionName, vgui::Panel *pParent, C_BaseEntity *pEntity, CTeamBitmapImage* pTeamImage ) +{ + KeyValues *pTeamImageSection = pInitData; + if (pSectionName) + { + pTeamImageSection = pInitData->FindKey( pSectionName ); + if ( !pTeamImageSection ) + return false; + } + + return pTeamImage->Init( pParent, pTeamImageSection, pEntity ); +} + diff --git a/game/client/TeamBitmapImage.h b/game/client/TeamBitmapImage.h new file mode 100644 index 00000000..9bcff214 --- /dev/null +++ b/game/client/TeamBitmapImage.h @@ -0,0 +1,80 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: This is a panel which is rendered image on top of an entity +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef TEAMBITMAPIMAGE_H +#define TEAMBITMAPIMAGE_H + +#ifdef _WIN32 +#pragma once +#endif + +//#include "tf_shareddefs.h" + +#include + +namespace vgui +{ + class Panel; +} + +class BitmapImage; +class C_BaseEntity; +class KeyValues; + +//----------------------------------------------------------------------------- +// A multiplexer bitmap that chooses a bitmap based on team +//----------------------------------------------------------------------------- +class CTeamBitmapImage +{ +public: + // construction, destruction + CTeamBitmapImage(); + ~CTeamBitmapImage(); + + // initialization + bool Init( vgui::Panel *pParent, KeyValues* pInitData, C_BaseEntity* pEntity ); + + // Alpha override... + void SetAlpha( float alpha ); + + // Paint the sucka. Paint it the size of the parent panel + void Paint( float yaw = 0.0f ); + +protected: + // Wrapper so we can implement this with EHANDLES some day + C_BaseEntity *GetEntity() { return m_pEntity; } + +private: + enum + { + // NOTE: Was MAX_TF_TEAMS not 4, but I don't like the dependency here. + BITMAP_COUNT = 4 + 1 + }; + + BitmapImage *m_ppImage[ BITMAP_COUNT ]; + C_BaseEntity *m_pEntity; + float m_Alpha; + bool m_bRelativeTeams; +}; + + +//----------------------------------------------------------------------------- +// Helper method to initialize a team image from KeyValues data.. +// KeyValues contains the bitmap data. pSectionName, if it exists, +// indicates which subsection of pInitData should be looked at to get at the +// image data. The final argument is the bitmap image to initialize. +// The function returns true if it succeeded. +// +// NOTE: This function looks for the key values 'material' and 'color' +// and uses them to set up the material + modulation color of the image +//----------------------------------------------------------------------------- +bool InitializeTeamImage( KeyValues *pInitData, const char* pSectionName, + vgui::Panel *pParent, C_BaseEntity *pEntity, CTeamBitmapImage* pBitmapImage ); + + +#endif // TEAMBITMAPIMAGE_H \ No newline at end of file diff --git a/game/client/ViewConeImage.cpp b/game/client/ViewConeImage.cpp new file mode 100644 index 00000000..9837ab58 --- /dev/null +++ b/game/client/ViewConeImage.cpp @@ -0,0 +1,83 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: This is a panel which is rendered image on top of an entity +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "ViewConeImage.h" +#include +#include +#include "vguimatsurface/IMatSystemSurface.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// initialization +//----------------------------------------------------------------------------- +bool CViewConeImage::Init( vgui::Panel *pParent, KeyValues* pInitData ) +{ + Assert( pParent ); + + // Load viewcone material + if (!m_Image.Init( pParent->GetVPanel(), pInitData )) + return false; + + // Position the view cone... + int viewconesize = pInitData->GetInt( "size", 32 ); + m_Image.SetRenderSize( viewconesize, viewconesize ); + + int cx, cy; + pParent->GetSize( cx, cy ); + m_Image.SetPos( (cx - viewconesize) / 2, (cy - viewconesize) / 2 ); + + return true; +} + +//----------------------------------------------------------------------------- +// Paint the sucka +//----------------------------------------------------------------------------- +void CViewConeImage::Paint( float yaw ) +{ + g_pMatSystemSurface->DisableClipping( true ); + + m_Image.DoPaint( NULL, yaw ); + + g_pMatSystemSurface->DisableClipping( false ); +} + +void CViewConeImage::SetColor( int r, int g, int b ) +{ + m_Image.SetColor( Color( r, g, b, 255 ) ); +} + +//----------------------------------------------------------------------------- +// Helper method to initialize a view cone image from KeyValues data.. +// KeyValues contains the bitmap data, pSectionName, if it exists, +// indicates which subsection of pInitData should be looked at to get at the +// image data. The final argument is the bitmap image to initialize. +// The function returns true if it succeeded. +// +// NOTE: This function looks for the key values 'material' and 'color' +// and uses them to set up the material + modulation color of the image +//----------------------------------------------------------------------------- +bool InitializeViewConeImage( KeyValues *pInitData, const char* pSectionName, + vgui::Panel *pParent, CViewConeImage* pViewConeImage ) +{ + KeyValues *pViewConeImageSection; + if (pSectionName) + { + pViewConeImageSection = pInitData->FindKey( pSectionName ); + if ( !pViewConeImageSection ) + return false; + } + else + { + pViewConeImageSection = pInitData; + } + + return pViewConeImage->Init( pParent, pViewConeImageSection ); +} + diff --git a/game/client/ViewConeImage.h b/game/client/ViewConeImage.h new file mode 100644 index 00000000..3359edbb --- /dev/null +++ b/game/client/ViewConeImage.h @@ -0,0 +1,56 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: This is a panel which draws a viewcone +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef VIEWCONEIMAGE_H +#define VIEWCONEIMAGE_H + +#include "shareddefs.h" +#include "VGUI_BitmapImage.h" + +namespace vgui +{ + class Panel; +} + +class C_BaseEntity; +class KeyValues; + +//----------------------------------------------------------------------------- +// A bitmap that renders a view cone based on angles +//----------------------------------------------------------------------------- +class CViewConeImage +{ +public: + // initialization + bool Init( vgui::Panel *pParent, KeyValues* pInitData ); + + // Paint the sucka + void Paint( float yaw ); + + void SetColor( int r, int g, int b ); + +private: + BitmapImage m_Image; +}; + + +//----------------------------------------------------------------------------- +// Helper method to initialize a view cone image from KeyValues data.. +// KeyValues contains the bitmap data, pSectionName, if it exists, +// indicates which subsection of pInitData should be looked at to get at the +// image data. The final argument is the bitmap image to initialize. +// The function returns true if it succeeded. +// +// NOTE: This function looks for the key values 'material' and 'color' +// and uses them to set up the material + modulation color of the image +//----------------------------------------------------------------------------- +bool InitializeViewConeImage( KeyValues *pInitData, const char* pSectionName, + vgui::Panel *pParent, CViewConeImage* pViewConeImage ); + + +#endif // VIEWCONEIMAGE_H \ No newline at end of file diff --git a/game/client/WaterLODMaterialProxy.cpp b/game/client/WaterLODMaterialProxy.cpp new file mode 100644 index 00000000..eea38e22 --- /dev/null +++ b/game/client/WaterLODMaterialProxy.cpp @@ -0,0 +1,83 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "materialsystem/IMaterialProxy.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" +#include "iviewrender.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + +// no inputs, assumes that the results go into $CHEAPWATERSTARTDISTANCE and $CHEAPWATERENDDISTANCE +class CWaterLODMaterialProxy : public IMaterialProxy +{ +public: + CWaterLODMaterialProxy(); + virtual ~CWaterLODMaterialProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pC_BaseEntity ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +private: + IMaterialVar *m_pCheapWaterStartDistanceVar; + IMaterialVar *m_pCheapWaterEndDistanceVar; +}; + +CWaterLODMaterialProxy::CWaterLODMaterialProxy() +{ + m_pCheapWaterStartDistanceVar = NULL; + m_pCheapWaterEndDistanceVar = NULL; +} + +CWaterLODMaterialProxy::~CWaterLODMaterialProxy() +{ +} + + +bool CWaterLODMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool foundVar; + m_pCheapWaterStartDistanceVar = pMaterial->FindVar( "$CHEAPWATERSTARTDISTANCE", &foundVar, false ); + if( !foundVar ) + return false; + + m_pCheapWaterEndDistanceVar = pMaterial->FindVar( "$CHEAPWATERENDDISTANCE", &foundVar, false ); + if( !foundVar ) + return false; + + return true; +} + +void CWaterLODMaterialProxy::OnBind( void *pC_BaseEntity ) +{ + if( !m_pCheapWaterStartDistanceVar || !m_pCheapWaterEndDistanceVar ) + { + return; + } + float start, end; + view->GetWaterLODParams( start, end ); + m_pCheapWaterStartDistanceVar->SetFloatValue( start ); + m_pCheapWaterEndDistanceVar->SetFloatValue( end ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CWaterLODMaterialProxy::GetMaterial() +{ + return m_pCheapWaterStartDistanceVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CWaterLODMaterialProxy, IMaterialProxy, "WaterLOD" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/WorldDimsProxy.cpp b/game/client/WorldDimsProxy.cpp new file mode 100644 index 00000000..2b9728b8 --- /dev/null +++ b/game/client/WorldDimsProxy.cpp @@ -0,0 +1,78 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "materialsystem/IMaterialProxy.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" +#include "c_world.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + +class CWorldDimsProxy : public IMaterialProxy +{ +public: + CWorldDimsProxy(); + virtual ~CWorldDimsProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pC_BaseEntity ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + + +public: + IMaterialVar *m_pMinsVar; + IMaterialVar *m_pMaxsVar; +}; + + +CWorldDimsProxy::CWorldDimsProxy() +{ + m_pMinsVar = m_pMaxsVar = NULL; +} + +CWorldDimsProxy::~CWorldDimsProxy() +{ +} + +bool CWorldDimsProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + m_pMinsVar = pMaterial->FindVar( "$world_mins", NULL, false ); + m_pMaxsVar = pMaterial->FindVar( "$world_maxs", NULL, false ); + return true; +} + +void CWorldDimsProxy::OnBind( void *pC_BaseEntity ) +{ + if ( m_pMinsVar && m_pMaxsVar ) + { + C_World *pWorld = GetClientWorldEntity(); + if ( pWorld ) + { + m_pMinsVar->SetVecValue( (const float*)&pWorld->m_WorldMins, 3 ); + m_pMaxsVar->SetVecValue( (const float*)&pWorld->m_WorldMaxs, 3 ); + } + } + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CWorldDimsProxy::GetMaterial() +{ + return m_pMinsVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CWorldDimsProxy, IMaterialProxy, "WorldDims" IMATERIAL_PROXY_INTERFACE_VERSION ); + + diff --git a/game/client/achievement_notification_panel.cpp b/game/client/achievement_notification_panel.cpp new file mode 100644 index 00000000..879d9d9e --- /dev/null +++ b/game/client/achievement_notification_panel.cpp @@ -0,0 +1,271 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "cbase.h" +#include "hud.h" +#include "hud_macros.h" +#include "hudelement.h" +#include "iclientmode.h" +#include "ienginevgui.h" +#include +#include +#include +#include +#include +#include +#include "achievement_notification_panel.h" +#include "steam/steam_api.h" +#include "iachievementmgr.h" +#include "fmtstr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +#define ACHIEVEMENT_NOTIFICATION_DURATION 10.0f + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +DECLARE_HUDELEMENT_DEPTH( CAchievementNotificationPanel, 100 ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAchievementNotificationPanel::CAchievementNotificationPanel( const char *pElementName ) : CHudElement( pElementName ), BaseClass( NULL, "AchievementNotificationPanel" ) +{ + Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + m_flHideTime = 0; + m_pPanelBackground = new EditablePanel( this, "Notification_Background" ); + m_pIcon = new ImagePanel( this, "Notification_Icon" ); + m_pLabelHeading = new Label( this, "HeadingLabel", "" ); + m_pLabelTitle = new Label( this, "TitleLabel", "" ); + + m_pIcon->SetShouldScaleImage( true ); + + vgui::ivgui()->AddTickSignal( GetVPanel() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::Init() +{ + ListenForGameEvent( "achievement_event" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + // load control settings... + LoadControlSettings( "resource/UI/AchievementNotification.res" ); + + BaseClass::ApplySchemeSettings( pScheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // Set background color of various elements. Need to do this in code, if we do it in res file it gets slammed by the + // scheme. (Incl. label background: some products don't have label background colors set in their scheme and helpfully slam it to white.) + SetBgColor( Color( 0, 0, 0, 0 ) ); + m_pLabelHeading->SetBgColor( Color( 0, 0, 0, 0 ) ); + m_pLabelTitle->SetBgColor( Color( 0, 0, 0, 0 ) ); + m_pPanelBackground->SetBgColor( Color( 62,70,55, 200 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::FireGameEvent( IGameEvent * event ) +{ + const char *name = event->GetName(); + if ( 0 == Q_strcmp( name, "achievement_event" ) ) + { + const char *pchName = event->GetString( "achievement_name" ); + int iCur = event->GetInt( "cur_val" ); + int iMax = event->GetInt( "max_val" ); + wchar_t szLocalizedName[256]=L""; + + if ( IsPC() ) + { + // shouldn't ever get achievement progress if steam not running and user logged in, but check just in case + if ( !steamapicontext->SteamUserStats() ) + { + Msg( "Steam not running, achievement progress notification not displayed\n" ); + } + + // use Steam to show achievement progress UI + CGameID gameID( engine->GetAppID() ); + steamapicontext->SteamUserStats()->IndicateAchievementProgress( gameID, pchName, iCur, iMax ); + } + else + { + // on X360 we need to show our own achievement progress UI + + const wchar_t *pchLocalizedName = ACHIEVEMENT_LOCALIZED_NAME_FROM_STR( pchName ); + Assert( pchLocalizedName ); + if ( !pchLocalizedName || !pchLocalizedName[0] ) + return; + Q_wcsncpy( szLocalizedName, pchLocalizedName, sizeof( szLocalizedName ) ); + + // this is achievement progress, compose the message of form: " (<#>/)" + wchar_t szFmt[128]=L""; + wchar_t szText[512]=L""; + wchar_t szNumFound[16]=L""; + wchar_t szNumTotal[16]=L""; + _snwprintf( szNumFound, ARRAYSIZE( szNumFound ), L"%i", iCur ); + _snwprintf( szNumTotal, ARRAYSIZE( szNumTotal ), L"%i", iMax ); + + const wchar_t *pchFmt = g_pVGuiLocalize->Find( "#GameUI_Achievement_Progress_Fmt" ); + if ( !pchFmt || !pchFmt[0] ) + return; + Q_wcsncpy( szFmt, pchFmt, sizeof( szFmt ) ); + + g_pVGuiLocalize->ConstructString( szText, sizeof( szText ), szFmt, 3, szLocalizedName, szNumFound, szNumTotal ); + AddNotification( pchName, g_pVGuiLocalize->Find( "#GameUI_Achievement_Progress" ), szText ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called on each tick +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::OnTick( void ) +{ + if ( ( m_flHideTime > 0 ) && ( m_flHideTime < gpGlobals->curtime ) ) + { + m_flHideTime = 0; + ShowNextNotification(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAchievementNotificationPanel::ShouldDraw( void ) +{ + return ( ( m_flHideTime > 0 ) && ( m_flHideTime > gpGlobals->curtime ) && CHudElement::ShouldDraw() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::AddNotification( const char *szIconBaseName, const wchar_t *pHeading, const wchar_t *pTitle ) +{ + // put this notification in our queue + int iQueueItem = m_queueNotification.AddToTail(); + Notification_t ¬ification = m_queueNotification[iQueueItem]; + Q_strncpy( notification.szIconBaseName, szIconBaseName, ARRAYSIZE( notification.szIconBaseName ) ); + Q_wcsncpy( notification.szHeading, pHeading, sizeof( notification.szHeading ) ); + Q_wcsncpy( notification.szTitle, pTitle, sizeof( notification.szTitle ) ); + + // if we are not currently displaying a notification, go ahead and show this one + if ( 0 == m_flHideTime ) + { + ShowNextNotification(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shows next notification in queue if there is one +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::ShowNextNotification() +{ + // see if we have anything to do + if ( 0 == m_queueNotification.Count() ) + { + m_flHideTime = 0; + return; + } + + Notification_t ¬ification = m_queueNotification[ m_queueNotification.Head() ]; + + m_flHideTime = gpGlobals->curtime + ACHIEVEMENT_NOTIFICATION_DURATION; + + // set the text and icon in the dialog + SetDialogVariable( "heading", notification.szHeading ); + SetDialogVariable( "title", notification.szTitle ); + const char *pchIconBaseName = notification.szIconBaseName; + if ( pchIconBaseName && pchIconBaseName[0] ) + { + m_pIcon->SetImage( CFmtStr( "achievements/%s.vmt", pchIconBaseName ) ); + } + + // resize the panel so it always looks good + + // get fonts + HFont hFontHeading = m_pLabelHeading->GetFont(); + HFont hFontTitle = m_pLabelTitle->GetFont(); + // determine how wide the text strings are + int iHeadingWidth = UTIL_ComputeStringWidth( hFontHeading, notification.szHeading ); + int iTitleWidth = UTIL_ComputeStringWidth( hFontTitle, notification.szTitle ); + // use the widest string + int iTextWidth = max( iHeadingWidth, iTitleWidth ); + // don't let it be insanely wide + iTextWidth = min( iTextWidth, XRES( 300 ) ); + int iIconWidth = m_pIcon->GetWide(); + int iSpacing = XRES( 10 ); + int iPanelWidth = iSpacing + iIconWidth + iSpacing + iTextWidth + iSpacing; + int iPanelX = GetWide() - iPanelWidth; + int iIconX = iPanelX + iSpacing; + int iTextX = iIconX + iIconWidth + iSpacing; + // resize all the elements + SetXAndWide( m_pPanelBackground, iPanelX, iPanelWidth ); + SetXAndWide( m_pIcon, iIconX, iIconWidth ); + SetXAndWide( m_pLabelHeading, iTextX, iTextWidth ); + SetXAndWide( m_pLabelTitle, iTextX, iTextWidth ); + + m_queueNotification.Remove( m_queueNotification.Head() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAchievementNotificationPanel::SetXAndWide( Panel *pPanel, int x, int wide ) +{ + int xCur, yCur; + pPanel->GetPos( xCur, yCur ); + pPanel->SetPos( x, yCur ); + pPanel->SetWide( wide ); +} + +CON_COMMAND_F( achievement_notification_test, "Test the hud notification UI", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ) +{ + static int iCount=0; + + CAchievementNotificationPanel *pPanel = GET_HUDELEMENT( CAchievementNotificationPanel ); + if ( pPanel ) + { + pPanel->AddNotification( "HL2_KILL_ODESSAGUNSHIP", L"Achievement Progress", ( 0 == ( iCount % 2 ) ? L"Test Notification Message A (1/10)" : + L"Test Message B" ) ); + } + +#if 0 + IGameEvent *event = gameeventmanager->CreateEvent( "achievement_event" ); + if ( event ) + { + const char *szTestStr[] = { "TF_GET_HEADSHOTS", "TF_PLAY_GAME_EVERYMAP", "TF_PLAY_GAME_EVERYCLASS", "TF_GET_HEALPOINTS" }; + event->SetString( "achievement_name", szTestStr[iCount%ARRAYSIZE(szTestStr)] ); + event->SetInt( "cur_val", ( iCount%9 ) + 1 ); + event->SetInt( "max_val", 10 ); + gameeventmanager->FireEvent( event ); + } +#endif + + iCount++; +} \ No newline at end of file diff --git a/game/client/achievement_notification_panel.h b/game/client/achievement_notification_panel.h new file mode 100644 index 00000000..61caab73 --- /dev/null +++ b/game/client/achievement_notification_panel.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ACHIEVEMENT_NOTIFICATION_PANEL_H +#define ACHIEVEMENT_NOTIFICATION_PANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "hudelement.h" + +using namespace vgui; + +class CAchievementNotificationPanel : public CHudElement, public EditablePanel +{ + DECLARE_CLASS_SIMPLE( CAchievementNotificationPanel, EditablePanel ); + +public: + CAchievementNotificationPanel( const char *pElementName ); + + virtual void Init(); + virtual void ApplySchemeSettings( IScheme *scheme ); + virtual bool ShouldDraw( void ); + virtual void PerformLayout( void ); + virtual void LevelInit( void ) { m_flHideTime = 0; } + virtual void FireGameEvent( IGameEvent * event ); + virtual void OnTick( void ); + + void AddNotification( const char *szIconBaseName, const wchar_t *pHeading, const wchar_t *pTitle ); + +private: + void ShowNextNotification(); + void SetXAndWide( Panel *pPanel, int x, int wide ); + + float m_flHideTime; + + Label *m_pLabelHeading; + Label *m_pLabelTitle; + EditablePanel *m_pPanelBackground; + ImagePanel *m_pIcon; + + struct Notification_t + { + char szIconBaseName[255]; + wchar_t szHeading[255]; + wchar_t szTitle[255]; + }; + + CUtlLinkedList m_queueNotification; +}; + +#endif // ACHIEVEMENT_NOTIFICATION_PANEL_H \ No newline at end of file diff --git a/game/client/alphamaterialproxy.cpp b/game/client/alphamaterialproxy.cpp new file mode 100644 index 00000000..0783dfe4 --- /dev/null +++ b/game/client/alphamaterialproxy.cpp @@ -0,0 +1,62 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "ProxyEntity.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// $sineVar : name of variable that controls the alpha level (float) +class CAlphaMaterialProxy : public CEntityMaterialProxy +{ +public: + CAlphaMaterialProxy(); + virtual ~CAlphaMaterialProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( C_BaseEntity *pEntity ); + virtual IMaterial *GetMaterial(); + +private: + IMaterialVar *m_AlphaVar; +}; + +CAlphaMaterialProxy::CAlphaMaterialProxy() +{ + m_AlphaVar = NULL; +} + +CAlphaMaterialProxy::~CAlphaMaterialProxy() +{ +} + + +bool CAlphaMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool foundVar; + m_AlphaVar = pMaterial->FindVar( "$alpha", &foundVar, false ); + return foundVar; +} + +void CAlphaMaterialProxy::OnBind( C_BaseEntity *pEnt ) +{ + if (m_AlphaVar) + { + m_AlphaVar->SetFloatValue( pEnt->m_clrRender->a ); + } +} + +IMaterial *CAlphaMaterialProxy::GetMaterial() +{ + if ( !m_AlphaVar ) + return NULL; + + return m_AlphaVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CAlphaMaterialProxy, IMaterialProxy, "Alpha" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/animatedentitytextureproxy.cpp b/game/client/animatedentitytextureproxy.cpp new file mode 100644 index 00000000..7736f461 --- /dev/null +++ b/game/client/animatedentitytextureproxy.cpp @@ -0,0 +1,51 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "BaseAnimatedTextureProxy.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CAnimatedEntityTextureProxy : public CBaseAnimatedTextureProxy +{ +public: + CAnimatedEntityTextureProxy() {} + virtual ~CAnimatedEntityTextureProxy() {} + + virtual float GetAnimationStartTime( void* pBaseEntity ); + virtual void AnimationWrapped( void* pC_BaseEntity ); + +}; + +EXPOSE_INTERFACE( CAnimatedEntityTextureProxy, IMaterialProxy, "AnimatedEntityTexture" IMATERIAL_PROXY_INTERFACE_VERSION ); + +float CAnimatedEntityTextureProxy::GetAnimationStartTime( void* pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + if (!pRend) + return 0.0f; + + C_BaseEntity* pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if (pEntity) + { + return pEntity->GetTextureAnimationStartTime(); + } + return 0.0f; +} + +void CAnimatedEntityTextureProxy::AnimationWrapped( void* pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + if (!pRend) + return; + + C_BaseEntity* pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if (pEntity) + { + pEntity->TextureAnimationWrapped(); + } +} diff --git a/game/client/animatedoffsettextureproxy.cpp b/game/client/animatedoffsettextureproxy.cpp new file mode 100644 index 00000000..393a5eb3 --- /dev/null +++ b/game/client/animatedoffsettextureproxy.cpp @@ -0,0 +1,56 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "BaseAnimatedTextureProxy.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CAnimatedOffsetTextureProxy : public CBaseAnimatedTextureProxy +{ +public: + CAnimatedOffsetTextureProxy() : m_flFrameOffset( 0.0f ) {} + + virtual ~CAnimatedOffsetTextureProxy() {} + + virtual float GetAnimationStartTime( void* pBaseEntity ); + virtual void OnBind( void *pBaseEntity ); + +protected: + + float m_flFrameOffset; +}; + +EXPOSE_INTERFACE( CAnimatedOffsetTextureProxy, IMaterialProxy, "AnimatedOffsetTexture" IMATERIAL_PROXY_INTERFACE_VERSION ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pArg - +// Output : float +//----------------------------------------------------------------------------- +float CAnimatedOffsetTextureProxy::GetAnimationStartTime( void* pArg ) +{ + return m_flFrameOffset; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pBaseEntity - +//----------------------------------------------------------------------------- +void CAnimatedOffsetTextureProxy::OnBind( void *pBaseEntity ) +{ + C_BaseEntity* pEntity = (C_BaseEntity*)pBaseEntity; + + if ( pEntity ) + { + m_flFrameOffset = pEntity->GetTextureAnimationStartTime(); + } + + // Call into the base class + CBaseAnimatedTextureProxy::OnBind( pBaseEntity ); +} + diff --git a/game/client/animatedtextureproxy.cpp b/game/client/animatedtextureproxy.cpp new file mode 100644 index 00000000..017af289 --- /dev/null +++ b/game/client/animatedtextureproxy.cpp @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "BaseAnimatedTextureProxy.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CAnimatedTextureProxy : public CBaseAnimatedTextureProxy +{ +public: + CAnimatedTextureProxy() {} + virtual ~CAnimatedTextureProxy() {} + virtual float GetAnimationStartTime( void* pBaseEntity ); +}; + +EXPOSE_INTERFACE( CAnimatedTextureProxy, IMaterialProxy, "AnimatedTexture" IMATERIAL_PROXY_INTERFACE_VERSION ); + +#pragma warning (disable : 4100) + +float CAnimatedTextureProxy::GetAnimationStartTime( void* pBaseEntity ) +{ + return 0; +} + diff --git a/game/client/animationlayer.h b/game/client/animationlayer.h new file mode 100644 index 00000000..e6fb14ce --- /dev/null +++ b/game/client/animationlayer.h @@ -0,0 +1,173 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef ANIMATIONLAYER_H +#define ANIMATIONLAYER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "rangecheckedvar.h" +#include "lerp_functions.h" + +class C_AnimationLayer +{ +public: + + // This allows the datatables to access private members. + ALLOW_DATATABLES_PRIVATE_ACCESS(); + + C_AnimationLayer(); + void Reset(); + + void SetOrder( int order ); + +public: + + bool IsActive( void ); + + CRangeCheckedVar m_nSequence; + CRangeCheckedVar m_flPrevCycle; + CRangeCheckedVar m_flWeight; + int m_nOrder; + + // used for automatic crossfades between sequence changes + CRangeCheckedVar m_flPlaybackRate; + CRangeCheckedVar m_flCycle; + + float GetFadeout( float flCurTime ); + + float m_flLayerAnimtime; + float m_flLayerFadeOuttime; +}; +#ifdef CLIENT_DLL + #define CAnimationLayer C_AnimationLayer +#endif + + +inline C_AnimationLayer::C_AnimationLayer() +{ + Reset(); +} + +inline void C_AnimationLayer::Reset() +{ + m_nSequence = 0; + m_flPrevCycle = 0; + m_flWeight = 0; + m_flPlaybackRate = 0; + m_flCycle = 0; + m_flLayerAnimtime = 0; + m_flLayerFadeOuttime = 0; +} + + +inline void C_AnimationLayer::SetOrder( int order ) +{ + m_nOrder = order; +} + +inline float C_AnimationLayer::GetFadeout( float flCurTime ) +{ + float s; + + if (m_flLayerFadeOuttime <= 0.0f) + { + s = 0; + } + else + { + // blend in over 0.2 seconds + s = 1.0 - (flCurTime - m_flLayerAnimtime) / m_flLayerFadeOuttime; + if (s > 0 && s <= 1.0) + { + // do a nice spline curve + s = 3 * s * s - 2 * s * s * s; + } + else if ( s > 1.0f ) + { + // Shouldn't happen, but maybe curtime is behind animtime? + s = 1.0f; + } + } + return s; +} + + +inline C_AnimationLayer LoopingLerp( float flPercent, C_AnimationLayer& from, C_AnimationLayer& to ) +{ + C_AnimationLayer output; + + output.m_nSequence = to.m_nSequence; + output.m_flCycle = LoopingLerp( flPercent, (float)from.m_flCycle, (float)to.m_flCycle ); + output.m_flPrevCycle = to.m_flPrevCycle; + output.m_flWeight = Lerp( flPercent, from.m_flWeight, to.m_flWeight ); + output.m_nOrder = to.m_nOrder; + + output.m_flLayerAnimtime = to.m_flLayerAnimtime; + output.m_flLayerFadeOuttime = to.m_flLayerFadeOuttime; + return output; +} + +inline C_AnimationLayer Lerp( float flPercent, const C_AnimationLayer& from, const C_AnimationLayer& to ) +{ + C_AnimationLayer output; + + output.m_nSequence = to.m_nSequence; + output.m_flCycle = Lerp( flPercent, from.m_flCycle, to.m_flCycle ); + output.m_flPrevCycle = to.m_flPrevCycle; + output.m_flWeight = Lerp( flPercent, from.m_flWeight, to.m_flWeight ); + output.m_nOrder = to.m_nOrder; + + output.m_flLayerAnimtime = to.m_flLayerAnimtime; + output.m_flLayerFadeOuttime = to.m_flLayerFadeOuttime; + return output; +} + +inline C_AnimationLayer LoopingLerp_Hermite( float flPercent, C_AnimationLayer& prev, C_AnimationLayer& from, C_AnimationLayer& to ) +{ + C_AnimationLayer output; + + output.m_nSequence = to.m_nSequence; + output.m_flCycle = LoopingLerp_Hermite( flPercent, (float)prev.m_flCycle, (float)from.m_flCycle, (float)to.m_flCycle ); + output.m_flPrevCycle = to.m_flPrevCycle; + output.m_flWeight = Lerp( flPercent, from.m_flWeight, to.m_flWeight ); + output.m_nOrder = to.m_nOrder; + + output.m_flLayerAnimtime = to.m_flLayerAnimtime; + output.m_flLayerFadeOuttime = to.m_flLayerFadeOuttime; + return output; +} + +// YWB: Specialization for interpolating euler angles via quaternions... +inline C_AnimationLayer Lerp_Hermite( float flPercent, const C_AnimationLayer& prev, const C_AnimationLayer& from, const C_AnimationLayer& to ) +{ + C_AnimationLayer output; + + output.m_nSequence = to.m_nSequence; + output.m_flCycle = Lerp_Hermite( flPercent, prev.m_flCycle, from.m_flCycle, to.m_flCycle ); + output.m_flPrevCycle = to.m_flPrevCycle; + output.m_flWeight = Lerp( flPercent, from.m_flWeight, to.m_flWeight ); + output.m_nOrder = to.m_nOrder; + + output.m_flLayerAnimtime = to.m_flLayerAnimtime; + output.m_flLayerFadeOuttime = to.m_flLayerFadeOuttime; + return output; +} + +inline void Lerp_Clamp( C_AnimationLayer &val ) +{ + Lerp_Clamp( val.m_nSequence ); + Lerp_Clamp( val.m_flCycle ); + Lerp_Clamp( val.m_flPrevCycle ); + Lerp_Clamp( val.m_flWeight ); + Lerp_Clamp( val.m_nOrder ); + Lerp_Clamp( val.m_flLayerAnimtime ); + Lerp_Clamp( val.m_flLayerFadeOuttime ); +} + +#endif // ANIMATIONLAYER_H diff --git a/game/client/baseanimatedtextureproxy.cpp b/game/client/baseanimatedtextureproxy.cpp new file mode 100644 index 00000000..9e581428 --- /dev/null +++ b/game/client/baseanimatedtextureproxy.cpp @@ -0,0 +1,138 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "BaseAnimatedTextureProxy.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" +#include "materialsystem/ITexture.h" +#include "tier1/KeyValues.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + +//----------------------------------------------------------------------------- +// Constructor, destructor: +//----------------------------------------------------------------------------- + +CBaseAnimatedTextureProxy::CBaseAnimatedTextureProxy() +{ + Cleanup(); +} + +CBaseAnimatedTextureProxy::~CBaseAnimatedTextureProxy() +{ + Cleanup(); +} + + +//----------------------------------------------------------------------------- +// Initialization, shutdown +//----------------------------------------------------------------------------- +bool CBaseAnimatedTextureProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + char const* pAnimatedTextureVarName = pKeyValues->GetString( "animatedTextureVar" ); + if( !pAnimatedTextureVarName ) + return false; + + bool foundVar; + m_AnimatedTextureVar = pMaterial->FindVar( pAnimatedTextureVarName, &foundVar, false ); + if( !foundVar ) + return false; + + char const* pAnimatedTextureFrameNumVarName = pKeyValues->GetString( "animatedTextureFrameNumVar" ); + if( !pAnimatedTextureFrameNumVarName ) + return false; + + m_AnimatedTextureFrameNumVar = pMaterial->FindVar( pAnimatedTextureFrameNumVarName, &foundVar, false ); + if( !foundVar ) + return false; + + m_FrameRate = pKeyValues->GetFloat( "animatedTextureFrameRate", 15 ); + m_WrapAnimation = !pKeyValues->GetInt( "animationNoWrap", 0 ); + return true; +} + +void CBaseAnimatedTextureProxy::Cleanup() +{ + m_AnimatedTextureVar = NULL; + m_AnimatedTextureFrameNumVar = NULL; +} + + +//----------------------------------------------------------------------------- +// Does the dirty deed +//----------------------------------------------------------------------------- +void CBaseAnimatedTextureProxy::OnBind( void *pEntity ) +{ + Assert ( m_AnimatedTextureVar ); + + if( m_AnimatedTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE ) + { + return; + } + ITexture *pTexture; + pTexture = m_AnimatedTextureVar->GetTextureValue(); + int numFrames = pTexture->GetNumAnimationFrames(); + + if ( numFrames <= 0 ) + { + Assert( !"0 frames in material calling animated texture proxy" ); + return; + } + + // NOTE: Must not use relative time based methods here + // because the bind proxy can be called many times per frame. + // Prevent multiple Wrap callbacks to be sent for no wrap mode + float startTime = GetAnimationStartTime(pEntity); + float deltaTime = gpGlobals->curtime - startTime; + float prevTime = deltaTime - gpGlobals->frametime; + + // Clamp.. + if (deltaTime < 0.0f) + deltaTime = 0.0f; + if (prevTime < 0.0f) + prevTime = 0.0f; + + float frame = m_FrameRate * deltaTime; + float prevFrame = m_FrameRate * prevTime; + + int intFrame = ((int)frame) % numFrames; + int intPrevFrame = ((int)prevFrame) % numFrames; + + // Report wrap situation... + if (intPrevFrame > intFrame) + { + if (m_WrapAnimation) + { + AnimationWrapped( pEntity ); + } + else + { + // Only sent the wrapped message once. + // when we're in non-wrapping mode + if (prevFrame < numFrames) + AnimationWrapped( pEntity ); + intFrame = numFrames - 1; + } + } + + m_AnimatedTextureFrameNumVar->SetIntValue( intFrame ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CBaseAnimatedTextureProxy::GetMaterial() +{ + return m_AnimatedTextureVar->GetOwningMaterial(); +} diff --git a/game/client/baseanimatedtextureproxy.h b/game/client/baseanimatedtextureproxy.h new file mode 100644 index 00000000..a08c571a --- /dev/null +++ b/game/client/baseanimatedtextureproxy.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BASEANIMATEDTEXTUREPROXY +#define BASEANIMATEDTEXTUREPROXY + +#include "materialsystem/IMaterialProxy.h" + +class IMaterial; +class IMaterialVar; + +#pragma warning (disable : 4100) + +class CBaseAnimatedTextureProxy : public IMaterialProxy +{ +public: + CBaseAnimatedTextureProxy(); + virtual ~CBaseAnimatedTextureProxy(); + + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pC_BaseEntity ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +protected: + // derived classes must implement this; it returns the time + // that the animation began + virtual float GetAnimationStartTime( void* pBaseEntity ) = 0; + + // Derived classes may implement this if they choose; + // this method is called whenever the animation wraps... + virtual void AnimationWrapped( void* pBaseEntity ) {} + +protected: + void Cleanup(); + + IMaterialVar *m_AnimatedTextureVar; + IMaterialVar *m_AnimatedTextureFrameNumVar; + float m_FrameRate; + bool m_WrapAnimation; +}; + +#endif // BASEANIMATEDTEXTUREPROXY \ No newline at end of file diff --git a/game/client/baseclientrendertargets.cpp b/game/client/baseclientrendertargets.cpp new file mode 100644 index 00000000..218a6d36 --- /dev/null +++ b/game/client/baseclientrendertargets.cpp @@ -0,0 +1,77 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Implementation for CBaseClientRenderTargets class. +// Provides Init functions for common render textures used by the engine. +// Mod makers can inherit from this class, and call the Create functions for +// only the render textures the want for their mod. +//=============================================================================// + +#include "cbase.h" +#include "baseclientrendertargets.h" // header +#include "materialsystem/imaterialsystemhardwareconfig.h" // Hardware config checks + +ITexture* CBaseClientRenderTargets::CreateWaterReflectionTexture( IMaterialSystem* pMaterialSystem, int iSize ) +{ + return pMaterialSystem->CreateNamedRenderTargetTextureEx2( + "_rt_WaterReflection", + iSize, iSize, RT_SIZE_PICMIP, + pMaterialSystem->GetBackBufferFormat(), + MATERIAL_RT_DEPTH_SHARED, + TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT, + CREATERENDERTARGETFLAGS_HDR ); +} + +ITexture* CBaseClientRenderTargets::CreateWaterRefractionTexture( IMaterialSystem* pMaterialSystem, int iSize ) +{ + return pMaterialSystem->CreateNamedRenderTargetTextureEx2( + "_rt_WaterRefraction", + iSize, iSize, RT_SIZE_PICMIP, + // This is different than reflection because it has to have alpha for fog factor. + IMAGE_FORMAT_RGBA8888, + MATERIAL_RT_DEPTH_SHARED, + TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT, + CREATERENDERTARGETFLAGS_HDR ); +} + +ITexture* CBaseClientRenderTargets::CreateCameraTexture( IMaterialSystem* pMaterialSystem, int iSize ) +{ + return pMaterialSystem->CreateNamedRenderTargetTextureEx2( + "_rt_Camera", + iSize, iSize, RT_SIZE_DEFAULT, + pMaterialSystem->GetBackBufferFormat(), + MATERIAL_RT_DEPTH_SHARED, + 0, + CREATERENDERTARGETFLAGS_HDR ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called by the engine in material system init and shutdown. +// Clients should override this in their inherited version, but the base +// is to init all standard render targets for use. +// Input : pMaterialSystem - the engine's material system (our singleton is not yet inited at the time this is called) +// pHardwareConfig - the user hardware config, useful for conditional render target setup +//----------------------------------------------------------------------------- +void CBaseClientRenderTargets::InitClientRenderTargets( IMaterialSystem* pMaterialSystem, IMaterialSystemHardwareConfig* pHardwareConfig, int iWaterTextureSize, int iCameraTextureSize ) +{ + // Water effects + m_WaterReflectionTexture.Init( CreateWaterReflectionTexture( pMaterialSystem, iWaterTextureSize ) ); + m_WaterRefractionTexture.Init( CreateWaterRefractionTexture( pMaterialSystem, iWaterTextureSize ) ); + + // Monitors + m_CameraTexture.Init( CreateCameraTexture( pMaterialSystem, iCameraTextureSize ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Shut down each CTextureReference we created in InitClientRenderTargets. +// Called by the engine in material system shutdown. +// Input : - +//----------------------------------------------------------------------------- +void CBaseClientRenderTargets::ShutdownClientRenderTargets() +{ + // Water effects + m_WaterReflectionTexture.Shutdown(); + m_WaterRefractionTexture.Shutdown(); + + // Monitors + m_CameraTexture.Shutdown(); +} \ No newline at end of file diff --git a/game/client/baseclientrendertargets.h b/game/client/baseclientrendertargets.h new file mode 100644 index 00000000..a74f481b --- /dev/null +++ b/game/client/baseclientrendertargets.h @@ -0,0 +1,61 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Has init functions for all the standard render targets used by most games. +// Mods who wish to make their own render targets can inherit from this class +// and in the 'InitClientRenderTargets' interface called by the engine, set up +// their own render targets as well as calling the init functions for various +// common render targets provided by this class. +// +// Note: Unless the client defines a singleton interface by inheriting from +// this class and exposing the singleton instance, these init and shutdown +// functions WILL NOT be called by the engine. +// +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#ifndef CLIENTRENDERTARTETS_H_ +#define CLIENTRENDERTARTETS_H_ +#ifdef _WIN32 +#pragma once +#endif + +#include "game/client/iclientrendertargets.h" // base class with interfaces called by the engine +#include "materialsystem\imaterialsystem.h" // for material system classes and interfaces + + +// Externs +class IMaterialSystem; +class IMaterialSystemHardwareConfig; + +class CBaseClientRenderTargets : public IClientRenderTargets +{ + // no networked vars + DECLARE_CLASS_GAMEROOT( CBaseClientRenderTargets, IClientRenderTargets ); +public: + // Interface called by engine during material system startup. + virtual void InitClientRenderTargets ( IMaterialSystem* pMaterialSystem, IMaterialSystemHardwareConfig* pHardwareConfig, int iWaterTextureSize = 1024, int iCameraTextureSize = 256 ); + // Shutdown all custom render targets here. + virtual void ShutdownClientRenderTargets ( void ); + +protected: + + // Standard render textures used by most mods-- Classes inheriting from + // this can choose to init these or not depending on their needs. + + // For reflective and refracting water + CTextureReference m_WaterReflectionTexture; + CTextureReference m_WaterRefractionTexture; + + // Used for monitors + CTextureReference m_CameraTexture; + + // Init functions for the common render targets + ITexture* CreateWaterReflectionTexture( IMaterialSystem* pMaterialSystem, int iSize = 1024 ); + ITexture* CreateWaterRefractionTexture( IMaterialSystem* pMaterialSystem, int iSize = 1024 ); + ITexture* CreateCameraTexture( IMaterialSystem* pMaterialSystem, int iSize = 256 ); + +}; + +#endif // CLIENTRENDERTARTETS_H_ \ No newline at end of file diff --git a/game/client/basepresence.cpp b/game/client/basepresence.cpp new file mode 100644 index 00000000..3dabe8af --- /dev/null +++ b/game/client/basepresence.cpp @@ -0,0 +1,94 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Base presence implementation for PC +// +//=====================================================================================// + +#include "cbase.h" +#include "basepresence.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Default global singleton. Mods should override this. +static CBasePresence s_basePresence; +IPresence *presence = NULL; + +//----------------------------------------------------------------------------- +// Steam version of Rich Presence is a WIP, so PC implementation is stubbed for now. +//----------------------------------------------------------------------------- +bool CBasePresence::Init( void ) +{ + if ( !presence ) + { + // Mod didn't override, default to base implementation + presence = &s_basePresence; + } + return true; +} +void CBasePresence::Shutdown( void ) +{ + // TODO: Implement for PC +} +void CBasePresence::Update( float frametime ) +{ + // TODO: Implement for PC +} +void CBasePresence::UserSetContext( unsigned int nUserIndex, unsigned int nContextId, unsigned int nContextValue, bool bAsync ) +{ + // TODO: Implement for PC +} +void CBasePresence::UserSetProperty( unsigned int nUserIndex, unsigned int nPropertyId, unsigned int nBytes, const void *pvValue, bool bAsync ) +{ + // TODO: Implement for PC +} +void CBasePresence::SetupGameProperties( CUtlVector< XUSER_CONTEXT > &contexts, CUtlVector< XUSER_PROPERTY > &properties ) +{ + // TODO: Implement for PC +} +unsigned int CBasePresence::GetPresenceID( const char *pIDName ) +{ + return 0; +} +const char *CBasePresence::GetPropertyIdString( const uint id ) +{ + return NULL; +} +void CBasePresence::GetPropertyDisplayString( uint id, uint value, char *pOutput, int nBytes ) +{ +} +void CBasePresence::StartStatsReporting( HANDLE handle, bool bArbitrated ) +{ +} +void CBasePresence::SetStat( uint iPropertyId, int iPropertyValue, int dataType ) +{ +} +void CBasePresence::UploadStats() +{ +} + +//--------------------------------------------------------- +// Debug support +//--------------------------------------------------------- +void CBasePresence::DebugUserSetContext( const CCommand &args ) +{ + if ( args.ArgC() == 3 ) + { + UserSetContext( 0, atoi( args.Arg( 1 ) ), atoi( args.Arg( 2 ) ) ); + } + else + { + Warning( "user_context \n" ); + } +} +void CBasePresence::DebugUserSetProperty( const CCommand &args ) +{ + if ( args.ArgC() == 3 ) + { + UserSetProperty( 0, strtoul( args.Arg( 1 ), NULL, 0 ), sizeof(int), args.Arg( 2 ) ); + } + else + { + Warning( "user_property \n" ); + } +} diff --git a/game/client/basepresence.h b/game/client/basepresence.h new file mode 100644 index 00000000..f4ea6335 --- /dev/null +++ b/game/client/basepresence.h @@ -0,0 +1,55 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: Base implementation of the IPresence interface +// +//============================================================================= + +#ifndef BASEPRESENCE_H +#define BASEPRESENCE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ipresence.h" +#include "igamesystem.h" + +//----------------------------------------------------------------------------- +// Purpose: Common implementation for setting user contexts and properties. +// Each client should inherit from this to implement mod-specific presence info. +//----------------------------------------------------------------------------- +class CBasePresence : public IPresence, public CAutoGameSystemPerFrame +{ +public: + // CBaseGameSystemPerFrame overrides + virtual bool Init( void ); + virtual void Shutdown( void ); + virtual void Update( float frametime ); + virtual char const *Name( void ) { return "presence"; } + + // IPresence Interface + virtual void UserSetContext( unsigned int nUserIndex, unsigned int nContextId, unsigned int nContextValue, bool bAsync = false ); + virtual void UserSetProperty( unsigned int nUserIndex, unsigned int nPropertyId, unsigned int nBytes, const void *pvValue, bool bAsync = false ); + virtual void SetupGameProperties( CUtlVector< XUSER_CONTEXT > &contexts, CUtlVector< XUSER_PROPERTY > &properties ); + virtual uint GetPresenceID( const char *pIdName ); + virtual void GetPropertyDisplayString( uint id, uint value, char *pOutput, int nBytes ); + virtual const char *GetPropertyIdString( const uint id ); + + // Stats reporting + virtual void StartStatsReporting( HANDLE handle, bool bArbitrated ); + virtual void SetStat( uint iPropertyId, int iPropertyValue, int dataType ); + virtual void UploadStats(); + +protected: + bool m_bArbitrated; + bool m_bReportingStats; + HANDLE m_hSession; + CUtlVector< XUSER_PROPERTY > m_PlayerStats; + + //--------------------------------------------------------- + // Debug support + //--------------------------------------------------------- + CON_COMMAND_MEMBER_F( CBasePresence, "user_context", DebugUserSetContext, "Set a Rich Presence Context: user_context ", 0 ) + CON_COMMAND_MEMBER_F( CBasePresence, "user_property", DebugUserSetProperty, "Set a Rich Presence Property: user_property ", 0 ) +}; + +#endif // BASEPRESENCE_H diff --git a/game/client/basepresence_xbox.cpp b/game/client/basepresence_xbox.cpp new file mode 100644 index 00000000..f21b877b --- /dev/null +++ b/game/client/basepresence_xbox.cpp @@ -0,0 +1,167 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Base rich presence implementation for Xbox360 +// +//=====================================================================================// + +#include "cbase.h" +#include "basepresence.h" +#include "cdll_client_int.h" +#include "ixboxsystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Default global instance. Mods should override this. +static CBasePresence s_basePresence; +IPresence *presence = NULL; + +//----------------------------------------------------------------------------- +// Purpose: Init +//----------------------------------------------------------------------------- +bool CBasePresence::Init( void ) +{ + if ( !presence ) + { + // Mod didn't override, default to base implementation + presence = &s_basePresence; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Shutdown +//----------------------------------------------------------------------------- +void CBasePresence::Shutdown( void ) +{ + // Do nothing +} + + +//----------------------------------------------------------------------------- +// Purpose: Per-frame update +//----------------------------------------------------------------------------- +void CBasePresence::Update( float frametime ) +{ + // Do nothing +} + + +//----------------------------------------------------------------------------- +// Contexts are strings that describe the current state of the game. +//----------------------------------------------------------------------------- +void CBasePresence::UserSetContext( unsigned int nUserIndex, unsigned int nContextId, unsigned int nContextValue, bool bAsync ) +{ + if ( !xboxsystem->UserSetContext( nUserIndex, nContextId, nContextValue, bAsync ) ) + { + Warning( "CBasePresence: UserSetContext failed.\n" ); + } +} + + +//----------------------------------------------------------------------------- +// Properties are (usually) numeric values that can be insterted into context strings. +//----------------------------------------------------------------------------- +void CBasePresence::UserSetProperty( unsigned int nUserIndex, unsigned int nPropertyId, unsigned int nBytes, const void *pvValue, bool bAsync ) +{ + if ( !xboxsystem->UserSetProperty( nUserIndex, nPropertyId, nBytes, pvValue, bAsync ) ) + { + Warning( "CBasePresence: UserSetProperty failed.\n" ); + } +} + +//----------------------------------------------------------------------------- +// Get game session properties from matchmaking. +//----------------------------------------------------------------------------- +void CBasePresence::SetupGameProperties( CUtlVector< XUSER_CONTEXT > &contexts, CUtlVector< XUSER_PROPERTY > &properties ) +{ + Assert( 0 ); +} + +//----------------------------------------------------------------------------- +// Convert a string to a presence ID. +//----------------------------------------------------------------------------- +uint CBasePresence::GetPresenceID( const char *pIdName ) +{ + Assert( 0 ); + return 0; +} + +//----------------------------------------------------------------------------- +// Convert a presence ID to a string. +//----------------------------------------------------------------------------- +const char *CBasePresence::GetPropertyIdString( const uint id ) +{ + Assert( 0 ); + return NULL; +} + +//----------------------------------------------------------------------------- +// Get display string for a game property. +//----------------------------------------------------------------------------- +void CBasePresence::GetPropertyDisplayString( uint id, uint value, char *pOutput, int nBytes ) +{ + Assert( 0 ); +} + +//----------------------------------------------------------------------------- +// Set up for reporting stats to Live. +//----------------------------------------------------------------------------- +void CBasePresence::StartStatsReporting( HANDLE handle, bool bArbitrated ) +{ + m_bArbitrated = bArbitrated; + m_hSession = handle; + m_bReportingStats = true; + m_PlayerStats.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Set a specific stat property. +//----------------------------------------------------------------------------- +void CBasePresence::SetStat( uint iPropertyId, int iPropertyValue, int dataType ) +{ + if ( m_bReportingStats ) + { + XUSER_PROPERTY prop; + prop.dwPropertyId = iPropertyId; + prop.value.nData = iPropertyValue; + prop.value.type = dataType; + m_PlayerStats.AddToTail( prop ); + } +} + +//----------------------------------------------------------------------------- +// Upload the stats to Live. +//----------------------------------------------------------------------------- +void CBasePresence::UploadStats() +{ + Assert( 0 ); +} + +//--------------------------------------------------------- +// Debug support +//--------------------------------------------------------- +void CBasePresence::DebugUserSetContext( const CCommand &args ) +{ + if ( args.ArgC() == 3 ) + { + UserSetContext( XBX_GetPrimaryUserId(), atoi( args.Arg( 1 ) ), atoi( args.Arg( 2 ) ) ); + } + else + { + Warning( "user_context \n" ); + } +} +void CBasePresence::DebugUserSetProperty( const CCommand &args ) +{ + if ( args.ArgC() == 3 ) + { + int value = atoi( args.Arg( 2 ) ); + UserSetProperty( XBX_GetPrimaryUserId(), strtoul( args.Arg( 1 ), NULL, 0 ), sizeof(int), &value ); + } + else + { + Warning( "user_property \n" ); + } +} diff --git a/game/client/beamdraw.cpp b/game/client/beamdraw.cpp new file mode 100644 index 00000000..eb8accb2 --- /dev/null +++ b/game/client/beamdraw.cpp @@ -0,0 +1,1530 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "cbase.h" +#include "beamdraw.h" +#include "enginesprite.h" +#include "IViewRender_Beams.h" +#include "view.h" +#include "iviewrender.h" +#include "engine/ivmodelinfo.h" +#include "fx_line.h" +#include "materialsystem/imaterialvar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar r_drawsprites; +extern ConVar r_DrawBeams; + +static IMaterial *g_pBeamWireframeMaterial; + +//----------------------------------------------------------------------------- +// Purpose: Retrieve sprite object and set it up for rendering +// Input : *pSpriteModel - +// frame - +// rendermode - +// Output : CEngineSprite +//----------------------------------------------------------------------------- +CEngineSprite *Draw_SetSpriteTexture( const model_t *pSpriteModel, int frame, int rendermode ) +{ + CEngineSprite *psprite; + IMaterial *material; + + psprite = ( CEngineSprite * )modelinfo->GetModelExtraData( pSpriteModel ); + Assert( psprite ); + + material = psprite->GetMaterial(); + if( !material ) + return NULL; + + CMatRenderContextPtr pRenderContext( materials ); + if ( ShouldDrawInWireFrameMode() || r_DrawBeams.GetInt() == 2 ) + { + if ( !g_pBeamWireframeMaterial ) + g_pBeamWireframeMaterial = materials->FindMaterial( "shadertest/wireframevertexcolor", TEXTURE_GROUP_OTHER ); + pRenderContext->Bind( g_pBeamWireframeMaterial, NULL ); + return psprite; + } + + psprite->SetFrame( frame ); + psprite->SetRenderMode( rendermode ); + + pRenderContext->Bind( material ); + return psprite; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pMaterial - +// source - +// color - +//----------------------------------------------------------------------------- +void DrawHalo(IMaterial* pMaterial, const Vector& source, float scale, float const* color, float flHDRColorScale ) +{ + static unsigned int nHDRColorScaleCache = 0; + Vector point, screen; + + if( pMaterial ) + { + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + // Transform source into screen space + ScreenTransform( source, screen ); + + meshBuilder.Color3fv (color); + meshBuilder.TexCoord2f (0, 0, 1); + VectorMA (source, -scale, CurrentViewUp(), point); + VectorMA (point, -scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color3fv (color); + meshBuilder.TexCoord2f (0, 0, 0); + VectorMA (source, scale, CurrentViewUp(), point); + VectorMA (point, -scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color3fv (color); + meshBuilder.TexCoord2f (0, 1, 0); + VectorMA (source, scale, CurrentViewUp(), point); + VectorMA (point, scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color3fv (color); + meshBuilder.TexCoord2f (0, 1, 1); + VectorMA (source, -scale, CurrentViewUp(), point); + VectorMA (point, scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Assumes the material has already been bound +//----------------------------------------------------------------------------- +void DrawSprite( const Vector &vecOrigin, float flWidth, float flHeight, color32 color ) +{ + unsigned char pColor[4] = { color.r, color.g, color.b, color.a }; + + // Generate half-widths + flWidth *= 0.5f; + flHeight *= 0.5f; + + // Compute direction vectors for the sprite + Vector fwd, right( 1, 0, 0 ), up( 0, 1, 0 ); + VectorSubtract( CurrentViewOrigin(), vecOrigin, fwd ); + float flDist = VectorNormalize( fwd ); + if (flDist >= 1e-3) + { + CrossProduct( CurrentViewUp(), fwd, right ); + flDist = VectorNormalize( right ); + if (flDist >= 1e-3) + { + CrossProduct( fwd, right, up ); + } + else + { + // In this case, fwd == g_vecVUp, it's right above or + // below us in screen space + CrossProduct( fwd, CurrentViewRight(), up ); + VectorNormalize( up ); + CrossProduct( up, fwd, right ); + } + } + + CMeshBuilder meshBuilder; + Vector point; + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + meshBuilder.Color4ubv (pColor); + meshBuilder.TexCoord2f (0, 0, 1); + VectorMA (vecOrigin, -flHeight, up, point); + VectorMA (point, -flWidth, right, point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv (pColor); + meshBuilder.TexCoord2f (0, 0, 0); + VectorMA (vecOrigin, flHeight, up, point); + VectorMA (point, -flWidth, right, point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv (pColor); + meshBuilder.TexCoord2f (0, 1, 0); + VectorMA (vecOrigin, flHeight, up, point); + VectorMA (point, flWidth, right, point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv (pColor); + meshBuilder.TexCoord2f (0, 1, 1); + VectorMA (vecOrigin, -flHeight, up, point); + VectorMA (point, flWidth, right, point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + + +//----------------------------------------------------------------------------- +// Compute vectors perpendicular to the beam +//----------------------------------------------------------------------------- +static void ComputeBeamPerpendicular( const Vector &vecBeamDelta, Vector *pPerp ) +{ + // Direction in worldspace of the center of the beam + Vector vecBeamCenter = vecBeamDelta; + VectorNormalize( vecBeamCenter ); + + CrossProduct( CurrentViewForward(), vecBeamCenter, *pPerp ); + VectorNormalize( *pPerp ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : noise_divisions - +// *prgNoise - +// *spritemodel - +// frame - +// rendermode - +// source - +// delta - +// flags - +// *color - +// fadescale - +//----------------------------------------------------------------------------- +void DrawSegs( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, const Vector& delta, + float startWidth, float endWidth, float scale, float freq, float speed, int segments, + int flags, float* color, float fadeLength, float flHDRColorScale ) +{ + int i, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep, brightness; + + Assert( fadeLength >= 0.0f ); + CEngineSprite *pSprite = Draw_SetSpriteTexture( spritemodel, frame, rendermode ); + if ( !pSprite ) + return; + + if ( segments < 2 ) + return; + + IMaterial *pMaterial = pSprite->GetMaterial(); + if( pMaterial ) + { + static unsigned int nHDRColorScaleCache = 0; + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + length = VectorLength( delta ); + float flMaxWidth = max(startWidth, endWidth) * 0.5f; + div = 1.0 / (segments-1); + + if ( length*div < flMaxWidth * 1.414 ) + { + // Here, we have too many segments; we could get overlap... so lets have less segments + segments = (int)(length / (flMaxWidth * 1.414)) + 1; + if ( segments < 2 ) + { + segments = 2; + } + } + + if ( segments > noise_divisions ) // UNDONE: Allow more segments? + { + segments = noise_divisions; + } + + div = 1.0 / (segments-1); + length *= 0.01; + + // UNDONE: Expose texture length scale factor to control "fuzziness" + + if ( flags & FBEAM_NOTILE ) + { + // Don't tile + vStep = div; + } + else + { + // Texture length texels per space pixel + vStep = length*div; + } + + // UNDONE: Expose this paramter as well(3.5)? Texture scroll rate along beam + vLast = fmod(freq*speed,1); // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + + if ( flags & FBEAM_SINENOISE ) + { + if ( segments < 16 ) + { + segments = 16; + div = 1.0 / (segments-1); + } + scale *= 100; + length = segments * (1.0/10); + } + else + { + scale *= length; + } + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((float)(noise_divisions-1) * div * 65536.0f); + noiseIndex = 0; + + if ( flags & FBEAM_SINENOISE ) + { + noiseIndex = 0; + } + + brightness = 1.0; + if ( flags & FBEAM_SHADEIN ) + { + brightness = 0; + } + + // What fraction of beam should be faded + Assert( fadeLength >= 0.0f ); + float fadeFraction = fadeLength/ delta.Length(); + + // BUGBUG: This code generates NANs when fadeFraction is zero! REVIST! + fadeFraction = clamp(fadeFraction,1e-6,1); + + // Choose two vectors that are perpendicular to the beam + Vector perp1; + ComputeBeamPerpendicular( delta, &perp1 ); + + // Specify all the segments. + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + CBeamSegDraw segDraw; + segDraw.Start( pRenderContext, segments, NULL ); + + for ( i = 0; i < segments; i++ ) + { + Assert( noiseIndex < (noise_divisions<<16) ); + BeamSeg_t curSeg; + curSeg.m_flAlpha = 1; + + fraction = i * div; + + // Fade in our out beam to fadeLength + + if ( (flags & FBEAM_SHADEIN) && (flags & FBEAM_SHADEOUT) ) + { + if (fraction < 0.5) + { + brightness = 2*(fraction/fadeFraction); + } + else + { + brightness = 2*(1.0 - (fraction/fadeFraction)); + } + } + else if ( flags & FBEAM_SHADEIN ) + { + brightness = fraction/fadeFraction; + } + else if ( flags & FBEAM_SHADEOUT ) + { + brightness = 1.0 - (fraction/fadeFraction); + } + + // clamps + if (brightness < 0 ) + { + brightness = 0; + } + else if (brightness > 1) + { + brightness = 1; + } + + VectorScale( *((Vector*)color), brightness, curSeg.m_vColor ); + + // UNDONE: Make this a spline instead of just a line? + VectorMA( source, fraction, delta, curSeg.m_vPos ); + + // Distort using noise + if ( scale != 0 ) + { + factor = prgNoise[noiseIndex>>16] * scale; + if ( flags & FBEAM_SINENOISE ) + { + float s, c; + SinCos( fraction*M_PI*length + freq, &s, &c ); + VectorMA( curSeg.m_vPos, factor * s, CurrentViewUp(), curSeg.m_vPos ); + // Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + VectorMA( curSeg.m_vPos, factor * c, CurrentViewRight(), curSeg.m_vPos ); + } + else + { + VectorMA( curSeg.m_vPos, factor, perp1, curSeg.m_vPos ); + } + } + + // Specify the next segment. + if( endWidth == startWidth ) + { + curSeg.m_flWidth = startWidth * 2; + } + else + { + curSeg.m_flWidth = ((fraction*(endWidth-startWidth))+startWidth) * 2; + } + + curSeg.m_flTexCoord = vLast; + segDraw.NextSeg( &curSeg ); + + + vLast += vStep; // Advance texture scroll (v axis only) + noiseIndex += noiseStep; + } + + segDraw.End(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CalcSegOrigin( Vector *vecOut, int iPoint, int noise_divisions, float *prgNoise, + const Vector &source, const Vector& delta, const Vector &perp, int segments, + float freq, float scale, float fraction, int flags ) +{ + Assert( segments > 1 ); + + float factor; + float length = VectorLength( delta ) * 0.01; + float div = 1.0 / (segments-1); + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + int noiseStep = (int)((float)(noise_divisions-1) * div * 65536.0f); + int noiseIndex = (iPoint) * noiseStep; + + // Sine noise beams have different length calculations + if ( flags & FBEAM_SINENOISE ) + { + length = segments * (1.0/10); + noiseIndex = 0; + } + + // UNDONE: Make this a spline instead of just a line? + VectorMA( source, fraction, delta, *vecOut ); + + // Distort using noise + if ( scale != 0 ) + { + factor = prgNoise[noiseIndex>>16] * scale; + if ( flags & FBEAM_SINENOISE ) + { + float s, c; + SinCos( fraction*M_PI*length + freq, &s, &c ); + VectorMA( *vecOut, factor * s, MainViewUp(), *vecOut ); + // Rotate the noise along the perpendicular axis a bit to keep the bolt from looking diagonal + VectorMA( *vecOut, factor * c, MainViewRight(), *vecOut ); + } + else + { + VectorMA( *vecOut, factor, perp, *vecOut ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : noise_divisions - +// *prgNoise - +// *spritemodel - +// frame - +// rendermode - +// source - +// delta - +// flags - +// *color - +// fadescale - +//----------------------------------------------------------------------------- +void DrawTeslaSegs( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, const Vector& delta, + float startWidth, float endWidth, float scale, float freq, float speed, int segments, + int flags, float* color, float fadeLength, float flHDRColorScale ) +{ + int i; + float div, length, fraction, vLast, vStep, brightness; + + Assert( fadeLength >= 0.0f ); + CEngineSprite *pSprite = Draw_SetSpriteTexture( spritemodel, frame, rendermode ); + if ( !pSprite ) + return; + + if ( segments < 2 ) + return; + + IMaterial *pMaterial = pSprite->GetMaterial(); + if( pMaterial ) + { + static unsigned int nHDRColorScaleCache = 0; + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + if ( segments > noise_divisions ) // UNDONE: Allow more segments? + segments = noise_divisions; + + length = VectorLength( delta ) * 0.01; + div = 1.0 / (segments-1); + + // UNDONE: Expose texture length scale factor to control "fuzziness" + vStep = length*div; // Texture length texels per space pixel + + // UNDONE: Expose this paramter as well(3.5)? Texture scroll rate along beam + vLast = fmod(freq*speed,1); // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + + brightness = 1.0; + if ( flags & FBEAM_SHADEIN ) + brightness = 0; + + // What fraction of beam should be faded + Assert( fadeLength >= 0.0f ); + float fadeFraction = fadeLength/ delta.Length(); + + // BUGBUG: This code generates NANs when fadeFraction is zero! REVIST! + fadeFraction = clamp(fadeFraction,1e-6,1); + + Vector perp; + ComputeBeamPerpendicular( delta, &perp ); + + // Specify all the segments. + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + CBeamSegDraw segDraw; + segDraw.Start( pRenderContext, segments, NULL ); + + // Keep track of how many times we've branched + int iBranches = 0; + + Vector vecStart, vecEnd; + float flWidth = 0; + float flEndWidth = 0; + + for ( i = 0; i < segments; i++ ) + { + BeamSeg_t curSeg; + curSeg.m_flAlpha = 1; + + fraction = i * div; + + // Fade in our out beam to fadeLength + + if ( (flags & FBEAM_SHADEIN) && (flags & FBEAM_SHADEOUT) ) + { + if (fraction < 0.5) + { + brightness = 2*(fraction/fadeFraction); + } + else + { + brightness = 2*(1.0 - (fraction/fadeFraction)); + } + } + else if ( flags & FBEAM_SHADEIN ) + { + brightness = fraction/fadeFraction; + } + else if ( flags & FBEAM_SHADEOUT ) + { + brightness = 1.0 - (fraction/fadeFraction); + } + + // clamps + if (brightness < 0 ) + { + brightness = 0; + } + else if (brightness > 1) + { + brightness = 1; + } + + VectorScale( *((Vector*)color), brightness, curSeg.m_vColor ); + + CalcSegOrigin( &curSeg.m_vPos, i, noise_divisions, prgNoise, source, delta, perp, segments, freq, scale, fraction, flags ); + + // Specify the next segment. + if( endWidth == startWidth ) + curSeg.m_flWidth = startWidth * 2; + else + curSeg.m_flWidth = ((fraction*(endWidth-startWidth))+startWidth) * 2; + + // Reduce the width by the current number of branches we've had + for ( int j = 0; i < iBranches; j++ ) + { + curSeg.m_flWidth *= 0.5; + } + + curSeg.m_flTexCoord = vLast; + + segDraw.NextSeg( &curSeg ); + + vLast += vStep; // Advance texture scroll (v axis only) + + // Now see if we'd like to branch here + // For now, always branch at the midpoint. + // We could branch randomly, and multiple times per beam + if ( i == (segments * 0.5) ) + { + // Figure out what the new width would be + // Halve the width because the beam is breaking in two, and halve it again because width is doubled above + flWidth = curSeg.m_flWidth * 0.25; + if ( flWidth > 1 ) + { + iBranches++; + + // Get an endpoint for the new branch + vecStart = curSeg.m_vPos; + vecEnd = source + delta + (MainViewUp() * 32) + (MainViewRight() * 32); + vecEnd -= vecStart; + + // Reduce the end width by the current number of branches we've had + flEndWidth = endWidth; + for ( int j = 0; i < iBranches; j++ ) + { + flEndWidth *= 0.5; + } + } + } + } + + segDraw.End(); + + // If we branched, draw the new beam too + if ( iBranches ) + { + DrawTeslaSegs( noise_divisions, prgNoise, spritemodel, frame, rendermode, + vecStart, vecEnd, flWidth, flEndWidth, scale, freq, speed, segments, + flags, color, fadeLength, flHDRColorScale ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : noise_divisions - +// *prgNoise - +// *beammodel - +// *halomodel - +// flHaloScale - +// startWidth - +// endWidth - +// scale - +// freq - +// speed - +// segments - +// * - +//----------------------------------------------------------------------------- + +void DrawSplineSegs( int noise_divisions, float *prgNoise, + const model_t* beammodel, const model_t* halomodel, float flHaloScale, + float frame, int rendermode, int numAttachments, Vector* attachment, + float startWidth, float endWidth, float scale, float freq, float speed, int segments, + int flags, float* color, float fadeLength, float flHDRColorScale ) +{ + int noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep, brightness; + float scaledColor[3]; + + model_t *beamsprite = ( model_t *)beammodel; + model_t *halosprite = ( model_t *)halomodel; + + CEngineSprite *pBeamSprite = Draw_SetSpriteTexture( beamsprite, frame, rendermode ); + if ( !pBeamSprite ) + return; + + // Figure out the number of segments. + if ( segments < 2 ) + return; + + IMaterial *pMaterial = pBeamSprite->GetMaterial(); + if( pMaterial ) + { + static unsigned int nHDRColorScaleCache = 0; + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + if ( segments > noise_divisions ) // UNDONE: Allow more segments? + segments = noise_divisions; + + if ( flags & FBEAM_SINENOISE ) + { + if ( segments < 16 ) + segments = 16; + } + + + IMaterial *pBeamMaterial = pBeamSprite->GetMaterial(); + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + CBeamSegDraw segDraw; + segDraw.Start( pRenderContext, (segments-1)*(numAttachments-1), pBeamMaterial ); + + CEngineSprite *pHaloSprite = (CEngineSprite *)modelinfo->GetModelExtraData( halosprite ); + IMaterial *pHaloMaterial = NULL; + if ( pHaloSprite ) + { + pHaloSprite->SetRenderMode( kRenderGlow ); + pHaloMaterial = pHaloSprite->GetMaterial(); + } + + //----------------------------------------------------------- + // Calculate widthStep if start and end width are different + //----------------------------------------------------------- + float widthStep; + if (startWidth != endWidth) + { + widthStep = (endWidth - startWidth)/numAttachments; + } + else + { + widthStep = 0; + } + + // Calculate total length of beam + float flBeamLength = (attachment[0]-attachment[numAttachments-1]).Length(); + + // What fraction of beam should be faded + float fadeFraction = fadeLength/flBeamLength; + if (fadeFraction > 1) + { + fadeFraction = 1; + } + //--------------------------------------------------------------- + // Go through each attachment drawing spline beams between them + //--------------------------------------------------------------- + Vector vLastPoint(0,0,0); + Vector pPre; // attachment point before the current beam + Vector pStart; // start of current beam + Vector pEnd; // end of current beam + Vector pNext; // attachment point after the current beam + + for (int j=0;j= numAttachments-1) + { + VectorCopy(attachment[j+1],pNext); + } + else + { + VectorCopy(attachment[j+2],pNext); + } + + Vector vDelta; + VectorSubtract(pEnd,pStart,vDelta); + length = VectorLength( vDelta ) * 0.01; + if ( length < 0.5 ) // Don't lose all of the noise/texture on short beams + length = 0.5; + div = 1.0 / (segments-1); + + // UNDONE: Expose texture length scale factor to control "fuzziness" + vStep = length*div; // Texture length texels per space pixel + + // UNDONE: Expose this paramter as well(3.5)? Texture scroll rate along beam + vLast = fmod(freq*speed,1); // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + + if ( flags & FBEAM_SINENOISE ) + { + scale = scale * 100; + length = segments * (1.0/10); + } + else + scale = scale * length; + + // ----------------------------------------------------------------------------- + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + // ----------------------------------------------------------------------------- + noiseStep = (int)((float)(noise_divisions-1) * div * 65536.0f); + noiseIndex = noiseStep; + + if ( flags & FBEAM_SINENOISE ) + noiseIndex = 0; + + brightness = 1.0; + if ( flags & FBEAM_SHADEIN ) + brightness = 0; + + BeamSeg_t seg; + seg.m_flAlpha = 1; + + VectorScale( color, brightness, scaledColor ); + seg.m_vColor.Init( scaledColor[0], scaledColor[1], scaledColor[2] ); + + + // ------------------------------------------------- + // Calc start and end widths for this segment + // ------------------------------------------------- + float startSegWidth = startWidth + (widthStep*j); + float endSegWidth = startWidth + (widthStep*(j+1)); + + // ------------------------------------------------- + // Now draw each segment + // ------------------------------------------------- + float fBestFraction = -1; + float bestDot = 0; + for (int i = 1; i < segments; i++ ) + { + fraction = i * div; + + // Fade in our out beam to fadeLength + // BUG BUG: should be based on total lengh of beam not this particular fraction + if ( flags & FBEAM_SHADEIN ) + { + brightness = fraction/fadeFraction; + if (brightness > 1) + { + brightness = 1; + } + } + else if ( flags & FBEAM_SHADEOUT ) + { + float fadeFraction = fadeLength/length; + brightness = 1.0 - (fraction/fadeFraction); + if (brightness < 0) + { + brightness = 0; + } + } + + // ----------------------------------------------------------- + // Calculate spline position + // ----------------------------------------------------------- + Vector vTarget(0,0,0); + + Catmull_Rom_Spline(pPre, pStart, pEnd, pNext, fraction, vTarget ); + + seg.m_vPos[0] = vTarget.x; + seg.m_vPos[1] = vTarget.y; + seg.m_vPos[2] = vTarget.z; + + // -------------------------------------------------------------- + // Keep track of segment most facing the player for halo effect + // -------------------------------------------------------------- + if (pHaloMaterial) + { + Vector vBeamDir1; + VectorSubtract(seg.m_vPos,vLastPoint,vBeamDir1); + VectorNormalize(vBeamDir1); + + Vector vLookDir; + VectorSubtract(CurrentViewOrigin(),seg.m_vPos,vLookDir); + VectorNormalize(vLookDir); + + float dotpr = fabs(DotProduct(vBeamDir1,vLookDir)); + static float thresh = 0.85; + if (dotpr > thresh && dotpr > bestDot) + { + bestDot = dotpr; + fBestFraction = fraction; + } + VectorCopy(seg.m_vPos,vLastPoint); + } + + + // ---------------------- + // Distort using noise + // ---------------------- + if ( scale != 0 ) + { + factor = prgNoise[noiseIndex>>16] * scale; + if ( flags & FBEAM_SINENOISE ) + { + float s, c; + SinCos( fraction*M_PI*length + freq, &s, &c ); + VectorMA( seg.m_vPos, factor * s, CurrentViewUp(), seg.m_vPos ); + // Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + VectorMA( seg.m_vPos, factor * c, CurrentViewRight(), seg.m_vPos ); + } + else + { + VectorMA( seg.m_vPos, factor, CurrentViewUp(), seg.m_vPos ); + // Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + factor = prgNoise[noiseIndex>>16] * scale * cos(fraction*M_PI*3+freq); + VectorMA( seg.m_vPos, factor, CurrentViewRight(), seg.m_vPos ); + } + } + + + // Scale width if non-zero spread + if (startWidth != endWidth) + seg.m_flWidth = ((fraction*(endSegWidth-startSegWidth))+startSegWidth)*2; + else + seg.m_flWidth = startWidth*2; + + seg.m_flTexCoord = vLast; + segDraw.NextSeg( &seg ); + + vLast += vStep; // Advance texture scroll (v axis only) + noiseIndex += noiseStep; + } + + + // -------------------------------------------------------------- + // Draw halo on segment most facing the player + // -------------------------------------------------------------- + if (false&&pHaloMaterial) + { + Vector vHaloPos(0,0,0); + if (bestDot != 0) + { + Catmull_Rom_Spline(pPre, pStart, pEnd, pNext, fBestFraction, vHaloPos); + } + else + { + Vector vBeamDir1; + VectorSubtract(pStart,pEnd,vBeamDir1); + VectorNormalize(vBeamDir1); + + Vector vLookDir; + VectorSubtract(CurrentViewOrigin(),pStart,vLookDir); + VectorNormalize(vLookDir); + + bestDot = fabs(DotProduct(vBeamDir1,vLookDir)); + static float thresh = 0.85; + if (bestDot > thresh) + { + fBestFraction = 0.5; + VectorAdd(pStart,pEnd,vHaloPos); + VectorScale(vHaloPos,0.5,vHaloPos); + } + } + if (fBestFraction > 0) + { + float fade = pow(bestDot,60); + if (fade > 1.0) fade = 1.0; + float haloColor[3]; + VectorScale( color, fade, haloColor ); + pRenderContext->Bind(pHaloMaterial); + float curWidth = (fBestFraction*(endSegWidth-startSegWidth))+startSegWidth; + DrawHalo(pHaloMaterial,vHaloPos,flHaloScale*curWidth/endWidth,haloColor, flHDRColorScale); + } + } + } + + segDraw.End(); + + // ------------------------ + // Draw halo at end of beam + // ------------------------ + if (pHaloMaterial) + { + pRenderContext->Bind(pHaloMaterial); + DrawHalo(pHaloMaterial,pEnd,flHaloScale,scaledColor, flHDRColorScale); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *spritemodel - +// frame - +// rendermode - +// source - +// scale - +// *color - +//----------------------------------------------------------------------------- +void BeamDrawHalo( const model_t* spritemodel, float frame, int rendermode, + const Vector& source, float scale, float* color, float flHDRColorScale ) +{ + CEngineSprite *pSprite = Draw_SetSpriteTexture( spritemodel, frame, rendermode ); + if ( !pSprite ) + return; + + DrawHalo( pSprite->GetMaterial(), source, scale, color, flHDRColorScale ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : noise_divisions - +// *prgNoise - +// *spritemodel - +// frame - +// rendermode - +// source - +// delta - +// width - +// scale - +// freq - +// speed - +// segments - +// *color - +//----------------------------------------------------------------------------- +void DrawDisk( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, const Vector& delta, + float width, float scale, float freq, float speed, int segments, float* color, float flHDRColorScale ) +{ + int i; + float div, length, fraction, vLast, vStep; + Vector point; + float w; + static unsigned int nHDRColorScaleCache = 0; + + CEngineSprite *pSprite = Draw_SetSpriteTexture( spritemodel, frame, rendermode ); + if ( !pSprite ) + return; + + if ( segments < 2 ) + return; + + IMaterial *pMaterial = pSprite->GetMaterial(); + if( pMaterial ) + { + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + if ( segments > noise_divisions ) // UNDONE: Allow more segments? + segments = noise_divisions; + + length = VectorLength( delta ) * 0.01; + if ( length < 0.5 ) // Don't lose all of the noise/texture on short beams + length = 0.5; + div = 1.0 / (segments-1); + + // UNDONE: Expose texture length scale factor to control "fuzziness" + vStep = length*div; // Texture length texels per space pixel + + // UNDONE: Expose this paramter as well(3.5)? Texture scroll rate along beam + vLast = fmod(freq*speed,1); // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + scale = scale * length; + + w = freq * delta[2]; + + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, (segments - 1) * 2 ); + + // NOTE: We must force the degenerate triangles to be on the edge + for ( i = 0; i < segments; i++ ) + { + float s, c; + fraction = i * div; + + point[0] = source[0]; + point[1] = source[1]; + point[2] = source[2]; + + meshBuilder.Color3fv( color ); + meshBuilder.TexCoord2f( 0, 1.0, vLast ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + SinCos( fraction * 2 * M_PI, &s, &c ); + point[0] = s * w + source[0]; + point[1] = c * w + source[1]; + point[2] = source[2]; + + meshBuilder.Color3fv( color ); + meshBuilder.TexCoord2f( 0, 0.0, vLast ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + vLast += vStep; // Advance texture scroll (v axis only) + } + + meshBuilder.End( ); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : noise_divisions - +// *prgNoise - +// *spritemodel - +// frame - +// rendermode - +// source - +// delta - +// width - +// scale - +// freq - +// speed - +// segments - +// *color - +//----------------------------------------------------------------------------- +void DrawCylinder( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, const Vector& delta, + float width, float scale, float freq, float speed, int segments, + float* color, float flHDRColorScale ) +{ + int i; + float div, length, fraction, vLast, vStep; + Vector point; + + CEngineSprite *pSprite = Draw_SetSpriteTexture( spritemodel, frame, rendermode ); + if ( !pSprite ) + return; + + if ( segments < 2 ) + return; + + IMaterial *pMaterial = pSprite->GetMaterial(); + if( pMaterial ) + { + static unsigned int nHDRColorScaleCache = 0; + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + if ( segments > noise_divisions ) // UNDONE: Allow more segments? + segments = noise_divisions; + + length = VectorLength( delta ) * 0.01; + if ( length < 0.5 ) // Don't lose all of the noise/texture on short beams + length = 0.5; + div = 1.0 / (segments-1); + + // UNDONE: Expose texture length scale factor to control "fuzziness" + vStep = length*div; // Texture length texels per space pixel + + // UNDONE: Expose this paramter as well(3.5)? Texture scroll rate along beam + vLast = fmod(freq*speed,1); // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + scale = scale * length; + + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, (segments - 1) * 2 ); + + float radius = delta[2]; + for ( i = 0; i < segments; i++ ) + { + float s, c; + fraction = i * div; + SinCos( fraction * 2 * M_PI, &s, &c ); + + point[0] = s * freq * radius + source[0]; + point[1] = c * freq * radius + source[1]; + point[2] = source[2] + width; + + meshBuilder.Color3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.TexCoord2f( 0, 1.0f, vLast ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + point[0] = s * freq * (radius + width) + source[0]; + point[1] = c * freq * (radius + width) + source[1]; + point[2] = source[2] - width; + + meshBuilder.Color3fv( color ); + meshBuilder.TexCoord2f( 0, 0.0f, vLast ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + vLast += vStep; // Advance texture scroll (v axis only) + } + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : noise_divisions - +// *prgNoise - +// (*pfnNoise - +//----------------------------------------------------------------------------- +void DrawRing( int noise_divisions, float *prgNoise, void (*pfnNoise)( float *noise, int divs, float scale ), + const model_t* spritemodel, float frame, int rendermode, + const Vector& source, const Vector& delta, float width, + float amplitude, float freq, float speed, int segments, float *color, float flHDRColorScale ) +{ + int i, j, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep; + Vector last1, last2, point, screen, screenLast(0,0,0), tmp, normal; + Vector center, xaxis, yaxis, zaxis; + float radius, x, y, scale; + Vector d; + + CEngineSprite *pSprite = Draw_SetSpriteTexture( spritemodel, frame, rendermode ); + if ( !pSprite ) + return; + + IMaterial *pMaterial = pSprite->GetMaterial(); + if( pMaterial ) + { + static unsigned int nHDRColorScaleCache = 0; + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + VectorCopy( delta, d ); + + if ( segments < 2 ) + return; + + segments = segments * M_PI; + + if ( segments > noise_divisions * 8 ) // UNDONE: Allow more segments? + segments = noise_divisions * 8; + + length = VectorLength( d ) * 0.01 * M_PI; + if ( length < 0.5 ) // Don't lose all of the noise/texture on short beams + length = 0.5; + div = 1.0 / (segments-1); + + // UNDONE: Expose texture length scale factor to control "fuzziness" + vStep = length*div/8.0; // Texture length texels per space pixel + + // UNDONE: Expose this paramter as well(3.5)? Texture scroll rate along beam + vLast = fmod(freq*speed,1); // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + scale = amplitude * length / 8.0; + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((noise_divisions-1) * div * 65536.0) * 8; + noiseIndex = 0; + + VectorScale( d, 0.5, d ); + VectorAdd( source, d, center ); + zaxis[0] = 0; zaxis[1] = 0; zaxis[2] = 1; + + VectorCopy( d, xaxis ); + radius = VectorLength( xaxis ); + + // cull beamring + // -------------------------------- + // Compute box center +/- radius + last1[0] = radius; + last1[1] = radius; + last1[2] = scale; + VectorAdd( center, last1, tmp ); // maxs + VectorSubtract( center, last1, screen ); // mins + + // Is that box in PVS && frustum? + if ( !engine->IsBoxVisible( screen, tmp ) || engine->CullBox( screen, tmp ) ) + { + return; + } + + yaxis[0] = xaxis[1]; yaxis[1] = -xaxis[0]; yaxis[2] = 0; + VectorNormalize( yaxis ); + VectorScale( yaxis, radius, yaxis ); + + j = segments / 8; + + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, (segments) * 2 ); + + for ( i = 0; i < segments + 1; i++ ) + { + fraction = i * div; + SinCos( fraction * 2 * M_PI, &x, &y ); + + point[0] = xaxis[0] * x + yaxis[0] * y + center[0]; + point[1] = xaxis[1] * x + yaxis[1] * y + center[1]; + point[2] = xaxis[2] * x + yaxis[2] * y + center[2]; + + // Distort using noise + if ( scale != 0.0f ) + { + factor = prgNoise[(noiseIndex>>16) & 0x7F] * scale; + VectorMA( point, factor, CurrentViewUp(), point ); + + // Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + factor = prgNoise[(noiseIndex>>16) & 0x7F] * scale * cos(fraction*M_PI*3*8+freq); + VectorMA( point, factor, CurrentViewRight(), point ); + } + + // Transform point into screen space + ScreenTransform( point, screen ); + + if (i != 0) + { + // Build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + // We don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + VectorScale( CurrentViewUp(), tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorMA( normal, -tmp[1], CurrentViewRight(), normal ); + + // Make a wide line + VectorMA( point, width, normal, last1 ); + VectorMA( point, -width, normal, last2 ); + + vLast += vStep; // Advance texture scroll (v axis only) + meshBuilder.Color3fv( color ); + meshBuilder.TexCoord2f( 0, 1.0f, vLast ); + meshBuilder.Position3fv( last2.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color3fv( color ); + meshBuilder.TexCoord2f( 0, 0.0f, vLast ); + meshBuilder.Position3fv( last1.Base() ); + meshBuilder.AdvanceVertex(); + } + VectorCopy( screen, screenLast ); + noiseIndex += noiseStep; + + j--; + if (j == 0 && amplitude != 0 ) + { + j = segments / 8; + (*pfnNoise)( prgNoise, noise_divisions, 1.0f ); + } + } + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : spritemodel - +// *pHead - +// delta - +// *screen - +// *screenLast - +// die - +// source - +// flags - +// width - +// amplitude - +// freq - +// *color - +//----------------------------------------------------------------------------- +void DrawBeamFollow( const model_t* spritemodel, BeamTrail_t* pHead, int frame, int rendermode, + Vector& delta, Vector& screen, Vector& screenLast, float die, + const Vector& source, int flags, float width, float amplitude, + float freq, float* color, float flHDRColorScale ) +{ + float fraction; + float div; + float vLast = 0.0; + float vStep = 1.0; + Vector last1, last2, tmp, normal; + float scaledColor[3]; + + CEngineSprite *pSprite = Draw_SetSpriteTexture( spritemodel, frame, rendermode ); + if ( !pSprite ) + return; + + IMaterial *pMaterial = pSprite->GetMaterial(); + if( pMaterial ) + { + static unsigned int nHDRColorScaleCache = 0; + IMaterialVar *pHDRColorScaleVar = pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetFloatValue( flHDRColorScale ); + } + } + + // UNDONE: This won't work, screen and screenLast must be extrapolated here to fix the + // first beam segment for this trail + + // Build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + // We don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + VectorScale( CurrentViewUp(), tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorMA( normal, -tmp[1], CurrentViewRight(), normal ); + + // Make a wide line + VectorMA( delta, width, normal, last1 ); + VectorMA( delta, -width, normal, last2 ); + + div = 1.0 / amplitude; + fraction = ( die - gpGlobals->curtime ) * div; + unsigned char nColor[3]; + + VectorScale( color, fraction, scaledColor ); + nColor[0] = (unsigned char)clamp( (int)(scaledColor[0] * 255.0f), 0, 255 ); + nColor[1] = (unsigned char)clamp( (int)(scaledColor[1] * 255.0f), 0, 255 ); + nColor[2] = (unsigned char)clamp( (int)(scaledColor[2] * 255.0f), 0, 255 ); + + // need to count the segments + int count = 0; + BeamTrail_t* pTraverse = pHead; + while ( pTraverse ) + { + ++count; + pTraverse = pTraverse->next; + } + + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, count ); + + while (pHead) + { + // Msg("%.2f ", fraction ); + meshBuilder.Position3fv( last1.Base() ); + meshBuilder.Color3ubv( nColor ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( last2.Base() ); + meshBuilder.Color3ubv( nColor ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + // Transform point into screen space + ScreenTransform( pHead->org, screen ); + // Build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + // We don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + VectorScale( CurrentViewUp(), tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorMA( normal, -tmp[1], CurrentViewRight(), normal ); + + // Make a wide line + VectorMA( pHead->org, width, normal, last1 ); + VectorMA( pHead->org, -width, normal, last2 ); + + vLast += vStep; // Advance texture scroll (v axis only) + + if (pHead->next != NULL) + { + fraction = (pHead->die - gpGlobals->curtime) * div; + VectorScale( color, fraction, scaledColor ); + nColor[0] = (unsigned char)clamp( (int)(scaledColor[0] * 255.0f), 0, 255 ); + nColor[1] = (unsigned char)clamp( (int)(scaledColor[1] * 255.0f), 0, 255 ); + nColor[2] = (unsigned char)clamp( (int)(scaledColor[2] * 255.0f), 0, 255 ); + } + else + { + fraction = 0.0; + nColor[0] = nColor[1] = nColor[2] = 0; + } + + meshBuilder.Position3fv( last2.Base() ); + meshBuilder.Color3ubv( nColor ); + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( last1.Base() ); + meshBuilder.Color3ubv( nColor ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.AdvanceVertex(); + + VectorCopy( screen, screenLast ); + + pHead = pHead->next; + } + + meshBuilder.End(); + pMesh->Draw(); +} + + +/* +P0 = start +P1 = control +P2 = end +P(t) = (1-t)^2 * P0 + 2t(1-t)*P1 + t^2 * P2 +*/ +void DrawBeamQuadratic( const Vector &start, const Vector &control, const Vector &end, float width, const Vector &color, float scrollOffset, float flHDRColorScale ) +{ + int subdivisions = 16; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + CBeamSegDraw beamDraw; + beamDraw.Start( pRenderContext, subdivisions+1, NULL ); + + BeamSeg_t seg; + seg.m_flAlpha = 1.0; + seg.m_flWidth = width; + + float t = 0; + float u = fmod( scrollOffset, 1 ); + float dt = 1.0 / (float)subdivisions; + for( int i = 0; i <= subdivisions; i++, t += dt ) + { + float omt = (1-t); + float p0 = omt*omt; + float p1 = 2*t*omt; + float p2 = t*t; + + seg.m_vPos = p0 * start + p1 * control + p2 * end; + seg.m_flTexCoord = u - t; + if ( i == 0 || i == subdivisions ) + { + // HACK: fade out the ends a bit + seg.m_vColor = vec3_origin; + } + else + { + seg.m_vColor = color; + } + beamDraw.NextSeg( &seg ); + } + + beamDraw.End(); +} diff --git a/game/client/beamdraw.h b/game/client/beamdraw.h new file mode 100644 index 00000000..4cfbb7cd --- /dev/null +++ b/game/client/beamdraw.h @@ -0,0 +1,173 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#if !defined( BEAMDRAW_H ) +#define BEAMDRAW_H +#ifdef _WIN32 +#pragma once +#endif + +#include "materialsystem/imaterial.h" +#include "materialsystem/imesh.h" +#include "mathlib/vector.h" +#include "tier2/beamsegdraw.h" +#include "c_pixel_visibility.h" + +#define NOISE_DIVISIONS 128 + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- + +struct model_t; +struct BeamTrail_t; + +//----------------------------------------------------------------------------- +// Purpose: Beams fill out this data structure +// This is also used for rendering +//----------------------------------------------------------------------------- + +class Beam_t : public CDefaultClientRenderable +{ +public: + Beam_t(); + + // Methods of IClientRenderable + virtual const Vector& GetRenderOrigin( void ); + virtual const QAngle& GetRenderAngles( void ); + virtual const matrix3x4_t &RenderableToWorldTransform(); + virtual void GetRenderBounds( Vector& mins, Vector& maxs ); + virtual bool ShouldDraw( void ); + virtual bool IsTransparent( void ); + virtual int DrawModel( int flags ); + virtual void ComputeFxBlend( ); + virtual int GetFxBlend( ); + + // Resets the beam state + void Reset(); + + // Method to computing the bounding box + void ComputeBounds(); + + // Bounding box... + Vector m_Mins; + Vector m_Maxs; + pixelvis_handle_t *m_queryHandleHalo; + float m_haloProxySize; + + // Data is below.. + + // Next beam in list + Beam_t* next; + + // Type of beam + int type; + int flags; + + // Control points for the beam + int numAttachments; + Vector attachment[MAX_BEAM_ENTS]; + Vector delta; + + // 0 .. 1 over lifetime of beam + float t; + float freq; + + // Time when beam should die + float die; + float width; + float endWidth; + float fadeLength; + float amplitude; + float life; + + // Color + float r, g, b; + float brightness; + + // Speed + float speed; + + // Animation + float frameRate; + float frame; + int segments; + + // Attachment entities for the beam + EHANDLE entity[MAX_BEAM_ENTS]; + int attachmentIndex[MAX_BEAM_ENTS]; + + // Model info + int modelIndex; + int haloIndex; + + float haloScale; + int frameCount; + + float rgNoise[NOISE_DIVISIONS+1]; + + // Popcorn trail for beam follows to use + BeamTrail_t* trail; + + // for TE_BEAMRINGPOINT + float start_radius; + float end_radius; + + // for FBEAM_ONLYNOISEONCE + bool m_bCalculatedNoise; + + float m_flHDRColorScale; + +#ifdef PORTAL + bool m_bDrawInMainRender; + bool m_bDrawInPortalRender; +#endif //#ifdef PORTAL +}; + + +int ScreenTransform( const Vector& point, Vector& screen ); + +void DrawSegs( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, const Vector& delta, + float startWidth, float endWidth, float scale, float freq, float speed, int segments, + int flags, float* color, float fadeLength, float flHDRColorScale = 1.0f ); +void DrawTeslaSegs( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, const Vector& delta, + float startWidth, float endWidth, float scale, float freq, float speed, int segments, + int flags, float* color, float fadeLength, float flHDRColorScale = 1.0f ); +void DrawSplineSegs( int noise_divisions, float *prgNoise, + const model_t* beammodel, const model_t* halomodel, float flHaloScale, + float frame, int rendermode, int numAttachments, Vector* attachment, + float startWidth, float endWidth, float scale, float freq, float speed, int segments, + int flags, float* color, float fadeLength, float flHDRColorScale = 1.0f ); +void DrawHalo(IMaterial* pMaterial, const Vector& source, float scale, float const* color, float flHDRColorScale = 1.0f ); +void BeamDrawHalo( const model_t* spritemodel, float frame, int rendermode, const Vector& source, + float scale, float* color, float flHDRColorScale = 1.0f ); +void DrawDisk( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, const Vector& delta, + float width, float scale, float freq, float speed, + int segments, float* color, float flHDRColorScale = 1.0f ); +void DrawCylinder( int noise_divisions, float *prgNoise, const model_t* spritemodel, + float frame, int rendermode, const Vector& source, + const Vector& delta, float width, float scale, float freq, + float speed, int segments, float* color, float flHDRColorScale = 1.0f ); +void DrawRing( int noise_divisions, float *prgNoise, void (*pfnNoise)( float *noise, int divs, float scale ), + const model_t* spritemodel, float frame, int rendermode, + const Vector& source, const Vector& delta, float width, float amplitude, + float freq, float speed, int segments, float* color, float flHDRColorScale = 1.0f ); +void DrawBeamFollow( const model_t* spritemodel, BeamTrail_t* pHead, int frame, int rendermode, Vector& delta, + Vector& screen, Vector& screenLast, float die, const Vector& source, + int flags, float width, float amplitude, float freq, float* color, float flHDRColorScale = 1.0f ); + +void DrawBeamQuadratic( const Vector &start, const Vector &control, const Vector &end, float width, const Vector &color, float scrollOffset, float flHDRColorScale = 1.0f ); +class CEngineSprite *Draw_SetSpriteTexture( const model_t *pSpriteModel, int frame, int rendermode ); + +//----------------------------------------------------------------------------- +// Assumes the material has already been bound +//----------------------------------------------------------------------------- +void DrawSprite( const Vector &vecOrigin, float flWidth, float flHeight, color32 color ); + +#endif // BEAMDRAW_H \ No newline at end of file diff --git a/game/client/bone_merge_cache.cpp b/game/client/bone_merge_cache.cpp new file mode 100644 index 00000000..0792db10 --- /dev/null +++ b/game/client/bone_merge_cache.cpp @@ -0,0 +1,164 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "cbase.h" +#include "bone_merge_cache.h" +#include "bone_setup.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// CBoneMergeCache +//----------------------------------------------------------------------------- + +CBoneMergeCache::CBoneMergeCache() +{ + m_pOwner = NULL; + m_pFollow = NULL; + m_pFollowHdr = NULL; + m_pOwnerHdr = NULL; + m_nFollowBoneSetupMask = 0; +} + +void CBoneMergeCache::Init( C_BaseAnimating *pOwner ) +{ + m_pOwner = pOwner; + m_pFollow = NULL; + m_pFollowHdr = NULL; + m_pOwnerHdr = NULL; + m_nFollowBoneSetupMask = 0; +} + +void CBoneMergeCache::UpdateCache() +{ + if ( !m_pOwner ) + return; + + CStudioHdr *pOwnerHdr = m_pOwner->GetModelPtr(); + if ( !pOwnerHdr ) + return; + + C_BaseAnimating *pTestFollow = m_pOwner->FindFollowedEntity(); + CStudioHdr *pTestHdr = (pTestFollow ? pTestFollow->GetModelPtr() : NULL); + if ( pTestFollow != m_pFollow || pTestHdr != m_pFollowHdr || pOwnerHdr != m_pOwnerHdr ) + { + m_MergedBones.Purge(); + m_BoneMergeBits.Purge(); + + // Update the cache. + if ( pTestFollow && pTestHdr && pOwnerHdr ) + { + m_pFollow = pTestFollow; + m_pFollowHdr = pTestHdr; + m_pOwnerHdr = pOwnerHdr; + + m_BoneMergeBits.SetSize( pOwnerHdr->numbones() / 8 + 1 ); + memset( m_BoneMergeBits.Base(), 0, m_BoneMergeBits.Count() ); + + mstudiobone_t *pOwnerBones = m_pOwnerHdr->pBone( 0 ); + + m_nFollowBoneSetupMask = BONE_USED_BY_BONE_MERGE; + for ( int i = 0; i < m_pOwnerHdr->numbones(); i++ ) + { + int parentBoneIndex = Studio_BoneIndexByName( m_pFollowHdr, pOwnerBones[i].pszName() ); + if ( parentBoneIndex < 0 ) + continue; + + // Add a merged bone here. + CMergedBone mergedBone; + mergedBone.m_iMyBone = i; + mergedBone.m_iParentBone = parentBoneIndex; + m_MergedBones.AddToTail( mergedBone ); + + m_BoneMergeBits[i>>3] |= ( 1 << ( i & 7 ) ); + + if ( ( m_pFollowHdr->boneFlags( parentBoneIndex ) & BONE_USED_BY_BONE_MERGE ) == 0 ) + { + m_nFollowBoneSetupMask = BONE_USED_BY_ANYTHING; + Warning("Performance warning: Merge with '%s'. Mark bone '%s' in model '%s' as being used by bone merge in the .qc!\n", + pOwnerHdr->pszName(), m_pFollowHdr->pBone( parentBoneIndex )->pszName(), m_pFollowHdr->pszName() ); + } + } + + // No merged bones found? Slam the mask to 0 + if ( !m_MergedBones.Count() ) + { + m_nFollowBoneSetupMask = 0; + } + } + else + { + m_pFollow = NULL; + m_pFollowHdr = NULL; + m_pOwnerHdr = NULL; + m_nFollowBoneSetupMask = 0; + } + } +} + +void CBoneMergeCache::MergeMatchingBones( int boneMask ) +{ + UpdateCache(); + + // If this is set, then all the other cache data is set. + if ( !m_pOwnerHdr || m_MergedBones.Count() == 0 ) + return; + + // Have the entity we're following setup its bones. + m_pFollow->SetupBones( NULL, -1, m_nFollowBoneSetupMask, gpGlobals->curtime ); + + // Now copy the bone matrices. + for ( int i=0; i < m_MergedBones.Count(); i++ ) + { + int iOwnerBone = m_MergedBones[i].m_iMyBone; + int iParentBone = m_MergedBones[i].m_iParentBone; + + // Only update bones reference by the bone mask. + if ( !( m_pOwnerHdr->boneFlags( iOwnerBone ) & boneMask ) ) + continue; + + MatrixCopy( m_pFollow->GetBone( iParentBone ), m_pOwner->GetBoneForWrite( iOwnerBone ) ); + } +} + + +bool CBoneMergeCache::GetAimEntOrigin( Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + UpdateCache(); + + // If this is set, then all the other cache data is set. + if ( !m_pOwnerHdr || m_MergedBones.Count() == 0 ) + return false; + + // We want the abs origin such that if we put the entity there, the first merged bone + // will be aligned. This way the entity will be culled in the correct position. + // + // ie: mEntity * mBoneLocal = mFollowBone + // so: mEntity = mFollowBone * Inverse( mBoneLocal ) + // + // Note: the code below doesn't take animation into account. If the attached entity animates + // all over the place, then this won't get the right results. + + // Get mFollowBone. + m_pFollow->SetupBones( NULL, -1, m_nFollowBoneSetupMask, gpGlobals->curtime ); + const matrix3x4_t &mFollowBone = m_pFollow->GetBone( m_MergedBones[0].m_iParentBone ); + + // Get Inverse( mBoneLocal ) + matrix3x4_t mBoneLocal, mBoneLocalInv; + SetupSingleBoneMatrix( m_pOwnerHdr, m_pOwner->GetSequence(), 0, m_MergedBones[0].m_iMyBone, mBoneLocal ); + MatrixInvert( mBoneLocal, mBoneLocalInv ); + + // Now calculate mEntity = mFollowBone * Inverse( mBoneLocal ) + matrix3x4_t mEntity; + ConcatTransforms( mFollowBone, mBoneLocalInv, mEntity ); + MatrixAngles( mEntity, *pAbsAngles, *pAbsOrigin ); + + return true; +} + diff --git a/game/client/bone_merge_cache.h b/game/client/bone_merge_cache.h new file mode 100644 index 00000000..7dafb2b7 --- /dev/null +++ b/game/client/bone_merge_cache.h @@ -0,0 +1,79 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef BONE_MERGE_CACHE_H +#define BONE_MERGE_CACHE_H +#ifdef _WIN32 +#pragma once +#endif + + +class C_BaseAnimating; +class CStudioHdr; + + +#include "mathlib/vector.h" + + +class CBoneMergeCache +{ +public: + + CBoneMergeCache(); + + void Init( C_BaseAnimating *pOwner ); + + // Updates the lookups that let it merge bones quickly. + void UpdateCache(); + + // This copies the transform from all bones in the followed entity that have + // names that match our bones. + void MergeMatchingBones( int boneMask ); + + // Returns true if the specified bone is one that gets merged in MergeMatchingBones. + int CBoneMergeCache::IsBoneMerged( int iBone ) const; + + // Gets the origin for the first merge bone on the parent. + bool GetAimEntOrigin( Vector *pAbsOrigin, QAngle *pAbsAngles ); + + +private: + + // This is the entity that we're keeping the cache updated for. + C_BaseAnimating *m_pOwner; + + // All the cache data is based off these. When they change, the cache data is regenerated. + // These are either all valid pointers or all NULL. + C_BaseAnimating *m_pFollow; + CStudioHdr *m_pFollowHdr; + CStudioHdr *m_pOwnerHdr; + + // This is the mask we need to use to set up bones on the followed entity to do the bone merge + int m_nFollowBoneSetupMask; + + // Cache data. + class CMergedBone + { + public: + unsigned short m_iMyBone; + unsigned short m_iParentBone; + }; + + CUtlVector m_MergedBones; + CUtlVector m_BoneMergeBits; // One bit for each bone. The bit is set if the bone gets merged. +}; + + +inline int CBoneMergeCache::IsBoneMerged( int iBone ) const +{ + if ( m_pOwnerHdr ) + return m_BoneMergeBits[iBone >> 3] & ( 1 << ( iBone & 7 ) ); + else + return 0; +} + + +#endif // BONE_MERGE_CACHE_H diff --git a/game/client/bonetoworldarray.h b/game/client/bonetoworldarray.h new file mode 100644 index 00000000..b51526e5 --- /dev/null +++ b/game/client/bonetoworldarray.h @@ -0,0 +1,70 @@ +//========== Copyright © 2006, Valve Corporation, All rights reserved. ======== +// +// Purpose: +// +//============================================================================= + +#ifndef BONETOWORLDARRAY_H +#define BONETOWORLDARRAY_H + +#include "tier0/tslist.h" + +#if defined( _WIN32 ) +#pragma once +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template +class CBoneToWorldArrays +{ +public: + enum + { + ALIGNMENT = 128, + }; + + CBoneToWorldArrays() + { + const int SIZE_ARRAY = AlignValue( sizeof(matrix3x4_t) * MAXSTUDIOBONES, ALIGNMENT ); + m_pBase = (matrix3x4_t *)_aligned_malloc( SIZE_ARRAY * NUM_ARRAYS, ALIGNMENT ); + for ( int i = 0; i < NUM_ARRAYS; i++ ) + { + matrix3x4_t *pArray = (matrix3x4_t *)((byte *)m_pBase + SIZE_ARRAY * i); + Assert( (size_t)pArray % ALIGNMENT == 0 ); + Free( pArray ); + } + } + + ~CBoneToWorldArrays() + { + _aligned_free( m_pBase ); + } + + int NumArrays() + { + return NUM_ARRAYS; + } + + matrix3x4_t *Alloc( bool bBlock = true ) + { + TSLNodeBase_t *p; + while ( ( p = m_Free.Pop() ) == NULL && bBlock ) + { + ThreadPause(); + } + return (matrix3x4_t *)p; + } + + void Free( matrix3x4_t *p ) + { + m_Free.Push( (TSLNodeBase_t *) p ); + } + +private: + CTSListBase m_Free; + matrix3x4_t *m_pBase; +}; + +#endif // BONETOWORLDARRAY_H diff --git a/game/client/c_ai_basehumanoid.cpp b/game/client/c_ai_basehumanoid.cpp new file mode 100644 index 00000000..58881050 --- /dev/null +++ b/game/client/c_ai_basehumanoid.cpp @@ -0,0 +1,169 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if 0 + +class C_AI_BaseHumanoid : public C_AI_BaseNPC +{ +public: + DECLARE_CLASS( C_AI_BaseHumanoid, C_AI_BaseNPC ); + DECLARE_CLIENTCLASS(); + + C_AI_BaseHumanoid(); + + // model specific + virtual bool Interpolate( float currentTime ); + virtual void StandardBlendingRules( CStudioHdr *pStudioHdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ); + + float m_recanimtime[3]; + AnimationLayer_t m_Layer[4][3]; +}; + + + +C_AI_BaseHumanoid::C_AI_BaseHumanoid() +{ + memset(m_recanimtime, 0, sizeof(m_recanimtime)); + memset(m_Layer, 0, sizeof(m_Layer)); +} + + +BEGIN_RECV_TABLE_NOBASE(AnimationLayer_t, DT_Animationlayer) + RecvPropInt(RECVINFO_NAME(nSequence,sequence)), + RecvPropFloat(RECVINFO_NAME(flCycle,cycle)), + RecvPropFloat(RECVINFO_NAME(flPlaybackrate,playbackrate)), + RecvPropFloat(RECVINFO_NAME(flWeight,weight)) +END_RECV_TABLE() + + + +IMPLEMENT_CLIENTCLASS_DT(C_AI_BaseHumanoid, DT_BaseHumanoid, CAI_BaseHumanoid) + /* + RecvPropDataTable(RECVINFO_DTNAME(m_Layer[0][2],m_Layer0),0, &REFERENCE_RECV_TABLE(DT_Animationlayer)), + RecvPropDataTable(RECVINFO_DTNAME(m_Layer[1][2],m_Layer1),0, &REFERENCE_RECV_TABLE(DT_Animationlayer)), + RecvPropDataTable(RECVINFO_DTNAME(m_Layer[2][2],m_Layer2),0, &REFERENCE_RECV_TABLE(DT_Animationlayer)), + RecvPropDataTable(RECVINFO_DTNAME(m_Layer[3][2],m_Layer3),0, &REFERENCE_RECV_TABLE(DT_Animationlayer)), + */ + RecvPropInt(RECVINFO_NAME(m_Layer[0][2].nSequence,sequence0)), + RecvPropFloat(RECVINFO_NAME(m_Layer[0][2].flCycle,cycle0)), + RecvPropFloat(RECVINFO_NAME(m_Layer[0][2].flPlaybackrate,playbackrate0)), + RecvPropFloat(RECVINFO_NAME(m_Layer[0][2].flWeight,weight0)), + RecvPropInt(RECVINFO_NAME(m_Layer[1][2].nSequence,sequence1)), + RecvPropFloat(RECVINFO_NAME(m_Layer[1][2].flCycle,cycle1)), + RecvPropFloat(RECVINFO_NAME(m_Layer[1][2].flPlaybackrate,playbackrate1)), + RecvPropFloat(RECVINFO_NAME(m_Layer[1][2].flWeight,weight1)), + RecvPropInt(RECVINFO_NAME(m_Layer[2][2].nSequence,sequence2)), + RecvPropFloat(RECVINFO_NAME(m_Layer[2][2].flCycle,cycle2)), + RecvPropFloat(RECVINFO_NAME(m_Layer[2][2].flPlaybackrate,playbackrate2)), + RecvPropFloat(RECVINFO_NAME(m_Layer[2][2].flWeight,weight2)), + RecvPropInt(RECVINFO_NAME(m_Layer[3][2].nSequence,sequence3)), + RecvPropFloat(RECVINFO_NAME(m_Layer[3][2].flCycle,cycle3)), + RecvPropFloat(RECVINFO_NAME(m_Layer[3][2].flPlaybackrate,playbackrate3)), + RecvPropFloat(RECVINFO_NAME(m_Layer[3][2].flWeight,weight3)) +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_AI_BaseHumanoid::StandardBlendingRules( CStudioHdr *pStudioHdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) +{ + VPROF( "C_AI_BaseHumanoid::StandardBlendingRules" ); + + BaseClass::StandardBlendingRules( pStudioHdr, pos, q, currentTime, boneMask ); + + if ( !hdr ) + { + return; + } + +#if 0 + float poseparam[MAXSTUDIOPOSEPARAM]; + + if ( GetSequence() >= hdr->numseq ) + { + SetSequence( 0 ); + } + + // interpolate pose parameters + for (int i = 0; i < hdr->numposeparameters; i++) + { + poseparam[ i ] = m_flPoseParameter[i]; + } + + // build root animation + float fCycle = GetCycle(); + CalcPose( hdr, NULL, pos, q, GetSequence(), fCycle, poseparam ); + + // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%30s %6.2f : %6.2f", hdr->pSeqdesc( GetSequence() )->pszLabel( ), fCycle, 1.0 ); + + MaintainSequenceTransitions( hdr, fCycle, poseparam, pos, q, boneMask ); + +#if 1 + for (i = 0; i < 4; i++) + { + if (m_Layer[i][2].nSequence != m_Layer[i][1].nSequence) + { + if (m_Layer[i][2].flWeight > 0.5) m_Layer[i][1].flWeight = 1.0; else m_Layer[i][1].flWeight = 0; + } + } +#endif + +#if 1 + for (i = 0; i < 4; i++) + { + Vector pos2[MAXSTUDIOBONES]; + Quaternion q2[MAXSTUDIOBONES]; + float fWeight = m_Layer[i][1].flWeight * (1 - dadt) + m_Layer[i][2].flWeight * dadt; + + /* + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), -i - 1, 0, + "%2d %6.2f %6.2f : %2d %6.2f %6.2f : %2d %6.2f %6.2f", + m_Layer[i][0].nSequence, m_Layer[i][0].flCycle, m_Layer[i][0].flWeight, + m_Layer[i][1].nSequence, m_Layer[i][1].flCycle, m_Layer[i][1].flWeight, + m_Layer[i][2].nSequence, m_Layer[i][2].flCycle, m_Layer[i][2].flWeight ); + */ + + if (fWeight > 0) + { + mstudioseqdesc_t *pseqdesc = hdr->pSeqdesc( m_Layer[i][2].nSequence ); + + float fCycle = m_Layer[i][2].flCycle; + + // UNDONE: Do IK here. + CalcPose( hdr, NULL, pos2, q2, m_Layer[i][2].nSequence, fCycle, poseparam ); + + if (fWeight > 1) + fWeight = 1; + SlerpBones( hdr, q, pos, pseqdesc, q2, pos2, fWeight ); + + engine->Con_NPrintf( 10 + i, "%30s %6.2f : %6.2f", pseqdesc->pszLabel(), fCycle, fWeight ); + } + else + { + engine->Con_NPrintf( 10 + i, "%30s %6.2f : %6.2f", " ", 0, 0 ); + } + + } +#endif + + CIKContext auto_ik; + auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), gpGlobals->curtime ); + CalcAutoplaySequences( hdr, &auto_ik, pos, q, poseparam, boneMask, currentTime ); + + float controllers[MAXSTUDIOBONECTRLS]; + GetBoneControllers(controllers); + CalcBoneAdj( hdr, pos, q, controllers ); +#endif +} + + +#endif diff --git a/game/client/c_ai_basenpc.cpp b/game/client/c_ai_basenpc.cpp new file mode 100644 index 00000000..f0402235 --- /dev/null +++ b/game/client/c_ai_basenpc.cpp @@ -0,0 +1,174 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_AI_BaseNPC.h" +#include "engine/IVDebugOverlay.h" + +#if defined( HL2_DLL ) || defined( HL2_EPISODIC ) +#include "c_basehlplayer.h" +#endif + +#include "death_pose.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define PING_MAX_TIME 2.0 + +IMPLEMENT_CLIENTCLASS_DT( C_AI_BaseNPC, DT_AI_BaseNPC, CAI_BaseNPC ) + RecvPropInt( RECVINFO( m_lifeState ) ), + RecvPropBool( RECVINFO( m_bPerformAvoidance ) ), + RecvPropBool( RECVINFO( m_bIsMoving ) ), + RecvPropBool( RECVINFO( m_bFadeCorpse ) ), + RecvPropInt( RECVINFO ( m_iDeathPose) ), + RecvPropInt( RECVINFO( m_iDeathFrame) ), + RecvPropInt( RECVINFO( m_iSpeedModRadius ) ), + RecvPropInt( RECVINFO( m_iSpeedModSpeed ) ), + RecvPropInt( RECVINFO( m_bSpeedModActive ) ), + RecvPropBool( RECVINFO( m_bImportanRagdoll ) ), + RecvPropFloat( RECVINFO( m_flTimePingEffect ) ), +END_RECV_TABLE() + +extern ConVar cl_npc_speedmod_intime; + +bool NPC_IsImportantNPC( C_BaseAnimating *pAnimating ) +{ + C_AI_BaseNPC *pBaseNPC = dynamic_cast < C_AI_BaseNPC* > ( pAnimating ); + + if ( pBaseNPC == NULL ) + return false; + + return pBaseNPC->ImportantRagdoll(); +} + +C_AI_BaseNPC::C_AI_BaseNPC() +{ +} + +//----------------------------------------------------------------------------- +// Makes ragdolls ignore npcclip brushes +//----------------------------------------------------------------------------- +unsigned int C_AI_BaseNPC::PhysicsSolidMaskForEntity( void ) const +{ + // This allows ragdolls to move through npcclip brushes + if ( !IsRagdoll() ) + { + return MASK_NPCSOLID; + } + return MASK_SOLID; +} + + +void C_AI_BaseNPC::ClientThink( void ) +{ + BaseClass::ClientThink(); + +#ifdef HL2_DLL + C_BaseHLPlayer *pPlayer = dynamic_cast( C_BasePlayer::GetLocalPlayer() ); + + if ( ShouldModifyPlayerSpeed() == true ) + { + if ( pPlayer ) + { + float flDist = (GetAbsOrigin() - pPlayer->GetAbsOrigin()).LengthSqr(); + + if ( flDist <= GetSpeedModifyRadius() ) + { + if ( pPlayer->m_hClosestNPC ) + { + if ( pPlayer->m_hClosestNPC != this ) + { + float flDistOther = (pPlayer->m_hClosestNPC->GetAbsOrigin() - pPlayer->GetAbsOrigin()).Length(); + + //If I'm closer than the other NPC then replace it with myself. + if ( flDist < flDistOther ) + { + pPlayer->m_hClosestNPC = this; + pPlayer->m_flSpeedModTime = gpGlobals->curtime + cl_npc_speedmod_intime.GetFloat(); + } + } + } + else + { + pPlayer->m_hClosestNPC = this; + pPlayer->m_flSpeedModTime = gpGlobals->curtime + cl_npc_speedmod_intime.GetFloat(); + } + } + } + } +#endif // HL2_DLL + +#ifdef HL2_EPISODIC + C_BaseHLPlayer *pPlayer = dynamic_cast( C_BasePlayer::GetLocalPlayer() ); + + if ( pPlayer && m_flTimePingEffect > gpGlobals->curtime ) + { + float fPingEffectTime = m_flTimePingEffect - gpGlobals->curtime; + + if ( fPingEffectTime > 0.0f ) + { + Vector vRight, vUp; + Vector vMins, vMaxs; + + float fFade; + + if( fPingEffectTime <= 1.0f ) + { + fFade = 1.0f - (1.0f - fPingEffectTime); + } + else + { + fFade = 1.0f; + } + + GetRenderBounds( vMins, vMaxs ); + AngleVectors (pPlayer->GetAbsAngles(), NULL, &vRight, &vUp ); + Vector p1 = GetAbsOrigin() + vRight * vMins.x + vUp * vMins.z; + Vector p2 = GetAbsOrigin() + vRight * vMaxs.x + vUp * vMins.z; + Vector p3 = GetAbsOrigin() + vUp * vMaxs.z; + + int r = 0 * fFade; + int g = 255 * fFade; + int b = 0 * fFade; + + debugoverlay->AddLineOverlay( p1, p2, r, g, b, true, 0.05f ); + debugoverlay->AddLineOverlay( p2, p3, r, g, b, true, 0.05f ); + debugoverlay->AddLineOverlay( p3, p1, r, g, b, true, 0.05f ); + } + } +#endif +} + +void C_AI_BaseNPC::OnDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnDataChanged( type ); + + if ( ( ShouldModifyPlayerSpeed() == true ) || ( m_flTimePingEffect > gpGlobals->curtime ) ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +} + +void C_AI_BaseNPC::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ) +{ + ForceSetupBonesAtTime( pDeltaBones0, gpGlobals->curtime - boneDt ); + GetRagdollCurSequenceWithDeathPose( this, pDeltaBones1, gpGlobals->curtime, m_iDeathPose, m_iDeathFrame ); + float ragdollCreateTime = PhysGetSyncCreateTime(); + if ( ragdollCreateTime != gpGlobals->curtime ) + { + // The next simulation frame begins before the end of this frame + // so initialize the ragdoll at that time so that it will reach the current + // position at curtime. Otherwise the ragdoll will simulate forward from curtime + // and pop into the future a bit at this point of transition + ForceSetupBonesAtTime( pCurrentBones, ragdollCreateTime ); + } + else + { + SetupBones( pCurrentBones, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); + } +} + diff --git a/game/client/c_ai_basenpc.h b/game/client/c_ai_basenpc.h new file mode 100644 index 00000000..0734c7bc --- /dev/null +++ b/game/client/c_ai_basenpc.h @@ -0,0 +1,61 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_AI_BASENPC_H +#define C_AI_BASENPC_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "c_basecombatcharacter.h" + +// NOTE: MOved all controller code into c_basestudiomodel +class C_AI_BaseNPC : public C_BaseCombatCharacter +{ + DECLARE_CLASS( C_AI_BaseNPC, C_BaseCombatCharacter ); + +public: + DECLARE_CLIENTCLASS(); + + C_AI_BaseNPC(); + virtual unsigned int PhysicsSolidMaskForEntity( void ) const; + virtual bool IsNPC( void ) { return true; } + bool IsMoving( void ){ return m_bIsMoving; } + bool ShouldAvoidObstacle( void ){ return m_bPerformAvoidance; } + virtual bool AddRagdollToFadeQueue( void ) { return m_bFadeCorpse; } + + virtual void GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ); + + int GetDeathPose( void ) { return m_iDeathPose; } + + bool ShouldModifyPlayerSpeed( void ) { return m_bSpeedModActive; } + int GetSpeedModifyRadius( void ) { return m_iSpeedModRadius; } + int GetSpeedModifySpeed( void ) { return m_iSpeedModSpeed; } + + void ClientThink( void ); + void OnDataChanged( DataUpdateType_t type ); + bool ImportantRagdoll( void ) { return m_bImportanRagdoll; } + +private: + C_AI_BaseNPC( const C_AI_BaseNPC & ); // not defined, not accessible + float m_flTimePingEffect; + int m_iDeathPose; + int m_iDeathFrame; + + int m_iSpeedModRadius; + int m_iSpeedModSpeed; + + bool m_bPerformAvoidance; + bool m_bIsMoving; + bool m_bFadeCorpse; + bool m_bSpeedModActive; + bool m_bImportanRagdoll; +}; + + +#endif // C_AI_BASENPC_H diff --git a/game/client/c_baseanimating.cpp b/game/client/c_baseanimating.cpp new file mode 100644 index 00000000..6a5f8f21 --- /dev/null +++ b/game/client/c_baseanimating.cpp @@ -0,0 +1,5762 @@ +//===== Copy right © 1996-2005, Valve Corporation, All rights reserved. ==// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_baseanimating.h" +#include "c_Sprite.h" +#include "model_types.h" +#include "bone_setup.h" +#include "ivrenderview.h" +#include "r_efx.h" +#include "dlight.h" +#include "beamdraw.h" +#include "cl_animevent.h" +#include "engine/IEngineSound.h" +#include "c_te_legacytempents.h" +#include "activitylist.h" +#include "animation.h" +#include "tier0/vprof.h" +#include "ClientEffectPrecacheSystem.h" +#include "ieffects.h" +#include "engine/ivmodelinfo.h" +#include "engine/IVDebugOverlay.h" +#include "c_te_effect_dispatch.h" +#include +#include "c_rope.h" +#include "isaverestore.h" +#include "datacache/imdlcache.h" +#include "eventlist.h" +#include "saverestore.h" +#include "physics_saverestore.h" +#include "vphysics/constraints.h" +#include "ragdoll_shared.h" +#include "view.h" +#include "c_ai_basenpc.h" +#include "c_entitydissolve.h" +#include "saverestoretypes.h" +#include "c_fire_smoke.h" +#include "input.h" +#include "soundinfo.h" +#include "tools/bonelist.h" +#include "toolframework/itoolframework.h" +#include "datacache/idatacache.h" +#include "gamestringpool.h" +#include "jigglebones.h" +#include "toolframework_client.h" +#include "vstdlib/jobthread.h" +#include "bonetoworldarray.h" +#include "posedebugger.h" +#include "tier0/ICommandLine.h" +#include "prediction.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar cl_SetupAllBones( "cl_SetupAllBones", "0" ); +ConVar r_sequence_debug( "r_sequence_debug", "" ); + +// If an NPC is moving faster than this, he should play the running footstep sound +const float RUN_SPEED_ESTIMATE_SQR = 150.0f * 150.0f; + +// Removed macro used by shared code stuff +#if defined( CBaseAnimating ) +#undef CBaseAnimating +#endif + + +mstudioevent_t *GetEventIndexForSequence( mstudioseqdesc_t &seqdesc ); + +C_EntityDissolve *DissolveEffect( C_BaseAnimating *pTarget, float flTime ); +C_EntityFlame *FireEffect( C_BaseAnimating *pTarget, C_BaseEntity *pServerFire, float *flScaleEnd, float *flTimeStart, float *flTimeEnd ); +bool NPC_IsImportantNPC( C_BaseAnimating *pAnimating ); +void VCollideWireframe_ChangeCallback( IConVar *pConVar, char const *pOldString, float flOldValue ); + +ConVar vcollide_wireframe( "vcollide_wireframe", "0", FCVAR_CHEAT, "Render physics collision models in wireframe", VCollideWireframe_ChangeCallback ); + +bool C_AnimationLayer::IsActive( void ) +{ + return (m_nOrder != C_BaseAnimatingOverlay::MAX_OVERLAYS); +} + +//----------------------------------------------------------------------------- +// Relative lighting entity +//----------------------------------------------------------------------------- +class C_InfoLightingRelative : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_InfoLightingRelative, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + void GetLightingOffset( matrix3x4_t &offset ); + +private: + EHANDLE m_hLightingLandmark; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_InfoLightingRelative, DT_InfoLightingRelative, CInfoLightingRelative) + RecvPropEHandle(RECVINFO(m_hLightingLandmark)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Relative lighting entity +//----------------------------------------------------------------------------- +void C_InfoLightingRelative::GetLightingOffset( matrix3x4_t &offset ) +{ + if ( m_hLightingLandmark.Get() ) + { + matrix3x4_t matWorldToLandmark; + MatrixInvert( m_hLightingLandmark->EntityToWorldTransform(), matWorldToLandmark ); + ConcatTransforms( EntityToWorldTransform(), matWorldToLandmark, offset ); + } + else + { + SetIdentityMatrix( offset ); + } +} + + +//----------------------------------------------------------------------------- +// Base Animating +//----------------------------------------------------------------------------- + +struct clientanimating_t +{ + C_BaseAnimating *pAnimating; + unsigned int flags; + clientanimating_t(C_BaseAnimating *_pAnim, unsigned int _flags ) : pAnimating(_pAnim), flags(_flags) {} +}; + +const unsigned int FCLIENTANIM_SEQUENCE_CYCLE = 0x00000001; + +static CUtlVector< clientanimating_t > g_ClientSideAnimationList; + +BEGIN_RECV_TABLE_NOBASE( C_BaseAnimating, DT_ServerAnimationData ) + RecvPropFloat(RECVINFO(m_flCycle)), +END_RECV_TABLE() + + +void RecvProxy_Sequence( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + // Have the regular proxy store the data. + RecvProxy_Int32ToInt32( pData, pStruct, pOut ); + + C_BaseAnimating *pAnimating = (C_BaseAnimating *)pStruct; + + if ( !pAnimating ) + return; + + pAnimating->SetReceivedSequence(); + + // render bounds may have changed + pAnimating->UpdateVisibility(); +} + +IMPLEMENT_CLIENTCLASS_DT(C_BaseAnimating, DT_BaseAnimating, CBaseAnimating) + RecvPropInt(RECVINFO(m_nSequence), 0, RecvProxy_Sequence), + RecvPropInt(RECVINFO(m_nForceBone)), + RecvPropVector(RECVINFO(m_vecForce)), + RecvPropInt(RECVINFO(m_nSkin)), + RecvPropInt(RECVINFO(m_nBody)), + RecvPropInt(RECVINFO(m_nHitboxSet)), + RecvPropFloat(RECVINFO(m_flModelWidthScale)), + +// RecvPropArray(RecvPropFloat(RECVINFO(m_flPoseParameter[0])), m_flPoseParameter), + RecvPropArray3(RECVINFO_ARRAY(m_flPoseParameter), RecvPropFloat(RECVINFO(m_flPoseParameter[0])) ), + + RecvPropFloat(RECVINFO(m_flPlaybackRate)), + + RecvPropArray3( RECVINFO_ARRAY(m_flEncodedController), RecvPropFloat(RECVINFO(m_flEncodedController[0]))), + + RecvPropInt( RECVINFO( m_bClientSideAnimation )), + RecvPropInt( RECVINFO( m_bClientSideFrameReset )), + + RecvPropInt( RECVINFO( m_nNewSequenceParity )), + RecvPropInt( RECVINFO( m_nResetEventsParity )), + RecvPropInt( RECVINFO( m_nMuzzleFlashParity ) ), + + RecvPropEHandle(RECVINFO(m_hLightingOrigin)), + RecvPropEHandle(RECVINFO(m_hLightingOriginRelative)), + + RecvPropDataTable( "serveranimdata", 0, 0, &REFERENCE_RECV_TABLE( DT_ServerAnimationData ) ), + + RecvPropFloat( RECVINFO( m_fadeMinDist ) ), + RecvPropFloat( RECVINFO( m_fadeMaxDist ) ), + RecvPropFloat( RECVINFO( m_flFadeScale ) ), + +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_BaseAnimating ) + + DEFINE_PRED_FIELD( m_nSkin, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nBody, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +// DEFINE_PRED_FIELD( m_nHitboxSet, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +// DEFINE_PRED_FIELD( m_flModelWidthScale, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nSequence, FIELD_INTEGER, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ), + DEFINE_PRED_FIELD( m_flPlaybackRate, FIELD_FLOAT, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ), + DEFINE_PRED_FIELD( m_flCycle, FIELD_FLOAT, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ), +// DEFINE_PRED_ARRAY( m_flPoseParameter, FIELD_FLOAT, MAXSTUDIOPOSEPARAM, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_ARRAY_TOL( m_flEncodedController, FIELD_FLOAT, MAXSTUDIOBONECTRLS, FTYPEDESC_INSENDTABLE, 0.02f ), + + DEFINE_FIELD( m_nPrevSequence, FIELD_INTEGER ), + //DEFINE_FIELD( m_flPrevEventCycle, FIELD_FLOAT ), + //DEFINE_FIELD( m_flEventCycle, FIELD_FLOAT ), + //DEFINE_FIELD( m_nEventSequence, FIELD_INTEGER ), + + DEFINE_PRED_FIELD( m_nNewSequenceParity, FIELD_INTEGER, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ), + DEFINE_PRED_FIELD( m_nResetEventsParity, FIELD_INTEGER, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ), + // DEFINE_PRED_FIELD( m_nPrevResetEventsParity, FIELD_INTEGER, 0 ), + + DEFINE_PRED_FIELD( m_nMuzzleFlashParity, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), + //DEFINE_FIELD( m_nOldMuzzleFlashParity, FIELD_CHARACTER ), + + //DEFINE_FIELD( m_nPrevNewSequenceParity, FIELD_INTEGER ), + //DEFINE_FIELD( m_nPrevResetEventsParity, FIELD_INTEGER ), + + // DEFINE_PRED_FIELD( m_vecForce, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ), + // DEFINE_PRED_FIELD( m_nForceBone, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + // DEFINE_PRED_FIELD( m_bClientSideAnimation, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + // DEFINE_PRED_FIELD( m_bClientSideFrameReset, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + + // DEFINE_FIELD( m_pRagdollInfo, RagdollInfo_t ), + // DEFINE_FIELD( m_CachedBones, CUtlVector < CBoneCacheEntry > ), + // DEFINE_FIELD( m_pActualAttachmentAngles, FIELD_VECTOR ), + // DEFINE_FIELD( m_pActualAttachmentOrigin, FIELD_VECTOR ), + + // DEFINE_FIELD( m_animationQueue, CUtlVector < C_AnimationLayer > ), + // DEFINE_FIELD( m_pIk, CIKContext ), + // DEFINE_FIELD( m_bLastClientSideFrameReset, FIELD_BOOLEAN ), + // DEFINE_FIELD( hdr, studiohdr_t ), + // DEFINE_FIELD( m_pRagdoll, IRagdoll ), + // DEFINE_FIELD( m_bStoreRagdollInfo, FIELD_BOOLEAN ), + + // DEFINE_FIELD( C_BaseFlex, m_iEyeAttachment, FIELD_INTEGER ), + +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( client_ragdoll, C_ClientRagdoll ); + +BEGIN_DATADESC( C_ClientRagdoll ) + DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bImportant, FIELD_BOOLEAN ), + DEFINE_FIELD( m_iCurrentFriction, FIELD_INTEGER ), + DEFINE_FIELD( m_iMinFriction, FIELD_INTEGER ), + DEFINE_FIELD( m_iMaxFriction, FIELD_INTEGER ), + DEFINE_FIELD( m_flFrictionModTime, FIELD_FLOAT ), + DEFINE_FIELD( m_flFrictionTime, FIELD_TIME ), + DEFINE_FIELD( m_iFrictionAnimState, FIELD_INTEGER ), + DEFINE_FIELD( m_bReleaseRagdoll, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nBody, FIELD_INTEGER ), + DEFINE_FIELD( m_nSkin, FIELD_INTEGER ), + DEFINE_FIELD( m_nRenderFX, FIELD_CHARACTER ), + DEFINE_FIELD( m_nRenderMode, FIELD_CHARACTER ), + DEFINE_FIELD( m_clrRender, FIELD_COLOR32 ), + DEFINE_FIELD( m_flEffectTime, FIELD_TIME ), + DEFINE_FIELD( m_bFadingOut, FIELD_BOOLEAN ), + + DEFINE_AUTO_ARRAY( m_flScaleEnd, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_flScaleTimeStart, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_flScaleTimeEnd, FIELD_FLOAT ), + DEFINE_EMBEDDEDBYREF( m_pRagdoll ), + +END_DATADESC() + +C_ClientRagdoll::C_ClientRagdoll( bool bRestoring ) +{ + m_iCurrentFriction = 0; + m_iFrictionAnimState = RAGDOLL_FRICTION_NONE; + m_bReleaseRagdoll = false; + m_bFadeOut = false; + m_bFadingOut = false; + m_bImportant = false; + + SetClassname("client_ragdoll"); + + if ( bRestoring == true ) + { + m_pRagdoll = new CRagdoll; + } +} + +void C_ClientRagdoll::OnSave( void ) +{ +} + +void C_ClientRagdoll::OnRestore( void ) +{ + CStudioHdr *hdr = GetModelPtr(); + + if ( hdr == NULL ) + { + const char *pModelName = STRING( GetModelName() ); + SetModel( pModelName ); + + hdr = GetModelPtr(); + + if ( hdr == NULL ) + return; + } + + if ( m_pRagdoll == NULL ) + return; + + ragdoll_t *pRagdollT = m_pRagdoll->GetRagdoll(); + + if ( pRagdollT == NULL || pRagdollT->list[0].pObject == NULL ) + { + m_bReleaseRagdoll = true; + m_pRagdoll = NULL; + Assert( !"Attempted to restore a ragdoll without physobjects!" ); + return; + } + + if ( GetFlags() & FL_DISSOLVING ) + { + DissolveEffect( this, m_flEffectTime ); + } + else if ( GetFlags() & FL_ONFIRE ) + { + C_EntityFlame *pFireChild = dynamic_cast( GetEffectEntity() ); + C_EntityFlame *pNewFireChild = FireEffect( this, pFireChild, m_flScaleEnd, m_flScaleTimeStart, m_flScaleTimeEnd ); + + //Set the new fire child as the new effect entity. + SetEffectEntity( pNewFireChild ); + } + + VPhysicsSetObject( NULL ); + VPhysicsSetObject( pRagdollT->list[0].pObject ); + + SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); + + pRagdollT->list[0].parentIndex = -1; + pRagdollT->list[0].originParentSpace.Init(); + + RagdollActivate( *pRagdollT, modelinfo->GetVCollide( GetModelIndex() ), GetModelIndex(), true ); + RagdollSetupAnimatedFriction( physenv, pRagdollT, GetModelIndex() ); + + m_pRagdoll->BuildRagdollBounds( this ); + + // UNDONE: The shadow & leaf system cleanup should probably be in C_BaseEntity::OnRestore() + // this must be recomputed because the model was NULL when this was set up + RemoveFromLeafSystem(); + AddToLeafSystem( RENDER_GROUP_OPAQUE_ENTITY ); + + DestroyShadow(); + CreateShadow(); + + SetNextClientThink( CLIENT_THINK_ALWAYS ); + + if ( m_bFadeOut == true ) + { + s_RagdollLRU.MoveToTopOfLRU( this, m_bImportant ); + } + + NoteRagdollCreationTick( this ); + + BaseClass::OnRestore(); + + RagdollMoved(); +} + +void C_ClientRagdoll::ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ) +{ + VPROF( "C_ClientRagdoll::ImpactTrace" ); + + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + + if( !pPhysicsObject ) + return; + + Vector dir = pTrace->endpos - pTrace->startpos; + + if ( iDamageType == DMG_BLAST ) + { + dir *= 500; // adjust impact strenght + + // apply force at object mass center + pPhysicsObject->ApplyForceCenter( dir ); + } + else + { + Vector hitpos; + + VectorMA( pTrace->startpos, pTrace->fraction, dir, hitpos ); + VectorNormalize( dir ); + + dir *= 4000; // adjust impact strenght + + // apply force where we hit it + pPhysicsObject->ApplyForceOffset( dir, hitpos ); + } + + m_pRagdoll->ResetRagdollSleepAfterTime(); +} + +ConVar g_debug_ragdoll_visualize( "g_debug_ragdoll_visualize", "0", FCVAR_CHEAT ); + +void C_ClientRagdoll::HandleAnimatedFriction( void ) +{ + if ( m_iFrictionAnimState == RAGDOLL_FRICTION_OFF ) + return; + + ragdoll_t *pRagdollT = NULL; + int iBoneCount = 0; + + if ( m_pRagdoll ) + { + pRagdollT = m_pRagdoll->GetRagdoll(); + iBoneCount = m_pRagdoll->RagdollBoneCount(); + + } + + if ( pRagdollT == NULL ) + return; + + switch ( m_iFrictionAnimState ) + { + case RAGDOLL_FRICTION_NONE: + { + m_iMinFriction = pRagdollT->animfriction.iMinAnimatedFriction; + m_iMaxFriction = pRagdollT->animfriction.iMaxAnimatedFriction; + + if ( m_iMinFriction != 0 || m_iMaxFriction != 0 ) + { + m_iFrictionAnimState = RAGDOLL_FRICTION_IN; + + m_flFrictionModTime = pRagdollT->animfriction.flFrictionTimeIn; + m_flFrictionTime = gpGlobals->curtime + m_flFrictionModTime; + + m_iCurrentFriction = m_iMinFriction; + } + else + { + m_iFrictionAnimState = RAGDOLL_FRICTION_OFF; + } + + break; + } + + case RAGDOLL_FRICTION_IN: + { + float flDeltaTime = (m_flFrictionTime - gpGlobals->curtime); + + m_iCurrentFriction = RemapValClamped( flDeltaTime , m_flFrictionModTime, 0, m_iMinFriction, m_iMaxFriction ); + + if ( flDeltaTime <= 0.0f ) + { + m_flFrictionModTime = pRagdollT->animfriction.flFrictionTimeHold; + m_flFrictionTime = gpGlobals->curtime + m_flFrictionModTime; + m_iFrictionAnimState = RAGDOLL_FRICTION_HOLD; + } + break; + } + + case RAGDOLL_FRICTION_HOLD: + { + if ( m_flFrictionTime < gpGlobals->curtime ) + { + m_flFrictionModTime = pRagdollT->animfriction.flFrictionTimeOut; + m_flFrictionTime = gpGlobals->curtime + m_flFrictionModTime; + m_iFrictionAnimState = RAGDOLL_FRICTION_OUT; + } + + break; + } + + case RAGDOLL_FRICTION_OUT: + { + float flDeltaTime = (m_flFrictionTime - gpGlobals->curtime); + + m_iCurrentFriction = RemapValClamped( flDeltaTime , 0, m_flFrictionModTime, m_iMinFriction, m_iMaxFriction ); + + if ( flDeltaTime <= 0.0f ) + { + m_iFrictionAnimState = RAGDOLL_FRICTION_OFF; + } + + break; + } + } + + for ( int i = 0; i < iBoneCount; i++ ) + { + if ( pRagdollT->list[i].pConstraint ) + pRagdollT->list[i].pConstraint->SetAngularMotor( 0, m_iCurrentFriction ); + } + + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + + if ( pPhysicsObject ) + { + pPhysicsObject->Wake(); + } +} + +ConVar g_ragdoll_fadespeed( "g_ragdoll_fadespeed", "600" ); +ConVar g_ragdoll_lvfadespeed( "g_ragdoll_lvfadespeed", "100" ); + +void C_ClientRagdoll::OnPVSStatusChanged( bool bInPVS ) +{ + if ( bInPVS ) + { + CreateShadow(); + } + else + { + DestroyShadow(); + } +} + +void C_ClientRagdoll::FadeOut( void ) +{ + if ( m_bFadingOut == false ) + { + return; + } + + int iAlpha = GetRenderColor().a; + int iFadeSpeed = ( g_RagdollLVManager.IsLowViolence() ) ? g_ragdoll_lvfadespeed.GetInt() : g_ragdoll_fadespeed.GetInt(); + + iAlpha = max( iAlpha - ( iFadeSpeed * gpGlobals->frametime ), 0 ); + + SetRenderMode( kRenderTransAlpha ); + SetRenderColorA( iAlpha ); + + if ( iAlpha == 0 ) + { + m_bReleaseRagdoll = true; + } +} + +void C_ClientRagdoll::SUB_Remove( void ) +{ + m_bFadingOut = true; + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +void C_ClientRagdoll::ClientThink( void ) +{ + if ( m_bReleaseRagdoll == true ) + { + Release(); + return; + } + + if ( g_debug_ragdoll_visualize.GetBool() ) + { + Vector vMins, vMaxs; + + Vector origin = m_pRagdoll->GetRagdollOrigin(); + m_pRagdoll->GetRagdollBounds( vMins, vMaxs ); + + debugoverlay->AddBoxOverlay( origin, vMins, vMaxs, QAngle( 0, 0, 0 ), 0, 255, 0, 16, 0 ); + } + + HandleAnimatedFriction(); + + FadeOut(); +} + +//----------------------------------------------------------------------------- +// Purpose: clear out any face/eye values stored in the material system +//----------------------------------------------------------------------------- +float C_ClientRagdoll::LastBoneChangedTime() +{ + // When did this last change? + return m_pRagdoll ? m_pRagdoll->GetLastVPhysicsUpdateTime() : -FLT_MAX; +} + + +//----------------------------------------------------------------------------- +// Purpose: clear out any face/eye values stored in the material system +//----------------------------------------------------------------------------- +void C_ClientRagdoll::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ + BaseClass::SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ); + + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return; + + int nFlexDescCount = hdr->numflexdesc(); + if ( nFlexDescCount ) + { + Assert( !pFlexDelayedWeights ); + memset( pFlexWeights, 0, nFlexWeightCount * sizeof(float) ); + } + + if ( m_iEyeAttachment > 0 ) + { + matrix3x4_t attToWorld; + if ( GetAttachment( m_iEyeAttachment, attToWorld ) ) + { + Vector local, tmp; + local.Init( 1000.0f, 0.0f, 0.0f ); + VectorTransform( local, attToWorld, tmp ); + modelrender->SetViewTarget( GetModelPtr(), GetBody(), tmp ); + } + } +} + +void C_ClientRagdoll::Release( void ) +{ + C_BaseEntity *pChild = GetEffectEntity(); + + if ( pChild && pChild->IsMarkedForDeletion() == false ) + { + pChild->Release(); + } + + if ( GetThinkHandle() != INVALID_THINK_HANDLE ) + { + ClientThinkList()->RemoveThinkable( GetClientHandle() ); + } + ClientEntityList().RemoveEntity( GetClientHandle() ); + + partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() ); + RemoveFromLeafSystem(); + + BaseClass::Release(); +} + +//----------------------------------------------------------------------------- +// Incremented each frame in InvalidateModelBones. Models compare this value to what it +// was last time they setup their bones to determine if they need to re-setup their bones. +static unsigned long g_iModelBoneCounter = 0; +CUtlVector g_PreviousBoneSetups; +static unsigned long g_iPreviousBoneCounter = (unsigned)-1; + +class C_BaseAnimatingGameSystem : public CAutoGameSystem +{ + void LevelShutdownPostEntity() + { + g_iPreviousBoneCounter = (unsigned)-1; + if ( g_PreviousBoneSetups.Count() != 0 ) + { + Msg( "%d entities in bone setup array. Should have been cleaned up by now\n", g_PreviousBoneSetups.Count() ); + g_PreviousBoneSetups.RemoveAll(); + } + } +} g_BaseAnimatingGameSystem; + + +//----------------------------------------------------------------------------- +// Purpose: convert axis rotations to a quaternion +//----------------------------------------------------------------------------- +C_BaseAnimating::C_BaseAnimating() : + m_iv_flCycle( "C_BaseAnimating::m_iv_flCycle" ), + m_iv_flPoseParameter( "C_BaseAnimating::m_iv_flPoseParameter" ), + m_iv_flEncodedController("C_BaseAnimating::m_iv_flEncodedController") +{ + m_vecForce.Init(); + m_nForceBone = -1; + + m_ClientSideAnimationListHandle = INVALID_CLIENTSIDEANIMATION_LIST_HANDLE; + + m_nPrevSequence = -1; + m_nRestoreSequence = -1; + m_pRagdoll = NULL; + m_builtRagdoll = false; + m_hitboxBoneCacheHandle = 0; + int i; + for ( i = 0; i < ARRAYSIZE( m_flEncodedController ); i++ ) + { + m_flEncodedController[ i ] = 0.0f; + } + + AddBaseAnimatingInterpolatedVars(); + + m_iMostRecentModelBoneCounter = 0xFFFFFFFF; + m_iMostRecentBoneSetupRequest = g_iPreviousBoneCounter - 1; + m_flLastBoneSetupTime = -FLT_MAX; + + m_vecPreRagdollMins = vec3_origin; + m_vecPreRagdollMaxs = vec3_origin; + + m_bStoreRagdollInfo = false; + m_pRagdollInfo = NULL; + + m_flPlaybackRate = 1.0f; + + m_nEventSequence = -1; + + m_pIk = NULL; + + // Assume false. Derived classes might fill in a receive table entry + // and in that case this would show up as true + m_bClientSideAnimation = false; + + m_nPrevNewSequenceParity = -1; + m_nPrevResetEventsParity = -1; + + m_nOldMuzzleFlashParity = 0; + m_nMuzzleFlashParity = 0; + + m_flModelWidthScale = 1.0f; + + m_iEyeAttachment = 0; +#ifdef _XBOX + m_iAccumulatedBoneMask = 0; +#endif + m_pStudioHdr = NULL; + m_hStudioHdr = MDLHANDLE_INVALID; + + m_bReceivedSequence = false; +} + +//----------------------------------------------------------------------------- +// Purpose: cleanup +//----------------------------------------------------------------------------- +C_BaseAnimating::~C_BaseAnimating() +{ + int i = g_PreviousBoneSetups.Find( this ); + if ( i != -1 ) + g_PreviousBoneSetups.FastRemove( i ); + RemoveFromClientSideAnimationList(); + + TermRopes(); + delete m_pRagdollInfo; + Assert(!m_pRagdoll); + delete m_pIk; + delete m_pBoneMergeCache; + Studio_DestroyBoneCache( m_hitboxBoneCacheHandle ); + UnlockStudioHdr(); + delete m_pStudioHdr; + delete m_pJiggleBones; +} + +bool C_BaseAnimating::UsesPowerOfTwoFrameBufferTexture( void ) +{ + return modelinfo->IsUsingFBTexture( GetModel(), GetSkin(), GetBody(), GetClientRenderable() ); +} + +//----------------------------------------------------------------------------- +// VPhysics object +//----------------------------------------------------------------------------- +int C_BaseAnimating::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ) +{ + if ( IsRagdoll() ) + { + int i; + for ( i = 0; i < m_pRagdoll->RagdollBoneCount(); ++i ) + { + if ( i >= listMax ) + break; + + pList[i] = m_pRagdoll->GetElement(i); + } + return i; + } + + return BaseClass::VPhysicsGetObjectList( pList, listMax ); +} + + +//----------------------------------------------------------------------------- +// Should this object cast render-to-texture shadows? +//----------------------------------------------------------------------------- +ShadowType_t C_BaseAnimating::ShadowCastType() +{ + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( !pStudioHdr || !pStudioHdr->SequencesAvailable() ) + return SHADOWS_NONE; + + if ( IsEffectActive(EF_NODRAW | EF_NOSHADOW) ) + return SHADOWS_NONE; + + if (pStudioHdr->GetNumSeq() == 0) + return SHADOWS_RENDER_TO_TEXTURE; + + if ( !IsRagdoll() ) + { + // If we have pose parameters, always update + if ( pStudioHdr->GetNumPoseParameters() > 0 ) + return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC; + + // If we have bone controllers, always update + if ( pStudioHdr->numbonecontrollers() > 0 ) + return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC; + + // If we use IK, always update + if ( pStudioHdr->numikchains() > 0 ) + return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC; + } + + // FIXME: Do something to check to see how many frames the current animation has + // If we do this, we have to be able to handle the case of changing ShadowCastTypes + // at the moment, they are assumed to be constant. + return SHADOWS_RENDER_TO_TEXTURE; +} + +//----------------------------------------------------------------------------- +// Purpose: convert axis rotations to a quaternion +//----------------------------------------------------------------------------- + +void C_BaseAnimating::SetPredictable( bool state ) +{ + BaseClass::SetPredictable( state ); + + UpdateRelevantInterpolatedVars(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets client side animation +//----------------------------------------------------------------------------- +void C_BaseAnimating::UseClientSideAnimation() +{ + m_bClientSideAnimation = true; +} + +void C_BaseAnimating::UpdateRelevantInterpolatedVars() +{ + MDLCACHE_CRITICAL_SECTION(); + // Remove any interpolated vars that need to be removed. + if ( !GetPredictable() && !IsClientCreated() && GetModelPtr() && GetModelPtr()->SequencesAvailable() ) + { + AddBaseAnimatingInterpolatedVars(); + } + else + { + RemoveBaseAnimatingInterpolatedVars(); + } +} + + +void C_BaseAnimating::AddBaseAnimatingInterpolatedVars() +{ + AddVar( m_flEncodedController, &m_iv_flEncodedController, LATCH_ANIMATION_VAR, true ); + AddVar( m_flPoseParameter, &m_iv_flPoseParameter, LATCH_ANIMATION_VAR, true ); + + int flags = LATCH_ANIMATION_VAR; + if ( m_bClientSideAnimation ) + flags |= EXCLUDE_AUTO_INTERPOLATE; + + AddVar( &m_flCycle, &m_iv_flCycle, flags, true ); +} + +void C_BaseAnimating::RemoveBaseAnimatingInterpolatedVars() +{ + RemoveVar( m_flEncodedController, false ); + RemoveVar( m_flPoseParameter, false ); + RemoveVar( &m_flCycle, false ); +} + +void C_BaseAnimating::LockStudioHdr() +{ + AUTO_LOCK( m_StudioHdrInitLock ); + const model_t *mdl = GetModel(); + if (mdl) + { + m_hStudioHdr = modelinfo->GetCacheHandle( mdl ); + if ( m_hStudioHdr != MDLHANDLE_INVALID ) + { + const studiohdr_t *pStudioHdr = mdlcache->LockStudioHdr( m_hStudioHdr ); + CStudioHdr *pStudioHdrContainer = NULL; + if ( !m_pStudioHdr ) + { + if ( pStudioHdr ) + { + pStudioHdrContainer = new CStudioHdr; + pStudioHdrContainer->Init( pStudioHdr, mdlcache ); + } + else + { + m_hStudioHdr = MDLHANDLE_INVALID; + } + } + else + { + pStudioHdrContainer = m_pStudioHdr; + } + + Assert( ( pStudioHdr == NULL && pStudioHdrContainer == NULL ) || pStudioHdrContainer->GetRenderHdr() == pStudioHdr ); + + if ( pStudioHdrContainer && pStudioHdrContainer->GetVirtualModel() ) + { + MDLHandle_t hVirtualModel = (MDLHandle_t)pStudioHdrContainer->GetRenderHdr()->virtualModel; + mdlcache->LockStudioHdr( hVirtualModel ); + } + m_pStudioHdr = pStudioHdrContainer; // must be last to ensure virtual model correctly set up + } + } +} + +void C_BaseAnimating::UnlockStudioHdr() +{ + if ( m_pStudioHdr ) + { + const model_t *mdl = GetModel(); + if (mdl) + { + mdlcache->UnlockStudioHdr( m_hStudioHdr ); + if ( m_pStudioHdr->GetVirtualModel() ) + { + MDLHandle_t hVirtualModel = (MDLHandle_t)m_pStudioHdr->GetRenderHdr()->virtualModel; + mdlcache->UnlockStudioHdr( hVirtualModel ); + } + } + } +} + + + +CStudioHdr *C_BaseAnimating::OnNewModel() +{ + if (m_pStudioHdr) + { + UnlockStudioHdr(); + delete m_pStudioHdr; + m_pStudioHdr = NULL; + } + + // remove transition animations playback + m_SequenceTransitioner.RemoveAll(); + + if (m_pJiggleBones) + { + delete m_pJiggleBones; + m_pJiggleBones = NULL; + } + + if ( !GetModel() ) + return NULL; + + LockStudioHdr(); + + UpdateRelevantInterpolatedVars(); + + CStudioHdr *hdr = GetModelPtr(); + if (hdr == NULL) + return NULL; + + InvalidateBoneCache(); + if ( m_pBoneMergeCache ) + { + delete m_pBoneMergeCache; + m_pBoneMergeCache = NULL; + // recreated in BuildTransformations + } + + Studio_DestroyBoneCache( m_hitboxBoneCacheHandle ); + m_hitboxBoneCacheHandle = 0; + + // Make sure m_CachedBones has space. + if ( m_CachedBoneData.Count() != hdr->numbones() ) + { + m_CachedBoneData.SetSize( hdr->numbones() ); + for ( int i=0; i < hdr->numbones(); i++ ) + { + SetIdentityMatrix( m_CachedBoneData[i] ); + } + } + m_BoneAccessor.Init( this, m_CachedBoneData.Base() ); // Always call this in case the studiohdr_t has changed. + + // Free any IK data + if (m_pIk) + { + delete m_pIk; + m_pIk = NULL; + } + + // Don't reallocate unless a different size. + if ( m_Attachments.Count() != hdr->GetNumAttachments() ) + { + m_Attachments.SetSize( hdr->GetNumAttachments() ); + + // This is to make sure we don't use the attachment before its been set up + for ( int i=0; i < m_Attachments.Count(); i++ ) + { + m_Attachments[i].m_bAnglesComputed = false; + m_Attachments[i].m_nLastFramecount = 0; +#ifdef _DEBUG + m_Attachments[i].m_AttachmentToWorld.Invalidate(); + m_Attachments[i].m_angRotation.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN ); + m_Attachments[i].m_vOriginVelocity.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN ); +#endif + } + + } + + Assert( hdr->GetNumPoseParameters() <= ARRAYSIZE( m_flPoseParameter ) ); + + m_iv_flPoseParameter.SetMaxCount( hdr->GetNumPoseParameters() ); + + int i; + for ( i = 0; i < hdr->GetNumPoseParameters() ; i++ ) + { + const mstudioposeparamdesc_t &Pose = hdr->pPoseParameter( i ); + m_iv_flPoseParameter.SetLooping( Pose.loop != 0.0f, i ); + // Note: We can't do this since if we get a DATA_UPDATE_CREATED (i.e., new entity) with both a new model and some valid pose parameters this will slam the + // pose parameters to zero and if the model goes dormant the pose parameter field will never be set to the true value. We shouldn't have to zero these out + // as they are under the control of the server and should be properly set + if ( !IsServerEntity() ) + { + SetPoseParameter( hdr, i, 0.0 ); + } + } + + int boneControllerCount = min( hdr->numbonecontrollers(), ARRAYSIZE( m_flEncodedController ) ); + + m_iv_flEncodedController.SetMaxCount( boneControllerCount ); + + for ( i = 0; i < boneControllerCount ; i++ ) + { + bool loop = (hdr->pBonecontroller( i )->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) != 0; + m_iv_flEncodedController.SetLooping( loop, i ); + SetBoneController( i, 0.0 ); + } + + InitModelEffects(); + + // lookup generic eye attachment, if exists + m_iEyeAttachment = LookupAttachment( "eyes" ); + + // If we didn't have a model before, then we might need to go in the interpolation list now. + if ( ShouldInterpolate() ) + AddToInterpolationList(); + + // objects with attachment points need to be queryable even if they're not solid + if ( hdr->GetNumAttachments() != 0 ) + { + AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); + } + + // Most entities clear out their sequences when they change models on the server, but + // not all entities network down their m_nSequence (like multiplayer game player entities), + // so we need to clear it out here. + if ( ShouldResetSequenceOnNewModel() ) + { + SetSequence(0); + } + + return hdr; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns index number of a given named bone +// Input : name of a bone +// Output : Bone index number or -1 if bone not found +//----------------------------------------------------------------------------- +int C_BaseAnimating::LookupBone( const char *szName ) +{ + Assert( GetModelPtr() ); + + return Studio_BoneIndexByName( GetModelPtr(), szName ); +} + +//========================================================= +//========================================================= +void C_BaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) +{ + matrix3x4_t bonetoworld; + GetBoneTransform( iBone, bonetoworld ); + + MatrixAngles( bonetoworld, angles, origin ); +} + +void C_BaseAnimating::GetBoneTransform( int iBone, matrix3x4_t &pBoneToWorld ) +{ + Assert( GetModelPtr() && iBone >= 0 && iBone < GetModelPtr()->numbones() ); + CBoneCache *pcache = GetBoneCache( NULL ); + + matrix3x4_t *pmatrix = pcache->GetCachedBone( iBone ); + + if ( !pmatrix ) + { + MatrixCopy( EntityToWorldTransform(), pBoneToWorld ); + return; + } + + Assert( pmatrix ); + + // FIXME + MatrixCopy( *pmatrix, pBoneToWorld ); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup to initialize our model effects once the model's loaded +//----------------------------------------------------------------------------- +void C_BaseAnimating::InitModelEffects( void ) +{ + m_bInitModelEffects = true; + TermRopes(); +} + +//----------------------------------------------------------------------------- +// Purpose: Load the model's keyvalues section and create effects listed inside it +//----------------------------------------------------------------------------- +void C_BaseAnimating::DelayedInitModelEffects( void ) +{ + m_bInitModelEffects = false; + + // Parse the keyvalues and see if they want to make ropes on this model. + KeyValues * modelKeyValues = new KeyValues(""); + if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) ) + { + // Do we have a cables section? + KeyValues *pkvAllCables = modelKeyValues->FindKey("Cables"); + if ( pkvAllCables ) + { + // Start grabbing the sounds and slotting them in + for ( KeyValues *pSingleCable = pkvAllCables->GetFirstSubKey(); pSingleCable; pSingleCable = pSingleCable->GetNextKey() ) + { + C_RopeKeyframe *pRope = C_RopeKeyframe::CreateFromKeyValues( this, pSingleCable ); + m_Ropes.AddToTail( pRope ); + } + } + + // Do we have a particles section? + KeyValues *pkvAllParticleEffects = modelKeyValues->FindKey("Particles"); + if ( pkvAllParticleEffects ) + { + // Start grabbing the sounds and slotting them in + for ( KeyValues *pSingleEffect = pkvAllParticleEffects->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() ) + { + const char *pszParticleEffect = pSingleEffect->GetString( "name", "" ); + const char *pszAttachment = pSingleEffect->GetString( "attachment_point", "" ); + const char *pszAttachType = pSingleEffect->GetString( "attachment_type", "" ); + + // Convert attach type + int iAttachType = GetAttachTypeFromString( pszAttachType ); + if ( iAttachType == -1 ) + { + Warning("Invalid attach type specified for particle effect in model '%s' keyvalues section. Trying to spawn effect '%s' with attach type of '%s'\n", GetModelName(), pszParticleEffect, pszAttachType ); + return; + } + + // Convert attachment point + int iAttachment = atoi(pszAttachment); + // See if we can find any attachment points matching the name + if ( pszAttachment[0] != '0' && iAttachment == 0 ) + { + iAttachment = LookupAttachment( pszAttachment ); + if ( iAttachment == -1 ) + { + Warning("Failed to find attachment point specified for particle effect in model '%s' keyvalues section. Trying to spawn effect '%s' on attachment named '%s'\n", GetModelName(), pszParticleEffect, pszAttachment ); + return; + } + } + + // Spawn the particle effect + ParticleProp()->Create( pszParticleEffect, (ParticleAttachment_t)iAttachType, iAttachment ); + } + } + } + + modelKeyValues->deleteThis(); +} + + +void C_BaseAnimating::TermRopes() +{ + FOR_EACH_LL( m_Ropes, i ) + m_Ropes[i]->Release(); + + m_Ropes.Purge(); +} + + +// FIXME: redundant? +void C_BaseAnimating::GetBoneControllers(float controllers[MAXSTUDIOBONECTRLS]) +{ + // interpolate two 0..1 encoded controllers to a single 0..1 controller + int i; + for( i=0; i < MAXSTUDIOBONECTRLS; i++) + { + controllers[ i ] = m_flEncodedController[ i ]; + } +} + +float C_BaseAnimating::GetPoseParameter( int iPoseParameter ) +{ + CStudioHdr *pStudioHdr = GetModelPtr(); + + if ( pStudioHdr == NULL ) + return 0.0f; + + if ( pStudioHdr->GetNumPoseParameters() < iPoseParameter ) + return 0.0f; + + if ( iPoseParameter < 0 ) + return 0.0f; + + return m_flPoseParameter[iPoseParameter]; +} + +// FIXME: redundant? +void C_BaseAnimating::GetPoseParameters( CStudioHdr *pStudioHdr, float poseParameter[MAXSTUDIOPOSEPARAM]) +{ + if ( !pStudioHdr ) + return; + + // interpolate pose parameters + int i; + for( i=0; i < pStudioHdr->GetNumPoseParameters(); i++) + { + poseParameter[i] = m_flPoseParameter[i]; + } + + +#if 0 // _DEBUG + if (/* Q_stristr( pStudioHdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex()) + { + DevMsgRT( "%s\n", pStudioHdr->pszName() ); + DevMsgRT( "%6.2f : ", gpGlobals->curtime ); + for( i=0; i < pStudioHdr->GetNumPoseParameters(); i++) + { + const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( i ); + + DevMsgRT( "%s %6.2f ", Pose.pszName(), poseParameter[i] * Pose.end + (1 - poseParameter[i]) * Pose.start ); + } + DevMsgRT( "\n" ); + } +#endif +} + + +float C_BaseAnimating::ClampCycle( float flCycle, bool isLooping ) +{ + if (isLooping) + { + // FIXME: does this work with negative framerate? + flCycle -= (int)flCycle; + if (flCycle < 0.0f) + { + flCycle += 1.0f; + } + } + else + { + flCycle = clamp( flCycle, 0.0f, 0.999f ); + } + return flCycle; +} + + +void C_BaseAnimating::GetCachedBoneMatrix( int boneIndex, matrix3x4_t &out ) +{ + MatrixCopy( GetBone( boneIndex ), out ); +} + + +//----------------------------------------------------------------------------- +// Purpose: move position and rotation transforms into global matrices +//----------------------------------------------------------------------------- +void C_BaseAnimating::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion *q, const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed ) +{ + VPROF_BUDGET( "C_BaseAnimating::BuildTransformations", VPROF_BUDGETGROUP_CLIENT_ANIMATION ); + + if ( !hdr ) + return; + + matrix3x4_t bonematrix; + bool boneSimulated[MAXSTUDIOBONES]; + + // no bones have been simulated + memset( boneSimulated, 0, sizeof(boneSimulated) ); + mstudiobone_t *pbones = hdr->pBone( 0 ); + + if ( m_pRagdoll ) + { + // simulate bones and update flags + int oldWritableBones = m_BoneAccessor.GetWritableBones(); + int oldReadableBones = m_BoneAccessor.GetReadableBones(); + m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING ); + m_BoneAccessor.SetReadableBones( BONE_USED_BY_ANYTHING ); + + m_pRagdoll->RagdollBone( this, pbones, hdr->numbones(), boneSimulated, m_BoneAccessor ); + + m_BoneAccessor.SetWritableBones( oldWritableBones ); + m_BoneAccessor.SetReadableBones( oldReadableBones ); + } + + // For EF_BONEMERGE entities, copy the bone matrices for any bones that have matching names. + bool boneMerge = IsEffectActive(EF_BONEMERGE); + if ( boneMerge || m_pBoneMergeCache ) + { + if ( boneMerge ) + { + if ( !m_pBoneMergeCache ) + { + m_pBoneMergeCache = new CBoneMergeCache; + m_pBoneMergeCache->Init( this ); + } + m_pBoneMergeCache->MergeMatchingBones( boneMask ); + } + else + { + delete m_pBoneMergeCache; + m_pBoneMergeCache = NULL; + } + } + + for (int i = 0; i < hdr->numbones(); i++) + { + // Only update bones reference by the bone mask. + if ( !( hdr->boneFlags( i ) & boneMask ) ) + continue; + + if ( m_pBoneMergeCache && m_pBoneMergeCache->IsBoneMerged( i ) ) + continue; + + // animate all non-simulated bones + if ( boneSimulated[i] || CalcProceduralBone( hdr, i, m_BoneAccessor )) + { + continue; + } + // skip bones that the IK has already setup + else if (boneComputed.IsBoneMarked( i )) + { + // dummy operation, just used to verify in debug that this should have happened + GetBoneForWrite( i ); + } + else + { + QuaternionMatrix( q[i], pos[i], bonematrix ); + + Assert( fabs( pos[i].x ) < 100000 ); + Assert( fabs( pos[i].y ) < 100000 ); + Assert( fabs( pos[i].z ) < 100000 ); + + if ( (hdr->boneFlags( i ) & BONE_ALWAYS_PROCEDURAL) && + (hdr->pBone( i )->proctype & STUDIO_PROC_JIGGLE) ) + { + // + // Physics-based "jiggle" bone + // Bone is assumed to be along the Z axis + // Pitch around X, yaw around Y + // + + // compute desired bone orientation + matrix3x4_t goalMX; + + if (pbones[i].parent == -1) + { + ConcatTransforms( cameraTransform, bonematrix, goalMX ); + } + else + { + ConcatTransforms( GetBone( pbones[i].parent ), bonematrix, goalMX ); + } + + // get jiggle properties from QC data + mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)pbones[i].pProcedure( ); + + if (!m_pJiggleBones) + { + m_pJiggleBones = new CJiggleBones; + } + + // do jiggle physics + m_pJiggleBones->BuildJiggleTransformations( i, gpGlobals->curtime, jiggleInfo, goalMX, GetBoneForWrite( i ) ); + + } + else if (hdr->boneParent(i) == -1) + { + ConcatTransforms( cameraTransform, bonematrix, GetBoneForWrite( i ) ); + } + else + { + ConcatTransforms( GetBone( hdr->boneParent(i) ), bonematrix, GetBoneForWrite( i ) ); + } + } + + if (hdr->boneParent(i) == -1) + { + // Apply client-side effects to the transformation matrix + ApplyBoneMatrixTransform( GetBoneForWrite( i ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Special effects +// Input : transform - +//----------------------------------------------------------------------------- +void C_BaseAnimating::ApplyBoneMatrixTransform( matrix3x4_t& transform ) +{ + switch( m_nRenderFX ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( RandomInt(0,49) == 0 ) + { + int axis = RandomInt(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], RandomFloat(1,1.484), transform[axis] ); + } + else if ( RandomInt(0,49) == 0 ) + { + float offset; + int axis = RandomInt(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = RandomFloat(-10,10); + transform[RandomInt(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0 + (gpGlobals->curtime - m_flAnimTime) * 10.0; + if ( scale > 2 ) // Don't blow up more than 200% + scale = 2; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + default: + break; + + } + + float scale = GetModelWidthScale(); + if ( scale != 1.0f ) + { + VectorScale( transform[0], scale, transform[0] ); + VectorScale( transform[1], scale, transform[1] ); + } +} + +void C_BaseAnimating::CreateUnragdollInfo( C_BaseAnimating *pRagdoll ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + + // It's already an active ragdoll, sigh + if ( m_pRagdollInfo && m_pRagdollInfo->m_bActive ) + { + Assert( 0 ); + return; + } + + // Now do the current bone setup + pRagdoll->SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); + + matrix3x4_t parentTransform; + QAngle newAngles( 0, pRagdoll->GetAbsAngles()[YAW], 0 ); + + AngleMatrix( GetAbsAngles(), GetAbsOrigin(), parentTransform ); + // pRagdoll->SaveRagdollInfo( hdr->numbones, parentTransform, m_BoneAccessor ); + + if ( !m_pRagdollInfo ) + { + m_pRagdollInfo = new RagdollInfo_t; + Assert( m_pRagdollInfo ); + if ( !m_pRagdollInfo ) + { + Msg( "Memory allocation of RagdollInfo_t failed!\n" ); + return; + } + } + + Q_memset( m_pRagdollInfo, 0, sizeof( *m_pRagdollInfo ) ); + + int numbones = hdr->numbones(); + + m_pRagdollInfo->m_bActive = true; + m_pRagdollInfo->m_flSaveTime = gpGlobals->curtime; + m_pRagdollInfo->m_nNumBones = numbones; + + for ( int i = 0; i < numbones; i++ ) + { + matrix3x4_t inverted; + matrix3x4_t output; + + if ( hdr->boneParent(i) == -1 ) + { + // Decompose into parent space + MatrixInvert( parentTransform, inverted ); + } + else + { + MatrixInvert( pRagdoll->m_BoneAccessor.GetBone( hdr->boneParent(i) ), inverted ); + } + + ConcatTransforms( inverted, pRagdoll->m_BoneAccessor.GetBone( i ), output ); + + MatrixAngles( output, + m_pRagdollInfo->m_rgBoneQuaternion[ i ], + m_pRagdollInfo->m_rgBonePos[ i ] ); + } +} + +void C_BaseAnimating::SaveRagdollInfo( int numbones, const matrix3x4_t &cameraTransform, CBoneAccessor &pBoneToWorld ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + + if ( !m_pRagdollInfo ) + { + m_pRagdollInfo = new RagdollInfo_t; + Assert( m_pRagdollInfo ); + if ( !m_pRagdollInfo ) + { + Msg( "Memory allocation of RagdollInfo_t failed!\n" ); + return; + } + memset( m_pRagdollInfo, 0, sizeof( *m_pRagdollInfo ) ); + } + + mstudiobone_t *pbones = hdr->pBone( 0 ); + + m_pRagdollInfo->m_bActive = true; + m_pRagdollInfo->m_flSaveTime = gpGlobals->curtime; + m_pRagdollInfo->m_nNumBones = numbones; + + for ( int i = 0; i < numbones; i++ ) + { + matrix3x4_t inverted; + matrix3x4_t output; + + if ( pbones[i].parent == -1 ) + { + // Decompose into parent space + MatrixInvert( cameraTransform, inverted ); + } + else + { + MatrixInvert( pBoneToWorld.GetBone( pbones[ i ].parent ), inverted ); + } + + ConcatTransforms( inverted, pBoneToWorld.GetBone( i ), output ); + + MatrixAngles( output, + m_pRagdollInfo->m_rgBoneQuaternion[ i ], + m_pRagdollInfo->m_rgBonePos[ i ] ); + } +} + +bool C_BaseAnimating::RetrieveRagdollInfo( Vector *pos, Quaternion *q ) +{ + if ( !m_bStoreRagdollInfo || !m_pRagdollInfo || !m_pRagdollInfo->m_bActive ) + return false; + + for ( int i = 0; i < m_pRagdollInfo->m_nNumBones; i++ ) + { + pos[ i ] = m_pRagdollInfo->m_rgBonePos[ i ]; + q[ i ] = m_pRagdollInfo->m_rgBoneQuaternion[ i ]; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Should we collide? +//----------------------------------------------------------------------------- + +CollideType_t C_BaseAnimating::GetCollideType( void ) +{ + if ( IsRagdoll() ) + return ENTITY_SHOULD_RESPOND; + + return BaseClass::GetCollideType(); +} + +//----------------------------------------------------------------------------- +// Purpose: if the active sequence changes, keep track of the previous ones and decay them based on their decay rate +//----------------------------------------------------------------------------- +void C_BaseAnimating::MaintainSequenceTransitions( CStudioHdr *hdr, float flCycle, float flPoseParameter[], Vector pos[], Quaternion q[], int boneMask ) +{ + VPROF( "C_BaseAnimating::MaintainSequenceTransitions" ); + + if ( !hdr ) + return; + + if ( prediction->InPrediction() ) + { + m_nPrevNewSequenceParity = m_nNewSequenceParity; + return; + } + + m_SequenceTransitioner.CheckForSequenceChange( + hdr, + GetSequence(), + m_nNewSequenceParity != m_nPrevNewSequenceParity, + !IsEffectActive(EF_NOINTERP) + ); + + m_nPrevNewSequenceParity = m_nNewSequenceParity; + + // Update the transition sequence list. + m_SequenceTransitioner.UpdateCurrent( + hdr, + GetSequence(), + flCycle, + m_flPlaybackRate, + gpGlobals->curtime + ); + + + // process previous sequences + for (int i = m_SequenceTransitioner.m_animationQueue.Count() - 2; i >= 0; i--) + { + C_AnimationLayer *blend = &m_SequenceTransitioner.m_animationQueue[i]; + + float dt = (gpGlobals->curtime - blend->m_flLayerAnimtime); + flCycle = blend->m_flCycle + dt * blend->m_flPlaybackRate * GetSequenceCycleRate( hdr, blend->m_nSequence ); + flCycle = ClampCycle( flCycle, IsSequenceLooping( hdr, blend->m_nSequence ) ); + +#if 1 // _DEBUG + if (/*Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex()) + { + DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f +\n", gpGlobals->curtime, hdr->pSeqdesc( blend->m_nSequence ).pszLabel(), flCycle, (float)blend->m_flWeight ); + } +#endif + + AccumulatePose( hdr, m_pIk, pos, q, blend->m_nSequence, flCycle, flPoseParameter, boneMask, blend->m_flWeight, gpGlobals->curtime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *hdr - +// pos[] - +// q[] - +//----------------------------------------------------------------------------- +void C_BaseAnimating::UnragdollBlend( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime ) +{ + if ( !hdr ) + { + return; + } + + if ( !m_pRagdollInfo || !m_pRagdollInfo->m_bActive ) + return; + + float dt = currentTime - m_pRagdollInfo->m_flSaveTime; + if ( dt > 0.2f ) + { + m_pRagdollInfo->m_bActive = false; + return; + } + + // Slerp bone sets together + float frac = dt / 0.2f; + frac = clamp( frac, 0.0f, 1.0f ); + + int i; + for ( i = 0; i < hdr->numbones(); i++ ) + { + VectorLerp( m_pRagdollInfo->m_rgBonePos[ i ], pos[ i ], frac, pos[ i ] ); + QuaternionSlerp( m_pRagdollInfo->m_rgBoneQuaternion[ i ], q[ i ], frac, q[ i ] ); + } +} + +void C_BaseAnimating::AccumulateLayers( CStudioHdr *hdr, Vector pos[], Quaternion q[], float poseparam[], float currentTime, int boneMask ) +{ + // Nothing here +} + +//----------------------------------------------------------------------------- +// Purpose: Do the default sequence blending rules as done in HL1 +//----------------------------------------------------------------------------- +void C_BaseAnimating::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) +{ + VPROF( "C_BaseAnimating::StandardBlendingRules" ); + + float poseparam[MAXSTUDIOPOSEPARAM]; + + if ( !hdr ) + return; + + if ( !hdr->SequencesAvailable() ) + { + return; + } + + if (GetSequence() >= hdr->GetNumSeq() || GetSequence() == -1 ) + { + SetSequence( 0 ); + } + + GetPoseParameters( hdr, poseparam ); + + // build root animation + float fCycle = GetCycle(); + +#if 1 //_DEBUG + if (/* Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex()) + { + DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f\n", currentTime, hdr->pSeqdesc( GetSequence() ).pszLabel(), fCycle, 1.0 ); + } +#endif + + InitPose( hdr, pos, q, boneMask ); + + AccumulatePose( hdr, m_pIk, pos, q, GetSequence(), fCycle, poseparam, boneMask, 1.0, currentTime ); + + // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%30s %6.2f : %6.2f", hdr->pSeqdesc( GetSequence() )->pszLabel( ), fCycle, 1.0 ); + + MaintainSequenceTransitions( hdr, fCycle, poseparam, pos, q, boneMask ); + + AccumulateLayers( hdr, pos, q, poseparam, currentTime, boneMask ); + + CIKContext auto_ik; + auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, boneMask ); + CalcAutoplaySequences( hdr, &auto_ik, pos, q, poseparam, boneMask, currentTime ); + + if ( hdr->numbonecontrollers() ) + { + float controllers[MAXSTUDIOBONECTRLS]; + GetBoneControllers(controllers); + CalcBoneAdj( hdr, pos, q, controllers, boneMask ); + } + UnragdollBlend( hdr, pos, q, currentTime ); + +#ifdef STUDIO_ENABLE_PERF_COUNTERS +#if _DEBUG + if (Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL) + { + DevMsgRT( "layers %4d : bones %4d : animated %4d\n", hdr->m_nPerfAnimationLayers, hdr->m_nPerfUsedBones, hdr->m_nPerfAnimatedBones ); + } +#endif +#endif + +} + + +//----------------------------------------------------------------------------- +// Purpose: Put a value into an attachment point by index +// Input : number - which point +// Output : float * - the attachment point +//----------------------------------------------------------------------------- +bool C_BaseAnimating::PutAttachment( int number, const matrix3x4_t &attachmentToWorld ) +{ + if ( number < 1 || number > m_Attachments.Count() ) + return false; + + CAttachmentData *pAtt = &m_Attachments[number-1]; + if ( gpGlobals->frametime > 0 && pAtt->m_nLastFramecount > 0 && pAtt->m_nLastFramecount == gpGlobals->framecount - 1 ) + { + Vector vecPreviousOrigin, vecOrigin; + MatrixPosition( pAtt->m_AttachmentToWorld, vecPreviousOrigin ); + MatrixPosition( attachmentToWorld, vecOrigin ); + pAtt->m_vOriginVelocity = (vecOrigin - vecPreviousOrigin) / gpGlobals->frametime; + } + else + { + pAtt->m_vOriginVelocity.Init(); + } + pAtt->m_nLastFramecount = gpGlobals->framecount; + pAtt->m_bAnglesComputed = false; + pAtt->m_AttachmentToWorld = attachmentToWorld; + +#ifdef _DEBUG + pAtt->m_angRotation.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN ); +#endif + + return true; +} + + +void C_BaseAnimating::SetupBones_AttachmentHelper( CStudioHdr *hdr ) +{ + if ( !hdr || !hdr->GetNumAttachments() ) + return; + + // calculate attachment points + matrix3x4_t world; + for (int i = 0; i < hdr->GetNumAttachments(); i++) + { + const mstudioattachment_t &pattachment = hdr->pAttachment( i ); + int iBone = hdr->GetAttachmentBone( i ); + if ( (pattachment.flags & ATTACHMENT_FLAG_WORLD_ALIGN) == 0 ) + { + ConcatTransforms( GetBone( iBone ), pattachment.local, world ); + } + else + { + Vector vecLocalBonePos, vecWorldBonePos; + MatrixGetColumn( pattachment.local, 3, vecLocalBonePos ); + VectorTransform( vecLocalBonePos, GetBone( iBone ), vecWorldBonePos ); + + SetIdentityMatrix( world ); + MatrixSetColumn( vecWorldBonePos, 3, world ); + } + + // FIXME: this shouldn't be here, it should client side on-demand only and hooked into the bone cache!! + FormatViewModelAttachment( i, world ); + PutAttachment( i + 1, world ); + } +} + +bool C_BaseAnimating::CalcAttachments() +{ + VPROF( "C_BaseAnimating::CalcAttachments" ); + + + // Make sure m_CachedBones is valid. + return SetupBones( NULL, -1, BONE_USED_BY_ATTACHMENT, gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the world location and world angles of an attachment +// Input : attachment name +// Output : location and angles +//----------------------------------------------------------------------------- +bool C_BaseAnimating::GetAttachment( const char *szName, Vector &absOrigin, QAngle &absAngles ) +{ + return GetAttachment( LookupAttachment( szName ), absOrigin, absAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get attachment point by index +// Input : number - which point +// Output : float * - the attachment point +//----------------------------------------------------------------------------- +bool C_BaseAnimating::GetAttachment( int number, Vector &origin, QAngle &angles ) +{ + // Note: this could be more efficient, but we want the matrix3x4_t version of GetAttachment to be the origin of + // attachment generation, so a derived class that wants to fudge attachments only + // has to reimplement that version. This also makes it work like the server in that regard. + if ( number < 1 || number > m_Attachments.Count() || !CalcAttachments() ) + { + // Set this to the model origin/angles so that we don't have stack fungus in origin and angles. + origin = GetAbsOrigin(); + angles = GetAbsAngles(); + return false; + } + + CAttachmentData *pData = &m_Attachments[number-1]; + if ( !pData->m_bAnglesComputed ) + { + MatrixAngles( pData->m_AttachmentToWorld, pData->m_angRotation ); + pData->m_bAnglesComputed = true; + } + angles = pData->m_angRotation; + MatrixPosition( pData->m_AttachmentToWorld, origin ); + return true; +} + +bool C_BaseAnimating::GetAttachment( int number, matrix3x4_t& matrix ) +{ + if ( number < 1 || number > m_Attachments.Count() ) + return false; + + if ( !CalcAttachments() ) + return false; + + matrix = m_Attachments[number-1].m_AttachmentToWorld; + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get attachment point by index (position only) +// Input : number - which point +//----------------------------------------------------------------------------- +bool C_BaseAnimating::GetAttachment( int number, Vector &origin ) +{ + // Note: this could be more efficient, but we want the matrix3x4_t version of GetAttachment to be the origin of + // attachment generation, so a derived class that wants to fudge attachments only + // has to reimplement that version. This also makes it work like the server in that regard. + matrix3x4_t attachmentToWorld; + if ( !GetAttachment( number, attachmentToWorld ) ) + { + // Set this to the model origin/angles so that we don't have stack fungus in origin and angles. + origin = GetAbsOrigin(); + return false; + } + + MatrixPosition( attachmentToWorld, origin ); + return true; +} + + +bool C_BaseAnimating::GetAttachment( const char *szName, Vector &absOrigin ) +{ + return GetAttachment( LookupAttachment( szName ), absOrigin ); +} + + + +bool C_BaseAnimating::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ) +{ + if ( number < 1 || number > m_Attachments.Count() ) + { + return false; + } + + if ( !CalcAttachments() ) + return false; + + originVel = m_Attachments[number-1].m_vOriginVelocity; + angleVel.Init(); + + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the attachment in local space +//----------------------------------------------------------------------------- +bool C_BaseAnimating::GetAttachmentLocal( int iAttachment, matrix3x4_t &attachmentToLocal ) +{ + matrix3x4_t attachmentToWorld; + if (!GetAttachment(iAttachment, attachmentToWorld)) + return false; + + matrix3x4_t worldToEntity; + MatrixInvert( EntityToWorldTransform(), worldToEntity ); + ConcatTransforms( worldToEntity, attachmentToWorld, attachmentToLocal ); + return true; +} + +bool C_BaseAnimating::GetAttachmentLocal( int iAttachment, Vector &origin, QAngle &angles ) +{ + matrix3x4_t attachmentToEntity; + + if ( GetAttachmentLocal( iAttachment, attachmentToEntity ) ) + { + origin.Init( attachmentToEntity[0][3], attachmentToEntity[1][3], attachmentToEntity[2][3] ); + MatrixAngles( attachmentToEntity, angles ); + return true; + } + return false; +} + +bool C_BaseAnimating::GetAttachmentLocal( int iAttachment, Vector &origin ) +{ + matrix3x4_t attachmentToEntity; + + if ( GetAttachmentLocal( iAttachment, attachmentToEntity ) ) + { + MatrixPosition( attachmentToEntity, origin ); + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Move sound location to center of body +//----------------------------------------------------------------------------- +bool C_BaseAnimating::GetSoundSpatialization( SpatializationInfo_t& info ) +{ + { + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); + if ( !BaseClass::GetSoundSpatialization( info ) ) + return false; + } + + // move sound origin to center if npc has IK + if ( info.pOrigin && IsNPC() && m_pIk) + { + *info.pOrigin = GetAbsOrigin(); + + Vector mins, maxs, center; + + modelinfo->GetModelBounds( GetModel(), mins, maxs ); + VectorAdd( mins, maxs, center ); + VectorScale( center, 0.5f, center ); + + (*info.pOrigin) += center; + } + return true; +} + + +bool C_BaseAnimating::IsViewModel() const +{ + return false; +} + +bool C_BaseAnimating::IsMenuModel() const +{ + return false; +} + +// UNDONE: Seems kind of silly to have this when we also have the cached bones in C_BaseAnimating +CBoneCache *C_BaseAnimating::GetBoneCache( CStudioHdr *pStudioHdr ) +{ + int boneMask = BONE_USED_BY_HITBOX; + CBoneCache *pcache = Studio_GetBoneCache( m_hitboxBoneCacheHandle ); + if ( pcache ) + { + if ( pcache->IsValid( gpGlobals->curtime, 0.0 ) ) + { + // in memory and still valid, use it! + return pcache; + } + // in memory, but not the same bone set, destroy & rebuild + if ( (pcache->m_boneMask & boneMask) != boneMask ) + { + Studio_DestroyBoneCache( m_hitboxBoneCacheHandle ); + m_hitboxBoneCacheHandle = 0; + pcache = NULL; + } + } + + if ( !pStudioHdr ) + pStudioHdr = GetModelPtr( ); + Assert(pStudioHdr); + + SetupBones( NULL, -1, boneMask, gpGlobals->curtime ); + + if ( pcache ) + { + // still in memory but out of date, refresh the bones. + pcache->UpdateBones( m_CachedBoneData.Base(), pStudioHdr->numbones(), gpGlobals->curtime ); + } + else + { + bonecacheparams_t params; + params.pStudioHdr = pStudioHdr; + // HACKHACK: We need the pointer to all bones here + params.pBoneToWorld = m_CachedBoneData.Base(); + params.curtime = gpGlobals->curtime; + params.boneMask = boneMask; + + m_hitboxBoneCacheHandle = Studio_CreateBoneCache( params ); + pcache = Studio_GetBoneCache( m_hitboxBoneCacheHandle ); + } + Assert(pcache); + return pcache; +} + + +class CTraceFilterSkipNPCsAndPlayers : public CTraceFilterSimple +{ +public: + CTraceFilterSkipNPCsAndPlayers( const IHandleEntity *passentity, int collisionGroup ) + : CTraceFilterSimple( passentity, collisionGroup ) + { + } + + virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) + { + if ( CTraceFilterSimple::ShouldHitEntity(pServerEntity, contentsMask) ) + { + C_BaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); + if ( !pEntity ) + return true; + + if ( pEntity->IsNPC() || pEntity->IsPlayer() ) + return false; + + return true; + } + return false; + } +}; + + +/* +void drawLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration) +{ + debugoverlay->AddLineOverlay( origin, dest, r, g, b, noDepthTest, duration ); +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: update latched IK contacts if they're in a moving reference frame. +//----------------------------------------------------------------------------- + +void C_BaseAnimating::UpdateIKLocks( float currentTime ) +{ + if (!m_pIk) + return; + + int targetCount = m_pIk->m_target.Count(); + if ( targetCount == 0 ) + return; + + for (int i = 0; i < targetCount; i++) + { + CIKTarget *pTarget = &m_pIk->m_target[i]; + + if (!pTarget->IsActive()) + continue; + + if (pTarget->GetOwner() != -1) + { + C_BaseEntity *pOwner = cl_entitylist->GetEnt( pTarget->GetOwner() ); + if (pOwner != NULL) + { + pTarget->UpdateOwner( pOwner->entindex(), pOwner->GetAbsOrigin(), pOwner->GetAbsAngles() ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find the ground or external attachment points needed by IK rules +//----------------------------------------------------------------------------- + +void C_BaseAnimating::CalculateIKLocks( float currentTime ) +{ + if (!m_pIk) + return; + + int targetCount = m_pIk->m_target.Count(); + if ( targetCount == 0 ) + return; + + // In TF, we might be attaching a player's view to a walking model that's using IK. If we are, it can + // get in here during the view setup code, and it's not normally supposed to be able to access the spatial + // partition that early in the rendering loop. So we allow access right here for that special case. + SpatialPartitionListMask_t curSuppressed = partition->GetSuppressedLists(); + partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, false ); + CBaseEntity::PushEnableAbsRecomputations( false ); + + Ray_t ray; + CTraceFilterSkipNPCsAndPlayers traceFilter( this, GetCollisionGroup() ); + + // FIXME: trace based on gravity or trace based on angles? + Vector up; + AngleVectors( GetRenderAngles(), NULL, NULL, &up ); + + // FIXME: check number of slots? + float minHeight = FLT_MAX; + float maxHeight = -FLT_MAX; + + for (int i = 0; i < targetCount; i++) + { + trace_t trace; + CIKTarget *pTarget = &m_pIk->m_target[i]; + + if (!pTarget->IsActive()) + continue; + + switch( pTarget->type) + { + case IK_GROUND: + { + Vector estGround; + Vector p1, p2; + + // adjust ground to original ground position + estGround = (pTarget->est.pos - GetRenderOrigin()); + estGround = estGround - (estGround * up) * up; + estGround = GetAbsOrigin() + estGround + pTarget->est.floor * up; + + VectorMA( estGround, pTarget->est.height, up, p1 ); + VectorMA( estGround, -pTarget->est.height, up, p2 ); + + float r = max( pTarget->est.radius, 1); + + // don't IK to other characters + ray.Init( p1, p2, Vector(-r,-r,0), Vector(r,r,r*2) ); + enginetrace->TraceRay( ray, PhysicsSolidMaskForEntity(), &traceFilter, &trace ); + + if ( trace.m_pEnt != NULL && trace.m_pEnt->GetMoveType() == MOVETYPE_PUSH ) + { + pTarget->SetOwner( trace.m_pEnt->entindex(), trace.m_pEnt->GetAbsOrigin(), trace.m_pEnt->GetAbsAngles() ); + } + else + { + pTarget->ClearOwner( ); + } + + if (trace.startsolid) + { + // trace from back towards hip + Vector tmp = estGround - pTarget->trace.closest; + tmp.NormalizeInPlace(); + ray.Init( estGround - tmp * pTarget->est.height, estGround, Vector(-r,-r,0), Vector(r,r,1) ); + + // debugoverlay->AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 255, 0, 0, 0, 0 ); + + enginetrace->TraceRay( ray, MASK_SOLID, &traceFilter, &trace ); + + if (!trace.startsolid) + { + p1 = trace.endpos; + VectorMA( p1, - pTarget->est.height, up, p2 ); + ray.Init( p1, p2, Vector(-r,-r,0), Vector(r,r,1) ); + + enginetrace->TraceRay( ray, MASK_SOLID, &traceFilter, &trace ); + } + + // debugoverlay->AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 0, 255, 0, 0, 0 ); + } + + + if (!trace.startsolid) + { + if (trace.DidHitWorld()) + { + // clamp normal to 33 degrees + const float limit = 0.832; + float dot = DotProduct(trace.plane.normal, up); + if (dot < limit) + { + Assert( dot >= 0 ); + // subtract out up component + Vector diff = trace.plane.normal - up * dot; + // scale remainder such that it and the up vector are a unit vector + float d = sqrt( (1 - limit * limit) / DotProduct( diff, diff ) ); + trace.plane.normal = up * limit + d * diff; + } + // FIXME: this is wrong with respect to contact position and actual ankle offset + pTarget->SetPosWithNormalOffset( trace.endpos, trace.plane.normal ); + pTarget->SetNormal( trace.plane.normal ); + pTarget->SetOnWorld( true ); + + // only do this on forward tracking or commited IK ground rules + if (pTarget->est.release < 0.1) + { + // keep track of ground height + float offset = DotProduct( pTarget->est.pos, up ); + if (minHeight > offset ) + minHeight = offset; + + if (maxHeight < offset ) + maxHeight = offset; + } + // FIXME: if we don't drop legs, running down hills looks horrible + /* + if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) + { + pTarget->est.pos = estGround; + } + */ + } + else if (trace.DidHitNonWorldEntity()) + { + pTarget->SetPos( trace.endpos ); + pTarget->SetAngles( GetRenderAngles() ); + + // only do this on forward tracking or commited IK ground rules + if (pTarget->est.release < 0.1) + { + float offset = DotProduct( pTarget->est.pos, up ); + if (minHeight > offset ) + minHeight = offset; + + if (maxHeight < offset ) + maxHeight = offset; + } + // FIXME: if we don't drop legs, running down hills looks horrible + /* + if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) + { + pTarget->est.pos = estGround; + } + */ + } + else + { + pTarget->IKFailed( ); + } + } + else + { + if (!trace.DidHitWorld()) + { + pTarget->IKFailed( ); + } + else + { + pTarget->SetPos( trace.endpos ); + pTarget->SetAngles( GetRenderAngles() ); + pTarget->SetOnWorld( true ); + } + } + + /* + debugoverlay->AddTextOverlay( p1, i, 0, "%d %.1f %.1f %.1f ", i, + pTarget->latched.deltaPos.x, pTarget->latched.deltaPos.y, pTarget->latched.deltaPos.z ); + debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -r, -r, -1 ), Vector( r, r, 1), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 0 ); + */ + // debugoverlay->AddBoxOverlay( pTarget->latched.pos, Vector( -2, -2, 2 ), Vector( 2, 2, 6), QAngle( 0, 0, 0 ), 0, 255, 0, 0, 0 ); + } + break; + + case IK_ATTACHMENT: + { + C_BaseEntity *pEntity = NULL; + float flDist = pTarget->est.radius; + + // FIXME: make entity finding sticky! + // FIXME: what should the radius check be? + for ( CEntitySphereQuery sphere( pTarget->est.pos, 64 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + C_BaseAnimating *pAnim = pEntity->GetBaseAnimating( ); + if (!pAnim) + continue; + + int iAttachment = pAnim->LookupAttachment( pTarget->offset.pAttachmentName ); + if (iAttachment <= 0) + continue; + + Vector origin; + QAngle angles; + pAnim->GetAttachment( iAttachment, origin, angles ); + + // debugoverlay->AddBoxOverlay( origin, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 0 ); + + float d = (pTarget->est.pos - origin).Length(); + + if ( d >= flDist) + continue; + + flDist = d; + pTarget->SetPos( origin ); + pTarget->SetAngles( angles ); + // debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 255, 0, 0, 0 ); + } + + if (flDist >= pTarget->est.radius) + { + // debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 0, 255, 0, 0 ); + // no solution, disable ik rule + pTarget->IKFailed( ); + } + } + break; + } + } + +#if defined( HL2_CLIENT_DLL ) + // Let's not bother sending IK info in MP games + if ( 1 == gpGlobals->maxClients ) + { + if (minHeight < FLT_MAX) + { + input->AddIKGroundContactInfo( entindex(), minHeight, maxHeight ); + } + } +#endif + + CBaseEntity::PopEnableAbsRecomputations(); + partition->SuppressLists( curSuppressed, true ); +} + +bool C_BaseAnimating::GetPoseParameterRange( int index, float &minValue, float &maxValue ) +{ + CStudioHdr *pStudioHdr = GetModelPtr(); + + if (pStudioHdr) + { + if (index >= 0 && index < pStudioHdr->GetNumPoseParameters()) + { + const mstudioposeparamdesc_t &pose = pStudioHdr->pPoseParameter( index ); + minValue = pose.start; + maxValue = pose.end; + return true; + } + } + minValue = 0.0f; + maxValue = 1.0f; + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Do HL1 style lipsynch +//----------------------------------------------------------------------------- +void C_BaseAnimating::ControlMouth( CStudioHdr *pstudiohdr ) +{ + if ( !MouthInfo().NeedsEnvelope() ) + return; + + if ( !pstudiohdr ) + return; + + int index = LookupPoseParameter( pstudiohdr, LIPSYNC_POSEPARAM_NAME ); + + if ( index != -1 ) + { + float value = GetMouth()->mouthopen / 64.0; + + float raw = value; + + if ( value > 1.0 ) + value = 1.0; + + float start, end; + GetPoseParameterRange( index, start, end ); + + value = (1.0 - value) * start + value * end; + + //Adrian - Set the pose parameter value. + //It has to be called "mouth". + SetPoseParameter( pstudiohdr, index, value ); + // Reset interpolation here since the client is controlling this rather than the server... + m_iv_flPoseParameter.SetHistoryValuesForItem( index, raw ); + } +} + +CMouthInfo *C_BaseAnimating::GetMouth( void ) +{ + return &m_mouth; +} + +#ifdef DEBUG_BONE_SETUP_THREADING +ConVar cl_warn_thread_contested_bone_setup("cl_warn_thread_contested_bone_setup", "0" ); +#endif +ConVar cl_threaded_bone_setup("cl_threaded_bone_setup", "0", 0, "Enable parallel processing of C_BaseAnimating::SetupBones()" ); + +//----------------------------------------------------------------------------- +// Purpose: Do the default sequence blending rules as done in HL1 +//----------------------------------------------------------------------------- + +static void SetupBonesOnBaseAnimating( C_BaseAnimating *&pBaseAnimating ) +{ + if ( !pBaseAnimating->GetMoveParent() ) + pBaseAnimating->SetupBones( NULL, -1, -1, gpGlobals->curtime ); +} + +static void PreThreadedBoneSetup() +{ + mdlcache->BeginLock(); +} + +static void PostThreadedBoneSetup() +{ + mdlcache->EndLock(); +} + +static bool g_bInThreadedBoneSetup; +static bool g_bDoThreadedBoneSetup; + +void C_BaseAnimating::InitBoneSetupThreadPool() +{ +} + +void C_BaseAnimating::ShutdownBoneSetupThreadPool() +{ +} + +void C_BaseAnimating::ThreadedBoneSetup() +{ + g_bDoThreadedBoneSetup = cl_threaded_bone_setup.GetBool(); + if ( g_bDoThreadedBoneSetup ) + { + int nCount = g_PreviousBoneSetups.Count(); + if ( nCount > 1 ) + { + g_bInThreadedBoneSetup = true; + + ParallelProcess( g_PreviousBoneSetups.Base(), nCount, &SetupBonesOnBaseAnimating, &PreThreadedBoneSetup, &PostThreadedBoneSetup ); + + g_bInThreadedBoneSetup = false; + } + } + g_iPreviousBoneCounter++; + g_PreviousBoneSetups.RemoveAll(); +} + +bool C_BaseAnimating::SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ) +{ + VPROF_BUDGET( "C_BaseAnimating::SetupBones", VPROF_BUDGETGROUP_CLIENT_ANIMATION ); + + if ( !IsBoneAccessAllowed() ) + { + static float lastWarning = 0.0f; + + // Prevent spammage!!! + if ( gpGlobals->realtime >= lastWarning + 1.0f ) + { + DevMsgRT( "*** ERROR: Bone access not allowed (entity %i:%s)\n", index, GetClassname() ); + lastWarning = gpGlobals->realtime; + } + } + + //boneMask = BONE_USED_BY_ANYTHING; // HACK HACK - this is a temp fix until we have accessors for bones to find out where problems are. + + if ( GetSequence() == -1 ) + return false; + + if ( boneMask == -1 ) + { + boneMask = m_iPrevBoneMask; + } + + // We should get rid of this someday when we have solutions for the odd cases where a bone doesn't + // get setup and its transform is asked for later. + if ( cl_SetupAllBones.GetInt() ) + { + boneMask |= BONE_USED_BY_ANYTHING; + } + + // Set up all bones if recording, too + if ( IsToolRecording() ) + { + boneMask |= BONE_USED_BY_ANYTHING; + } + + if ( g_bInThreadedBoneSetup ) + { + if ( !m_BoneSetupLock.TryLock() ) + { + return false; + } + } + +#ifdef DEBUG_BONE_SETUP_THREADING + if ( cl_warn_thread_contested_bone_setup.GetBool() ) + { + if ( !m_BoneSetupLock.TryLock() ) + { + Msg( "Contested bone setup in frame %d!\n", gpGlobals->framecount ); + } + else + { + m_BoneSetupLock.Unlock(); + } + } +#endif + + AUTO_LOCK( m_BoneSetupLock ); + + if ( g_bInThreadedBoneSetup ) + { + m_BoneSetupLock.Unlock(); + } + + if ( m_iMostRecentModelBoneCounter != g_iModelBoneCounter ) + { + // Clear out which bones we've touched this frame if this is + // the first time we've seen this object this frame. + if ( LastBoneChangedTime() >= m_flLastBoneSetupTime ) + { + m_BoneAccessor.SetReadableBones( 0 ); + m_BoneAccessor.SetWritableBones( 0 ); + m_flLastBoneSetupTime = currentTime; + } + m_iPrevBoneMask = m_iAccumulatedBoneMask; + m_iAccumulatedBoneMask = 0; + +#ifdef STUDIO_ENABLE_PERF_COUNTERS + CStudioHdr *hdr = GetModelPtr(); + if (hdr) + { + hdr->ClearPerfCounters(); + } +#endif + } + + int nBoneCount = m_CachedBoneData.Count(); + if ( g_bDoThreadedBoneSetup && !g_bInThreadedBoneSetup && ( nBoneCount >= 16 ) && !GetMoveParent() && m_iMostRecentBoneSetupRequest != g_iPreviousBoneCounter ) + { + m_iMostRecentBoneSetupRequest = g_iPreviousBoneCounter; + Assert( g_PreviousBoneSetups.Find( this ) == -1 ); + g_PreviousBoneSetups.AddToTail( this ); + } + + // Keep track of everthing asked for over the entire frame + m_iAccumulatedBoneMask |= boneMask; + + // Make sure that we know that we've already calculated some bone stuff this time around. + m_iMostRecentModelBoneCounter = g_iModelBoneCounter; + + // Have we cached off all bones meeting the flag set? + if( ( m_BoneAccessor.GetReadableBones() & boneMask ) != boneMask ) + { + MDLCACHE_CRITICAL_SECTION(); + + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr || !hdr->SequencesAvailable() ) + return false; + + // Setup our transform based on render angles and origin. + matrix3x4_t parentTransform; + AngleMatrix( GetRenderAngles(), GetRenderOrigin(), parentTransform ); + + // Load the boneMask with the total of what was asked for last frame. + boneMask |= m_iPrevBoneMask; + + // Allow access to the bones we're setting up so we don't get asserts in here. + int oldReadableBones = m_BoneAccessor.GetReadableBones(); + m_BoneAccessor.SetWritableBones( m_BoneAccessor.GetReadableBones() | boneMask ); + m_BoneAccessor.SetReadableBones( m_BoneAccessor.GetWritableBones() ); + + if (hdr->flags() & STUDIOHDR_FLAGS_STATIC_PROP) + { + MatrixCopy( parentTransform, GetBoneForWrite( 0 ) ); + } + else + { + TrackBoneSetupEnt( this ); + + // This is necessary because it's possible that CalculateIKLocks will trigger our move children + // to call GetAbsOrigin(), and they'll use our OLD bone transforms to get their attachments + // since we're right in the middle of setting up our new transforms. + // + // Setting this flag forces move children to keep their abs transform invalidated. + AddFlag( EFL_SETTING_UP_BONES ); + + // only allocate an ik block if the npc can use it + if ( !m_pIk && hdr->numikchains() > 0 && !(m_EntClientFlags & ENTCLIENTFLAG_DONTUSEIK) ) + m_pIk = new CIKContext; + + Vector pos[MAXSTUDIOBONES]; + Quaternion q[MAXSTUDIOBONES]; + + int bonesMaskNeedRecalc = boneMask | oldReadableBones; // Hack to always recalc bones, to fix the arm jitter in the new CS player anims until Ken makes the real fix + + if ( m_pIk ) + { + if (Teleported() || IsEffectActive(EF_NOINTERP)) + m_pIk->ClearTargets(); + + m_pIk->Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, bonesMaskNeedRecalc ); + } + + // Let pose debugger know that we are blending + g_pPoseDebugger->StartBlending( this, hdr ); + + StandardBlendingRules( hdr, pos, q, currentTime, bonesMaskNeedRecalc ); + + CBoneBitList boneComputed; + // don't calculate IK on ragdolls + if ( m_pIk && !IsRagdoll() ) + { + UpdateIKLocks( currentTime ); + + m_pIk->UpdateTargets( pos, q, m_BoneAccessor.GetBoneArrayForWrite(), boneComputed ); + + CalculateIKLocks( currentTime ); + m_pIk->SolveDependencies( pos, q, m_BoneAccessor.GetBoneArrayForWrite(), boneComputed ); + } + + BuildTransformations( hdr, pos, q, parentTransform, bonesMaskNeedRecalc, boneComputed ); + + RemoveFlag( EFL_SETTING_UP_BONES ); + ControlMouth( hdr ); + } + + if( !( oldReadableBones & BONE_USED_BY_ATTACHMENT ) && ( boneMask & BONE_USED_BY_ATTACHMENT ) ) + { + SetupBones_AttachmentHelper( hdr ); + } + } + + // Do they want to get at the bone transforms? If it's just making sure an aiment has + // its bones setup, it doesn't need the transforms yet. + if ( pBoneToWorldOut ) + { + if ( nMaxBones >= m_CachedBoneData.Count() ) + { + memcpy( pBoneToWorldOut, m_CachedBoneData.Base(), sizeof( matrix3x4_t ) * m_CachedBoneData.Count() ); + } + else + { + Warning( "SetupBones: invalid bone array size (%d - needs %d)\n", nMaxBones, m_CachedBoneData.Count() ); + return false; + } + } + + return true; +} + + +C_BaseAnimating* C_BaseAnimating::FindFollowedEntity() +{ + C_BaseEntity *follow = GetFollowedEntity(); + + if ( !follow ) + return NULL; + + if ( follow->IsDormant() ) + return NULL; + + if ( !follow->GetModel() ) + { + Warning( "mod_studio: MOVETYPE_FOLLOW with no model.\n" ); + return NULL; + } + + if ( modelinfo->GetModelType( follow->GetModel() ) != mod_studio ) + { + Warning( "Attached %s (mod_studio) to %s (%d)\n", + modelinfo->GetModelName( GetModel() ), + modelinfo->GetModelName( follow->GetModel() ), + modelinfo->GetModelType( follow->GetModel() ) ); + return NULL; + } + + return assert_cast< C_BaseAnimating* >( follow ); +} + + + +void C_BaseAnimating::InvalidateBoneCache() +{ + m_iMostRecentModelBoneCounter = g_iModelBoneCounter - 1; + m_flLastBoneSetupTime = -FLT_MAX; +} + + +bool C_BaseAnimating::IsBoneCacheValid() const +{ + return m_iMostRecentModelBoneCounter == g_iModelBoneCounter; +} + + +// Causes an assert to happen if bones or attachments are used while this is false. +struct BoneAccess +{ + BoneAccess() + { + bAllowBoneAccessForNormalModels = false; + bAllowBoneAccessForViewModels = false; + tag = NULL; + } + + bool bAllowBoneAccessForNormalModels; + bool bAllowBoneAccessForViewModels; + char const *tag; +}; + +static CUtlVector< BoneAccess > g_BoneAccessStack; +static BoneAccess g_BoneAcessBase; + +bool C_BaseAnimating::IsBoneAccessAllowed() const +{ + if ( IsViewModel() ) + return g_BoneAcessBase.bAllowBoneAccessForViewModels; + else + return g_BoneAcessBase.bAllowBoneAccessForNormalModels; +} + +// (static function) +void C_BaseAnimating::PushAllowBoneAccess( bool bAllowForNormalModels, bool bAllowForViewModels, char const *tagPush ) +{ + BoneAccess save = g_BoneAcessBase; + g_BoneAccessStack.AddToTail( save ); + + Assert( g_BoneAccessStack.Count() < 32 ); // Most likely we are leaking "PushAllowBoneAccess" calls if PopBoneAccess is never called. Consider using AutoAllowBoneAccess. + g_BoneAcessBase.bAllowBoneAccessForNormalModels = bAllowForNormalModels; + g_BoneAcessBase.bAllowBoneAccessForViewModels = bAllowForViewModels; + g_BoneAcessBase.tag = tagPush; +} + +void C_BaseAnimating::PopBoneAccess( char const *tagPop ) +{ + // Validate that pop matches the push + Assert( ( g_BoneAcessBase.tag == tagPop ) || ( g_BoneAcessBase.tag && g_BoneAcessBase.tag != ( char const * ) 1 && tagPop && tagPop != ( char const * ) 1 && !strcmp( g_BoneAcessBase.tag, tagPop ) ) ); + int lastIndex = g_BoneAccessStack.Count() - 1; + if ( lastIndex < 0 ) + { + Assert( !"C_BaseAnimating::PopBoneAccess: Stack is empty!!!" ); + return; + } + g_BoneAcessBase = g_BoneAccessStack[lastIndex ]; + g_BoneAccessStack.Remove( lastIndex ); +} + +C_BaseAnimating::AutoAllowBoneAccess::AutoAllowBoneAccess( bool bAllowForNormalModels, bool bAllowForViewModels ) +{ + C_BaseAnimating::PushAllowBoneAccess( bAllowForNormalModels, bAllowForViewModels, ( char const * ) 1 ); +} + +C_BaseAnimating::AutoAllowBoneAccess::~AutoAllowBoneAccess( ) +{ + C_BaseAnimating::PopBoneAccess( ( char const * ) 1 ); +} + +// (static function) +void C_BaseAnimating::InvalidateBoneCaches() +{ + g_iModelBoneCounter++; +} + + +ConVar r_drawothermodels( "r_drawothermodels", "1", FCVAR_CHEAT, "0=Off, 1=Normal, 2=Wireframe" ); + +//----------------------------------------------------------------------------- +// Purpose: Draws the object +// Input : flags - +//----------------------------------------------------------------------------- +int C_BaseAnimating::DrawModel( int flags ) +{ + VPROF_BUDGET( "C_BaseAnimating::DrawModel", VPROF_BUDGETGROUP_MODEL_RENDERING ); + if ( !m_bReadyToDraw ) + return 0; + + int drawn = 0; + + if ( r_drawothermodels.GetInt() ) + { + MDLCACHE_CRITICAL_SECTION(); + + int extraFlags = 0; + if ( r_drawothermodels.GetInt() == 2 ) + { + extraFlags |= STUDIO_WIREFRAME; + } + + if ( flags & STUDIO_SHADOWDEPTHTEXTURE ) + { + extraFlags |= STUDIO_SHADOWDEPTHTEXTURE; + } + + // Necessary for lighting blending + CreateModelInstance(); + + if ( !IsFollowingEntity() ) + { + drawn = InternalDrawModel( flags|extraFlags ); + } + else + { + // this doesn't draw unless master entity is visible and it's a studio model!!! + C_BaseAnimating *follow = FindFollowedEntity(); + if ( follow ) + { + // recompute master entity bone structure + int baseDrawn = follow->DrawModel( 0 ); + + // draw entity + // FIXME: Currently only draws if aiment is drawn. + // BUGBUG: Fixup bbox and do a separate cull for follow object + if ( baseDrawn ) + { + drawn = InternalDrawModel( STUDIO_RENDER|extraFlags ); + } + } + } + } + + // If we're visualizing our bboxes, draw them + DrawBBoxVisualizations(); + + return drawn; +} + +//----------------------------------------------------------------------------- +// Gets the hitbox-to-world transforms, returns false if there was a problem +//----------------------------------------------------------------------------- +bool C_BaseAnimating::HitboxToWorldTransforms( matrix3x4_t *pHitboxToWorld[MAXSTUDIOBONES] ) +{ + MDLCACHE_CRITICAL_SECTION(); + + if ( !GetModel() ) + return false; + + CStudioHdr *pStudioHdr = GetModelPtr(); + if (!pStudioHdr) + return false; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( GetHitboxSet() ); + if ( !set ) + return false; + + if ( !set->numhitboxes ) + return false; + + CBoneCache *pCache = GetBoneCache( pStudioHdr ); + pCache->ReadCachedBonePointers( pHitboxToWorld, pStudioHdr->numbones() ); + return true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool C_BaseAnimating::OnPostInternalDrawModel( ClientModelRenderInfo_t *pInfo ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool C_BaseAnimating::OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ) +{ + if ( m_hLightingOriginRelative.Get() ) + { + C_InfoLightingRelative *pInfoLighting = assert_cast( m_hLightingOriginRelative.Get() ); + pInfoLighting->GetLightingOffset( pInfo->lightingOffset ); + pInfo->pLightingOffset = &pInfo->lightingOffset; + } + if ( m_hLightingOrigin ) + { + pInfo->pLightingOrigin = &(m_hLightingOrigin->GetAbsOrigin()); + } + + return true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void C_BaseAnimating::DoInternalDrawModel( ClientModelRenderInfo_t *pInfo, DrawModelState_t *pState, matrix3x4_t *pBoneToWorldArray ) +{ + if ( pState) + { + modelrender->DrawModelExecute( *pState, *pInfo, pBoneToWorldArray ); + } + + if ( vcollide_wireframe.GetBool() ) + { + if ( IsRagdoll() ) + { + m_pRagdoll->DrawWireframe(); + } + else if ( IsSolid() && CollisionProp()->GetSolid() == SOLID_VPHYSICS ) + { + vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() ); + if ( pCollide && pCollide->solidCount == 1 ) + { + static color32 debugColor = {0,255,255,0}; + matrix3x4_t matrix; + AngleMatrix( GetAbsAngles(), GetAbsOrigin(), matrix ); + engine->DebugDrawPhysCollide( pCollide->solids[0], NULL, matrix, debugColor ); + if ( VPhysicsGetObject() ) + { + static color32 debugColorPhys = {255,0,0,0}; + matrix3x4_t matrix; + VPhysicsGetObject()->GetPositionMatrix( &matrix ); + engine->DebugDrawPhysCollide( pCollide->solids[0], NULL, matrix, debugColorPhys ); + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Draws the object +// Input : flags - +//----------------------------------------------------------------------------- +int C_BaseAnimating::InternalDrawModel( int flags ) +{ + VPROF( "C_BaseAnimating::InternalDrawModel" ); + + if ( !GetModel() ) + return 0; + + // This should never happen, but if the server class hierarchy has bmodel entities derived from CBaseAnimating or does a + // SetModel with the wrong type of model, this could occur. + if ( modelinfo->GetModelType( GetModel() ) != mod_studio ) + { + return BaseClass::DrawModel( flags ); + } + + // Make sure hdr is valid for drawing + if ( !GetModelPtr() ) + return 0; + + if ( IsEffectActive( EF_ITEM_BLINK ) ) + { + flags |= STUDIO_ITEM_BLINK; + } + + ClientModelRenderInfo_t info; + ClientModelRenderInfo_t *pInfo; + + pInfo = &info; + + pInfo->flags = flags; + pInfo->pRenderable = this; + pInfo->instance = GetModelInstance(); + pInfo->entity_index = index; + pInfo->pModel = GetModel(); + pInfo->origin = GetRenderOrigin(); + pInfo->angles = GetRenderAngles(); + pInfo->skin = GetSkin(); + pInfo->body = m_nBody; + pInfo->hitboxset = m_nHitboxSet; + + if ( !OnInternalDrawModel( pInfo ) ) + { + return 0; + } + + Assert( !pInfo->pModelToWorld); + if ( !pInfo->pModelToWorld ) + { + pInfo->pModelToWorld = &pInfo->modelToWorld; + + // Turns the origin + angles into a matrix + AngleMatrix( pInfo->angles, pInfo->origin, pInfo->modelToWorld ); + } + + DrawModelState_t state; + matrix3x4_t *pBoneToWorld; + bool bMarkAsDrawn = modelrender->DrawModelSetup( *pInfo, &state, NULL, &pBoneToWorld ); + DoInternalDrawModel( pInfo, ( bMarkAsDrawn && ( pInfo->flags & STUDIO_RENDER ) ) ? &state : NULL, pBoneToWorld ); + + OnPostInternalDrawModel( pInfo ); + + return bMarkAsDrawn; +} + +extern ConVar muzzleflash_light; + +void C_BaseAnimating::ProcessMuzzleFlashEvent() +{ + // If we have an attachment, then stick a light on it. + if ( muzzleflash_light.GetBool() ) + { + //FIXME: We should really use a named attachment for this + if ( m_Attachments.Count() > 0 ) + { + Vector vAttachment; + QAngle dummyAngles; + GetAttachment( 1, vAttachment, dummyAngles ); + + // Make an elight + dlight_t *el = effects->CL_AllocElight( LIGHT_INDEX_MUZZLEFLASH + index ); + el->origin = vAttachment; + el->radius = random->RandomInt( 32, 64 ); + el->decay = el->radius / 0.05f; + el->die = gpGlobals->curtime + 0.05f; + el->color.r = 255; + el->color.g = 192; + el->color.b = 64; + el->color.exponent = 5; + } + } +} + +//----------------------------------------------------------------------------- +// Internal routine to process animation events for studiomodels +//----------------------------------------------------------------------------- +void C_BaseAnimating::DoAnimationEvents( CStudioHdr *pStudioHdr ) +{ + if ( !pStudioHdr ) + return; + + bool watch = false; // Q_strstr( hdr->name, "rifle" ) ? true : false; + + //Adrian: eh? This should never happen. + if ( GetSequence() == -1 ) + return; + + // build root animation + float flEventCycle = GetCycle(); + + // If we're invisible, don't draw the muzzle flash + bool bIsInvisible = !IsVisible() && !IsViewModel() && !IsMenuModel(); + + if ( bIsInvisible && !clienttools->IsInRecordingMode() ) + return; + + // add in muzzleflash effect + if ( ShouldMuzzleFlash() ) + { + DisableMuzzleFlash(); + + ProcessMuzzleFlashEvent(); + } + + // If we're invisible, don't process animation events. + if ( bIsInvisible ) + return; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( GetSequence() ); + + if (seqdesc.numevents == 0) + return; + + // Forces anim event indices to get set and returns pEvent(0); + mstudioevent_t *pevent = GetEventIndexForSequence( seqdesc ); + + if ( watch ) + { + Msg( "%i cycle %f\n", gpGlobals->tickcount, GetCycle() ); + } + + bool resetEvents = m_nResetEventsParity != m_nPrevResetEventsParity; + m_nPrevResetEventsParity = m_nResetEventsParity; + + if (m_nEventSequence != GetSequence() || resetEvents ) + { + if ( watch ) + { + Msg( "new seq: %i - old seq: %i - reset: %s - m_flCycle %f - Model Name: %s - (time %.3f)\n", + GetSequence(), m_nEventSequence, + resetEvents ? "true" : "false", + GetCycle(), pStudioHdr->pszName(), + gpGlobals->curtime); + } + + m_nEventSequence = GetSequence(); + flEventCycle = 0.0f; + m_flPrevEventCycle = -0.01; // back up to get 0'th frame animations + } + + // stalled? + if (flEventCycle == m_flPrevEventCycle) + return; + + if ( watch ) + { + Msg( "%i (seq %d cycle %.3f ) evcycle %.3f prevevcycle %.3f (time %.3f)\n", + gpGlobals->tickcount, + GetSequence(), + GetCycle(), + flEventCycle, + m_flPrevEventCycle, + gpGlobals->curtime ); + } + + // check for looping + BOOL bLooped = false; + if (flEventCycle <= m_flPrevEventCycle) + { + if (m_flPrevEventCycle - flEventCycle > 0.5) + { + bLooped = true; + } + else + { + // things have backed up, which is bad since it'll probably result in a hitch in the animation playback + // but, don't play events again for the same time slice + return; + } + } + + // This makes sure events that occur at the end of a sequence occur are + // sent before events that occur at the beginning of a sequence. + if (bLooped) + { + for (int i = 0; i < (int)seqdesc.numevents; i++) + { + // ignore all non-client-side events + + if ( pevent[i].type & AE_TYPE_NEWEVENTSYSTEM ) + { + if ( !( pevent[i].type & AE_TYPE_CLIENT ) ) + continue; + } + else if ( pevent[i].event < 5000 ) //Adrian - Support the old event system + continue; + + if ( pevent[i].cycle <= m_flPrevEventCycle ) + continue; + + if ( watch ) + { + Msg( "%i FE %i Looped cycle %f, prev %f ev %f (time %.3f)\n", + gpGlobals->tickcount, + pevent[i].event, + pevent[i].cycle, + m_flPrevEventCycle, + flEventCycle, + gpGlobals->curtime ); + } + + + FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ); + } + + // Necessary to get the next loop working + m_flPrevEventCycle = -0.01; + } + + for (int i = 0; i < (int)seqdesc.numevents; i++) + { + if ( pevent[i].type & AE_TYPE_NEWEVENTSYSTEM ) + { + if ( !( pevent[i].type & AE_TYPE_CLIENT ) ) + continue; + } + else if ( pevent[i].event < 5000 ) //Adrian - Support the old event system + continue; + + if ( (pevent[i].cycle > m_flPrevEventCycle && pevent[i].cycle <= flEventCycle) ) + { + if ( watch ) + { + Msg( "%i (seq: %d) FE %i Normal cycle %f, prev %f ev %f (time %.3f)\n", + gpGlobals->tickcount, + GetSequence(), + pevent[i].event, + pevent[i].cycle, + m_flPrevEventCycle, + flEventCycle, + gpGlobals->curtime ); + } + + FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ); + } + } + + m_flPrevEventCycle = flEventCycle; +} + +//----------------------------------------------------------------------------- +// Purpose: Parses a muzzle effect event and sends it out for drawing +// Input : *options - event parameters in text format +// isFirstPerson - whether this is coming from an NPC or the player +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseAnimating::DispatchMuzzleEffect( const char *options, bool isFirstPerson ) +{ + const char *p = options; + char token[128]; + int weaponType = 0; + + // Get the first parameter + p = nexttoken( token, p, ' ' ); + + // Find the weapon type + if ( token ) + { + //TODO: Parse the type from a list instead + if ( Q_stricmp( token, "COMBINE" ) == 0 ) + { + weaponType = MUZZLEFLASH_COMBINE; + } + else if ( Q_stricmp( token, "SMG1" ) == 0 ) + { + weaponType = MUZZLEFLASH_SMG1; + } + else if ( Q_stricmp( token, "PISTOL" ) == 0 ) + { + weaponType = MUZZLEFLASH_PISTOL; + } + else if ( Q_stricmp( token, "SHOTGUN" ) == 0 ) + { + weaponType = MUZZLEFLASH_SHOTGUN; + } + else if ( Q_stricmp( token, "357" ) == 0 ) + { + weaponType = MUZZLEFLASH_357; + } + else if ( Q_stricmp( token, "RPG" ) == 0 ) + { + weaponType = MUZZLEFLASH_RPG; + } + else + { + //NOTENOTE: This means you specified an invalid muzzleflash type, check your spelling? + Assert( 0 ); + } + } + else + { + //NOTENOTE: This means that there wasn't a proper parameter passed into the animevent + Assert( 0 ); + return false; + } + + // Get the second parameter + p = nexttoken( token, p, ' ' ); + + int attachmentIndex = -1; + + // Find the attachment name + if ( token ) + { + attachmentIndex = LookupAttachment( token ); + + // Found an invalid attachment + if ( attachmentIndex <= 0 ) + { + //NOTENOTE: This means that the attachment you're trying to use is invalid + Assert( 0 ); + return false; + } + } + else + { + //NOTENOTE: This means that there wasn't a proper parameter passed into the animevent + Assert( 0 ); + return false; + } + + // Send it out + tempents->MuzzleFlash( weaponType, GetRefEHandle(), attachmentIndex, isFirstPerson ); + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void MaterialFootstepSound( C_BaseAnimating *pEnt, bool bLeftFoot, float flVolume ) +{ + trace_t tr; + Vector traceStart; + QAngle angles; + + int attachment; + + //!!!PERF - These string lookups here aren't the swiftest, but + // this doesn't get called very frequently unless a lot of NPCs + // are using this code. + if( bLeftFoot ) + { + attachment = pEnt->LookupAttachment( "LeftFoot" ); + } + else + { + attachment = pEnt->LookupAttachment( "RightFoot" ); + } + + if( attachment == -1 ) + { + // Exit if this NPC doesn't have the proper attachments. + return; + } + + pEnt->GetAttachment( attachment, traceStart, angles ); + + UTIL_TraceLine( traceStart, traceStart - Vector( 0, 0, 48.0f), MASK_SHOT_HULL, pEnt, COLLISION_GROUP_NONE, &tr ); + if( tr.fraction < 1.0 && tr.m_pEnt ) + { + surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps ); + if( psurf ) + { + EmitSound_t params; + if( bLeftFoot ) + { + params.m_pSoundName = physprops->GetString(psurf->sounds.stepleft); + } + else + { + params.m_pSoundName = physprops->GetString(psurf->sounds.stepright); + } + + CPASAttenuationFilter filter( pEnt, params.m_pSoundName ); + + params.m_bWarnOnDirectWaveReference = true; + params.m_flVolume = flVolume; + + pEnt->EmitSound( filter, pEnt->entindex(), params ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *origin - +// *angles - +// event - +// *options - +// numAttachments - +// attachments[] - +//----------------------------------------------------------------------------- +void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + Vector attachOrigin; + QAngle attachAngles; + + switch( event ) + { + case AE_CL_CREATE_PARTICLE_EFFECT: + { + int iAttachment = -1; + int iAttachType = PATTACH_ABSORIGIN_FOLLOW; + char token[256]; + char szParticleEffect[256]; + + // Get the particle effect name + const char *p = options; + p = nexttoken(token, p, ' '); + if ( token ) + { + Q_strncpy( szParticleEffect, token, sizeof(szParticleEffect) ); + } + + // Get the attachment type + p = nexttoken(token, p, ' '); + if ( token ) + { + iAttachType = GetAttachTypeFromString( token ); + if ( iAttachType == -1 ) + { + Warning("Invalid attach type specified for particle effect anim event. Trying to spawn effect '%s' with attach type of '%s'\n", szParticleEffect, token ); + return; + } + } + + // Get the attachment point index + p = nexttoken(token, p, ' '); + if ( token ) + { + iAttachment = atoi(token); + + // See if we can find any attachment points matching the name + if ( token[0] != '0' && iAttachment == 0 ) + { + iAttachment = LookupAttachment( token ); + if ( iAttachment == -1 ) + { + Warning("Failed to find attachment point specified for particle effect anim event. Trying to spawn effect '%s' on attachment named '%s'\n", szParticleEffect, token ); + return; + } + } + } + + // Spawn the particle effect + ParticleProp()->Create( szParticleEffect, (ParticleAttachment_t)iAttachType, iAttachment ); + } + break; + + case AE_CL_PLAYSOUND: + { + CLocalPlayerFilter filter; + + if ( m_Attachments.Count() > 0) + { + GetAttachment( 1, attachOrigin, attachAngles ); + EmitSound( filter, GetSoundSourceIndex(), options, &attachOrigin ); + } + else + { + EmitSound( filter, GetSoundSourceIndex(), options, &GetAbsOrigin() ); + } + } + break; + case AE_CL_STOPSOUND: + { + StopSound( GetSoundSourceIndex(), options ); + } + break; + + case CL_EVENT_FOOTSTEP_LEFT: + { +#ifndef HL2MP + char pSoundName[256]; + if ( !options || !options[0] ) + { + options = "NPC_CombineS"; + } + + Vector vel; + EstimateAbsVelocity( vel ); + + // If he's moving fast enough, play the run sound + if ( vel.Length2DSqr() > RUN_SPEED_ESTIMATE_SQR ) + { + Q_snprintf( pSoundName, 256, "%s.RunFootstepLeft", options ); + } + else + { + Q_snprintf( pSoundName, 256, "%s.FootstepLeft", options ); + } + EmitSound( pSoundName ); +#endif + } + break; + + case CL_EVENT_FOOTSTEP_RIGHT: + { +#ifndef HL2MP + char pSoundName[256]; + if ( !options || !options[0] ) + { + options = "NPC_CombineS"; + } + + Vector vel; + EstimateAbsVelocity( vel ); + // If he's moving fast enough, play the run sound + if ( vel.Length2DSqr() > RUN_SPEED_ESTIMATE_SQR ) + { + Q_snprintf( pSoundName, 256, "%s.RunFootstepRight", options ); + } + else + { + Q_snprintf( pSoundName, 256, "%s.FootstepRight", options ); + } + EmitSound( pSoundName ); +#endif + } + break; + + case CL_EVENT_MFOOTSTEP_LEFT: + { + MaterialFootstepSound( this, true, VOL_NORM * 0.5f ); + } + break; + + case CL_EVENT_MFOOTSTEP_RIGHT: + { + MaterialFootstepSound( this, false, VOL_NORM * 0.5f ); + } + break; + + case CL_EVENT_MFOOTSTEP_LEFT_LOUD: + { + MaterialFootstepSound( this, true, VOL_NORM ); + } + break; + + case CL_EVENT_MFOOTSTEP_RIGHT_LOUD: + { + MaterialFootstepSound( this, false, VOL_NORM ); + } + break; + + // Eject brass + case CL_EVENT_EJECTBRASS1: + if ( m_Attachments.Count() > 0 ) + { + if ( MainViewOrigin().DistToSqr( GetAbsOrigin() ) < (256 * 256) ) + { + Vector attachOrigin; + QAngle attachAngles; + + if( GetAttachment( 2, attachOrigin, attachAngles ) ) + { + tempents->EjectBrass( attachOrigin, attachAngles, GetAbsAngles(), atoi( options ) ); + } + } + } + break; + + case AE_MUZZLEFLASH: + { + // Send out the effect for a player + DispatchMuzzleEffect( options, true ); + break; + } + + case AE_NPC_MUZZLEFLASH: + { + // Send out the effect for an NPC + DispatchMuzzleEffect( options, false ); + break; + } + + // OBSOLETE EVENTS. REPLACED BY NEWER SYSTEMS. + // See below in FireObsoleteEvent() for comments on what to use instead. + case AE_CLIENT_EFFECT_ATTACH: + case CL_EVENT_DISPATCHEFFECT0: + case CL_EVENT_DISPATCHEFFECT1: + case CL_EVENT_DISPATCHEFFECT2: + case CL_EVENT_DISPATCHEFFECT3: + case CL_EVENT_DISPATCHEFFECT4: + case CL_EVENT_DISPATCHEFFECT5: + case CL_EVENT_DISPATCHEFFECT6: + case CL_EVENT_DISPATCHEFFECT7: + case CL_EVENT_DISPATCHEFFECT8: + case CL_EVENT_DISPATCHEFFECT9: + case CL_EVENT_MUZZLEFLASH0: + case CL_EVENT_MUZZLEFLASH1: + case CL_EVENT_MUZZLEFLASH2: + case CL_EVENT_MUZZLEFLASH3: + case CL_EVENT_NPC_MUZZLEFLASH0: + case CL_EVENT_NPC_MUZZLEFLASH1: + case CL_EVENT_NPC_MUZZLEFLASH2: + case CL_EVENT_NPC_MUZZLEFLASH3: + case CL_EVENT_SPARK0: + case CL_EVENT_SOUND: + FireObsoleteEvent( origin, angles, event, options ); + break; + + case AE_CL_ENABLE_BODYGROUP: + { + int index = FindBodygroupByName( options ); + if ( index >= 0 ) + { + SetBodygroup( index, 1 ); + } + } + break; + + case AE_CL_DISABLE_BODYGROUP: + { + int index = FindBodygroupByName( options ); + if ( index >= 0 ) + { + SetBodygroup( index, 0 ); + } + } + break; + + case AE_CL_BODYGROUP_SET_VALUE: + { + char szBodygroupName[256]; + int value = 0; + + char token[256]; + + const char *p = options; + + // Bodygroup Name + p = nexttoken(token, p, ' '); + if ( token ) + { + Q_strncpy( szBodygroupName, token, sizeof(szBodygroupName) ); + } + + // Get the desired value + p = nexttoken(token, p, ' '); + if ( token ) + { + value = atoi( token ); + } + + int index = FindBodygroupByName( szBodygroupName ); + if ( index >= 0 ) + { + SetBodygroup( index, value ); + } + } + break; + + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: These events are all obsolete events, left here to support old games. +// Their systems have all been replaced with better ones. +//----------------------------------------------------------------------------- +void C_BaseAnimating::FireObsoleteEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + Vector attachOrigin; + QAngle attachAngles; + + switch( event ) + { + // Obsolete. Use the AE_CL_CREATE_PARTICLE_EFFECT event instead, which uses the artist driven particle system & editor. + case AE_CLIENT_EFFECT_ATTACH: + { + int iAttachment = -1; + int iParam = 0; + char token[128]; + char effectFunc[128]; + + const char *p = options; + + p = nexttoken(token, p, ' '); + + if( token ) + { + Q_strncpy( effectFunc, token, sizeof(effectFunc) ); + } + + p = nexttoken(token, p, ' '); + + if( token ) + { + iAttachment = atoi(token); + } + + p = nexttoken(token, p, ' '); + + if( token ) + { + iParam = atoi(token); + } + + if ( iAttachment != -1 && m_Attachments.Count() >= iAttachment ) + { + GetAttachment( iAttachment, attachOrigin, attachAngles ); + + // Fill out the generic data + CEffectData data; + data.m_vOrigin = attachOrigin; + data.m_vAngles = attachAngles; + AngleVectors( attachAngles, &data.m_vNormal ); + data.m_hEntity = GetRefEHandle(); + data.m_nAttachmentIndex = iAttachment + 1; + data.m_fFlags = iParam; + + DispatchEffect( effectFunc, data ); + } + } + break; + + // Obsolete. Use the AE_CL_CREATE_PARTICLE_EFFECT event instead, which uses the artist driven particle system & editor. + case CL_EVENT_DISPATCHEFFECT0: + case CL_EVENT_DISPATCHEFFECT1: + case CL_EVENT_DISPATCHEFFECT2: + case CL_EVENT_DISPATCHEFFECT3: + case CL_EVENT_DISPATCHEFFECT4: + case CL_EVENT_DISPATCHEFFECT5: + case CL_EVENT_DISPATCHEFFECT6: + case CL_EVENT_DISPATCHEFFECT7: + case CL_EVENT_DISPATCHEFFECT8: + case CL_EVENT_DISPATCHEFFECT9: + { + int iAttachment = -1; + + // First person muzzle flashes + switch (event) + { + case CL_EVENT_DISPATCHEFFECT0: + iAttachment = 0; + break; + + case CL_EVENT_DISPATCHEFFECT1: + iAttachment = 1; + break; + + case CL_EVENT_DISPATCHEFFECT2: + iAttachment = 2; + break; + + case CL_EVENT_DISPATCHEFFECT3: + iAttachment = 3; + break; + + case CL_EVENT_DISPATCHEFFECT4: + iAttachment = 4; + break; + + case CL_EVENT_DISPATCHEFFECT5: + iAttachment = 5; + break; + + case CL_EVENT_DISPATCHEFFECT6: + iAttachment = 6; + break; + + case CL_EVENT_DISPATCHEFFECT7: + iAttachment = 7; + break; + + case CL_EVENT_DISPATCHEFFECT8: + iAttachment = 8; + break; + + case CL_EVENT_DISPATCHEFFECT9: + iAttachment = 9; + break; + } + + if ( iAttachment != -1 && m_Attachments.Count() > iAttachment ) + { + GetAttachment( iAttachment+1, attachOrigin, attachAngles ); + + // Fill out the generic data + CEffectData data; + data.m_vOrigin = attachOrigin; + data.m_vAngles = attachAngles; + AngleVectors( attachAngles, &data.m_vNormal ); + data.m_hEntity = GetRefEHandle(); + data.m_nAttachmentIndex = iAttachment + 1; + + DispatchEffect( options, data ); + } + } + break; + + // Obsolete. Use the AE_MUZZLEFLASH / AE_NPC_MUZZLEFLASH events instead. + case CL_EVENT_MUZZLEFLASH0: + case CL_EVENT_MUZZLEFLASH1: + case CL_EVENT_MUZZLEFLASH2: + case CL_EVENT_MUZZLEFLASH3: + case CL_EVENT_NPC_MUZZLEFLASH0: + case CL_EVENT_NPC_MUZZLEFLASH1: + case CL_EVENT_NPC_MUZZLEFLASH2: + case CL_EVENT_NPC_MUZZLEFLASH3: + { + int iAttachment = -1; + bool bFirstPerson = true; + + // First person muzzle flashes + switch (event) + { + case CL_EVENT_MUZZLEFLASH0: + iAttachment = 0; + break; + + case CL_EVENT_MUZZLEFLASH1: + iAttachment = 1; + break; + + case CL_EVENT_MUZZLEFLASH2: + iAttachment = 2; + break; + + case CL_EVENT_MUZZLEFLASH3: + iAttachment = 3; + break; + + // Third person muzzle flashes + case CL_EVENT_NPC_MUZZLEFLASH0: + iAttachment = 0; + bFirstPerson = false; + break; + + case CL_EVENT_NPC_MUZZLEFLASH1: + iAttachment = 1; + bFirstPerson = false; + break; + + case CL_EVENT_NPC_MUZZLEFLASH2: + iAttachment = 2; + bFirstPerson = false; + break; + + case CL_EVENT_NPC_MUZZLEFLASH3: + iAttachment = 3; + bFirstPerson = false; + break; + } + + if ( iAttachment != -1 && m_Attachments.Count() > iAttachment ) + { + GetAttachment( iAttachment+1, attachOrigin, attachAngles ); + int entId = render->GetViewEntity(); + ClientEntityHandle_t hEntity = ClientEntityList().EntIndexToHandle( entId ); + tempents->MuzzleFlash( attachOrigin, attachAngles, atoi( options ), hEntity, bFirstPerson ); + } + } + break; + + // Obsolete: Use the AE_CL_CREATE_PARTICLE_EFFECT event instead, which uses the artist driven particle system & editor. + case CL_EVENT_SPARK0: + { + Vector vecForward; + GetAttachment( 1, attachOrigin, attachAngles ); + AngleVectors( attachAngles, &vecForward ); + g_pEffects->Sparks( attachOrigin, atoi( options ), 1, &vecForward ); + } + break; + + // Obsolete: Use the AE_CL_PLAYSOUND event instead, which doesn't rely on a magic number in the .qc + case CL_EVENT_SOUND: + { + CLocalPlayerFilter filter; + + if ( m_Attachments.Count() > 0) + { + GetAttachment( 1, attachOrigin, attachAngles ); + EmitSound( filter, GetSoundSourceIndex(), options, &attachOrigin ); + } + else + { + EmitSound( filter, GetSoundSourceIndex(), options ); + } + } + break; + + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseAnimating::IsSelfAnimating() +{ + if ( m_bClientSideAnimation ) + return true; + + // Yes, we use animtime. + int iMoveType = GetMoveType(); + if ( iMoveType != MOVETYPE_STEP && + iMoveType != MOVETYPE_NONE && + iMoveType != MOVETYPE_WALK && + iMoveType != MOVETYPE_FLY && + iMoveType != MOVETYPE_FLYGRAVITY ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Called by networking code when an entity is new to the PVS or comes down with the EF_NOINTERP flag set. +// The position history data is flushed out right after this call, so we need to store off the current data +// in the latched fields so we try to interpolate +// Input : *ent - +// full_reset - +//----------------------------------------------------------------------------- +void C_BaseAnimating::ResetLatched( void ) +{ + // Reset the IK + if ( m_pIk ) + { + delete m_pIk; + m_pIk = NULL; + } + + BaseClass::ResetLatched(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- + +bool C_BaseAnimating::Interpolate( float flCurrentTime ) +{ + // ragdolls don't need interpolation + if ( m_pRagdoll ) + return true; + + VPROF( "C_BaseAnimating::Interpolate" ); + + Vector oldOrigin; + QAngle oldAngles; + float flOldCycle = GetCycle(); + int nChangeFlags = 0; + + if ( !m_bClientSideAnimation ) + m_iv_flCycle.SetLooping( IsSequenceLooping( GetSequence() ) ); + + int bNoMoreChanges; + int retVal = BaseInterpolatePart1( flCurrentTime, oldOrigin, oldAngles, bNoMoreChanges ); + if ( retVal == INTERPOLATE_STOP ) + { + if ( bNoMoreChanges ) + RemoveFromInterpolationList(); + return true; + } + + + // Did cycle change? + if( GetCycle() != flOldCycle ) + nChangeFlags |= ANIMATION_CHANGED; + + if ( bNoMoreChanges ) + RemoveFromInterpolationList(); + + BaseInterpolatePart2( oldOrigin, oldAngles, nChangeFlags ); + return true; +} + + +//----------------------------------------------------------------------------- +// returns true if we're currently being ragdolled +//----------------------------------------------------------------------------- +bool C_BaseAnimating::IsRagdoll() const +{ + return m_pRagdoll && (m_nRenderFX == kRenderFxRagdoll); +} + + +//----------------------------------------------------------------------------- +// implements these so ragdolls can handle frustum culling & leaf visibility +//----------------------------------------------------------------------------- + +void C_BaseAnimating::GetRenderBounds( Vector& theMins, Vector& theMaxs ) +{ + if ( IsRagdoll() ) + { + m_pRagdoll->GetRagdollBounds( theMins, theMaxs ); + } + else if ( GetModel() ) + { + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( !pStudioHdr|| !pStudioHdr->SequencesAvailable() || GetSequence() == -1 ) + { + theMins = vec3_origin; + theMaxs = vec3_origin; + return; + } + if (!VectorCompare( vec3_origin, pStudioHdr->view_bbmin() ) || !VectorCompare( vec3_origin, pStudioHdr->view_bbmax() )) + { + // clipping bounding box + VectorCopy ( pStudioHdr->view_bbmin(), theMins); + VectorCopy ( pStudioHdr->view_bbmax(), theMaxs); + } + else + { + // movement bounding box + VectorCopy ( pStudioHdr->hull_min(), theMins); + VectorCopy ( pStudioHdr->hull_max(), theMaxs); + } + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( GetSequence() ); + VectorMin( seqdesc.bbmin, theMins, theMins ); + VectorMax( seqdesc.bbmax, theMaxs, theMaxs ); + } + else + { + theMins = vec3_origin; + theMaxs = vec3_origin; + } +} + + +//----------------------------------------------------------------------------- +// implements these so ragdolls can handle frustum culling & leaf visibility +//----------------------------------------------------------------------------- +const Vector& C_BaseAnimating::GetRenderOrigin( void ) +{ + if ( IsRagdoll() ) + { + return m_pRagdoll->GetRagdollOrigin(); + } + else + { + return BaseClass::GetRenderOrigin(); + } +} + +const QAngle& C_BaseAnimating::GetRenderAngles( void ) +{ + if ( IsRagdoll() ) + { + return vec3_angle; + + } + else + { + return BaseClass::GetRenderAngles(); + } +} + +void C_BaseAnimating::RagdollMoved( void ) +{ + SetAbsOrigin( m_pRagdoll->GetRagdollOrigin() ); + SetAbsAngles( vec3_angle ); + + Vector mins, maxs; + m_pRagdoll->GetRagdollBounds( mins, maxs ); + SetCollisionBounds( mins, maxs ); + + // If the ragdoll moves, its render-to-texture shadow is dirty + InvalidatePhysicsRecursive( ANIMATION_CHANGED ); +} + + +//----------------------------------------------------------------------------- +// Purpose: My physics object has been updated, react or extract data +//----------------------------------------------------------------------------- +void C_BaseAnimating::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + // FIXME: Should make sure the physics objects being passed in + // is the ragdoll physics object, but I think it's pretty safe not to check + if (IsRagdoll()) + { + m_pRagdoll->VPhysicsUpdate( pPhysics ); + + RagdollMoved(); + + return; + } + + BaseClass::VPhysicsUpdate( pPhysics ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_BaseAnimating::PreDataUpdate( DataUpdateType_t updateType ) +{ + m_flOldCycle = GetCycle(); + m_nOldSequence = GetSequence(); + + int i; + for ( i=0;iflags() & STUDIOHDR_FLAGS_STATIC_PROP ) ) + { + m_iv_flCycle.Reset(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_BaseAnimating::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + m_bLastClientSideFrameReset = m_bClientSideFrameReset; +} + +void C_BaseAnimating::ForceSetupBonesAtTime( matrix3x4_t *pBonesOut, float flTime ) +{ + // blow the cached prev bones + InvalidateBoneCache(); + + // reset root position to flTime + Interpolate( flTime ); + + // Setup bone state at the given time + SetupBones( pBonesOut, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, flTime ); +} + +void C_BaseAnimating::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ) +{ + ForceSetupBonesAtTime( pDeltaBones0, gpGlobals->curtime - boneDt ); + ForceSetupBonesAtTime( pDeltaBones1, gpGlobals->curtime ); + float ragdollCreateTime = PhysGetSyncCreateTime(); + if ( ragdollCreateTime != gpGlobals->curtime ) + { + // The next simulation frame begins before the end of this frame + // so initialize the ragdoll at that time so that it will reach the current + // position at curtime. Otherwise the ragdoll will simulate forward from curtime + // and pop into the future a bit at this point of transition + ForceSetupBonesAtTime( pCurrentBones, ragdollCreateTime ); + } + else + { + memcpy( pCurrentBones, m_CachedBoneData.Base(), sizeof( matrix3x4_t ) * m_CachedBoneData.Count() ); + } +} + +C_BaseAnimating *C_BaseAnimating::CreateRagdollCopy() +{ + //Adrian: We now create a separate entity that becomes this entity's ragdoll. + //That way the server side version of this entity can go away. + //Plus we can hook save/restore code to these ragdolls so they don't fall on restore anymore. + C_ClientRagdoll *pRagdoll = new C_ClientRagdoll( false ); + if ( pRagdoll == NULL ) + return NULL; + + TermRopes(); + + const model_t *model = GetModel(); + const char *pModelName = modelinfo->GetModelName( model ); + + if ( pRagdoll->InitializeAsClientEntity( pModelName, RENDER_GROUP_OPAQUE_ENTITY ) == false ) + { + pRagdoll->Release(); + return NULL; + } + + // move my current model instance to the ragdoll's so decals are preserved. + SnatchModelInstance( pRagdoll ); + + // We need to take these from the entity + pRagdoll->SetAbsOrigin( GetAbsOrigin() ); + pRagdoll->SetAbsAngles( GetAbsAngles() ); + + pRagdoll->IgniteRagdoll( this ); + pRagdoll->TransferDissolveFrom( this ); + pRagdoll->InitModelEffects(); + + if ( AddRagdollToFadeQueue() == true ) + { + pRagdoll->m_bImportant = NPC_IsImportantNPC( this ); + s_RagdollLRU.MoveToTopOfLRU( pRagdoll, pRagdoll->m_bImportant ); + pRagdoll->m_bFadeOut = true; + } + + m_builtRagdoll = true; + AddEffects( EF_NODRAW ); + + if ( IsEffectActive( EF_NOSHADOW ) ) + { + pRagdoll->AddEffects( EF_NOSHADOW ); + } + + pRagdoll->m_nRenderFX = kRenderFxRagdoll; + pRagdoll->SetRenderMode( GetRenderMode() ); + pRagdoll->SetRenderColor( GetRenderColor().r, GetRenderColor().g, GetRenderColor().b, GetRenderColor().a ); + + pRagdoll->m_nBody = m_nBody; + pRagdoll->m_nSkin = GetSkin(); + pRagdoll->m_vecForce = m_vecForce; + pRagdoll->m_nForceBone = m_nForceBone; + pRagdoll->SetNextClientThink( CLIENT_THINK_ALWAYS ); + + pRagdoll->SetModelName( AllocPooledString(pModelName) ); + return pRagdoll; +} + +C_BaseAnimating *C_BaseAnimating::BecomeRagdollOnClient() +{ + MoveToLastReceivedPosition( true ); + GetAbsOrigin(); + C_BaseAnimating *pRagdoll = CreateRagdollCopy(); + + matrix3x4_t boneDelta0[MAXSTUDIOBONES]; + matrix3x4_t boneDelta1[MAXSTUDIOBONES]; + matrix3x4_t currentBones[MAXSTUDIOBONES]; + const float boneDt = 0.1f; + GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ); + pRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); + return pRagdoll; +} + +bool C_BaseAnimating::InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr || m_pRagdoll || m_builtRagdoll ) + return false; + + m_builtRagdoll = true; + + // Store off our old mins & maxs + m_vecPreRagdollMins = WorldAlignMins(); + m_vecPreRagdollMaxs = WorldAlignMaxs(); + + + // Force MOVETYPE_STEP interpolation + MoveType_t savedMovetype = GetMoveType(); + SetMoveType( MOVETYPE_STEP ); + + // HACKHACK: force time to last interpolation position + m_flPlaybackRate = 1; + + m_pRagdoll = CreateRagdoll( this, hdr, m_vecForce, m_nForceBone, pDeltaBones0, pDeltaBones1, pCurrentBonePosition, boneDt ); + + // Cause the entity to recompute its shadow type and make a + // version which only updates when physics state changes + // NOTE: We have to do this after m_pRagdoll is assigned above + // because that's what ShadowCastType uses to figure out which type of shadow to use. + DestroyShadow(); + CreateShadow(); + + // Cache off ragdoll bone positions/quaternions + if ( m_bStoreRagdollInfo && m_pRagdoll ) + { + matrix3x4_t parentTransform; + AngleMatrix( GetAbsAngles(), GetAbsOrigin(), parentTransform ); + // FIXME/CHECK: This might be too expensive to do every frame??? + SaveRagdollInfo( hdr->numbones(), parentTransform, m_BoneAccessor ); + } + + SetMoveType( savedMovetype ); + + // Now set the dieragdoll sequence to get transforms for all + // non-simulated bones + m_nRestoreSequence = GetSequence(); + SetSequence( SelectWeightedSequence( ACT_DIERAGDOLL ) ); + m_nPrevSequence = GetSequence(); + m_flPlaybackRate = 0; + UpdatePartitionListEntry(); + + NoteRagdollCreationTick( this ); + + UpdateVisibility(); + return true; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_BaseAnimating::OnDataChanged( DataUpdateType_t updateType ) +{ + // don't let server change sequences after becoming a ragdoll + if ( m_pRagdoll && GetSequence() != m_nPrevSequence ) + { + SetSequence( m_nPrevSequence ); + m_flPlaybackRate = 0; + } + + if ( !m_pRagdoll && m_nRestoreSequence != -1 ) + { + SetSequence( m_nRestoreSequence ); + m_nRestoreSequence = -1; + } + + if (updateType == DATA_UPDATE_CREATED) + { + m_nPrevSequence = -1; + m_nRestoreSequence = -1; + } + + + + bool modelchanged = false; + + // UNDONE: The base class does this as well. So this is kind of ugly + // but getting a model by index is pretty cheap... + const model_t *pModel = modelinfo->GetModel( GetModelIndex() ); + + if ( pModel != GetModel() ) + { + modelchanged = true; + } + + BaseClass::OnDataChanged( updateType ); + + if ( (updateType == DATA_UPDATE_CREATED) || modelchanged ) + { + ResetLatched(); + // if you have this pose parameter, activate HL1-style lipsync/wave envelope tracking + if ( LookupPoseParameter( LIPSYNC_POSEPARAM_NAME ) != -1 ) + { + MouthInfo().ActivateEnvelope(); + } + } + + // If there's a significant change, make sure the shadow updates + if ( modelchanged || (GetSequence() != m_nPrevSequence)) + { + InvalidatePhysicsRecursive( ANIMATION_CHANGED ); + m_nPrevSequence = GetSequence(); + } + + // Only need to think if animating client side + if ( m_bClientSideAnimation ) + { + // Check to see if we should reset our frame + if ( m_bClientSideFrameReset != m_bLastClientSideFrameReset ) + { + SetCycle( 0 ); + } + } + // build a ragdoll if necessary + if ( m_nRenderFX == kRenderFxRagdoll && !m_builtRagdoll ) + { + BecomeRagdollOnClient(); + } + + //HACKHACK!!! + if ( m_nRenderFX == kRenderFxRagdoll && m_builtRagdoll == true ) + { + if ( m_pRagdoll == NULL ) + AddEffects( EF_NODRAW ); + } + + if ( m_pRagdoll && m_nRenderFX != kRenderFxRagdoll ) + { + ClearRagdoll(); + } + + // If ragdolling and get EF_NOINTERP, we probably were dead and are now respawning, + // don't do blend out of ragdoll at respawn spot. + if ( IsEffectActive( EF_NOINTERP ) && + m_pRagdollInfo && + m_pRagdollInfo->m_bActive ) + { + Msg( "delete ragdoll due to nointerp\n" ); + // Remove ragdoll info + delete m_pRagdollInfo; + m_pRagdollInfo = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseAnimating::AddEntity( void ) +{ + // Server says don't interpolate this frame, so set previous info to new info. + if ( IsEffectActive(EF_NOINTERP) ) + { + ResetLatched(); + } + + BaseClass::AddEntity(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the index of the attachment point with the specified name +//----------------------------------------------------------------------------- +int C_BaseAnimating::LookupAttachment( const char *pAttachmentName ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return -1; + } + + // NOTE: Currently, the network uses 0 to mean "no attachment" + // thus the client must add one to the index of the attachment + // UNDONE: Make the server do this too to be consistent. + return Studio_FindAttachment( hdr, pAttachmentName ) + 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Get a random index of an attachment point with the specified substring in its name +//----------------------------------------------------------------------------- +int C_BaseAnimating::LookupRandomAttachment( const char *pAttachmentNameSubstring ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return -1; + } + + // NOTE: Currently, the network uses 0 to mean "no attachment" + // thus the client must add one to the index of the attachment + // UNDONE: Make the server do this too to be consistent. + return Studio_FindRandomAttachment( hdr, pAttachmentNameSubstring ) + 1; +} + + +void C_BaseAnimating::ClientSideAnimationChanged() +{ + if ( !m_bClientSideAnimation || m_ClientSideAnimationListHandle == INVALID_CLIENTSIDEANIMATION_LIST_HANDLE ) + return; + + MDLCACHE_CRITICAL_SECTION(); + + clientanimating_t &anim = g_ClientSideAnimationList.Element(m_ClientSideAnimationListHandle); + Assert(anim.pAnimating == this); + anim.flags = ComputeClientSideAnimationFlags(); + + m_SequenceTransitioner.CheckForSequenceChange( + GetModelPtr(), + GetSequence(), + m_nNewSequenceParity != m_nPrevNewSequenceParity, + !IsEffectActive(EF_NOINTERP) + ); +} + +unsigned int C_BaseAnimating::ComputeClientSideAnimationFlags() +{ + return FCLIENTANIM_SEQUENCE_CYCLE; +} + +void C_BaseAnimating::UpdateClientSideAnimation() +{ + // Update client side animation + if ( m_bClientSideAnimation ) + { + + Assert( m_ClientSideAnimationListHandle != INVALID_CLIENTSIDEANIMATION_LIST_HANDLE ); + if ( GetSequence() != -1 ) + { + // latch old values + OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR ); + // move frame forward + FrameAdvance( 0.0f ); // 0 means to use the time we last advanced instead of a constant + } + } + else + { + Assert( m_ClientSideAnimationListHandle == INVALID_CLIENTSIDEANIMATION_LIST_HANDLE ); + } +} + + +void C_BaseAnimating::Simulate() +{ + if ( m_bInitModelEffects ) + { + DelayedInitModelEffects(); + } + + if ( gpGlobals->frametime != 0.0f ) + { + DoAnimationEvents( GetModelPtr() ); + } + BaseClass::Simulate(); + if ( GetSequence() != -1 && m_pRagdoll && ( m_nRenderFX != kRenderFxRagdoll ) ) + { + ClearRagdoll(); + } +} + + +bool C_BaseAnimating::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) +{ + if ( ray.m_IsRay && IsSolidFlagSet( FSOLID_CUSTOMRAYTEST )) + { + if (!TestHitboxes( ray, fContentsMask, tr )) + return true; + + return tr.DidHit(); + } + + if ( !ray.m_IsRay && IsSolidFlagSet( FSOLID_CUSTOMBOXTEST )) + { + if (!TestHitboxes( ray, fContentsMask, tr )) + return true; + + return true; + } + + // We shouldn't get here. + Assert(0); + return false; +} + + +// UNDONE: This almost works. The client entities have no control over their solid box +// Also they have no ability to expose FSOLID_ flags to the engine to force the accurate +// collision tests. +// Add those and the client hitboxes will be robust +bool C_BaseAnimating::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) +{ + VPROF( "C_BaseAnimating::TestHitboxes" ); + + CStudioHdr *pStudioHdr = GetModelPtr(); + if (!pStudioHdr) + return false; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet ); + if ( !set || !set->numhitboxes ) + return false; + + // Use vcollide for box traces. + if ( !ray.m_IsRay ) + return false; + + // This *has* to be true for the existing code to function correctly. + Assert( ray.m_StartOffset == vec3_origin ); + + CBoneCache *pCache = GetBoneCache( pStudioHdr ); + matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; + pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() ); + + if ( TraceToStudio( physprops, ray, pStudioHdr, set, hitboxbones, fContentsMask, tr ) ) + { + mstudiobbox_t *pbox = set->pHitbox( tr.hitbox ); + mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone); + tr.surface.name = "**studio**"; + tr.surface.flags = SURF_HITBOX; + tr.surface.surfaceProps = physprops->GetSurfaceIndex( pBone->pszSurfaceProp() ); + if ( IsRagdoll() ) + { + IPhysicsObject *pReplace = m_pRagdoll->GetElement( tr.physicsbone ); + if ( pReplace ) + { + VPhysicsSetObject( NULL ); + VPhysicsSetObject( pReplace ); + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Check sequence framerate +// Input : iSequence - +// Output : float +//----------------------------------------------------------------------------- +float C_BaseAnimating::GetSequenceCycleRate( CStudioHdr *pStudioHdr, int iSequence ) +{ + if ( !pStudioHdr ) + return 0.0f; + + return Studio_CPS( pStudioHdr, pStudioHdr->pSeqdesc(iSequence), iSequence, m_flPoseParameter ); +} + +float C_BaseAnimating::GetAnimTimeInterval( void ) const +{ +#define MAX_ANIMTIME_INTERVAL 0.2f + + float flInterval = min( gpGlobals->curtime - m_flAnimTime, MAX_ANIMTIME_INTERVAL ); + return flInterval; +} + + +//----------------------------------------------------------------------------- +// Sets the cycle, marks the entity as being dirty +//----------------------------------------------------------------------------- +void C_BaseAnimating::SetCycle( float flCycle ) +{ + if ( m_flCycle != flCycle ) + { + m_flCycle = flCycle; + InvalidatePhysicsRecursive( ANIMATION_CHANGED ); + } +} + +//----------------------------------------------------------------------------- +// Sets the sequence, marks the entity as being dirty +//----------------------------------------------------------------------------- +void C_BaseAnimating::SetSequence( int nSequence ) +{ + if ( m_nSequence != nSequence ) + { + /* + CStudioHdr *hdr = GetModelPtr(); + // Assert( hdr ); + if ( hdr ) + { + Assert( nSequence >= 0 && nSequence < hdr->GetNumSeq() ); + } + */ + + m_nSequence = nSequence; + InvalidatePhysicsRecursive( ANIMATION_CHANGED ); + if ( m_bClientSideAnimation ) + { + ClientSideAnimationChanged(); + } + } +} + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up some interval (default 0.1) into the future +//========================================================= +void C_BaseAnimating::StudioFrameAdvance() +{ + if ( m_bClientSideAnimation ) + return; + + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return; + + bool watch = 0;//Q_strstr( hdr->name, "objects/human_obj_powerpack_build.mdl" ) ? true : false; + + //if (!anim.prevanimtime) + //{ + //anim.prevanimtime = m_flAnimTime = gpGlobals->curtime; + //} + + // How long since last animtime + float flInterval = GetAnimTimeInterval(); + + if (flInterval <= 0.001) + { + // Msg("%s : %s : %5.3f (skip)\n", STRING(pev->classname), GetSequenceName( GetSequence() ), GetCycle() ); + return; + } + + //anim.prevanimtime = m_flAnimTime; + float cycleAdvance = flInterval * GetSequenceCycleRate( hdr, GetSequence() ) * m_flPlaybackRate; + float flNewCycle = GetCycle() + cycleAdvance; + m_flAnimTime = gpGlobals->curtime; + + if ( watch ) + { + Msg("%s %6.3f : %6.3f (%.3f)\n", GetClassname(), gpGlobals->curtime, m_flAnimTime, flInterval ); + } + + if ( flNewCycle < 0.0f || flNewCycle >= 1.0f ) + { + if ( IsSequenceLooping( hdr, GetSequence() ) ) + { + flNewCycle -= (int)(flNewCycle); + } + else + { + flNewCycle = (flNewCycle < 0.0f) ? 0.0f : 1.0f; + } + + m_bSequenceFinished = true; // just in case it wasn't caught in GetEvents + } + + SetCycle( flNewCycle ); + + m_flGroundSpeed = GetSequenceGroundSpeed( hdr, GetSequence() ); + +#if 0 + // I didn't have a test case for this, but it seems like the right thing to do. Check multi-player! + + // Msg("%s : %s : %5.1f\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() ); + InvalidatePhysicsRecursive( ANIMATION_CHANGED ); +#endif + + if ( watch ) + { + Msg("%s : %s : %5.1f\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() ); + } +} + +float C_BaseAnimating::GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence ) +{ + float t = SequenceDuration( pStudioHdr, iSequence ); + + if (t > 0) + { + return GetSequenceMoveDist( pStudioHdr, iSequence ) / t; + } + else + { + return 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// Input : iSequence - +// +// Output : float +//----------------------------------------------------------------------------- +float C_BaseAnimating::GetSequenceMoveDist( CStudioHdr *pStudioHdr, int iSequence ) +{ + Vector vecReturn; + + ::GetSequenceLinearMotion( pStudioHdr, iSequence, m_flPoseParameter, &vecReturn ); + + return vecReturn.Length(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// Input : iSequence - +// *pVec - +// +//----------------------------------------------------------------------------- +void C_BaseAnimating::GetSequenceLinearMotion( int iSequence, Vector *pVec ) +{ + ::GetSequenceLinearMotion( GetModelPtr(), iSequence, m_flPoseParameter, pVec ); +} + +void C_BaseAnimating::GetBlendedLinearVelocity( Vector *pVec ) +{ + Vector vecDist; + float flDuration; + + GetSequenceLinearMotion( GetSequence(), &vecDist ); + flDuration = SequenceDuration( GetSequence() ); + + VectorScale( vecDist, 1.0 / flDuration, *pVec ); + + Vector tmp; + for (int i = m_SequenceTransitioner.m_animationQueue.Count() - 2; i >= 0; i--) + { + C_AnimationLayer *blend = &m_SequenceTransitioner.m_animationQueue[i]; + + GetSequenceLinearMotion( blend->m_nSequence, &vecDist ); + flDuration = SequenceDuration( blend->m_nSequence ); + + VectorScale( vecDist, 1.0 / flDuration, tmp ); + + float flWeight = blend->GetFadeout( gpGlobals->curtime ); + *pVec = Lerp( flWeight, *pVec, tmp ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flInterval - +// Output : float +//----------------------------------------------------------------------------- +float C_BaseAnimating::FrameAdvance( float flInterval ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return 0.0f; + + bool bWatch = false; // Q_strstr( hdr->name, "commando" ) ? true : false; + + float curtime = gpGlobals->curtime; + + if (flInterval == 0.0f) + { + flInterval = ( curtime - m_flAnimTime ); + if (flInterval <= 0.001f) + { + return 0.0f; + } + } + + if ( !m_flAnimTime ) + { + flInterval = 0.0f; + } + + float cyclerate = GetSequenceCycleRate( hdr, GetSequence() ); + float addcycle = flInterval * cyclerate * m_flPlaybackRate; + + if( GetServerIntendedCycle() != -1.0f ) + { + // The server would like us to ease in a correction so that we will animate the same on the client and server. + // So we will actually advance the average of what we would have done and what the server wants. + float serverCycle = GetServerIntendedCycle(); + float serverAdvance = serverCycle - GetCycle(); + bool adjustOkay = serverAdvance > 0.0f;// only want to go forward. backing up looks really jarring, even when slight + if( serverAdvance < -0.8f ) + { + // Oh wait, it was just a wraparound from .9 to .1. + serverAdvance += 1; + adjustOkay = true; + } + + if( adjustOkay ) + { + float originalAdvance = addcycle; + addcycle = (serverAdvance + addcycle) / 2; + + const float MAX_CYCLE_ADJUSTMENT = 0.1f; + addcycle = min( MAX_CYCLE_ADJUSTMENT, addcycle );// Don't do too big of a jump; it's too jarring as well. + + DevMsg( 2, "(%d): Cycle latch used to correct %.2f in to %.2f instead of %.2f.\n", + entindex(), GetCycle(), GetCycle() + addcycle, GetCycle() + originalAdvance ); + } + + SetServerIntendedCycle(-1.0f); // Only use a correction once, it isn't valid any time but right now. + } + + float flNewCycle = GetCycle() + addcycle; + m_flAnimTime = curtime; + + if ( bWatch ) + { + Msg("%i CLIENT Time: %6.3f : (Interval %f) : cycle %f rate %f add %f\n", + gpGlobals->tickcount, gpGlobals->curtime, flInterval, flNewCycle, cyclerate, addcycle ); + } + + if ( (flNewCycle < 0.0f) || (flNewCycle >= 1.0f) ) + { + if ( IsSequenceLooping( hdr, GetSequence() ) ) + { + flNewCycle -= (int)(flNewCycle); + } + else + { + flNewCycle = (flNewCycle < 0.0f) ? 0.0f : 1.0f; + } + m_bSequenceFinished = true; + } + + SetCycle( flNewCycle ); + + return flInterval; +} + +// Stubs for weapon prediction +void C_BaseAnimating::ResetSequenceInfo( void ) +{ + if (GetSequence() == -1) + { + SetSequence( 0 ); + } + + CStudioHdr *pStudioHdr = GetModelPtr(); + m_flGroundSpeed = GetSequenceGroundSpeed( pStudioHdr, GetSequence() ); + m_bSequenceLoops = ((GetSequenceFlags( pStudioHdr, GetSequence() ) & STUDIO_LOOPING) != 0); + // m_flAnimTime = gpGlobals->time; + m_flPlaybackRate = 1.0; + m_bSequenceFinished = false; + m_flLastEventCheck = 0; + + m_nNewSequenceParity = ( ++m_nNewSequenceParity ) & EF_PARITY_MASK; + m_nResetEventsParity = ( ++m_nResetEventsParity ) & EF_PARITY_MASK; + + // FIXME: why is this called here? Nothing should have changed to make this nessesary + SetEventIndexForSequence( pStudioHdr->pSeqdesc( GetSequence() ) ); +} + +//========================================================= +//========================================================= + +bool C_BaseAnimating::IsSequenceLooping( CStudioHdr *pStudioHdr, int iSequence ) +{ + return (::GetSequenceFlags( pStudioHdr, iSequence ) & STUDIO_LOOPING) != 0; +} + +float C_BaseAnimating::SequenceDuration( CStudioHdr *pStudioHdr, int iSequence ) +{ + if ( !pStudioHdr ) + { + return 0.1f; + } + + if (iSequence >= pStudioHdr->GetNumSeq() || iSequence < 0 ) + { + DevWarning( 2, "C_BaseAnimating::SequenceDuration( %d ) out of range\n", iSequence ); + return 0.1; + } + + return Studio_Duration( pStudioHdr, iSequence, m_flPoseParameter ); + +} + +int C_BaseAnimating::FindTransitionSequence( int iCurrentSequence, int iGoalSequence, int *piDir ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return -1; + } + + if (piDir == NULL) + { + int iDir = 1; + int sequence = ::FindTransitionSequence( hdr, iCurrentSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransitionSequence( hdr, iCurrentSequence, iGoalSequence, piDir ); + +} + +void C_BaseAnimating::SetBodygroup( int iGroup, int iValue ) +{ + Assert( GetModelPtr() ); + + ::SetBodygroup( GetModelPtr( ), m_nBody, iGroup, iValue ); +} + +int C_BaseAnimating::GetBodygroup( int iGroup ) +{ + Assert( GetModelPtr() ); + + return ::GetBodygroup( GetModelPtr( ), m_nBody, iGroup ); +} + +const char *C_BaseAnimating::GetBodygroupName( int iGroup ) +{ + Assert( GetModelPtr() ); + + return ::GetBodygroupName( GetModelPtr( ), iGroup ); +} + +int C_BaseAnimating::FindBodygroupByName( const char *name ) +{ + Assert( GetModelPtr() ); + + return ::FindBodygroupByName( GetModelPtr( ), name ); +} + +int C_BaseAnimating::GetBodygroupCount( int iGroup ) +{ + Assert( GetModelPtr() ); + + return ::GetBodygroupCount( GetModelPtr( ), iGroup ); +} + +int C_BaseAnimating::GetNumBodyGroups( void ) +{ + Assert( GetModelPtr() ); + + return ::GetNumBodyGroups( GetModelPtr( ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : setnum - +//----------------------------------------------------------------------------- +void C_BaseAnimating::SetHitboxSet( int setnum ) +{ +#ifdef _DEBUG + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( !pStudioHdr ) + return; + + if (setnum > pStudioHdr->numhitboxsets()) + { + // Warn if an bogus hitbox set is being used.... + static bool s_bWarned = false; + if (!s_bWarned) + { + Warning("Using bogus hitbox set in entity %s!\n", GetClassname() ); + s_bWarned = true; + } + setnum = 0; + } +#endif + + m_nHitboxSet = setnum; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *setname - +//----------------------------------------------------------------------------- +void C_BaseAnimating::SetHitboxSetByName( const char *setname ) +{ + m_nHitboxSet = FindHitboxSetByName( GetModelPtr(), setname ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int C_BaseAnimating::GetHitboxSet( void ) +{ + return m_nHitboxSet; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +const char *C_BaseAnimating::GetHitboxSetName( void ) +{ + return ::GetHitboxSetName( GetModelPtr(), m_nHitboxSet ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int C_BaseAnimating::GetHitboxSetCount( void ) +{ + return ::GetHitboxSetCount( GetModelPtr() ); +} + +static Vector hullcolor[8] = +{ + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 0.5, 0.5 ), + Vector( 0.5, 1.0, 0.5 ), + Vector( 1.0, 1.0, 0.5 ), + Vector( 0.5, 0.5, 1.0 ), + Vector( 1.0, 0.5, 1.0 ), + Vector( 0.5, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ) +}; + +//----------------------------------------------------------------------------- +// Purpose: Draw the current hitboxes +//----------------------------------------------------------------------------- +void C_BaseAnimating::DrawClientHitboxes( float duration /*= 0.0f*/, bool monocolor /*= false*/ ) +{ + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( !pStudioHdr ) + return; + + mstudiohitboxset_t *set =pStudioHdr->pHitboxSet( m_nHitboxSet ); + if ( !set ) + return; + + Vector position; + QAngle angles; + + int r = 255; + int g = 0; + int b = 0; + + for ( int i = 0; i < set->numhitboxes; i++ ) + { + mstudiobbox_t *pbox = set->pHitbox( i ); + + GetBonePosition( pbox->bone, position, angles ); + + if ( !monocolor ) + { + int j = (pbox->group % 8); + r = ( int ) ( 255.0f * hullcolor[j][0] ); + g = ( int ) ( 255.0f * hullcolor[j][1] ); + b = ( int ) ( 255.0f * hullcolor[j][2] ); + } + + debugoverlay->AddBoxOverlay( position, pbox->bbmin, pbox->bbmax, angles, r, g, b, 0 ,duration ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : activity - +// Output : int C_BaseAnimating::SelectWeightedSequence +//----------------------------------------------------------------------------- +int C_BaseAnimating::SelectWeightedSequence ( int activity ) +{ + Assert( activity != ACT_INVALID ); + + return ::SelectWeightedSequence( GetModelPtr(), activity ); + +} + +//========================================================= +//========================================================= +int C_BaseAnimating::LookupPoseParameter( CStudioHdr *pstudiohdr, const char *szName ) +{ + if ( !pstudiohdr ) + return 0; + + for (int i = 0; i < pstudiohdr->GetNumPoseParameters(); i++) + { + if (stricmp( pstudiohdr->pPoseParameter( i ).pszName(), szName ) == 0) + { + return i; + } + } + + // AssertMsg( 0, UTIL_VarArgs( "poseparameter %s couldn't be mapped!!!\n", szName ) ); + return -1; // Error +} + +//========================================================= +//========================================================= +float C_BaseAnimating::SetPoseParameter( CStudioHdr *pStudioHdr, const char *szName, float flValue ) +{ + return SetPoseParameter( pStudioHdr, LookupPoseParameter( pStudioHdr, szName ), flValue ); +} + +float C_BaseAnimating::SetPoseParameter( CStudioHdr *pStudioHdr, int iParameter, float flValue ) +{ + if ( !pStudioHdr ) + { + Assert(!"C_BaseAnimating::SetPoseParameter: model missing"); + return flValue; + } + + if (iParameter >= 0) + { + float flNewValue; + flValue = Studio_SetPoseParameter( pStudioHdr, iParameter, flValue, flNewValue ); + m_flPoseParameter[ iParameter ] = flNewValue; + } + + return flValue; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *label - +// Output : int +//----------------------------------------------------------------------------- +int C_BaseAnimating::LookupSequence( const char *label ) +{ + Assert( GetModelPtr() ); + return ::LookupSequence( GetModelPtr(), label ); +} + +void C_BaseAnimating::Release() +{ + ClearRagdoll(); + BaseClass::Release(); +} + +void C_BaseAnimating::Clear( void ) +{ + Q_memset(&m_mouth, 0, sizeof(m_mouth)); + BaseClass::Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: Clear current ragdoll +//----------------------------------------------------------------------------- +void C_BaseAnimating::ClearRagdoll() +{ + if ( m_pRagdoll ) + { + // immediately mark the member ragdoll as being NULL, + // so that we have no reentrancy problems with the delete + // (such as the disappearance of the ragdoll physics waking up + // IVP which causes other objects to move and have a touch + // callback on the ragdoll entity, which was a crash on TF) + // That is to say: it is vital that the member be cleared out + // BEFORE the delete occurs. + CRagdoll * RESTRICT pDoomed = m_pRagdoll; + m_pRagdoll = NULL; + + delete pDoomed; + + // Set to null so that the destructor's call to DestroyObject won't destroy + // m_pObjects[ 0 ] twice since that's the physics object for the prop + VPhysicsSetObject( NULL ); + + // If we have ragdoll mins/maxs, we've just come out of ragdoll, so restore them + if ( m_vecPreRagdollMins != vec3_origin || m_vecPreRagdollMaxs != vec3_origin ) + { + SetCollisionBounds( m_vecPreRagdollMins, m_vecPreRagdollMaxs ); + } + } + m_builtRagdoll = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Looks up an activity by name. +// Input : label - Name of the activity, ie "ACT_IDLE". +// Output : Returns the activity ID or ACT_INVALID. +//----------------------------------------------------------------------------- +int C_BaseAnimating::LookupActivity( const char *label ) +{ + Assert( GetModelPtr() ); + return ::LookupActivity( GetModelPtr(), label ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// Input : iSequence - +// +// Output : char +//----------------------------------------------------------------------------- +const char *C_BaseAnimating::GetSequenceActivityName( int iSequence ) +{ + if( iSequence == -1 ) + { + return "Not Found!"; + } + + if ( !GetModelPtr() ) + return "No model!"; + + return ::GetSequenceActivityName( GetModelPtr(), iSequence ); +} + +//========================================================= +//========================================================= +float C_BaseAnimating::SetBoneController ( int iController, float flValue ) +{ + Assert( GetModelPtr() ); + + CStudioHdr *pmodel = GetModelPtr(); + + Assert(iController >= 0 && iController < NUM_BONECTRLS); + + float controller = m_flEncodedController[iController]; + float retVal = Studio_SetController( pmodel, iController, flValue, controller ); + m_flEncodedController[iController] = controller; + return retVal; +} + + +void C_BaseAnimating::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + CBaseEntity *pMoveParent; + if ( IsEffectActive( EF_BONEMERGE ) && IsEffectActive( EF_BONEMERGE_FASTCULL ) && (pMoveParent = GetMoveParent()) != NULL ) + { + // Doing this saves a lot of CPU. + *pAbsOrigin = pMoveParent->WorldSpaceCenter(); + *pAbsAngles = pMoveParent->GetRenderAngles(); + } + else + { + if ( !m_pBoneMergeCache || !m_pBoneMergeCache->GetAimEntOrigin( pAbsOrigin, pAbsAngles ) ) + BaseClass::GetAimEntOrigin( pAttachedTo, pAbsOrigin, pAbsAngles ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// Input : iSequence - +// +// Output : char +//----------------------------------------------------------------------------- +const char *C_BaseAnimating::GetSequenceName( int iSequence ) +{ + if( iSequence == -1 ) + { + return "Not Found!"; + } + + if ( !GetModelPtr() ) + return "No model!"; + + return ::GetSequenceName( GetModelPtr(), iSequence ); +} + +Activity C_BaseAnimating::GetSequenceActivity( int iSequence ) +{ + if( iSequence == -1 ) + { + return ACT_INVALID; + } + + if ( !GetModelPtr() ) + return ACT_INVALID; + + return (Activity)::GetSequenceActivity( GetModelPtr(), iSequence ); +} + + +//----------------------------------------------------------------------------- +// Computes a box that surrounds all hitboxes +//----------------------------------------------------------------------------- +bool C_BaseAnimating::ComputeHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) +{ + // Note that this currently should not be called during position recomputation because of IK. + // The code below recomputes bones so as to get at the hitboxes, + // which causes IK to trigger, which causes raycasts against the other entities to occur, + // which is illegal to do while in the computeabsposition phase. + + CStudioHdr *pStudioHdr = GetModelPtr(); + if (!pStudioHdr) + return false; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet ); + if ( !set || !set->numhitboxes ) + return false; + + CBoneCache *pCache = GetBoneCache( pStudioHdr ); + matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; + pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() ); + + // Compute a box in world space that surrounds this entity + pVecWorldMins->Init( FLT_MAX, FLT_MAX, FLT_MAX ); + pVecWorldMaxs->Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + + Vector vecBoxAbsMins, vecBoxAbsMaxs; + for ( int i = 0; i < set->numhitboxes; i++ ) + { + mstudiobbox_t *pbox = set->pHitbox(i); + + TransformAABB( *hitboxbones[pbox->bone], pbox->bbmin, pbox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs ); + VectorMin( *pVecWorldMins, vecBoxAbsMins, *pVecWorldMins ); + VectorMax( *pVecWorldMaxs, vecBoxAbsMaxs, *pVecWorldMaxs ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Computes a box that surrounds all hitboxes, in entity space +//----------------------------------------------------------------------------- +bool C_BaseAnimating::ComputeEntitySpaceHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) +{ + // Note that this currently should not be called during position recomputation because of IK. + // The code below recomputes bones so as to get at the hitboxes, + // which causes IK to trigger, which causes raycasts against the other entities to occur, + // which is illegal to do while in the computeabsposition phase. + + CStudioHdr *pStudioHdr = GetModelPtr(); + if (!pStudioHdr) + return false; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet ); + if ( !set || !set->numhitboxes ) + return false; + + CBoneCache *pCache = GetBoneCache( pStudioHdr ); + matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; + pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() ); + + // Compute a box in world space that surrounds this entity + pVecWorldMins->Init( FLT_MAX, FLT_MAX, FLT_MAX ); + pVecWorldMaxs->Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + + matrix3x4_t worldToEntity, boneToEntity; + MatrixInvert( EntityToWorldTransform(), worldToEntity ); + + Vector vecBoxAbsMins, vecBoxAbsMaxs; + for ( int i = 0; i < set->numhitboxes; i++ ) + { + mstudiobbox_t *pbox = set->pHitbox(i); + + ConcatTransforms( worldToEntity, *hitboxbones[pbox->bone], boneToEntity ); + TransformAABB( boneToEntity, pbox->bbmin, pbox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs ); + VectorMin( *pVecWorldMins, vecBoxAbsMins, *pVecWorldMins ); + VectorMax( *pVecWorldMaxs, vecBoxAbsMaxs, *pVecWorldMaxs ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : scale - +//----------------------------------------------------------------------------- +void C_BaseAnimating::SetModelWidthScale( float scale ) +{ + m_flModelWidthScale = scale; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float C_BaseAnimating::GetModelWidthScale() const +{ + return m_flModelWidthScale; +} + +//----------------------------------------------------------------------------- +// Purpose: Clientside bone follower class. Used just to visualize them. +// Bone followers WON'T be sent to the client if VISUALIZE_FOLLOWERS is +// undefined in the server's physics_bone_followers.cpp +//----------------------------------------------------------------------------- +class C_BoneFollower : public C_BaseEntity +{ + DECLARE_CLASS( C_BoneFollower, C_BaseEntity ); + DECLARE_CLIENTCLASS(); +public: + C_BoneFollower( void ) + { + } + + bool ShouldDraw( void ); + int DrawModel( int flags ); + bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ); + +private: + int m_modelIndex; + int m_solidIndex; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_BoneFollower, DT_BoneFollower, CBoneFollower ) + RecvPropInt( RECVINFO( m_modelIndex ) ), + RecvPropInt( RECVINFO( m_solidIndex ) ), +END_RECV_TABLE() + +void VCollideWireframe_ChangeCallback( IConVar *pConVar, char const *pOldString, float flOldValue ) +{ + for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) ) + { + pEntity->UpdateVisibility(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether object should render. +//----------------------------------------------------------------------------- +bool C_BoneFollower::ShouldDraw( void ) +{ + return ( vcollide_wireframe.GetBool() ); //MOTODO +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_BoneFollower::DrawModel( int flags ) +{ + vcollide_t *pCollide = modelinfo->GetVCollide( m_modelIndex ); + if ( pCollide ) + { + static color32 debugColor = {0,255,255,0}; + matrix3x4_t matrix; + AngleMatrix( GetAbsAngles(), GetAbsOrigin(), matrix ); + engine->DebugDrawPhysCollide( pCollide->solids[m_solidIndex], NULL, matrix, debugColor ); + } + return 1; +} + +bool C_BoneFollower::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) +{ + vcollide_t *pCollide = modelinfo->GetVCollide( m_modelIndex ); + Assert( pCollide && pCollide->solidCount > m_solidIndex ); + + physcollision->TraceBox( ray, pCollide->solids[m_solidIndex], GetAbsOrigin(), GetAbsAngles(), &trace ); + + if ( trace.fraction >= 1 ) + return false; + + // return owner as trace hit + trace.m_pEnt = GetOwnerEntity(); + trace.hitgroup = 0;//m_hitGroup; + trace.physicsbone = 0;//m_physicsBone; // UNDONE: Get physics bone index & hitgroup + return trace.DidHit(); +} + + +void C_BaseAnimating::DisableMuzzleFlash() +{ + m_nOldMuzzleFlashParity = m_nMuzzleFlashParity; +} + + +void C_BaseAnimating::DoMuzzleFlash() +{ + m_nMuzzleFlashParity = (m_nMuzzleFlashParity+1) & ((1 << EF_MUZZLEFLASH_BITS) - 1); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DevMsgRT( char const* pMsg, ... ) +{ + if (gpGlobals->frametime != 0.0f) + { + va_list argptr; + va_start( argptr, pMsg ); + // + { + static char string[1024]; + Q_vsnprintf (string, sizeof( string ), pMsg, argptr); + DevMsg( 1, "%s", string ); + } + // DevMsg( pMsg, argptr ); + va_end( argptr ); + } +} + + +void C_BaseAnimating::ForceClientSideAnimationOn() +{ + m_bClientSideAnimation = true; + AddToClientSideAnimationList(); +} + + +void C_BaseAnimating::AddToClientSideAnimationList() +{ + // Already in list + if ( m_ClientSideAnimationListHandle != INVALID_CLIENTSIDEANIMATION_LIST_HANDLE ) + return; + + clientanimating_t list( this, 0 ); + m_ClientSideAnimationListHandle = g_ClientSideAnimationList.AddToTail( list ); + ClientSideAnimationChanged(); +} + +void C_BaseAnimating::RemoveFromClientSideAnimationList() +{ + // Not in list yet + if ( INVALID_CLIENTSIDEANIMATION_LIST_HANDLE == m_ClientSideAnimationListHandle ) + return; + + unsigned int c = g_ClientSideAnimationList.Count(); + + Assert( m_ClientSideAnimationListHandle < c ); + + unsigned int last = c - 1; + + if ( last == m_ClientSideAnimationListHandle ) + { + // Just wipe the final entry + g_ClientSideAnimationList.FastRemove( last ); + } + else + { + clientanimating_t lastEntry = g_ClientSideAnimationList[ last ]; + // Remove the last entry + g_ClientSideAnimationList.FastRemove( last ); + + // And update it's handle to point to this slot. + lastEntry.pAnimating->m_ClientSideAnimationListHandle = m_ClientSideAnimationListHandle; + g_ClientSideAnimationList[ m_ClientSideAnimationListHandle ] = lastEntry; + } + + // Invalidate our handle no matter what. + m_ClientSideAnimationListHandle = INVALID_CLIENTSIDEANIMATION_LIST_HANDLE; +} + + +// static method +void C_BaseAnimating::UpdateClientSideAnimations() +{ + VPROF_BUDGET( "UpdateClientSideAnimations", VPROF_BUDGETGROUP_CLIENT_ANIMATION ); + + int c = g_ClientSideAnimationList.Count(); + for ( int i = 0; i < c ; ++i ) + { + clientanimating_t &anim = g_ClientSideAnimationList.Element(i); + if ( !(anim.flags & FCLIENTANIM_SEQUENCE_CYCLE) ) + continue; + Assert( anim.pAnimating ); + anim.pAnimating->UpdateClientSideAnimation(); + } +} + +CBoneList *C_BaseAnimating::RecordBones( CStudioHdr *hdr, matrix3x4_t *pBoneState ) +{ + if ( !ToolsEnabled() ) + return NULL; + + VPROF_BUDGET( "C_BaseAnimating::RecordBones", VPROF_BUDGETGROUP_TOOLS ); + + // Possible optimization: Instead of inverting everything while recording, record the pos/q stuff into a structure instead? + Assert( hdr ); + + // Setup our transform based on render angles and origin. + matrix3x4_t parentTransform; + AngleMatrix( GetRenderAngles(), GetRenderOrigin(), parentTransform ); + + CBoneList *boneList = CBoneList::Alloc(); + Assert( boneList ); + + boneList->m_nBones = hdr->numbones(); + + for ( int i = 0; i < hdr->numbones(); i++ ) + { + matrix3x4_t inverted; + matrix3x4_t output; + + mstudiobone_t *bone = hdr->pBone( i ); + + // Only update bones referenced during setup + if ( !(bone->flags & BONE_USED_BY_ANYTHING ) ) + { + boneList->m_quatRot[ i ].Init( 0.0f, 0.0f, 0.0f, 1.0f ); // Init by default sets all 0's, which is invalid + boneList->m_vecPos[ i ].Init(); + continue; + } + + if ( bone->parent == -1 ) + { + // Decompose into parent space + MatrixInvert( parentTransform, inverted ); + } + else + { + MatrixInvert( pBoneState[ bone->parent ], inverted ); + } + + ConcatTransforms( inverted, pBoneState[ i ], output ); + + MatrixAngles( output, + boneList->m_quatRot[ i ], + boneList->m_vecPos[ i ] ); + } + + return boneList; +} + +void C_BaseAnimating::GetToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + VPROF_BUDGET( "C_BaseAnimating::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); + + // Force the animation to drive bones + CStudioHdr *hdr = GetModelPtr(); + matrix3x4_t *pBones = (matrix3x4_t*)_alloca( ( hdr ? hdr->numbones() : 1 ) * sizeof(matrix3x4_t) ); + if ( hdr ) + { + SetupBones( pBones, hdr->numbones(), BONE_USED_BY_ANYTHING, gpGlobals->curtime ); + } + else + { + SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); + } + + BaseClass::GetToolRecordingState( msg ); + + static BaseAnimatingRecordingState_t state; + state.m_nSkin = GetSkin(); + state.m_nBody = m_nBody; + state.m_nSequence = m_nSequence; + state.m_pBoneList = NULL; + msg->SetPtr( "baseanimating", &state ); + msg->SetInt( "viewmodel", IsViewModel() ? 1 : 0 ); + + if ( hdr ) + { + state.m_pBoneList = RecordBones( hdr, pBones ); + } +} + +void C_BaseAnimating::CleanupToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + BaseAnimatingRecordingState_t *pState = (BaseAnimatingRecordingState_t*)msg->GetPtr( "baseanimating" ); + if ( pState && pState->m_pBoneList ) + { + pState->m_pBoneList->Release(); + } + + BaseClass::CleanupToolRecordingState( msg ); +} + +LocalFlexController_t C_BaseAnimating::GetNumFlexControllers( void ) +{ + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return LocalFlexController_t(0); + + return pstudiohdr->numflexcontrollers(); +} + +const char *C_BaseAnimating::GetFlexDescFacs( int iFlexDesc ) +{ + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return 0; + + mstudioflexdesc_t *pflexdesc = pstudiohdr->pFlexdesc( iFlexDesc ); + + return pflexdesc->pszFACS( ); +} + +const char *C_BaseAnimating::GetFlexControllerName( LocalFlexController_t iFlexController ) +{ + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return 0; + + mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( iFlexController ); + + return pflexcontroller->pszName( ); +} + +const char *C_BaseAnimating::GetFlexControllerType( LocalFlexController_t iFlexController ) +{ + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return 0; + + mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( iFlexController ); + + return pflexcontroller->pszType( ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the fade scale of the entity in question +// Output : unsigned char - 0 - 255 alpha value +//----------------------------------------------------------------------------- +unsigned char C_BaseAnimating::GetClientSideFade( void ) +{ + return UTIL_ComputeEntityFade( this, m_fadeMinDist, m_fadeMaxDist, m_flFadeScale ); +} + +//----------------------------------------------------------------------------- +// Purpose: Note that we've been transmitted a sequence +//----------------------------------------------------------------------------- +void C_BaseAnimating::SetReceivedSequence( void ) +{ + m_bReceivedSequence = true; +} + +//----------------------------------------------------------------------------- +// Purpose: See if we should force reset our sequence on a new model +//----------------------------------------------------------------------------- +bool C_BaseAnimating::ShouldResetSequenceOnNewModel( void ) +{ + return ( m_bReceivedSequence == false ); +} diff --git a/game/client/c_baseanimating.h b/game/client/c_baseanimating.h new file mode 100644 index 00000000..03941a30 --- /dev/null +++ b/game/client/c_baseanimating.h @@ -0,0 +1,742 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#ifndef C_BASEANIMATING_H +#define C_BASEANIMATING_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" +#include "studio.h" +#include "UtlVector.h" +#include "ragdoll.h" +#include "mouthinfo.h" +// Shared activities +#include "ai_activity.h" +#include "animationlayer.h" +#include "sequence_transitioner.h" +#include "bone_accessor.h" +#include "bone_merge_cache.h" +#include "ragdoll_shared.h" +#include "tier0/threadtools.h" +#include "datacache/idatacache.h" + +#define LIPSYNC_POSEPARAM_NAME "mouth" +#define NUM_HITBOX_FIRES 10 + +/* +class C_BaseClientShader +{ + virtual void RenderMaterial( C_BaseEntity *pEntity, int count, const vec4_t *verts, const vec4_t *normals, const vec2_t *texcoords, vec4_t *lightvalues ); +}; +*/ + +class IRagdoll; +class CIKContext; +class CIKState; +class ConVar; +class C_RopeKeyframe; +class CBoneBitList; +class CBoneList; +class KeyValues; +class CJiggleBones; +FORWARD_DECLARE_HANDLE( memhandle_t ); +typedef unsigned short MDLHandle_t; + +extern ConVar vcollide_wireframe; + + +struct ClientModelRenderInfo_t : public ModelRenderInfo_t +{ + // Added space for lighting origin override. Just allocated space, need to set base pointer + matrix3x4_t lightingOffset; + + // Added space for model to world matrix. Just allocated space, need to set base pointer + matrix3x4_t modelToWorld; +}; + +struct RagdollInfo_t +{ + bool m_bActive; + float m_flSaveTime; + int m_nNumBones; + Vector m_rgBonePos[MAXSTUDIOBONES]; + Quaternion m_rgBoneQuaternion[MAXSTUDIOBONES]; +}; + + +class CAttachmentData +{ +public: + matrix3x4_t m_AttachmentToWorld; + QAngle m_angRotation; + Vector m_vOriginVelocity; + int m_nLastFramecount : 31; + int m_bAnglesComputed : 1; +}; + + +typedef unsigned int ClientSideAnimationListHandle_t; + +#define INVALID_CLIENTSIDEANIMATION_LIST_HANDLE (ClientSideAnimationListHandle_t)~0 + + +class C_BaseAnimating : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_BaseAnimating, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + DECLARE_INTERPOLATION(); + + enum + { + NUM_POSEPAREMETERS = 24, + NUM_BONECTRLS = 4 + }; + + C_BaseAnimating(); + ~C_BaseAnimating(); + + virtual C_BaseAnimating* GetBaseAnimating() { return this; } + + bool UsesPowerOfTwoFrameBufferTexture( void ); + + virtual bool Interpolate( float currentTime ); + virtual void Simulate(); + virtual void Release(); + + float GetAnimTimeInterval( void ) const; + + virtual unsigned char GetClientSideFade( void ); + + // Get bone controller values. + virtual void GetBoneControllers(float controllers[MAXSTUDIOBONECTRLS]); + virtual float SetBoneController ( int iController, float flValue ); + + LocalFlexController_t GetNumFlexControllers( void ); + const char *GetFlexDescFacs( int iFlexDesc ); + const char *GetFlexControllerName( LocalFlexController_t iFlexController ); + const char *GetFlexControllerType( LocalFlexController_t iFlexController ); + + virtual void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ); + + // Computes a box that surrounds all hitboxes + bool ComputeHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ); + bool ComputeEntitySpaceHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ); + + // Gets the hitbox-to-world transforms, returns false if there was a problem + bool HitboxToWorldTransforms( matrix3x4_t *pHitboxToWorld[MAXSTUDIOBONES] ); + + // base model functionality + float ClampCycle( float cycle, bool isLooping ); + virtual void GetPoseParameters( CStudioHdr *pStudioHdr, float poseParameter[MAXSTUDIOPOSEPARAM] ); + virtual void BuildTransformations( CStudioHdr *pStudioHdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed ); + virtual void ApplyBoneMatrixTransform( matrix3x4_t& transform ); + virtual int VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ); + + // model specific + virtual bool SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ); + virtual void UpdateIKLocks( float currentTime ); + virtual void CalculateIKLocks( float currentTime ); + virtual int DrawModel( int flags ); + virtual int InternalDrawModel( int flags ); + virtual bool OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ); + virtual bool OnPostInternalDrawModel( ClientModelRenderInfo_t *pInfo ); + void DoInternalDrawModel( ClientModelRenderInfo_t *pInfo, DrawModelState_t *pState, matrix3x4_t *pBoneToWorldArray = NULL ); + + // + virtual CMouthInfo *GetMouth(); + virtual void ControlMouth( CStudioHdr *pStudioHdr ); + + // override in sub-classes + virtual void DoAnimationEvents( CStudioHdr *pStudio ); + virtual void FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ); + virtual void FireObsoleteEvent( const Vector& origin, const QAngle& angles, int event, const char *options ); + + // Parses and distributes muzzle flash events + virtual bool DispatchMuzzleEffect( const char *options, bool isFirstPerson ); + + // virtual void AllocateMaterials( void ); + // virtual void FreeMaterials( void ); + + virtual CStudioHdr *OnNewModel( void ); + CStudioHdr *GetModelPtr() const; + void InvalidateMdlCache(); + + virtual void SetPredictable( bool state ); + void UseClientSideAnimation(); + + // C_BaseClientShader **p_ClientShaders; + + virtual void StandardBlendingRules( CStudioHdr *pStudioHdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ); + void UnragdollBlend( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime ); + + void MaintainSequenceTransitions( CStudioHdr *hdr, float flCycle, float flPoseParameter[], Vector pos[], Quaternion q[], int boneMask ); + + virtual void AccumulateLayers( CStudioHdr *hdr, Vector pos[], Quaternion q[], float poseparam[], float currentTime, int boneMask ); + + // Attachments + int LookupAttachment( const char *pAttachmentName ); + int LookupRandomAttachment( const char *pAttachmentNameSubstring ); + + int LookupPoseParameter( CStudioHdr *pStudioHdr, const char *szName ); + inline int LookupPoseParameter( const char *szName ) { return LookupPoseParameter(GetModelPtr(), szName); } + + float SetPoseParameter( CStudioHdr *pStudioHdr, const char *szName, float flValue ); + inline float SetPoseParameter( const char *szName, float flValue ) { return SetPoseParameter( GetModelPtr(), szName, flValue ); } + float SetPoseParameter( CStudioHdr *pStudioHdr, int iParameter, float flValue ); + inline float SetPoseParameter( int iParameter, float flValue ) { return SetPoseParameter( GetModelPtr(), iParameter, flValue ); } + + float GetPoseParameter( int iPoseParameter ); + + bool GetPoseParameterRange( int iPoseParameter, float &minValue, float &maxValue ); + + int LookupBone( const char *szName ); + void GetBonePosition( int iBone, Vector &origin, QAngle &angles ); + void GetBoneTransform( int iBone, matrix3x4_t &pBoneToWorld ); + + + //bool solveIK(float a, float b, const Vector &Foot, const Vector &Knee1, Vector &Knee2); + //void DebugIK( mstudioikchain_t *pikchain ); + + virtual void PreDataUpdate( DataUpdateType_t updateType ); + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void NotifyShouldTransmit( ShouldTransmitState_t state ); + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void AddEntity( void ); + + // This can be used to force client side animation to be on. Only use if you know what you're doing! + // Normally, the server entity should set this. + void ForceClientSideAnimationOn(); + + void AddToClientSideAnimationList(); + void RemoveFromClientSideAnimationList(); + + virtual bool IsSelfAnimating(); + virtual void ResetLatched(); + + // implements these so ragdolls can handle frustum culling & leaf visibility + virtual void GetRenderBounds( Vector& theMins, Vector& theMaxs ); + virtual const Vector& GetRenderOrigin( void ); + virtual const QAngle& GetRenderAngles( void ); + + virtual bool GetSoundSpatialization( SpatializationInfo_t& info ); + + // Attachments. + bool GetAttachment( const char *szName, Vector &absOrigin ); + bool GetAttachment( const char *szName, Vector &absOrigin, QAngle &absAngles ); + + // Inherited from C_BaseEntity + virtual bool GetAttachment( int number, Vector &origin ); + virtual bool GetAttachment( int number, Vector &origin, QAngle &angles ); + virtual bool GetAttachment( int number, matrix3x4_t &matrix ); + virtual bool GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ); + + // Returns the attachment in local space + bool GetAttachmentLocal( int iAttachment, matrix3x4_t &attachmentToLocal ); + bool GetAttachmentLocal( int iAttachment, Vector &origin, QAngle &angles ); + bool GetAttachmentLocal( int iAttachment, Vector &origin ); + + // Should this object cast render-to-texture shadows? + virtual ShadowType_t ShadowCastType(); + + // Should we collide? + virtual CollideType_t GetCollideType( void ); + + virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); + virtual bool TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); + + // returns true if we're currently being ragdolled + bool IsRagdoll() const; + virtual C_BaseAnimating *BecomeRagdollOnClient(); + C_BaseAnimating *CreateRagdollCopy(); + bool InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt ); + void IgniteRagdoll( C_BaseAnimating *pSource ); + void TransferDissolveFrom( C_BaseAnimating *pSource ); + virtual void SaveRagdollInfo( int numbones, const matrix3x4_t &cameraTransform, CBoneAccessor &pBoneToWorld ); + virtual bool RetrieveRagdollInfo( Vector *pos, Quaternion *q ); + virtual void Clear( void ); + void ClearRagdoll(); + void CreateUnragdollInfo( C_BaseAnimating *pRagdoll ); + void ForceSetupBonesAtTime( matrix3x4_t *pBonesOut, float flTime ); + virtual void GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ); + + // For shadows rendering the correct body + sequence... + virtual int GetBody() { return m_nBody; } + virtual int GetSkin() { return m_nSkin; } + + bool IsOnFire() { return ( (GetFlags() & FL_ONFIRE) != 0 ); } + + inline float GetPlaybackRate(); + inline void SetPlaybackRate( float rate ); + + void SetModelWidthScale( float scale ); + float GetModelWidthScale() const; + + int GetSequence(); + void SetSequence(int nSequence); + inline void ResetSequence(int nSequence); + float GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence ); + inline float GetSequenceGroundSpeed( int iSequence ) { return GetSequenceGroundSpeed(GetModelPtr(), iSequence); } + bool IsSequenceLooping( CStudioHdr *pStudioHdr, int iSequence ); + inline bool IsSequenceLooping( int iSequence ) { return IsSequenceLooping(GetModelPtr(),iSequence); } + float GetSequenceMoveDist( CStudioHdr *pStudioHdr, int iSequence ); + void GetSequenceLinearMotion( int iSequence, Vector *pVec ); + void GetBlendedLinearVelocity( Vector *pVec ); + int LookupSequence ( const char *label ); + int LookupActivity( const char *label ); + char const *GetSequenceName( int iSequence ); + char const *GetSequenceActivityName( int iSequence ); + Activity GetSequenceActivity( int iSequence ); + virtual void StudioFrameAdvance(); // advance animation frame to some time in the future + + // Clientside animation + virtual float FrameAdvance( float flInterval = 0.0f ); + virtual float GetSequenceCycleRate( CStudioHdr *pStudioHdr, int iSequence ); + virtual void UpdateClientSideAnimation(); + void ClientSideAnimationChanged(); + virtual unsigned int ComputeClientSideAnimationFlags(); + + void SetCycle( float flCycle ); + float GetCycle() const; + + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + + const char *GetBodygroupName( int iGroup ); + int FindBodygroupByName( const char *name ); + int GetBodygroupCount( int iGroup ); + int GetNumBodyGroups( void ); + + class CBoneCache *GetBoneCache( CStudioHdr *pStudioHdr ); + void SetHitboxSet( int setnum ); + void SetHitboxSetByName( const char *setname ); + int GetHitboxSet( void ); + char const *GetHitboxSetName( void ); + int GetHitboxSetCount( void ); + void DrawClientHitboxes( float duration = 0.0f, bool monocolor = false ); + + C_BaseAnimating* FindFollowedEntity(); + + virtual bool IsActivityFinished( void ) { return m_bSequenceFinished; } + inline bool IsSequenceFinished( void ); + inline bool SequenceLoops( void ) { return m_bSequenceLoops; } + + // All view model attachments origins are stretched so you can place entities at them and + // they will match up with where the attachment winds up being drawn on the view model, since + // the view models are drawn with a different FOV. + // + // If you're drawing something inside of a view model's DrawModel() function, then you want the + // original attachment origin instead of the adjusted one. To get that, call this on the + // adjusted attachment origin. + virtual void UncorrectViewModelAttachment( Vector &vOrigin ) {} + + // Call this if SetupBones() has already been called this frame but you need to move the + // entity and rerender. + void InvalidateBoneCache(); + bool IsBoneCacheValid() const; // Returns true if the bone cache is considered good for this frame. + void GetCachedBoneMatrix( int boneIndex, matrix3x4_t &out ); + + // Wrappers for CBoneAccessor. + const matrix3x4_t& GetBone( int iBone ) const; + matrix3x4_t& GetBoneForWrite( int iBone ); + + // Used for debugging. Will produce asserts if someone tries to setup bones or + // attachments before it's allowed. + // Use the "AutoAllowBoneAccess" class to auto push/pop bone access. + // Use a distinct "tag" when pushing/popping - asserts when push/pop tags do not match. + struct AutoAllowBoneAccess + { + AutoAllowBoneAccess( bool bAllowForNormalModels, bool bAllowForViewModels ); + ~AutoAllowBoneAccess( void ); + }; + static void PushAllowBoneAccess( bool bAllowForNormalModels, bool bAllowForViewModels, char const *tagPush ); + static void PopBoneAccess( char const *tagPop ); + static void ThreadedBoneSetup(); + static void InitBoneSetupThreadPool(); + static void ShutdownBoneSetupThreadPool(); + + // Invalidate bone caches so all SetupBones() calls force bone transforms to be regenerated. + static void InvalidateBoneCaches(); + + // Purpose: My physics object has been updated, react or extract data + virtual void VPhysicsUpdate( IPhysicsObject *pPhysics ); + + void DisableMuzzleFlash(); // Turn off the muzzle flash (ie: signal that we handled the server's event). + virtual void DoMuzzleFlash(); // Force a muzzle flash event. Note: this only QUEUES an event, so + // ProcessMuzzleFlashEvent will get called later. + bool ShouldMuzzleFlash() const; // Is the muzzle flash event on? + + // This is called to do the actual muzzle flash effect. + virtual void ProcessMuzzleFlashEvent(); + + // Update client side animations + static void UpdateClientSideAnimations(); + + // Load the model's keyvalues section and create effects listed inside it + void InitModelEffects( void ); + + // Sometimes the server wants to update the client's cycle to get the two to run in sync (for proper hit detection) + virtual void SetServerIntendedCycle( float intended ) { intended; } + virtual float GetServerIntendedCycle( void ) { return -1.0f; } + + // For prediction + int SelectWeightedSequence ( int activity ); + void ResetSequenceInfo( void ); + float SequenceDuration( void ); + float SequenceDuration( CStudioHdr *pStudioHdr, int iSequence ); + inline float SequenceDuration( int iSequence ) { return SequenceDuration(GetModelPtr(), iSequence); } + int FindTransitionSequence( int iCurrentSequence, int iGoalSequence, int *piDir ); + + void RagdollMoved( void ); + + virtual void GetToolRecordingState( KeyValues *msg ); + virtual void CleanupToolRecordingState( KeyValues *msg ); + + void SetReceivedSequence( void ); + virtual bool ShouldResetSequenceOnNewModel( void ); + + virtual bool IsViewModel() const; + +protected: + // View models scale their attachment positions to account for FOV. To get the unmodified + // attachment position (like if you're rendering something else during the view model's DrawModel call), + // use TransformViewModelAttachmentToWorld. + virtual void FormatViewModelAttachment( int nAttachment, matrix3x4_t &attachmentToWorld ) {} + + // View models say yes to this. + bool IsBoneAccessAllowed() const; + CMouthInfo& MouthInfo(); + + // Models used in a ModelPanel say yes to this + virtual bool IsMenuModel() const; + + // Allow studio models to tell C_BaseEntity what their m_nBody value is + virtual int GetStudioBody( void ) { return m_nBody; } + + virtual bool CalcAttachments(); + +private: + // This method should return true if the bones have changed + SetupBones needs to be called + virtual float LastBoneChangedTime() { return FLT_MAX; } + + CBoneList* RecordBones( CStudioHdr *hdr, matrix3x4_t *pBoneState ); + + bool PutAttachment( int number, const matrix3x4_t &attachmentToWorld ); + void TermRopes(); + + void DelayedInitModelEffects( void ); + + void UpdateRelevantInterpolatedVars(); + void AddBaseAnimatingInterpolatedVars(); + void RemoveBaseAnimatingInterpolatedVars(); + + void LockStudioHdr(); + void UnlockStudioHdr(); +public: + CRagdoll *m_pRagdoll; + + // Texture group to use + int m_nSkin; + + // Object bodygroup + int m_nBody; + + // Hitbox set to use (default 0) + int m_nHitboxSet; + + CSequenceTransitioner m_SequenceTransitioner; + +protected: + CIKContext *m_pIk; + + int m_iEyeAttachment; + + // Animation playback framerate + float m_flPlaybackRate; + + // Decomposed ragdoll info + bool m_bStoreRagdollInfo; + RagdollInfo_t *m_pRagdollInfo; + Vector m_vecForce; + int m_nForceBone; + + // Is bone cache valid + // bone transformation matrix + unsigned long m_iMostRecentModelBoneCounter; + unsigned long m_iMostRecentBoneSetupRequest; + int m_iPrevBoneMask; + int m_iAccumulatedBoneMask; + + CBoneAccessor m_BoneAccessor; + CThreadFastMutex m_BoneSetupLock; + + ClientSideAnimationListHandle_t m_ClientSideAnimationListHandle; + + // Client-side animation + bool m_bClientSideFrameReset; + +protected: + + float m_fadeMinDist; + float m_fadeMaxDist; + float m_flFadeScale; + +private: + + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // cycle index of when events were last checked + bool m_bSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + bool m_bSequenceLoops; // true if the sequence loops + + // Mouth lipsync/envelope following values + CMouthInfo m_mouth; + + CNetworkVar( float, m_flModelWidthScale ); + + // Animation blending factors + float m_flPoseParameter[MAXSTUDIOPOSEPARAM]; + CInterpolatedVarArray< float, MAXSTUDIOPOSEPARAM > m_iv_flPoseParameter; + float m_flOldPoseParameters[MAXSTUDIOPOSEPARAM]; + + int m_nPrevSequence; + int m_nRestoreSequence; + + // Ropes that got spawned when the model was created. + CUtlLinkedList m_Ropes; + + // event processing info + float m_flPrevEventCycle; + int m_nEventSequence; + + float m_flEncodedController[MAXSTUDIOBONECTRLS]; + CInterpolatedVarArray< float, MAXSTUDIOBONECTRLS > m_iv_flEncodedController; + float m_flOldEncodedController[MAXSTUDIOBONECTRLS]; + + // Clientside animation + bool m_bClientSideAnimation; + bool m_bLastClientSideFrameReset; + + int m_nNewSequenceParity; + int m_nResetEventsParity; + + int m_nPrevNewSequenceParity; + int m_nPrevResetEventsParity; + + bool m_builtRagdoll; + Vector m_vecPreRagdollMins; + Vector m_vecPreRagdollMaxs; + + // Current animation sequence + int m_nSequence; + bool m_bReceivedSequence; + + // Current cycle location from server +protected: + float m_flCycle; + CInterpolatedVar< float > m_iv_flCycle; + float m_flOldCycle; +private: + int m_nOldSequence; + CBoneMergeCache *m_pBoneMergeCache; // This caches the strcmp lookups that it has to do + // when merg + + CUtlVector< matrix3x4_t > m_CachedBoneData; // never access this directly. Use m_BoneAccessor. + memhandle_t m_hitboxBoneCacheHandle; + float m_flLastBoneSetupTime; + CJiggleBones *m_pJiggleBones; + + // Calculated attachment points + CUtlVector m_Attachments; + + void SetupBones_AttachmentHelper( CStudioHdr *pStudioHdr ); + + EHANDLE m_hLightingOrigin; + EHANDLE m_hLightingOriginRelative; + + // These are compared against each other to determine if the entity should muzzle flash. + CNetworkVar( unsigned char, m_nMuzzleFlashParity ); + unsigned char m_nOldMuzzleFlashParity; + + bool m_bInitModelEffects; + +private: + mutable CStudioHdr *m_pStudioHdr; + mutable MDLHandle_t m_hStudioHdr; + CThreadFastMutex m_StudioHdrInitLock; +}; + +enum +{ + RAGDOLL_FRICTION_OFF = -2, + RAGDOLL_FRICTION_NONE, + RAGDOLL_FRICTION_IN, + RAGDOLL_FRICTION_HOLD, + RAGDOLL_FRICTION_OUT, +}; + +class C_ClientRagdoll : public C_BaseAnimating, public IPVSNotify +{ + +public: + C_ClientRagdoll( bool bRestoring = true ); + DECLARE_CLASS( C_ClientRagdoll, C_BaseAnimating ); + DECLARE_DATADESC(); + + // inherited from IPVSNotify + virtual void OnPVSStatusChanged( bool bInPVS ); + + virtual void Release( void ); + virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ); + virtual void ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ); + void ClientThink( void ); + void ReleaseRagdoll( void ) { m_bReleaseRagdoll = true; } + bool ShouldSavePhysics( void ) { return true; } + virtual void OnSave(); + virtual void OnRestore(); + virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_SAVE_NON_NETWORKABLE; } + virtual IPVSNotify* GetPVSNotifyInterface() { return this; } + + void HandleAnimatedFriction( void ); + virtual void SUB_Remove( void ); + + void FadeOut( void ); + virtual float LastBoneChangedTime(); + + bool m_bFadeOut; + bool m_bImportant; + float m_flEffectTime; + +private: + int m_iCurrentFriction; + int m_iMinFriction; + int m_iMaxFriction; + float m_flFrictionModTime; + float m_flFrictionTime; + + int m_iFrictionAnimState; + bool m_bReleaseRagdoll; + + bool m_bFadingOut; + + float m_flScaleEnd[NUM_HITBOX_FIRES]; + float m_flScaleTimeStart[NUM_HITBOX_FIRES]; + float m_flScaleTimeEnd[NUM_HITBOX_FIRES]; +}; + +//----------------------------------------------------------------------------- +// Purpose: Serves the 90% case of calling SetSequence / ResetSequenceInfo. +//----------------------------------------------------------------------------- +inline void C_BaseAnimating::ResetSequence(int nSequence) +{ + SetSequence( nSequence ); + ResetSequenceInfo(); +} + +inline float C_BaseAnimating::GetPlaybackRate() +{ + return m_flPlaybackRate; +} + +inline void C_BaseAnimating::SetPlaybackRate( float rate ) +{ + m_flPlaybackRate = rate; +} + +inline const matrix3x4_t& C_BaseAnimating::GetBone( int iBone ) const +{ + return m_BoneAccessor.GetBone( iBone ); +} + +inline matrix3x4_t& C_BaseAnimating::GetBoneForWrite( int iBone ) +{ + return m_BoneAccessor.GetBoneForWrite( iBone ); +} + + +inline bool C_BaseAnimating::ShouldMuzzleFlash() const +{ + return m_nOldMuzzleFlashParity != m_nMuzzleFlashParity; +} + +inline float C_BaseAnimating::GetCycle() const +{ + return m_flCycle; +} + +//----------------------------------------------------------------------------- +// Purpose: return a pointer to an updated studiomdl cache cache +//----------------------------------------------------------------------------- + +inline CStudioHdr *C_BaseAnimating::GetModelPtr() const +{ +#ifdef _DEBUG + // GetModelPtr() is often called before OnNewModel() so go ahead and set it up first chance. + static IDataCacheSection *pModelCache = datacache->FindSection( "ModelData" ); + AssertOnce( pModelCache->IsFrameLocking() ); +#endif + if ( !m_pStudioHdr && GetModel() ) + { + const_cast(this)->LockStudioHdr(); + } + return ( m_pStudioHdr && m_pStudioHdr->IsValid() ) ? m_pStudioHdr : NULL; +} + + +inline void C_BaseAnimating::InvalidateMdlCache() +{ + UnlockStudioHdr(); + if ( m_pStudioHdr != NULL ) + { + delete m_pStudioHdr; + m_pStudioHdr = NULL; + } +} + +//----------------------------------------------------------------------------- +// Sequence access +//----------------------------------------------------------------------------- +inline int C_BaseAnimating::GetSequence() +{ + return m_nSequence; +} + +inline bool C_BaseAnimating::IsSequenceFinished( void ) +{ + return m_bSequenceFinished; +} + +inline float C_BaseAnimating::SequenceDuration( void ) +{ + return SequenceDuration( GetSequence() ); +} + + +//----------------------------------------------------------------------------- +// Mouth +//----------------------------------------------------------------------------- +inline CMouthInfo& C_BaseAnimating::MouthInfo() +{ + return m_mouth; +} + + +// FIXME: move these to somewhere that makes sense +void GetColumn( matrix3x4_t& src, int column, Vector &dest ); +void SetColumn( Vector &src, int column, matrix3x4_t& dest ); + +EXTERN_RECV_TABLE(DT_BaseAnimating); + + +extern void DevMsgRT( char const* pMsg, ... ); + +#endif // C_BASEANIMATING_H diff --git a/game/client/c_baseanimatingoverlay.cpp b/game/client/c_baseanimatingoverlay.cpp new file mode 100644 index 00000000..ec8e65bd --- /dev/null +++ b/game/client/c_baseanimatingoverlay.cpp @@ -0,0 +1,566 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "c_baseanimatingoverlay.h" +#include "bone_setup.h" +#include "tier0/vprof.h" +#include "engine/IVDebugOverlay.h" +#include "datacache/imdlcache.h" +#include "eventlist.h" + +#include "dt_utlvector_recv.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar r_sequence_debug; + +C_BaseAnimatingOverlay::C_BaseAnimatingOverlay() +{ + // FIXME: where does this initialization go now? + //for ( int i=0; i < MAX_OVERLAYS; i++ ) + //{ + // memset( &m_Layer[i], 0, sizeof(m_Layer[0]) ); + // m_Layer[i].m_nOrder = MAX_OVERLAYS; + //} + + // FIXME: where does this initialization go now? + // AddVar( m_Layer, &m_iv_AnimOverlay, LATCH_ANIMATION_VAR ); +} + +#undef CBaseAnimatingOverlay + + + +BEGIN_RECV_TABLE_NOBASE(CAnimationLayer, DT_Animationlayer) + RecvPropInt( RECVINFO_NAME(m_nSequence, m_nSequence)), + RecvPropFloat( RECVINFO_NAME(m_flCycle, m_flCycle)), + RecvPropFloat( RECVINFO_NAME(m_flPrevCycle, m_flPrevCycle)), + RecvPropFloat( RECVINFO_NAME(m_flWeight, m_flWeight)), + RecvPropInt( RECVINFO_NAME(m_nOrder, m_nOrder)) +END_RECV_TABLE() + +const char *s_m_iv_AnimOverlayNames[C_BaseAnimatingOverlay::MAX_OVERLAYS] = +{ + "C_BaseAnimatingOverlay::m_iv_AnimOverlay00", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay01", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay02", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay03", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay04", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay05", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay06", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay07", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay08", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay09", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay10", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay11", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay12", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay13", + "C_BaseAnimatingOverlay::m_iv_AnimOverlay14" +}; + +void ResizeAnimationLayerCallback( void *pStruct, int offsetToUtlVector, int len ) +{ + C_BaseAnimatingOverlay *pEnt = (C_BaseAnimatingOverlay*)pStruct; + CUtlVector < C_AnimationLayer > *pVec = &pEnt->m_AnimOverlay; + CUtlVector< CInterpolatedVar< C_AnimationLayer > > *pVecIV = &pEnt->m_iv_AnimOverlay; + + Assert( (char*)pVec - (char*)pEnt == offsetToUtlVector ); + Assert( pVec->Count() == pVecIV->Count() ); + Assert( pVec->Count() <= C_BaseAnimatingOverlay::MAX_OVERLAYS ); + + int diff = len - pVec->Count(); + + + + if ( diff == 0 ) + return; + + // remove all entries + for ( int i=0; i < pVec->Count(); i++ ) + { + pEnt->RemoveVar( &pVec->Element( i ) ); + } + + // adjust vector sizes + if ( diff > 0 ) + { + pVec->AddMultipleToTail( diff ); + pVecIV->AddMultipleToTail( diff ); + } + else + { + pVec->RemoveMultiple( len, -diff ); + pVecIV->RemoveMultiple( len, -diff ); + } + + // Rebind all the variables in the ent's list. + for ( int i=0; i < len; i++ ) + { + IInterpolatedVar *pWatcher = &pVecIV->Element( i ); + pWatcher->SetDebugName( s_m_iv_AnimOverlayNames[i] ); + pEnt->AddVar( &pVec->Element( i ), pWatcher, LATCH_ANIMATION_VAR, true ); + } + // FIXME: need to set historical values of nOrder in pVecIV to MAX_OVERLAY + +} + + +BEGIN_RECV_TABLE_NOBASE( C_BaseAnimatingOverlay, DT_OverlayVars ) + RecvPropUtlVector( + RECVINFO_UTLVECTOR_SIZEFN( m_AnimOverlay, ResizeAnimationLayerCallback ), + C_BaseAnimatingOverlay::MAX_OVERLAYS, + RecvPropDataTable(NULL, 0, 0, &REFERENCE_RECV_TABLE( DT_Animationlayer ) ) ) +END_RECV_TABLE() + + +IMPLEMENT_CLIENTCLASS_DT( C_BaseAnimatingOverlay, DT_BaseAnimatingOverlay, CBaseAnimatingOverlay ) + RecvPropDataTable( "overlay_vars", 0, 0, &REFERENCE_RECV_TABLE( DT_OverlayVars ) ) +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_BaseAnimatingOverlay ) + +/* + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[0][2].m_nSequence, FIELD_INTEGER ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[0][2].m_flCycle, FIELD_FLOAT ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[0][2].m_flPlaybackRate, FIELD_FLOAT), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[0][2].m_flWeight, FIELD_FLOAT), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[1][2].m_nSequence, FIELD_INTEGER ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[1][2].m_flCycle, FIELD_FLOAT ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[1][2].m_flPlaybackRate, FIELD_FLOAT), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[1][2].m_flWeight, FIELD_FLOAT), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[2][2].m_nSequence, FIELD_INTEGER ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[2][2].m_flCycle, FIELD_FLOAT ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[2][2].m_flPlaybackRate, FIELD_FLOAT), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[2][2].m_flWeight, FIELD_FLOAT), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[3][2].m_nSequence, FIELD_INTEGER ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[3][2].m_flCycle, FIELD_FLOAT ), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[3][2].m_flPlaybackRate, FIELD_FLOAT), + DEFINE_FIELD( C_BaseAnimatingOverlay, m_Layer[3][2].m_flWeight, FIELD_FLOAT), +*/ + +END_PREDICTION_DATA() + +C_AnimationLayer* C_BaseAnimatingOverlay::GetAnimOverlay( int i ) +{ + Assert( i >= 0 && i < MAX_OVERLAYS ); + return &m_AnimOverlay[i]; +} + + +void C_BaseAnimatingOverlay::SetNumAnimOverlays( int num ) +{ + if ( m_AnimOverlay.Count() < num ) + { + m_AnimOverlay.AddMultipleToTail( num - m_AnimOverlay.Count() ); + } + else if ( m_AnimOverlay.Count() > num ) + { + m_AnimOverlay.RemoveMultiple( num, m_AnimOverlay.Count() - num ); + } +} + + +int C_BaseAnimatingOverlay::GetNumAnimOverlays() const +{ + return m_AnimOverlay.Count(); +} + + +void C_BaseAnimatingOverlay::GetRenderBounds( Vector& theMins, Vector& theMaxs ) +{ + BaseClass::GetRenderBounds( theMins, theMaxs ); + + if ( !IsRagdoll() ) + { + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( !pStudioHdr || !pStudioHdr->SequencesAvailable() ) + return; + + int nSequences = pStudioHdr->GetNumSeq(); + + int i; + for (i = 0; i < m_AnimOverlay.Count(); i++) + { + if (m_AnimOverlay[i].m_flWeight > 0.0) + { + if ( m_AnimOverlay[i].m_nSequence >= nSequences ) + { + continue; + } + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( m_AnimOverlay[i].m_nSequence ); + VectorMin( seqdesc.bbmin, theMins, theMins ); + VectorMax( seqdesc.bbmax, theMaxs, theMaxs ); + } + } + } +} + + + +void C_BaseAnimatingOverlay::CheckForLayerChanges( CStudioHdr *hdr, float currentTime ) +{ + CDisableRangeChecks disableRangeChecks; + + bool bLayersChanged = false; + + // FIXME: damn, there has to be a better way than this. + int i; + for (i = 0; i < m_iv_AnimOverlay.Count(); i++) + { + CDisableRangeChecks disableRangeChecks; + + int iHead, iPrev1, iPrev2; + m_iv_AnimOverlay[i].GetInterpolationInfo( currentTime, &iHead, &iPrev1, &iPrev2 ); + + // fake up previous cycle values. + float t0; + C_AnimationLayer *pHead = m_iv_AnimOverlay[i].GetHistoryValue( iHead, t0 ); + // reset previous + float t1; + C_AnimationLayer *pPrev1 = m_iv_AnimOverlay[i].GetHistoryValue( iPrev1, t1 ); + // reset previous previous + float t2; + C_AnimationLayer *pPrev2 = m_iv_AnimOverlay[i].GetHistoryValue( iPrev2, t2 ); + + if ( pHead && pPrev1 && pHead->m_nSequence != pPrev1->m_nSequence ) + { + bLayersChanged = true; + #if 1 // _DEBUG + if (/* Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex()) + { + DevMsgRT( "(%7.4f : %30s : %5.3f : %4.2f : %1d)\n", t0, hdr->pSeqdesc( pHead->m_nSequence ).pszLabel(), (float)pHead->m_flCycle, (float)pHead->m_flWeight, i ); + DevMsgRT( "(%7.4f : %30s : %5.3f : %4.2f : %1d)\n", t1, hdr->pSeqdesc( pPrev1->m_nSequence ).pszLabel(), (float)pPrev1->m_flCycle, (float)pPrev1->m_flWeight, i ); + if (pPrev2) + DevMsgRT( "(%7.4f : %30s : %5.3f : %4.2f : %1d)\n", t2, hdr->pSeqdesc( pPrev2->m_nSequence ).pszLabel(), (float)pPrev2->m_flCycle, (float)pPrev2->m_flWeight, i ); + } + #endif + + if (pPrev1) + { + pPrev1->m_nSequence = pHead->m_nSequence; + pPrev1->m_flCycle = pHead->m_flPrevCycle; + pPrev1->m_flWeight = pHead->m_flWeight; + } + + if (pPrev2) + { + float num = 0; + if ( fabs( t0 - t1 ) > 0.001f ) + num = (t2 - t1) / (t0 - t1); + + pPrev2->m_nSequence = pHead->m_nSequence; + float flTemp; + if (IsSequenceLooping( hdr, pHead->m_nSequence )) + { + flTemp = LoopingLerp( num, (float)pHead->m_flPrevCycle, (float)pHead->m_flCycle ); + } + else + { + flTemp = Lerp( num, (float)pHead->m_flPrevCycle, (float)pHead->m_flCycle ); + } + pPrev2->m_flCycle = flTemp; + pPrev2->m_flWeight = pHead->m_flWeight; + } + + /* + if (stricmp( r_seq_overlay_debug.GetString(), hdr->name ) == 0) + { + DevMsgRT( "(%30s %6.2f : %6.2f : %6.2f)\n", hdr->pSeqdesc( pHead->nSequence ).pszLabel(), (float)pPrev2->m_flCycle, (float)pPrev1->m_flCycle, (float)pHead->m_flCycle ); + } + */ + + m_iv_AnimOverlay[i].SetLooping( IsSequenceLooping( hdr, pHead->m_nSequence ) ); + m_iv_AnimOverlay[i].Interpolate( currentTime ); + + // reset event indexes + m_flOverlayPrevEventCycle[i] = pHead->m_flPrevCycle - 0.01; + } + } + + if (bLayersChanged) + { + // render bounds may have changed + UpdateVisibility(); + } +} + + + +void C_BaseAnimatingOverlay::AccumulateLayers( CStudioHdr *hdr, Vector pos[], Quaternion q[], float poseparam[], float currentTime, int boneMask ) +{ + BaseClass::AccumulateLayers( hdr, pos, q, poseparam, currentTime, boneMask ); + int i; + + // resort the layers + int layer[MAX_OVERLAYS]; + for (i = 0; i < MAX_OVERLAYS; i++) + { + layer[i] = MAX_OVERLAYS; + } + for (i = 0; i < m_AnimOverlay.Count(); i++) + { + if (m_AnimOverlay[i].m_nOrder < MAX_OVERLAYS) + { + /* + Assert( layer[m_AnimOverlay[i].m_nOrder] == MAX_OVERLAYS ); + layer[m_AnimOverlay[i].m_nOrder] = i; + */ + // hacky code until initialization of new layers is finished + if (layer[m_AnimOverlay[i].m_nOrder] != MAX_OVERLAYS) + { + m_AnimOverlay[i].m_nOrder = MAX_OVERLAYS; + } + else + { + layer[m_AnimOverlay[i].m_nOrder] = i; + } + } + } + + CheckForLayerChanges( hdr, currentTime ); + + int nSequences = hdr->GetNumSeq(); + + // add in the overlay layers + int j; + for (j = 0; j < MAX_OVERLAYS; j++) + { + i = layer[ j ]; + if (i < m_AnimOverlay.Count()) + { + if ( m_AnimOverlay[i].m_nSequence >= nSequences ) + { + continue; + } + + /* + DevMsgRT( 1 , "%.3f %.3f %.3f\n", currentTime, fWeight, dadt ); + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), -j - 1, 0, + "%2d(%s) : %6.2f : %6.2f", + m_AnimOverlay[i].m_nSequence, + hdr->pSeqdesc( m_AnimOverlay[i].m_nSequence )->pszLabel(), + m_AnimOverlay[i].m_flCycle, + m_AnimOverlay[i].m_flWeight + ); + */ + + float fWeight = m_AnimOverlay[i].m_flWeight; + + if (fWeight > 0) + { + // check to see if the sequence changed + // FIXME: move this to somewhere more reasonable + // do a nice spline interpolation of the values + // if ( m_AnimOverlay[i].m_nSequence != m_iv_AnimOverlay.GetPrev( i )->nSequence ) + float fCycle = m_AnimOverlay[ i ].m_flCycle; + + fCycle = ClampCycle( fCycle, IsSequenceLooping( m_AnimOverlay[i].m_nSequence ) ); + + if (fWeight > 1) + fWeight = 1; + + AccumulatePose( hdr, m_pIk, pos, q, m_AnimOverlay[i].m_nSequence, fCycle, poseparam, boneMask, fWeight, currentTime ); + +#if 1 // _DEBUG + if (/* Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex()) + { + if (1) + { + DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f : %1d\n", currentTime, hdr->pSeqdesc( m_AnimOverlay[i].m_nSequence ).pszLabel(), fCycle, fWeight, i ); + } + else + { + int iHead, iPrev1, iPrev2; + m_iv_AnimOverlay[i].GetInterpolationInfo( currentTime, &iHead, &iPrev1, &iPrev2 ); + + // fake up previous cycle values. + float t0; + C_AnimationLayer *pHead = m_iv_AnimOverlay[i].GetHistoryValue( iHead, t0 ); + // reset previous + float t1; + C_AnimationLayer *pPrev1 = m_iv_AnimOverlay[i].GetHistoryValue( iPrev1, t1 ); + // reset previous previous + float t2; + C_AnimationLayer *pPrev2 = m_iv_AnimOverlay[i].GetHistoryValue( iPrev2, t2 ); + + if ( pHead && pPrev1 && pPrev2 ) + { + DevMsgRT( "%6.2f : %30s %6.2f (%6.2f:%6.2f:%6.2f) : %6.2f (%6.2f:%6.2f:%6.2f) : %1d\n", currentTime, hdr->pSeqdesc( m_AnimOverlay[i].m_nSequence ).pszLabel(), + fCycle, (float)pPrev2->m_flCycle, (float)pPrev1->m_flCycle, (float)pHead->m_flCycle, + fWeight, (float)pPrev2->m_flWeight, (float)pPrev1->m_flWeight, (float)pHead->m_flWeight, + i ); + } + else + { + DevMsgRT( "%6.2f : %30s %6.2f : %6.2f : %1d\n", currentTime, hdr->pSeqdesc( m_AnimOverlay[i].m_nSequence ).pszLabel(), fCycle, fWeight, i ); + } + + } + } +#endif + +//#define DEBUG_TF2_OVERLAYS +#if defined( DEBUG_TF2_OVERLAYS ) + engine->Con_NPrintf( 10 + j, "%30s %6.2f : %6.2f : %1d", hdr->pSeqdesc( m_AnimOverlay[i].m_nSequence ).pszLabel(), fCycle, fWeight, i ); + } + else + { + engine->Con_NPrintf( 10 + j, "%30s %6.2f : %6.2f : %1d", " ", 0.f, 0.f, i ); +#endif + } + } +#if defined( DEBUG_TF2_OVERLAYS ) + else + { + engine->Con_NPrintf( 10 + j, "%30s %6.2f : %6.2f : %1d", " ", 0.f, 0.f, i ); + } +#endif + } +} + +void C_BaseAnimatingOverlay::DoAnimationEvents( CStudioHdr *pStudioHdr ) +{ + if ( !pStudioHdr || !pStudioHdr->SequencesAvailable() ) + return; + + MDLCACHE_CRITICAL_SECTION(); + + int nSequences = pStudioHdr->GetNumSeq(); + + BaseClass::DoAnimationEvents( pStudioHdr ); + + bool watch = false; // Q_strstr( hdr->name, "rifle" ) ? true : false; + + CheckForLayerChanges( pStudioHdr, gpGlobals->curtime ); // !!! + + int j; + for (j = 0; j < m_AnimOverlay.Count(); j++) + { + if ( m_AnimOverlay[j].m_nSequence >= nSequences ) + { + continue; + } + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( m_AnimOverlay[j].m_nSequence ); + if ( seqdesc.numevents == 0 ) + continue; + + // stalled? + if (m_AnimOverlay[j].m_flCycle == m_flOverlayPrevEventCycle[j]) + continue; + + bool bLoopingSequence = IsSequenceLooping( m_AnimOverlay[j].m_nSequence ); + + bool bLooped = false; + + //in client code, m_flOverlayPrevEventCycle is set to -1 when we first start an overlay, looping or not + if ( bLoopingSequence && + m_flOverlayPrevEventCycle[j] > 0.0f && + m_AnimOverlay[j].m_flCycle <= m_flOverlayPrevEventCycle[j] ) + { + if (m_flOverlayPrevEventCycle[j] - m_AnimOverlay[j].m_flCycle > 0.5) + { + bLooped = true; + } + else + { + // things have backed up, which is bad since it'll probably result in a hitch in the animation playback + // but, don't play events again for the same time slice + return; + } + } + + mstudioevent_t *pevent = seqdesc.pEvent( 0 ); + + // This makes sure events that occur at the end of a sequence occur are + // sent before events that occur at the beginning of a sequence. + if (bLooped) + { + for (int i = 0; i < (int)seqdesc.numevents; i++) + { + // ignore all non-client-side events + if ( pevent[i].type & AE_TYPE_NEWEVENTSYSTEM ) + { + if ( !( pevent[i].type & AE_TYPE_CLIENT ) ) + continue; + } + else if ( pevent[i].event < 5000 ) //Adrian - Support the old event system + continue; + + if ( pevent[i].cycle <= m_flOverlayPrevEventCycle[j] ) + continue; + + if ( watch ) + { + Msg( "%i FE %i Looped cycle %f, prev %f ev %f (time %.3f)\n", + gpGlobals->tickcount, + pevent[i].event, + pevent[i].cycle, + m_flOverlayPrevEventCycle[j], + m_AnimOverlay[j].m_flCycle, + gpGlobals->curtime ); + } + + + FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ); + } + + // Necessary to get the next loop working + m_flOverlayPrevEventCycle[j] = -0.01; + } + + for (int i = 0; i < (int)seqdesc.numevents; i++) + { + if ( pevent[i].type & AE_TYPE_NEWEVENTSYSTEM ) + { + if ( !( pevent[i].type & AE_TYPE_CLIENT ) ) + continue; + } + else if ( pevent[i].event < 5000 ) //Adrian - Support the old event system + continue; + + if ( (pevent[i].cycle > m_flOverlayPrevEventCycle[j] && pevent[i].cycle <= m_AnimOverlay[j].m_flCycle) ) + { + if ( watch ) + { + Msg( "%i (seq: %d) FE %i Normal cycle %f, prev %f ev %f (time %.3f)\n", + gpGlobals->tickcount, + m_AnimOverlay[j].m_nSequence, + pevent[i].event, + pevent[i].cycle, + m_flOverlayPrevEventCycle[j], + m_AnimOverlay[j].m_flCycle, + gpGlobals->curtime ); + } + + FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ); + } + } + + m_flOverlayPrevEventCycle[j] = m_AnimOverlay[j].m_flCycle; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStudioHdr *C_BaseAnimatingOverlay::OnNewModel() +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); + + // Clear out animation layers + for ( int i=0; i < m_AnimOverlay.Count(); i++ ) + { + m_AnimOverlay[i].Reset(); + m_AnimOverlay[i].m_nOrder = MAX_OVERLAYS; + } + + return hdr; +} \ No newline at end of file diff --git a/game/client/c_baseanimatingoverlay.h b/game/client/c_baseanimatingoverlay.h new file mode 100644 index 00000000..d371371d --- /dev/null +++ b/game/client/c_baseanimatingoverlay.h @@ -0,0 +1,68 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef C_BASEANIMATINGOVERLAY_H +#define C_BASEANIMATINGOVERLAY_H +#pragma once + +#include "c_baseanimating.h" + +// For shared code. +#define CBaseAnimatingOverlay C_BaseAnimatingOverlay + + +class C_BaseAnimatingOverlay : public C_BaseAnimating +{ +public: + DECLARE_CLASS( C_BaseAnimatingOverlay, C_BaseAnimating ); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + DECLARE_INTERPOLATION(); + + + C_BaseAnimatingOverlay(); + + virtual CStudioHdr *OnNewModel(); + + C_AnimationLayer* GetAnimOverlay( int i ); + void SetNumAnimOverlays( int num ); // This makes sure there is space for this # of layers. + int GetNumAnimOverlays() const; + + virtual void GetRenderBounds( Vector& theMins, Vector& theMaxs ); + + void CheckForLayerChanges( CStudioHdr *hdr, float currentTime ); + + // model specific + virtual void AccumulateLayers( CStudioHdr *hdr, Vector pos[], Quaternion q[], float poseparam[], float currentTime, int boneMask ); + + virtual void DoAnimationEvents( CStudioHdr *pStudioHdr ); + + enum + { + MAX_OVERLAYS = 15, + }; + + CUtlVector < C_AnimationLayer > m_AnimOverlay; + + CUtlVector < CInterpolatedVar< C_AnimationLayer > > m_iv_AnimOverlay; + + float m_flOverlayPrevEventCycle[ MAX_OVERLAYS ]; + +private: + C_BaseAnimatingOverlay( const C_BaseAnimatingOverlay & ); // not defined, not accessible +}; + + +EXTERN_RECV_TABLE(DT_BaseAnimatingOverlay); + + +#endif // C_BASEANIMATINGOVERLAY_H + + + + diff --git a/game/client/c_basecombatcharacter.cpp b/game/client/c_basecombatcharacter.cpp new file mode 100644 index 00000000..019b6438 --- /dev/null +++ b/game/client/c_basecombatcharacter.cpp @@ -0,0 +1,93 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client's C_BaseCombatCharacter entity +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basecombatcharacter.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined( CBaseCombatCharacter ) +#undef CBaseCombatCharacter +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BaseCombatCharacter::C_BaseCombatCharacter() +{ + for ( int i=0; i < m_iAmmo.Count(); i++ ) + m_iAmmo.Set( i, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BaseCombatCharacter::~C_BaseCombatCharacter() +{ +} + +/* +//----------------------------------------------------------------------------- +// Purpose: Returns the amount of ammunition of the specified type the character's carrying +//----------------------------------------------------------------------------- +int C_BaseCombatCharacter::GetAmmoCount( char *szName ) const +{ + return GetAmmoCount( g_pGameRules->GetAmmoDef()->Index(szName) ); +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: Overload our muzzle flash and send it to any actively held weapon +//----------------------------------------------------------------------------- +void C_BaseCombatCharacter::DoMuzzleFlash() +{ + // Our weapon takes our muzzle flash command + C_BaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( pWeapon ) + { + pWeapon->DoMuzzleFlash(); + //NOTENOTE: We do not chain to the base here + } + else + { + BaseClass::DoMuzzleFlash(); + } +} +IMPLEMENT_CLIENTCLASS(C_BaseCombatCharacter, DT_BaseCombatCharacter, CBaseCombatCharacter); + +// Only send active weapon index to local player +BEGIN_RECV_TABLE_NOBASE( C_BaseCombatCharacter, DT_BCCLocalPlayerExclusive ) + RecvPropTime( RECVINFO( m_flNextAttack ) ), +END_RECV_TABLE(); + + +BEGIN_RECV_TABLE(C_BaseCombatCharacter, DT_BaseCombatCharacter) + RecvPropDataTable( "bcc_localdata", 0, 0, &REFERENCE_RECV_TABLE(DT_BCCLocalPlayerExclusive) ), + RecvPropEHandle( RECVINFO( m_hActiveWeapon ) ), + RecvPropArray3( RECVINFO_ARRAY(m_hMyWeapons), RecvPropEHandle( RECVINFO( m_hMyWeapons[0] ) ) ), + +#ifdef INVASION_CLIENT_DLL + RecvPropInt( RECVINFO( m_iPowerups ) ), +#endif + +END_RECV_TABLE() + + +BEGIN_PREDICTION_DATA( C_BaseCombatCharacter ) + + DEFINE_PRED_ARRAY( m_iAmmo, FIELD_INTEGER, MAX_AMMO_TYPES, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flNextAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_hActiveWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_ARRAY( m_hMyWeapons, FIELD_EHANDLE, MAX_WEAPONS, FTYPEDESC_INSENDTABLE ), + +END_PREDICTION_DATA() diff --git a/game/client/c_basecombatcharacter.h b/game/client/c_basecombatcharacter.h new file mode 100644 index 00000000..92631350 --- /dev/null +++ b/game/client/c_basecombatcharacter.h @@ -0,0 +1,147 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Defines the client-side representation of CBaseCombatCharacter. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_BASECOMBATCHARACTER_H +#define C_BASECOMBATCHARACTER_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "shareddefs.h" +#include "c_baseflex.h" + +class C_BaseCombatWeapon; +class C_WeaponCombatShield; + +class C_BaseCombatCharacter : public C_BaseFlex +{ + DECLARE_CLASS( C_BaseCombatCharacter, C_BaseFlex ); +public: + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_BaseCombatCharacter( void ); + virtual ~C_BaseCombatCharacter( void ); + + virtual bool IsBaseCombatCharacter( void ) { return true; }; + virtual C_BaseCombatCharacter *MyCombatCharacterPointer( void ) { return this; } + + // ----------------------- + // Ammo + // ----------------------- + void RemoveAmmo( int iCount, int iAmmoIndex ); + void RemoveAmmo( int iCount, const char *szName ); + void RemoveAllAmmo( ); + int GetAmmoCount( int iAmmoIndex ) const; + int GetAmmoCount( char *szName ) const; + + C_BaseCombatWeapon* Weapon_OwnsThisType( const char *pszWeapon, int iSubType = 0 ) const; // True if already owns a weapon of this class + virtual bool Weapon_Switch( C_BaseCombatWeapon *pWeapon, int viewmodelindex = 0 ); + virtual bool Weapon_CanSwitchTo(C_BaseCombatWeapon *pWeapon); + + // I can't use my current weapon anymore. Switch me to the next best weapon. + bool SwitchToNextBestWeapon(C_BaseCombatWeapon *pCurrent); + + virtual C_BaseCombatWeapon *GetActiveWeapon( void ) const; + int WeaponCount() const; + C_BaseCombatWeapon *GetWeapon( int i ) const; + + // This is a sort of hack back-door only used by physgun! + void SetAmmoCount( int iCount, int iAmmoIndex ); + + float GetNextAttack() const { return m_flNextAttack; } + void SetNextAttack( float flWait ) { m_flNextAttack = flWait; } + + virtual int BloodColor(); + + // Blood color (see BLOOD_COLOR_* macros in baseentity.h) + void SetBloodColor( int nBloodColor ); + + virtual void DoMuzzleFlash(); + +public: + + float m_flNextAttack; + + +protected: + + int m_bloodColor; // color of blood particless + + +private: + CNetworkArray( int, m_iAmmo, MAX_AMMO_TYPES ); + + CHandle m_hMyWeapons[MAX_WEAPONS]; + CHandle< C_BaseCombatWeapon > m_hActiveWeapon; +private: + C_BaseCombatCharacter( const C_BaseCombatCharacter & ); // not defined, not accessible + + +//----------------------- +#ifdef INVASION_CLIENT_DLL +public: + virtual void Release( void ); + virtual void SetDormant( bool bDormant ); + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink( void ); + + // TF2 Powerups + virtual bool CanBePoweredUp( void ) { return true; } + bool HasPowerup( int iPowerup ) { return ( m_iPowerups & (1 << iPowerup) ) != 0; }; + virtual void PowerupStart( int iPowerup, bool bInitial ); + virtual void PowerupEnd( int iPowerup ); + void RemoveAllPowerups( void ); + + // Powerup effects + void AddEMPEffect( float flSize ); + void AddBuffEffect( float flSize ); + + C_WeaponCombatShield *GetShield( void ); + +public: + int m_iPowerups; + int m_iPrevPowerups; +#endif + +}; + +inline C_BaseCombatCharacter *ToBaseCombatCharacter( C_BaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsBaseCombatCharacter() ) + return NULL; + +#if _DEBUG + return dynamic_cast( pEntity ); +#else + return static_cast( pEntity ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int C_BaseCombatCharacter::WeaponCount() const +{ + return MAX_WEAPONS; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : i - +//----------------------------------------------------------------------------- +inline C_BaseCombatWeapon *C_BaseCombatCharacter::GetWeapon( int i ) const +{ + Assert( (i >= 0) && (i < MAX_WEAPONS) ); + return m_hMyWeapons[i].Get(); +} + +EXTERN_RECV_TABLE(DT_BaseCombatCharacter); + +#endif // C_BASECOMBATCHARACTER_H diff --git a/game/client/c_basecombatweapon.cpp b/game/client/c_basecombatweapon.cpp new file mode 100644 index 00000000..85301001 --- /dev/null +++ b/game/client/c_basecombatweapon.cpp @@ -0,0 +1,530 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client side implementation of CBaseCombatWeapon. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "history_resource.h" +#include "iclientmode.h" +#include "iinput.h" +#include "weapon_selection.h" +#include "hud_crosshair.h" +#include "engine/ivmodelinfo.h" +#include "tier0/vprof.h" +#include "hltvcamera.h" +#include "tier1/KeyValues.h" +#include "toolframework/itoolframework.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Gets the local client's active weapon, if any. +//----------------------------------------------------------------------------- +C_BaseCombatWeapon *GetActiveWeapon( void ) +{ + C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); + + if ( !player ) + return NULL; + + return player->GetActiveWeapon(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::SetDormant( bool bDormant ) +{ + // If I'm going from active to dormant and I'm carried by another player, holster me. + if ( !IsDormant() && bDormant && !IsCarriedByLocalPlayer() ) + { + Holster( NULL ); + } + + BaseClass::SetDormant( bDormant ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::NotifyShouldTransmit( ShouldTransmitState_t state ) +{ + BaseClass::NotifyShouldTransmit(state); + + if (state == SHOULDTRANSMIT_END) + { + if (m_iState == WEAPON_IS_ACTIVE) + { + m_iState = WEAPON_IS_CARRIED_BY_PLAYER; + } + } + else if( state == SHOULDTRANSMIT_START ) + { + if( m_iState == WEAPON_IS_CARRIED_BY_PLAYER ) + { + if( GetOwner() && GetOwner()->GetActiveWeapon() == this ) + { + // Restore the Activeness of the weapon if we client-twiddled it off in the first case above. + m_iState = WEAPON_IS_ACTIVE; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: To wrap PORTAL mod specific functionality into one place +//----------------------------------------------------------------------------- +static inline bool ShouldDrawLocalPlayer( void ) +{ +#if defined( PORTAL ) + return true; +#else + return C_BasePlayer::ShouldDrawLocalPlayer(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::OnRestore() +{ + BaseClass::OnRestore(); + + // if the player is holding this weapon, + // mark it as just restored so it won't show as a new pickup + if (GetOwner() == C_BasePlayer::GetLocalPlayer()) + { + m_bJustRestored = true; + } +} + +int C_BaseCombatWeapon::GetWorldModelIndex( void ) +{ + return m_iWorldModelIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged(updateType); + + CHandle< C_BaseCombatWeapon > handle = this; + + // If it's being carried by the *local* player, on the first update, + // find the registered weapon for this ID + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + C_BaseCombatCharacter *pOwner = GetOwner(); + + // check if weapon is carried by local player + bool bIsLocalPlayer = pPlayer && pPlayer == pOwner; + if ( bIsLocalPlayer && !ShouldDrawLocalPlayer() ) + { + // If I was just picked up, or created & immediately carried, add myself to this client's list of weapons + if ( (m_iState != WEAPON_NOT_CARRIED ) && (m_iOldState == WEAPON_NOT_CARRIED) ) + { + // Tell the HUD this weapon's been picked up + if ( ShouldDrawPickup() ) + { + CBaseHudWeaponSelection *pHudSelection = GetHudWeaponSelection(); + if ( pHudSelection ) + { + pHudSelection->OnWeaponPickup( this ); + } + + pPlayer->EmitSound( "Player.PickupWeapon" ); + } + } + } + else // weapon carried by other player or not at all + { + // See comment below + EnsureCorrectRenderingModel(); + } + + UpdateVisibility(); + + m_iOldState = m_iState; + + m_bJustRestored = false; +} + +//----------------------------------------------------------------------------- +// Is anyone carrying it? +//----------------------------------------------------------------------------- +bool C_BaseCombatWeapon::IsBeingCarried() const +{ + return ( m_hOwner.Get() != NULL ); +} + +//----------------------------------------------------------------------------- +// Is the carrier alive? +//----------------------------------------------------------------------------- +bool C_BaseCombatWeapon::IsCarrierAlive() const +{ + if ( !m_hOwner.Get() ) + return false; + + return m_hOwner.Get()->GetHealth() > 0; +} + +//----------------------------------------------------------------------------- +// Should this object cast shadows? +//----------------------------------------------------------------------------- +ShadowType_t C_BaseCombatWeapon::ShadowCastType() +{ + if ( IsEffectActive( /*EF_NODRAW |*/ EF_NOSHADOW ) ) + return SHADOWS_NONE; + + if (!IsBeingCarried()) + return SHADOWS_RENDER_TO_TEXTURE; + + if (IsCarriedByLocalPlayer()) + return SHADOWS_NONE; + + return (m_iState != WEAPON_IS_CARRIED_BY_PLAYER) ? SHADOWS_RENDER_TO_TEXTURE : SHADOWS_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: This weapon is the active weapon, and it should now draw anything +// it wants to. This gets called every frame. +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::Redraw() +{ + if ( g_pClientMode->ShouldDrawCrosshair() ) + { + DrawCrosshair(); + } + + // ammo drawing has been moved into hud_ammo.cpp +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the weapon's crosshair +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::DrawCrosshair() +{ + C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); + if ( !player ) + return; + + Color clr = gHUD.m_clrNormal; +/* + + // TEST: if the thing under your crosshair is on a different team, light the crosshair with a different color. + Vector vShootPos, vShootAngles; + GetShootPosition( vShootPos, vShootAngles ); + + Vector vForward; + AngleVectors( vShootAngles, &vForward ); + + + // Change the color depending on if we're looking at a friend or an enemy. + CPartitionFilterListMask filter( PARTITION_ALL_CLIENT_EDICTS ); + trace_t tr; + traceline->TraceLine( vShootPos, vShootPos + vForward * 10000, COLLISION_GROUP_NONE, MASK_SHOT, &tr, true, ~0, &filter ); + + if ( tr.index != 0 && tr.index != INVALID_CLIENTENTITY_HANDLE ) + { + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( tr.index ); + if ( pEnt ) + { + if ( pEnt->GetTeamNumber() != player->GetTeamNumber() ) + { + g = b = 0; + } + } + } +*/ + + CHudCrosshair *crosshair = GET_HUDELEMENT( CHudCrosshair ); + if ( !crosshair ) + return; + + // Find out if this weapon's auto-aimed onto a target + bool bOnTarget = ( m_iState == WEAPON_IS_ONTARGET ); + + if ( player->GetFOV() >= 90 ) + { + // normal crosshairs + if ( bOnTarget && GetWpnData().iconAutoaim ) + { + clr[3] = 255; + + crosshair->SetCrosshair( GetWpnData().iconAutoaim, clr ); + } + else if ( GetWpnData().iconCrosshair ) + { + clr[3] = 255; + crosshair->SetCrosshair( GetWpnData().iconCrosshair, clr ); + } + else + { + crosshair->ResetCrosshair(); + } + } + else + { + Color white( 255, 255, 255, 255 ); + + // zoomed crosshairs + if (bOnTarget && GetWpnData().iconZoomedAutoaim) + crosshair->SetCrosshair(GetWpnData().iconZoomedAutoaim, white); + else if ( GetWpnData().iconZoomedCrosshair ) + crosshair->SetCrosshair( GetWpnData().iconZoomedCrosshair, white ); + else + crosshair->ResetCrosshair(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: This weapon is the active weapon, and the viewmodel for it was just drawn. +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::ViewModelDrawn( C_BaseViewModel *pViewModel ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this client's carrying this weapon +//----------------------------------------------------------------------------- +bool C_BaseCombatWeapon::IsCarriedByLocalPlayer( void ) +{ + if ( !GetOwner() ) + return false; + + return ( GetOwner() == C_BasePlayer::GetLocalPlayer() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this weapon is the local client's currently wielded weapon +//----------------------------------------------------------------------------- +bool C_BaseCombatWeapon::IsActiveByLocalPlayer( void ) +{ + if ( IsCarriedByLocalPlayer() ) + { + return (m_iState == WEAPON_IS_ACTIVE); + } + + return false; +} + +bool C_BaseCombatWeapon::GetShootPosition( Vector &vOrigin, QAngle &vAngles ) +{ + // Get the entity because the weapon doesn't have the right angles. + C_BaseCombatCharacter *pEnt = ToBaseCombatCharacter( GetOwner() ); + if ( pEnt ) + { + if ( pEnt == C_BasePlayer::GetLocalPlayer() ) + { + vAngles = pEnt->EyeAngles(); + } + else + { + vAngles = pEnt->GetRenderAngles(); + } + } + else + { + vAngles.Init(); + } + + QAngle vDummy; + if ( IsActiveByLocalPlayer() && !ShouldDrawLocalPlayer() ) + { + C_BasePlayer *player = ToBasePlayer( pEnt ); + C_BaseViewModel *vm = player ? player->GetViewModel( 0 ) : NULL; + if ( vm ) + { + int iAttachment = vm->LookupAttachment( "muzzle" ); + if ( vm->GetAttachment( iAttachment, vOrigin, vDummy ) ) + { + return true; + } + } + } + else + { + // Thirdperson + int iAttachment = LookupAttachment( "muzzle" ); + if ( GetAttachment( iAttachment, vOrigin, vDummy ) ) + { + return true; + } + } + + vOrigin = GetRenderOrigin(); + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseCombatWeapon::ShouldDraw( void ) +{ + if ( m_iWorldModelIndex == 0 ) + return false; + + // FIXME: All weapons with owners are set to transmit in CBaseCombatWeapon::UpdateTransmitState, + // even if they have EF_NODRAW set, so we have to check this here. Ideally they would never + // transmit except for the weapons owned by the local player. + if ( IsEffectActive( EF_NODRAW ) ) + return false; + + C_BaseCombatCharacter *pOwner = GetOwner(); + + // weapon has no owner, always draw it + if ( !pOwner ) + return true; + + bool bIsActive = ( m_iState == WEAPON_IS_ACTIVE ); + + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + + // carried by local player? + if ( pOwner == pLocalPlayer ) + { + // Only ever show the active weapon + if ( !bIsActive ) + return false; + + // 3rd person mode + if ( ShouldDrawLocalPlayer() ) + return true; + + // don't draw active weapon if not in some kind of 3rd person mode, the viewmodel will do that + return false; + } + + // If it's a player, then only show active weapons + if ( pOwner->IsPlayer() ) + { + // Show it if it's active... + return bIsActive; + } + + // FIXME: We may want to only show active weapons on NPCs + // These are carried by AIs; always show them + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if a weapon-pickup icon should be displayed when this weapon is received +//----------------------------------------------------------------------------- +bool C_BaseCombatWeapon::ShouldDrawPickup( void ) +{ + if ( GetWeaponFlags() & ITEM_FLAG_NOITEMPICKUP ) + return false; + + if ( m_bJustRestored ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Render the weapon. Draw the Viewmodel if the weapon's being carried +// by this player, otherwise draw the worldmodel. +//----------------------------------------------------------------------------- +int C_BaseCombatWeapon::DrawModel( int flags ) +{ + VPROF_BUDGET( "C_BaseCombatWeapon::DrawModel", VPROF_BUDGETGROUP_MODEL_RENDERING ); + if ( !m_bReadyToDraw ) + return 0; + + if ( !IsVisible() ) + return 0; + + // check if local player chases owner of this weapon in first person + C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); + + if ( localplayer && localplayer->IsObserver() && GetOwner() ) + { + // don't draw weapon if chasing this guy as spectator + // we don't check that in ShouldDraw() since this may change + // without notification + + if ( localplayer->GetObserverMode() == OBS_MODE_IN_EYE && + localplayer->GetObserverTarget() == GetOwner() ) + return false; + } + + // See comment below + EnsureCorrectRenderingModel(); + + return BaseClass::DrawModel( flags ); +} + +// If the local player is visible (thirdperson mode, tf2 taunts, etc., then make sure that we are using the +// w_ (world) model not the v_ (view) model or else the model can flicker, etc. +// Otherwise, if we're not the local player, always use the world model +void C_BaseCombatWeapon::EnsureCorrectRenderingModel() +{ + C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); + if ( localplayer && + localplayer == GetOwner() && + !ShouldDrawLocalPlayer() ) + { + return; + } + + // BRJ 10/14/02 + // FIXME: Remove when Yahn's client-side prediction is done + // It's a hacky workaround for the model indices fighting + // (GetRenderBounds uses the model index, which is for the view model) + SetModelIndex( GetWorldModelIndex() ); + + // Validate our current sequence just in case ( in theory the view and weapon models should have the same sequences for sequences that overlap at least ) + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( pStudioHdr && + GetSequence() >= pStudioHdr->GetNumSeq() ) + { + SetSequence( 0 ); + } +} + +//----------------------------------------------------------------------------- +// tool recording +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::GetToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + int nModelIndex = GetModelIndex(); + int nWorldModelIndex = GetWorldModelIndex(); + if ( nModelIndex != nWorldModelIndex ) + { + SetModelIndex( nWorldModelIndex ); + } + + BaseClass::GetToolRecordingState( msg ); + + if ( m_iState == WEAPON_NOT_CARRIED ) + { + BaseEntityRecordingState_t *pBaseEntity = (BaseEntityRecordingState_t*)msg->GetPtr( "baseentity" ); + pBaseEntity->m_nOwner = -1; + } + else + { + msg->SetInt( "worldmodel", 1 ); + if ( m_iState == WEAPON_IS_ACTIVE ) + { + BaseEntityRecordingState_t *pBaseEntity = (BaseEntityRecordingState_t*)msg->GetPtr( "baseentity" ); + pBaseEntity->m_bVisible = true; + } + } + + if ( nModelIndex != nWorldModelIndex ) + { + SetModelIndex( nModelIndex ); + } +} diff --git a/game/client/c_basecombatweapon.h b/game/client/c_basecombatweapon.h new file mode 100644 index 00000000..6c514a80 --- /dev/null +++ b/game/client/c_basecombatweapon.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client's CBaseCombatWeapon entity +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#ifndef C_BASECOMBATWEAPON_H +#define C_BASECOMBATWEAPON_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "basecombatweapon_shared.h" +#include "weapons_resource.h" + +class CViewSetup; +class C_BaseViewModel; + +// Accessors for local weapons +C_BaseCombatWeapon *GetActiveWeapon( void ); + + +#endif // C_BASECOMBATWEAPON \ No newline at end of file diff --git a/game/client/c_basedoor.cpp b/game/client/c_basedoor.cpp new file mode 100644 index 00000000..ff8829fe --- /dev/null +++ b/game/client/c_basedoor.cpp @@ -0,0 +1,28 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basedoor.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef CBaseDoor +#undef CBaseDoor +#endif + +IMPLEMENT_CLIENTCLASS_DT(C_BaseDoor, DT_BaseDoor, CBaseDoor) + RecvPropFloat(RECVINFO(m_flWaveHeight)), +END_RECV_TABLE() + +C_BaseDoor::C_BaseDoor( void ) +{ + m_flWaveHeight = 0.0f; +} + +C_BaseDoor::~C_BaseDoor( void ) +{ +} diff --git a/game/client/c_basedoor.h b/game/client/c_basedoor.h new file mode 100644 index 00000000..c638abb3 --- /dev/null +++ b/game/client/c_basedoor.h @@ -0,0 +1,32 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( C_BASEDOOR_H ) +#define C_BASEDOOR_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" + +#if defined( CLIENT_DLL ) +#define CBaseDoor C_BaseDoor +#endif + +class C_BaseDoor : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_BaseDoor, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_BaseDoor( void ); + ~C_BaseDoor( void ); + +public: + float m_flWaveHeight; +}; + +#endif // C_BASEDOOR_H \ No newline at end of file diff --git a/game/client/c_baseentity.cpp b/game/client/c_baseentity.cpp new file mode 100644 index 00000000..1dcde22a --- /dev/null +++ b/game/client/c_baseentity.cpp @@ -0,0 +1,6204 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_baseentity.h" +#include "prediction.h" +#include "model_types.h" +#include "iviewrender_beams.h" +#include "dlight.h" +#include "iviewrender.h" +#include "view.h" +#include "iefx.h" +#include "c_team.h" +#include "clientmode.h" +#include "usercmd.h" +#include "engine/IEngineSound.h" +#include "crtdbg.h" +#include "engine/IEngineTrace.h" +#include "engine/ivmodelinfo.h" +#include "tier0/vprof.h" +#include "fx_line.h" +#include "interface.h" +#include "materialsystem/IMaterialSystem.h" +#include "soundinfo.h" +#include "mathlib/vmatrix.h" +#include "isaverestore.h" +#include "interval.h" +#include "engine/ivdebugoverlay.h" +#include "c_ai_basenpc.h" +#include "apparent_velocity_helper.h" +#include "c_baseanimatingoverlay.h" +#include "tier1/KeyValues.h" +#include "hltvcamera.h" +#include "datacache/imdlcache.h" +#include "toolframework/itoolframework.h" +#include "toolframework_client.h" +#include "decals.h" +#include "cdll_bounded_cvars.h" +#include "inetchannelinfo.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#ifdef INTERPOLATEDVAR_PARANOID_MEASUREMENT + int g_nInterpolatedVarsChanged = 0; + bool g_bRestoreInterpolatedVarValues = false; +#endif + + +static bool g_bWasSkipping = (bool)-1; +static bool g_bWasThreaded =(bool)-1; +static int g_nThreadModeTicks = 0; +static ConVar cl_interp_threadmodeticks( "cl_interp_threadmodeticks", "0", 0, "Additional interpolation ticks to use when interpolating with threaded engine mode set." ); + + +void cc_cl_interp_all_changed( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + ConVarRef var( pConVar ); + if ( var.GetInt() ) + { + C_BaseEntityIterator iterator; + C_BaseEntity *pEnt; + while ( (pEnt = iterator.Next()) != NULL ) + { + if ( pEnt->ShouldInterpolate() ) + { + pEnt->AddToInterpolationList(); + } + } + } +} + + +static ConVar cl_extrapolate( "cl_extrapolate", "1", FCVAR_CHEAT, "Enable/disable extrapolation if interpolation history runs out." ); +static ConVar cl_interp_npcs( "cl_interp_npcs", "0.0", FCVAR_USERINFO, "Interpolate NPC positions starting this many seconds in past (or cl_interp, if greater)" ); +static ConVar cl_interp_all( "cl_interp_all", "0", 0, "Disable interpolation list optimizations.", 0, 0, 0, 0, cc_cl_interp_all_changed ); +ConVar r_drawmodeldecals( "r_drawmodeldecals", "1" ); +extern ConVar cl_showerror; +int C_BaseEntity::m_nPredictionRandomSeed = -1; +C_BasePlayer *C_BaseEntity::m_pPredictionPlayer = NULL; +bool C_BaseEntity::s_bAbsQueriesValid = true; +bool C_BaseEntity::s_bAbsRecomputationEnabled = true; +bool C_BaseEntity::s_bInterpolate = true; + +bool C_BaseEntity::sm_bDisableTouchFuncs = false; // Disables PhysicsTouch and PhysicsStartTouch function calls + +static ConVar r_drawrenderboxes( "r_drawrenderboxes", "0", FCVAR_CHEAT ); + +static bool g_bAbsRecomputationStack[8]; +static unsigned short g_iAbsRecomputationStackPos = 0; + +// All the entities that want Interpolate() called on them. +static CUtlLinkedList g_InterpolationList; +static CUtlLinkedList g_TeleportList; + +#if !defined( NO_ENTITY_PREDICTION ) +//----------------------------------------------------------------------------- +// Purpose: Maintains a list of predicted or client created entities +//----------------------------------------------------------------------------- +class CPredictableList : public IPredictableList +{ +public: + virtual C_BaseEntity *GetPredictable( int slot ); + virtual int GetPredictableCount( void ); + +protected: + void AddToPredictableList( ClientEntityHandle_t add ); + void RemoveFromPredictablesList( ClientEntityHandle_t remove ); + +private: + CUtlVector< ClientEntityHandle_t > m_Predictables; + + friend class C_BaseEntity; +}; + +// Create singleton +static CPredictableList g_Predictables; +IPredictableList *predictables = &g_Predictables; + +//----------------------------------------------------------------------------- +// Purpose: Add entity to list +// Input : add - +// Output : int +//----------------------------------------------------------------------------- +void CPredictableList::AddToPredictableList( ClientEntityHandle_t add ) +{ + // This is a hack to remap slot to index + if ( m_Predictables.Find( add ) != m_Predictables.InvalidIndex() ) + { + return; + } + + // Add to general list + m_Predictables.AddToTail( add ); + + // Maintain sort order by entindex + int count = m_Predictables.Size(); + if ( count < 2 ) + return; + + int i, j; + for ( i = 0; i < count; i++ ) + { + for ( j = i + 1; j < count; j++ ) + { + ClientEntityHandle_t h1 = m_Predictables[ i ]; + ClientEntityHandle_t h2 = m_Predictables[ j ]; + + C_BaseEntity *p1 = cl_entitylist->GetBaseEntityFromHandle( h1 ); + C_BaseEntity *p2 = cl_entitylist->GetBaseEntityFromHandle( h2 ); + + if ( !p1 || !p2 ) + { + Assert( 0 ); + continue; + } + + if ( p1->entindex() != -1 && + p2->entindex() != -1 ) + { + if ( p1->entindex() < p2->entindex() ) + continue; + } + + if ( p2->entindex() == -1 ) + continue; + + m_Predictables[ i ] = h2; + m_Predictables[ j ] = h1; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : remove - +//----------------------------------------------------------------------------- +void CPredictableList::RemoveFromPredictablesList( ClientEntityHandle_t remove ) +{ + m_Predictables.FindAndRemove( remove ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : slot - +// Output : C_BaseEntity +//----------------------------------------------------------------------------- +C_BaseEntity *CPredictableList::GetPredictable( int slot ) +{ + return cl_entitylist->GetBaseEntityFromHandle( m_Predictables[ slot ] ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CPredictableList::GetPredictableCount( void ) +{ + return m_Predictables.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Searc predictables for previously created entity (by testId) +// Input : testId - +// Output : static C_BaseEntity +//----------------------------------------------------------------------------- +static C_BaseEntity *FindPreviouslyCreatedEntity( CPredictableId& testId ) +{ + int c = predictables->GetPredictableCount(); + + int i; + for ( i = 0; i < c; i++ ) + { + C_BaseEntity *e = predictables->GetPredictable( i ); + if ( !e || !e->IsClientCreated() ) + continue; + + // Found it, note use of operator == + if ( testId == e->m_PredictableID ) + { + return e; + } + } + + return NULL; +} +#endif + +abstract_class IRecordingList +{ +public: + virtual ~IRecordingList() {}; + virtual void AddToList( ClientEntityHandle_t add ) = 0; + virtual void RemoveFromList( ClientEntityHandle_t remove ) = 0; + + virtual int Count() = 0; + virtual IClientRenderable *Get( int index ) = 0; +}; + +class CRecordingList : public IRecordingList +{ +public: + virtual void AddToList( ClientEntityHandle_t add ); + virtual void RemoveFromList( ClientEntityHandle_t remove ); + + virtual int Count(); + IClientRenderable *Get( int index ); +private: + CUtlVector< ClientEntityHandle_t > m_Recording; +}; + +static CRecordingList g_RecordingList; +IRecordingList *recordinglist = &g_RecordingList; + +//----------------------------------------------------------------------------- +// Purpose: Add entity to list +// Input : add - +// Output : int +//----------------------------------------------------------------------------- +void CRecordingList::AddToList( ClientEntityHandle_t add ) +{ + // This is a hack to remap slot to index + if ( m_Recording.Find( add ) != m_Recording.InvalidIndex() ) + { + return; + } + + // Add to general list + m_Recording.AddToTail( add ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : remove - +//----------------------------------------------------------------------------- +void CRecordingList::RemoveFromList( ClientEntityHandle_t remove ) +{ + m_Recording.FindAndRemove( remove ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : slot - +// Output : IClientRenderable +//----------------------------------------------------------------------------- +IClientRenderable *CRecordingList::Get( int index ) +{ + return cl_entitylist->GetClientRenderableFromHandle( m_Recording[ index ] ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CRecordingList::Count() +{ + return m_Recording.Count(); +} + +// Should these be somewhere else? +#define PITCH 0 + +// HACK HACK: 3/28/02 ywb Had to proxy around this or interpolation is borked in multiplayer, not sure what +// the issue is, just a global optimizer bug I presume +#pragma optimize( "g", off ) +//----------------------------------------------------------------------------- +// Purpose: Decodes animtime and notes when it changes +// Input : *pStruct - ( C_BaseEntity * ) used to flag animtime is changine +// *pVarData - +// *pIn - +// objectID - +//----------------------------------------------------------------------------- +void RecvProxy_AnimTime( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_BaseEntity *pEntity = ( C_BaseEntity * )pStruct; + Assert( pOut == &pEntity->m_flAnimTime ); + + int t; + int tickbase; + int addt; + + // Unpack the data. + addt = pData->m_Value.m_Int; + + // Note, this needs to be encoded relative to packet timestamp, not raw client clock + tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() ); + + t = tickbase; + // and then go back to floating point time. + t += addt; // Add in an additional up to 256 100ths from the server + + // center m_flAnimTime around current time. + while (t < gpGlobals->tickcount - 127) + t += 256; + while (t > gpGlobals->tickcount + 127) + t -= 256; + + pEntity->m_flAnimTime = ( t * TICK_INTERVAL ); +} + +void RecvProxy_SimulationTime( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_BaseEntity *pEntity = ( C_BaseEntity * )pStruct; + Assert( pOut == &pEntity->m_flSimulationTime ); + + int t; + int tickbase; + int addt; + + // Unpack the data. + addt = pData->m_Value.m_Int; + + // Note, this needs to be encoded relative to packet timestamp, not raw client clock + tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() ); + + t = tickbase; + // and then go back to floating point time. + t += addt; // Add in an additional up to 256 100ths from the server + + // center m_flSimulationTime around current time. + while (t < gpGlobals->tickcount - 127) + t += 256; + while (t > gpGlobals->tickcount + 127) + t -= 256; + + pEntity->m_flSimulationTime = ( t * TICK_INTERVAL ); +} + +void RecvProxy_LocalVelocity( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + CBaseEntity *pEnt = (CBaseEntity *)pStruct; + + Vector vecVelocity; + + vecVelocity.x = pData->m_Value.m_Vector[0]; + vecVelocity.y = pData->m_Value.m_Vector[1]; + vecVelocity.z = pData->m_Value.m_Vector[2]; + + // SetLocalVelocity checks to see if the value has changed + pEnt->SetLocalVelocity( vecVelocity ); +} +void RecvProxy_ToolRecording( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + if ( !ToolsEnabled() ) + return; + + CBaseEntity *pEnt = (CBaseEntity *)pStruct; + pEnt->SetToolRecording( pData->m_Value.m_Int != 0 ); +} + +#pragma optimize( "g", on ) + +// Expose it to the engine. +IMPLEMENT_CLIENTCLASS(C_BaseEntity, DT_BaseEntity, CBaseEntity); + +static void RecvProxy_MoveType( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + ((C_BaseEntity*)pStruct)->SetMoveType( (MoveType_t)(pData->m_Value.m_Int) ); +} + +static void RecvProxy_MoveCollide( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + ((C_BaseEntity*)pStruct)->SetMoveCollide( (MoveCollide_t)(pData->m_Value.m_Int) ); +} + +static void RecvProxy_Solid( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + ((C_BaseEntity*)pStruct)->SetSolid( (SolidType_t)pData->m_Value.m_Int ); +} + +static void RecvProxy_SolidFlags( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + ((C_BaseEntity*)pStruct)->SetSolidFlags( pData->m_Value.m_Int ); +} + +void RecvProxy_EffectFlags( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + ((C_BaseEntity*)pStruct)->SetEffects( pData->m_Value.m_Int ); +} + + +BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_AnimTimeMustBeFirst ) + RecvPropInt( RECVINFO(m_flAnimTime), 0, RecvProxy_AnimTime ), +END_RECV_TABLE() + + +#ifndef NO_ENTITY_PREDICTION +BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_PredictableId ) + RecvPropPredictableId( RECVINFO( m_PredictableID ) ), + RecvPropInt( RECVINFO( m_bIsPlayerSimulated ) ), +END_RECV_TABLE() +#endif + + +BEGIN_RECV_TABLE_NOBASE(C_BaseEntity, DT_BaseEntity) + RecvPropDataTable( "AnimTimeMustBeFirst", 0, 0, &REFERENCE_RECV_TABLE(DT_AnimTimeMustBeFirst) ), + RecvPropInt( RECVINFO(m_flSimulationTime), 0, RecvProxy_SimulationTime ), + + RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), +#if PREDICTION_ERROR_CHECK_LEVEL > 1 + RecvPropVector( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ), +#else + RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ), +#endif + RecvPropInt(RECVINFO(m_nModelIndex) ), + + RecvPropInt(RECVINFO(m_fEffects), 0, RecvProxy_EffectFlags ), + RecvPropInt(RECVINFO(m_nRenderMode)), + RecvPropInt(RECVINFO(m_nRenderFX)), + RecvPropInt(RECVINFO(m_clrRender)), + RecvPropInt(RECVINFO(m_iTeamNum)), + RecvPropInt(RECVINFO(m_CollisionGroup)), + RecvPropFloat(RECVINFO(m_flElasticity)), + RecvPropFloat(RECVINFO(m_flShadowCastDistance)), + RecvPropEHandle( RECVINFO(m_hOwnerEntity) ), + RecvPropEHandle( RECVINFO(m_hEffectEntity) ), + RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ), + RecvPropInt( RECVINFO( m_iParentAttachment ) ), + + RecvPropInt( "movetype", 0, SIZEOF_IGNORE, 0, RecvProxy_MoveType ), + RecvPropInt( "movecollide", 0, SIZEOF_IGNORE, 0, RecvProxy_MoveCollide ), + RecvPropDataTable( RECVINFO_DT( m_Collision ), 0, &REFERENCE_RECV_TABLE(DT_CollisionProperty) ), + + RecvPropInt( RECVINFO ( m_iTextureFrameIndex ) ), +#if !defined( NO_ENTITY_PREDICTION ) + RecvPropDataTable( "predictable_id", 0, 0, &REFERENCE_RECV_TABLE( DT_PredictableId ) ), +#endif + + RecvPropInt ( RECVINFO( m_bSimulatedEveryTick ), 0, RecvProxy_InterpolationAmountChanged ), + RecvPropInt ( RECVINFO( m_bAnimatedEveryTick ), 0, RecvProxy_InterpolationAmountChanged ), + RecvPropBool ( RECVINFO( m_bAlternateSorting ) ), + +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA_NO_BASE( C_BaseEntity ) + + // These have a special proxy to handle send/receive + DEFINE_PRED_TYPEDESCRIPTION( m_Collision, CCollisionProperty ), + + DEFINE_PRED_FIELD( m_MoveType, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_MoveCollide, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), + + DEFINE_FIELD( m_vecAbsVelocity, FIELD_VECTOR ), + DEFINE_PRED_FIELD_TOL( m_vecVelocity, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.5f ), +// DEFINE_PRED_FIELD( m_fEffects, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nRenderMode, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nRenderFX, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), +// DEFINE_PRED_FIELD( m_flAnimTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), +// DEFINE_PRED_FIELD( m_flSimulationTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_fFlags, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD_TOL( m_vecViewOffset, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.25f ), + DEFINE_PRED_FIELD( m_nModelIndex, FIELD_SHORT, FTYPEDESC_INSENDTABLE | FTYPEDESC_MODELINDEX ), + DEFINE_PRED_FIELD( m_flFriction, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_iTeamNum, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_hOwnerEntity, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), + +// DEFINE_FIELD( m_nSimulationTick, FIELD_INTEGER ), + + DEFINE_PRED_FIELD( m_hNetworkMoveParent, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), +// DEFINE_PRED_FIELD( m_pMoveParent, FIELD_EHANDLE ), +// DEFINE_PRED_FIELD( m_pMoveChild, FIELD_EHANDLE ), +// DEFINE_PRED_FIELD( m_pMovePeer, FIELD_EHANDLE ), +// DEFINE_PRED_FIELD( m_pMovePrevPeer, FIELD_EHANDLE ), + + DEFINE_PRED_FIELD_TOL( m_vecNetworkOrigin, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.002f ), + DEFINE_PRED_FIELD( m_angNetworkAngles, FIELD_VECTOR, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ), + DEFINE_FIELD( m_vecAbsOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ), + DEFINE_FIELD( m_vecOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_angRotation, FIELD_VECTOR ), + +// DEFINE_FIELD( m_hGroundEntity, FIELD_EHANDLE ), + DEFINE_FIELD( m_nWaterLevel, FIELD_CHARACTER ), + DEFINE_FIELD( m_nWaterType, FIELD_CHARACTER ), + DEFINE_FIELD( m_vecAngVelocity, FIELD_VECTOR ), +// DEFINE_FIELD( m_vecAbsAngVelocity, FIELD_VECTOR ), + + +// DEFINE_FIELD( model, FIELD_INTEGER ), // writing pointer literally +// DEFINE_FIELD( index, FIELD_INTEGER ), +// DEFINE_FIELD( m_ClientHandle, FIELD_SHORT ), +// DEFINE_FIELD( m_Partition, FIELD_SHORT ), +// DEFINE_FIELD( m_hRender, FIELD_SHORT ), + DEFINE_FIELD( m_bDormant, FIELD_BOOLEAN ), +// DEFINE_FIELD( current_position, FIELD_INTEGER ), +// DEFINE_FIELD( m_flLastMessageTime, FIELD_FLOAT ), + DEFINE_FIELD( m_vecBaseVelocity, FIELD_VECTOR ), + DEFINE_FIELD( m_iEFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_flGravity, FIELD_FLOAT ), +// DEFINE_FIELD( m_ModelInstance, FIELD_SHORT ), + DEFINE_FIELD( m_flProxyRandomValue, FIELD_FLOAT ), + +// DEFINE_FIELD( m_PredictableID, FIELD_INTEGER ), +// DEFINE_FIELD( m_pPredictionContext, FIELD_POINTER ), + // Stuff specific to rendering and therefore not to be copied back and forth + // DEFINE_PRED_FIELD( m_clrRender, color32, FTYPEDESC_INSENDTABLE ), + // DEFINE_FIELD( m_bReadyToDraw, FIELD_BOOLEAN ), + // DEFINE_FIELD( anim, CLatchedAnim ), + // DEFINE_FIELD( mouth, CMouthInfo ), + // DEFINE_FIELD( GetAbsOrigin(), FIELD_VECTOR ), + // DEFINE_FIELD( GetAbsAngles(), FIELD_VECTOR ), + // DEFINE_FIELD( m_nNumAttachments, FIELD_SHORT ), + // DEFINE_FIELD( m_pAttachmentAngles, FIELD_VECTOR ), + // DEFINE_FIELD( m_pAttachmentOrigin, FIELD_VECTOR ), + // DEFINE_FIELD( m_listentry, CSerialEntity ), + // DEFINE_FIELD( m_ShadowHandle, ClientShadowHandle_t ), + // DEFINE_FIELD( m_hThink, ClientThinkHandle_t ), + // Definitely private and not copied around + // DEFINE_FIELD( m_bPredictable, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_CollisionGroup, FIELD_INTEGER ), + // DEFINE_FIELD( m_DataChangeEventRef, FIELD_INTEGER ), +#if !defined( CLIENT_DLL ) + // DEFINE_FIELD( m_bPredictionEligible, FIELD_BOOLEAN ), +#endif +END_PREDICTION_DATA() + +//----------------------------------------------------------------------------- +// Helper functions. +//----------------------------------------------------------------------------- + +void SpewInterpolatedVar( CInterpolatedVar< Vector > *pVar ) +{ + Msg( "--------------------------------------------------\n" ); + int i = pVar->GetHead(); + CApparentVelocity apparent; + float prevtime = 0.0f; + while ( 1 ) + { + float changetime; + Vector *pVal = pVar->GetHistoryValue( i, changetime ); + if ( !pVal ) + break; + + float vel = apparent.AddSample( changetime, *pVal ); + Msg( "%6.6f: (%.2f %.2f %.2f), vel: %.2f [dt %.1f]\n", changetime, VectorExpand( *pVal ), vel, prevtime == 0.0f ? 0.0f : 1000.0f * ( changetime - prevtime ) ); + i = pVar->GetNext( i ); + prevtime = changetime; + } + Msg( "--------------------------------------------------\n" ); +} + +void SpewInterpolatedVar( CInterpolatedVar< Vector > *pVar, float flNow, float flInterpAmount, bool bSpewAllEntries = true ) +{ + float target = flNow - flInterpAmount; + + Msg( "--------------------------------------------------\n" ); + int i = pVar->GetHead(); + CApparentVelocity apparent; + float newtime = 999999.0f; + Vector newVec( 0, 0, 0 ); + bool bSpew = true; + + while ( 1 ) + { + float changetime; + Vector *pVal = pVar->GetHistoryValue( i, changetime ); + if ( !pVal ) + break; + + if ( bSpew && target >= changetime ) + { + Vector o; + pVar->DebugInterpolate( &o, flNow ); + bool bInterp = newtime != 999999.0f; + float frac = 0.0f; + char desc[ 32 ]; + + if ( bInterp ) + { + frac = ( target - changetime ) / ( newtime - changetime ); + Q_snprintf( desc, sizeof( desc ), "interpolated [%.2f]", frac ); + } + else + { + bSpew = true; + int savei = i; + i = pVar->GetNext( i ); + float oldtertime = 0.0f; + pVar->GetHistoryValue( i, oldtertime ); + + if ( changetime != oldtertime ) + { + frac = ( target - changetime ) / ( changetime - oldtertime ); + } + + Q_snprintf( desc, sizeof( desc ), "extrapolated [%.2f]", frac ); + i = savei; + } + + if ( bSpew ) + { + Msg( " > %6.6f: (%.2f %.2f %.2f) %s for %.1f msec\n", + target, + VectorExpand( o ), + desc, + 1000.0f * ( target - changetime ) ); + bSpew = false; + } + } + + float vel = apparent.AddSample( changetime, *pVal ); + if ( bSpewAllEntries ) + { + Msg( " %6.6f: (%.2f %.2f %.2f), vel: %.2f [dt %.1f]\n", changetime, VectorExpand( *pVal ), vel, newtime == 999999.0f ? 0.0f : 1000.0f * ( newtime - changetime ) ); + } + i = pVar->GetNext( i ); + newtime = changetime; + newVec = *pVal; + } + Msg( "--------------------------------------------------\n" ); +} +void SpewInterpolatedVar( CInterpolatedVar< float > *pVar ) +{ + Msg( "--------------------------------------------------\n" ); + int i = pVar->GetHead(); + CApparentVelocity apparent; + while ( 1 ) + { + float changetime; + float *pVal = pVar->GetHistoryValue( i, changetime ); + if ( !pVal ) + break; + + float vel = apparent.AddSample( changetime, *pVal ); + Msg( "%6.6f: (%.2f), vel: %.2f\n", changetime, *pVal, vel ); + i = pVar->GetNext( i ); + } + Msg( "--------------------------------------------------\n" ); +} + +template +void GetInterpolatedVarTimeRange( CInterpolatedVar *pVar, float &flMin, float &flMax ) +{ + flMin = 1e23; + flMax = -1e23; + + int i = pVar->GetHead(); + CApparentVelocity apparent; + while ( 1 ) + { + float changetime; + if ( !pVar->GetHistoryValue( i, changetime ) ) + return; + + flMin = min( flMin, changetime ); + flMax = max( flMax, changetime ); + i = pVar->GetNext( i ); + } +} + + +//----------------------------------------------------------------------------- +// Global methods related to when abs data is correct +//----------------------------------------------------------------------------- +void C_BaseEntity::SetAbsQueriesValid( bool bValid ) +{ + // @MULTICORE: Always allow in worker threads, assume higher level code is handling correctly + if ( !ThreadInMainThread() ) + return; + + if ( !bValid ) + { + s_bAbsQueriesValid = false; + } + else + { + s_bAbsQueriesValid = true; + } +} + +bool C_BaseEntity::IsAbsQueriesValid( void ) +{ + if ( !ThreadInMainThread() ) + return true; + return s_bAbsQueriesValid; +} + +void C_BaseEntity::PushEnableAbsRecomputations( bool bEnable ) +{ + if ( !ThreadInMainThread() ) + return; + if ( g_iAbsRecomputationStackPos < ARRAYSIZE( g_bAbsRecomputationStack ) ) + { + g_bAbsRecomputationStack[g_iAbsRecomputationStackPos] = s_bAbsRecomputationEnabled; + ++g_iAbsRecomputationStackPos; + s_bAbsRecomputationEnabled = bEnable; + } + else + { + Assert( false ); + } +} + +void C_BaseEntity::PopEnableAbsRecomputations() +{ + if ( !ThreadInMainThread() ) + return; + if ( g_iAbsRecomputationStackPos > 0 ) + { + --g_iAbsRecomputationStackPos; + s_bAbsRecomputationEnabled = g_bAbsRecomputationStack[g_iAbsRecomputationStackPos]; + } + else + { + Assert( false ); + } +} + +void C_BaseEntity::EnableAbsRecomputations( bool bEnable ) +{ + if ( !ThreadInMainThread() ) + return; + // This should only be called at the frame level. Use PushEnableAbsRecomputations + // if you're blocking out a section of code. + Assert( g_iAbsRecomputationStackPos == 0 ); + + s_bAbsRecomputationEnabled = bEnable; +} + +bool C_BaseEntity::IsAbsRecomputationsEnabled() +{ + if ( !ThreadInMainThread() ) + return true; + return s_bAbsRecomputationEnabled; +} + +int C_BaseEntity::GetTextureFrameIndex( void ) +{ + return m_iTextureFrameIndex; +} + +void C_BaseEntity::SetTextureFrameIndex( int iIndex ) +{ + m_iTextureFrameIndex = iIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *map - +//----------------------------------------------------------------------------- +void C_BaseEntity::Interp_SetupMappings( VarMapping_t *map ) +{ + if( !map ) + return; + + int c = map->m_Entries.Count(); + for ( int i = 0; i < c; i++ ) + { + VarMapEntry_t *e = &map->m_Entries[ i ]; + IInterpolatedVar *watcher = e->watcher; + void *data = e->data; + int type = e->type; + + watcher->Setup( data, type ); + watcher->SetInterpolationAmount( GetInterpolationAmount( watcher->GetType() ) ); + } +} + +void C_BaseEntity::Interp_RestoreToLastNetworked( VarMapping_t *map ) +{ + PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, "restoretolastnetworked" ); + + Vector oldOrigin = GetLocalOrigin(); + QAngle oldAngles = GetLocalAngles(); + + int c = map->m_Entries.Count(); + for ( int i = 0; i < c; i++ ) + { + VarMapEntry_t *e = &map->m_Entries[ i ]; + IInterpolatedVar *watcher = e->watcher; + watcher->RestoreToLastNetworked(); + } + + BaseInterpolatePart2( oldOrigin, oldAngles, 0 ); +} + +void C_BaseEntity::Interp_UpdateInterpolationAmounts( VarMapping_t *map ) +{ + if( !map ) + return; + + int c = map->m_Entries.Count(); + for ( int i = 0; i < c; i++ ) + { + VarMapEntry_t *e = &map->m_Entries[ i ]; + IInterpolatedVar *watcher = e->watcher; + watcher->SetInterpolationAmount( GetInterpolationAmount( watcher->GetType() ) ); + } +} + +void C_BaseEntity::Interp_HierarchyUpdateInterpolationAmounts() +{ + Interp_UpdateInterpolationAmounts( GetVarMapping() ); + + for ( C_BaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() ) + { + pChild->Interp_HierarchyUpdateInterpolationAmounts(); + } +} + +inline int C_BaseEntity::Interp_Interpolate( VarMapping_t *map, float currentTime ) +{ + int bNoMoreChanges = 1; + if ( currentTime < map->m_lastInterpolationTime ) + { + for ( int i = 0; i < map->m_nInterpolatedEntries; i++ ) + { + VarMapEntry_t *e = &map->m_Entries[ i ]; + + e->m_bNeedsToInterpolate = true; + } + } + map->m_lastInterpolationTime = currentTime; + + for ( int i = 0; i < map->m_nInterpolatedEntries; i++ ) + { + VarMapEntry_t *e = &map->m_Entries[ i ]; + + if ( !e->m_bNeedsToInterpolate ) + continue; + + IInterpolatedVar *watcher = e->watcher; + Assert( !( watcher->GetType() & EXCLUDE_AUTO_INTERPOLATE ) ); + + + if ( watcher->Interpolate( currentTime ) ) + e->m_bNeedsToInterpolate = false; + else + bNoMoreChanges = 0; + } + + return bNoMoreChanges; +} + +//----------------------------------------------------------------------------- +// Functions. +//----------------------------------------------------------------------------- +C_BaseEntity::C_BaseEntity() : + m_iv_vecOrigin( "C_BaseEntity::m_iv_vecOrigin" ), + m_iv_angRotation( "C_BaseEntity::m_iv_angRotation" ) +{ + AddVar( &m_vecOrigin, &m_iv_vecOrigin, LATCH_SIMULATION_VAR ); + AddVar( &m_angRotation, &m_iv_angRotation, LATCH_SIMULATION_VAR ); + + m_DataChangeEventRef = -1; + m_EntClientFlags = 0; + + m_iParentAttachment = 0; + m_nRenderFXBlend = 255; + + SetPredictionEligible( false ); + m_bPredictable = false; + + m_bSimulatedEveryTick = false; + m_bAnimatedEveryTick = false; + m_pPhysicsObject = NULL; + +#ifdef _DEBUG + m_vecAbsOrigin = vec3_origin; + m_angAbsRotation = vec3_angle; + m_vecNetworkOrigin.Init(); + m_angNetworkAngles.Init(); + m_vecAbsOrigin.Init(); +// m_vecAbsAngVelocity.Init(); + m_vecVelocity.Init(); + m_vecAbsVelocity.Init(); + m_vecViewOffset.Init(); + m_vecBaseVelocity.Init(); + + m_iCurrentThinkContext = NO_THINK_CONTEXT; + +#endif + + m_nSimulationTick = -1; + + // Assume drawing everything + m_bReadyToDraw = true; + m_flProxyRandomValue = 0.0f; + + m_fBBoxVisFlags = 0; +#if !defined( NO_ENTITY_PREDICTION ) + m_pPredictionContext = NULL; +#endif + Clear(); + + m_InterpolationListEntry = 0xFFFF; + m_TeleportListEntry = 0xFFFF; + +#ifndef NO_TOOLFRAMEWORK + m_bEnabledInToolView = true; + m_bToolRecording = false; + m_ToolHandle = 0; + m_nLastRecordedFrame = -1; + m_bRecordInTools = true; +#endif + + ParticleProp()->Init( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +C_BaseEntity::~C_BaseEntity() +{ + Term(); + ClearDataChangedEvent( m_DataChangeEventRef ); +#if !defined( NO_ENTITY_PREDICTION ) + delete m_pPredictionContext; +#endif + RemoveFromInterpolationList(); + RemoveFromTeleportList(); +} + +void C_BaseEntity::Clear( void ) +{ + m_bDormant = true; + + m_nCreationTick = -1; + m_RefEHandle.Term(); + m_ModelInstance = MODEL_INSTANCE_INVALID; + m_ShadowHandle = CLIENTSHADOW_INVALID_HANDLE; + m_hRender = INVALID_CLIENT_RENDER_HANDLE; + m_hThink = INVALID_THINK_HANDLE; + m_AimEntsListHandle = INVALID_AIMENTS_LIST_HANDLE; + + index = -1; + m_Collision.Init( this ); + SetLocalOrigin( vec3_origin ); + SetLocalAngles( vec3_angle ); + model = NULL; + m_vecAbsOrigin.Init(); + m_angAbsRotation.Init(); + m_vecVelocity.Init(); + ClearFlags(); + m_vecViewOffset.Init(); + m_vecBaseVelocity.Init(); + m_nModelIndex = 0; + m_flAnimTime = 0; + m_flSimulationTime = 0; + SetSolid( SOLID_NONE ); + SetSolidFlags( 0 ); + SetMoveCollide( MOVECOLLIDE_DEFAULT ); + SetMoveType( MOVETYPE_NONE ); + + ClearEffects(); + m_iEFlags = 0; + m_nRenderMode = 0; + m_nOldRenderMode = 0; + SetRenderColor( 255, 255, 255, 255 ); + m_nRenderFX = 0; + m_flFriction = 0.0f; + m_flGravity = 0.0f; + SetCheckUntouch( false ); + m_ShadowDirUseOtherEntity = NULL; + + m_nLastThinkTick = gpGlobals->tickcount; + + // Remove prediction context if it exists +#if !defined( NO_ENTITY_PREDICTION ) + delete m_pPredictionContext; + m_pPredictionContext = NULL; +#endif + // Do not enable this on all entities. It forces bone setup for entities that + // don't need it. + //AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); + + UpdateVisibility(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::Spawn( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::Activate() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::SpawnClientEntity( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Attach to entity +// Input : *pEnt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::Init( int entnum, int iSerialNum ) +{ + Assert( entnum >= 0 && entnum < NUM_ENT_ENTRIES ); + + index = entnum; + + cl_entitylist->AddNetworkableEntity( GetIClientUnknown(), entnum, iSerialNum ); + + CollisionProp()->CreatePartitionHandle(); + + Interp_SetupMappings( GetVarMapping() ); + + m_nCreationTick = gpGlobals->tickcount; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseEntity::InitializeAsClientEntity( const char *pszModelName, RenderGroup_t renderGroup ) +{ + int nModelIndex; + + if ( pszModelName != NULL ) + { + nModelIndex = modelinfo->GetModelIndex( pszModelName ); + + if ( nModelIndex == -1 ) + { + // Model could not be found + Assert( !"Model could not be found, index is -1" ); + return false; + } + } + else + { + nModelIndex = -1; + } + + Interp_SetupMappings( GetVarMapping() ); + + return InitializeAsClientEntityByIndex( nModelIndex, renderGroup ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseEntity::InitializeAsClientEntityByIndex( int iIndex, RenderGroup_t renderGroup ) +{ + // Setup model data. + SetModelByIndex( iIndex ); + + // Add the client entity to the master entity list. + cl_entitylist->AddNonNetworkableEntity( GetIClientUnknown() ); + Assert( GetClientHandle() != ClientEntityList().InvalidHandle() ); + + // Add the client entity to the renderable "leaf system." (Renderable) + AddToLeafSystem( renderGroup ); + + // Add the client entity to the spatial partition. (Collidable) + CollisionProp()->CreatePartitionHandle(); + + index = -1; + + SpawnClientEntity(); + + return true; +} + + +void C_BaseEntity::Term() +{ + C_BaseEntity::PhysicsRemoveTouchedList( this ); + C_BaseEntity::PhysicsRemoveGroundList( this ); + DestroyAllDataObjects(); + +#if !defined( NO_ENTITY_PREDICTION ) + // Remove from the predictables list + if ( GetPredictable() || IsClientCreated() ) + { + g_Predictables.RemoveFromPredictablesList( GetClientHandle() ); + } + + // If it's play simulated, remove from simulation list if the player still exists... + if ( IsPlayerSimulated() && C_BasePlayer::GetLocalPlayer() ) + { + C_BasePlayer::GetLocalPlayer()->RemoveFromPlayerSimulationList( this ); + } +#endif + + if ( GetClientHandle() != INVALID_CLIENTENTITY_HANDLE ) + { + if ( GetThinkHandle() != INVALID_THINK_HANDLE ) + { + ClientThinkList()->RemoveThinkable( GetClientHandle() ); + } + + // Remove from the client entity list. + ClientEntityList().RemoveEntity( GetClientHandle() ); + + m_RefEHandle = INVALID_CLIENTENTITY_HANDLE; + } + + // Are we in the partition? + CollisionProp()->DestroyPartitionHandle(); + + // If Client side only entity index will be -1 + if ( index != -1 ) + { + beams->KillDeadBeams( this ); + } + + // Clean up the model instance + DestroyModelInstance(); + + // Clean up drawing + RemoveFromLeafSystem(); + + RemoveFromAimEntsList(); +} + + +void C_BaseEntity::SetRefEHandle( const CBaseHandle &handle ) +{ + m_RefEHandle = handle; +} + + +const CBaseHandle& C_BaseEntity::GetRefEHandle() const +{ + return m_RefEHandle; +} + +//----------------------------------------------------------------------------- +// Purpose: Free beams and destroy object +//----------------------------------------------------------------------------- +void C_BaseEntity::Release() +{ + { + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, true ); + UnlinkFromHierarchy(); + } + + // Note that this must be called from here, not the destructor, because otherwise the + // vtable is hosed and the derived classes function is not going to get called!!! + if ( IsIntermediateDataAllocated() ) + { + DestroyIntermediateData(); + } + + UpdateOnRemove(); + + delete this; +} + + +//----------------------------------------------------------------------------- +// Only meant to be called from subclasses. +// Returns true if instance valid, false otherwise +//----------------------------------------------------------------------------- +void C_BaseEntity::CreateModelInstance() +{ + if ( m_ModelInstance == MODEL_INSTANCE_INVALID ) + { + m_ModelInstance = modelrender->CreateInstance( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::DestroyModelInstance() +{ + if (m_ModelInstance != MODEL_INSTANCE_INVALID) + { + modelrender->DestroyInstance( m_ModelInstance ); + m_ModelInstance = MODEL_INSTANCE_INVALID; + } +} + +void C_BaseEntity::SetRemovalFlag( bool bRemove ) +{ + if (bRemove) + m_iEFlags |= EFL_KILLME; + else + m_iEFlags &= ~EFL_KILLME; +} + + +//----------------------------------------------------------------------------- +// VPhysics objects.. +//----------------------------------------------------------------------------- +int C_BaseEntity::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ) +{ + IPhysicsObject *pPhys = VPhysicsGetObject(); + if ( pPhys ) + { + // multi-object entities must implement this function + Assert( !(pPhys->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) ); + if ( listMax > 0 ) + { + pList[0] = pPhys; + return 1; + } + } + return 0; +} + +bool C_BaseEntity::VPhysicsIsFlesh( void ) +{ + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + int material = pList[i]->GetMaterialIndex(); + const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material ); + // Is flesh ?, don't allow pickup + if ( pSurfaceData->game.material == CHAR_TEX_ANTLION || pSurfaceData->game.material == CHAR_TEX_FLESH || pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH || pSurfaceData->game.material == CHAR_TEX_ALIENFLESH ) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Returns the health fraction +//----------------------------------------------------------------------------- +float C_BaseEntity::HealthFraction() const +{ + if (GetMaxHealth() == 0) + return 1.0f; + + float flFraction = (float)GetHealth() / (float)GetMaxHealth(); + flFraction = clamp( flFraction, 0.0f, 1.0f ); + return flFraction; +} + + +//----------------------------------------------------------------------------- +// Purpose: Retrieves the coordinate frame for this entity. +// Input : forward - Receives the entity's forward vector. +// right - Receives the entity's right vector. +// up - Receives the entity's up vector. +//----------------------------------------------------------------------------- +void C_BaseEntity::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const +{ + // This call is necessary to cause m_rgflCoordinateFrame to be recomputed + const matrix3x4_t &entityToWorld = EntityToWorldTransform(); + + if (pForward != NULL) + { + MatrixGetColumn( entityToWorld, 0, *pForward ); + } + + if (pRight != NULL) + { + MatrixGetColumn( entityToWorld, 1, *pRight ); + *pRight *= -1.0f; + } + + if (pUp != NULL) + { + MatrixGetColumn( entityToWorld, 2, *pUp ); + } +} + +void C_BaseEntity::UpdateVisibility() +{ + if ( ShouldDraw() && !IsDormant() && ( !ToolsEnabled() || IsEnabledInToolView() ) ) + { + // add/update leafsystem + AddToLeafSystem(); + } + else + { + // remove from leaf system + RemoveFromLeafSystem(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether object should render. +//----------------------------------------------------------------------------- +bool C_BaseEntity::ShouldDraw() +{ +// Only test this in tf2 +#if defined( INVASION_CLIENT_DLL ) + // Let the client mode (like commander mode) reject drawing entities. + if (g_pClientMode && !g_pClientMode->ShouldDrawEntity(this) ) + return false; +#endif + + // Some rendermodes prevent rendering + if ( m_nRenderMode == kRenderNone ) + return false; + + return (model != 0) && !IsEffectActive(EF_NODRAW) && (index != 0); +} + +bool C_BaseEntity::TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace ) +{ + return false; +} + +bool C_BaseEntity::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Used when the collision prop is told to ask game code for the world-space surrounding box +//----------------------------------------------------------------------------- +void C_BaseEntity::ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) +{ + // This should only be called if you're using USE_GAME_CODE on the server + // and you forgot to implement the client-side version of this method. + Assert(0); +} + + +//----------------------------------------------------------------------------- +// Purpose: Derived classes will have to write their own message cracking routines!!! +// Input : length - +// *data - +//----------------------------------------------------------------------------- +void C_BaseEntity::ReceiveMessage( int classID, bf_read &msg ) +{ + // BaseEntity doesn't have a base class we could relay this message to + Assert( classID == GetClientClass()->m_ClassID ); + + int messageType = msg.ReadByte(); + switch( messageType ) + { + case BASEENTITY_MSG_REMOVE_DECALS: RemoveAllDecals(); + break; + } +} + + +void* C_BaseEntity::GetDataTableBasePtr() +{ + return this; +} + + +//----------------------------------------------------------------------------- +// Should this object cast shadows? +//----------------------------------------------------------------------------- +ShadowType_t C_BaseEntity::ShadowCastType() +{ + if (IsEffectActive(EF_NODRAW | EF_NOSHADOW)) + return SHADOWS_NONE; + + int modelType = modelinfo->GetModelType( model ); + return (modelType == mod_studio) ? SHADOWS_RENDER_TO_TEXTURE : SHADOWS_NONE; +} + + +//----------------------------------------------------------------------------- +// Per-entity shadow cast distance + direction +//----------------------------------------------------------------------------- +bool C_BaseEntity::GetShadowCastDistance( float *pDistance, ShadowType_t shadowType ) const +{ + if ( m_flShadowCastDistance != 0.0f ) + { + *pDistance = m_flShadowCastDistance; + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BaseEntity *C_BaseEntity::GetShadowUseOtherEntity( void ) const +{ + return m_ShadowDirUseOtherEntity; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::SetShadowUseOtherEntity( C_BaseEntity *pEntity ) +{ + m_ShadowDirUseOtherEntity = pEntity; +} + +CInterpolatedVar< QAngle >& C_BaseEntity::GetRotationInterpolator() +{ + return m_iv_angRotation; +} + +CInterpolatedVar< Vector >& C_BaseEntity::GetOriginInterpolator() +{ + return m_iv_vecOrigin; +} + +//----------------------------------------------------------------------------- +// Purpose: Return a per-entity shadow cast direction +//----------------------------------------------------------------------------- +bool C_BaseEntity::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const +{ + if ( m_ShadowDirUseOtherEntity ) + return m_ShadowDirUseOtherEntity->GetShadowCastDirection( pDirection, shadowType ); + + return false; +} + + +//----------------------------------------------------------------------------- +// Should this object receive shadows? +//----------------------------------------------------------------------------- +bool C_BaseEntity::ShouldReceiveProjectedTextures( int flags ) +{ + Assert( flags & SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ); + + if ( IsEffectActive( EF_NODRAW ) ) + return false; + + if( flags & SHADOW_FLAGS_FLASHLIGHT ) + { + if ( GetRenderMode() > kRenderNormal && GetRenderColor().a == 0 ) + return false; + + return true; + } + + Assert( flags & SHADOW_FLAGS_SHADOW ); + + if ( IsEffectActive( EF_NORECEIVESHADOW ) ) + return false; + + if (modelinfo->GetModelType( model ) == mod_studio) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Shadow-related methods +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsShadowDirty( ) +{ + return IsEFlagSet( EFL_DIRTY_SHADOWUPDATE ); +} + +void C_BaseEntity::MarkShadowDirty( bool bDirty ) +{ + if ( bDirty ) + { + AddEFlags( EFL_DIRTY_SHADOWUPDATE ); + } + else + { + RemoveEFlags( EFL_DIRTY_SHADOWUPDATE ); + } +} + +IClientRenderable *C_BaseEntity::GetShadowParent() +{ + C_BaseEntity *pParent = GetMoveParent(); + return pParent ? pParent->GetClientRenderable() : NULL; +} + +IClientRenderable *C_BaseEntity::FirstShadowChild() +{ + C_BaseEntity *pChild = FirstMoveChild(); + return pChild ? pChild->GetClientRenderable() : NULL; +} + +IClientRenderable *C_BaseEntity::NextShadowPeer() +{ + C_BaseEntity *pPeer = NextMovePeer(); + return pPeer ? pPeer->GetClientRenderable() : NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns index into entities list for this entity +// Output : Index +//----------------------------------------------------------------------------- +int C_BaseEntity::entindex( void ) const +{ + return index; +} + +int C_BaseEntity::GetSoundSourceIndex() const +{ +#ifdef _DEBUG + if ( index != -1 ) + { + Assert( index == GetRefEHandle().GetEntryIndex() ); + } +#endif + return GetRefEHandle().GetEntryIndex(); +} + +//----------------------------------------------------------------------------- +// Get render origin and angles +//----------------------------------------------------------------------------- +const Vector& C_BaseEntity::GetRenderOrigin( void ) +{ + return GetAbsOrigin(); +} + +const QAngle& C_BaseEntity::GetRenderAngles( void ) +{ + return GetAbsAngles(); +} + +const matrix3x4_t &C_BaseEntity::RenderableToWorldTransform() +{ + return EntityToWorldTransform(); +} + +IPVSNotify* C_BaseEntity::GetPVSNotifyInterface() +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : theMins - +// theMaxs - +//----------------------------------------------------------------------------- +void C_BaseEntity::GetRenderBounds( Vector& theMins, Vector& theMaxs ) +{ + int nModelType = modelinfo->GetModelType( model ); + if (nModelType == mod_studio || nModelType == mod_brush) + { + modelinfo->GetModelRenderBounds( GetModel(), theMins, theMaxs ); + } + else + { + // By default, we'll just snack on the collision bounds, transform + // them into entity-space, and call it a day. + if ( GetRenderAngles() == CollisionProp()->GetCollisionAngles() ) + { + theMins = CollisionProp()->OBBMins(); + theMaxs = CollisionProp()->OBBMaxs(); + } + else + { + Assert( CollisionProp()->GetCollisionAngles() == vec3_angle ); + if ( IsPointSized() ) + { + //theMins = CollisionProp()->GetCollisionOrigin(); + //theMaxs = theMins; + theMins = theMaxs = vec3_origin; + } + else + { + // NOTE: This shouldn't happen! Or at least, I haven't run + // into a valid case where it should yet. +// Assert(0); + IRotateAABB( EntityToWorldTransform(), CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), theMins, theMaxs ); + } + } + } +} + +void C_BaseEntity::GetRenderBoundsWorldspace( Vector& mins, Vector& maxs ) +{ + DefaultRenderBoundsWorldspace( this, mins, maxs ); +} + + +void C_BaseEntity::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType ) +{ + m_EntClientFlags |= ENTCLIENTFLAG_GETTINGSHADOWRENDERBOUNDS; + GetRenderBounds( mins, maxs ); + m_EntClientFlags &= ~ENTCLIENTFLAG_GETTINGSHADOWRENDERBOUNDS; +} + + +//----------------------------------------------------------------------------- +// Purpose: Last received origin +// Output : const float +//----------------------------------------------------------------------------- +const Vector& C_BaseEntity::GetAbsOrigin( void ) const +{ + Assert( s_bAbsQueriesValid ); + const_cast(this)->CalcAbsolutePosition(); + return m_vecAbsOrigin; +} + + +//----------------------------------------------------------------------------- +// Purpose: Last received angles +// Output : const +//----------------------------------------------------------------------------- +const QAngle& C_BaseEntity::GetAbsAngles( void ) const +{ + Assert( s_bAbsQueriesValid ); + const_cast(this)->CalcAbsolutePosition(); + return m_angAbsRotation; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : org - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetNetworkOrigin( const Vector& org ) +{ + m_vecNetworkOrigin = org; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ang - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetNetworkAngles( const QAngle& ang ) +{ + m_angNetworkAngles = ang; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const Vector& +//----------------------------------------------------------------------------- +const Vector& C_BaseEntity::GetNetworkOrigin() const +{ + return m_vecNetworkOrigin; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const QAngle& +//----------------------------------------------------------------------------- +const QAngle& C_BaseEntity::GetNetworkAngles() const +{ + return m_angNetworkAngles; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get current model pointer for this entity +// Output : const struct model_s +//----------------------------------------------------------------------------- +const model_t *C_BaseEntity::GetModel( void ) const +{ + return model; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get model index for this entity +// Output : int - model index +//----------------------------------------------------------------------------- +int C_BaseEntity::GetModelIndex( void ) const +{ + return m_nModelIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetModelIndex( int index ) +{ + m_nModelIndex = index; + const model_t *pModel = modelinfo->GetModel( m_nModelIndex ); + SetModelPointer( pModel ); +} + +void C_BaseEntity::SetModelPointer( const model_t *pModel ) +{ + if ( pModel != model ) + { + DestroyModelInstance(); + model = pModel; + OnNewModel(); + + UpdateVisibility(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : val - +// moveCollide - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetMoveType( MoveType_t val, MoveCollide_t moveCollide /*= MOVECOLLIDE_DEFAULT*/ ) +{ + // Make sure the move type + move collide are compatible... +#ifdef _DEBUG + if ((val != MOVETYPE_FLY) && (val != MOVETYPE_FLYGRAVITY)) + { + Assert( moveCollide == MOVECOLLIDE_DEFAULT ); + } +#endif + + m_MoveType = val; + SetMoveCollide( moveCollide ); +} + +void C_BaseEntity::SetMoveCollide( MoveCollide_t val ) +{ + m_MoveCollide = val; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get rendermode +// Output : int - the render mode +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsTransparent( void ) +{ + bool modelIsTransparent = modelinfo->IsTranslucent(model); + return modelIsTransparent || (m_nRenderMode != kRenderNormal); +} + +bool C_BaseEntity::IsTwoPass( void ) +{ + return modelinfo->IsTranslucentTwoPass( GetModel() ); +} + +bool C_BaseEntity::UsesPowerOfTwoFrameBufferTexture() +{ + return false; +} + +bool C_BaseEntity::UsesFullFrameBufferTexture() +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Get pointer to CMouthInfo data +// Output : CMouthInfo +//----------------------------------------------------------------------------- +CMouthInfo *C_BaseEntity::GetMouth( void ) +{ + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Retrieve sound spatialization info for the specified sound on this entity +// Input : info - +// Output : Return false to indicate sound is not audible +//----------------------------------------------------------------------------- +bool C_BaseEntity::GetSoundSpatialization( SpatializationInfo_t& info ) +{ + // World is always audible + if ( entindex() == 0 ) + { + return true; + } + + // Out of PVS + if ( IsDormant() ) + { + return false; + } + + // pModel might be NULL, but modelinfo can handle that + const model_t *pModel = GetModel(); + + if ( info.pflRadius ) + { + *info.pflRadius = modelinfo->GetModelRadius( pModel ); + } + + if ( info.pOrigin ) + { + *info.pOrigin = GetAbsOrigin(); + + // move origin to middle of brush + if ( modelinfo->GetModelType( pModel ) == mod_brush ) + { + Vector mins, maxs, center; + + modelinfo->GetModelBounds( pModel, mins, maxs ); + VectorAdd( mins, maxs, center ); + VectorScale( center, 0.5f, center ); + + (*info.pOrigin) += center; + } + } + + if ( info.pAngles ) + { + VectorCopy( GetAbsAngles(), *info.pAngles ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Get attachment point by index +// Input : number - which point +// Output : float * - the attachment point +//----------------------------------------------------------------------------- +bool C_BaseEntity::GetAttachment( int number, Vector &origin, QAngle &angles ) +{ + origin = GetAbsOrigin(); + angles = GetAbsAngles(); + return true; +} + +bool C_BaseEntity::GetAttachment( int number, Vector &origin ) +{ + origin = GetAbsOrigin(); + return true; +} + +bool C_BaseEntity::GetAttachment( int number, matrix3x4_t &matrix ) +{ + MatrixCopy( EntityToWorldTransform(), matrix ); + return true; +} + +bool C_BaseEntity::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ) +{ + originVel = GetAbsVelocity(); + angleVel.Init(); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get this entity's rendering clip plane if one is defined +// Output : float * - The clip plane to use, or NULL if no clip plane is defined +//----------------------------------------------------------------------------- +float *C_BaseEntity::GetRenderClipPlane( void ) +{ + if( m_bEnableRenderingClipPlane ) + return m_fRenderingClipPlane; + else + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_BaseEntity::DrawBrushModel( bool bSort, bool bShadowDepth ) +{ + VPROF_BUDGET( "C_BaseEntity::DrawBrushModel", VPROF_BUDGETGROUP_BRUSHMODEL_RENDERING ); + // Identity brushes are drawn in view->DrawWorld as an optimization + Assert ( modelinfo->GetModelType( model ) == mod_brush ); + + if ( bShadowDepth ) + { + render->DrawBrushModelShadowDepth( this, (model_t *)model, GetAbsOrigin(), GetAbsAngles(), bSort ); + } + else + { + render->DrawBrushModel( this, (model_t *)model, GetAbsOrigin(), GetAbsAngles(), bSort ); + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the object +// Input : flags - +//----------------------------------------------------------------------------- +int C_BaseEntity::DrawModel( int flags ) +{ + if ( !m_bReadyToDraw ) + return 0; + + int drawn = 0; + if ( !model ) + { + return drawn; + } + + int modelType = modelinfo->GetModelType( model ); + switch ( modelType ) + { + case mod_brush: + drawn = DrawBrushModel( flags & STUDIO_TRANSPARENCY ? true : false, flags & STUDIO_SHADOWDEPTHTEXTURE ? true : false ); + break; + case mod_studio: + // All studio models must be derived from C_BaseAnimating. Issue warning. + Warning( "ERROR: Can't draw studio model %s because %s is not derived from C_BaseAnimating\n", + modelinfo->GetModelName( model ), GetClientClass()->m_pNetworkName ? GetClientClass()->m_pNetworkName : "unknown" ); + break; + case mod_sprite: + //drawn = DrawSprite(); + Warning( "ERROR: Sprite model's not supported any more except in legacy temp ents\n" ); + break; + default: + break; + } + + // If we're visualizing our bboxes, draw them + DrawBBoxVisualizations(); + + return drawn; +} + +//----------------------------------------------------------------------------- +// Purpose: Setup the bones for drawing +//----------------------------------------------------------------------------- +bool C_BaseEntity::SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Setup vertex weights for drawing +//----------------------------------------------------------------------------- +void C_BaseEntity::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Process any local client-side animation events +//----------------------------------------------------------------------------- +void C_BaseEntity::DoAnimationEvents( ) +{ +} + + +void C_BaseEntity::UpdatePartitionListEntry() +{ + // Don't add the world entity + CollideType_t shouldCollide = GetCollideType(); + + // Choose the list based on what kind of collisions we want + int list = PARTITION_CLIENT_NON_STATIC_EDICTS; + if (shouldCollide == ENTITY_SHOULD_COLLIDE) + list |= PARTITION_CLIENT_SOLID_EDICTS; + else if (shouldCollide == ENTITY_SHOULD_RESPOND) + list |= PARTITION_CLIENT_RESPONSIVE_EDICTS; + + // add the entity to the KD tree so we will collide against it + partition->RemoveAndInsert( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, list, CollisionProp()->GetPartitionHandle() ); +} + + +void C_BaseEntity::NotifyShouldTransmit( ShouldTransmitState_t state ) +{ + // Init should have been called before we get in here. + Assert( CollisionProp()->GetPartitionHandle() != PARTITION_INVALID_HANDLE ); + if ( entindex() < 0 ) + return; + + switch( state ) + { + case SHOULDTRANSMIT_START: + { + // We've just been sent by the server. Become active. + SetDormant( false ); + + UpdatePartitionListEntry(); + +#if !defined( NO_ENTITY_PREDICTION ) + // Note that predictables get a chance to hook up to their server counterparts here + if ( m_PredictableID.IsActive() ) + { + // Find corresponding client side predicted entity and remove it from predictables + m_PredictableID.SetAcknowledged( true ); + + C_BaseEntity *otherEntity = FindPreviouslyCreatedEntity( m_PredictableID ); + if ( otherEntity ) + { + Assert( otherEntity->IsClientCreated() ); + Assert( otherEntity->m_PredictableID.IsActive() ); + Assert( ClientEntityList().IsHandleValid( otherEntity->GetClientHandle() ) ); + + otherEntity->m_PredictableID.SetAcknowledged( true ); + + if ( OnPredictedEntityRemove( false, otherEntity ) ) + { + // Mark it for delete after receive all network data + otherEntity->Release(); + } + } + } +#endif + } + break; + + case SHOULDTRANSMIT_END: + { + // Clear out links if we're out of the picture... + UnlinkFromHierarchy(); + + // We're no longer being sent by the server. Become dormant. + SetDormant( true ); + + // remove the entity from the KD tree so we won't collide against it + partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() ); + + } + break; + + default: + Assert( 0 ); + break; + } +} + +//----------------------------------------------------------------------------- +// Call this in PostDataUpdate if you don't chain it down! +//----------------------------------------------------------------------------- +void C_BaseEntity::MarkMessageReceived() +{ + m_flLastMessageTime = engine->GetLastTimeStamp(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Entity is about to be decoded from the network stream +// Input : bnewentity - is this a new entity this update? +//----------------------------------------------------------------------------- +void C_BaseEntity::PreDataUpdate( DataUpdateType_t updateType ) +{ + // Register for an OnDataChanged call and call OnPreDataChanged(). + if ( AddDataChangeEvent( this, updateType, &m_DataChangeEventRef ) ) + { + OnPreDataChanged( updateType ); + } + + + // Need to spawn on client before receiving original network data + // in case it overrides any values set up in spawn ( e.g., m_iState ) + bool bnewentity = (updateType == DATA_UPDATE_CREATED); + + if ( !bnewentity ) + { + Interp_RestoreToLastNetworked( GetVarMapping() ); + } + + if ( bnewentity && !IsClientCreated() ) + { + m_flSpawnTime = engine->GetLastTimeStamp(); + MDLCACHE_CRITICAL_SECTION(); + Spawn(); + } + +#if 0 // Yahn suggesting commenting this out as a fix to demo recording not working + // If the entity moves itself every FRAME on the server but doesn't update animtime, + // then use the current server time as the time for interpolation. + if ( IsSelfAnimating() ) + { + m_flAnimTime = engine->GetLastTimeStamp(); + } +#endif + + m_vecOldOrigin = GetNetworkOrigin(); + m_vecOldAngRotation = GetNetworkAngles(); + + m_flOldAnimTime = m_flAnimTime; + m_flOldSimulationTime = m_flSimulationTime; + + m_nOldRenderMode = m_nRenderMode; + + if ( m_hRender != INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->EnableAlternateSorting( m_hRender, m_bAlternateSorting ); + } +} + +const Vector& C_BaseEntity::GetOldOrigin() +{ + return m_vecOldOrigin; +} + + +void C_BaseEntity::UnlinkChild( C_BaseEntity *pParent, C_BaseEntity *pChild ) +{ + Assert( pChild ); + Assert( pParent != pChild ); + Assert( pChild->GetMoveParent() == pParent ); + + // Unlink from parent + // NOTE: pParent *may well be NULL*! This occurs + // when a child has unlinked from a parent, and the child + // remains in the PVS but the parent has not + if (pParent && (pParent->m_pMoveChild == pChild)) + { + Assert( !(pChild->m_pMovePrevPeer.IsValid()) ); + pParent->m_pMoveChild = pChild->m_pMovePeer; + } + + // Unlink from siblings... + if (pChild->m_pMovePrevPeer) + { + pChild->m_pMovePrevPeer->m_pMovePeer = pChild->m_pMovePeer; + } + if (pChild->m_pMovePeer) + { + pChild->m_pMovePeer->m_pMovePrevPeer = pChild->m_pMovePrevPeer; + } + + pChild->m_pMovePeer = NULL; + pChild->m_pMovePrevPeer = NULL; + pChild->m_pMoveParent = NULL; + pChild->RemoveFromAimEntsList(); + + Interp_HierarchyUpdateInterpolationAmounts(); +} + +void C_BaseEntity::LinkChild( C_BaseEntity *pParent, C_BaseEntity *pChild ) +{ + Assert( !pChild->m_pMovePeer.IsValid() ); + Assert( !pChild->m_pMovePrevPeer.IsValid() ); + Assert( !pChild->m_pMoveParent.IsValid() ); + Assert( pParent != pChild ); + +#ifdef _DEBUG + // Make sure the child isn't already in this list + C_BaseEntity *pExistingChild; + for ( pExistingChild = pParent->FirstMoveChild(); pExistingChild; pExistingChild = pExistingChild->NextMovePeer() ) + { + Assert( pChild != pExistingChild ); + } +#endif + + pChild->m_pMovePrevPeer = NULL; + pChild->m_pMovePeer = pParent->m_pMoveChild; + if (pChild->m_pMovePeer) + { + pChild->m_pMovePeer->m_pMovePrevPeer = pChild; + } + pParent->m_pMoveChild = pChild; + pChild->m_pMoveParent = pParent; + pChild->AddToAimEntsList(); + + Interp_HierarchyUpdateInterpolationAmounts(); +} + +CUtlVector< C_BaseEntity * > g_AimEntsList; + + +//----------------------------------------------------------------------------- +// Moves all aiments +//----------------------------------------------------------------------------- +void C_BaseEntity::MarkAimEntsDirty() +{ + // FIXME: With the dirty bits hooked into cycle + sequence, it's unclear + // that this is even necessary any more (provided aiments are always accessing + // joints or attachments of the move parent). + // + // NOTE: This is a tricky algorithm. This list does not actually contain + // all aim-ents in its list. It actually contains all hierarchical children, + // of which aim-ents are a part. We can tell if something is an aiment if it has + // the EF_BONEMERGE effect flag set. + // + // We will first iterate over all aiments and clear their DIRTY_ABSTRANSFORM flag, + // which is necessary to cause them to recompute their aim-ent origin + // the next time CalcAbsPosition is called. Because CalcAbsPosition calls MoveToAimEnt + // and MoveToAimEnt calls SetAbsOrigin/SetAbsAngles, that is how CalcAbsPosition + // will cause the aim-ent's (and all its children's) dirty state to be correctly updated. + // + // Then we will iterate over the loop a second time and call CalcAbsPosition on them, + int i; + int c = g_AimEntsList.Count(); + for ( i = 0; i < c; ++i ) + { + C_BaseEntity *pEnt = g_AimEntsList[ i ]; + Assert( pEnt && pEnt->GetMoveParent() ); + if ( pEnt->IsEffectActive(EF_BONEMERGE | EF_PARENT_ANIMATES) ) + { + pEnt->AddEFlags( EFL_DIRTY_ABSTRANSFORM ); + } + } +} + + +void C_BaseEntity::CalcAimEntPositions() +{ + VPROF("CalcAimEntPositions"); + int i; + int c = g_AimEntsList.Count(); + for ( i = 0; i < c; ++i ) + { + C_BaseEntity *pEnt = g_AimEntsList[ i ]; + Assert( pEnt ); + Assert( pEnt->GetMoveParent() ); + if ( pEnt->IsEffectActive(EF_BONEMERGE) ) + { + pEnt->CalcAbsolutePosition( ); + } + } +} + + +void C_BaseEntity::AddToAimEntsList() +{ + // Already in list + if ( m_AimEntsListHandle != INVALID_AIMENTS_LIST_HANDLE ) + return; + + m_AimEntsListHandle = g_AimEntsList.AddToTail( this ); +} + +void C_BaseEntity::RemoveFromAimEntsList() +{ + // Not in list yet + if ( INVALID_AIMENTS_LIST_HANDLE == m_AimEntsListHandle ) + { + return; + } + + unsigned int c = g_AimEntsList.Count(); + + Assert( m_AimEntsListHandle < c ); + + unsigned int last = c - 1; + + if ( last == m_AimEntsListHandle ) + { + // Just wipe the final entry + g_AimEntsList.FastRemove( last ); + } + else + { + C_BaseEntity *lastEntity = g_AimEntsList[ last ]; + // Remove the last entry + g_AimEntsList.FastRemove( last ); + + // And update it's handle to point to this slot. + lastEntity->m_AimEntsListHandle = m_AimEntsListHandle; + g_AimEntsList[ m_AimEntsListHandle ] = lastEntity; + } + + // Invalidate our handle no matter what. + m_AimEntsListHandle = INVALID_AIMENTS_LIST_HANDLE; +} + +//----------------------------------------------------------------------------- +// Update move-parent if needed. For SourceTV. +//----------------------------------------------------------------------------- +void C_BaseEntity::HierarchyUpdateMoveParent() +{ + if ( m_hNetworkMoveParent.ToInt() == m_pMoveParent.ToInt() ) + return; + + HierarchySetParent( m_hNetworkMoveParent ); +} + + +//----------------------------------------------------------------------------- +// Connects us up to hierarchy +//----------------------------------------------------------------------------- +void C_BaseEntity::HierarchySetParent( C_BaseEntity *pNewParent ) +{ + // NOTE: When this is called, we expect to have a valid + // local origin, etc. that we received from network daa + EHANDLE newParentHandle; + newParentHandle.Set( pNewParent ); + if (newParentHandle.ToInt() == m_pMoveParent.ToInt()) + return; + + if (m_pMoveParent.IsValid()) + { + UnlinkChild( m_pMoveParent, this ); + } + if (pNewParent) + { + LinkChild( pNewParent, this ); + } + + InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED ); +} + + +//----------------------------------------------------------------------------- +// Unlinks from hierarchy +//----------------------------------------------------------------------------- +void C_BaseEntity::SetParent( C_BaseEntity *pParentEntity, int iParentAttachment ) +{ + // NOTE: This version is meant to be called *outside* of PostDataUpdate + // as it assumes the moveparent has a valid handle + EHANDLE newParentHandle; + newParentHandle.Set( pParentEntity ); + if (newParentHandle.ToInt() == m_pMoveParent.ToInt()) + return; + + // NOTE: Have to do this before the unlink to ensure local coords are valid + Vector vecAbsOrigin = GetAbsOrigin(); + QAngle angAbsRotation = GetAbsAngles(); + Vector vecAbsVelocity = GetAbsVelocity(); + + // First deal with unlinking + if (m_pMoveParent.IsValid()) + { + UnlinkChild( m_pMoveParent, this ); + } + + if (pParentEntity) + { + LinkChild( pParentEntity, this ); + } + + m_iParentAttachment = iParentAttachment; + + m_vecAbsOrigin.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + m_angAbsRotation.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + m_vecAbsVelocity.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + + SetAbsOrigin(vecAbsOrigin); + SetAbsAngles(angAbsRotation); + SetAbsVelocity(vecAbsVelocity); + +} + + +//----------------------------------------------------------------------------- +// Unlinks from hierarchy +//----------------------------------------------------------------------------- +void C_BaseEntity::UnlinkFromHierarchy() +{ + // Clear out links if we're out of the picture... + if ( m_pMoveParent.IsValid() ) + { + UnlinkChild( m_pMoveParent, this ); + } + + //Adrian: This was causing problems with the local network backdoor with entities coming in and out of the PVS at certain times. + //This would work fine if a full entity update was coming (caused by certain factors like too many entities entering the pvs at once). + //but otherwise it would not detect the change on the client (since the server and client shouldn't be out of sync) and the var would not be updated like it should. + //m_iParentAttachment = 0; + + // unlink also all move children + C_BaseEntity *pChild = FirstMoveChild(); + while( pChild ) + { + if ( pChild->m_pMoveParent != this ) + { + Warning( "C_BaseEntity::UnlinkFromHierarchy(): Entity has a child with the wrong parent!\n" ); + Assert( 0 ); + UnlinkChild( this, pChild ); + pChild->UnlinkFromHierarchy(); + } + else + pChild->UnlinkFromHierarchy(); + pChild = FirstMoveChild(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Make sure that the correct model is referenced for this entity +//----------------------------------------------------------------------------- +void C_BaseEntity::ValidateModelIndex( void ) +{ + SetModelByIndex( m_nModelIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: Entity data has been parsed and unpacked. Now do any necessary decoding, munging +// Input : bnewentity - was this entity new in this update packet? +//----------------------------------------------------------------------------- +void C_BaseEntity::PostDataUpdate( DataUpdateType_t updateType ) +{ + MDLCACHE_CRITICAL_SECTION(); + + PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, "postdataupdate" ); + + // NOTE: This *has* to happen first. Otherwise, Origin + angles may be wrong + if ( m_nRenderFX == kRenderFxRagdoll && updateType == DATA_UPDATE_CREATED ) + { + MoveToLastReceivedPosition( true ); + } + else + { + MoveToLastReceivedPosition( false ); + } + + // If it's the world, force solid flags + if ( index == 0 ) + { + m_nModelIndex = 1; + SetSolid( SOLID_BSP ); + + // FIXME: Should these be assertions? + SetAbsOrigin( vec3_origin ); + SetAbsAngles( vec3_angle ); + } + + if ( m_nOldRenderMode != m_nRenderMode ) + { + SetRenderMode( (RenderMode_t)m_nRenderMode, true ); + } + + bool animTimeChanged = ( m_flAnimTime != m_flOldAnimTime ) ? true : false; + bool originChanged = ( m_vecOldOrigin != GetLocalOrigin() ) ? true : false; + bool anglesChanged = ( m_vecOldAngRotation != GetLocalAngles() ) ? true : false; + bool simTimeChanged = ( m_flSimulationTime != m_flOldSimulationTime ) ? true : false; + + // Detect simulation changes + bool simulationChanged = originChanged || anglesChanged || simTimeChanged; + + bool bPredictable = GetPredictable(); + + // For non-predicted and non-client only ents, we need to latch network values into the interpolation histories + if ( !bPredictable && !IsClientCreated() ) + { + if ( animTimeChanged ) + { + OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR ); + } + + if ( simulationChanged ) + { + OnLatchInterpolatedVariables( LATCH_SIMULATION_VAR ); + } + } + // For predictables, we also need to store off the last networked value + else if ( bPredictable ) + { + // Just store off last networked value for use in prediction + OnStoreLastNetworkedValue(); + } + + // Deal with hierarchy. Have to do it here (instead of in a proxy) + // because this is the only point at which all entities are loaded + // If this condition isn't met, then a child was sent without its parent + Assert( m_hNetworkMoveParent.Get() || !m_hNetworkMoveParent.IsValid() ); + HierarchySetParent(m_hNetworkMoveParent); + + MarkMessageReceived(); + + // Make sure that the correct model is referenced for this entity + ValidateModelIndex(); + + // If this entity was new, then latch in various values no matter what. + if ( updateType == DATA_UPDATE_CREATED ) + { + // Construct a random value for this instance + m_flProxyRandomValue = random->RandomFloat( 0, 1 ); + + ResetLatched(); + + m_nCreationTick = gpGlobals->tickcount; + } + + CheckInitPredictable( "PostDataUpdate" ); + + // It's possible that a new entity will need to be forceably added to the + // player simulation list. If so, do this here +#if !defined( NO_ENTITY_PREDICTION ) + C_BasePlayer *local = C_BasePlayer::GetLocalPlayer(); + if ( IsPlayerSimulated() && + ( NULL != local ) && + ( local == m_hOwnerEntity ) ) + { + // Make sure player is driving simulation (field is only ever sent to local player) + SetPlayerSimulated( local ); + } +#endif + + UpdatePartitionListEntry(); + + // Add the entity to the nointerp list. + if ( !IsClientCreated() ) + { + if ( Teleported() || IsEffectActive(EF_NOINTERP) ) + AddToTeleportList(); + } + + // if we changed parents, recalculate visibility + if ( m_hOldMoveParent != m_hNetworkMoveParent ) + { + UpdateVisibility(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *context - +//----------------------------------------------------------------------------- +void C_BaseEntity::CheckInitPredictable( const char *context ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + // Prediction is disabled + if ( !cl_predict->GetInt() ) + return; + + C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); + + if ( !player ) + return; + + if ( !GetPredictionEligible() ) + { + if ( m_PredictableID.IsActive() && + ( player->index - 1 ) == m_PredictableID.GetPlayer() ) + { + // If it comes through with an ID, it should be eligible + SetPredictionEligible( true ); + } + else + { + return; + } + } + + if ( IsClientCreated() ) + return; + + if ( !ShouldPredict() ) + return; + + if ( IsIntermediateDataAllocated() ) + return; + + // Msg( "Predicting init %s at %s\n", GetClassname(), context ); + + InitPredictable(); +#endif +} + +bool C_BaseEntity::IsSelfAnimating() +{ + return true; +} + + +//----------------------------------------------------------------------------- +// EFlags.. +//----------------------------------------------------------------------------- +int C_BaseEntity::GetEFlags() const +{ + return m_iEFlags; +} + +void C_BaseEntity::SetEFlags( int iEFlags ) +{ + m_iEFlags = iEFlags; +} + + +//----------------------------------------------------------------------------- +// Sets the model... +//----------------------------------------------------------------------------- +void C_BaseEntity::SetModelByIndex( int nModelIndex ) +{ + SetModelIndex( nModelIndex ); +} + + +//----------------------------------------------------------------------------- +// Set model... (NOTE: Should only be used by client-only entities +//----------------------------------------------------------------------------- +bool C_BaseEntity::SetModel( const char *pModelName ) +{ + if ( pModelName ) + { + int nModelIndex = modelinfo->GetModelIndex( pModelName ); + SetModelByIndex( nModelIndex ); + return ( nModelIndex != -1 ); + } + else + { + SetModelByIndex( -1 ); + return false; + } +} + +void C_BaseEntity::OnStoreLastNetworkedValue() +{ + bool bRestore = false; + Vector savePos; + QAngle saveAng; + + // Kind of a hack, but we want to latch the actual networked value for origin/angles, not what's sitting in m_vecOrigin in the + // ragdoll case where we don't copy it over in MoveToLastNetworkOrigin + if ( m_nRenderFX == kRenderFxRagdoll && GetPredictable() ) + { + bRestore = true; + savePos = GetLocalOrigin(); + saveAng = GetLocalAngles(); + + MoveToLastReceivedPosition( true ); + } + + int c = m_VarMap.m_Entries.Count(); + for ( int i = 0; i < c; i++ ) + { + VarMapEntry_t *e = &m_VarMap.m_Entries[ i ]; + IInterpolatedVar *watcher = e->watcher; + + int type = watcher->GetType(); + + if ( type & EXCLUDE_AUTO_LATCH ) + continue; + + watcher->NoteLastNetworkedValue(); + } + + if ( bRestore ) + { + SetLocalOrigin( savePos ); + SetLocalAngles( saveAng ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: The animtime is about to be changed in a network update, store off various fields so that +// we can use them to do blended sequence transitions, etc. +// Input : *pState - the (mostly) previous state data +//----------------------------------------------------------------------------- + +void C_BaseEntity::OnLatchInterpolatedVariables( int flags ) +{ + float changetime = GetLastChangeTime( flags ); + + bool bUpdateLastNetworkedValue = !(flags & INTERPOLATE_OMIT_UPDATE_LAST_NETWORKED) ? true : false; + + PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, bUpdateLastNetworkedValue ? "latch+net" : "latch" ); + + int c = m_VarMap.m_Entries.Count(); + for ( int i = 0; i < c; i++ ) + { + VarMapEntry_t *e = &m_VarMap.m_Entries[ i ]; + IInterpolatedVar *watcher = e->watcher; + + int type = watcher->GetType(); + + if ( !(type & flags) ) + continue; + + if ( type & EXCLUDE_AUTO_LATCH ) + continue; + + if ( watcher->NoteChanged( changetime, bUpdateLastNetworkedValue ) ) + e->m_bNeedsToInterpolate = true; + } + + if ( ShouldInterpolate() ) + { + AddToInterpolationList(); + } +} + +int CBaseEntity::BaseInterpolatePart1( float ¤tTime, Vector &oldOrigin, QAngle &oldAngles, int &bNoMoreChanges ) +{ + // Don't mess with the world!!! + bNoMoreChanges = 1; + + + // These get moved to the parent position automatically + if ( IsFollowingEntity() || !IsInterpolationEnabled() ) + { + // Assume current origin ( no interpolation ) + MoveToLastReceivedPosition(); + return INTERPOLATE_STOP; + } + + + if ( GetPredictable() || IsClientCreated() ) + { + C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); + if ( localplayer && currentTime == gpGlobals->curtime ) + { + currentTime = localplayer->GetFinalPredictedTime(); + currentTime -= TICK_INTERVAL; + currentTime += ( gpGlobals->interpolation_amount * TICK_INTERVAL ); + } + } + + oldOrigin = m_vecOrigin; + oldAngles = m_angRotation; + + bNoMoreChanges = Interp_Interpolate( GetVarMapping(), currentTime ); + if ( cl_interp_all.GetInt() || (m_EntClientFlags & ENTCLIENTFLAG_ALWAYS_INTERPOLATE) ) + bNoMoreChanges = 0; + + return INTERPOLATE_CONTINUE; +} + +#if 0 +static ConVar cl_watchplayer( "cl_watchplayer", "-1", 0 ); +#endif + +void C_BaseEntity::BaseInterpolatePart2( Vector &oldOrigin, QAngle &oldAngles, int nChangeFlags ) +{ + if ( m_vecOrigin != oldOrigin ) + { + nChangeFlags |= POSITION_CHANGED; + } + + if( m_angRotation != oldAngles ) + { + nChangeFlags |= ANGLES_CHANGED; + } + + if ( nChangeFlags != 0 ) + { + InvalidatePhysicsRecursive( nChangeFlags ); + } + +#if 0 + if ( IsPlayer() && + cl_watchplayer.GetInt() == entindex() && + C_BasePlayer::GetLocalPlayer() && + GetTeam() == C_BasePlayer::GetLocalPlayer()->GetTeam() ) + { + // SpewInterpolatedVar( &m_iv_vecOrigin, gpGlobals->curtime, GetInterpolationAmount( LATCH_SIMULATION_VAR ), false ); + Vector vel; + EstimateAbsVelocity( vel ); + float spd = vel.Length(); + + Msg( "estimated %f\n", spd ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Default interpolation for entities +// Output : true means entity should be drawn, false means probably not +//----------------------------------------------------------------------------- +bool C_BaseEntity::Interpolate( float currentTime ) +{ + VPROF( "C_BaseEntity::Interpolate" ); + + Vector oldOrigin; + QAngle oldAngles; + + int bNoMoreChanges; + int retVal = BaseInterpolatePart1( currentTime, oldOrigin, oldAngles, bNoMoreChanges ); + + // If all the Interpolate() calls returned that their values aren't going to + // change anymore, then get us out of the interpolation list. + if ( bNoMoreChanges ) + RemoveFromInterpolationList(); + + if ( retVal == INTERPOLATE_STOP ) + return true; + + int nChangeFlags = 0; + BaseInterpolatePart2( oldOrigin, oldAngles, nChangeFlags ); + + return true; +} + +CStudioHdr *C_BaseEntity::OnNewModel() +{ + return NULL; +} + +void C_BaseEntity::OnNewParticleEffect( const char *pszParticleName, CNewParticleEffect *pNewParticleEffect ) +{ + return; +} + +// Above this velocity and we'll assume a warp/teleport +#define MAX_INTERPOLATE_VELOCITY 4000.0f +#define MAX_INTERPOLATE_VELOCITY_PLAYER 1250.0f + +//----------------------------------------------------------------------------- +// Purpose: Determine whether entity was teleported ( so we can disable interpolation ) +// Input : *ent - +// Output : bool +//----------------------------------------------------------------------------- +bool C_BaseEntity::Teleported( void ) +{ + // Disable interpolation when hierarchy changes + if (m_hOldMoveParent != m_hNetworkMoveParent || m_iOldParentAttachment != m_iParentAttachment) + { + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Is this a submodel of the world ( model name starts with * )? +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsSubModel( void ) +{ + if ( model && + modelinfo->GetModelType( model ) == mod_brush && + modelinfo->GetModelName( model )[0] == '*' ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Create entity lighting effects +//----------------------------------------------------------------------------- +void C_BaseEntity::CreateLightEffects( void ) +{ + dlight_t *dl; + + // Is this for player flashlights only, if so move to linkplayers? + if ( index == render->GetViewEntity() ) + return; + + if (IsEffectActive(EF_BRIGHTLIGHT)) + { + dl = effects->CL_AllocDlight ( index ); + dl->origin = GetAbsOrigin(); + dl->origin[2] += 16; + dl->color.r = dl->color.g = dl->color.b = 250; + dl->radius = random->RandomFloat(400,431); + dl->die = gpGlobals->curtime + 0.001; + } + if (IsEffectActive(EF_DIMLIGHT)) + { + dl = effects->CL_AllocDlight ( index ); + dl->origin = GetAbsOrigin(); + dl->color.r = dl->color.g = dl->color.b = 100; + dl->radius = random->RandomFloat(200,231); + dl->die = gpGlobals->curtime + 0.001; + } +} + +void C_BaseEntity::MoveToLastReceivedPosition( bool force ) +{ + if ( force || ( m_nRenderFX != kRenderFxRagdoll ) ) + { + SetLocalOrigin( GetNetworkOrigin() ); + SetLocalAngles( GetNetworkAngles() ); + } +} + +bool C_BaseEntity::ShouldInterpolate() +{ + if ( render->GetViewEntity() == index ) + return true; + + if ( index == 0 || !GetModel() ) + return false; + + // always interpolate if visible + if ( IsVisible() ) + return true; + + // if any movement child needs interpolation, we have to interpolate too + C_BaseEntity *pChild = FirstMoveChild(); + while( pChild ) + { + if ( pChild->ShouldInterpolate() ) + return true; + + pChild = pChild->NextMovePeer(); + } + + // don't interpolate + return false; +} + + +void C_BaseEntity::ProcessTeleportList() +{ + int iNext; + for ( int iCur=g_TeleportList.Head(); iCur != g_TeleportList.InvalidIndex(); iCur=iNext ) + { + iNext = g_TeleportList.Next( iCur ); + C_BaseEntity *pCur = g_TeleportList[iCur]; + + bool teleport = pCur->Teleported(); + bool ef_nointerp = pCur->IsEffectActive(EF_NOINTERP); + + if ( teleport || ef_nointerp ) + { + // Undo the teleport flag.. + pCur->m_hOldMoveParent = pCur->m_hNetworkMoveParent; + pCur->m_iOldParentAttachment = pCur->m_iParentAttachment; + // Zero out all but last update. + pCur->MoveToLastReceivedPosition( true ); + pCur->ResetLatched(); + } + else + { + // Get it out of the list as soon as we can. + pCur->RemoveFromTeleportList(); + } + } +} + + +void C_BaseEntity::CheckInterpolatedVarParanoidMeasurement() +{ + // What we're doing here is to check all the entities that were not in the interpolation + // list and make sure that there's no entity that should be in the list that isn't. + +#ifdef INTERPOLATEDVAR_PARANOID_MEASUREMENT + int iHighest = ClientEntityList().GetHighestEntityIndex(); + for ( int i=0; i <= iHighest; i++ ) + { + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntity( i ); + if ( !pEnt || pEnt->m_InterpolationListEntry != 0xFFFF || !pEnt->ShouldInterpolate() ) + continue; + + // Player angles always generates this error when the console is up. + if ( pEnt->entindex() == 1 && engine->Con_IsVisible() ) + continue; + + // View models tend to screw up this test unnecesarily because they modify origin, + // angles, and + if ( dynamic_cast( pEnt ) ) + continue; + + g_bRestoreInterpolatedVarValues = true; + g_nInterpolatedVarsChanged = 0; + pEnt->Interpolate( gpGlobals->curtime ); + g_bRestoreInterpolatedVarValues = false; + + if ( g_nInterpolatedVarsChanged > 0 ) + { + static int iWarningCount = 0; + Warning( "(%d): An entity (%d) should have been in g_InterpolationList.\n", iWarningCount++, pEnt->entindex() ); + break; + } + } +#endif +} + + +void C_BaseEntity::ProcessInterpolatedList() +{ + CheckInterpolatedVarParanoidMeasurement(); + + // Interpolate the minimal set of entities that need it. + int iNext; + for ( int iCur=g_InterpolationList.Head(); iCur != g_InterpolationList.InvalidIndex(); iCur=iNext ) + { + iNext = g_InterpolationList.Next( iCur ); + C_BaseEntity *pCur = g_InterpolationList[iCur]; + + pCur->m_bReadyToDraw = pCur->Interpolate( gpGlobals->curtime ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Add entity to visibile entities list +//----------------------------------------------------------------------------- +void C_BaseEntity::AddEntity( void ) +{ + // Don't ever add the world, it's drawn separately + if ( index == 0 ) + return; + + // Create flashlight effects, etc. + CreateLightEffects(); +} + + +//----------------------------------------------------------------------------- +// Returns the aiment render origin + angles +//----------------------------------------------------------------------------- +void C_BaseEntity::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles ) +{ + // Should be overridden for things that attach to attchment points + + // Slam origin to the origin of the entity we are attached to... + *pOrigin = pAttachedTo->GetAbsOrigin(); + *pAngles = pAttachedTo->GetAbsAngles(); +} + + +void C_BaseEntity::StopFollowingEntity( ) +{ + Assert( IsFollowingEntity() ); + + SetParent( NULL ); + RemoveEffects( EF_BONEMERGE ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); +} + +bool C_BaseEntity::IsFollowingEntity() +{ + return IsEffectActive(EF_BONEMERGE) && (GetMoveType() == MOVETYPE_NONE) && GetMoveParent(); +} + +C_BaseEntity *CBaseEntity::GetFollowedEntity() +{ + if (!IsFollowingEntity()) + return NULL; + return GetMoveParent(); +} + + +//----------------------------------------------------------------------------- +// Default implementation for GetTextureAnimationStartTime +//----------------------------------------------------------------------------- +float C_BaseEntity::GetTextureAnimationStartTime() +{ + return m_flSpawnTime; +} + + +//----------------------------------------------------------------------------- +// Default implementation, indicates that a texture animation has wrapped +//----------------------------------------------------------------------------- +void C_BaseEntity::TextureAnimationWrapped() +{ +} + + +void C_BaseEntity::ClientThink() +{ +} + +void C_BaseEntity::Simulate() +{ + AddEntity(); // Legacy support. Once-per-frame stuff should go in Simulate(). +} + +// Defined in engine +static ConVar cl_interpolate( "cl_interpolate", "1.0f", FCVAR_USERINFO | FCVAR_DEVELOPMENTONLY ); + +// (static function) +void C_BaseEntity::InterpolateServerEntities() +{ + VPROF_BUDGET( "C_BaseEntity::InterpolateServerEntities", VPROF_BUDGETGROUP_INTERPOLATION ); + + s_bInterpolate = cl_interpolate.GetBool(); + + // Don't interpolate during timedemo playback + if ( engine->IsPlayingTimeDemo() ) + { + s_bInterpolate = false; + } + + // Don't interpolate, either, if we are timing out + INetChannelInfo *nci = engine->GetNetChannelInfo(); + if ( nci && nci->GetTimeSinceLastReceived() > 0.5f ) + { + s_bInterpolate = false; + } + + if ( IsSimulatingOnAlternateTicks() != g_bWasSkipping || + IsEngineThreaded() != g_bWasThreaded || + cl_interp_threadmodeticks.GetInt() != g_nThreadModeTicks ) + { + g_bWasSkipping = IsSimulatingOnAlternateTicks(); + g_bWasThreaded = IsEngineThreaded(); + g_nThreadModeTicks = cl_interp_threadmodeticks.GetInt(); + + C_BaseEntityIterator iterator; + C_BaseEntity *pEnt; + while ( (pEnt = iterator.Next()) != NULL ) + { + pEnt->Interp_UpdateInterpolationAmounts( pEnt->GetVarMapping() ); + } + } + + // Enable extrapolation? + CInterpolationContext context; + context.SetLastTimeStamp( engine->GetLastTimeStamp() ); + if ( cl_extrapolate.GetBool() && !engine->IsPaused() ) + { + context.EnableExtrapolation( true ); + } + + // Smoothly interpolate position for server entities. + ProcessTeleportList(); + ProcessInterpolatedList(); +} + + +// (static function) +void C_BaseEntity::AddVisibleEntities() +{ +#if !defined( NO_ENTITY_PREDICTION ) + VPROF_BUDGET( "C_BaseEntity::AddVisibleEntities", VPROF_BUDGETGROUP_WORLD_RENDERING ); + + // Let non-dormant client created predictables get added, too + int c = predictables->GetPredictableCount(); + for ( int i = 0 ; i < c ; i++ ) + { + C_BaseEntity *pEnt = predictables->GetPredictable( i ); + if ( !pEnt ) + continue; + + if ( !pEnt->IsClientCreated() ) + continue; + + // Only draw until it's ack'd since that means a real entity has arrived + if ( pEnt->m_PredictableID.GetAcknowledged() ) + continue; + + // Don't draw if dormant + if ( pEnt->IsDormantPredictable() ) + continue; + + pEnt->UpdateVisibility(); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +//----------------------------------------------------------------------------- +void C_BaseEntity::OnPreDataChanged( DataUpdateType_t type ) +{ + m_hOldMoveParent = m_hNetworkMoveParent; + m_iOldParentAttachment = m_iParentAttachment; +} + +void C_BaseEntity::OnDataChanged( DataUpdateType_t type ) +{ + // See if it needs to allocate prediction stuff + CheckInitPredictable( "OnDataChanged" ); + + // Set up shadows; do it here so that objects can change shadowcasting state + CreateShadow(); + + if ( type == DATA_UPDATE_CREATED ) + { + UpdateVisibility(); + } +} + +ClientThinkHandle_t C_BaseEntity::GetThinkHandle() +{ + return m_hThink; +} + + +void C_BaseEntity::SetThinkHandle( ClientThinkHandle_t hThink ) +{ + m_hThink = hThink; +} + + +//----------------------------------------------------------------------------- +// Purpose: This routine modulates renderamt according to m_nRenderFX's value +// This is a client side effect and will not be in-sync on machines across a +// network game. +// Input : origin - +// alpha - +// Output : int +//----------------------------------------------------------------------------- +void C_BaseEntity::ComputeFxBlend( void ) +{ + // Don't recompute if we've already computed this frame + if ( m_nFXComputeFrame == gpGlobals->framecount ) + return; + + MDLCACHE_CRITICAL_SECTION(); + int blend=0; + float offset; + + offset = ((int)index) * 363.0;// Use ent index to de-sync these fx + + switch( m_nRenderFX ) + { + case kRenderFxPulseSlowWide: + blend = m_clrRender->a + 0x40 * sin( gpGlobals->curtime * 2 + offset ); + break; + + case kRenderFxPulseFastWide: + blend = m_clrRender->a + 0x40 * sin( gpGlobals->curtime * 8 + offset ); + break; + + case kRenderFxPulseFastWider: + blend = ( 0xff * fabs(sin( gpGlobals->curtime * 12 + offset ) ) ); + break; + + case kRenderFxPulseSlow: + blend = m_clrRender->a + 0x10 * sin( gpGlobals->curtime * 2 + offset ); + break; + + case kRenderFxPulseFast: + blend = m_clrRender->a + 0x10 * sin( gpGlobals->curtime * 8 + offset ); + break; + + // JAY: HACK for now -- not time based + case kRenderFxFadeSlow: + if ( m_clrRender->a > 0 ) + { + SetRenderColorA( m_clrRender->a - 1 ); + } + else + { + SetRenderColorA( 0 ); + } + blend = m_clrRender->a; + break; + + case kRenderFxFadeFast: + if ( m_clrRender->a > 3 ) + { + SetRenderColorA( m_clrRender->a - 4 ); + } + else + { + SetRenderColorA( 0 ); + } + blend = m_clrRender->a; + break; + + case kRenderFxSolidSlow: + if ( m_clrRender->a < 255 ) + { + SetRenderColorA( m_clrRender->a + 1 ); + } + else + { + SetRenderColorA( 255 ); + } + blend = m_clrRender->a; + break; + + case kRenderFxSolidFast: + if ( m_clrRender->a < 252 ) + { + SetRenderColorA( m_clrRender->a + 4 ); + } + else + { + SetRenderColorA( 255 ); + } + blend = m_clrRender->a; + break; + + case kRenderFxStrobeSlow: + blend = 20 * sin( gpGlobals->curtime * 4 + offset ); + if ( blend < 0 ) + { + blend = 0; + } + else + { + blend = m_clrRender->a; + } + break; + + case kRenderFxStrobeFast: + blend = 20 * sin( gpGlobals->curtime * 16 + offset ); + if ( blend < 0 ) + { + blend = 0; + } + else + { + blend = m_clrRender->a; + } + break; + + case kRenderFxStrobeFaster: + blend = 20 * sin( gpGlobals->curtime * 36 + offset ); + if ( blend < 0 ) + { + blend = 0; + } + else + { + blend = m_clrRender->a; + } + break; + + case kRenderFxFlickerSlow: + blend = 20 * (sin( gpGlobals->curtime * 2 ) + sin( gpGlobals->curtime * 17 + offset )); + if ( blend < 0 ) + { + blend = 0; + } + else + { + blend = m_clrRender->a; + } + break; + + case kRenderFxFlickerFast: + blend = 20 * (sin( gpGlobals->curtime * 16 ) + sin( gpGlobals->curtime * 23 + offset )); + if ( blend < 0 ) + { + blend = 0; + } + else + { + blend = m_clrRender->a; + } + break; + + case kRenderFxHologram: + case kRenderFxDistort: + { + Vector tmp; + float dist; + + VectorCopy( GetAbsOrigin(), tmp ); + VectorSubtract( tmp, CurrentViewOrigin(), tmp ); + dist = DotProduct( tmp, CurrentViewForward() ); + + // Turn off distance fade + if ( m_nRenderFX == kRenderFxDistort ) + { + dist = 1; + } + if ( dist <= 0 ) + { + blend = 0; + } + else + { + SetRenderColorA( 180 ); + if ( dist <= 100 ) + blend = m_clrRender->a; + else + blend = (int) ((1.0 - (dist - 100) * (1.0 / 400.0)) * m_clrRender->a); + blend += random->RandomInt(-32,31); + } + } + break; + + case kRenderFxNone: + case kRenderFxClampMinScale: + default: + if (m_nRenderMode == kRenderNormal) + blend = 255; + else + blend = m_clrRender->a; + break; + + } + + blend = clamp( blend, 0, 255 ); + + // Look for client-side fades + unsigned char nFadeAlpha = GetClientSideFade(); + if ( nFadeAlpha != 255 ) + { + float flBlend = blend / 255.0f; + float flFade = nFadeAlpha / 255.0f; + blend = (int)( flBlend * flFade * 255.0f + 0.5f ); + blend = clamp( blend, 0, 255 ); + } + + m_nRenderFXBlend = blend; + m_nFXComputeFrame = gpGlobals->framecount; + + // Update the render group + if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->SetRenderGroup( GetRenderHandle(), GetRenderGroup() ); + } + + // Tell our shadow + if ( m_ShadowHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + g_pClientShadowMgr->SetFalloffBias( m_ShadowHandle, (255 - m_nRenderFXBlend) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_BaseEntity::GetFxBlend( void ) +{ + Assert( m_nFXComputeFrame == gpGlobals->framecount ); + return m_nRenderFXBlend; +} + +//----------------------------------------------------------------------------- +// Determine the color modulation amount +//----------------------------------------------------------------------------- + +void C_BaseEntity::GetColorModulation( float* color ) +{ + color[0] = m_clrRender->r / 255.0f; + color[1] = m_clrRender->g / 255.0f; + color[2] = m_clrRender->b / 255.0f; +} + + +//----------------------------------------------------------------------------- +// Returns true if we should add this to the collision list +//----------------------------------------------------------------------------- +CollideType_t C_BaseEntity::GetCollideType( void ) +{ + if ( !m_nModelIndex || !model ) + return ENTITY_SHOULD_NOT_COLLIDE; + + if ( !IsSolid( ) ) + return ENTITY_SHOULD_NOT_COLLIDE; + + // If the model is a bsp or studio (i.e. it can collide with the player + if ( ( modelinfo->GetModelType( model ) != mod_brush ) && ( modelinfo->GetModelType( model ) != mod_studio ) ) + return ENTITY_SHOULD_NOT_COLLIDE; + + // Don't get stuck on point sized entities ( world doesn't count ) + if ( m_nModelIndex != 1 ) + { + if ( IsPointSized() ) + return ENTITY_SHOULD_NOT_COLLIDE; + } + + return ENTITY_SHOULD_COLLIDE; +} + + +//----------------------------------------------------------------------------- +// Is this a brush model? +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsBrushModel() const +{ + int modelType = modelinfo->GetModelType( model ); + return (modelType == mod_brush); +} + + +//----------------------------------------------------------------------------- +// This method works when we've got a studio model +//----------------------------------------------------------------------------- +void C_BaseEntity::AddStudioDecal( const Ray_t& ray, int hitbox, int decalIndex, + bool doTrace, trace_t& tr, int maxLODToDecal ) +{ + if (doTrace) + { + enginetrace->ClipRayToEntity( ray, MASK_SHOT, this, &tr ); + + // Trace the ray against the entity + if (tr.fraction == 1.0f) + return; + + // Set the trace index appropriately... + tr.m_pEnt = this; + } + + // Exit out after doing the trace so any other effects that want to happen can happen. + if ( !r_drawmodeldecals.GetBool() ) + return; + + // Found the point, now lets apply the decals + CreateModelInstance(); + + // FIXME: Pass in decal up? + Vector up(0, 0, 1); + + if (doTrace && (GetSolid() == SOLID_VPHYSICS) && !tr.startsolid && !tr.allsolid) + { + // Choose a more accurate normal direction + // Also, since we have more accurate info, we can avoid pokethru + Vector temp; + VectorSubtract( tr.endpos, tr.plane.normal, temp ); + Ray_t betterRay; + betterRay.Init( tr.endpos, temp ); + modelrender->AddDecal( m_ModelInstance, betterRay, up, decalIndex, GetStudioBody(), true, maxLODToDecal ); + } + else + { + modelrender->AddDecal( m_ModelInstance, ray, up, decalIndex, GetStudioBody(), false, maxLODToDecal ); + } +} + + +//----------------------------------------------------------------------------- +// This method works when we've got a brush model +//----------------------------------------------------------------------------- +void C_BaseEntity::AddBrushModelDecal( const Ray_t& ray, const Vector& decalCenter, + int decalIndex, bool doTrace, trace_t& tr ) +{ + if ( doTrace ) + { + enginetrace->ClipRayToEntity( ray, MASK_SHOT, this, &tr ); + if ( tr.fraction == 1.0f ) + return; + } + + effects->DecalShoot( decalIndex, index, + model, GetAbsOrigin(), GetAbsAngles(), decalCenter, 0, 0 ); +} + + +//----------------------------------------------------------------------------- +// A method to apply a decal to an entity +//----------------------------------------------------------------------------- +void C_BaseEntity::AddDecal( const Vector& rayStart, const Vector& rayEnd, + const Vector& decalCenter, int hitbox, int decalIndex, bool doTrace, trace_t& tr, int maxLODToDecal ) +{ + Ray_t ray; + ray.Init( rayStart, rayEnd ); + + // FIXME: Better bloat? + // Bloat a little bit so we get the intersection + ray.m_Delta *= 1.1f; + + int modelType = modelinfo->GetModelType( model ); + switch ( modelType ) + { + case mod_studio: + AddStudioDecal( ray, hitbox, decalIndex, doTrace, tr, maxLODToDecal ); + break; + + case mod_brush: + AddBrushModelDecal( ray, decalCenter, decalIndex, doTrace, tr ); + break; + + default: + // By default, no collision + tr.fraction = 1.0f; + break; + } +} + +//----------------------------------------------------------------------------- +// A method to remove all decals from an entity +//----------------------------------------------------------------------------- +void C_BaseEntity::RemoveAllDecals( void ) +{ + // For now, we only handle removing decals from studiomodels + if ( modelinfo->GetModelType( model ) == mod_studio ) + { + CreateModelInstance(); + modelrender->RemoveAllDecals( m_ModelInstance ); + } +} + +bool C_BaseEntity::SnatchModelInstance( C_BaseEntity *pToEntity ) +{ + if ( !modelrender->ChangeInstance( GetModelInstance(), pToEntity ) ) + return false; // engine could move modle handle + + // remove old handle from toentity if any + if ( pToEntity->GetModelInstance() != MODEL_INSTANCE_INVALID ) + pToEntity->DestroyModelInstance(); + + // move the handle to other entity + pToEntity->SetModelInstance( GetModelInstance() ); + + // delete own reference + SetModelInstance( MODEL_INSTANCE_INVALID ); + + return true; +} + +#include "tier0/memdbgoff.h" + +//----------------------------------------------------------------------------- +// C_BaseEntity new/delete +// All fields in the object are all initialized to 0. +//----------------------------------------------------------------------------- +void *C_BaseEntity::operator new( size_t stAllocateBlock ) +{ + Assert( stAllocateBlock != 0 ); + MEM_ALLOC_CREDIT(); + void *pMem = g_pMemAlloc->Alloc( stAllocateBlock ); + memset( pMem, 0, stAllocateBlock ); + return pMem; +} + +void *C_BaseEntity::operator new[]( size_t stAllocateBlock ) +{ + Assert( stAllocateBlock != 0 ); + MEM_ALLOC_CREDIT(); + void *pMem = g_pMemAlloc->Alloc( stAllocateBlock ); + memset( pMem, 0, stAllocateBlock ); + return pMem; +} + +void *C_BaseEntity::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ) +{ + Assert( stAllocateBlock != 0 ); + void *pMem = g_pMemAlloc->Alloc( stAllocateBlock, pFileName, nLine ); + memset( pMem, 0, stAllocateBlock ); + return pMem; +} + +void *C_BaseEntity::operator new[]( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ) +{ + Assert( stAllocateBlock != 0 ); + void *pMem = g_pMemAlloc->Alloc( stAllocateBlock, pFileName, nLine ); + memset( pMem, 0, stAllocateBlock ); + return pMem; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pMem - +//----------------------------------------------------------------------------- +void C_BaseEntity::operator delete( void *pMem ) +{ +#ifdef _DEBUG + // set the memory to a known value + int size = g_pMemAlloc->GetSize( pMem ); + Q_memset( pMem, 0xdd, size ); +#endif + + // get the engine to free the memory + g_pMemAlloc->Free( pMem ); +} + +#include "tier0/memdbgon.h" + +//======================================================================================== +// TEAM HANDLING +//======================================================================================== +C_Team *C_BaseEntity::GetTeam( void ) +{ + return GetGlobalTeam( m_iTeamNum ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int C_BaseEntity::GetTeamNumber( void ) const +{ + return m_iTeamNum; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_BaseEntity::GetRenderTeamNumber( void ) +{ + return GetTeamNumber(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if these entities are both in at least one team together +//----------------------------------------------------------------------------- +bool C_BaseEntity::InSameTeam( C_BaseEntity *pEntity ) +{ + if ( !pEntity ) + return false; + + return ( pEntity->GetTeam() == GetTeam() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the entity's on the same team as the local player +//----------------------------------------------------------------------------- +bool C_BaseEntity::InLocalTeam( void ) +{ + return ( GetTeam() == GetLocalTeam() ); +} + + +void C_BaseEntity::SetNextClientThink( float nextThinkTime ) +{ + Assert( GetClientHandle() != INVALID_CLIENTENTITY_HANDLE ); + ClientThinkList()->SetNextClientThink( GetClientHandle(), nextThinkTime ); +} + +void C_BaseEntity::AddToLeafSystem() +{ + AddToLeafSystem( GetRenderGroup() ); +} + +void C_BaseEntity::AddToLeafSystem( RenderGroup_t group ) +{ + if( m_hRender == INVALID_CLIENT_RENDER_HANDLE ) + { + // create new renderer handle + ClientLeafSystem()->AddRenderable( this, group ); + ClientLeafSystem()->EnableAlternateSorting( m_hRender, m_bAlternateSorting ); + } + else + { + // handle already exists, just update group & origin + ClientLeafSystem()->SetRenderGroup( m_hRender, group ); + ClientLeafSystem()->RenderableChanged( m_hRender ); + } +} + + +//----------------------------------------------------------------------------- +// Creates the shadow (if it doesn't already exist) based on shadow cast type +//----------------------------------------------------------------------------- +void C_BaseEntity::CreateShadow() +{ + ShadowType_t shadowType = ShadowCastType(); + if (shadowType == SHADOWS_NONE) + { + DestroyShadow(); + } + else + { + if (m_ShadowHandle == CLIENTSHADOW_INVALID_HANDLE) + { + int flags = SHADOW_FLAGS_SHADOW; + if (shadowType != SHADOWS_SIMPLE) + flags |= SHADOW_FLAGS_USE_RENDER_TO_TEXTURE; + if (shadowType == SHADOWS_RENDER_TO_TEXTURE_DYNAMIC) + flags |= SHADOW_FLAGS_ANIMATING_SOURCE; + m_ShadowHandle = g_pClientShadowMgr->CreateShadow(GetClientHandle(), flags); + } + } +} + +//----------------------------------------------------------------------------- +// Removes the shadow +//----------------------------------------------------------------------------- +void C_BaseEntity::DestroyShadow() +{ + // NOTE: This will actually cause the shadow type to be recomputed + // if the entity doesn't immediately go away + if (m_ShadowHandle != CLIENTSHADOW_INVALID_HANDLE) + { + g_pClientShadowMgr->DestroyShadow(m_ShadowHandle); + m_ShadowHandle = CLIENTSHADOW_INVALID_HANDLE; + } +} + + +//----------------------------------------------------------------------------- +// Removes the entity from the leaf system +//----------------------------------------------------------------------------- +void C_BaseEntity::RemoveFromLeafSystem() +{ + // Detach from the leaf lists. + if( m_hRender != INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->RemoveRenderable( m_hRender ); + m_hRender = INVALID_CLIENT_RENDER_HANDLE; + } + DestroyShadow(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Flags this entity as being inside or outside of this client's PVS +// on the server. +// NOTE: this is meaningless for client-side only entities. +// Input : inside_pvs - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetDormant( bool bDormant ) +{ + Assert( IsServerEntity() ); + m_bDormant = bDormant; + + // Kill drawing if we became dormant. + UpdateVisibility(); + + ParticleProp()->OwnerSetDormantTo( bDormant ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether this entity is dormant. Client/server entities become +// dormant when they leave the PVS on the server. Client side entities +// can decide for themselves whether to become dormant. +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsDormant( void ) +{ + if ( IsServerEntity() ) + { + return m_bDormant; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Tells the entity that it's about to be destroyed due to the client receiving +// an uncompressed update that's caused it to destroy all entities & recreate them. +//----------------------------------------------------------------------------- +void C_BaseEntity::SetDestroyedOnRecreateEntities( void ) +{ + // Robin: We need to destroy all our particle systems immediately, because + // we're about to be recreated, and their owner EHANDLEs will match up to + // the new entity, but it won't know anything about them. + ParticleProp()->StopEmissionAndDestroyImmediately(); +} + +//----------------------------------------------------------------------------- +// These methods recompute local versions as well as set abs versions +//----------------------------------------------------------------------------- +void C_BaseEntity::SetAbsOrigin( const Vector& absOrigin ) +{ + // This is necessary to get the other fields of m_rgflCoordinateFrame ok + CalcAbsolutePosition(); + + if ( m_vecAbsOrigin == absOrigin ) + return; + + // All children are invalid, but we are not + InvalidatePhysicsRecursive( POSITION_CHANGED ); + RemoveEFlags( EFL_DIRTY_ABSTRANSFORM ); + + m_vecAbsOrigin = absOrigin; + MatrixSetColumn( absOrigin, 3, m_rgflCoordinateFrame ); + + C_BaseEntity *pMoveParent = GetMoveParent(); + + if (!pMoveParent) + { + m_vecOrigin = absOrigin; + return; + } + + // Moveparent case: transform the abs position into local space + VectorITransform( absOrigin, pMoveParent->EntityToWorldTransform(), (Vector&)m_vecOrigin ); +} + +void C_BaseEntity::SetAbsAngles( const QAngle& absAngles ) +{ + // This is necessary to get the other fields of m_rgflCoordinateFrame ok + CalcAbsolutePosition(); + + // FIXME: The normalize caused problems in server code like momentary_rot_button that isn't + // handling things like +/-180 degrees properly. This should be revisited. + //QAngle angleNormalize( AngleNormalize( absAngles.x ), AngleNormalize( absAngles.y ), AngleNormalize( absAngles.z ) ); + + if ( m_angAbsRotation == absAngles ) + return; + + InvalidatePhysicsRecursive( ANGLES_CHANGED ); + RemoveEFlags( EFL_DIRTY_ABSTRANSFORM ); + + m_angAbsRotation = absAngles; + AngleMatrix( absAngles, m_rgflCoordinateFrame ); + MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame ); + + C_BaseEntity *pMoveParent = GetMoveParent(); + + if (!pMoveParent) + { + m_angRotation = absAngles; + return; + } + + // Moveparent case: we're aligned with the move parent + if ( m_angAbsRotation == pMoveParent->GetAbsAngles() ) + { + m_angRotation.Init( ); + } + else + { + // Moveparent case: transform the abs transform into local space + matrix3x4_t worldToParent, localMatrix; + MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent ); + ConcatTransforms( worldToParent, m_rgflCoordinateFrame, localMatrix ); + MatrixAngles( localMatrix, (QAngle &)m_angRotation ); + } +} + +void C_BaseEntity::SetAbsVelocity( const Vector &vecAbsVelocity ) +{ + if ( m_vecAbsVelocity == vecAbsVelocity ) + return; + + // The abs velocity won't be dirty since we're setting it here + InvalidatePhysicsRecursive( VELOCITY_CHANGED ); + m_iEFlags &= ~EFL_DIRTY_ABSVELOCITY; + + m_vecAbsVelocity = vecAbsVelocity; + + C_BaseEntity *pMoveParent = GetMoveParent(); + + if (!pMoveParent) + { + m_vecVelocity = vecAbsVelocity; + return; + } + + // First subtract out the parent's abs velocity to get a relative + // velocity measured in world space + Vector relVelocity; + VectorSubtract( vecAbsVelocity, pMoveParent->GetAbsVelocity(), relVelocity ); + + // Transform velocity into parent space + VectorIRotate( relVelocity, pMoveParent->EntityToWorldTransform(), m_vecVelocity ); +} + +/* +void C_BaseEntity::SetAbsAngularVelocity( const QAngle &vecAbsAngVelocity ) +{ + // The abs velocity won't be dirty since we're setting it here + InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY ); + m_iEFlags &= ~EFL_DIRTY_ABSANGVELOCITY; + + m_vecAbsAngVelocity = vecAbsAngVelocity; + + C_BaseEntity *pMoveParent = GetMoveParent(); + if (!pMoveParent) + { + m_vecAngVelocity = vecAbsAngVelocity; + return; + } + + // First subtract out the parent's abs velocity to get a relative + // angular velocity measured in world space + QAngle relAngVelocity; + relAngVelocity = vecAbsAngVelocity - pMoveParent->GetAbsAngularVelocity(); + + matrix3x4_t entityToWorld; + AngleMatrix( relAngVelocity, entityToWorld ); + + // Moveparent case: transform the abs angular vel into local space + matrix3x4_t worldToParent, localMatrix; + MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent ); + ConcatTransforms( worldToParent, entityToWorld, localMatrix ); + MatrixAngles( localMatrix, m_vecAngVelocity ); +} +*/ + + +// Prevent these for now until hierarchy is properly networked +const Vector& C_BaseEntity::GetLocalOrigin( void ) const +{ + return m_vecOrigin; +} + +vec_t C_BaseEntity::GetLocalOriginDim( int iDim ) const +{ + return m_vecOrigin[iDim]; +} + +// Prevent these for now until hierarchy is properly networked +void C_BaseEntity::SetLocalOrigin( const Vector& origin ) +{ + if (m_vecOrigin != origin) + { + InvalidatePhysicsRecursive( POSITION_CHANGED ); + m_vecOrigin = origin; + } +} + +void C_BaseEntity::SetLocalOriginDim( int iDim, vec_t flValue ) +{ + if (m_vecOrigin[iDim] != flValue) + { + InvalidatePhysicsRecursive( POSITION_CHANGED ); + m_vecOrigin[iDim] = flValue; + } +} + + +// Prevent these for now until hierarchy is properly networked +const QAngle& C_BaseEntity::GetLocalAngles( void ) const +{ + return m_angRotation; +} + +vec_t C_BaseEntity::GetLocalAnglesDim( int iDim ) const +{ + return m_angRotation[iDim]; +} + +// Prevent these for now until hierarchy is properly networked +void C_BaseEntity::SetLocalAngles( const QAngle& angles ) +{ + // NOTE: The angle normalize is a little expensive, but we can save + // a bunch of time in interpolation if we don't have to invalidate everything + // and sometimes it's off by a normalization amount + + // FIXME: The normalize caused problems in server code like momentary_rot_button that isn't + // handling things like +/-180 degrees properly. This should be revisited. + //QAngle angleNormalize( AngleNormalize( angles.x ), AngleNormalize( angles.y ), AngleNormalize( angles.z ) ); + + if (m_angRotation != angles) + { + // This will cause the velocities of all children to need recomputation + InvalidatePhysicsRecursive( ANGLES_CHANGED ); + m_angRotation = angles; + } +} + +void C_BaseEntity::SetLocalAnglesDim( int iDim, vec_t flValue ) +{ + flValue = AngleNormalize( flValue ); + if (m_angRotation[iDim] != flValue) + { + // This will cause the velocities of all children to need recomputation + InvalidatePhysicsRecursive( ANGLES_CHANGED ); + m_angRotation[iDim] = flValue; + } +} + +void C_BaseEntity::SetLocalVelocity( const Vector &vecVelocity ) +{ + if (m_vecVelocity != vecVelocity) + { + InvalidatePhysicsRecursive( VELOCITY_CHANGED ); + m_vecVelocity = vecVelocity; + } +} + +void C_BaseEntity::SetLocalAngularVelocity( const QAngle &vecAngVelocity ) +{ + if (m_vecAngVelocity != vecAngVelocity) + { +// InvalidatePhysicsRecursive( ANG_VELOCITY_CHANGED ); + m_vecAngVelocity = vecAngVelocity; + } +} + + +//----------------------------------------------------------------------------- +// Sets the local position from a transform +//----------------------------------------------------------------------------- +void C_BaseEntity::SetLocalTransform( const matrix3x4_t &localTransform ) +{ + Vector vecLocalOrigin; + QAngle vecLocalAngles; + MatrixGetColumn( localTransform, 3, vecLocalOrigin ); + MatrixAngles( localTransform, vecLocalAngles ); + SetLocalOrigin( vecLocalOrigin ); + SetLocalAngles( vecLocalAngles ); +} + + +//----------------------------------------------------------------------------- +// FIXME: REMOVE!!! +//----------------------------------------------------------------------------- +void C_BaseEntity::MoveToAimEnt( ) +{ + Vector vecAimEntOrigin; + QAngle vecAimEntAngles; + GetAimEntOrigin( GetMoveParent(), &vecAimEntOrigin, &vecAimEntAngles ); + SetAbsOrigin( vecAimEntOrigin ); + SetAbsAngles( vecAimEntAngles ); +} + + +void C_BaseEntity::BoneMergeFastCullBloat( Vector &localMins, Vector &localMaxs, const Vector &thisEntityMins, const Vector &thisEntityMaxs ) const +{ + // By default, we bloat the bbox for fastcull ents by the maximum length it could hang out of the parent bbox, + // it one corner were touching the edge of the parent's box, and the whole diagonal stretched out. + float flExpand = (thisEntityMaxs - thisEntityMins).Length(); + + localMins.x -= flExpand; + localMins.y -= flExpand; + localMins.z -= flExpand; + + localMaxs.x += flExpand; + localMaxs.y += flExpand; + localMaxs.z += flExpand; +} + + +matrix3x4_t& C_BaseEntity::GetParentToWorldTransform( matrix3x4_t &tempMatrix ) +{ + CBaseEntity *pMoveParent = GetMoveParent(); + if ( !pMoveParent ) + { + Assert( false ); + SetIdentityMatrix( tempMatrix ); + return tempMatrix; + } + + if ( m_iParentAttachment != 0 ) + { + Vector vOrigin; + QAngle vAngles; + if ( pMoveParent->GetAttachment( m_iParentAttachment, vOrigin, vAngles ) ) + { + AngleMatrix( vAngles, vOrigin, tempMatrix ); + return tempMatrix; + } + } + + // If we fall through to here, then just use the move parent's abs origin and angles. + return pMoveParent->EntityToWorldTransform(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculates the absolute position of an edict in the world +// assumes the parent's absolute origin has already been calculated +//----------------------------------------------------------------------------- +void C_BaseEntity::CalcAbsolutePosition( ) +{ + // There are periods of time where we're gonna have to live with the + // fact that we're in an indeterminant state and abs queries (which + // shouldn't be happening at all; I have assertions for those), will + // just have to accept stale data. + if (!s_bAbsRecomputationEnabled) + return; + + // FIXME: Recompute absbox!!! + if ((m_iEFlags & EFL_DIRTY_ABSTRANSFORM) == 0) + { + // quick check to make sure we really don't need an update + // Assert( m_pMoveParent || m_vecAbsOrigin == GetLocalOrigin() ); + return; + } + + AUTO_LOCK( m_CalcAbsolutePositionMutex ); + + if ((m_iEFlags & EFL_DIRTY_ABSTRANSFORM) == 0) // need second check in event another thread grabbed mutex and did the calculation + { + return; + } + + RemoveEFlags( EFL_DIRTY_ABSTRANSFORM ); + + if (!m_pMoveParent) + { + // Construct the entity-to-world matrix + // Start with making an entity-to-parent matrix + AngleMatrix( GetLocalAngles(), GetLocalOrigin(), m_rgflCoordinateFrame ); + m_vecAbsOrigin = GetLocalOrigin(); + m_angAbsRotation = GetLocalAngles(); + NormalizeAngles( m_angAbsRotation ); + return; + } + + if ( IsEffectActive(EF_BONEMERGE) ) + { + MoveToAimEnt(); + return; + } + + // Construct the entity-to-world matrix + // Start with making an entity-to-parent matrix + matrix3x4_t matEntityToParent; + AngleMatrix( GetLocalAngles(), matEntityToParent ); + MatrixSetColumn( GetLocalOrigin(), 3, matEntityToParent ); + + // concatenate with our parent's transform + matrix3x4_t scratchMatrix; + ConcatTransforms( GetParentToWorldTransform( scratchMatrix ), matEntityToParent, m_rgflCoordinateFrame ); + + // pull our absolute position out of the matrix + MatrixGetColumn( m_rgflCoordinateFrame, 3, m_vecAbsOrigin ); + + // if we have any angles, we have to extract our absolute angles from our matrix + if ( m_angRotation == vec3_angle && m_iParentAttachment == 0 ) + { + // just copy our parent's absolute angles + VectorCopy( m_pMoveParent->GetAbsAngles(), m_angAbsRotation ); + } + else + { + MatrixAngles( m_rgflCoordinateFrame, m_angAbsRotation ); + } + + // This is necessary because it's possible that our moveparent's CalculateIKLocks will trigger its move children + // (ie: this entity) to call GetAbsOrigin(), and they'll use the moveparent's OLD bone transforms to get their attachments + // since the moveparent is right in the middle of setting up new transforms. + // + // So here, we keep our absorigin invalidated. It means we're returning an origin that is a frame old to CalculateIKLocks, + // but we'll still render with the right origin. + if ( m_iParentAttachment != 0 && (m_pMoveParent->GetFlags() & EFL_SETTING_UP_BONES) ) + { + m_iEFlags |= EFL_DIRTY_ABSTRANSFORM; + } +} + +void C_BaseEntity::CalcAbsoluteVelocity() +{ + if ((m_iEFlags & EFL_DIRTY_ABSVELOCITY ) == 0) + return; + + AUTO_LOCK( m_CalcAbsoluteVelocityMutex ); + + if ((m_iEFlags & EFL_DIRTY_ABSVELOCITY) == 0) // need second check in event another thread grabbed mutex and did the calculation + { + return; + } + + m_iEFlags &= ~EFL_DIRTY_ABSVELOCITY; + + CBaseEntity *pMoveParent = GetMoveParent(); + if ( !pMoveParent ) + { + m_vecAbsVelocity = m_vecVelocity; + return; + } + + VectorRotate( m_vecVelocity, pMoveParent->EntityToWorldTransform(), m_vecAbsVelocity ); + + + // Add in the attachments velocity if it exists + if ( m_iParentAttachment != 0 ) + { + Vector vOriginVel; + Quaternion vAngleVel; + if ( pMoveParent->GetAttachmentVelocity( m_iParentAttachment, vOriginVel, vAngleVel ) ) + { + m_vecAbsVelocity += vOriginVel; + return; + } + } + + // Now add in the parent abs velocity + m_vecAbsVelocity += pMoveParent->GetAbsVelocity(); +} + +/* +void C_BaseEntity::CalcAbsoluteAngularVelocity() +{ + if ((m_iEFlags & EFL_DIRTY_ABSANGVELOCITY ) == 0) + return; + + m_iEFlags &= ~EFL_DIRTY_ABSANGVELOCITY; + + CBaseEntity *pMoveParent = GetMoveParent(); + if ( !pMoveParent ) + { + m_vecAbsAngVelocity = m_vecAngVelocity; + return; + } + + matrix3x4_t angVelToParent, angVelToWorld; + AngleMatrix( m_vecAngVelocity, angVelToParent ); + ConcatTransforms( pMoveParent->EntityToWorldTransform(), angVelToParent, angVelToWorld ); + MatrixAngles( angVelToWorld, m_vecAbsAngVelocity ); + + // Now add in the parent abs angular velocity + m_vecAbsAngVelocity += pMoveParent->GetAbsAngularVelocity(); +} +*/ + + +//----------------------------------------------------------------------------- +// Computes the abs position of a point specified in local space +//----------------------------------------------------------------------------- +void C_BaseEntity::ComputeAbsPosition( const Vector &vecLocalPosition, Vector *pAbsPosition ) +{ + C_BaseEntity *pMoveParent = GetMoveParent(); + if ( !pMoveParent ) + { + *pAbsPosition = vecLocalPosition; + } + else + { + VectorTransform( vecLocalPosition, pMoveParent->EntityToWorldTransform(), *pAbsPosition ); + } +} + + +//----------------------------------------------------------------------------- +// Computes the abs position of a point specified in local space +//----------------------------------------------------------------------------- +void C_BaseEntity::ComputeAbsDirection( const Vector &vecLocalDirection, Vector *pAbsDirection ) +{ + C_BaseEntity *pMoveParent = GetMoveParent(); + if ( !pMoveParent ) + { + *pAbsDirection = vecLocalDirection; + } + else + { + VectorRotate( vecLocalDirection, pMoveParent->EntityToWorldTransform(), *pAbsDirection ); + } +} + + + +//----------------------------------------------------------------------------- +// Mark shadow as dirty +//----------------------------------------------------------------------------- +void C_BaseEntity::MarkRenderHandleDirty( ) +{ + // Invalidate render leaf too + ClientRenderHandle_t handle = GetRenderHandle(); + if ( handle != INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->RenderableChanged( handle ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::ShutdownPredictable( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + Assert( GetPredictable() ); + + g_Predictables.RemoveFromPredictablesList( GetClientHandle() ); + DestroyIntermediateData(); + SetPredictable( false ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Turn entity into something the predicts locally +//----------------------------------------------------------------------------- +void C_BaseEntity::InitPredictable( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + Assert( !GetPredictable() ); + + // Mark as predictable + SetPredictable( true ); + // Allocate buffers into which we copy data + AllocateIntermediateData(); + // Add to list of predictables + g_Predictables.AddToPredictableList( GetClientHandle() ); + // Copy everything from "this" into the original_state_data + // object. Don't care about client local stuff, so pull from slot 0 which + + // should be empty anyway... + PostNetworkDataReceived( 0 ); + + // Copy original data into all prediction slots, so we don't get an error saying we "mispredicted" any + // values which are still at their initial values + for ( int i = 0; i < MULTIPLAYER_BACKUP; i++ ) + { + SaveData( "InitPredictable", i, PC_EVERYTHING ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetPredictable( bool state ) +{ + m_bPredictable = state; + + // update interpolation times + Interp_UpdateInterpolationAmounts( GetVarMapping() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::GetPredictable( void ) const +{ + return m_bPredictable; +} + +//----------------------------------------------------------------------------- +// Purpose: Transfer data for intermediate frame to current entity +// Input : copyintermediate - +// last_predicted - +//----------------------------------------------------------------------------- +void C_BaseEntity::PreEntityPacketReceived( int commands_acknowledged ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + // Don't need to copy intermediate data if server did ack any new commands + bool copyintermediate = ( commands_acknowledged > 0 ) ? true : false; + + Assert( GetPredictable() ); + Assert( cl_predict->GetInt() ); + + // First copy in any intermediate predicted data for non-networked fields + if ( copyintermediate ) + { + RestoreData( "PreEntityPacketReceived", commands_acknowledged - 1, PC_NON_NETWORKED_ONLY ); + RestoreData( "PreEntityPacketReceived", SLOT_ORIGINALDATA, PC_NETWORKED_ONLY ); + } + else + { + RestoreData( "PreEntityPacketReceived(no commands ack)", SLOT_ORIGINALDATA, PC_EVERYTHING ); + } + + // At this point the entity has original network data restored as of the last time the + // networking was updated, and it has any intermediate predicted values properly copied over + // Unpacked and OnDataChanged will fill in any changed, networked fields. + + // That networked data will be copied forward into the starting slot for the next prediction round +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called every time PreEntityPacket received is called +// copy any networked data into original_state +// Input : errorcheck - +// last_predicted - +//----------------------------------------------------------------------------- +void C_BaseEntity::PostEntityPacketReceived( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + Assert( GetPredictable() ); + Assert( cl_predict->GetInt() ); + + // Always mark as changed + AddDataChangeEvent( this, DATA_UPDATE_DATATABLE_CHANGED, &m_DataChangeEventRef ); + + // Save networked fields into "original data" store + SaveData( "PostEntityPacketReceived", SLOT_ORIGINALDATA, PC_NETWORKED_ONLY ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called once per frame after all updating is done +// Input : errorcheck - +// last_predicted - +//----------------------------------------------------------------------------- +bool C_BaseEntity::PostNetworkDataReceived( int commands_acknowledged ) +{ + bool haderrors = false; +#if !defined( NO_ENTITY_PREDICTION ) + Assert( GetPredictable() ); + + bool errorcheck = ( commands_acknowledged > 0 ) ? true : false; + + // Store network data into post networking pristine state slot (slot 64) + SaveData( "PostNetworkDataReceived", SLOT_ORIGINALDATA, PC_EVERYTHING ); + + // Show any networked fields that are different + bool showthis = cl_showerror.GetInt() >= 2; + + if ( cl_showerror.GetInt() < 0 ) + { + if ( entindex() == -cl_showerror.GetInt() ) + { + showthis = true; + } + else + { + showthis = false; + } + } + + if ( errorcheck ) + { + void *predicted_state_data = GetPredictedFrame( commands_acknowledged - 1 ); + Assert( predicted_state_data ); + const void *original_state_data = GetOriginalNetworkDataObject(); + Assert( original_state_data ); + + bool counterrors = true; + bool reporterrors = showthis; + bool copydata = false; + + CPredictionCopy errorCheckHelper( PC_NETWORKED_ONLY, + predicted_state_data, PC_DATA_PACKED, + original_state_data, PC_DATA_PACKED, + counterrors, reporterrors, copydata ); + // Suppress debugging output + int ecount = errorCheckHelper.TransferData( "", -1, GetPredDescMap() ); + if ( ecount > 0 ) + { + haderrors = true; + // Msg( "%i errors %i on entity %i %s\n", gpGlobals->tickcount, ecount, index, IsClientCreated() ? "true" : "false" ); + } + } +#endif + return haderrors; +} + +// Stuff implemented for weapon prediction code +void C_BaseEntity::SetSize( const Vector &vecMin, const Vector &vecMax ) +{ + SetCollisionBounds( vecMin, vecMax ); +} + +//----------------------------------------------------------------------------- +// Purpose: Just look up index +// Input : *name - +// Output : int +//----------------------------------------------------------------------------- +int C_BaseEntity::PrecacheModel( const char *name ) +{ + return modelinfo->GetModelIndex( name ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *obj - +//----------------------------------------------------------------------------- +void C_BaseEntity::Remove( ) +{ + // Nothing for now, if it's a predicted entity, could flag as "delete" or dormant + if ( GetPredictable() || IsClientCreated() ) + { + // Make it solid + AddSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); + + AddEFlags( EFL_KILLME ); // Make sure to ignore further calls into here or UTIL_Remove. + } + + Release(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::GetPredictionEligible( void ) const +{ +#if !defined( NO_ENTITY_PREDICTION ) + return m_bPredictionEligible; +#else + return false; +#endif +} + + +C_BaseEntity* C_BaseEntity::Instance( CBaseHandle hEnt ) +{ + return ClientEntityList().GetBaseEntityFromHandle( hEnt ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iEnt - +// Output : C_BaseEntity +//----------------------------------------------------------------------------- +C_BaseEntity *C_BaseEntity::Instance( int iEnt ) +{ + return ClientEntityList().GetBaseEntity( iEnt ); +} + +#pragma warning( push ) +#include +#pragma warning( pop ) + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +const char *C_BaseEntity::GetClassname( void ) +{ + static char outstr[ 256 ]; + outstr[ 0 ] = 0; + bool gotname = false; +#ifndef NO_ENTITY_PREDICTION + if ( GetPredDescMap() ) + { + const char *mapname = GetClassMap().Lookup( GetPredDescMap()->dataClassName ); + if ( mapname && mapname[ 0 ] ) + { + Q_snprintf( outstr, sizeof( outstr ), "%s", mapname ); + gotname = true; + } + } +#endif + + if ( !gotname ) + { + Q_strncpy( outstr, typeid( *this ).name(), sizeof( outstr ) ); + } + + return outstr; +} + +const char *C_BaseEntity::GetDebugName( void ) +{ + return GetClassname(); +} + +//----------------------------------------------------------------------------- +// Purpose: Creates an entity by string name, but does not spawn it +// Input : *className - +// Output : C_BaseEntity +//----------------------------------------------------------------------------- +C_BaseEntity *CreateEntityByName( const char *className ) +{ + C_BaseEntity *ent = GetClassMap().CreateEntity( className ); + if ( ent ) + { + return ent; + } + + Warning( "Can't find factory for entity: %s\n", className ); + return NULL; +} + +#ifdef _DEBUG +CON_COMMAND( cl_sizeof, "Determines the size of the specified client class." ) +{ + if ( args.ArgC() != 2 ) + { + Msg( "cl_sizeof \n" ); + return; + } + + int size = GetClassMap().GetClassSize( args[ 1 ] ); + + Msg( "%s is %i bytes\n", args[ 1 ], size ); +} +#endif + +CON_COMMAND_F( dlight_debug, "Creates a dlight in front of the player", FCVAR_CHEAT ) +{ + dlight_t *el = effects->CL_AllocDlight( 1 ); + C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); + if ( !player ) + return; + Vector start = player->EyePosition(); + Vector forward; + player->EyeVectors( &forward ); + Vector end = start + forward * MAX_TRACE_LENGTH; + trace_t tr; + UTIL_TraceLine( start, end, MASK_SHOT_HULL & (~CONTENTS_GRATE), player, COLLISION_GROUP_NONE, &tr ); + el->origin = tr.endpos - forward * 12.0f; + el->radius = 200; + el->decay = el->radius / 5.0f; + el->die = gpGlobals->curtime + 5.0f; + el->color.r = 255; + el->color.g = 192; + el->color.b = 64; + el->color.exponent = 5; + +} +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsClientCreated( void ) const +{ +#ifndef NO_ENTITY_PREDICTION + if ( m_pPredictionContext != NULL ) + { + // For now can't be both + Assert( !GetPredictable() ); + return true; + } +#endif + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classname - +// *module - +// line - +// Output : C_BaseEntity +//----------------------------------------------------------------------------- +C_BaseEntity *C_BaseEntity::CreatePredictedEntityByName( const char *classname, const char *module, int line, bool persist /*= false */ ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + C_BasePlayer *player = C_BaseEntity::GetPredictionPlayer(); + + Assert( player ); + Assert( player->m_pCurrentCommand ); + Assert( prediction->InPrediction() ); + + C_BaseEntity *ent = NULL; + + // What's my birthday (should match server) + int command_number = player->m_pCurrentCommand->command_number; + // Who's my daddy? + int player_index = player->entindex() - 1; + + // Create id/context + CPredictableId testId; + testId.Init( player_index, command_number, classname, module, line ); + + // If repredicting, should be able to find the entity in the previously created list + if ( !prediction->IsFirstTimePredicted() ) + { + // Only find previous instance if entity was created with persist set + if ( persist ) + { + ent = FindPreviouslyCreatedEntity( testId ); + if ( ent ) + { + return ent; + } + } + + return NULL; + } + + // Try to create it + ent = CreateEntityByName( classname ); + if ( !ent ) + { + return NULL; + } + + // It's predictable + ent->SetPredictionEligible( true ); + + // Set up "shared" id number + ent->m_PredictableID.SetRaw( testId.GetRaw() ); + + // Get a context (mostly for debugging purposes) + PredictionContext *context = new PredictionContext; + context->m_bActive = true; + context->m_nCreationCommandNumber = command_number; + context->m_nCreationLineNumber = line; + context->m_pszCreationModule = module; + + // Attach to entity + ent->m_pPredictionContext = context; + + // Add to client entity list + ClientEntityList().AddNonNetworkableEntity( ent ); + + // and predictables + g_Predictables.AddToPredictableList( ent->GetClientHandle() ); + + // Duhhhh..., but might as well be safe + Assert( !ent->GetPredictable() ); + Assert( ent->IsClientCreated() ); + + // Add the client entity to the spatial partition. (Collidable) + ent->CollisionProp()->CreatePartitionHandle(); + + // CLIENT ONLY FOR NOW!!! + ent->index = -1; + + if ( AddDataChangeEvent( ent, DATA_UPDATE_CREATED, &ent->m_DataChangeEventRef ) ) + { + ent->OnPreDataChanged( DATA_UPDATE_CREATED ); + } + + ent->Interp_UpdateInterpolationAmounts( ent->GetVarMapping() ); + + return ent; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called each packet that the entity is created on and finally gets called after the next packet +// that doesn't have a create message for the "parent" entity so that the predicted version +// can be removed. Return true to delete entity right away. +//----------------------------------------------------------------------------- +bool C_BaseEntity::OnPredictedEntityRemove( bool isbeingremoved, C_BaseEntity *predicted ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + // Nothing right now, but in theory you could look at the error in origins and set + // up something to smooth out the error + PredictionContext *ctx = predicted->m_pPredictionContext; + Assert( ctx ); + if ( ctx ) + { + // Create backlink to actual entity + ctx->m_hServerEntity = this; + + /* + Msg( "OnPredictedEntity%s: %s created %s(%i) instance(%i)\n", + isbeingremoved ? "Remove" : "Acknowledge", + predicted->GetClassname(), + ctx->m_pszCreationModule, + ctx->m_nCreationLineNumber, + predicted->m_PredictableID.GetInstanceNumber() ); + */ + } + + // If it comes through with an ID, it should be eligible + SetPredictionEligible( true ); + + // Start predicting simulation forward from here + CheckInitPredictable( "OnPredictedEntityRemove" ); + + // Always mark it dormant since we are the "real" entity now + predicted->SetDormantPredictable( true ); + + InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED ); + + // By default, signal that it should be deleted right away + // If a derived class implements this method, it might chain to here but return + // false if it wants to keep the dormant predictable around until the chain of + // DATA_UPDATE_CREATED messages passes +#endif + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOwner - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetOwnerEntity( C_BaseEntity *pOwner ) +{ + m_hOwnerEntity = pOwner; +} + +//----------------------------------------------------------------------------- +// Purpose: Put the entity in the specified team +//----------------------------------------------------------------------------- +void C_BaseEntity::ChangeTeam( int iTeamNum ) +{ + m_iTeamNum = iTeamNum; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : name - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetModelName( string_t name ) +{ + m_ModelName = name; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : string_t +//----------------------------------------------------------------------------- +string_t C_BaseEntity::GetModelName( void ) const +{ + return m_ModelName; +} + +//----------------------------------------------------------------------------- +// Purpose: Nothing yet, could eventually supercede Term() +//----------------------------------------------------------------------------- +void C_BaseEntity::UpdateOnRemove( void ) +{ + VPhysicsDestroyObject(); + + Assert( !GetMoveParent() ); + UnlinkFromHierarchy(); + SetGroundEntity( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : canpredict - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetPredictionEligible( bool canpredict ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + m_bPredictionEligible = canpredict; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a value that scales all damage done by this entity. +//----------------------------------------------------------------------------- +float C_BaseEntity::GetAttackDamageScale( void ) +{ + float flScale = 1; +// Not hooked up to prediction yet +#if 0 + FOR_EACH_LL( m_DamageModifiers, i ) + { + if ( !m_DamageModifiers[i]->IsDamageDoneToMe() ) + { + flScale *= m_DamageModifiers[i]->GetModifier(); + } + } +#endif + return flScale; +} + +#if !defined( NO_ENTITY_PREDICTION ) +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsDormantPredictable( void ) const +{ + return m_bDormantPredictable; +} +#endif +//----------------------------------------------------------------------------- +// Purpose: +// Input : dormant - +//----------------------------------------------------------------------------- +void C_BaseEntity::SetDormantPredictable( bool dormant ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + Assert( IsClientCreated() ); + + m_bDormantPredictable = true; + m_nIncomingPacketEntityBecameDormant = prediction->GetIncomingPacketNumber(); + +// Do we need to do the following kinds of things? +#if 0 + // Remove from collisions + SetSolid( SOLID_NOT ); + // Don't render + AddEffects( EF_NODRAW ); +#endif +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Used to determine when a dorman client predictable can be safely deleted +// Note that it can be deleted earlier than this by OnPredictedEntityRemove returning true +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::BecameDormantThisPacket( void ) const +{ +#if !defined( NO_ENTITY_PREDICTION ) + Assert( IsDormantPredictable() ); + + if ( m_nIncomingPacketEntityBecameDormant != prediction->GetIncomingPacketNumber() ) + return false; + + return true; +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsIntermediateDataAllocated( void ) const +{ +#if !defined( NO_ENTITY_PREDICTION ) + return m_pOriginalData != NULL ? true : false; +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::AllocateIntermediateData( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + if ( m_pOriginalData ) + return; + size_t allocsize = GetIntermediateDataSize(); + Assert( allocsize > 0 ); + + m_pOriginalData = new unsigned char[ allocsize ]; + Q_memset( m_pOriginalData, 0, allocsize ); + for ( int i = 0; i < MULTIPLAYER_BACKUP; i++ ) + { + m_pIntermediateData[ i ] = new unsigned char[ allocsize ]; + Q_memset( m_pIntermediateData[ i ], 0, allocsize ); + } + + m_nIntermediateDataCount = 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::DestroyIntermediateData( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + if ( !m_pOriginalData ) + return; + for ( int i = 0; i < MULTIPLAYER_BACKUP; i++ ) + { + delete[] m_pIntermediateData[ i ]; + m_pIntermediateData[ i ] = NULL; + } + delete[] m_pOriginalData; + m_pOriginalData = NULL; + + m_nIntermediateDataCount = 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : slots_to_remove - +// number_of_commands_run - +//----------------------------------------------------------------------------- +void C_BaseEntity::ShiftIntermediateDataForward( int slots_to_remove, int number_of_commands_run ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + Assert( m_pIntermediateData ); + if ( !m_pIntermediateData ) + return; + + Assert( number_of_commands_run >= slots_to_remove ); + + // Just moving pointers, yeah + CUtlVector< unsigned char * > saved; + + // Remember first slots + int i = 0; + for ( ; i < slots_to_remove; i++ ) + { + saved.AddToTail( m_pIntermediateData[ i ] ); + } + + // Move rest of slots forward up to last slot + for ( ; i < number_of_commands_run; i++ ) + { + m_pIntermediateData[ i - slots_to_remove ] = m_pIntermediateData[ i ]; + } + + // Put remembered slots onto end + for ( i = 0; i < slots_to_remove; i++ ) + { + int slot = number_of_commands_run - slots_to_remove + i; + + m_pIntermediateData[ slot ] = saved[ i ]; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : framenumber - +//----------------------------------------------------------------------------- +void *C_BaseEntity::GetPredictedFrame( int framenumber ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + Assert( framenumber >= 0 ); + + if ( !m_pOriginalData ) + { + Assert( 0 ); + return NULL; + } + return (void *)m_pIntermediateData[ framenumber % MULTIPLAYER_BACKUP ]; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void *C_BaseEntity::GetOriginalNetworkDataObject( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + if ( !m_pOriginalData ) + { + Assert( 0 ); + return NULL; + } + return (void *)m_pOriginalData; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::ComputePackedOffsets( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + datamap_t *map = GetPredDescMap(); + if ( !map ) + return; + + if ( map->packed_offsets_computed ) + return; + + ComputePackedSize_R( map ); + + Assert( map->packed_offsets_computed ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int C_BaseEntity::GetIntermediateDataSize( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + ComputePackedOffsets(); + + const datamap_t *map = GetPredDescMap(); + + Assert( map->packed_offsets_computed ); + + int size = map->packed_size; + + Assert( size > 0 ); + + // At least 4 bytes to avoid some really bad stuff + return max( size, 4 ); +#else + return 0; +#endif +} + +static int g_FieldSizes[FIELD_TYPECOUNT] = +{ + 0, // FIELD_VOID + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(Vector), // FIELD_VECTOR + sizeof(Quaternion), // FIELD_QUATERNION + sizeof(int), // FIELD_INTEGER + sizeof(char), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(color32), // FIELD_COLOR32 + sizeof(int), // FIELD_EMBEDDED (handled specially) + sizeof(int), // FIELD_CUSTOM (handled specially) + + //--------------------------------- + + sizeof(int), // FIELD_CLASSPTR + sizeof(EHANDLE), // FIELD_EHANDLE + sizeof(int), // FIELD_EDICT + + sizeof(Vector), // FIELD_POSITION_VECTOR + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_TICK + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME + + sizeof(int), // FIELD_INPUT (uses custom type) + sizeof(int *), // FIELD_FUNCTION + sizeof(VMatrix), // FIELD_VMATRIX + sizeof(VMatrix), // FIELD_VMATRIX_WORLDSPACE + sizeof(matrix3x4_t),// FIELD_MATRIX3X4_WORLDSPACE // NOTE: Use array(FIELD_FLOAT, 12) for matrix3x4_t NOT in worldspace + sizeof(interval_t), // FIELD_INTERVAL + sizeof(int), // FIELD_MODELINDEX +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *map - +// Output : int +//----------------------------------------------------------------------------- +int C_BaseEntity::ComputePackedSize_R( datamap_t *map ) +{ + if ( !map ) + { + Assert( 0 ); + return 0; + } + + // Already computed + if ( map->packed_offsets_computed ) + { + return map->packed_size; + } + + int current_position = 0; + + // Recurse to base classes first... + if ( map->baseMap ) + { + current_position += ComputePackedSize_R( map->baseMap ); + } + + int c = map->dataNumFields; + int i; + typedescription_t *field; + + for ( i = 0; i < c; i++ ) + { + field = &map->dataDesc[ i ]; + + // Always descend into embedded types... + if ( field->fieldType != FIELD_EMBEDDED ) + { + // Skip all private fields + if ( field->flags & FTYPEDESC_PRIVATE ) + continue; + } + + switch ( field->fieldType ) + { + default: + case FIELD_MODELINDEX: + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_TIME: + case FIELD_TICK: + case FIELD_CUSTOM: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_POSITION_VECTOR: + case FIELD_FUNCTION: + Assert( 0 ); + break; + + case FIELD_EMBEDDED: + { + Assert( field->td != NULL ); + + int embeddedsize = ComputePackedSize_R( field->td ); + + field->fieldOffset[ TD_OFFSET_PACKED ] = current_position; + + current_position += embeddedsize; + } + break; + + case FIELD_FLOAT: + case FIELD_VECTOR: + case FIELD_QUATERNION: + case FIELD_INTEGER: + case FIELD_EHANDLE: + { + // These should be dword aligned + current_position = (current_position + 3) & ~3; + field->fieldOffset[ TD_OFFSET_PACKED ] = current_position; + Assert( field->fieldSize >= 1 ); + current_position += g_FieldSizes[ field->fieldType ] * field->fieldSize; + } + break; + + case FIELD_SHORT: + { + // This should be word aligned + current_position = (current_position + 1) & ~1; + field->fieldOffset[ TD_OFFSET_PACKED ] = current_position; + Assert( field->fieldSize >= 1 ); + current_position += g_FieldSizes[ field->fieldType ] * field->fieldSize; + } + break; + + case FIELD_STRING: + case FIELD_COLOR32: + case FIELD_BOOLEAN: + case FIELD_CHARACTER: + { + field->fieldOffset[ TD_OFFSET_PACKED ] = current_position; + Assert( field->fieldSize >= 1 ); + current_position += g_FieldSizes[ field->fieldType ] * field->fieldSize; + } + break; + case FIELD_VOID: + { + // Special case, just skip it + } + break; + } + } + + map->packed_size = current_position; + map->packed_offsets_computed = true; + + return current_position; +} + +// Convenient way to delay removing oneself +void C_BaseEntity::SUB_Remove( void ) +{ + if (m_iHealth > 0) + { + // this situation can screw up NPCs who can't tell their entity pointers are invalid. + m_iHealth = 0; + DevWarning( 2, "SUB_Remove called on entity with health > 0\n"); + } + + Remove( ); +} + +CBaseEntity *FindEntityInFrontOfLocalPlayer() +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer ) + { + // Get the entity under my crosshair + trace_t tr; + Vector forward; + pPlayer->EyeVectors( &forward ); + UTIL_TraceLine( pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_COORD_RANGE, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() ) + { + return tr.m_pEnt; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Debug command to wipe the decals off an entity +//----------------------------------------------------------------------------- +static void RemoveDecals_f( void ) +{ + CBaseEntity *pHit = FindEntityInFrontOfLocalPlayer(); + if ( pHit ) + { + pHit->RemoveAllDecals(); + } +} + +static ConCommand cl_removedecals( "cl_removedecals", RemoveDecals_f, "Remove the decals from the entity under the crosshair.", FCVAR_CHEAT ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::ToggleBBoxVisualization( int fVisFlags ) +{ + if ( m_fBBoxVisFlags & fVisFlags ) + { + m_fBBoxVisFlags &= ~fVisFlags; + } + else + { + m_fBBoxVisFlags |= fVisFlags; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void ToggleBBoxVisualization( int fVisFlags, const CCommand &args ) +{ + CBaseEntity *pHit; + + int iEntity = -1; + if ( args.ArgC() >= 2 ) + { + iEntity = atoi( args[ 1 ] ); + } + + if ( iEntity == -1 ) + { + pHit = FindEntityInFrontOfLocalPlayer(); + } + else + { + pHit = cl_entitylist->GetBaseEntity( iEntity ); + } + + if ( pHit ) + { + pHit->ToggleBBoxVisualization( fVisFlags ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Command to toggle visualizations of bboxes on the client +//----------------------------------------------------------------------------- +CON_COMMAND_F( cl_ent_bbox, "Displays the client's bounding box for the entity under the crosshair.", FCVAR_CHEAT ) +{ + ToggleBBoxVisualization( CBaseEntity::VISUALIZE_COLLISION_BOUNDS, args ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Command to toggle visualizations of bboxes on the client +//----------------------------------------------------------------------------- +CON_COMMAND_F( cl_ent_absbox, "Displays the client's absbox for the entity under the crosshair.", FCVAR_CHEAT ) +{ + ToggleBBoxVisualization( CBaseEntity::VISUALIZE_SURROUNDING_BOUNDS, args ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Command to toggle visualizations of bboxes on the client +//----------------------------------------------------------------------------- +CON_COMMAND_F( cl_ent_rbox, "Displays the client's render box for the entity under the crosshair.", FCVAR_CHEAT ) +{ + ToggleBBoxVisualization( CBaseEntity::VISUALIZE_RENDER_BOUNDS, args ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseEntity::DrawBBoxVisualizations( void ) +{ + if ( m_fBBoxVisFlags & VISUALIZE_COLLISION_BOUNDS ) + { + debugoverlay->AddBoxOverlay( CollisionProp()->GetCollisionOrigin(), CollisionProp()->OBBMins(), + CollisionProp()->OBBMaxs(), CollisionProp()->GetCollisionAngles(), 190, 190, 0, 0, 0.01 ); + } + + if ( m_fBBoxVisFlags & VISUALIZE_SURROUNDING_BOUNDS ) + { + Vector vecSurroundMins, vecSurroundMaxs; + CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs ); + debugoverlay->AddBoxOverlay( vec3_origin, vecSurroundMins, + vecSurroundMaxs, vec3_angle, 0, 255, 255, 0, 0.01 ); + } + + if ( m_fBBoxVisFlags & VISUALIZE_RENDER_BOUNDS || r_drawrenderboxes.GetInt() ) + { + Vector vecRenderMins, vecRenderMaxs; + GetRenderBounds( vecRenderMins, vecRenderMaxs ); + debugoverlay->AddBoxOverlay( GetRenderOrigin(), vecRenderMins, vecRenderMaxs, + GetRenderAngles(), 255, 0, 255, 0, 0.01 ); + } +} + + +//----------------------------------------------------------------------------- +// Sets the render mode +//----------------------------------------------------------------------------- +void C_BaseEntity::SetRenderMode( RenderMode_t nRenderMode, bool bForceUpdate ) +{ + m_nRenderMode = nRenderMode; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : RenderGroup_t +//----------------------------------------------------------------------------- +RenderGroup_t C_BaseEntity::GetRenderGroup() +{ + // Don't sort things that don't need rendering + if ( m_nRenderMode == kRenderNone ) + return RENDER_GROUP_OPAQUE_ENTITY; + + // When an entity has a material proxy, we have to recompute + // translucency here because the proxy may have changed it. + if (modelinfo->ModelHasMaterialProxy( GetModel() )) + { + modelinfo->RecomputeTranslucency( const_cast(GetModel()), GetSkin(), GetBody(), GetClientRenderable() ); + } + + // NOTE: Bypassing the GetFXBlend protection logic because we want this to + // be able to be called from AddToLeafSystem. + int nTempComputeFrame = m_nFXComputeFrame; + m_nFXComputeFrame = gpGlobals->framecount; + + int nFXBlend = GetFxBlend(); + + m_nFXComputeFrame = nTempComputeFrame; + + // Don't need to sort invisible stuff + if ( nFXBlend == 0 ) + return RENDER_GROUP_OPAQUE_ENTITY; + + // Figure out its RenderGroup. + int modelType = modelinfo->GetModelType( model ); + RenderGroup_t renderGroup = (modelType == mod_brush) ? RENDER_GROUP_OPAQUE_BRUSH : RENDER_GROUP_OPAQUE_ENTITY; + if ( ( nFXBlend != 255 ) || IsTransparent() ) + { + if ( m_nRenderMode != kRenderEnvironmental ) + { + renderGroup = RENDER_GROUP_TRANSLUCENT_ENTITY; + } + else + { + renderGroup = RENDER_GROUP_OTHER; + } + } + + if ( ( renderGroup == RENDER_GROUP_TRANSLUCENT_ENTITY ) && + ( modelinfo->IsTranslucentTwoPass( model ) ) ) + { + renderGroup = RENDER_GROUP_TWOPASS; + } + + return renderGroup; +} + +//----------------------------------------------------------------------------- +// Purpose: Copy from this entity into one of the save slots (original or intermediate) +// Input : slot - +// type - +// false - +// false - +// true - +// false - +// NULL - +// Output : int +//----------------------------------------------------------------------------- +int C_BaseEntity::SaveData( const char *context, int slot, int type ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + VPROF( "C_BaseEntity::SaveData" ); + + void *dest = ( slot == SLOT_ORIGINALDATA ) ? GetOriginalNetworkDataObject() : GetPredictedFrame( slot ); + Assert( dest ); + char sz[ 64 ]; + if ( slot == SLOT_ORIGINALDATA ) + { + Q_snprintf( sz, sizeof( sz ), "%s SaveData(original)", context ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "%s SaveData(slot %02i)", context, slot ); + + // Remember high water mark so that we can detect below if we are reading from a slot not yet predicted into... + m_nIntermediateDataCount = slot; + } + + CPredictionCopy copyHelper( type, dest, PC_DATA_PACKED, this, PC_DATA_NORMAL ); + int error_count = copyHelper.TransferData( sz, entindex(), GetPredDescMap() ); + return error_count; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Restore data from specified slot into current entity +// Input : slot - +// type - +// false - +// false - +// true - +// false - +// NULL - +// Output : int +//----------------------------------------------------------------------------- +int C_BaseEntity::RestoreData( const char *context, int slot, int type ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + VPROF( "C_BaseEntity::RestoreData" ); + + const void *src = ( slot == SLOT_ORIGINALDATA ) ? GetOriginalNetworkDataObject() : GetPredictedFrame( slot ); + Assert( src ); + char sz[ 64 ]; + if ( slot == SLOT_ORIGINALDATA ) + { + Q_snprintf( sz, sizeof( sz ), "%s RestoreData(original)", context ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "%s RestoreData(slot %02i)", context, slot ); + + // This assert will fire if the server ack'd a CUserCmd which we hadn't predicted yet... + // In that case, we'd be comparing "old" data from this "unused" slot with the networked data and reporting all kinds of prediction errors possibly. + Assert( slot <= m_nIntermediateDataCount ); + } + + // some flags shouldn't be predicted - as we find them, add them to the savedEFlagsMask + const int savedEFlagsMask = EFL_DIRTY_SHADOWUPDATE; + int savedEFlags = GetEFlags() & savedEFlagsMask; + + CPredictionCopy copyHelper( type, this, PC_DATA_NORMAL, src, PC_DATA_PACKED ); + int error_count = copyHelper.TransferData( sz, entindex(), GetPredDescMap() ); + + // set non-predicting flags back to their prior state + RemoveEFlags( savedEFlagsMask ); + AddEFlags( savedEFlags ); + + OnPostRestoreData(); + + return error_count; +#else + return 0; +#endif +} + + +void C_BaseEntity::OnPostRestoreData() +{ + // HACK Force recomputation of origin + InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED ); + + if ( GetMoveParent() ) + { + AddToAimEntsList(); + } + + // If our model index has changed, then make sure it's reflected in our model pointer. + if ( GetModel() != modelinfo->GetModel( GetModelIndex() ) ) + { + MDLCACHE_CRITICAL_SECTION(); + SetModelByIndex( GetModelIndex() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Determine approximate velocity based on updates from server +// Input : vel - +//----------------------------------------------------------------------------- +void C_BaseEntity::EstimateAbsVelocity( Vector& vel ) +{ + if ( this == C_BasePlayer::GetLocalPlayer() ) + { + vel = GetAbsVelocity(); + return; + } + + CInterpolationContext context; + context.EnableExtrapolation( true ); + m_iv_vecOrigin.GetDerivative_SmoothVelocity( &vel, gpGlobals->curtime ); +} + +void C_BaseEntity::Interp_Reset( VarMapping_t *map ) +{ + PREDICTION_TRACKVALUECHANGESCOPE_ENTITY( this, "reset" ); + int c = map->m_Entries.Count(); + for ( int i = 0; i < c; i++ ) + { + VarMapEntry_t *e = &map->m_Entries[ i ]; + IInterpolatedVar *watcher = e->watcher; + + watcher->Reset(); + } +} + +void C_BaseEntity::ResetLatched() +{ + if ( IsClientCreated() ) + return; + + Interp_Reset( GetVarMapping() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Fixme, this needs a better solution +// Input : flags - +// Output : float +//----------------------------------------------------------------------------- + +static float AdjustInterpolationAmount( C_BaseEntity *pEntity, float baseInterpolation ) +{ + if ( cl_interp_npcs.GetFloat() > 0 ) + { + const float minNPCInterpolationTime = cl_interp_npcs.GetFloat(); + const float minNPCInterpolation = TICK_INTERVAL * ( TIME_TO_TICKS( minNPCInterpolationTime ) + 1 ); + + if ( minNPCInterpolation > baseInterpolation ) + { + while ( pEntity ) + { + if ( pEntity->IsNPC() ) + return minNPCInterpolation; + + pEntity = pEntity->GetMoveParent(); + } + } + } + + return baseInterpolation; +} + +//------------------------------------- +float C_BaseEntity::GetInterpolationAmount( int flags ) +{ + // If single player server is "skipping ticks" everything needs to interpolate for a bit longer + int serverTickMultiple = 1; + if ( IsSimulatingOnAlternateTicks() ) + { + serverTickMultiple = 2; + } + + if ( GetPredictable() || IsClientCreated() ) + { + return TICK_INTERVAL * serverTickMultiple; + } + + // Always fully interpolate during multi-player or during demo playback... + if ( ( gpGlobals->maxClients > 1 ) || + engine->IsPlayingDemo() ) + { + return AdjustInterpolationAmount( this, TICKS_TO_TIME ( TIME_TO_TICKS( GetClientInterpAmount() ) + serverTickMultiple ) ); + } + + int expandedServerTickMultiple = serverTickMultiple; + if ( IsEngineThreaded() ) + { + expandedServerTickMultiple += cl_interp_threadmodeticks.GetInt(); + } + + if ( IsAnimatedEveryTick() && IsSimulatedEveryTick() ) + { + return TICK_INTERVAL * expandedServerTickMultiple; + } + + if ( ( flags & LATCH_ANIMATION_VAR ) && IsAnimatedEveryTick() ) + { + return TICK_INTERVAL * expandedServerTickMultiple; + } + if ( ( flags & LATCH_SIMULATION_VAR ) && IsSimulatedEveryTick() ) + { + return TICK_INTERVAL * expandedServerTickMultiple; + } + + return AdjustInterpolationAmount( this, TICK_INTERVAL * ( TIME_TO_TICKS( GetClientInterpAmount() ) + serverTickMultiple ) ); +} + + +float C_BaseEntity::GetLastChangeTime( int flags ) +{ + if ( GetPredictable() || IsClientCreated() ) + { + return gpGlobals->curtime; + } + + // make sure not both flags are set, we can't resolve that + Assert( !( (flags & LATCH_ANIMATION_VAR) && (flags & LATCH_SIMULATION_VAR) ) ); + + if ( flags & LATCH_ANIMATION_VAR ) + { + return GetAnimTime(); + } + + if ( flags & LATCH_SIMULATION_VAR ) + { + float st = GetSimulationTime(); + if ( st == 0.0f ) + { + return gpGlobals->curtime; + } + return st; + } + + Assert( 0 ); + + return gpGlobals->curtime; +} + +const Vector& C_BaseEntity::GetPrevLocalOrigin() const +{ + return m_iv_vecOrigin.GetPrev(); +} + +const QAngle& C_BaseEntity::GetPrevLocalAngles() const +{ + return m_iv_angRotation.GetPrev(); +} + +//----------------------------------------------------------------------------- +// Simply here for game shared +//----------------------------------------------------------------------------- +bool C_BaseEntity::IsFloating() +{ + // NOTE: This is only here because it's called by game shared. + // The server uses it to lower falling impact damage + return false; +} + + +BEGIN_DATADESC_NO_BASE( C_BaseEntity ) + DEFINE_FIELD( m_ModelName, FIELD_STRING ), + DEFINE_FIELD( m_vecAbsOrigin, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ), + DEFINE_ARRAY( m_rgflCoordinateFrame, FIELD_FLOAT, 12 ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore) + DEFINE_FIELD( m_fFlags, FIELD_INTEGER ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseEntity::ShouldSavePhysics() +{ + return false; +} + +//----------------------------------------------------------------------------- +// handler to do stuff before you are saved +//----------------------------------------------------------------------------- +void C_BaseEntity::OnSave() +{ + // Here, we must force recomputation of all abs data so it gets saved correctly + // We can't leave the dirty bits set because the loader can't cope with it. + CalcAbsolutePosition(); + CalcAbsoluteVelocity(); +} + + +//----------------------------------------------------------------------------- +// handler to do stuff after you are restored +//----------------------------------------------------------------------------- +void C_BaseEntity::OnRestore() +{ + InvalidatePhysicsRecursive( POSITION_CHANGED | ANGLES_CHANGED | VELOCITY_CHANGED ); + + UpdatePartitionListEntry(); + CollisionProp()->UpdatePartition(); + + UpdateVisibility(); +} + +//----------------------------------------------------------------------------- +// Purpose: Saves the current object out to disk, by iterating through the objects +// data description hierarchy +// Input : &save - save buffer which the class data is written to +// Output : int - 0 if the save failed, 1 on success +//----------------------------------------------------------------------------- +int C_BaseEntity::Save( ISave &save ) +{ + // loop through the data description list, saving each data desc block + int status = SaveDataDescBlock( save, GetDataDescMap() ); + + return status; +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively saves all the classes in an object, in reverse order (top down) +// Output : int 0 on failure, 1 on success +//----------------------------------------------------------------------------- +int C_BaseEntity::SaveDataDescBlock( ISave &save, datamap_t *dmap ) +{ + int nResult = save.WriteAll( this, dmap ); + return nResult; +} + +void C_BaseEntity::SetClassname( const char *className ) +{ + m_iClassname = MAKE_STRING( className ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Restores the current object from disk, by iterating through the objects +// data description hierarchy +// Input : &restore - restore buffer which the class data is read from +// Output : int - 0 if the restore failed, 1 on success +//----------------------------------------------------------------------------- +int C_BaseEntity::Restore( IRestore &restore ) +{ + // loops through the data description list, restoring each data desc block in order + int status = RestoreDataDescBlock( restore, GetDataDescMap() ); + + // NOTE: Do *not* use GetAbsOrigin() here because it will + // try to recompute m_rgflCoordinateFrame! + MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame ); + + // Restablish ground entity + if ( m_hGroundEntity != NULL ) + { + m_hGroundEntity->AddEntityToGroundList( this ); + } + + return status; +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively restores all the classes in an object, in reverse order (top down) +// Output : int 0 on failure, 1 on success +//----------------------------------------------------------------------------- +int C_BaseEntity::RestoreDataDescBlock( IRestore &restore, datamap_t *dmap ) +{ + return restore.ReadAll( this, dmap ); +} + +//----------------------------------------------------------------------------- +// capabilities +//----------------------------------------------------------------------------- +int C_BaseEntity::ObjectCaps( void ) +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : C_AI_BaseNPC +//----------------------------------------------------------------------------- +C_AI_BaseNPC *C_BaseEntity::MyNPCPointer( void ) +{ + if ( IsNPC() ) + { + return assert_cast(this); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: For each client (only can be local client in client .dll ) checks the client has disabled CC and if so, removes them from +// the recipient list. +// Input : filter - +//----------------------------------------------------------------------------- +void C_BaseEntity::RemoveRecipientsIfNotCloseCaptioning( C_RecipientFilter& filter ) +{ + extern ConVar closecaption; + if ( !closecaption.GetBool() ) + { + filter.Reset(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : recording - +// Output : inline void +//----------------------------------------------------------------------------- +void C_BaseEntity::EnableInToolView( bool bEnable ) +{ +#ifndef NO_TOOLFRAMEWORK + m_bEnabledInToolView = bEnable; + UpdateVisibility(); +#endif +} + +void C_BaseEntity::SetToolRecording( bool recording ) +{ +#ifndef NO_TOOLFRAMEWORK + m_bToolRecording = recording; + if ( m_bToolRecording ) + { + recordinglist->AddToList( GetClientHandle() ); + } + else + { + recordinglist->RemoveFromList( GetClientHandle() ); + } +#endif +} + +bool C_BaseEntity::HasRecordedThisFrame() const +{ +#ifndef NO_TOOLFRAMEWORK + Assert( m_nLastRecordedFrame <= gpGlobals->framecount ); + return m_nLastRecordedFrame == gpGlobals->framecount; +#else + return false; +#endif +} + +void C_BaseEntity::GetToolRecordingState( KeyValues *msg ) +{ + Assert( ToolsEnabled() ); + if ( !ToolsEnabled() ) + return; + + VPROF_BUDGET( "C_BaseEntity::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); + + C_BaseEntity *pOwner = m_hOwnerEntity; + + static BaseEntityRecordingState_t state; + state.m_flTime = gpGlobals->curtime; + state.m_pModelName = modelinfo->GetModelName( GetModel() ); + state.m_nOwner = pOwner ? pOwner->entindex() : -1; + state.m_nEffects = m_fEffects; + state.m_bVisible = ShouldDraw() && !IsDormant(); + state.m_bRecordFinalVisibleSample = false; + state.m_vecRenderOrigin = GetRenderOrigin(); + state.m_vecRenderAngles = GetRenderAngles(); + + // use EF_NOINTERP if the owner or a hierarchical parent has NO_INTERP + if ( pOwner && pOwner->IsEffectActive( EF_NOINTERP ) ) + { + state.m_nEffects |= EF_NOINTERP; + } + C_BaseEntity *pParent = GetMoveParent(); + while ( pParent ) + { + if ( pParent->IsEffectActive( EF_NOINTERP ) ) + { + state.m_nEffects |= EF_NOINTERP; + break; + } + + pParent = pParent->GetMoveParent(); + } + + msg->SetPtr( "baseentity", &state ); +} + +void C_BaseEntity::CleanupToolRecordingState( KeyValues *msg ) +{ +} + +void C_BaseEntity::RecordToolMessage() +{ + Assert( IsToolRecording() ); + if ( !IsToolRecording() ) + return; + + if ( HasRecordedThisFrame() ) + return; + + KeyValues *msg = new KeyValues( "entity_state" ); + + // Post a message back to all IToolSystems + GetToolRecordingState( msg ); + Assert( (int)GetToolHandle() != 0 ); + ToolFramework_PostToolMessage( GetToolHandle(), msg ); + CleanupToolRecordingState( msg ); + + msg->deleteThis(); + + m_nLastRecordedFrame = gpGlobals->framecount; +} + +// (static function) +void C_BaseEntity::ToolRecordEntities() +{ + VPROF_BUDGET( "C_BaseEntity::ToolRecordEnties", VPROF_BUDGETGROUP_TOOLS ); + + if ( !ToolsEnabled() || !clienttools->IsInRecordingMode() ) + return; + + // Let non-dormant client created predictables get added, too + int c = recordinglist->Count(); + for ( int i = 0 ; i < c ; i++ ) + { + IClientRenderable *pRenderable = recordinglist->Get( i ); + if ( !pRenderable ) + continue; + + pRenderable->RecordToolMessage(); + } +} + + +void C_BaseEntity::AddToInterpolationList() +{ + if ( m_InterpolationListEntry == 0xFFFF ) + m_InterpolationListEntry = g_InterpolationList.AddToTail( this ); +} + + +void C_BaseEntity::RemoveFromInterpolationList() +{ + if ( m_InterpolationListEntry != 0xFFFF ) + { + g_InterpolationList.Remove( m_InterpolationListEntry ); + m_InterpolationListEntry = 0xFFFF; + } +} + + +void C_BaseEntity::AddToTeleportList() +{ + if ( m_TeleportListEntry == 0xFFFF ) + m_TeleportListEntry = g_TeleportList.AddToTail( this ); +} + + +void C_BaseEntity::RemoveFromTeleportList() +{ + if ( m_TeleportListEntry != 0xFFFF ) + { + g_TeleportList.Remove( m_TeleportListEntry ); + m_TeleportListEntry = 0xFFFF; + } +} + + +void C_BaseEntity::AddVar( void *data, IInterpolatedVar *watcher, int type, bool bSetup ) +{ + // Only add it if it hasn't been added yet. + bool bAddIt = true; + for ( int i=0; i < m_VarMap.m_Entries.Count(); i++ ) + { + if ( m_VarMap.m_Entries[i].watcher == watcher ) + { + if ( (type & EXCLUDE_AUTO_INTERPOLATE) != (watcher->GetType() & EXCLUDE_AUTO_INTERPOLATE) ) + { + // Its interpolation mode changed, so get rid of it and re-add it. + RemoveVar( m_VarMap.m_Entries[i].data, true ); + } + else + { + // They're adding something that's already there. No need to re-add it. + bAddIt = false; + } + + break; + } + } + + if ( bAddIt ) + { + // watchers must have a debug name set + Assert( watcher->GetDebugName() != NULL ); + + VarMapEntry_t map; + map.data = data; + map.watcher = watcher; + map.type = type; + map.m_bNeedsToInterpolate = true; + if ( type & EXCLUDE_AUTO_INTERPOLATE ) + { + m_VarMap.m_Entries.AddToTail( map ); + } + else + { + m_VarMap.m_Entries.AddToHead( map ); + ++m_VarMap.m_nInterpolatedEntries; + } + } + + if ( bSetup ) + { + watcher->Setup( data, type ); + watcher->SetInterpolationAmount( GetInterpolationAmount( watcher->GetType() ) ); + } +} + + +void C_BaseEntity::RemoveVar( void *data, bool bAssert ) +{ + for ( int i=0; i < m_VarMap.m_Entries.Count(); i++ ) + { + if ( m_VarMap.m_Entries[i].data == data ) + { + if ( !( m_VarMap.m_Entries[i].type & EXCLUDE_AUTO_INTERPOLATE ) ) + --m_VarMap.m_nInterpolatedEntries; + + m_VarMap.m_Entries.Remove( i ); + return; + } + } + if ( bAssert ) + { + Assert( !"RemoveVar" ); + } +} + +void C_BaseEntity::CheckCLInterpChanged() +{ + float flCurValue_Interp = GetClientInterpAmount(); + static float flLastValue_Interp = flCurValue_Interp; + + float flCurValue_InterpNPCs = cl_interp_npcs.GetFloat(); + static float flLastValue_InterpNPCs = flCurValue_InterpNPCs; + + if ( flLastValue_Interp != flCurValue_Interp || + flLastValue_InterpNPCs != flCurValue_InterpNPCs ) + { + flLastValue_Interp = flCurValue_Interp; + flLastValue_InterpNPCs = flCurValue_InterpNPCs; + + // Tell all the existing entities to update their interpolation amounts to account for the change. + C_BaseEntityIterator iterator; + C_BaseEntity *pEnt; + while ( (pEnt = iterator.Next()) != NULL ) + { + pEnt->Interp_UpdateInterpolationAmounts( pEnt->GetVarMapping() ); + } + } +} + +void C_BaseEntity::DontRecordInTools() +{ +#ifndef NO_TOOLFRAMEWORK + m_bRecordInTools = false; +#endif +} + +int C_BaseEntity::GetCreationTick() const +{ + return m_nCreationTick; +} + +//------------------------------------------------------------------------------ +void CC_CL_Find_Ent( const CCommand& args ) +{ + if ( args.ArgC() < 2 ) + { + Msg( "Format: cl_find_ent \n" ); + return; + } + + int iCount = 0; + const char *pszSubString = args[1]; + Msg("Searching for client entities with classname containing substring: '%s'\n", pszSubString ); + + C_BaseEntity *ent = NULL; + while ( (ent = ClientEntityList().NextBaseEntity(ent)) != NULL ) + { + const char *pszClassname = ent->GetClassname(); + + bool bMatches = false; + if ( pszClassname && pszClassname[0] ) + { + if ( Q_stristr( pszClassname, pszSubString ) ) + { + bMatches = true; + } + } + + if ( bMatches ) + { + iCount++; + Msg(" '%s' (entindex %d) %s \n", pszClassname ? pszClassname : "[NO NAME]", ent->entindex(), ent->IsDormant() ? "(DORMANT)" : "" ); + } + } + + Msg("Found %d matches.\n", iCount); +} +static ConCommand cl_find_ent("cl_find_ent", CC_CL_Find_Ent, "Find and list all client entities with classnames that contain the specified substring.\nFormat: cl_find_ent \n", FCVAR_CHEAT); + +//------------------------------------------------------------------------------ +void CC_CL_Find_Ent_Index( const CCommand& args ) +{ + if ( args.ArgC() < 2 ) + { + Msg( "Format: cl_find_ent_index \n" ); + return; + } + + int iIndex = atoi(args[1]); + C_BaseEntity *ent = ClientEntityList().GetBaseEntity( iIndex ); + if ( ent ) + { + const char *pszClassname = ent->GetClassname(); + Msg(" '%s' (entindex %d) %s \n", pszClassname ? pszClassname : "[NO NAME]", iIndex, ent->IsDormant() ? "(DORMANT)" : "" ); + } + else + { + Msg("Found no entity at %d.\n", iIndex); + } +} +static ConCommand cl_find_ent_index("cl_find_ent_index", CC_CL_Find_Ent_Index, "Display data for clientside entity matching specified index.\nFormat: cl_find_ent_index \n", FCVAR_CHEAT); diff --git a/game/client/c_baseentity.h b/game/client/c_baseentity.h new file mode 100644 index 00000000..d3a1aef1 --- /dev/null +++ b/game/client/c_baseentity.h @@ -0,0 +1,2096 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: A base class for the client-side representation of entities. +// +// This class encompasses both entities that are created on the server +// and networked to the client AND entities that are created on the +// client. +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef C_BASEENTITY_H +#define C_BASEENTITY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mathlib/vector.h" +#include "IClientEntityInternal.h" +#include "engine/IVModelRender.h" +#include "client_class.h" +#include "IClientShadowMgr.h" +#include "ehandle.h" +#include "iclientunknown.h" +#include "client_thinklist.h" +#if !defined( NO_ENTITY_PREDICTION ) +#include "predictableid.h" +#endif +#include "soundflags.h" +#include "shareddefs.h" +#include "networkvar.h" +#include "interpolatedvar.h" +#include "collisionproperty.h" +#include "particle_property.h" +#include "toolframework/itoolentity.h" +#include "tier0/threadtools.h" + +class C_Team; +class IPhysicsObject; +class IClientVehicle; +class CPredictionCopy; +class C_BasePlayer; +struct studiohdr_t; +class CStudioHdr; +class CDamageModifier; +class IRecipientFilter; +class CUserCmd; +struct solid_t; +class ISave; +class IRestore; +class C_BaseAnimating; +class C_AI_BaseNPC; +struct EmitSound_t; +class C_RecipientFilter; +class CTakeDamageInfo; +class C_BaseCombatCharacter; +class CEntityMapData; +class ConVar; + +struct CSoundParameters; + +typedef unsigned int AimEntsListHandle_t; + +#define INVALID_AIMENTS_LIST_HANDLE (AimEntsListHandle_t)~0 + +extern void RecvProxy_IntToColor32( const CRecvProxyData *pData, void *pStruct, void *pOut ); +extern void RecvProxy_LocalVelocity( const CRecvProxyData *pData, void *pStruct, void *pOut ); + +enum CollideType_t +{ + ENTITY_SHOULD_NOT_COLLIDE = 0, + ENTITY_SHOULD_COLLIDE, + ENTITY_SHOULD_RESPOND +}; + +class VarMapEntry_t +{ + + +public: + unsigned short type; + unsigned short m_bNeedsToInterpolate; // Set to false when this var doesn't + // need Interpolate() called on it anymore. + void *data; + IInterpolatedVar *watcher; +}; + +struct VarMapping_t +{ + VarMapping_t() + { + m_nInterpolatedEntries = 0; + } + + CUtlVector< VarMapEntry_t > m_Entries; + int m_nInterpolatedEntries; + float m_lastInterpolationTime; +}; + + + +#define DECLARE_INTERPOLATION + + +// How many data slots to use when in multiplayer. +#define MULTIPLAYER_BACKUP 90 + + +struct serialentity_t; + +typedef CHandle EHANDLE; // The client's version of EHANDLE. + +typedef void (C_BaseEntity::*BASEPTR)(void); +typedef void (C_BaseEntity::*ENTITYFUNCPTR)(C_BaseEntity *pOther ); + +// For entity creation on the client +typedef C_BaseEntity* (*DISPATCHFUNCTION)( void ); + +#include "touchlink.h" +#include "groundlink.h" + +#if !defined( NO_ENTITY_PREDICTION ) +//----------------------------------------------------------------------------- +// Purpose: For fully client side entities we use this information to determine +// authoritatively if the server has acknowledged creating this entity, etc. +//----------------------------------------------------------------------------- +struct PredictionContext +{ + PredictionContext() + { + m_bActive = false; + m_nCreationCommandNumber = -1; + m_pszCreationModule = NULL; + m_nCreationLineNumber = 0; + m_hServerEntity = NULL; + } + + // The command_number of the usercmd which created this entity + bool m_bActive; + int m_nCreationCommandNumber; + char const *m_pszCreationModule; + int m_nCreationLineNumber; + // The entity to whom we are attached + CHandle< C_BaseEntity > m_hServerEntity; +}; +#endif + +//----------------------------------------------------------------------------- +// Purpose: think contexts +//----------------------------------------------------------------------------- +struct thinkfunc_t +{ + BASEPTR m_pfnThink; + string_t m_iszContext; + int m_nNextThinkTick; + int m_nLastThinkTick; +}; + +#define CREATE_PREDICTED_ENTITY( className ) \ + C_BaseEntity::CreatePredictedEntityByName( className, __FILE__, __LINE__ ); + + + +// Entity flags that only exist on the client. +#define ENTCLIENTFLAG_GETTINGSHADOWRENDERBOUNDS 0x0001 // Tells us if we're getting the real ent render bounds or the shadow render bounds. +#define ENTCLIENTFLAG_DONTUSEIK 0x0002 // Don't use IK on this entity even if its model has IK. +#define ENTCLIENTFLAG_ALWAYS_INTERPOLATE 0x0004 // Used by view models. + + +//----------------------------------------------------------------------------- +// Purpose: Base client side entity object +//----------------------------------------------------------------------------- +class C_BaseEntity : public IClientEntity +{ +// Construction + DECLARE_CLASS_NOBASE( C_BaseEntity ); + + friend class CPrediction; + friend void cc_cl_interp_all_changed( IConVar *pConVar, const char *pOldString, float flOldValue ); + +public: + DECLARE_DATADESC(); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_BaseEntity(); + virtual ~C_BaseEntity(); + + static C_BaseEntity *CreatePredictedEntityByName( const char *classname, const char *module, int line, bool persist = false ); + + // FireBullets uses shared code for prediction. + virtual void FireBullets( const FireBulletsInfo_t &info ); + virtual bool ShouldDrawUnderwaterBulletBubbles(); + virtual bool ShouldDrawWaterImpacts( void ) { return true; } + virtual bool HandleShotImpactingWater( const FireBulletsInfo_t &info, + const Vector &vecEnd, ITraceFilter *pTraceFilter, Vector *pVecTracerDest ); + virtual ITraceFilter* GetBeamTraceFilter( void ); + virtual void DispatchTraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); + virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); + virtual void DoImpactEffect( trace_t &tr, int nDamageType ); + virtual void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); + virtual int GetTracerAttachment( void ); + void ComputeTracerStartPosition( const Vector &vecShotSrc, Vector *pVecTracerStart ); + void TraceBleed( float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType ); + virtual int BloodColor(); + virtual const char* GetTracerType(); + + virtual void Spawn( void ); + virtual void SpawnClientEntity( void ); + virtual void Precache( void ); + virtual void Activate(); + + virtual void ParseMapData( CEntityMapData *mapData ); + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + virtual bool KeyValue( const char *szKeyName, float flValue ); + virtual bool KeyValue( const char *szKeyName, const Vector &vecValue ); + virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + + // Entities block Line-Of-Sight for NPCs by default. + // Set this to false if you want to change this behavior. + void SetBlocksLOS( bool bBlocksLOS ); + bool BlocksLOS( void ); + void SetAIWalkable( bool bBlocksLOS ); + bool IsAIWalkable( void ); + + + void Interp_SetupMappings( VarMapping_t *map ); + + // Returns 1 if there are no more changes (ie: we could call RemoveFromInterpolationList). + int Interp_Interpolate( VarMapping_t *map, float currentTime ); + + void Interp_RestoreToLastNetworked( VarMapping_t *map ); + void Interp_UpdateInterpolationAmounts( VarMapping_t *map ); + void Interp_HierarchyUpdateInterpolationAmounts(); + + // Called by the CLIENTCLASS macros. + virtual bool Init( int entnum, int iSerialNum ); + + // Called in the destructor to shutdown everything. + void Term(); + + // memory handling, uses calloc so members are zero'd out on instantiation + void *operator new( size_t stAllocateBlock ); + void *operator new[]( size_t stAllocateBlock ); + void *operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ); + void *operator new[]( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ); + void operator delete( void *pMem ); + void operator delete( void *pMem, int nBlockUse, const char *pFileName, int nLine ) { operator delete( pMem ); } + + // This just picks one of the routes to IClientUnknown. + IClientUnknown* GetIClientUnknown() { return this; } + virtual C_BaseAnimating* GetBaseAnimating() { return NULL; } + virtual void SetClassname( const char *className ); + + string_t m_iClassname; + +// IClientUnknown overrides. +public: + + virtual void SetRefEHandle( const CBaseHandle &handle ); + virtual const CBaseHandle& GetRefEHandle() const; + + void SetToolHandle( HTOOLHANDLE handle ); + HTOOLHANDLE GetToolHandle() const; + + void EnableInToolView( bool bEnable ); + bool IsEnabledInToolView() const; + + void SetToolRecording( bool recording ); + bool IsToolRecording() const; + bool HasRecordedThisFrame() const; + virtual void RecordToolMessage(); + + void DontRecordInTools(); + bool ShouldRecordInTools() const; + + virtual void Release(); + virtual ICollideable* GetCollideable() { return &m_Collision; } + virtual IClientNetworkable* GetClientNetworkable() { return this; } + virtual IClientRenderable* GetClientRenderable() { return this; } + virtual IClientEntity* GetIClientEntity() { return this; } + virtual C_BaseEntity* GetBaseEntity() { return this; } + virtual IClientThinkable* GetClientThinkable() { return this; } + + +// Methods of IClientRenderable +public: + + virtual const Vector& GetRenderOrigin( void ); + virtual const QAngle& GetRenderAngles( void ); + virtual Vector GetObserverCamOrigin( void ) { return GetRenderOrigin(); } // Return the origin for player observers tracking this target + virtual const matrix3x4_t & RenderableToWorldTransform(); + virtual bool IsTransparent( void ); + virtual bool IsTwoPass( void ); + virtual bool UsesPowerOfTwoFrameBufferTexture(); + virtual bool UsesFullFrameBufferTexture(); + virtual const model_t *GetModel( void ) const; + virtual int DrawModel( int flags ); + virtual void ComputeFxBlend( void ); + virtual int GetFxBlend( void ); + virtual bool LODTest() { return true; } // NOTE: UNUSED + virtual void GetRenderBounds( Vector& mins, Vector& maxs ); + virtual IPVSNotify* GetPVSNotifyInterface(); + virtual void GetRenderBoundsWorldspace( Vector& absMins, Vector& absMaxs ); + + virtual void GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType ); + + // Determine the color modulation amount + virtual void GetColorModulation( float* color ); + + virtual void OnThreadedDrawSetup() {} +public: + virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); + virtual bool TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); + + // To mimic server call convention + C_BaseEntity *GetOwnerEntity( void ) const; + void SetOwnerEntity( C_BaseEntity *pOwner ); + + C_BaseEntity *GetEffectEntity( void ) const; + void SetEffectEntity( C_BaseEntity *pEffectEnt ); + + // This function returns a value that scales all damage done by this entity. + // Use CDamageModifier to hook in damage modifiers on a guy. + virtual float GetAttackDamageScale( void ); + +// IClientNetworkable implementation. +public: + virtual void NotifyShouldTransmit( ShouldTransmitState_t state ); + + // save out interpolated values + virtual void PreDataUpdate( DataUpdateType_t updateType ); + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void ValidateModelIndex( void ); + + // pvs info. NOTE: Do not override these!! + virtual void SetDormant( bool bDormant ); + virtual bool IsDormant( void ); + + // Tells the entity that it's about to be destroyed due to the client receiving + // an uncompressed update that's caused it to destroy all entities & recreate them. + virtual void SetDestroyedOnRecreateEntities( void ); + + virtual int GetEFlags() const; + virtual void SetEFlags( int iEFlags ); + void AddEFlags( int nEFlagMask ); + void RemoveEFlags( int nEFlagMask ); + bool IsEFlagSet( int nEFlagMask ) const; + + // checks to see if the entity is marked for deletion + bool IsMarkedForDeletion( void ); + + virtual int entindex( void ) const; + + // This works for client-only entities and returns the GetEntryIndex() of the entity's handle, + // so the sound system can get an IClientEntity from it. + int GetSoundSourceIndex() const; + + // Server to client message received + virtual void ReceiveMessage( int classID, bf_read &msg ); + + virtual void* GetDataTableBasePtr(); + +// IClientThinkable. +public: + // Called whenever you registered for a think message (with SetNextClientThink). + virtual void ClientThink(); + + virtual ClientThinkHandle_t GetThinkHandle(); + virtual void SetThinkHandle( ClientThinkHandle_t hThink ); + + +public: + + void AddVar( void *data, IInterpolatedVar *watcher, int type, bool bSetup=false ); + void RemoveVar( void *data, bool bAssert=true ); + VarMapping_t* GetVarMapping(); + + VarMapping_t m_VarMap; + + +public: + // An inline version the game code can use + CCollisionProperty *CollisionProp(); + const CCollisionProperty*CollisionProp() const; + CParticleProperty *ParticleProp(); + const CParticleProperty *ParticleProp() const; + + // Simply here for game shared + bool IsFloating(); + + virtual bool ShouldSavePhysics(); + +// save/restore stuff + virtual void OnSave(); + virtual void OnRestore(); + // capabilities for save/restore + virtual int ObjectCaps( void ); + // only overload these if you have special data to serialize + virtual int Save( ISave &save ); + virtual int Restore( IRestore &restore ); + +private: + + int SaveDataDescBlock( ISave &save, datamap_t *dmap ); + int RestoreDataDescBlock( IRestore &restore, datamap_t *dmap ); + + // Called after restoring data into prediction slots. This function is used in place of proxies + // on the variables, so if some variable like m_nModelIndex needs to update other state (like + // the model pointer), it is done here. + void OnPostRestoreData(); + +public: + + // Called after spawn, and in the case of self-managing objects, after load + virtual bool CreateVPhysics(); + + // Convenience routines to init the vphysics simulation for this object. + // This creates a static object. Something that behaves like world geometry - solid, but never moves + IPhysicsObject *VPhysicsInitStatic( void ); + + // This creates a normal vphysics simulated object + IPhysicsObject *VPhysicsInitNormal( SolidType_t solidType, int nSolidFlags, bool createAsleep, solid_t *pSolid = NULL ); + + // This creates a vphysics object with a shadow controller that follows the AI + // Move the object to where it should be and call UpdatePhysicsShadowToCurrentPosition() + IPhysicsObject *VPhysicsInitShadow( bool allowPhysicsMovement, bool allowPhysicsRotation, solid_t *pSolid = NULL ); + +private: + // called by all vphysics inits + bool VPhysicsInitSetup(); +public: + + void VPhysicsSetObject( IPhysicsObject *pPhysics ); + // destroy and remove the physics object for this entity + virtual void VPhysicsDestroyObject( void ); + + // Purpose: My physics object has been updated, react or extract data + virtual void VPhysicsUpdate( IPhysicsObject *pPhysics ); + inline IPhysicsObject *VPhysicsGetObject( void ) const { return m_pPhysicsObject; } + virtual int VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ); + virtual bool VPhysicsIsFlesh( void ); + +// IClientEntity implementation. +public: + virtual bool SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ); + virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ); + virtual bool UsesFlexDelayedWeights() { return false; } + virtual void DoAnimationEvents( void ); + + // Add entity to visible entities list? + virtual void AddEntity( void ); + + virtual const Vector& GetAbsOrigin( void ) const; + virtual const QAngle& GetAbsAngles( void ) const; + + const Vector& GetNetworkOrigin() const; + const QAngle& GetNetworkAngles() const; + + void SetNetworkOrigin( const Vector& org ); + void SetNetworkAngles( const QAngle& ang ); + + const Vector& GetLocalOrigin( void ) const; + void SetLocalOrigin( const Vector& origin ); + vec_t GetLocalOriginDim( int iDim ) const; // You can use the X_INDEX, Y_INDEX, and Z_INDEX defines here. + void SetLocalOriginDim( int iDim, vec_t flValue ); + + const QAngle& GetLocalAngles( void ) const; + void SetLocalAngles( const QAngle& angles ); + vec_t GetLocalAnglesDim( int iDim ) const; // You can use the X_INDEX, Y_INDEX, and Z_INDEX defines here. + void SetLocalAnglesDim( int iDim, vec_t flValue ); + + virtual const Vector& GetPrevLocalOrigin() const; + virtual const QAngle& GetPrevLocalAngles() const; + + void SetLocalTransform( const matrix3x4_t &localTransform ); + + void SetModelName( string_t name ); + string_t GetModelName( void ) const; + + int GetModelIndex( void ) const; + void SetModelIndex( int index ); + + // These methods return a *world-aligned* box relative to the absorigin of the entity. + // This is used for collision purposes and is *not* guaranteed + // to surround the entire entity's visual representation + // NOTE: It is illegal to ask for the world-aligned bounds for + // SOLID_BSP objects + virtual const Vector& WorldAlignMins( ) const; + virtual const Vector& WorldAlignMaxs( ) const; + + // This defines collision bounds *in whatever space is currently defined by the solid type* + // SOLID_BBOX: World Align + // SOLID_OBB: Entity space + // SOLID_BSP: Entity space + // SOLID_VPHYSICS Not used + void SetCollisionBounds( const Vector& mins, const Vector &maxs ); + + // NOTE: These use the collision OBB to compute a reasonable center point for the entity + virtual const Vector& WorldSpaceCenter( ) const; + + // FIXME: Do we want this? + const Vector& WorldAlignSize( ) const; + bool IsPointSized() const; + + // Returns a radius of a sphere + // *centered at the world space center* bounding the collision representation + // of the entity. NOTE: The world space center *may* move when the entity rotates. + float BoundingRadius() const; + + // Used when the collision prop is told to ask game code for the world-space surrounding box + virtual void ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ); + + // Returns the entity-to-world transform + matrix3x4_t &EntityToWorldTransform(); + const matrix3x4_t &EntityToWorldTransform() const; + + // Some helper methods that transform a point from entity space to world space + back + void EntityToWorldSpace( const Vector &in, Vector *pOut ) const; + void WorldToEntitySpace( const Vector &in, Vector *pOut ) const; + + // This function gets your parent's transform. If you're parented to an attachment, + // this calculates the attachment's transform and gives you that. + // + // You must pass in tempMatrix for scratch space - it may need to fill that in and return it instead of + // pointing you right at a variable in your parent. + matrix3x4_t& GetParentToWorldTransform( matrix3x4_t &tempMatrix ); + + void GetVectors(Vector* forward, Vector* right, Vector* up) const; + + // Sets abs angles, but also sets local angles to be appropriate + void SetAbsOrigin( const Vector& origin ); + void SetAbsAngles( const QAngle& angles ); + + void AddFlag( int flags ); + void RemoveFlag( int flagsToRemove ); + void ToggleFlag( int flagToToggle ); + int GetFlags( void ) const; + void ClearFlags(); + + MoveType_t GetMoveType( void ) const; + MoveCollide_t GetMoveCollide( void ) const; + virtual SolidType_t GetSolid( void ) const; + + virtual int GetSolidFlags( void ) const; + bool IsSolidFlagSet( int flagMask ) const; + void SetSolidFlags( int nFlags ); + void AddSolidFlags( int nFlags ); + void RemoveSolidFlags( int nFlags ); + bool IsSolid() const; + + virtual class CMouthInfo *GetMouth( void ); + + // Retrieve sound spatialization info for the specified sound on this entity + // Return false to indicate sound is not audible + virtual bool GetSoundSpatialization( SpatializationInfo_t& info ); + + // Attachments + virtual int LookupAttachment( const char *pAttachmentName ) { return -1; } + virtual bool GetAttachment( int number, matrix3x4_t &matrix ); + virtual bool GetAttachment( int number, Vector &origin ); + virtual bool GetAttachment( int number, Vector &origin, QAngle &angles ); + virtual bool GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ); + + // Team handling + virtual C_Team *GetTeam( void ); + virtual int GetTeamNumber( void ) const; + virtual void ChangeTeam( int iTeamNum ); // Assign this entity to a team. + virtual int GetRenderTeamNumber( void ); + virtual bool InSameTeam( C_BaseEntity *pEntity ); // Returns true if the specified entity is on the same team as this one + virtual bool InLocalTeam( void ); + + // ID Target handling + virtual bool IsValidIDTarget( void ) { return false; } + virtual char *GetIDString( void ) { return ""; }; + + // See CSoundEmitterSystem + void EmitSound( const char *soundname, float soundtime = 0.0f, float *duration = NULL ); // Override for doing the general case of CPASAttenuationFilter( this ), and EmitSound( filter, entindex(), etc. ); + void EmitSound( const char *soundname, HSOUNDSCRIPTHANDLE& handle, float soundtime = 0.0f, float *duration = NULL ); // Override for doing the general case of CPASAttenuationFilter( this ), and EmitSound( filter, entindex(), etc. ); + void StopSound( const char *soundname ); + void StopSound( const char *soundname, HSOUNDSCRIPTHANDLE& handle ); + void GenderExpandString( char const *in, char *out, int maxlen ); + + static float GetSoundDuration( const char *soundname, char const *actormodel ); + + static bool GetParametersForSound( const char *soundname, CSoundParameters ¶ms, const char *actormodel ); + static bool GetParametersForSound( const char *soundname, HSOUNDSCRIPTHANDLE& handle, CSoundParameters ¶ms, const char *actormodel ); + + static void EmitSound( IRecipientFilter& filter, int iEntIndex, const char *soundname, const Vector *pOrigin = NULL, float soundtime = 0.0f, float *duration = NULL ); + static void EmitSound( IRecipientFilter& filter, int iEntIndex, const char *soundname, HSOUNDSCRIPTHANDLE& handle, const Vector *pOrigin = NULL, float soundtime = 0.0f, float *duration = NULL ); + static void StopSound( int iEntIndex, const char *soundname ); + static soundlevel_t LookupSoundLevel( const char *soundname ); + static soundlevel_t LookupSoundLevel( const char *soundname, HSOUNDSCRIPTHANDLE& handle ); + + static void EmitSound( IRecipientFilter& filter, int iEntIndex, const EmitSound_t & params ); + static void EmitSound( IRecipientFilter& filter, int iEntIndex, const EmitSound_t & params, HSOUNDSCRIPTHANDLE& handle ); + + static void StopSound( int iEntIndex, int iChannel, const char *pSample ); + + static void EmitAmbientSound( int entindex, const Vector& origin, const char *soundname, int flags = 0, float soundtime = 0.0f, float *duration = NULL ); + + // These files need to be listed in scripts/game_sounds_manifest.txt + static HSOUNDSCRIPTHANDLE PrecacheScriptSound( const char *soundname ); + static void PrefetchScriptSound( const char *soundname ); + + // For each client who appears to be a valid recipient, checks the client has disabled CC and if so, removes them from + // the recipient list. + static void RemoveRecipientsIfNotCloseCaptioning( C_RecipientFilter& filter ); + static void EmitCloseCaption( IRecipientFilter& filter, int entindex, char const *token, CUtlVector< Vector >& soundorigins, float duration, bool warnifmissing = false ); + + // Moves all aiments into their correct position for the frame + static void MarkAimEntsDirty(); + static void CalcAimEntPositions(); + + static bool IsPrecacheAllowed(); + static void SetAllowPrecache( bool allow ); + + static bool m_bAllowPrecache; + + static bool IsSimulatingOnAlternateTicks(); + +// C_BaseEntity local functions +public: + + void UpdatePartitionListEntry(); + + // This can be used to setup the entity as a client-only entity. + // Override this to perform per-entity clientside setup + virtual bool InitializeAsClientEntity( const char *pszModelName, RenderGroup_t renderGroup ); + + + + + + // This function gets called on all client entities once per simulation phase. + // It dispatches events like OnDataChanged(), and calls the legacy function AddEntity(). + virtual void Simulate(); + + + // This event is triggered during the simulation phase if an entity's data has changed. It is + // better to hook this instead of PostDataUpdate() because in PostDataUpdate(), server entity origins + // are incorrect and attachment points can't be used. + virtual void OnDataChanged( DataUpdateType_t type ); + + // This is called once per frame before any data is read in from the server. + virtual void OnPreDataChanged( DataUpdateType_t type ); + + bool IsStandable() const; + bool IsBSPModel() const; + + + // If this is a vehicle, returns the vehicle interface + virtual IClientVehicle* GetClientVehicle() { return NULL; } + + // Returns the aiment render origin + angles + virtual void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ); + + // get network origin from previous update + virtual const Vector& GetOldOrigin(); + + // Methods relating to traversing hierarchy + C_BaseEntity *GetMoveParent( void ) const; + C_BaseEntity *GetRootMoveParent(); + C_BaseEntity *FirstMoveChild( void ) const; + C_BaseEntity *NextMovePeer( void ) const; + + inline ClientEntityHandle_t GetClientHandle() const { return ClientEntityHandle_t( m_RefEHandle ); } + inline bool IsServerEntity( void ); + + virtual RenderGroup_t GetRenderGroup(); + + virtual void GetToolRecordingState( KeyValues *msg ); + virtual void CleanupToolRecordingState( KeyValues *msg ); + + // The value returned by here determines whether or not (and how) the entity + // is put into the spatial partition. + virtual CollideType_t GetCollideType( void ); + + virtual bool ShouldDraw(); + inline bool IsVisible() const { return m_hRender != INVALID_CLIENT_RENDER_HANDLE; } + void UpdateVisibility(); + + // Returns true if the entity changes its position every frame on the server but it doesn't + // set animtime. In that case, the client returns true here so it copies the server time to + // animtime in OnDataChanged and the position history is correct for interpolation. + virtual bool IsSelfAnimating(); + + // Set appropriate flags and store off data when these fields are about to change + virtual void OnLatchInterpolatedVariables( int flags ); + // For predictable entities, stores last networked value + void OnStoreLastNetworkedValue(); + + // Initialize things given a new model. + virtual CStudioHdr *OnNewModel(); + virtual void OnNewParticleEffect( const char *pszParticleName, CNewParticleEffect *pNewParticleEffect ); + + bool IsSimulatedEveryTick() const; + bool IsAnimatedEveryTick() const; + void SetSimulatedEveryTick( bool sim ); + void SetAnimatedEveryTick( bool anim ); + + void Interp_Reset( VarMapping_t *map ); + virtual void ResetLatched(); + + float GetInterpolationAmount( int flags ); + float GetLastChangeTime( int flags ); + + // Interpolate the position for rendering + virtual bool Interpolate( float currentTime ); + + // Did the object move so far that it shouldn't interpolate? + bool Teleported( void ); + // Is this a submodel of the world ( *1 etc. in name ) ( brush models only ) + virtual bool IsSubModel( void ); + // Deal with EF_* flags + virtual void CreateLightEffects( void ); + + void AddToAimEntsList(); + void RemoveFromAimEntsList(); + + // Reset internal fields + virtual void Clear( void ); + // Helper to draw raw brush models + virtual int DrawBrushModel( bool bSort, bool bShadowDepth ); + + // returns the material animation start time + virtual float GetTextureAnimationStartTime(); + // Indicates that a texture animation has wrapped + virtual void TextureAnimationWrapped(); + + // Set the next think time. Pass in CLIENT_THINK_ALWAYS to have Think() called each frame. + virtual void SetNextClientThink( float nextThinkTime ); + + // anything that has health can override this... + virtual void SetHealth(int iHealth) {} + virtual int GetHealth() const { return 0; } + virtual int GetMaxHealth() const { return 1; } + + // Returns the health fraction + float HealthFraction() const; + + // Should this object cast shadows? + virtual ShadowType_t ShadowCastType(); + + // Should this object receive shadows? + virtual bool ShouldReceiveProjectedTextures( int flags ); + + // Shadow-related methods + virtual bool IsShadowDirty( ); + virtual void MarkShadowDirty( bool bDirty ); + virtual IClientRenderable *GetShadowParent(); + virtual IClientRenderable *FirstShadowChild(); + virtual IClientRenderable *NextShadowPeer(); + + // Sets up a render handle so the leaf system will draw this entity. + void AddToLeafSystem(); + void AddToLeafSystem( RenderGroup_t group ); + // remove entity form leaf system again + void RemoveFromLeafSystem(); + + // A method to apply a decal to an entity + virtual void AddDecal( const Vector& rayStart, const Vector& rayEnd, + const Vector& decalCenter, int hitbox, int decalIndex, bool doTrace, trace_t& tr, int maxLODToDecal = ADDDECAL_TO_ALL_LODS ); + // A method to remove all decals from an entity + void RemoveAllDecals( void ); + + // Is this a brush model? + bool IsBrushModel() const; + + // A random value 0-1 used by proxies to make sure they're not all in sync + float ProxyRandomValue() const { return m_flProxyRandomValue; } + + // The spawn time of this entity + float SpawnTime() const { return m_flSpawnTime; } + + virtual bool IsClientCreated( void ) const; + + virtual void UpdateOnRemove( void ); + + virtual void SUB_Remove( void ); + + // Prediction stuff + ///////////////// + void CheckInitPredictable( const char *context ); + + void AllocateIntermediateData( void ); + void DestroyIntermediateData( void ); + void ShiftIntermediateDataForward( int slots_to_remove, int previous_last_slot ); + + void *GetPredictedFrame( int framenumber ); + void *GetOriginalNetworkDataObject( void ); + bool IsIntermediateDataAllocated( void ) const; + + void InitPredictable( void ); + void ShutdownPredictable( void ); + + virtual void SetPredictable( bool state ); + bool GetPredictable( void ) const; + void PreEntityPacketReceived( int commands_acknowledged ); + void PostEntityPacketReceived( void ); + bool PostNetworkDataReceived( int commands_acknowledged ); + bool GetPredictionEligible( void ) const; + void SetPredictionEligible( bool canpredict ); + + enum + { + SLOT_ORIGINALDATA = -1, + }; + + int SaveData( const char *context, int slot, int type ); + virtual int RestoreData( const char *context, int slot, int type ); + + virtual char const * DamageDecal( int bitsDamageType, int gameMaterial ); + virtual void DecalTrace( trace_t *pTrace, char const *decalName ); + virtual void ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ); + + virtual bool ShouldPredict( void ) { return false; }; + // interface function pointers + void (C_BaseEntity::*m_pfnThink)(void); + virtual void Think( void ) + { + AssertMsg( m_pfnThink != &C_BaseEntity::Think, "Infinite recursion is infinitely bad." ); + + if ( m_pfnThink ) + { + ( this->*m_pfnThink )(); + } + } + + void PhysicsDispatchThink( BASEPTR thinkFunc ); + + // Toggle the visualization of the entity's abs/bbox + enum + { + VISUALIZE_COLLISION_BOUNDS = 0x1, + VISUALIZE_SURROUNDING_BOUNDS = 0x2, + VISUALIZE_RENDER_BOUNDS = 0x4, + }; + + void ToggleBBoxVisualization( int fVisFlags ); + void DrawBBoxVisualizations( void ); + +// Methods implemented on both client and server +public: + void SetSize( const Vector &vecMin, const Vector &vecMax ); // UTIL_SetSize( pev, mins, maxs ); + char const *GetClassname( void ); + char const *GetDebugName( void ); + static int PrecacheModel( const char *name ); + static bool PrecacheSound( const char *name ); + static void PrefetchSound( const char *name ); + void Remove( ); // UTIL_Remove( this ); + +public: + + // Returns the attachment point index on our parent that our transform is relative to. + // 0 if we're relative to the parent's absorigin and absangles. + unsigned char GetParentAttachment() const; + + // Externalized data objects ( see sharreddefs.h for DataObjectType_t ) + bool HasDataObjectType( int type ) const; + void AddDataObjectType( int type ); + void RemoveDataObjectType( int type ); + + void *GetDataObject( int type ); + void *CreateDataObject( int type ); + void DestroyDataObject( int type ); + void DestroyAllDataObjects( void ); + + // Determine approximate velocity based on updates from server + void EstimateAbsVelocity( Vector& vel ); + +#if !defined( NO_ENTITY_PREDICTION ) + // The player drives simulation of this entity + void SetPlayerSimulated( C_BasePlayer *pOwner ); + bool IsPlayerSimulated( void ) const; + CBasePlayer *GetSimulatingPlayer( void ); + void UnsetPlayerSimulated( void ); +#endif + + // Sorry folks, here lies TF2-specific stuff that really has no other place to go + virtual bool CanBePoweredUp( void ) { return false; } + virtual bool AttemptToPowerup( int iPowerup, float flTime, float flAmount = 0, C_BaseEntity *pAttacker = NULL, CDamageModifier *pDamageModifier = NULL ) { return false; } + + void SetCheckUntouch( bool check ); + bool GetCheckUntouch() const; + + virtual bool IsCurrentlyTouching( void ) const; + + virtual void StartTouch( C_BaseEntity *pOther ); + virtual void Touch( C_BaseEntity *pOther ); + virtual void EndTouch( C_BaseEntity *pOther ); + + void (C_BaseEntity ::*m_pfnTouch)( C_BaseEntity *pOther ); + + void PhysicsStep( void ); + +protected: + static bool sm_bDisableTouchFuncs; // Disables PhysicsTouch and PhysicsStartTouch function calls + +public: + touchlink_t *PhysicsMarkEntityAsTouched( C_BaseEntity *other ); + void PhysicsTouch( C_BaseEntity *pentOther ); + void PhysicsStartTouch( C_BaseEntity *pentOther ); + + // HACKHACK:Get the trace_t from the last physics touch call (replaces the even-hackier global trace vars) + static const trace_t &GetTouchTrace( void ); + + // FIXME: Should be private, but I can't make em private just yet + void PhysicsImpact( C_BaseEntity *other, trace_t &trace ); + void PhysicsMarkEntitiesAsTouching( C_BaseEntity *other, trace_t &trace ); + void PhysicsMarkEntitiesAsTouchingEventDriven( C_BaseEntity *other, trace_t &trace ); + + // Physics helper + static void PhysicsRemoveTouchedList( C_BaseEntity *ent ); + static void PhysicsNotifyOtherOfUntouch( C_BaseEntity *ent, C_BaseEntity *other ); + static void PhysicsRemoveToucher( C_BaseEntity *other, touchlink_t *link ); + + groundlink_t *AddEntityToGroundList( CBaseEntity *other ); + void PhysicsStartGroundContact( CBaseEntity *pentOther ); + + static void PhysicsNotifyOtherOfGroundRemoval( CBaseEntity *ent, CBaseEntity *other ); + static void PhysicsRemoveGround( CBaseEntity *other, groundlink_t *link ); + static void PhysicsRemoveGroundList( CBaseEntity *ent ); + + void StartGroundContact( CBaseEntity *ground ); + void EndGroundContact( CBaseEntity *ground ); + + void SetGroundChangeTime( float flTime ); + float GetGroundChangeTime( void ); + + // Remove this as ground entity for all object resting on this object + void WakeRestingObjects(); + bool HasNPCsOnIt(); + + bool PhysicsCheckWater( void ); + void PhysicsCheckVelocity( void ); + void PhysicsAddHalfGravity( float timestep ); + void PhysicsAddGravityMove( Vector &move ); + + virtual unsigned int PhysicsSolidMaskForEntity( void ) const; + + void SetGroundEntity( C_BaseEntity *ground ); + C_BaseEntity *GetGroundEntity( void ); + + void PhysicsPushEntity( const Vector& push, trace_t *pTrace ); + void PhysicsCheckWaterTransition( void ); + + // Performs the collision resolution for fliers. + void PerformFlyCollisionResolution( trace_t &trace, Vector &move ); + void ResolveFlyCollisionBounce( trace_t &trace, Vector &vecVelocity, float flMinTotalElasticity = 0.0f ); + void ResolveFlyCollisionSlide( trace_t &trace, Vector &vecVelocity ); + void ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity ); + + void PhysicsCheckForEntityUntouch( void ); + + // Creates the shadow (if it doesn't already exist) based on shadow cast type + void CreateShadow(); + + // Destroys the shadow; causes its type to be recomputed if the entity doesn't go away immediately. + void DestroyShadow(); + +protected: + // think function handling + enum thinkmethods_t + { + THINK_FIRE_ALL_FUNCTIONS, + THINK_FIRE_BASE_ONLY, + THINK_FIRE_ALL_BUT_BASE, + }; +public: + + // Unlinks from hierarchy + // Set the movement parent. Your local origin and angles will become relative to this parent. + // If iAttachment is a valid attachment on the parent, then your local origin and angles + // are relative to the attachment on this entity. + void SetParent( C_BaseEntity *pParentEntity, int iParentAttachment=0 ); + + bool PhysicsRunThink( thinkmethods_t thinkMethod = THINK_FIRE_ALL_FUNCTIONS ); + bool PhysicsRunSpecificThink( int nContextIndex, BASEPTR thinkFunc ); + + virtual void PhysicsSimulate( void ); + virtual bool IsAlive( void ); + + bool IsInWorld( void ) { return true; } + + ///////////////// + + virtual bool IsPlayer( void ) const { return false; }; + virtual bool IsBaseCombatCharacter( void ) { return false; }; + virtual C_BaseCombatCharacter *MyCombatCharacterPointer( void ) { return NULL; } + virtual bool IsNPC( void ) { return false; } + C_AI_BaseNPC *MyNPCPointer( void ); + // TF2 specific + virtual bool IsBaseObject( void ) const { return false; } + + virtual bool IsBaseTrain( void ) const { return false; } + + // Returns the eye point + angles (used for viewing + shooting) + virtual Vector EyePosition( void ); + virtual const QAngle& EyeAngles( void ); // Direction of eyes + virtual const QAngle& LocalEyeAngles( void ); // Direction of eyes in local space (pl.v_angle) + + // position of ears + virtual Vector EarPosition( void ); + + Vector EyePosition( void ) const; // position of eyes + const QAngle &EyeAngles( void ) const; // Direction of eyes in world space + const QAngle &LocalEyeAngles( void ) const; // Direction of eyes + Vector EarPosition( void ) const; // position of ears + + // Called by physics to see if we should avoid a collision test.... + virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const; + + // Sets physics parameters + void SetFriction( float flFriction ); + + void SetGravity( float flGravity ); + float GetGravity( void ) const; + + // Sets the model from a model index + void SetModelByIndex( int nModelIndex ); + + // Set model... (NOTE: Should only be used by client-only entities + // Returns false if the model name is bogus + bool SetModel( const char *pModelName ); + + void SetModelPointer( const model_t *pModel ); + + // Access movetype and solid. + void SetMoveType( MoveType_t val, MoveCollide_t moveCollide = MOVECOLLIDE_DEFAULT ); // Set to one of the MOVETYPE_ defines. + void SetMoveCollide( MoveCollide_t val ); // Set to one of the MOVECOLLIDE_ defines. + void SetSolid( SolidType_t val ); // Set to one of the SOLID_ defines. + + // NOTE: Setting the abs velocity in either space will cause a recomputation + // in the other space, so setting the abs velocity will also set the local vel + void SetLocalVelocity( const Vector &vecVelocity ); + void SetAbsVelocity( const Vector &vecVelocity ); + const Vector& GetLocalVelocity() const; + const Vector& GetAbsVelocity( ) const; + + void ApplyLocalVelocityImpulse( const Vector &vecImpulse ); + void ApplyAbsVelocityImpulse( const Vector &vecImpulse ); + void ApplyLocalAngularVelocityImpulse( const AngularImpulse &angImpulse ); + + // NOTE: Setting the abs velocity in either space will cause a recomputation + // in the other space, so setting the abs velocity will also set the local vel + void SetLocalAngularVelocity( const QAngle &vecAngVelocity ); + const QAngle& GetLocalAngularVelocity( ) const; + +// void SetAbsAngularVelocity( const QAngle &vecAngAbsVelocity ); +// const QAngle& GetAbsAngularVelocity( ) const; + + const Vector& GetBaseVelocity() const; + void SetBaseVelocity( const Vector& v ); + + const Vector& GetViewOffset() const; + void SetViewOffset( const Vector& v ); + + // Invalidates the abs state of all children + void InvalidatePhysicsRecursive( int nChangeFlags ); + + ClientRenderHandle_t GetRenderHandle() const; + + void SetRemovalFlag( bool bRemove ); + + // Effects... + bool IsEffectActive( int nEffectMask ) const; + void AddEffects( int nEffects ); + void RemoveEffects( int nEffects ); + int GetEffects( void ) const; + void ClearEffects( void ); + void SetEffects( int nEffects ); + + // Computes the abs position of a point specified in local space + void ComputeAbsPosition( const Vector &vecLocalPosition, Vector *pAbsPosition ); + + // Computes the abs position of a direction specified in local space + void ComputeAbsDirection( const Vector &vecLocalDirection, Vector *pAbsDirection ); + + // These methods encapsulate MOVETYPE_FOLLOW, which became obsolete + void FollowEntity( CBaseEntity *pBaseEntity, bool bBoneMerge = true ); + void StopFollowingEntity( ); // will also change to MOVETYPE_NONE + bool IsFollowingEntity(); + CBaseEntity *GetFollowedEntity(); + + // For shadows rendering the correct body + sequence... + virtual int GetBody() { return 0; } + virtual int GetSkin() { return 0; } + + // Stubs on client + void NetworkStateManualMode( bool activate ) { } + void NetworkStateChanged() { } + void NetworkStateChanged( void *pVar ) { } + void NetworkStateSetUpdateInterval( float N ) { } + void NetworkStateForceUpdate() { } + + // Think functions with contexts + int RegisterThinkContext( const char *szContext ); + BASEPTR ThinkSet( BASEPTR func, float flNextThinkTime = 0, const char *szContext = NULL ); + void SetNextThink( float nextThinkTime, const char *szContext = NULL ); + float GetNextThink( const char *szContext = NULL ); + float GetLastThink( const char *szContext = NULL ); + int GetNextThinkTick( const char *szContext = NULL ); + int GetLastThinkTick( const char *szContext = NULL ); + + // These set entity flags (EFL_*) to help optimize queries + void CheckHasThinkFunction( bool isThinkingHint = false ); + void CheckHasGamePhysicsSimulation(); + bool WillThink(); + bool WillSimulateGamePhysics(); + int GetFirstThinkTick(); // get first tick thinking on any context + + float GetAnimTime() const; + void SetAnimTime( float at ); + + float GetSimulationTime() const; + void SetSimulationTime( float st ); + + int GetCreationTick() const; + +#ifdef _DEBUG + void FunctionCheck( void *pFunction, const char *name ); + + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + //COMPILE_TIME_ASSERT( sizeof(func) == 4 ); + m_pfnTouch = func; + //FunctionCheck( *(reinterpret_cast(&m_pfnTouch)), name ); + return func; + } +#endif + + // Gets the model instance + shadow handle + virtual ModelInstanceHandle_t GetModelInstance() { return m_ModelInstance; } + void SetModelInstance( ModelInstanceHandle_t hInstance) { m_ModelInstance = hInstance; } + bool SnatchModelInstance( C_BaseEntity * pToEntity ); + virtual ClientShadowHandle_t GetShadowHandle() const { return m_ShadowHandle; } + virtual ClientRenderHandle_t& RenderHandle(); + + void CreateModelInstance(); + + // Sets the origin + angles to match the last position received + void MoveToLastReceivedPosition( bool force = false ); + +protected: + // Only meant to be called from subclasses + void DestroyModelInstance(); + + // Interpolate entity + static void ProcessTeleportList(); + static void ProcessInterpolatedList(); + static void CheckInterpolatedVarParanoidMeasurement(); + + // overrideable rules if an entity should interpolate + virtual bool ShouldInterpolate(); + + // Call this in OnDataChanged if you don't chain it down! + void MarkMessageReceived(); + + // Gets the last message time + float GetLastMessageTime() const { return m_flLastMessageTime; } + + // For non-players + int PhysicsClipVelocity (const Vector& in, const Vector& normal, Vector& out, float overbounce ); + + // Allow entities to perform client-side fades + virtual unsigned char GetClientSideFade() { return 255; } + +protected: + // Two part guts of Interpolate(). Shared with C_BaseAnimating. + enum + { + INTERPOLATE_STOP=0, + INTERPOLATE_CONTINUE + }; + + // Returns INTERPOLATE_STOP or INTERPOLATE_CONTINUE. + // bNoMoreChanges is set to 1 if you can call RemoveFromInterpolationList on the entity. + int BaseInterpolatePart1( float ¤tTime, Vector &oldOrigin, QAngle &oldAngles, int &bNoMoreChanges ); + void BaseInterpolatePart2( Vector &oldOrigin, QAngle &oldAngles, int nChangeFlags ); + + +public: + // Accessors for above + static int GetPredictionRandomSeed( void ); + static void SetPredictionRandomSeed( const CUserCmd *cmd ); + static C_BasePlayer *GetPredictionPlayer( void ); + static void SetPredictionPlayer( C_BasePlayer *player ); + static void CheckCLInterpChanged(); + + // Collision group accessors + int GetCollisionGroup() const; + void SetCollisionGroup( int collisionGroup ); + void CollisionRulesChanged(); + + static C_BaseEntity *Instance( int iEnt ); + // Doesn't do much, but helps with trace results + static C_BaseEntity *Instance( IClientEntity *ent ); + static C_BaseEntity *Instance( CBaseHandle hEnt ); + // For debugging shared code + static bool IsServer( void ); + static bool IsClient( void ); + static char const *GetDLLType( void ); + static void SetAbsQueriesValid( bool bValid ); + static bool IsAbsQueriesValid( void ); + + // Enable/disable abs recomputations on a stack. + static void PushEnableAbsRecomputations( bool bEnable ); + static void PopEnableAbsRecomputations(); + + // This requires the abs recomputation stack to be empty and just sets the global state. + // It should only be used at the scope of the frame loop. + static void EnableAbsRecomputations( bool bEnable ); + + static bool IsAbsRecomputationsEnabled( void ); + + + // Bloat the culling bbox past the parent ent's bbox in local space if EF_BONEMERGE_FASTCULL is set. + virtual void BoneMergeFastCullBloat( Vector &localMins, Vector &localMaxs, const Vector &thisEntityMins, const Vector &thisEntityMaxs ) const; + + + // Accessors for color. + const color32 GetRenderColor() const; + void SetRenderColor( byte r, byte g, byte b ); + void SetRenderColor( byte r, byte g, byte b, byte a ); + void SetRenderColorR( byte r ); + void SetRenderColorG( byte g ); + void SetRenderColorB( byte b ); + void SetRenderColorA( byte a ); + + void SetRenderMode( RenderMode_t nRenderMode, bool bForceUpdate = false ); + RenderMode_t GetRenderMode() const; + +public: + + // Determine what entity this corresponds to + int index; + + // Render information + unsigned char m_nRenderFX; + unsigned char m_nRenderFXBlend; + + // Entity flags that are only for the client (ENTCLIENTFLAG_ defines). + unsigned short m_EntClientFlags; + + CNetworkColor32( m_clrRender ); + +private: + + // Model for rendering + const model_t *model; + + +public: + // Time animation sequence or frame was last changed + float m_flAnimTime; + float m_flOldAnimTime; + + float m_flSimulationTime; + float m_flOldSimulationTime; + + +private: + // Effects to apply + int m_fEffects; + unsigned char m_nRenderMode; + unsigned char m_nOldRenderMode; + +public: + // Used to store the state we were added to the BSP as, so it can + // reinsert the entity if the state changes. + ClientRenderHandle_t m_hRender; // link into spatial partition + + // Interpolation says don't draw yet + bool m_bReadyToDraw; + + // Should we be interpolating? + static bool IsInterpolationEnabled(); + + + // + int m_nNextThinkTick; + int m_nLastThinkTick; + + // Object model index + short m_nModelIndex; + + char m_takedamage; + char m_lifeState; + + int m_iHealth; + + // was pev->speed + float m_flSpeed; + + // Team Handling + int m_iTeamNum; + +#if !defined( NO_ENTITY_PREDICTION ) + // Certain entities (projectiles) can be created on the client + CPredictableId m_PredictableID; + PredictionContext *m_pPredictionContext; +#endif + + // used so we know when things are no longer touching + int touchStamp; + + // Called after predicted entity has been acknowledged so that no longer needed entity can + // be deleted + // Return true to force deletion right now, regardless of isbeingremoved + virtual bool OnPredictedEntityRemove( bool isbeingremoved, C_BaseEntity *predicted ); + + bool IsDormantPredictable( void ) const; + bool BecameDormantThisPacket( void ) const; + void SetDormantPredictable( bool dormant ); + + int GetWaterLevel() const; + void SetWaterLevel( int nLevel ); + int GetWaterType() const; + void SetWaterType( int nType ); + + float GetElasticity( void ) const; + + int GetTextureFrameIndex( void ); + void SetTextureFrameIndex( int iIndex ); + + virtual bool GetShadowCastDistance( float *pDist, ShadowType_t shadowType ) const; + virtual bool GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const; + virtual C_BaseEntity *GetShadowUseOtherEntity( void ) const; + virtual void SetShadowUseOtherEntity( C_BaseEntity *pEntity ); + + CInterpolatedVar< QAngle >& GetRotationInterpolator(); + CInterpolatedVar< Vector >& GetOriginInterpolator(); + virtual bool AddRagdollToFadeQueue( void ) { return true; } + + // Dirty bits + void MarkRenderHandleDirty(); + + // used by SourceTV since move-parents may be missing when child spawns. + void HierarchyUpdateMoveParent(); + +protected: + int m_nFXComputeFrame; + + // FIXME: Should I move the functions handling these out of C_ClientEntity + // and into C_BaseEntity? Then we could make these private. + // Client handle + CBaseHandle m_RefEHandle; // Reference ehandle. Used to generate ehandles off this entity. + +private: + // Set by tools if this entity should route "info" to various tools listening to HTOOLENTITIES +#ifndef NO_TOOLFRAMEWORK + bool m_bEnabledInToolView; + bool m_bToolRecording; + HTOOLHANDLE m_ToolHandle; + int m_nLastRecordedFrame; + bool m_bRecordInTools; // should this entity be recorded in the tools (we exclude some things like models for menus) +#endif + +protected: + // pointer to the entity's physics object (vphysics.dll) + IPhysicsObject *m_pPhysicsObject; + +#if !defined( NO_ENTITY_PREDICTION ) + bool m_bPredictionEligible; +#endif + + int m_nSimulationTick; + + // Think contexts + int GetIndexForThinkContext( const char *pszContext ); + CUtlVector< thinkfunc_t > m_aThinkFunctions; + int m_iCurrentThinkContext; + + // Object eye position + Vector m_vecViewOffset; + + // Allow studio models to tell us what their m_nBody value is + virtual int GetStudioBody( void ) { return 0; } + +private: + friend void OnRenderStart(); + + // This can be used to setup the entity as a client-only entity. It gets an entity handle, + // a render handle, and is put into the spatial partition. + bool InitializeAsClientEntityByIndex( int iIndex, RenderGroup_t renderGroup ); + + // Figure out the smoothly interpolated origin for all server entities. Happens right before + // letting all entities simulate. + static void InterpolateServerEntities(); + + // Check which entities want to be drawn and add them to the leaf system. + static void AddVisibleEntities(); + + // For entities marked for recording, post bone messages to IToolSystems + static void ToolRecordEntities(); + + // Computes the base velocity + void UpdateBaseVelocity( void ); + + // Physics-related private methods + void PhysicsPusher( void ); + void PhysicsNone( void ); + void PhysicsNoclip( void ); + void PhysicsParent( void ); + void PhysicsStepRunTimestep( float timestep ); + void PhysicsToss( void ); + void PhysicsCustom( void ); + + // Simulation in local space of rigid children + void PhysicsRigidChild( void ); + + // Computes absolute position based on hierarchy + void CalcAbsolutePosition( ); + void CalcAbsoluteVelocity(); + + // Computes new angles based on the angular velocity + void SimulateAngles( float flFrameTime ); + + // Implement this if you use MOVETYPE_CUSTOM + virtual void PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity ); + + // methods related to decal adding + void AddStudioDecal( const Ray_t& ray, int hitbox, int decalIndex, bool doTrace, trace_t& tr, int maxLODToDecal = ADDDECAL_TO_ALL_LODS ); + void AddBrushModelDecal( const Ray_t& ray, const Vector& decalCenter, int decalIndex, bool doTrace, trace_t& tr ); + + void ComputePackedOffsets( void ); + int ComputePackedSize_R( datamap_t *map ); + int GetIntermediateDataSize( void ); + + void UnlinkChild( C_BaseEntity *pParent, C_BaseEntity *pChild ); + void LinkChild( C_BaseEntity *pParent, C_BaseEntity *pChild ); + void HierarchySetParent( C_BaseEntity *pNewParent ); + void UnlinkFromHierarchy(); + + // Computes the water level + type + void UpdateWaterState(); + + // Checks a sweep without actually performing the move + void PhysicsCheckSweep( const Vector& vecAbsStart, const Vector &vecAbsDelta, trace_t *pTrace ); + + // FIXME: REMOVE!!! + void MoveToAimEnt( ); + + // Sets/Gets the next think based on context index + void SetNextThink( int nContextIndex, float thinkTime ); + void SetLastThink( int nContextIndex, float thinkTime ); + float GetNextThink( int nContextIndex ) const; + int GetNextThinkTick( int nContextIndex ) const; + + // Object velocity + Vector m_vecVelocity; + + Vector m_vecAbsVelocity; + + // was pev->avelocity + QAngle m_vecAngVelocity; + +// QAngle m_vecAbsAngVelocity; + +#if !defined( NO_ENTITY_PREDICTION ) + // It's still in the list for "fixup purposes" and simulation, but don't try to render it any more... + bool m_bDormantPredictable; + + // So we can clean it up + int m_nIncomingPacketEntityBecameDormant; +#endif + + // The spawn time of the entity + float m_flSpawnTime; + + // Timestamp of message arrival + float m_flLastMessageTime; + + // Base velocity + Vector m_vecBaseVelocity; + + // Gravity multiplier + float m_flGravity; + + // Model instance data.. + ModelInstanceHandle_t m_ModelInstance; + + // Shadow data + ClientShadowHandle_t m_ShadowHandle; + + // A random value used by material proxies for each model instance. + float m_flProxyRandomValue; + + ClientThinkHandle_t m_hThink; + + int m_iEFlags; // entity flags EFL_* + + // Object movetype + unsigned char m_MoveType; + unsigned char m_MoveCollide; + unsigned char m_iParentAttachment; // 0 if we're relative to the parent's absorigin and absangles. + unsigned char m_iOldParentAttachment; + + unsigned char m_nWaterLevel; + unsigned char m_nWaterType; + // For client/server entities, true if the entity goes outside the PVS. + // Unused for client only entities. + bool m_bDormant; + // Prediction system + bool m_bPredictable; + + + // Hierarchy + CHandle m_pMoveParent; + CHandle m_pMoveChild; + CHandle m_pMovePeer; + CHandle m_pMovePrevPeer; + + // The moveparent received from networking data + CHandle m_hNetworkMoveParent; + CHandle m_hOldMoveParent; + + string_t m_ModelName; + + CNetworkVarEmbedded( CCollisionProperty, m_Collision ); + CNetworkVarEmbedded( CParticleProperty, m_Particles ); + + // Physics state + float m_flElasticity; + + float m_flShadowCastDistance; + EHANDLE m_ShadowDirUseOtherEntity; + + EHANDLE m_hGroundEntity; + float m_flGroundChangeTime; + + + // Friction. + float m_flFriction; + + Vector m_vecAbsOrigin; + + // Object orientation + QAngle m_angAbsRotation; + + Vector m_vecOldOrigin; + QAngle m_vecOldAngRotation; + + Vector m_vecOrigin; + CInterpolatedVar< Vector > m_iv_vecOrigin; + QAngle m_angRotation; + CInterpolatedVar< QAngle > m_iv_angRotation; + + // Specifies the entity-to-world transform + matrix3x4_t m_rgflCoordinateFrame; + + // Last values to come over the wire. Used for interpolation. + Vector m_vecNetworkOrigin; + QAngle m_angNetworkAngles; + + // Behavior flags + int m_fFlags; + + // used to cull collision tests + int m_CollisionGroup; + +#if !defined( NO_ENTITY_PREDICTION ) + // For storing prediction results and pristine network state + byte *m_pIntermediateData[ MULTIPLAYER_BACKUP ]; + byte *m_pOriginalData; + int m_nIntermediateDataCount; + + bool m_bIsPlayerSimulated; +#endif + + CNetworkVar( bool, m_bSimulatedEveryTick ); + CNetworkVar( bool, m_bAnimatedEveryTick ); + CNetworkVar( bool, m_bAlternateSorting ); + + //Adrian + unsigned char m_iTextureFrameIndex; + + // Bbox visualization + unsigned char m_fBBoxVisFlags; + + // The list that holds OnDataChanged events uses this to make sure we don't get multiple + // OnDataChanged calls in the same frame if the client receives multiple packets. + int m_DataChangeEventRef; + +#if !defined( NO_ENTITY_PREDICTION ) + // Player who is driving my simulation + CHandle< CBasePlayer > m_hPlayerSimulationOwner; +#endif + + // The owner! + EHANDLE m_hOwnerEntity; + EHANDLE m_hEffectEntity; + + // This is a random seed used by the networking code to allow client - side prediction code + // randon number generators to spit out the same random numbers on both sides for a particular + // usercmd input. + static int m_nPredictionRandomSeed; + static C_BasePlayer *m_pPredictionPlayer; + static bool s_bAbsQueriesValid; + static bool s_bAbsRecomputationEnabled; + + static bool s_bInterpolate; + + int m_fDataObjectTypes; + + AimEntsListHandle_t m_AimEntsListHandle; + int m_nCreationTick; + + +public: + float m_fRenderingClipPlane[4]; //world space clip plane when drawing + bool m_bEnableRenderingClipPlane; //true to use the custom clip plane when drawing + float * GetRenderClipPlane( void ); // Rendering clip plane, should be 4 floats, return value of NULL indicates a disabled render clip plane + +protected: + + void AddToInterpolationList(); + void RemoveFromInterpolationList(); + unsigned short m_InterpolationListEntry; // Entry into g_InterpolationList (or g_InterpolationList.InvalidIndex if not in the list). + + void AddToTeleportList(); + void RemoveFromTeleportList(); + unsigned short m_TeleportListEntry; + + CThreadFastMutex m_CalcAbsolutePositionMutex; + CThreadFastMutex m_CalcAbsoluteVelocityMutex; +}; + +EXTERN_RECV_TABLE(DT_BaseEntity); + +inline bool FClassnameIs( C_BaseEntity *pEntity, const char *szClassname ) +{ + Assert( pEntity ); + if ( pEntity == NULL ) + return false; + + return !strcmp( pEntity->GetClassname(), szClassname ) ? true : false; +} + +#define SetThink( a ) ThinkSet( static_cast (a), 0, NULL ) +#define SetContextThink( a, b, context ) ThinkSet( static_cast (a), (b), context ) + +#ifdef _DEBUG +#define SetTouch( a ) TouchSet( static_cast (a), #a ) + +#else +#define SetTouch( a ) m_pfnTouch = static_cast (a) + +#endif + + +//----------------------------------------------------------------------------- +// An inline version the game code can use +//----------------------------------------------------------------------------- +inline CCollisionProperty *C_BaseEntity::CollisionProp() +{ + return &m_Collision; +} + +inline const CCollisionProperty *C_BaseEntity::CollisionProp() const +{ + return &m_Collision; +} + +//----------------------------------------------------------------------------- +// An inline version the game code can use +//----------------------------------------------------------------------------- +inline CParticleProperty *C_BaseEntity::ParticleProp() +{ + return &m_Particles; +} + +inline const CParticleProperty *C_BaseEntity::ParticleProp() const +{ + return &m_Particles; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether this entity was created on the client. +//----------------------------------------------------------------------------- +inline bool C_BaseEntity::IsServerEntity( void ) +{ + return index != -1; +} + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- +inline matrix3x4_t &C_BaseEntity::EntityToWorldTransform() +{ + Assert( s_bAbsQueriesValid ); + CalcAbsolutePosition(); + return m_rgflCoordinateFrame; +} + +inline const matrix3x4_t &C_BaseEntity::EntityToWorldTransform() const +{ + Assert( s_bAbsQueriesValid ); + const_cast(this)->CalcAbsolutePosition(); + return m_rgflCoordinateFrame; +} + +//----------------------------------------------------------------------------- +// Some helper methods that transform a point from entity space to world space + back +//----------------------------------------------------------------------------- +inline void C_BaseEntity::EntityToWorldSpace( const Vector &in, Vector *pOut ) const +{ + if ( GetAbsAngles() == vec3_angle ) + { + VectorAdd( in, GetAbsOrigin(), *pOut ); + } + else + { + VectorTransform( in, EntityToWorldTransform(), *pOut ); + } +} + +inline void C_BaseEntity::WorldToEntitySpace( const Vector &in, Vector *pOut ) const +{ + if ( GetAbsAngles() == vec3_angle ) + { + VectorSubtract( in, GetAbsOrigin(), *pOut ); + } + else + { + VectorITransform( in, EntityToWorldTransform(), *pOut ); + } +} + +inline const Vector &C_BaseEntity::GetAbsVelocity( ) const +{ + Assert( s_bAbsQueriesValid ); + const_cast(this)->CalcAbsoluteVelocity(); + return m_vecAbsVelocity; +} + +inline C_BaseEntity *C_BaseEntity::Instance( IClientEntity *ent ) +{ + return ent ? ent->GetBaseEntity() : NULL; +} + +// For debugging shared code +inline bool C_BaseEntity::IsServer( void ) +{ + return false; +} + +inline bool C_BaseEntity::IsClient( void ) +{ + return true; +} + +inline const char *C_BaseEntity::GetDLLType( void ) +{ + return "client"; +} + + +//----------------------------------------------------------------------------- +// Methods relating to solid type + flags +//----------------------------------------------------------------------------- +inline void C_BaseEntity::SetSolidFlags( int nFlags ) +{ + CollisionProp()->SetSolidFlags( nFlags ); +} + +inline bool C_BaseEntity::IsSolidFlagSet( int flagMask ) const +{ + return CollisionProp()->IsSolidFlagSet( flagMask ); +} + +inline int C_BaseEntity::GetSolidFlags( void ) const +{ + return CollisionProp()->GetSolidFlags( ); +} + +inline void C_BaseEntity::AddSolidFlags( int nFlags ) +{ + CollisionProp()->AddSolidFlags( nFlags ); +} + +inline void C_BaseEntity::RemoveSolidFlags( int nFlags ) +{ + CollisionProp()->RemoveSolidFlags( nFlags ); +} + +inline bool C_BaseEntity::IsSolid() const +{ + return CollisionProp()->IsSolid( ); +} + +inline void C_BaseEntity::SetSolid( SolidType_t val ) +{ + CollisionProp()->SetSolid( val ); +} + +inline SolidType_t C_BaseEntity::GetSolid( ) const +{ + return CollisionProp()->GetSolid( ); +} + +inline void C_BaseEntity::SetCollisionBounds( const Vector& mins, const Vector &maxs ) +{ + CollisionProp()->SetCollisionBounds( mins, maxs ); +} + + +//----------------------------------------------------------------------------- +// Methods relating to bounds +//----------------------------------------------------------------------------- +inline const Vector& C_BaseEntity::WorldAlignMins( ) const +{ + Assert( !CollisionProp()->IsBoundsDefinedInEntitySpace() ); + Assert( CollisionProp()->GetCollisionAngles() == vec3_angle ); + return CollisionProp()->OBBMins(); +} + +inline const Vector& C_BaseEntity::WorldAlignMaxs( ) const +{ + Assert( !CollisionProp()->IsBoundsDefinedInEntitySpace() ); + Assert( CollisionProp()->GetCollisionAngles() == vec3_angle ); + return CollisionProp()->OBBMaxs(); +} + +inline const Vector& C_BaseEntity::WorldAlignSize( ) const +{ + Assert( !CollisionProp()->IsBoundsDefinedInEntitySpace() ); + Assert( CollisionProp()->GetCollisionAngles() == vec3_angle ); + return CollisionProp()->OBBSize(); +} + +inline float CBaseEntity::BoundingRadius() const +{ + return CollisionProp()->BoundingRadius(); +} + +inline bool CBaseEntity::IsPointSized() const +{ + return CollisionProp()->BoundingRadius() == 0.0f; +} + + +//----------------------------------------------------------------------------- +// Methods relating to traversing hierarchy +//----------------------------------------------------------------------------- +inline C_BaseEntity *C_BaseEntity::GetMoveParent( void ) const +{ + return m_pMoveParent; +} + +inline C_BaseEntity *C_BaseEntity::FirstMoveChild( void ) const +{ + return m_pMoveChild; +} + +inline C_BaseEntity *C_BaseEntity::NextMovePeer( void ) const +{ + return m_pMovePeer; +} + +//----------------------------------------------------------------------------- +// Velocity +//----------------------------------------------------------------------------- +inline const Vector& C_BaseEntity::GetLocalVelocity() const +{ + return m_vecVelocity; +} + +inline const QAngle& C_BaseEntity::GetLocalAngularVelocity( ) const +{ + return m_vecAngVelocity; +} + +inline const Vector& C_BaseEntity::GetBaseVelocity() const +{ + return m_vecBaseVelocity; +} + +inline void C_BaseEntity::SetBaseVelocity( const Vector& v ) +{ + m_vecBaseVelocity = v; +} + +inline void C_BaseEntity::SetFriction( float flFriction ) +{ + m_flFriction = flFriction; +} + +inline void C_BaseEntity::SetGravity( float flGravity ) +{ + m_flGravity = flGravity; +} + +inline float C_BaseEntity::GetGravity( void ) const +{ + return m_flGravity; +} + +inline int C_BaseEntity::GetWaterLevel() const +{ + return m_nWaterLevel; +} + +inline void C_BaseEntity::SetWaterLevel( int nLevel ) +{ + m_nWaterLevel = nLevel; +} + +inline float C_BaseEntity::GetElasticity( void ) const +{ + return m_flElasticity; +} + +inline const color32 CBaseEntity::GetRenderColor() const +{ + return m_clrRender.Get(); +} + +inline void C_BaseEntity::SetRenderColor( byte r, byte g, byte b ) +{ + color32 clr = { r, g, b, m_clrRender->a }; + m_clrRender = clr; +} + +inline void C_BaseEntity::SetRenderColor( byte r, byte g, byte b, byte a ) +{ + color32 clr = { r, g, b, a }; + m_clrRender = clr; +} + +inline void C_BaseEntity::SetRenderColorR( byte r ) +{ + SetRenderColor( r, GetRenderColor().g, GetRenderColor().b ); +} + +inline void C_BaseEntity::SetRenderColorG( byte g ) +{ + SetRenderColor( GetRenderColor().r, g, GetRenderColor().b ); +} + +inline void C_BaseEntity::SetRenderColorB( byte b ) +{ + SetRenderColor( GetRenderColor().r, GetRenderColor().g, b ); +} + +inline void C_BaseEntity::SetRenderColorA( byte a ) +{ + SetRenderColor( GetRenderColor().r, GetRenderColor().g, GetRenderColor().b, a ); +} + +inline RenderMode_t CBaseEntity::GetRenderMode() const +{ + return (RenderMode_t)m_nRenderMode; +} + +//----------------------------------------------------------------------------- +// checks to see if the entity is marked for deletion +//----------------------------------------------------------------------------- +inline bool C_BaseEntity::IsMarkedForDeletion( void ) +{ + return (m_iEFlags & EFL_KILLME); +} + +inline void C_BaseEntity::AddEFlags( int nEFlagMask ) +{ + m_iEFlags |= nEFlagMask; +} + +inline void C_BaseEntity::RemoveEFlags( int nEFlagMask ) +{ + m_iEFlags &= ~nEFlagMask; +} + +inline bool CBaseEntity::IsEFlagSet( int nEFlagMask ) const +{ + return (m_iEFlags & nEFlagMask) != 0; +} + +inline unsigned char CBaseEntity::GetParentAttachment() const +{ + return m_iParentAttachment; +} + +inline const Vector& CBaseEntity::GetViewOffset() const +{ + return m_vecViewOffset; +} + +inline void CBaseEntity::SetViewOffset( const Vector& v ) +{ + m_vecViewOffset = v; +} + +inline ClientRenderHandle_t CBaseEntity::GetRenderHandle() const +{ + return m_hRender; +} + +inline ClientRenderHandle_t& CBaseEntity::RenderHandle() +{ + return m_hRender; +} + +//----------------------------------------------------------------------------- +// Methods to cast away const +//----------------------------------------------------------------------------- +inline Vector C_BaseEntity::EyePosition( void ) const +{ + return const_cast(this)->EyePosition(); +} + +inline const QAngle &C_BaseEntity::EyeAngles( void ) const // Direction of eyes in world space +{ + return const_cast(this)->EyeAngles(); +} + +inline const QAngle &C_BaseEntity::LocalEyeAngles( void ) const // Direction of eyes +{ + return const_cast(this)->LocalEyeAngles(); +} + +inline Vector C_BaseEntity::EarPosition( void ) const // position of ears +{ + return const_cast(this)->EarPosition(); +} + +inline VarMapping_t* C_BaseEntity::GetVarMapping() +{ + return &m_VarMap; +} + +//----------------------------------------------------------------------------- +// Should we be interpolating? +//----------------------------------------------------------------------------- +inline bool C_BaseEntity::IsInterpolationEnabled() +{ + return s_bInterpolate; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// Output : inline void +//----------------------------------------------------------------------------- +inline void C_BaseEntity::SetToolHandle( HTOOLHANDLE handle ) +{ +#ifndef NO_TOOLFRAMEWORK + m_ToolHandle = handle; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : inline HTOOLHANDLE +//----------------------------------------------------------------------------- +inline HTOOLHANDLE C_BaseEntity::GetToolHandle() const +{ +#ifndef NO_TOOLFRAMEWORK + return m_ToolHandle; +#else + return (HTOOLHANDLE)0; +#endif +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +inline bool C_BaseEntity::IsEnabledInToolView() const +{ +#ifndef NO_TOOLFRAMEWORK + return m_bEnabledInToolView; +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : inline bool +//----------------------------------------------------------------------------- +inline bool C_BaseEntity::ShouldRecordInTools() const +{ +#ifndef NO_TOOLFRAMEWORK + return m_bRecordInTools; +#else + return true; +#endif +} + +C_BaseEntity *CreateEntityByName( const char *className ); + +#endif // C_BASEENTITY_H diff --git a/game/client/c_baseflex.cpp b/game/client/c_baseflex.cpp new file mode 100644 index 00000000..0108410a --- /dev/null +++ b/game/client/c_baseflex.cpp @@ -0,0 +1,1999 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "filesystem.h" +#include "sentence.h" +#include "hud_closecaption.h" +#include "engine/ivmodelinfo.h" +#include "engine/ivdebugoverlay.h" +#include "bone_setup.h" +#include "soundinfo.h" +#include "tools/bonelist.h" +#include "KeyValues.h" +#include "tier0/vprof.h" +#include "toolframework/itoolframework.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +bool UseHWMorphVCDs(); + +ConVar g_CV_PhonemeDelay("phonemedelay", "0", 0, "Phoneme delay to account for sound system latency." ); +ConVar g_CV_PhonemeFilter("phonemefilter", "0.08", 0, "Time duration of box filter to pass over phonemes." ); +ConVar g_CV_FlexRules("flex_rules", "1", 0, "Allow flex animation rules to run." ); +ConVar g_CV_BlinkDuration("blink_duration", "0.2", 0, "How many seconds an eye blink will last." ); +ConVar g_CV_FlexSmooth("flex_smooth", "1", 0, "Applies smoothing/decay curve to flex animation controller changes." ); + +#if defined( CBaseFlex ) +#undef CBaseFlex +#endif + +IMPLEMENT_CLIENTCLASS_DT(C_BaseFlex, DT_BaseFlex, CBaseFlex) + RecvPropArray3( RECVINFO_ARRAY(m_flexWeight), RecvPropFloat(RECVINFO(m_flexWeight[0]))), + RecvPropInt(RECVINFO(m_blinktoggle)), + RecvPropVector(RECVINFO(m_viewtarget)), + +#ifdef HL2_CLIENT_DLL + RecvPropFloat( RECVINFO(m_vecViewOffset[0]) ), + RecvPropFloat( RECVINFO(m_vecViewOffset[1]) ), + RecvPropFloat( RECVINFO(m_vecViewOffset[2]) ), + + RecvPropVector(RECVINFO(m_vecLean)), + RecvPropVector(RECVINFO(m_vecShift)), +#endif + +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_BaseFlex ) + +/* + // DEFINE_FIELD( C_BaseFlex, m_viewtarget, FIELD_VECTOR ), + // DEFINE_ARRAY( C_BaseFlex, m_flexWeight, FIELD_FLOAT, 64 ), + // DEFINE_FIELD( C_BaseFlex, m_blinktoggle, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_blinktime, FIELD_FLOAT ), + // DEFINE_FIELD( C_BaseFlex, m_prevviewtarget, FIELD_VECTOR ), + // DEFINE_ARRAY( C_BaseFlex, m_prevflexWeight, FIELD_FLOAT, 64 ), + // DEFINE_FIELD( C_BaseFlex, m_prevblinktoggle, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_iBlink, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_iEyeUpdown, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_iEyeRightleft, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_FileList, CUtlVector < CFlexSceneFile * > ), +*/ + +END_PREDICTION_DATA() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool GetHWMExpressionFileName( const char *pFilename, char *pHWMFilename ) +{ + // Are we even using hardware morph? + if ( !UseHWMorphVCDs() ) + return false; + + // Do we have a valid filename? + if ( !( pFilename && pFilename[0] ) ) + return false; + + // Check to see if we already have an player/hwm/* filename. + if ( ( V_strstr( pFilename, "player/hwm" ) != NULL ) || ( V_strstr( pFilename, "player\\hwm" ) != NULL ) ) + { + V_strcpy( pHWMFilename, pFilename ); + return true; + } + + // Find the hardware morph scene name and pass that along as well. + char szExpression[MAX_PATH]; + V_strcpy( szExpression, pFilename ); + + char szExpressionHWM[MAX_PATH]; + szExpressionHWM[0] = '\0'; + + char *pszToken = strtok( szExpression, "/\\" ); + while ( pszToken != NULL ) + { + V_strcat( szExpressionHWM, pszToken, sizeof( szExpressionHWM ) ); + if ( !V_stricmp( pszToken, "player" ) ) + { + V_strcat( szExpressionHWM, "\\hwm", sizeof( szExpressionHWM ) ); + } + + pszToken = strtok( NULL, "/\\" ); + if ( pszToken != NULL ) + { + V_strcat( szExpressionHWM, "\\", sizeof( szExpressionHWM ) ); + } + } + + V_strcpy( pHWMFilename, szExpressionHWM ); + return true; +} + +C_BaseFlex::C_BaseFlex() : + m_iv_viewtarget( "C_BaseFlex::m_iv_viewtarget" ), + m_iv_flexWeight("C_BaseFlex:m_iv_flexWeight" ), +#ifdef HL2_CLIENT_DLL + m_iv_vecLean("C_BaseFlex:m_iv_vecLean" ), + m_iv_vecShift("C_BaseFlex:m_iv_vecShift" ), +#endif + m_LocalToGlobal( 0, 0, FlexSettingLessFunc ) +{ +#ifdef _DEBUG + ((Vector&)m_viewtarget).Init(); +#endif + + AddVar( &m_viewtarget, &m_iv_viewtarget, LATCH_ANIMATION_VAR | INTERPOLATE_LINEAR_ONLY ); + AddVar( m_flexWeight, &m_iv_flexWeight, LATCH_ANIMATION_VAR ); + + // Fill in phoneme class lookup + SetupMappings( "phonemes" ); + + m_flFlexDelayedWeight = NULL; + + /// Make sure size is correct + Assert( PHONEME_CLASS_STRONG + 1 == NUM_PHONEME_CLASSES ); + +#ifdef HL2_CLIENT_DLL + // Get general lean vector + AddVar( &m_vecLean, &m_iv_vecLean, LATCH_ANIMATION_VAR ); + AddVar( &m_vecShift, &m_iv_vecShift, LATCH_ANIMATION_VAR ); +#endif +} + +C_BaseFlex::~C_BaseFlex() +{ + delete[] m_flFlexDelayedWeight; + m_SceneEvents.RemoveAll(); + m_LocalToGlobal.RemoveAll(); +} + + +void C_BaseFlex::Spawn() +{ + BaseClass::Spawn(); + + InitPhonemeMappings(); +} + +// TF Player overrides all of these with class specific files +void C_BaseFlex::InitPhonemeMappings() +{ + SetupMappings( "phonemes" ); +} + +void C_BaseFlex::SetupMappings( char const *pchFileRoot ) +{ + // Fill in phoneme class lookup + memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) ); + + Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ]; + Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot ); + normal->required = true; + + Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ]; + Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot ); + Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ]; + Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot ); +} + +//----------------------------------------------------------------------------- +// Purpose: initialize fast lookups when model changes +//----------------------------------------------------------------------------- + +CStudioHdr *C_BaseFlex::OnNewModel() +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); + + // init to invalid setting + m_iBlink = -1; + m_iEyeUpdown = LocalFlexController_t(-1); + m_iEyeRightleft = LocalFlexController_t(-1); + m_bSearchedForEyeFlexes = false; + m_iMouthAttachment = 0; + + delete[] m_flFlexDelayedWeight; + m_flFlexDelayedWeight = NULL; + + if (hdr) + { + if (hdr->numflexdesc()) + { + m_flFlexDelayedWeight = new float [hdr->numflexdesc()]; + + for (int i = 0; i < hdr->numflexdesc(); i++) + { + m_flFlexDelayedWeight[i] = 0.0; + } + } + + m_iv_flexWeight.SetMaxCount( hdr->numflexcontrollers() ); + + m_iMouthAttachment = LookupAttachment( "mouth" ); + } + + return hdr; +} + + +void C_BaseFlex::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) +{ + BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask ); + +#ifdef HL2_CLIENT_DLL + // shift pelvis, rotate body + if (hdr->GetNumIKChains() != 0 && (m_vecShift.x != 0.0 || m_vecShift.y != 0.0)) + { + //CIKContext auto_ik; + //auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, boneMask ); + //auto_ik.AddAllLocks( pos, q ); + + matrix3x4_t rootxform; + AngleMatrix( GetRenderAngles(), GetRenderOrigin(), rootxform ); + + Vector localShift; + VectorIRotate( m_vecShift, rootxform, localShift ); + Vector localLean; + VectorIRotate( m_vecLean, rootxform, localLean ); + + Vector p0 = pos[0]; + float length = VectorNormalize( p0 ); + + // shift the root bone, but keep the height off the origin the same + Vector shiftPos = pos[0] + localShift; + VectorNormalize( shiftPos ); + Vector leanPos = pos[0] + localLean; + VectorNormalize( leanPos ); + pos[0] = shiftPos * length; + + // rotate the root bone based on how much it was "leaned" + Vector p1; + CrossProduct( p0, leanPos, p1 ); + float sinAngle = VectorNormalize( p1 ); + float cosAngle = DotProduct( p0, leanPos ); + float angle = atan2( sinAngle, cosAngle ) * 180 / M_PI; + Quaternion q1; + angle = clamp( angle, -45, 45 ); + AxisAngleQuaternion( p1, angle, q1 ); + QuaternionMult( q1, q[0], q[0] ); + QuaternionNormalize( q[0] ); + + // DevMsgRT( " (%.2f) %.2f %.2f %.2f\n", angle, p1.x, p1.y, p1.z ); + // auto_ik.SolveAllLocks( pos, q ); + } +#endif +} + + + +//----------------------------------------------------------------------------- +// Purpose: place "voice" sounds on mouth +//----------------------------------------------------------------------------- + +bool C_BaseFlex::GetSoundSpatialization( SpatializationInfo_t& info ) +{ + bool bret = BaseClass::GetSoundSpatialization( info ); + // Default things it's audible, put it at a better spot? + if ( bret ) + { + if (info.info.nChannel == CHAN_VOICE && m_iMouthAttachment > 0) + { + Vector origin; + QAngle angles; + + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); + + if (GetAttachment( m_iMouthAttachment, origin, angles )) + { + if (info.pOrigin) + { + *info.pOrigin = origin; + } + + if (info.pAngles) + { + *info.pAngles = angles; + } + } + } + } + + return bret; +} + + +//----------------------------------------------------------------------------- +// Purpose: run the interpreted FAC's expressions, converting flex_controller +// values into FAC weights +//----------------------------------------------------------------------------- +void C_BaseFlex::RunFlexRules( CStudioHdr *hdr, float *dest ) +{ + if ( !g_CV_FlexRules.GetInt() ) + return; + + if ( !hdr ) + return; + +/* + // 0 means run them all + int nFlexRulesToRun = 0; + + const char *pszExpression = flex_expression.GetString(); + if ( pszExpression ) + { + nFlexRulesToRun = atoi(pszExpression); // 0 will be returned if not a numeric string + } +//*/ + + hdr->RunFlexRules( g_flexweight, dest ); +} + +class CFlexSceneFileManager : CAutoGameSystem +{ +public: + + CFlexSceneFileManager() : CAutoGameSystem( "CFlexSceneFileManager" ) + { + } + + virtual bool Init() + { + // Trakcer 16692: Preload these at startup to avoid hitch first time we try to load them during actual gameplay + FindSceneFile( NULL, "phonemes", true ); + FindSceneFile( NULL, "phonemes_weak", true ); + FindSceneFile(NULL, "phonemes_strong", true ); + +#if defined( HL2_CLIENT_DLL ) + FindSceneFile( NULL, "random", true ); + FindSceneFile( NULL, "randomAlert", true ); +#endif + +#if defined( TF_CLIENT_DLL ) + // HACK TO ALL TF TO HAVE PER CLASS OVERRIDES + char const *pTFClasses[] = + { + "scout", + "sniper", + "soldier", + "demo", + "medic", + "heavy", + "pyro", + "spy", + "engineer", + }; + + char fn[ MAX_PATH ]; + for ( int i = 0; i < ARRAYSIZE( pTFClasses ); ++i ) + { + Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_weak", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_strong", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + + if ( !IsX360() ) + { + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_weak", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_strong", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + } + + Q_snprintf( fn, sizeof( fn ), "player/%s/emotion/emotion", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + if ( !IsX360() ) + { + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/emotion/emotion", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + } + } +#endif + + return true; + } + + // Tracker 14992: We used to load 18K of .vfes for every C_BaseFlex who lipsynced, but now we only load those files once globally. + // Note, we could wipe these between levels, but they don't ever load more than the weak/normal/strong phoneme classes that I can tell + // so I'll just leave them loaded forever for now + virtual void Shutdown() + { + DeleteSceneFiles(); + } + + //----------------------------------------------------------------------------- + // Purpose: Sets up translations + // Input : *instance - + // *pSettinghdr - + // Output : void + //----------------------------------------------------------------------------- + void EnsureTranslations( C_BaseFlex *instance, const flexsettinghdr_t *pSettinghdr ) + { + // The only time instance is NULL is in Init() above, where we're just loading the .vfe files off of the hard disk. + if ( instance ) + { + instance->EnsureTranslations( pSettinghdr ); + } + } + + void *FindSceneFile( C_BaseFlex *instance, const char *filename, bool allowBlockingIO ) + { + char szFilename[MAX_PATH]; + Assert( V_strlen( filename ) < MAX_PATH ); + V_strcpy( szFilename, filename ); + +#if defined( TF_CLIENT_DLL ) + char szHWMFilename[MAX_PATH]; + if ( GetHWMExpressionFileName( szFilename, szHWMFilename ) ) + { + V_strcpy( szFilename, szHWMFilename ); + } +#endif + + Q_FixSlashes( szFilename ); + + // See if it's already loaded + int i; + for ( i = 0; i < m_FileList.Count(); i++ ) + { + CFlexSceneFile *file = m_FileList[ i ]; + if ( file && !Q_stricmp( file->filename, szFilename ) ) + { + // Make sure translations (local to global flex controller) are set up for this instance + EnsureTranslations( instance, ( const flexsettinghdr_t * )file->buffer ); + return file->buffer; + } + } + + if ( !allowBlockingIO ) + { + return NULL; + } + + // Load file into memory + void *buffer = NULL; + int len = filesystem->ReadFileEx( VarArgs( "expressions/%s.vfe", szFilename ), "GAME", &buffer ); + + if ( !len ) + return NULL; + + // Create scene entry + CFlexSceneFile *pfile = new CFlexSceneFile; + // Remember filename + Q_strncpy( pfile->filename, szFilename, sizeof( pfile->filename ) ); + // Remember data pointer + pfile->buffer = buffer; + // Add to list + m_FileList.AddToTail( pfile ); + + // Swap the entire file + if ( IsX360() ) + { + CByteswap swap; + swap.ActivateByteSwapping( true ); + byte *pData = (byte*)buffer; + flexsettinghdr_t *pHdr = (flexsettinghdr_t*)pData; + swap.SwapFieldsToTargetEndian( pHdr ); + + // Flex Settings + flexsetting_t *pFlexSetting = (flexsetting_t*)((byte*)pHdr + pHdr->flexsettingindex); + for ( int i = 0; i < pHdr->numflexsettings; ++i, ++pFlexSetting ) + { + swap.SwapFieldsToTargetEndian( pFlexSetting ); + + flexweight_t *pWeight = (flexweight_t*)(((byte*)pFlexSetting) + pFlexSetting->settingindex ); + for ( int j = 0; j < pFlexSetting->numsettings; ++j, ++pWeight ) + { + swap.SwapFieldsToTargetEndian( pWeight ); + } + } + + // indexes + pData = (byte*)pHdr + pHdr->indexindex; + swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numindexes ); + + // keymappings + pData = (byte*)pHdr + pHdr->keymappingindex; + swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys ); + + // keyname indices + pData = (byte*)pHdr + pHdr->keynameindex; + swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys ); + } + + // Fill in translation table + EnsureTranslations( instance, ( const flexsettinghdr_t * )pfile->buffer ); + + // Return data + return pfile->buffer; + } + +private: + + void DeleteSceneFiles() + { + while ( m_FileList.Count() > 0 ) + { + CFlexSceneFile *file = m_FileList[ 0 ]; + m_FileList.Remove( 0 ); + delete[] file->buffer; + delete file; + } + } + + CUtlVector< CFlexSceneFile * > m_FileList; +}; + +CFlexSceneFileManager g_FlexSceneFileManager; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void *C_BaseFlex::FindSceneFile( const char *filename ) +{ + return g_FlexSceneFileManager.FindSceneFile( this, filename, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: make sure the eyes are within 30 degrees of forward +//----------------------------------------------------------------------------- +Vector C_BaseFlex::SetViewTarget( CStudioHdr *pStudioHdr ) +{ + if ( !pStudioHdr ) + return Vector( 0, 0, 0); + + // aim the eyes + Vector tmp = m_viewtarget; + + if ( !m_bSearchedForEyeFlexes ) + { + m_bSearchedForEyeFlexes = true; + + m_iEyeUpdown = FindFlexController( "eyes_updown" ); + m_iEyeRightleft = FindFlexController( "eyes_rightleft" ); + + if ( m_iEyeUpdown != -1 ) + { + pStudioHdr->pFlexcontroller( m_iEyeUpdown )->localToGlobal = AddGlobalFlexController( "eyes_updown" ); + } + if ( m_iEyeRightleft != -1 ) + { + pStudioHdr->pFlexcontroller( m_iEyeRightleft )->localToGlobal = AddGlobalFlexController( "eyes_rightleft" ); + } + } + + if (m_iEyeAttachment > 0) + { + matrix3x4_t attToWorld; + if (!GetAttachment( m_iEyeAttachment, attToWorld )) + { + return Vector( 0, 0, 0); + } + + Vector local; + VectorITransform( tmp, attToWorld, local ); + + // FIXME: clamp distance to something based on eyeball distance + if (local.x < 6) + { + local.x = 6; + } + float flDist = local.Length(); + VectorNormalize( local ); + + // calculate animated eye deflection + Vector eyeDeflect; + QAngle eyeAng( 0, 0, 0 ); + if ( m_iEyeUpdown != -1 ) + { + mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeUpdown ); + eyeAng.x = g_flexweight[ pflex->localToGlobal ]; + } + + if ( m_iEyeRightleft != -1 ) + { + mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeRightleft ); + eyeAng.y = g_flexweight[ pflex->localToGlobal ]; + } + + // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%5.3f %5.3f", eyeAng.x, eyeAng.y ); + + AngleVectors( eyeAng, &eyeDeflect ); + eyeDeflect.x = 0; + + // reduce deflection the more the eye is off center + // FIXME: this angles make no damn sense + eyeDeflect = eyeDeflect * (local.x * local.x); + local = local + eyeDeflect; + VectorNormalize( local ); + + // check to see if the eye is aiming outside the max eye deflection + float flMaxEyeDeflection = pStudioHdr->MaxEyeDeflection(); + if ( local.x < flMaxEyeDeflection ) + { + // if so, clamp it to 30 degrees offset + // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 1, 0, "%5.3f %5.3f %5.3f", local.x, local.y, local.z ); + local.x = 0; + float d = local.LengthSqr(); + if ( d > 0.0f ) + { + d = sqrtf( ( 1.0f - flMaxEyeDeflection * flMaxEyeDeflection ) / ( local.y*local.y + local.z*local.z ) ); + local.x = flMaxEyeDeflection; + local.y = local.y * d; + local.z = local.z * d; + } + else + { + local.x = 1.0; + } + } + local = local * flDist; + VectorTransform( local, attToWorld, tmp ); + } + + modelrender->SetViewTarget( GetModelPtr(), GetBody(), tmp ); + + /* + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%.2f %.2f %.2f : %.2f %.2f %.2f", + m_viewtarget.x, m_viewtarget.y, m_viewtarget.z, + m_prevviewtarget.x, m_prevviewtarget.y, m_prevviewtarget.z ); + */ + + return tmp; +} + +#define STRONG_CROSSFADE_START 0.60f +#define WEAK_CROSSFADE_START 0.40f + +//----------------------------------------------------------------------------- +// Purpose: +// Here's the formula +// 0.5 is neutral 100 % of the default setting +// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END +// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START +// so we don't get huge numbers +// Input : *classes - +// emphasis_intensity - +//----------------------------------------------------------------------------- +void C_BaseFlex::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity ) +{ + // See which blends are available for the current phoneme + bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid; + bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid; + + // Better have phonemes in general + Assert( classes[ PHONEME_CLASS_NORMAL ].valid ); + + if ( emphasis_intensity > STRONG_CROSSFADE_START ) + { + if ( has_strong ) + { + // Blend in some of strong + float dist_remaining = 1.0f - emphasis_intensity; + float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START; + classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac; + } + else + { + emphasis_intensity = min( emphasis_intensity, STRONG_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else if ( emphasis_intensity < WEAK_CROSSFADE_START ) + { + if ( has_weak ) + { + // Blend in some weak + float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity; + float frac = dist_remaining / ( WEAK_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START; + classes[ PHONEME_CLASS_WEAK ].amount = frac; + } + else + { + emphasis_intensity = max( emphasis_intensity, WEAK_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else + { + // Assume 0.5 (neutral) becomes a scaling of 1.0f + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classes - +// phoneme - +// scale - +// newexpression - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression ) +{ + int type; + + // Setup weights for any emphasis blends + bool skip = SetupEmphasisBlend( classes, phoneme ); + // Uh-oh, missing or unknown phoneme??? + if ( skip ) + { + return; + } + + // Compute blend weights + ComputeBlendedSetting( classes, emphasis_intensity ); + + for ( type = 0; type < NUM_PHONEME_CLASSES; type++ ) + { + Emphasized_Phoneme *info = &classes[ type ]; + if ( !info->valid || info->amount == 0.0f ) + continue; + + const flexsettinghdr_t *actual_flexsetting_header = info->base; + const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme ); + if (!pSetting) + { + continue; + } + + flexweight_t *pWeights = NULL; + + int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights ); + if ( pWeights ) + { + for ( int i = 0; i < truecount; i++) + { + // Translate to global controller number + int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key ); + // Add scaled weighting in + g_flexweight[j] += info->amount * scale * pWeights->weight; + // Go to next setting + pWeights++; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A lot of the one time setup and also resets amount to 0.0f default +// for strong/weak/normal tracks +// Returning true == skip this phoneme +// Input : *classes - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseFlex::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme ) +{ + int i; + + bool skip = false; + + for ( i = 0; i < NUM_PHONEME_CLASSES; i++ ) + { + Emphasized_Phoneme *info = &classes[ i ]; + + // Assume it's bogus + info->valid = false; + info->amount = 0.0f; + + // One time setup + if ( !info->basechecked ) + { + info->basechecked = true; + info->base = (flexsettinghdr_t *)FindSceneFile( info->classname ); + } + info->exp = NULL; + if ( info->base ) + { + Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') ); + info->exp = info->base->pIndexedSetting( phoneme ); + } + + if ( info->required && ( !info->base || !info->exp ) ) + { + skip = true; + break; + } + + if ( info->exp ) + { + info->valid = true; + } + } + + return skip; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classes - +// *sentence - +// t - +// dt - +// juststarted - +//----------------------------------------------------------------------------- +ConVar g_CV_PhonemeSnap("phonemesnap", "2", 0, "Lod at level at which visemes stops always considering two phonemes, regardless of duration." ); +void C_BaseFlex::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + + int pcount = sentence->GetRuntimePhonemeCount(); + for ( int k = 0; k < pcount; k++ ) + { + const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k ); + + if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime()) + { + bool bCrossfade = true; + if ((hdr->flags() & STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE) == 0) + { + if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD0) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 0); + } + else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD1) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 1); + } + else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD2) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 2); + } + else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD3) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 3); + } + else + { + bCrossfade = false; + } + } + + if (bCrossfade) + { + if (k < pcount-1) + { + const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 ); + if ( next ) + { + dt = max( dt, min( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); + } + } + } + } + + float t1 = ( phoneme->GetStartTime() - t) / dt; + float t2 = ( phoneme->GetEndTime() - t) / dt; + + if (t1 < 1.0 && t2 > 0) + { + float scale; + + // clamp + if (t2 > 1) + t2 = 1; + if (t1 < 0) + t1 = 0; + + // FIXME: simple box filter. Should use something fancier + scale = (t2 - t1); + + AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classes - +//----------------------------------------------------------------------------- +void C_BaseFlex::ProcessVisemes( Emphasized_Phoneme *classes ) +{ + // Any sounds being played? + if ( !MouthInfo().IsActive() ) + return; + + // Multiple phoneme tracks can overlap, look across all such tracks. + for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ ) + { + CVoiceData *vd = MouthInfo().GetVoiceSource( source ); + if ( !vd || vd->ShouldIgnorePhonemes() ) + continue; + + CSentence *sentence = engine->GetSentence( vd->GetSource() ); + if ( !sentence ) + continue; + + float sentence_length = engine->GetSentenceLength( vd->GetSource() ); + float timesincestart = vd->GetElapsedTime(); + + // This sound should be done...why hasn't it been removed yet??? + if ( timesincestart >= ( sentence_length + 2.0f ) ) + continue; + + // Adjust actual time + float t = timesincestart - g_CV_PhonemeDelay.GetFloat(); + + // Get box filter duration + float dt = g_CV_PhonemeFilter.GetFloat(); + + // Streaming sounds get an additional delay... + /* + // Tracker 20534: Probably not needed any more with the async sound stuff that + // we now have (we don't have a disk i/o hitch on startup which might have been + // messing up the startup timing a bit ) + bool streaming = engine->IsStreaming( vd->m_pAudioSource ); + if ( streaming ) + { + t -= g_CV_PhonemeDelayStreaming.GetFloat(); + } + */ + + // Assume sound has been playing for a while... + bool juststarted = false; + + // Get intensity setting for this time (from spline) + float emphasis_intensity = sentence->GetIntensity( t, sentence_length ); + + // Blend and add visemes together + AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: fill keyvalues message with flex state +// Input : +//----------------------------------------------------------------------------- +void C_BaseFlex::GetToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + VPROF_BUDGET( "C_BaseFlex::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); + + BaseClass::GetToolRecordingState( msg ); + + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return; + + memset( g_flexweight, 0, sizeof( g_flexweight ) ); + + if ( hdr->numflexcontrollers() == 0 ) + return; + + LocalFlexController_t i; + + ProcessSceneEvents( true ); + + // FIXME: shouldn't this happen at runtime? + // initialize the models local to global flex controller mappings + if (hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1) + { + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() ); + hdr->pFlexcontroller( i )->localToGlobal = j; + } + } + + // blend weights from server + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i ); + + g_flexweight[pflex->localToGlobal] = m_flexWeight[i]; + // rescale + g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min; + } + + ProcessSceneEvents( false ); + + // check for blinking + if (m_blinktoggle != m_prevblinktoggle) + { + m_prevblinktoggle = m_blinktoggle; + m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat(); + } + + if (m_iBlink == -1) + m_iBlink = AddGlobalFlexController( "blink" ); + g_flexweight[m_iBlink] = 0; + + // FIXME: this needs a better algorithm + // blink the eyes + float t = (m_blinktime - gpGlobals->curtime) * M_PI * 0.5 * (1.0/g_CV_BlinkDuration.GetFloat()); + if (t > 0) + { + // do eyeblink falloff curve + t = cos(t); + if (t > 0) + { + g_flexweight[m_iBlink] = sqrtf( t ) * 2; + if (g_flexweight[m_iBlink] > 1) + g_flexweight[m_iBlink] = 2.0 - g_flexweight[m_iBlink]; + } + } + + // Drive the mouth from .wav file playback... + ProcessVisemes( m_PhonemeClasses ); + + // Necessary??? + SetViewTarget( hdr ); + + Vector viewtarget = m_viewtarget; // Use the unfiltered value + + // HACK HACK: Unmap eyes right/left amounts + if (m_iEyeUpdown != -1 && m_iEyeRightleft != -1) + { + mstudioflexcontroller_t *flexupdown = hdr->pFlexcontroller( m_iEyeUpdown ); + mstudioflexcontroller_t *flexrightleft = hdr->pFlexcontroller( m_iEyeRightleft ); + + if ( flexupdown->localToGlobal != -1 && flexrightleft->localToGlobal != -1 ) + { + float updown = g_flexweight[ flexupdown->localToGlobal ]; + float rightleft = g_flexweight[ flexrightleft->localToGlobal ]; + + if ( flexupdown->min != flexupdown->max ) + { + updown = RemapVal( updown, flexupdown->min, flexupdown->max, 0.0f, 1.0f ); + } + if ( flexrightleft->min != flexrightleft->max ) + { + rightleft = RemapVal( rightleft, flexrightleft->min, flexrightleft->max, 0.0f, 1.0f ); + } + + g_flexweight[ flexupdown->localToGlobal ] = updown; + g_flexweight[ flexrightleft->localToGlobal ] = rightleft; + } + } + + // Convert back to normalized weights + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i ); + + // rescale + if ( pflex->max != pflex->min ) + { + g_flexweight[pflex->localToGlobal] = ( g_flexweight[pflex->localToGlobal] - pflex->min ) / ( pflex->max - pflex->min ); + } + } + + static BaseFlexRecordingState_t state; + state.m_nFlexCount = MAXSTUDIOFLEXCTRL; + state.m_pDestWeight = g_flexweight; + state.m_vecViewTarget = viewtarget; + msg->SetPtr( "baseflex", &state ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::OnThreadedDrawSetup() +{ + if (m_iEyeAttachment < 0) + return; + + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + CalcAttachments(); +} + + +//----------------------------------------------------------------------------- +// Should we use delayed flex weights? +//----------------------------------------------------------------------------- +bool C_BaseFlex::UsesFlexDelayedWeights() +{ + return ( m_flFlexDelayedWeight && g_CV_FlexSmooth.GetBool() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return; + + memset( g_flexweight, 0, sizeof(g_flexweight) ); + + // FIXME: this should assert then, it's too complex a class for the model + if ( hdr->numflexcontrollers() == 0 ) + { + int nSizeInBytes = nFlexWeightCount * sizeof( float ); + memset( pFlexWeights, 0, nSizeInBytes ); + if ( pFlexDelayedWeights ) + { + memset( pFlexDelayedWeights, 0, nSizeInBytes ); + } + return; + } + + LocalFlexController_t i; + + ProcessSceneEvents( true ); + + // FIXME: shouldn't this happen at runtime? + // initialize the models local to global flex controller mappings + if ( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1 ) + { + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() ); + hdr->pFlexcontroller( i )->localToGlobal = j; + } + } + + // get the networked flexweights and convert them from 0..1 to real dynamic range + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i ); + + g_flexweight[pflex->localToGlobal] = m_flexWeight[i]; + // rescale + g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min; + } + + ProcessSceneEvents( false ); + + // check for blinking + if (m_blinktoggle != m_prevblinktoggle) + { + m_prevblinktoggle = m_blinktoggle; + m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat(); + } + + if (m_iBlink == -1) + { + m_iBlink = AddGlobalFlexController( "blink" ); + } + + // FIXME: this needs a better algorithm + // blink the eyes + float flBlinkDuration = g_CV_BlinkDuration.GetFloat(); + float flOOBlinkDuration = ( flBlinkDuration > 0 ) ? 1.0f / flBlinkDuration : 0.0f; + float t = ( m_blinktime - gpGlobals->curtime ) * M_PI * 0.5 * flOOBlinkDuration; + if (t > 0) + { + // do eyeblink falloff curve + t = cos(t); + if (t > 0.0f && t < 1.0f) + { + t = sqrtf( t ) * 2.0f; + if (t > 1.0f) + t = 2.0f - t; + t = clamp( t, 0.0f, 1.0f ); + // add it to whatever the blink track is doing + g_flexweight[m_iBlink] = clamp( g_flexweight[m_iBlink] + t, 0.0, 1.0 ); + } + } + + // Drive the mouth from .wav file playback... + ProcessVisemes( m_PhonemeClasses ); + + // convert the flex controllers into actual flex values + RunFlexRules( hdr, pFlexWeights ); + + // aim the eyes + SetViewTarget( hdr ); + + if ( pFlexDelayedWeights ) + { + // process the delayed version of the flexweights + float d = 1.0; + if ( gpGlobals->frametime != 0 ) + { + d = ExponentialDecay( 0.8, 0.033, gpGlobals->frametime ); + } + for ( i = LocalFlexController_t(0); i < hdr->numflexdesc(); i++) + { + m_flFlexDelayedWeight[i] = m_flFlexDelayedWeight[i] * d + pFlexWeights[i] * (1 - d); + pFlexDelayedWeights[i] = m_flFlexDelayedWeight[i]; + } + // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), i-hdr->numflexcontrollers, 0, "%.3f", d ); + } + + /* + for (i = 0; i < hdr->numflexdesc; i++) + { + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), i-hdr->numflexcontrollers, 0, "%2d:%s : %3.2f", i, hdr->pFlexdesc( i )->pszFACS(), pFlexWeights[i] ); + } + */ + + /* + for (i = 0; i < g_numflexcontrollers; i++) + { + int j = hdr->pFlexcontroller( i )->link; + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), -i, 0, "%s %3.2f", g_flexcontroller[i], g_flexweight[j] ); + } + */ +} + + + +int C_BaseFlex::g_numflexcontrollers; +char * C_BaseFlex::g_flexcontroller[MAXSTUDIOFLEXCTRL*4]; +float C_BaseFlex::g_flexweight[MAXSTUDIOFLEXDESC]; + +int C_BaseFlex::AddGlobalFlexController( char *szName ) +{ + int i; + for (i = 0; i < g_numflexcontrollers; i++) + { + if (Q_stricmp( g_flexcontroller[i], szName ) == 0) + { + return i; + } + } + + if ( g_numflexcontrollers < MAXSTUDIOFLEXCTRL * 4 ) + { + g_flexcontroller[g_numflexcontrollers++] = strdup( szName ); + } + else + { + // FIXME: missing runtime error condition + } + return i; +} + +char const *C_BaseFlex::GetGlobalFlexControllerName( int idx ) +{ + if ( idx < 0 || idx >= g_numflexcontrollers ) + { + return ""; + } + + return g_flexcontroller[ idx ]; +} + +const flexsetting_t *C_BaseFlex::FindNamedSetting( const flexsettinghdr_t *pSettinghdr, const char *expr ) +{ + int i; + const flexsetting_t *pSetting = NULL; + + for ( i = 0; i < pSettinghdr->numflexsettings; i++ ) + { + pSetting = pSettinghdr->pSetting( i ); + if ( !pSetting ) + continue; + + const char *name = pSetting->pszName(); + + if ( !stricmp( name, expr ) ) + break; + } + + if ( i>=pSettinghdr->numflexsettings ) + { + return NULL; + } + + return pSetting; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::StartChoreoScene( CChoreoScene *scene ) +{ + if ( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() ) + { + return; + } + + m_ActiveChoreoScenes.AddToTail( scene ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::RemoveChoreoScene( CChoreoScene *scene ) +{ + // Assert( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() ); + + m_ActiveChoreoScenes.FindAndRemove( scene ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all active SceneEvents +//----------------------------------------------------------------------------- +void C_BaseFlex::ClearSceneEvents( CChoreoScene *scene, bool canceled ) +{ + if ( !scene ) + { + m_SceneEvents.RemoveAll(); + return; + } + + for ( int i = m_SceneEvents.Count() - 1; i >= 0; i-- ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + + Assert( info ); + Assert( info->m_pScene ); + Assert( info->m_pEvent ); + + if ( info->m_pScene != scene ) + continue; + + if ( !ClearSceneEvent( info, false, canceled )) + { + // unknown expression to clear!! + Assert( 0 ); + } + + // Free this slot + info->m_pEvent = NULL; + info->m_pScene = NULL; + info->m_bStarted = false; + + m_SceneEvents.Remove( i ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Stop specifics of expression +//----------------------------------------------------------------------------- + +bool C_BaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled ) +{ + Assert( info ); + Assert( info->m_pScene ); + Assert( info->m_pEvent ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add string indexed scene/expression/duration to list of active SceneEvents +// Input : scenefile - +// expression - +// duration - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget, bool bClientSide ) +{ + if ( !scene || !event ) + { + Msg( "C_BaseFlex::AddSceneEvent: scene or event was NULL!!!\n" ); + return; + } + + CChoreoActor *actor = event->GetActor(); + if ( !actor ) + { + Msg( "C_BaseFlex::AddSceneEvent: event->GetActor() was NULL!!!\n" ); + return; + } + + + CSceneEventInfo info; + + memset( (void *)&info, 0, sizeof( info ) ); + + info.m_pEvent = event; + info.m_pScene = scene; + info.m_hTarget = pTarget; + info.m_bStarted = false; + info.m_bClientSide = bClientSide; + + if (StartSceneEvent( &info, scene, event, actor, pTarget )) + { + m_SceneEvents.AddToTail( info ); + } + else + { + scene->SceneMsg( "C_BaseFlex::AddSceneEvent: event failed\n" ); + // Assert( 0 ); // expression failed to start + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) +{ + switch ( event->GetType() ) + { + default: + break; + + case CChoreoEvent::FLEXANIMATION: + info->InitWeight( this ); + return true; + + case CChoreoEvent::EXPRESSION: + return true; + + case CChoreoEvent::SEQUENCE: + if ( info->m_bClientSide ) + { + return RequestStartSequenceSceneEvent( info, scene, event, actor, pTarget ); + } + break; + + case CChoreoEvent::SPEAK: + if ( info->m_bClientSide ) + { + return true; + } + break; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseFlex::RequestStartSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) +{ + info->m_nSequence = LookupSequence( event->GetParameters() ); + + // make sure sequence exists + if ( info->m_nSequence < 0 ) + return false; + + info->m_pActor = actor; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove expression +// Input : scenefile - +// expression - +//----------------------------------------------------------------------------- +void C_BaseFlex::RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill ) +{ + Assert( event ); + + for ( int i = 0 ; i < m_SceneEvents.Count(); i++ ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + + Assert( info ); + Assert( info->m_pEvent ); + + if ( info->m_pScene != scene ) + continue; + + if ( info->m_pEvent != event) + continue; + + if (ClearSceneEvent( info, fastKill, false )) + { + // Free this slot + info->m_pEvent = NULL; + info->m_pScene = NULL; + info->m_bStarted = false; + + m_SceneEvents.Remove( i ); + return; + } + } + + // many events refuse to start due to bogus parameters +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if the event should be considered "completed" +//----------------------------------------------------------------------------- +bool C_BaseFlex::CheckSceneEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + for ( int i = 0 ; i < m_SceneEvents.Count(); i++ ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + + Assert( info ); + Assert( info->m_pEvent ); + + if ( info->m_pScene != scene ) + continue; + + if ( info->m_pEvent != event) + continue; + + return CheckSceneEventCompletion( info, currenttime, scene, event ); + } + return true; +} + + + +bool C_BaseFlex::CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + return true; +} + +void C_BaseFlex::SetFlexWeight( LocalFlexController_t index, float value ) +{ + if (index >= 0 && index < GetNumFlexControllers()) + { + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return; + + mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index ); + + if (pflexcontroller->max != pflexcontroller->min) + { + value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min); + value = clamp( value, 0.0, 1.0 ); + } + + m_flexWeight[ index ] = value; + } +} + +float C_BaseFlex::GetFlexWeight( LocalFlexController_t index ) +{ + if (index >= 0 && index < GetNumFlexControllers()) + { + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return 0; + + mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index ); + + if (pflexcontroller->max != pflexcontroller->min) + { + return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min; + } + + return m_flexWeight[index]; + } + return 0.0; +} + +LocalFlexController_t C_BaseFlex::FindFlexController( const char *szName ) +{ + for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) + { + if (stricmp( GetFlexControllerName( i ), szName ) == 0) + { + return i; + } + } + + // AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) ); + return LocalFlexController_t(-1); +} + +//----------------------------------------------------------------------------- +// Purpose: Default implementation +//----------------------------------------------------------------------------- +void C_BaseFlex::ProcessSceneEvents( bool bFlexEvents ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + + // slowly decay to netural expression + + if ( bFlexEvents ) + { + for ( LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) + { + SetFlexWeight( i, GetFlexWeight( i ) * 0.95 ); + } + } + + // Iterate SceneEvents and look for active slots + for ( int i = 0; i < m_SceneEvents.Count(); i++ ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + Assert( info ); + + // FIXME: Need a safe handle to m_pEvent in case of memory deletion? + CChoreoEvent *event = info->m_pEvent; + Assert( event ); + + CChoreoScene *scene = info->m_pScene; + Assert( scene ); + + if ( ProcessSceneEvent( bFlexEvents, info, scene, event ) ) + { + info->m_bStarted = true; + } + } +} + +//----------------------------------------------------------------------------- +// Various methods to process facial SceneEvents: +//----------------------------------------------------------------------------- +bool C_BaseFlex::ProcessFlexAnimationSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->HasEndTime() ); + if ( event->HasEndTime() ) + { + AddFlexAnimation( info ); + } + return true; +} + +bool C_BaseFlex::ProcessFlexSettingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + // Flexanimations have to have an end time!!! + if ( !event->HasEndTime() ) + return true; + + VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" ); + + // Look up the actual strings + const char *scenefile = event->GetParameters(); + const char *name = event->GetParameters2(); + + // Have to find both strings + if ( scenefile && name ) + { + // Find the scene file + const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true ); + if ( pExpHdr ) + { + float scenetime = scene->GetTime(); + + float scale = event->GetIntensity( scenetime ); + + // Add the named expression + AddFlexSetting( name, scale, pExpHdr, !info->m_bStarted ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Each CBaseFlex maintains a UtlRBTree of mappings, one for each loaded flex scene file it uses. This is used to +// sort the entries in the RBTree +// Input : lhs - +// rhs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseFlex::FlexSettingLessFunc( const FS_LocalToGlobal_t& lhs, const FS_LocalToGlobal_t& rhs ) +{ + return lhs.m_Key < rhs.m_Key; +} + +//----------------------------------------------------------------------------- +// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but +// we just do this in memory with an array of integers (could be shorts, I suppose) +// Input : *pSettinghdr - +//----------------------------------------------------------------------------- +void C_BaseFlex::EnsureTranslations( const flexsettinghdr_t *pSettinghdr ) +{ + Assert( pSettinghdr ); + + FS_LocalToGlobal_t entry( pSettinghdr ); + + unsigned short idx = m_LocalToGlobal.Find( entry ); + if ( idx != m_LocalToGlobal.InvalidIndex() ) + return; + + entry.SetCount( pSettinghdr->numkeys ); + + for ( int i = 0; i < pSettinghdr->numkeys; ++i ) + { + entry.m_Mapping[ i ] = AddGlobalFlexController( pSettinghdr->pLocalName( i ) ); + } + + m_LocalToGlobal.Insert( entry ); +} + +//----------------------------------------------------------------------------- +// Purpose: Look up instance specific mapping +// Input : *pSettinghdr - +// key - +// Output : int +//----------------------------------------------------------------------------- +int C_BaseFlex::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key ) +{ + FS_LocalToGlobal_t entry( pSettinghdr ); + + int idx = m_LocalToGlobal.Find( entry ); + if ( idx == m_LocalToGlobal.InvalidIndex() ) + { + // This should never happen!!! + Assert( 0 ); + Warning( "Unable to find mapping for flexcontroller %i, settings %p on %i/%s\n", key, pSettinghdr, entindex(), GetClassname() ); + EnsureTranslations( pSettinghdr ); + idx = m_LocalToGlobal.Find( entry ); + if ( idx == m_LocalToGlobal.InvalidIndex() ) + { + Error( "CBaseFlex::FlexControllerLocalToGlobal failed!\n" ); + } + } + + FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ]; + // Validate lookup + Assert( result.m_nCount != 0 && key < result.m_nCount ); + int index = result.m_Mapping[ key ]; + return index; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *expr - +// scale - +// *pSettinghdr - +// newexpression - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddFlexSetting( const char *expr, float scale, + const flexsettinghdr_t *pSettinghdr, bool newexpression ) +{ + int i; + const flexsetting_t *pSetting = NULL; + + // Find the named setting in the base + for ( i = 0; i < pSettinghdr->numflexsettings; i++ ) + { + pSetting = pSettinghdr->pSetting( i ); + if ( !pSetting ) + continue; + + const char *name = pSetting->pszName(); + + if ( !stricmp( name, expr ) ) + break; + } + + if ( i>=pSettinghdr->numflexsettings ) + { + return; + } + + flexweight_t *pWeights = NULL; + int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights ); + if ( !pWeights ) + return; + + for (i = 0; i < truecount; i++, pWeights++) + { + // Translate to local flex controller + // this is translating from the settings's local index to the models local index + int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key ); + + // blend scaled weighting in to total (post networking g_flexweight!!!!) + float s = clamp( scale * pWeights->influence, 0.0f, 1.0f ); + g_flexweight[index] = g_flexweight[index] * (1.0f - s) + pWeights->weight * s; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseFlex::ProcessSceneEvent( bool bFlexEvents, CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::FLEXANIMATION: + if ( bFlexEvents ) + { + return ProcessFlexAnimationSceneEvent( info, scene, event ); + } + return true; + + case CChoreoEvent::EXPRESSION: + if ( !bFlexEvents ) + { + return ProcessFlexSettingSceneEvent( info, scene, event ); + } + return true; + + case CChoreoEvent::SEQUENCE: + if ( info->m_bClientSide ) + { + if ( !bFlexEvents ) + { + return ProcessSequenceSceneEvent( info, scene, event ); + } + return true; + } + break; + + case CChoreoEvent::SPEAK: + if ( info->m_bClientSide ) + { + return true; + } + break; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *parameters - +//----------------------------------------------------------------------------- +bool C_BaseFlex::ProcessSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !info || !event || !scene ) + return false; + + SetSequence( info->m_nSequence ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddFlexAnimation( CSceneEventInfo *info ) +{ + if ( !info ) + return; + + CChoreoEvent *event = info->m_pEvent; + if ( !event ) + return; + + CChoreoScene *scene = info->m_pScene; + if ( !scene ) + return; + + if ( !event->GetTrackLookupSet() ) + { + // Create lookup data + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + if ( track->IsComboType() ) + { + char name[ 512 ]; + Q_strncpy( name, "right_" ,sizeof(name)); + Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); + + track->SetFlexControllerIndex( max( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 ); + + Q_strncpy( name, "left_" ,sizeof(name)); + Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); + + track->SetFlexControllerIndex( max( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 ); + } + else + { + track->SetFlexControllerIndex( max( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 ); + } + } + + event->SetTrackLookupSet( true ); + } + + if ( !scene_clientflex.GetBool() ) + return; + + float scenetime = scene->GetTime(); + + float weight = event->GetIntensity( scenetime ); + + // decay if this is a background scene and there's other flex animations playing + weight = weight * info->UpdateWeight( this ); + + // Compute intensity for each track in animation and apply + // Iterate animation tracks + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + continue; + + // Map track flex controller to global name + if ( track->IsComboType() ) + { + for ( int side = 0; side < 2; side++ ) + { + LocalFlexController_t controller = track->GetRawFlexControllerIndex( side ); + + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, side ); + if ( controller >= LocalFlexController_t(0) ) + { + float orig = GetFlexWeight( controller ); + float value = orig * (1 - weight) + flIntensity * weight; + SetFlexWeight( controller, value ); + } + } + } + else + { + LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 ); + + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, 0 ); + if ( controller >= LocalFlexController_t(0) ) + { + float orig = GetFlexWeight( controller ); + float value = orig * (1 - weight) + flIntensity * weight; + SetFlexWeight( controller, value ); + } + } + } + + info->m_bStarted = true; +} + +void CSceneEventInfo::InitWeight( C_BaseFlex *pActor ) +{ + m_flWeight = 1.0; +} + +//----------------------------------------------------------------------------- +// Purpose: update weight for background events. Only call once per think +//----------------------------------------------------------------------------- + +float CSceneEventInfo::UpdateWeight( C_BaseFlex *pActor ) +{ + m_flWeight = min( m_flWeight + 0.1, 1.0 ); + return m_flWeight; +} + +BEGIN_BYTESWAP_DATADESC( flexsettinghdr_t ) + DEFINE_FIELD( id, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_ARRAY( name, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( length, FIELD_INTEGER ), + DEFINE_FIELD( numflexsettings, FIELD_INTEGER ), + DEFINE_FIELD( flexsettingindex, FIELD_INTEGER ), + DEFINE_FIELD( nameindex, FIELD_INTEGER ), + DEFINE_FIELD( numindexes, FIELD_INTEGER ), + DEFINE_FIELD( indexindex, FIELD_INTEGER ), + DEFINE_FIELD( numkeys, FIELD_INTEGER ), + DEFINE_FIELD( keynameindex, FIELD_INTEGER ), + DEFINE_FIELD( keymappingindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( flexsetting_t ) + DEFINE_FIELD( nameindex, FIELD_INTEGER ), + DEFINE_FIELD( obsolete1, FIELD_INTEGER ), + DEFINE_FIELD( numsettings, FIELD_INTEGER ), + DEFINE_FIELD( index, FIELD_INTEGER ), + DEFINE_FIELD( obsolete2, FIELD_INTEGER ), + DEFINE_FIELD( settingindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( flexweight_t ) + DEFINE_FIELD( key, FIELD_INTEGER ), + DEFINE_FIELD( weight, FIELD_FLOAT ), + DEFINE_FIELD( influence, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + diff --git a/game/client/c_baseflex.h b/game/client/c_baseflex.h new file mode 100644 index 00000000..c6b17510 --- /dev/null +++ b/game/client/c_baseflex.h @@ -0,0 +1,301 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +// Client-side CBasePlayer + +#ifndef C_STUDIOFLEX_H +#define C_STUDIOFLEX_H +#pragma once + + +#include "c_baseanimating.h" +#include "c_baseanimatingoverlay.h" +#include "sceneentity_shared.h" + +#include "UtlVector.h" + +//----------------------------------------------------------------------------- +// Purpose: Item in list of loaded scene files +//----------------------------------------------------------------------------- +class CFlexSceneFile +{ +public: + enum + { + MAX_FLEX_FILENAME = 128, + }; + + char filename[ MAX_FLEX_FILENAME ]; + void *buffer; +}; + +// For phoneme emphasis track +struct Emphasized_Phoneme; +class CSentence; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_BaseFlex : public C_BaseAnimatingOverlay +{ + DECLARE_CLASS( C_BaseFlex, C_BaseAnimatingOverlay ); +public: + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + DECLARE_INTERPOLATION(); + + C_BaseFlex(); + virtual ~C_BaseFlex(); + + virtual void Spawn(); + + virtual void InitPhonemeMappings(); + + void SetupMappings( char const *pchFileRoot ); + + virtual CStudioHdr *OnNewModel( void ); + + virtual void StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ); + + virtual void OnThreadedDrawSetup(); + + // model specific + virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ); + virtual bool UsesFlexDelayedWeights(); + + virtual void RunFlexRules( CStudioHdr *pStudioHdr, float *dest ); + + virtual Vector SetViewTarget( CStudioHdr *pStudioHdr ); + + virtual bool GetSoundSpatialization( SpatializationInfo_t& info ); + + virtual void GetToolRecordingState( KeyValues *msg ); + + // Called at the lowest level to actually apply a flex animation + void AddFlexAnimation( CSceneEventInfo *info ); + + void SetFlexWeight( LocalFlexController_t index, float value ); + float GetFlexWeight( LocalFlexController_t index ); + + // Look up flex controller index by global name + LocalFlexController_t FindFlexController( const char *szName ); + +public: + Vector m_viewtarget; + CInterpolatedVar< Vector > m_iv_viewtarget; + // indexed by model local flexcontroller + float m_flexWeight[MAXSTUDIOFLEXCTRL]; + CInterpolatedVarArray< float, MAXSTUDIOFLEXCTRL > m_iv_flexWeight; + + int m_blinktoggle; + + static int AddGlobalFlexController( char *szName ); + static char const *GetGlobalFlexControllerName( int idx ); + + // bah, this should be unified with all prev/current stuff. + +public: + + // Keep track of what scenes are being played + void StartChoreoScene( CChoreoScene *scene ); + void RemoveChoreoScene( CChoreoScene *scene ); + + // Start the specifics of an scene event + virtual bool StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, C_BaseEntity *pTarget ); + + // Manipulation of events for the object + // Should be called by think function to process all scene events + // The default implementation resets m_flexWeight array and calls + // AddSceneEvents + virtual void ProcessSceneEvents( bool bFlexEvents ); + + // Assumes m_flexWeight array has been set up, this adds the actual currently playing + // expressions to the flex weights and adds other scene events as needed + virtual bool ProcessSceneEvent( bool bFlexEvents, CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ); + + virtual bool ProcessSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ); + + // Remove all playing events + void ClearSceneEvents( CChoreoScene *scene, bool canceled ); + + // Stop specifics of event + virtual bool ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled ); + + // Add the event to the queue for this actor + void AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, C_BaseEntity *pTarget = NULL, bool bClientSide = false ); + + // Remove the event from the queue for this actor + void RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill ); + + // Checks to see if the event should be considered "completed" + bool CheckSceneEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + + // Checks to see if a event should be considered "completed" + virtual bool CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + + int FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key ); + void EnsureTranslations( const flexsettinghdr_t *pSettinghdr ); + + // For handling scene files + void *FindSceneFile( const char *filename ); + +private: + + bool RequestStartSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ); + + bool ProcessFlexAnimationSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ); + bool ProcessFlexSettingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ); + void AddFlexSetting( const char *expr, float scale, + const flexsettinghdr_t *pSettinghdr, bool newexpression ); + + // Array of active SceneEvents, in order oldest to newest + CUtlVector < CSceneEventInfo > m_SceneEvents; + CUtlVector < CChoreoScene * > m_ActiveChoreoScenes; + + bool HasSceneEvents() const; + +private: +// Mapping for each loaded scene file used by this actor + struct FS_LocalToGlobal_t + { + explicit FS_LocalToGlobal_t() : + m_Key( 0 ), + m_nCount( 0 ), + m_Mapping( 0 ) + { + } + + explicit FS_LocalToGlobal_t( const flexsettinghdr_t *key ) : + m_Key( key ), + m_nCount( 0 ), + m_Mapping( 0 ) + { + } + + void SetCount( int count ) + { + Assert( !m_Mapping ); + Assert( count > 0 ); + m_nCount = count; + m_Mapping = new int[ m_nCount ]; + Q_memset( m_Mapping, 0, m_nCount * sizeof( int ) ); + } + + FS_LocalToGlobal_t( const FS_LocalToGlobal_t& src ) + { + m_Key = src.m_Key; + delete m_Mapping; + m_Mapping = new int[ src.m_nCount ]; + Q_memcpy( m_Mapping, src.m_Mapping, src.m_nCount * sizeof( int ) ); + + m_nCount = src.m_nCount; + } + + ~FS_LocalToGlobal_t() + { + delete m_Mapping; + m_nCount = 0; + m_Mapping = 0; + } + + const flexsettinghdr_t *m_Key; + int m_nCount; + int *m_Mapping; + }; + + static bool FlexSettingLessFunc( const FS_LocalToGlobal_t& lhs, const FS_LocalToGlobal_t& rhs ); + + CUtlRBTree< FS_LocalToGlobal_t, unsigned short > m_LocalToGlobal; + + float m_blinktime; + int m_prevblinktoggle; + + int m_iBlink; + LocalFlexController_t m_iEyeUpdown; + LocalFlexController_t m_iEyeRightleft; + bool m_bSearchedForEyeFlexes; + int m_iMouthAttachment; + + float *m_flFlexDelayedWeight; + + // shared flex controllers + static int g_numflexcontrollers; + static char *g_flexcontroller[MAXSTUDIOFLEXCTRL*4]; // room for global set of flexcontrollers + static float g_flexweight[MAXSTUDIOFLEXDESC]; + +protected: + + enum + { + PHONEME_CLASS_WEAK = 0, + PHONEME_CLASS_NORMAL, + PHONEME_CLASS_STRONG, + + NUM_PHONEME_CLASSES + }; + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + struct Emphasized_Phoneme + { + // Global fields, setup at start + char classname[ 64 ]; + bool required; + // Global fields setup first time tracks played + bool basechecked; + const flexsettinghdr_t *base; + const flexsetting_t *exp; + + // Local fields, processed for each sentence + bool valid; + float amount; + }; + + Emphasized_Phoneme m_PhonemeClasses[ NUM_PHONEME_CLASSES ]; + +private: + + C_BaseFlex( const C_BaseFlex & ); // not defined, not accessible + + const flexsetting_t *FindNamedSetting( const flexsettinghdr_t *pSettinghdr, const char *expr ); + + void ProcessVisemes( Emphasized_Phoneme *classes ); + void AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted ); + void AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression ); + bool SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme ); + void ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity ); + +#ifdef HL2_CLIENT_DLL +public: + + Vector m_vecLean; + CInterpolatedVar< Vector > m_iv_vecLean; + Vector m_vecShift; + CInterpolatedVar< Vector > m_iv_vecShift; +#endif +}; + + +//----------------------------------------------------------------------------- +// Do we have active expressions? +//----------------------------------------------------------------------------- +inline bool C_BaseFlex::HasSceneEvents() const +{ + return m_SceneEvents.Count() != 0; +} + + +EXTERN_RECV_TABLE(DT_BaseFlex); + +float *GetVisemeWeights( int phoneme ); + +#endif // C_STUDIOFLEX_H + + + + diff --git a/game/client/c_baseplayer.cpp b/game/client/c_baseplayer.cpp new file mode 100644 index 00000000..74f0a00b --- /dev/null +++ b/game/client/c_baseplayer.cpp @@ -0,0 +1,2450 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =====// +// +// Purpose: Client-side CBasePlayer. +// +// - Manages the player's flashlight effect. +// +//===========================================================================// +#include "cbase.h" +#include "c_baseplayer.h" +#include "flashlighteffect.h" +#include "weapon_selection.h" +#include "history_resource.h" +#include "iinput.h" +#include "input.h" +#include "view.h" +#include "iviewrender.h" +#include "iclientmode.h" +#include "in_buttons.h" +#include "engine/IEngineSound.h" +#include "c_soundscape.h" +#include "usercmd.h" +#include "c_playerresource.h" +#include "iclientvehicle.h" +#include "view_shared.h" +#include "movevars_shared.h" +#include "prediction.h" +#include "tier0/vprof.h" +#include "filesystem.h" +#include "bitbuf.h" +#include "KeyValues.h" +#include "particles_simple.h" +#include "fx_water.h" +#include "hltvcamera.h" +#include "toolframework/itoolframework.h" +#include "toolframework_client.h" +#include "view_scene.h" +#include "c_vguiscreen.h" +#include "datacache/imdlcache.h" +#include "vgui/isurface.h" +#include "voice_status.h" +#include "fx.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Don't alias here +#if defined( CBasePlayer ) +#undef CBasePlayer +#endif + +int g_nKillCamMode = OBS_MODE_NONE; +int g_nKillCamTarget1 = 0; +int g_nKillCamTarget2 = 0; + +extern ConVar mp_forcecamera; // in gamevars_shared.h + +#define FLASHLIGHT_DISTANCE 1000 +#define MAX_VGUI_INPUT_MODE_SPEED 30 +#define MAX_VGUI_INPUT_MODE_SPEED_SQ (MAX_VGUI_INPUT_MODE_SPEED*MAX_VGUI_INPUT_MODE_SPEED) + +static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET); +static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET); + +bool CommentaryModeShouldSwallowInput( C_BasePlayer *pPlayer ); + +extern ConVar default_fov; +#ifndef _XBOX +extern ConVar sensitivity; +#endif + +static C_BasePlayer *s_pLocalPlayer = NULL; + +static ConVar cl_customsounds ( "cl_customsounds", "0", 0, "Enable customized player sound playback" ); +static ConVar spec_track ( "spec_track", "0", 0, "Tracks an entity in spec mode" ); +static ConVar cl_smooth ( "cl_smooth", "1", 0, "Smooth view/eye origin after prediction errors" ); +static ConVar cl_smoothtime ( + "cl_smoothtime", + "0.1", + 0, + "Smooth client's view after prediction error over this many seconds", + true, 0.01, // min/max is 0.01/2.0 + true, 2.0 + ); + +ConVar spec_freeze_time( "spec_freeze_time", "4.0", FCVAR_CHEAT | FCVAR_REPLICATED, "Time spend frozen in observer freeze cam." ); +ConVar spec_freeze_traveltime( "spec_freeze_traveltime", "0.4", FCVAR_CHEAT | FCVAR_REPLICATED, "Time taken to zoom in to frame a target in observer freeze cam.", true, 0.01, false, 0 ); +ConVar spec_freeze_distance_min( "spec_freeze_distance_min", "96", FCVAR_CHEAT, "Minimum random distance from the target to stop when framing them in observer freeze cam." ); +ConVar spec_freeze_distance_max( "spec_freeze_distance_max", "200", FCVAR_CHEAT, "Maximum random distance from the target to stop when framing them in observer freeze cam." ); + +void RecvProxy_LocalVelocityX( const CRecvProxyData *pData, void *pStruct, void *pOut ); +void RecvProxy_LocalVelocityY( const CRecvProxyData *pData, void *pStruct, void *pOut ); +void RecvProxy_LocalVelocityZ( const CRecvProxyData *pData, void *pStruct, void *pOut ); + +void RecvProxy_ObserverTarget( const CRecvProxyData *pData, void *pStruct, void *pOut ); + +// -------------------------------------------------------------------------------- // +// RecvTable for CPlayerState. +// -------------------------------------------------------------------------------- // + + BEGIN_RECV_TABLE_NOBASE(CPlayerState, DT_PlayerState) + RecvPropInt (RECVINFO(deadflag)), + END_RECV_TABLE() + + +BEGIN_RECV_TABLE_NOBASE( CPlayerLocalData, DT_Local ) + RecvPropArray3( RECVINFO_ARRAY(m_chAreaBits), RecvPropInt(RECVINFO(m_chAreaBits[0]))), + RecvPropArray3( RECVINFO_ARRAY(m_chAreaPortalBits), RecvPropInt(RECVINFO(m_chAreaPortalBits[0]))), + RecvPropInt(RECVINFO(m_iHideHUD)), + + // View + + RecvPropFloat(RECVINFO(m_flFOVRate)), + + RecvPropInt (RECVINFO(m_bDucked)), + RecvPropInt (RECVINFO(m_bDucking)), + RecvPropInt (RECVINFO(m_bInDuckJump)), + RecvPropFloat (RECVINFO(m_flDucktime)), + RecvPropFloat (RECVINFO(m_flDuckJumpTime)), + RecvPropFloat (RECVINFO(m_flJumpTime)), + RecvPropFloat (RECVINFO(m_flFallVelocity)), + +#if PREDICTION_ERROR_CHECK_LEVEL > 1 + RecvPropFloat (RECVINFO_NAME( m_vecPunchAngle.m_Value[0], m_vecPunchAngle[0])), + RecvPropFloat (RECVINFO_NAME( m_vecPunchAngle.m_Value[1], m_vecPunchAngle[1])), + RecvPropFloat (RECVINFO_NAME( m_vecPunchAngle.m_Value[2], m_vecPunchAngle[2] )), + RecvPropFloat (RECVINFO_NAME( m_vecPunchAngleVel.m_Value[0], m_vecPunchAngleVel[0] )), + RecvPropFloat (RECVINFO_NAME( m_vecPunchAngleVel.m_Value[1], m_vecPunchAngleVel[1] )), + RecvPropFloat (RECVINFO_NAME( m_vecPunchAngleVel.m_Value[2], m_vecPunchAngleVel[2] )), +#else + RecvPropVector (RECVINFO(m_vecPunchAngle)), + RecvPropVector (RECVINFO(m_vecPunchAngleVel)), +#endif + + RecvPropInt (RECVINFO(m_bDrawViewmodel)), + RecvPropInt (RECVINFO(m_bWearingSuit)), + RecvPropBool (RECVINFO(m_bPoisoned)), + RecvPropFloat (RECVINFO(m_flStepSize)), + RecvPropInt (RECVINFO(m_bAllowAutoMovement)), + + // 3d skybox data + RecvPropInt(RECVINFO(m_skybox3d.scale)), + RecvPropVector(RECVINFO(m_skybox3d.origin)), + RecvPropInt(RECVINFO(m_skybox3d.area)), + + // 3d skybox fog data + RecvPropInt( RECVINFO( m_skybox3d.fog.enable ) ), + RecvPropInt( RECVINFO( m_skybox3d.fog.blend ) ), + RecvPropVector( RECVINFO( m_skybox3d.fog.dirPrimary ) ), + RecvPropInt( RECVINFO( m_skybox3d.fog.colorPrimary ) ), + RecvPropInt( RECVINFO( m_skybox3d.fog.colorSecondary ) ), + RecvPropFloat( RECVINFO( m_skybox3d.fog.start ) ), + RecvPropFloat( RECVINFO( m_skybox3d.fog.end ) ), + RecvPropFloat( RECVINFO( m_skybox3d.fog.maxdensity ) ), + + // fog data + RecvPropEHandle( RECVINFO( m_PlayerFog.m_hCtrl ) ), + + // audio data + RecvPropVector( RECVINFO( m_audio.localSound[0] ) ), + RecvPropVector( RECVINFO( m_audio.localSound[1] ) ), + RecvPropVector( RECVINFO( m_audio.localSound[2] ) ), + RecvPropVector( RECVINFO( m_audio.localSound[3] ) ), + RecvPropVector( RECVINFO( m_audio.localSound[4] ) ), + RecvPropVector( RECVINFO( m_audio.localSound[5] ) ), + RecvPropVector( RECVINFO( m_audio.localSound[6] ) ), + RecvPropVector( RECVINFO( m_audio.localSound[7] ) ), + RecvPropInt( RECVINFO( m_audio.soundscapeIndex ) ), + RecvPropInt( RECVINFO( m_audio.localBits ) ), + RecvPropEHandle( RECVINFO( m_audio.ent ) ), +END_RECV_TABLE() + +// -------------------------------------------------------------------------------- // +// This data only gets sent to clients that ARE this player entity. +// -------------------------------------------------------------------------------- // + + BEGIN_RECV_TABLE_NOBASE( C_BasePlayer, DT_LocalPlayerExclusive ) + + RecvPropDataTable ( RECVINFO_DT(m_Local),0, &REFERENCE_RECV_TABLE(DT_Local) ), + + RecvPropFloat ( RECVINFO(m_vecViewOffset[0]) ), + RecvPropFloat ( RECVINFO(m_vecViewOffset[1]) ), + RecvPropFloat ( RECVINFO(m_vecViewOffset[2]) ), + RecvPropFloat ( RECVINFO(m_flFriction) ), + + RecvPropArray3 ( RECVINFO_ARRAY(m_iAmmo), RecvPropInt( RECVINFO(m_iAmmo[0])) ), + + RecvPropInt ( RECVINFO(m_fOnTarget) ), + + RecvPropInt ( RECVINFO( m_nTickBase ) ), + RecvPropInt ( RECVINFO( m_nNextThinkTick ) ), + + RecvPropEHandle ( RECVINFO( m_hLastWeapon ) ), + RecvPropEHandle ( RECVINFO( m_hGroundEntity ) ), + + RecvPropFloat ( RECVINFO(m_vecVelocity[0]), 0, RecvProxy_LocalVelocityX ), + RecvPropFloat ( RECVINFO(m_vecVelocity[1]), 0, RecvProxy_LocalVelocityY ), + RecvPropFloat ( RECVINFO(m_vecVelocity[2]), 0, RecvProxy_LocalVelocityZ ), + + RecvPropVector ( RECVINFO( m_vecBaseVelocity ) ), + + RecvPropEHandle ( RECVINFO( m_hConstraintEntity)), + RecvPropVector ( RECVINFO( m_vecConstraintCenter) ), + RecvPropFloat ( RECVINFO( m_flConstraintRadius )), + RecvPropFloat ( RECVINFO( m_flConstraintWidth )), + RecvPropFloat ( RECVINFO( m_flConstraintSpeedFactor )), + + RecvPropFloat ( RECVINFO( m_flDeathTime )), + + RecvPropInt ( RECVINFO( m_nWaterLevel ) ), + RecvPropFloat ( RECVINFO( m_flLaggedMovementValue )), + + END_RECV_TABLE() + + +// -------------------------------------------------------------------------------- // +// DT_BasePlayer datatable. +// -------------------------------------------------------------------------------- // + IMPLEMENT_CLIENTCLASS_DT(C_BasePlayer, DT_BasePlayer, CBasePlayer) + // We have both the local and nonlocal data in here, but the server proxies + // only send one. + RecvPropDataTable( "localdata", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalPlayerExclusive) ), + + RecvPropDataTable(RECVINFO_DT(pl), 0, &REFERENCE_RECV_TABLE(DT_PlayerState), DataTableRecvProxy_StaticDataTable), + + RecvPropInt (RECVINFO(m_iFOV)), + RecvPropInt (RECVINFO(m_iFOVStart)), + RecvPropFloat (RECVINFO(m_flFOVTime)), + RecvPropInt (RECVINFO(m_iDefaultFOV)), + RecvPropEHandle (RECVINFO(m_hZoomOwner)), + + RecvPropEHandle( RECVINFO(m_hVehicle) ), + RecvPropEHandle( RECVINFO(m_hUseEntity) ), + + RecvPropInt (RECVINFO(m_iHealth)), + RecvPropInt (RECVINFO(m_lifeState)), + + RecvPropInt (RECVINFO(m_iBonusProgress)), + RecvPropInt (RECVINFO(m_iBonusChallenge)), + + RecvPropFloat (RECVINFO(m_flMaxspeed)), + RecvPropInt (RECVINFO(m_fFlags)), + + + RecvPropInt (RECVINFO(m_iObserverMode) ), + RecvPropEHandle (RECVINFO(m_hObserverTarget), RecvProxy_ObserverTarget ), + RecvPropArray ( RecvPropEHandle( RECVINFO( m_hViewModel[0] ) ), m_hViewModel ), + + + RecvPropString( RECVINFO(m_szLastPlaceName) ), + + RecvPropInt( RECVINFO( m_ubEFNoInterpParity ) ), + + END_RECV_TABLE() + +BEGIN_PREDICTION_DATA_NO_BASE( CPlayerState ) + + DEFINE_PRED_FIELD( deadflag, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + // DEFINE_FIELD( netname, string_t ), + // DEFINE_FIELD( fixangle, FIELD_INTEGER ), + // DEFINE_FIELD( anglechange, FIELD_FLOAT ), + // DEFINE_FIELD( v_angle, FIELD_VECTOR ), + +END_PREDICTION_DATA() + +BEGIN_PREDICTION_DATA_NO_BASE( CPlayerLocalData ) + + // DEFINE_PRED_TYPEDESCRIPTION( m_skybox3d, sky3dparams_t ), + // DEFINE_PRED_TYPEDESCRIPTION( m_fog, fogparams_t ), + // DEFINE_PRED_TYPEDESCRIPTION( m_audio, audioparams_t ), + DEFINE_FIELD( m_nStepside, FIELD_INTEGER ), + + DEFINE_PRED_FIELD( m_iHideHUD, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +#if PREDICTION_ERROR_CHECK_LEVEL > 1 + DEFINE_PRED_FIELD( m_vecPunchAngle, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_vecPunchAngleVel, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ), +#else + DEFINE_PRED_FIELD_TOL( m_vecPunchAngle, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.125f ), + DEFINE_PRED_FIELD_TOL( m_vecPunchAngleVel, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.125f ), +#endif + DEFINE_PRED_FIELD( m_bDrawViewmodel, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bWearingSuit, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bPoisoned, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bAllowAutoMovement, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + + DEFINE_PRED_FIELD( m_bDucked, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bDucking, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bInDuckJump, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flDucktime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flDuckJumpTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flJumpTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD_TOL( m_flFallVelocity, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, 0.5f ), +// DEFINE_PRED_FIELD( m_nOldButtons, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_FIELD( m_nOldButtons, FIELD_INTEGER ), + DEFINE_PRED_FIELD( m_flStepSize, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_FIELD( m_flFOVRate, FIELD_FLOAT ), + +END_PREDICTION_DATA() + +BEGIN_PREDICTION_DATA( C_BasePlayer ) + + DEFINE_PRED_TYPEDESCRIPTION( m_Local, CPlayerLocalData ), + DEFINE_PRED_TYPEDESCRIPTION( pl, CPlayerState ), + + DEFINE_PRED_FIELD( m_iFOV, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_hZoomOwner, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flFOVTime, FIELD_FLOAT, 0 ), + DEFINE_PRED_FIELD( m_iFOVStart, FIELD_INTEGER, 0 ), + + DEFINE_PRED_FIELD( m_hVehicle, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD_TOL( m_flMaxspeed, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, 0.5f ), + DEFINE_PRED_FIELD( m_iHealth, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_iBonusProgress, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_iBonusChallenge, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_fOnTarget, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_lifeState, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nWaterLevel, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), + + DEFINE_PRED_FIELD_TOL( m_vecBaseVelocity, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.05 ), + + DEFINE_FIELD( m_nButtons, FIELD_INTEGER ), + DEFINE_FIELD( m_flWaterJumpTime, FIELD_FLOAT ), + DEFINE_FIELD( m_nImpulse, FIELD_INTEGER ), + DEFINE_FIELD( m_flStepSoundTime, FIELD_FLOAT ), + DEFINE_FIELD( m_flSwimSoundTime, FIELD_FLOAT ), + DEFINE_FIELD( m_vecLadderNormal, FIELD_VECTOR ), + DEFINE_FIELD( m_flPhysics, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( m_szAnimExtension, FIELD_CHARACTER ), + DEFINE_FIELD( m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( m_afButtonReleased, FIELD_INTEGER ), + // DEFINE_FIELD( m_vecOldViewAngles, FIELD_VECTOR ), + + // DEFINE_ARRAY( m_iOldAmmo, FIELD_INTEGER, MAX_AMMO_TYPES ), + + //DEFINE_FIELD( m_hOldVehicle, FIELD_EHANDLE ), + // DEFINE_FIELD( m_pModelLight, dlight_t* ), + // DEFINE_FIELD( m_pEnvironmentLight, dlight_t* ), + // DEFINE_FIELD( m_pBrightLight, dlight_t* ), + DEFINE_PRED_FIELD( m_hLastWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), + + DEFINE_PRED_FIELD( m_nTickBase, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + + DEFINE_PRED_FIELD( m_hGroundEntity, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), + + DEFINE_PRED_ARRAY( m_hViewModel, FIELD_EHANDLE, MAX_VIEWMODELS, FTYPEDESC_INSENDTABLE ), + + DEFINE_FIELD( m_surfaceFriction, FIELD_FLOAT ), + +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( player, C_BasePlayer ); + +// -------------------------------------------------------------------------------- // +// Functions. +// -------------------------------------------------------------------------------- // +C_BasePlayer::C_BasePlayer() : m_iv_vecViewOffset( "C_BasePlayer::m_iv_vecViewOffset" ) +{ + AddVar( &m_vecViewOffset, &m_iv_vecViewOffset, LATCH_SIMULATION_VAR ); + +#ifdef _DEBUG + m_vecLadderNormal.Init(); + m_vecOldViewAngles.Init(); +#endif + + m_pFlashlight = NULL; + + m_pCurrentVguiScreen = NULL; + m_pCurrentCommand = NULL; + + m_flPredictionErrorTime = -100; + m_StuckLast = 0; + m_bWasFrozen = false; + + m_bResampleWaterSurface = true; + + ResetObserverMode(); + + m_vecPredictionError.Init(); + m_flPredictionErrorTime = 0; + + m_surfaceProps = 0; + m_pSurfaceData = NULL; + m_surfaceFriction = 1.0f; + m_chTextureType = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BasePlayer::~C_BasePlayer() +{ + DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); + if ( this == s_pLocalPlayer ) + { + s_pLocalPlayer = NULL; + } + + if (m_pFlashlight) + { + delete m_pFlashlight; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BasePlayer::Spawn( void ) +{ + // Clear all flags except for FL_FULLEDICT + ClearFlags(); + AddFlag( FL_CLIENT ); + + int effects = GetEffects() & EF_NOSHADOW; + SetEffects( effects ); + + m_iFOV = 0; // init field of view. + + SetModel( "models/player.mdl" ); + + Precache(); + + SetThink(NULL); + + SharedSpawn(); + + m_bWasFreezeFraming = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BasePlayer::AudioStateIsUnderwater( Vector vecMainViewOrigin ) +{ + if ( IsObserver() ) + { + // Just check the view position + int cont = enginetrace->GetPointContents ( vecMainViewOrigin ); + return (cont & MASK_WATER); + } + + return ( GetWaterLevel() >= WL_Eyes ); +} + +bool C_BasePlayer::IsHLTV() const +{ + return ( IsLocalPlayer() && engine->IsHLTV() ); +} + +CBaseEntity *C_BasePlayer::GetObserverTarget() const // returns players targer or NULL +{ +#ifndef _XBOX + if ( IsHLTV() ) + { + return HLTVCamera()->GetPrimaryTarget(); + } +#endif + + if ( GetObserverMode() == OBS_MODE_ROAMING ) + { + return NULL; // no target in roaming mode + } + else + { + return m_hObserverTarget; + } +} + +// Called from Recv Proxy, mainly to reset tone map scale +void C_BasePlayer::SetObserverTarget( EHANDLE hObserverTarget ) +{ + // If the observer target is changing to an entity that the client doesn't know about yet, + // it can resolve to NULL. If the client didn't have an observer target before, then + // comparing EHANDLEs directly will see them as equal, since it uses Get(), and compares + // NULL to NULL. To combat this, we need to check against GetEntryIndex() and + // GetSerialNumber(). + if ( hObserverTarget.GetEntryIndex() != m_hObserverTarget.GetEntryIndex() || + hObserverTarget.GetSerialNumber() != m_hObserverTarget.GetSerialNumber()) + { + // Init based on the new handle's entry index and serial number, so that it's Get() + // has a chance to become non-NULL even if it currently resolves to NULL. + m_hObserverTarget.Init( hObserverTarget.GetEntryIndex(), hObserverTarget.GetSerialNumber() ); + + IGameEvent *event = gameeventmanager->CreateEvent( "spec_target_updated" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + + if ( IsLocalPlayer() ) + { + ResetToneMapping(1.0); + } + } +} + +int C_BasePlayer::GetObserverMode() const +{ +#ifndef _XBOX + if ( IsHLTV() ) + { + return HLTVCamera()->GetMode(); + } +#endif + + return m_iObserverMode; +} + +bool C_BasePlayer::ViewModel_IsTransparent( void ) +{ + return IsTransparent(); +} + +//----------------------------------------------------------------------------- +// Used by prediction, sets the view angles for the player +//----------------------------------------------------------------------------- +void C_BasePlayer::SetLocalViewAngles( const QAngle &viewAngles ) +{ + pl.v_angle = viewAngles; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ang - +//----------------------------------------------------------------------------- +void C_BasePlayer::SetViewAngles( const QAngle& ang ) +{ + SetLocalAngles( ang ); + SetNetworkAngles( ang ); +} + + +surfacedata_t* C_BasePlayer::GetGroundSurface() +{ + // + // Find the name of the material that lies beneath the player. + // + Vector start, end; + VectorCopy( GetAbsOrigin(), start ); + VectorCopy( start, end ); + + // Straight down + end.z -= 64; + + // Fill in default values, just in case. + + Ray_t ray; + ray.Init( start, end, GetPlayerMins(), GetPlayerMaxs() ); + + trace_t trace; + UTIL_TraceRay( ray, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + + if ( trace.fraction == 1.0f ) + return NULL; // no ground + + return physprops->GetSurfaceData( trace.surface.surfaceProps ); +} + + +//----------------------------------------------------------------------------- +// returns the player name +//----------------------------------------------------------------------------- +const char * C_BasePlayer::GetPlayerName() +{ + return g_PR ? g_PR->GetPlayerName( entindex() ) : ""; +} + +//----------------------------------------------------------------------------- +// Is the player dead? +//----------------------------------------------------------------------------- +bool C_BasePlayer::IsPlayerDead() +{ + return pl.deadflag == true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BasePlayer::SetVehicleRole( int nRole ) +{ + if ( !IsInAVehicle() ) + return; + + // HL2 has only a player in a vehicle. + if ( nRole > VEHICLE_ROLE_DRIVER ) + return; + + char szCmd[64]; + Q_snprintf( szCmd, sizeof( szCmd ), "vehicleRole %i\n", nRole ); + engine->ServerCmd( szCmd ); +} + +//----------------------------------------------------------------------------- +// Purpose: Store original ammo data to see what has changed +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_BasePlayer::OnPreDataChanged( DataUpdateType_t updateType ) +{ + for (int i = 0; i < MAX_AMMO_TYPES; ++i) + { + m_iOldAmmo[i] = GetAmmoCount(i); + } + + m_bWasFreezeFraming = (GetObserverMode() == OBS_MODE_FREEZECAM); + m_hOldFogController = m_Local.m_PlayerFog.m_hCtrl; + + BaseClass::OnPreDataChanged( updateType ); +} + +void C_BasePlayer::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); + + m_ubOldEFNoInterpParity = m_ubEFNoInterpParity; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_BasePlayer::PostDataUpdate( DataUpdateType_t updateType ) +{ + // This has to occur here as opposed to OnDataChanged so that EHandles to the player created + // on this same frame are not stomped because prediction thinks there + // isn't a local player yet!!! + + if ( updateType == DATA_UPDATE_CREATED ) + { + // Make sure s_pLocalPlayer is correct + + int iLocalPlayerIndex = engine->GetLocalPlayer(); + + if ( g_nKillCamMode ) + iLocalPlayerIndex = g_nKillCamTarget1; + + if ( iLocalPlayerIndex == index ) + { + Assert( s_pLocalPlayer == NULL ); + s_pLocalPlayer = this; + + // Reset our sound mixed in case we were in a freeze cam when we + // changed level, which would cause the snd_soundmixer to be left modified. + ConVar *pVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); + pVar->Revert(); + } + } + + bool bForceEFNoInterp = ( m_ubOldEFNoInterpParity != m_ubEFNoInterpParity ); + + if ( IsLocalPlayer() ) + { + SetSimulatedEveryTick( true ); + } + else + { + SetSimulatedEveryTick( false ); + + // estimate velocity for non local players + float flTimeDelta = m_flSimulationTime - m_flOldSimulationTime; + if ( flTimeDelta > 0 && !( IsEffectActive(EF_NOINTERP) || bForceEFNoInterp ) ) + { + Vector newVelo = (GetNetworkOrigin() - GetOldOrigin() ) / flTimeDelta; + SetAbsVelocity( newVelo); + } + } + + BaseClass::PostDataUpdate( updateType ); + + // Only care about this for local player + if ( IsLocalPlayer() ) + { + QAngle angles; + engine->GetViewAngles( angles ); + if ( updateType == DATA_UPDATE_CREATED ) + { + SetLocalViewAngles( angles ); + m_flOldPlayerZ = GetLocalOrigin().z; + } + SetLocalAngles( angles ); + + if ( !m_bWasFreezeFraming && GetObserverMode() == OBS_MODE_FREEZECAM ) + { + m_vecFreezeFrameStart = MainViewOrigin(); + m_flFreezeFrameStartTime = gpGlobals->curtime; + m_flFreezeFrameDistance = RandomFloat( spec_freeze_distance_min.GetFloat(), spec_freeze_distance_max.GetFloat() ); + m_flFreezeZOffset = RandomFloat( -30, 20 ); + m_bSentFreezeFrame = false; + + IGameEvent *pEvent = gameeventmanager->CreateEvent( "show_freezepanel" ); + if ( pEvent ) + { + pEvent->SetInt( "killer", GetObserverTarget() ? GetObserverTarget()->entindex() : 0 ); + gameeventmanager->FireEventClientSide( pEvent ); + } + + // Force the sound mixer to the freezecam mixer + ConVar *pVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); + pVar->SetValue( "FreezeCam_Only" ); + } + else if ( m_bWasFreezeFraming && GetObserverMode() != OBS_MODE_FREEZECAM ) + { + IGameEvent *pEvent = gameeventmanager->CreateEvent( "hide_freezepanel" ); + if ( pEvent ) + { + gameeventmanager->FireEventClientSide( pEvent ); + } + + view->FreezeFrame(0); + + ConVar *pVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); + pVar->Revert(); + } + } + + // If we are updated while paused, allow the player origin to be snapped by the + // server if we receive a packet from the server + if ( engine->IsPaused() || bForceEFNoInterp ) + { + ResetLatched(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BasePlayer::CanSetSoundMixer( void ) +{ + // Can't set sound mixers when we're in freezecam mode, since it has a code-enforced mixer + return (GetObserverMode() != OBS_MODE_FREEZECAM); +} + +void C_BasePlayer::ReceiveMessage( int classID, bf_read &msg ) +{ + if ( classID != GetClientClass()->m_ClassID ) + { + // message is for subclass + BaseClass::ReceiveMessage( classID, msg ); + return; + } + + int messageType = msg.ReadByte(); + + switch( messageType ) + { + case PLAY_PLAYER_JINGLE: + PlayPlayerJingle(); + break; + } +} + +void C_BasePlayer::OnRestore() +{ + BaseClass::OnRestore(); + + if ( IsLocalPlayer() ) + { + // debounce the attack key, for if it was used for restore + input->ClearInputButton( IN_ATTACK | IN_ATTACK2 ); + // GetButtonBits() has to be called for the above to take effect + input->GetButtonBits( 0 ); + } + + // For ammo history icons to current value so they don't flash on level transtions + for ( int i = 0; i < MAX_AMMO_TYPES; i++ ) + { + m_iOldAmmo[i] = GetAmmoCount(i); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Process incoming data +//----------------------------------------------------------------------------- +void C_BasePlayer::OnDataChanged( DataUpdateType_t updateType ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + if ( IsLocalPlayer() ) + { + SetPredictionEligible( true ); + } +#endif + + BaseClass::OnDataChanged( updateType ); + + // Only care about this for local player + if ( IsLocalPlayer() ) + { + // Reset engine areabits pointer + render->SetAreaState( m_Local.m_chAreaBits, m_Local.m_chAreaPortalBits ); + + // Check for Ammo pickups. + for ( int i = 0; i < MAX_AMMO_TYPES; i++ ) + { + if ( GetAmmoCount(i) > m_iOldAmmo[i] ) + { + // Don't add to ammo pickup if the ammo doesn't do it + const FileWeaponInfo_t *pWeaponData = gWR.GetWeaponFromAmmo(i); + + if ( !pWeaponData || !( pWeaponData->iFlags & ITEM_FLAG_NOAMMOPICKUPS ) ) + { + // We got more ammo for this ammo index. Add it to the ammo history + CHudHistoryResource *pHudHR = GET_HUDELEMENT( CHudHistoryResource ); + if( pHudHR ) + { + pHudHR->AddToHistory( HISTSLOT_AMMO, i, abs(GetAmmoCount(i) - m_iOldAmmo[i]) ); + } + } + } + } + + Soundscape_Update( m_Local.m_audio ); + + if ( m_hOldFogController != m_Local.m_PlayerFog.m_hCtrl ) + { + FogControllerChanged( updateType == DATA_UPDATE_CREATED ); + } + } +} + + +//----------------------------------------------------------------------------- +// Did we just enter a vehicle this frame? +//----------------------------------------------------------------------------- +bool C_BasePlayer::JustEnteredVehicle() +{ + if ( !IsInAVehicle() ) + return false; + + return ( m_hOldVehicle == m_hVehicle ); +} + +//----------------------------------------------------------------------------- +// Are we in VGUI input mode?. +//----------------------------------------------------------------------------- +bool C_BasePlayer::IsInVGuiInputMode() const +{ + return (m_pCurrentVguiScreen.Get() != NULL); +} + +//----------------------------------------------------------------------------- +// Are we inputing to a view model vgui screen +//----------------------------------------------------------------------------- +bool C_BasePlayer::IsInViewModelVGuiInputMode() const +{ + C_BaseEntity *pScreenEnt = m_pCurrentVguiScreen.Get(); + + if ( !pScreenEnt ) + return false; + + Assert( dynamic_cast(pScreenEnt) ); + C_VGuiScreen *pVguiScreen = static_cast(pScreenEnt); + + return ( pVguiScreen->IsAttachedToViewModel() && pVguiScreen->AcceptsInput() ); +} + +//----------------------------------------------------------------------------- +// Check to see if we're in vgui input mode... +//----------------------------------------------------------------------------- +void C_BasePlayer::DetermineVguiInputMode( CUserCmd *pCmd ) +{ + // If we're dead, close down and abort! + if ( !IsAlive() ) + { + DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); + m_pCurrentVguiScreen.Set( NULL ); + return; + } + + // If we're in vgui mode *and* we're holding down mouse buttons, + // stay in vgui mode even if we're outside the screen bounds + if (m_pCurrentVguiScreen.Get() && (pCmd->buttons & (IN_ATTACK | IN_ATTACK2)) ) + { + SetVGuiScreenButtonState( m_pCurrentVguiScreen.Get(), pCmd->buttons ); + + // Kill all attack inputs if we're in vgui screen mode + pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2); + return; + } + + // We're not in vgui input mode if we're moving, or have hit a key + // that will make us move... + + // Not in vgui mode if we're moving too quickly + // ROBIN: Disabled movement preventing VGUI screen usage + //if (GetVelocity().LengthSqr() > MAX_VGUI_INPUT_MODE_SPEED_SQ) + if ( 0 ) + { + DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); + m_pCurrentVguiScreen.Set( NULL ); + return; + } + + // Don't enter vgui mode if we've got combat buttons held down + bool bAttacking = false; + if ( ((pCmd->buttons & IN_ATTACK) || (pCmd->buttons & IN_ATTACK2)) && !m_pCurrentVguiScreen.Get() ) + { + bAttacking = true; + } + + // Not in vgui mode if we're pushing any movement key at all + // Not in vgui mode if we're in a vehicle... + // ROBIN: Disabled movement preventing VGUI screen usage + //if ((pCmd->forwardmove > MAX_VGUI_INPUT_MODE_SPEED) || + // (pCmd->sidemove > MAX_VGUI_INPUT_MODE_SPEED) || + // (pCmd->upmove > MAX_VGUI_INPUT_MODE_SPEED) || + // (pCmd->buttons & IN_JUMP) || + // (bAttacking) ) + if ( bAttacking || IsInAVehicle() ) + { + DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); + m_pCurrentVguiScreen.Set( NULL ); + return; + } + + // Don't interact with world screens when we're in a menu + if ( vgui::surface()->IsCursorVisible() ) + { + DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); + m_pCurrentVguiScreen.Set( NULL ); + return; + } + + // Not in vgui mode if there are no nearby screens + C_BaseEntity *pOldScreen = m_pCurrentVguiScreen.Get(); + + m_pCurrentVguiScreen = FindNearbyVguiScreen( EyePosition(), pCmd->viewangles, GetTeamNumber() ); + + if (pOldScreen != m_pCurrentVguiScreen) + { + DeactivateVguiScreen( pOldScreen ); + ActivateVguiScreen( m_pCurrentVguiScreen.Get() ); + } + + if (m_pCurrentVguiScreen.Get()) + { + SetVGuiScreenButtonState( m_pCurrentVguiScreen.Get(), pCmd->buttons ); + + // Kill all attack inputs if we're in vgui screen mode + pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Input handling +//----------------------------------------------------------------------------- +bool C_BasePlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) +{ + // Allow the vehicle to clamp the view angles + if ( IsInAVehicle() ) + { + IClientVehicle *pVehicle = m_hVehicle.Get()->GetClientVehicle(); + if ( pVehicle ) + { + pVehicle->UpdateViewAngles( this, pCmd ); + engine->SetViewAngles( pCmd->viewangles ); + } + } + else + { +#ifndef _X360 + if ( joy_autosprint.GetBool() ) +#endif + { + if ( input->KeyState( &in_joyspeed ) != 0.0f ) + { + pCmd->buttons |= IN_SPEED; + } + } + + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( pWeapon ) + { + pWeapon->CreateMove( flInputSampleTime, pCmd, m_vecOldViewAngles ); + } + } + + // If the frozen flag is set, prevent view movement (server prevents the rest of the movement) + if ( GetFlags() & FL_FROZEN ) + { + // Don't stomp the first time we get frozen + if ( m_bWasFrozen ) + { + // Stomp the new viewangles with old ones + pCmd->viewangles = m_vecOldViewAngles; + engine->SetViewAngles( pCmd->viewangles ); + } + else + { + m_bWasFrozen = true; + } + } + else + { + m_bWasFrozen = false; + } + + m_vecOldViewAngles = pCmd->viewangles; + + // Check to see if we're in vgui input mode... + DetermineVguiInputMode( pCmd ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Player has changed to a new team +//----------------------------------------------------------------------------- +void C_BasePlayer::TeamChange( int iNewTeam ) +{ + // Base class does nothing +} + + +//----------------------------------------------------------------------------- +// Purpose: Creates, destroys, and updates the flashlight effect as needed. +//----------------------------------------------------------------------------- +void C_BasePlayer::UpdateFlashlight() +{ + // The dim light is the flashlight. + if ( IsEffectActive( EF_DIMLIGHT ) ) + { + if (!m_pFlashlight) + { + // Turned on the headlight; create it. + m_pFlashlight = new CFlashlightEffect(index); + + if (!m_pFlashlight) + return; + + m_pFlashlight->TurnOn(); + } + + Vector vecForward, vecRight, vecUp; + EyeVectors( &vecForward, &vecRight, &vecUp ); + + // Update the light with the new position and direction. + m_pFlashlight->UpdateLight( EyePosition(), vecForward, vecRight, vecUp, FLASHLIGHT_DISTANCE ); + } + else if (m_pFlashlight) + { + // Turned off the flashlight; delete it. + delete m_pFlashlight; + m_pFlashlight = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Creates player flashlight if it's ative +//----------------------------------------------------------------------------- +void C_BasePlayer::Flashlight( void ) +{ + UpdateFlashlight(); + + // Check for muzzle flash and apply to view model + C_BaseAnimating *ve = this; + if ( GetObserverMode() == OBS_MODE_IN_EYE ) + { + ve = dynamic_cast< C_BaseAnimating* >( GetObserverTarget() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Engine is asking whether to add this player to the visible entities list +//----------------------------------------------------------------------------- +void C_BasePlayer::AddEntity( void ) +{ + // FIXME/UNDONE: Should the local player say yes to adding itself now + // and then, when it ges time to render and it shouldn't still do the render with + // STUDIO_EVENTS set so that its attachment points will get updated even if not + // in third person? + + // Add in water effects + if ( IsLocalPlayer() ) + { + CreateWaterEffects(); + } + + // If set to invisible, skip. Do this before resetting the entity pointer so it has + // valid data to decide whether it's visible. + if ( !IsVisible() || !g_pClientMode->ShouldDrawLocalPlayer( this ) ) + { + return; + } + + // Server says don't interpolate this frame, so set previous info to new info. + if ( IsEffectActive(EF_NOINTERP) || + Teleported() ) + { + ResetLatched(); + } + + // Add in lighting effects + CreateLightEffects(); +} + +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BasePlayer::CreateWaterEffects( void ) +{ + // Must be completely submerged to bother + if ( GetWaterLevel() < 3 ) + { + m_bResampleWaterSurface = true; + return; + } + + // Do special setup if this is our first time back underwater + if ( m_bResampleWaterSurface ) + { + // Reset our particle timer + m_tWaterParticleTimer.Init( 32 ); + + // Find the surface of the water to clip against + m_flWaterSurfaceZ = UTIL_WaterLevel( WorldSpaceCenter(), WorldSpaceCenter().z, WorldSpaceCenter().z + 256 ); + m_bResampleWaterSurface = false; + } + + // Make sure the emitter is setup + if ( m_pWaterEmitter == NULL ) + { + if ( ( m_pWaterEmitter = WaterDebrisEffect::Create( "splish" ) ) == NULL ) + return; + } + + Vector vecVelocity; + GetVectors( &vecVelocity, NULL, NULL ); + + Vector offset = WorldSpaceCenter(); + + m_pWaterEmitter->SetSortOrigin( offset ); + + SimpleParticle *pParticle; + + float curTime = gpGlobals->frametime; + + // Add as many particles as we need + while ( m_tWaterParticleTimer.NextEvent( curTime ) ) + { + offset = WorldSpaceCenter() + ( vecVelocity * 128.0f ) + RandomVector( -128, 128 ); + + // Make sure we don't start out of the water! + if ( offset.z > m_flWaterSurfaceZ ) + { + offset.z = ( m_flWaterSurfaceZ - 8.0f ); + } + + pParticle = (SimpleParticle *) m_pWaterEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_Fleck_Cement[random->RandomInt(0,1)], offset ); + + if (pParticle == NULL) + continue; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 2.0f, 4.0f ); + + pParticle->m_vecVelocity = RandomVector( -2.0f, 2.0f ); + + //FIXME: We should tint these based on the water's fog value! + float color = random->RandomInt( 32, 128 ); + pParticle->m_uchColor[0] = color; + pParticle->m_uchColor[1] = color; + pParticle->m_uchColor[2] = color; + + pParticle->m_uchStartSize = 1; + pParticle->m_uchEndSize = 1; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -0.5f, 0.5f ); + } +} + +//----------------------------------------------------------------------------- +// Called when not in tactical mode. Allows view to be overriden for things like driving a tank. +//----------------------------------------------------------------------------- +void C_BasePlayer::OverrideView( CViewSetup *pSetup ) +{ +} + +bool C_BasePlayer::ShouldInterpolate() +{ + // always interpolate myself + if ( IsLocalPlayer() ) + return true; +#ifndef _XBOX + // always interpolate entity if followed by HLTV + if ( HLTVCamera()->GetCameraMan() == this ) + return true; +#endif + return BaseClass::ShouldInterpolate(); +} + + +bool C_BasePlayer::ShouldDraw() +{ + return ( !IsLocalPlayer() || C_BasePlayer::ShouldDrawLocalPlayer() || (GetObserverMode() == OBS_MODE_DEATHCAM ) ) && + BaseClass::ShouldDraw(); +} + +int C_BasePlayer::DrawModel( int flags ) +{ + // if local player is spectating this player in first person mode, don't draw it + C_BasePlayer * player = C_BasePlayer::GetLocalPlayer(); + + if ( player && player->IsObserver() ) + { + if ( player->GetObserverMode() == OBS_MODE_IN_EYE && + player->GetObserverTarget() == this ) + return 0; + } + + return BaseClass::DrawModel( flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector C_BasePlayer::GetChaseCamViewOffset( CBaseEntity *target ) +{ + C_BasePlayer *player = ToBasePlayer( target ); + + if ( player && player->IsAlive() ) + { + if( player->GetFlags() & FL_DUCKING ) + return VEC_DUCK_VIEW; + + return VEC_VIEW; + } + + // assume it's the players ragdoll + return VEC_DEAD_VIEWHEIGHT; +} + +void C_BasePlayer::CalcChaseCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) +{ + C_BaseEntity *target = GetObserverTarget(); + + if ( !target ) + { + // just copy a save in-map position + VectorCopy( EyePosition(), eyeOrigin ); + VectorCopy( EyeAngles(), eyeAngles ); + return; + }; + + // If our target isn't visible, we're at a camera point of some kind. + // Instead of letting the player rotate around an invisible point, treat + // the point as a fixed camera. + if ( !target->GetBaseAnimating() && !target->GetModel() ) + { + CalcRoamingView( eyeOrigin, eyeAngles, fov ); + return; + } + + // QAngle tmpangles; + + Vector forward, viewpoint; + + // GetObserverCamOrigin() returns ragdoll pos if player is ragdolled + Vector origin = target->GetObserverCamOrigin(); + + VectorAdd( origin, GetChaseCamViewOffset( target ), origin ); + + QAngle viewangles; + + if ( GetObserverMode() == OBS_MODE_IN_EYE ) + { + viewangles = eyeAngles; + } + else if ( IsLocalPlayer() ) + { + engine->GetViewAngles( viewangles ); + } + else + { + viewangles = EyeAngles(); + } + + m_flObserverChaseDistance += gpGlobals->frametime*48.0f; + + float flMaxDistance = CHASE_CAM_DISTANCE; + if ( target && target->IsBaseTrain() ) + { + // if this is a train, we want to be back a little further so we can see more of it + flMaxDistance *= 2.5f; + } + m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, 16, flMaxDistance ); + + AngleVectors( viewangles, &forward ); + + VectorNormalize( forward ); + + VectorMA(origin, -m_flObserverChaseDistance, forward, viewpoint ); + + trace_t trace; + CTraceFilterNoNPCsOrPlayer filter( target, COLLISION_GROUP_NONE ); + C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace + UTIL_TraceHull( origin, viewpoint, WALL_MIN, WALL_MAX, MASK_SOLID, &filter, &trace ); + C_BaseEntity::PopEnableAbsRecomputations(); + + if (trace.fraction < 1.0) + { + viewpoint = trace.endpos; + m_flObserverChaseDistance = VectorLength(origin - eyeOrigin); + } + + VectorCopy( viewangles, eyeAngles ); + VectorCopy( viewpoint, eyeOrigin ); + + fov = GetFOV(); +} + +void C_BasePlayer::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) +{ + C_BaseEntity *target = GetObserverTarget(); + + if ( !target ) + { + target = this; + } + + m_flObserverChaseDistance = 0.0; + + eyeOrigin = target->EyePosition(); + eyeAngles = target->EyeAngles(); + + if ( spec_track.GetInt() > 0 ) + { + C_BaseEntity *target = ClientEntityList().GetBaseEntity( spec_track.GetInt() ); + + if ( target ) + { + Vector v = target->GetAbsOrigin(); v.z += 54; + QAngle a; VectorAngles( v - eyeOrigin, a ); + + NormalizeAngles( a ); + eyeAngles = a; + engine->SetViewAngles( a ); + } + } + + // Apply a smoothing offset to smooth out prediction errors. + Vector vSmoothOffset; + GetPredictionErrorSmoothingVector( vSmoothOffset ); + eyeOrigin += vSmoothOffset; + + fov = GetFOV(); +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate the view for the player while he's in freeze frame observer mode +//----------------------------------------------------------------------------- +void C_BasePlayer::CalcFreezeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ) +{ + C_BaseEntity *pTarget = GetObserverTarget(); + if ( !pTarget ) + { + CalcDeathCamView( eyeOrigin, eyeAngles, fov ); + return; + } + + // Zoom towards our target + float flCurTime = (gpGlobals->curtime - m_flFreezeFrameStartTime); + float flBlendPerc = clamp( flCurTime / spec_freeze_traveltime.GetFloat(), 0, 1 ); + flBlendPerc = SimpleSpline( flBlendPerc ); + + Vector vecCamDesired = pTarget->GetObserverCamOrigin(); // Returns ragdoll origin if they're ragdolled + VectorAdd( vecCamDesired, GetChaseCamViewOffset( pTarget ), vecCamDesired ); + Vector vecCamTarget = vecCamDesired; + if ( pTarget->IsAlive() ) + { + // Look at their chest, not their head + Vector maxs = GameRules()->GetViewVectors()->m_vHullMax; + vecCamTarget.z -= (maxs.z * 0.5); + } + else + { + vecCamTarget.z += VEC_DEAD_VIEWHEIGHT.z; // look over ragdoll, not through + } + + // Figure out a view position in front of the target + Vector vecEyeOnPlane = eyeOrigin; + vecEyeOnPlane.z = vecCamTarget.z; + Vector vecTargetPos = vecCamTarget; + Vector vecToTarget = vecTargetPos - vecEyeOnPlane; + VectorNormalize( vecToTarget ); + + // Stop a few units away from the target, and shift up to be at the same height + vecTargetPos = vecCamTarget - (vecToTarget * m_flFreezeFrameDistance); + float flEyePosZ = pTarget->EyePosition().z; + vecTargetPos.z = flEyePosZ + m_flFreezeZOffset; + + // Now trace out from the target, so that we're put in front of any walls + trace_t trace; + C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace + UTIL_TraceHull( vecCamTarget, vecTargetPos, WALL_MIN, WALL_MAX, MASK_SOLID, pTarget, COLLISION_GROUP_NONE, &trace ); + C_BaseEntity::PopEnableAbsRecomputations(); + if (trace.fraction < 1.0) + { + // The camera's going to be really close to the target. So we don't end up + // looking at someone's chest, aim close freezecams at the target's eyes. + vecTargetPos = trace.endpos; + vecCamTarget = vecCamDesired; + + // To stop all close in views looking up at character's chins, move the view up. + vecTargetPos.z += fabs(vecCamTarget.z - vecTargetPos.z) * 0.85; + C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace + UTIL_TraceHull( vecCamTarget, vecTargetPos, WALL_MIN, WALL_MAX, MASK_SOLID, pTarget, COLLISION_GROUP_NONE, &trace ); + C_BaseEntity::PopEnableAbsRecomputations(); + vecTargetPos = trace.endpos; + } + + // Look directly at the target + vecToTarget = vecCamTarget - vecTargetPos; + VectorNormalize( vecToTarget ); + VectorAngles( vecToTarget, eyeAngles ); + + VectorLerp( m_vecFreezeFrameStart, vecTargetPos, flBlendPerc, eyeOrigin ); + + if ( flCurTime >= spec_freeze_traveltime.GetFloat() && !m_bSentFreezeFrame ) + { + IGameEvent *pEvent = gameeventmanager->CreateEvent( "freezecam_started" ); + if ( pEvent ) + { + gameeventmanager->FireEventClientSide( pEvent ); + } + + m_bSentFreezeFrame = true; + view->FreezeFrame( spec_freeze_time.GetFloat() ); + } +} + +void C_BasePlayer::CalcInEyeCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) +{ + C_BaseEntity *target = GetObserverTarget(); + + if ( !target ) + { + // just copy a save in-map position + VectorCopy( EyePosition(), eyeOrigin ); + VectorCopy( EyeAngles(), eyeAngles ); + return; + }; + + if ( !target->IsAlive() ) + { + // if dead, show from 3rd person + CalcChaseCamView( eyeOrigin, eyeAngles, fov ); + return; + } + + fov = GetFOV(); // TODO use tragets FOV + + m_flObserverChaseDistance = 0.0; + + eyeAngles = target->EyeAngles(); + eyeOrigin = target->GetAbsOrigin(); + + // Apply punch angle + VectorAdd( eyeAngles, GetPunchAngle(), eyeAngles ); + + if( engine->IsHLTV() ) + { + if ( target->GetFlags() & FL_DUCKING ) + { + eyeOrigin += VEC_DUCK_VIEW; + } + else + { + eyeOrigin += VEC_VIEW; + } + } + else + { + Vector offset = m_vecViewOffset; +#ifdef HL2MP + offset = target->GetViewOffset(); +#endif + eyeOrigin += offset; // hack hack + } + + engine->SetViewAngles( eyeAngles ); +} + +void C_BasePlayer::CalcDeathCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) +{ + CBaseEntity * pKiller = NULL; + + if ( mp_forcecamera.GetInt() == OBS_ALLOW_ALL ) + { + // if mp_forcecamera is off let user see killer or look around + pKiller = GetObserverTarget(); + eyeAngles = EyeAngles(); + } + + float interpolation = ( gpGlobals->curtime - m_flDeathTime ) / DEATH_ANIMATION_TIME; + interpolation = clamp( interpolation, 0.0f, 1.0f ); + + m_flObserverChaseDistance += gpGlobals->frametime*48.0f; + m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, 16, CHASE_CAM_DISTANCE ); + + QAngle aForward = eyeAngles; + Vector origin = EyePosition(); + + IRagdoll *pRagdoll = GetRepresentativeRagdoll(); + if ( pRagdoll ) + { + origin = pRagdoll->GetRagdollOrigin(); + origin.z += VEC_DEAD_VIEWHEIGHT.z; // look over ragdoll, not through + } + + if ( pKiller && pKiller->IsPlayer() && (pKiller != this) ) + { + Vector vKiller = pKiller->EyePosition() - origin; + QAngle aKiller; VectorAngles( vKiller, aKiller ); + InterpolateAngles( aForward, aKiller, eyeAngles, interpolation ); + }; + + Vector vForward; AngleVectors( eyeAngles, &vForward ); + + VectorNormalize( vForward ); + + VectorMA( origin, -m_flObserverChaseDistance, vForward, eyeOrigin ); + + trace_t trace; // clip against world + C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace + UTIL_TraceHull( origin, eyeOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); + C_BaseEntity::PopEnableAbsRecomputations(); + + if (trace.fraction < 1.0) + { + eyeOrigin = trace.endpos; + m_flObserverChaseDistance = VectorLength(origin - eyeOrigin); + } + + fov = GetFOV(); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Return the weapon to have open the weapon selection on, based upon our currently active weapon +// Base class just uses the weapon that's currently active. +//----------------------------------------------------------------------------- +C_BaseCombatWeapon *C_BasePlayer::GetActiveWeaponForSelection( void ) +{ + return GetActiveWeapon(); +} + +C_BaseAnimating* C_BasePlayer::GetRenderedWeaponModel() +{ + // Attach to either their weapon model or their view model. + if ( ShouldDrawLocalPlayer() || !IsLocalPlayer() ) + { + return GetActiveWeapon(); + } + else + { + return GetViewModel(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a pointer to the local player, if it exists yet. +// Output : C_BasePlayer +//----------------------------------------------------------------------------- +C_BasePlayer *C_BasePlayer::GetLocalPlayer( void ) +{ + return s_pLocalPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bThirdperson - +//----------------------------------------------------------------------------- +void C_BasePlayer::ThirdPersonSwitch( bool bThirdperson ) +{ + // We've switch from first to third, or vice versa. + UpdateVisibility(); +} + +//----------------------------------------------------------------------------- +// Purpose: single place to decide whether the local player should draw +//----------------------------------------------------------------------------- +bool C_BasePlayer::ShouldDrawLocalPlayer() +{ + return input->CAM_IsThirdPerson() || ( ToolsEnabled() && ToolFramework_IsThirdPersonCamera() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BasePlayer::IsLocalPlayer( void ) const +{ + return ( GetLocalPlayer() == this ); +} + +int C_BasePlayer::GetUserID( void ) +{ + player_info_t pi; + + if ( !engine->GetPlayerInfo( entindex(), &pi ) ) + return -1; + + return pi.userID; +} + + +// For weapon prediction +void C_BasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + // FIXME +} + +void C_BasePlayer::UpdateClientData( void ) +{ + // Update all the items + for ( int i = 0; i < WeaponCount(); i++ ) + { + if ( GetWeapon(i) ) // each item updates it's successors + GetWeapon(i)->UpdateClientData( this ); + } +} + +// Prediction stuff +void C_BasePlayer::PreThink( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + ItemPreFrame(); + + UpdateClientData(); + + UpdateUnderwaterState(); + + // Update the player's fog data if necessary. + UpdateFogController(); + + if (m_lifeState >= LIFE_DYING) + return; + + // + // If we're not on the ground, we're falling. Update our falling velocity. + // + if ( !( GetFlags() & FL_ONGROUND ) ) + { + m_Local.m_flFallVelocity = -GetAbsVelocity().z; + } +#endif +} + +void C_BasePlayer::PostThink( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + MDLCACHE_CRITICAL_SECTION(); + + if ( IsAlive()) + { + if ( !CommentaryModeShouldSwallowInput( this ) ) + { + // do weapon stuff + ItemPostFrame(); + } + + if ( GetFlags() & FL_ONGROUND ) + { + m_Local.m_flFallVelocity = 0; + } + + // Don't allow bogus sequence on player + if ( GetSequence() == -1 ) + { + SetSequence( 0 ); + } + + StudioFrameAdvance(); + } + + // Even if dead simulate entities + SimulatePlayerSimulatedEntities(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: send various tool messages - viewoffset, and base class messages (flex and bones) +//----------------------------------------------------------------------------- +void C_BasePlayer::GetToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + VPROF_BUDGET( "C_BasePlayer::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); + + BaseClass::GetToolRecordingState( msg ); + + msg->SetInt( "baseplayer", 1 ); + msg->SetInt( "localplayer", IsLocalPlayer() ? 1 : 0 ); + msg->SetString( "playername", GetPlayerName() ); + + static CameraRecordingState_t state; + state.m_flFOV = GetFOV(); + + float flZNear = view->GetZNear(); + float flZFar = view->GetZFar(); + CalcView( state.m_vecEyePosition, state.m_vecEyeAngles, flZNear, flZFar, state.m_flFOV ); + state.m_bThirdPerson = !engine->IsPaused() && ::input->CAM_IsThirdPerson(); + + // this is a straight copy from ClientModeShared::OverrideView, + // When that method is removed in favor of rolling it into CalcView, + // then this code can (should!) be removed + if ( state.m_bThirdPerson ) + { + Vector cam_ofs; + ::input->CAM_GetCameraOffset( cam_ofs ); + + QAngle camAngles; + camAngles[ PITCH ] = cam_ofs[ PITCH ]; + camAngles[ YAW ] = cam_ofs[ YAW ]; + camAngles[ ROLL ] = 0; + + Vector camForward, camRight, camUp; + AngleVectors( camAngles, &camForward, &camRight, &camUp ); + + VectorMA( state.m_vecEyePosition, -cam_ofs[ ROLL ], camForward, state.m_vecEyePosition ); + + // Override angles from third person camera + VectorCopy( camAngles, state.m_vecEyeAngles ); + } + + msg->SetPtr( "camera", &state ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Simulate the player for this frame +//----------------------------------------------------------------------------- +void C_BasePlayer::Simulate() +{ + //Frame updates + if ( this == C_BasePlayer::GetLocalPlayer() ) + { + //Update the flashlight + Flashlight(); + + // Update the player's fog data if necessary. + UpdateFogController(); + } + else + { + // update step sounds for all other players + Vector vel; + EstimateAbsVelocity( vel ); + UpdateStepSound( GetGroundSurface(), GetAbsOrigin(), vel ); + } + + BaseClass::Simulate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBaseViewModel +//----------------------------------------------------------------------------- +C_BaseViewModel *C_BasePlayer::GetViewModel( int index /*= 0*/ ) +{ + Assert( index >= 0 && index < MAX_VIEWMODELS ); + + C_BaseViewModel *vm = m_hViewModel[ index ]; + + if ( GetObserverMode() == OBS_MODE_IN_EYE ) + { + C_BasePlayer *target = ToBasePlayer( GetObserverTarget() ); + + // get the targets viewmodel unless the target is an observer itself + if ( target && target != this && !target->IsObserver() ) + { + vm = target->GetViewModel( index ); + } + } + + return vm; +} + +C_BaseCombatWeapon *C_BasePlayer::GetActiveWeapon( void ) const +{ + const C_BasePlayer *fromPlayer = this; + + // if localplayer is in InEye spectator mode, return weapon on chased player + if ( (fromPlayer == GetLocalPlayer()) && ( GetObserverMode() == OBS_MODE_IN_EYE) ) + { + C_BaseEntity *target = GetObserverTarget(); + + if ( target && target->IsPlayer() ) + { + fromPlayer = ToBasePlayer( target ); + } + } + + return fromPlayer->C_BaseCombatCharacter::GetActiveWeapon(); +} + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector C_BasePlayer::GetAutoaimVector( float flScale ) +{ + // Never autoaim a predicted weapon (for now) + Vector forward; + AngleVectors( GetAbsAngles() + m_Local.m_vecPunchAngle, &forward ); + return forward; +} + +void C_BasePlayer::PlayPlayerJingle() +{ +#ifndef _XBOX + // Find player sound for shooter + player_info_t info; + engine->GetPlayerInfo( entindex(), &info ); + + if ( !cl_customsounds.GetBool() ) + return; + + // Doesn't have a jingle sound + if ( !info.customFiles[1] ) + return; + + char soundhex[ 16 ]; + Q_binarytohex( (byte *)&info.customFiles[1], sizeof( info.customFiles[1] ), soundhex, sizeof( soundhex ) ); + + // See if logo has been downloaded. + char fullsoundname[ 512 ]; + Q_snprintf( fullsoundname, sizeof( fullsoundname ), "sound/temp/%s.wav", soundhex ); + + if ( !filesystem->FileExists( fullsoundname ) ) + { + char custname[ 512 ]; + Q_snprintf( custname, sizeof( custname ), "downloads/%s.dat", soundhex ); + // it may have been downloaded but not copied under materials folder + if ( !filesystem->FileExists( custname ) ) + return; // not downloaded yet + + // copy from download folder to materials/temp folder + // this is done since material system can access only materials/*.vtf files + + if ( !engine->CopyFile( custname, fullsoundname) ) + return; + } + + Q_snprintf( fullsoundname, sizeof( fullsoundname ), "temp/%s.wav", soundhex ); + + CLocalPlayerFilter filter; + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = fullsoundname; + ep.m_flVolume = VOL_NORM; + ep.m_SoundLevel = SNDLVL_NORM; + + C_BaseEntity::EmitSound( filter, GetSoundSourceIndex(), ep ); +#endif +} + +// Stuff for prediction +void C_BasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeat) +{ + // FIXME: Do something here? +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BasePlayer::ResetAutoaim( void ) +{ +#if 0 + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = QAngle( 0, 0, 0 ); + engine->CrosshairAngle( edict(), 0, 0 ); + } +#endif + m_fOnTarget = false; +} + +bool C_BasePlayer::ShouldPredict( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + // Do this before calling into baseclass so prediction data block gets allocated + if ( IsLocalPlayer() ) + { + return true; + } +#endif + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Special processing for player simulation +// NOTE: Don't chain to BaseClass!!!! +//----------------------------------------------------------------------------- +void C_BasePlayer::PhysicsSimulate( void ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + VPROF( "C_BasePlayer::PhysicsSimulate" ); + // If we've got a moveparent, we must simulate that first. + CBaseEntity *pMoveParent = GetMoveParent(); + if (pMoveParent) + { + pMoveParent->PhysicsSimulate(); + } + + // Make sure not to simulate this guy twice per frame + if (m_nSimulationTick == gpGlobals->tickcount) + return; + + m_nSimulationTick = gpGlobals->tickcount; + + if ( !IsLocalPlayer() ) + return; + + C_CommandContext *ctx = GetCommandContext(); + Assert( ctx ); + Assert( ctx->needsprocessing ); + if ( !ctx->needsprocessing ) + return; + + ctx->needsprocessing = false; + + // Handle FL_FROZEN. + if(GetFlags() & FL_FROZEN) + { + ctx->cmd.forwardmove = 0; + ctx->cmd.sidemove = 0; + ctx->cmd.upmove = 0; + ctx->cmd.buttons = 0; + ctx->cmd.impulse = 0; + //VectorCopy ( pl.v_angle, ctx->cmd.viewangles ); + } + + // Run the next command + prediction->RunCommand( + this, + &ctx->cmd, + MoveHelper() ); +#endif +} + +const QAngle& C_BasePlayer::GetPunchAngle() +{ + return m_Local.m_vecPunchAngle.Get(); +} + + +void C_BasePlayer::SetPunchAngle( const QAngle &angle ) +{ + m_Local.m_vecPunchAngle = angle; +} + + +float C_BasePlayer::GetWaterJumpTime() const +{ + return m_flWaterJumpTime; +} + +void C_BasePlayer::SetWaterJumpTime( float flWaterJumpTime ) +{ + m_flWaterJumpTime = flWaterJumpTime; +} + +float C_BasePlayer::GetSwimSoundTime() const +{ + return m_flSwimSoundTime; +} + +void C_BasePlayer::SetSwimSoundTime( float flSwimSoundTime ) +{ + m_flSwimSoundTime = flSwimSoundTime; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if this object can be +used by the player +//----------------------------------------------------------------------------- +bool C_BasePlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) +{ + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float C_BasePlayer::GetFOV( void ) +{ + if ( GetObserverMode() == OBS_MODE_IN_EYE ) + { + C_BasePlayer *pTargetPlayer = dynamic_cast( GetObserverTarget() ); + + // get fov from observer target. Not if target is observer itself + if ( pTargetPlayer && !pTargetPlayer->IsObserver() ) + { + return pTargetPlayer->GetFOV(); + } + } + + // Allow our vehicle to override our FOV if it's currently at the default FOV. + float flDefaultFOV; + IClientVehicle *pVehicle = GetVehicle(); + if ( pVehicle ) + { + CacheVehicleView(); + flDefaultFOV = ( m_flVehicleViewFOV == 0 ) ? GetDefaultFOV() : m_flVehicleViewFOV; + } + else + { + flDefaultFOV = GetDefaultFOV(); + } + + float fFOV = ( m_iFOV == 0 ) ? flDefaultFOV : m_iFOV; + + // Don't do lerping during prediction. It's only necessary when actually rendering, + // and it'll cause problems due to prediction timing messiness. + if ( !prediction->InPrediction() ) + { + // See if we need to lerp the values for local player + if ( IsLocalPlayer() && ( fFOV != m_iFOVStart ) && (m_Local.m_flFOVRate > 0.0f ) ) + { + float deltaTime = (float)( gpGlobals->curtime - m_flFOVTime ) / m_Local.m_flFOVRate; + +#if !defined( NO_ENTITY_PREDICTION ) + if ( GetPredictable() ) + { + // m_flFOVTime was set to a predicted time in the future, because the FOV change was predicted. + deltaTime = (float)( GetFinalPredictedTime() - m_flFOVTime ); + deltaTime += ( gpGlobals->interpolation_amount * TICK_INTERVAL ); + deltaTime /= m_Local.m_flFOVRate; + } +#endif + + if ( deltaTime >= 1.0f ) + { + //If we're past the zoom time, just take the new value and stop lerping + m_iFOVStart = fFOV; + } + else + { + fFOV = SimpleSplineRemapValClamped( deltaTime, 0.0f, 1.0f, (float) m_iFOVStart, fFOV ); + } + } + } + + return fFOV; +} + +void RecvProxy_LocalVelocityX( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; + + Assert( pPlayer ); + + float flNewVel_x = pData->m_Value.m_Float; + + Vector vecVelocity = pPlayer->GetLocalVelocity(); + + if( vecVelocity.x != flNewVel_x ) // Should this use an epsilon check? + { + vecVelocity.x = flNewVel_x; + pPlayer->SetLocalVelocity( vecVelocity ); + } +} + +void RecvProxy_LocalVelocityY( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; + + Assert( pPlayer ); + + float flNewVel_y = pData->m_Value.m_Float; + + Vector vecVelocity = pPlayer->GetLocalVelocity(); + + if( vecVelocity.y != flNewVel_y ) + { + vecVelocity.y = flNewVel_y; + pPlayer->SetLocalVelocity( vecVelocity ); + } +} + +void RecvProxy_LocalVelocityZ( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; + + Assert( pPlayer ); + + float flNewVel_z = pData->m_Value.m_Float; + + Vector vecVelocity = pPlayer->GetLocalVelocity(); + + if( vecVelocity.z != flNewVel_z ) + { + vecVelocity.z = flNewVel_z; + pPlayer->SetLocalVelocity( vecVelocity ); + } +} + +void RecvProxy_ObserverTarget( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; + + Assert( pPlayer ); + + EHANDLE hTarget; + + RecvProxy_IntToEHandle( pData, pStruct, &hTarget ); + + pPlayer->SetObserverTarget( hTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove this player from a vehicle +//----------------------------------------------------------------------------- +void C_BasePlayer::LeaveVehicle( void ) +{ + if ( NULL == m_hVehicle.Get() ) + return; + +// Let server do this for now +#if 0 + IClientVehicle *pVehicle = GetVehicle(); + Assert( pVehicle ); + + int nRole = pVehicle->GetPassengerRole( this ); + Assert( nRole != VEHICLE_ROLE_NONE ); + + SetParent( NULL ); + + // Find the first non-blocked exit point: + Vector vNewPos = GetAbsOrigin(); + QAngle qAngles = GetAbsAngles(); + pVehicle->GetPassengerExitPoint( nRole, &vNewPos, &qAngles ); + OnVehicleEnd( vNewPos ); + SetAbsOrigin( vNewPos ); + SetAbsAngles( qAngles ); + + m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; + RemoveEffects( EF_NODRAW ); + + SetMoveType( MOVETYPE_WALK ); + SetCollisionGroup( COLLISION_GROUP_PLAYER ); + + qAngles[ROLL] = 0; + SnapEyeAngles( qAngles ); + + m_hVehicle = NULL; + pVehicle->SetPassenger(nRole, NULL); + + Weapon_Switch( m_hLastWeapon ); +#endif +} + + +float C_BasePlayer::GetMinFOV() const +{ + if ( gpGlobals->maxClients == 1 ) + { + // Let them do whatever they want, more or less, in single player + return 5; + } + else + { + return 75; + } +} + +float C_BasePlayer::GetFinalPredictedTime() const +{ + return ( m_nFinalPredictedTick * TICK_INTERVAL ); +} + +void C_BasePlayer::NotePredictionError( const Vector &vDelta ) +{ + // don't worry about prediction errors when dead + if ( !IsAlive() ) + return; + +#if !defined( NO_ENTITY_PREDICTION ) + Vector vOldDelta; + + GetPredictionErrorSmoothingVector( vOldDelta ); + + // sum all errors within smoothing time + m_vecPredictionError = vDelta + vOldDelta; + + // remember when last error happened + m_flPredictionErrorTime = gpGlobals->curtime; + + ResetLatched(); +#endif +} + + +// offset curtime and setup bones at that time using fake interpolation +// fake interpolation means we don't have reliable interpolation history (the local player doesn't animate locally) +// so we just modify cycle and origin directly and use that as a fake guess +void C_BasePlayer::ForceSetupBonesAtTimeFakeInterpolation( matrix3x4_t *pBonesOut, float curtimeOffset ) +{ + // we don't have any interpolation data, so fake it + float cycle = m_flCycle; + Vector origin = GetLocalOrigin(); + + // blow the cached prev bones + InvalidateBoneCache(); + // reset root position to flTime + Interpolate( gpGlobals->curtime + curtimeOffset ); + + // force cycle back by boneDt + m_flCycle = fmod( 10 + cycle + m_flPlaybackRate * curtimeOffset, 1.0f ); + SetLocalOrigin( origin + curtimeOffset * GetLocalVelocity() ); + // Setup bone state to extrapolate physics velocity + SetupBones( pBonesOut, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime + curtimeOffset ); + + m_flCycle = cycle; + SetLocalOrigin( origin ); +} + +void C_BasePlayer::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ) +{ + if ( !IsLocalPlayer() ) + { + BaseClass::GetRagdollInitBoneArrays(pDeltaBones0, pDeltaBones1, pCurrentBones, boneDt); + return; + } + ForceSetupBonesAtTimeFakeInterpolation( pDeltaBones0, -boneDt ); + ForceSetupBonesAtTimeFakeInterpolation( pDeltaBones1, 0 ); + float ragdollCreateTime = PhysGetSyncCreateTime(); + if ( ragdollCreateTime != gpGlobals->curtime ) + { + ForceSetupBonesAtTimeFakeInterpolation( pCurrentBones, ragdollCreateTime - gpGlobals->curtime ); + } + else + { + SetupBones( pCurrentBones, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); + } +} + + +void C_BasePlayer::GetPredictionErrorSmoothingVector( Vector &vOffset ) +{ +#if !defined( NO_ENTITY_PREDICTION ) + if ( engine->IsPlayingDemo() || !cl_smooth.GetInt() || !cl_predict->GetInt() ) + { + vOffset.Init(); + return; + } + + float errorAmount = ( gpGlobals->curtime - m_flPredictionErrorTime ) / cl_smoothtime.GetFloat(); + + if ( errorAmount >= 1.0f ) + { + vOffset.Init(); + return; + } + + errorAmount = 1.0f - errorAmount; + + vOffset = m_vecPredictionError * errorAmount; +#else + vOffset.Init(); +#endif +} + + +IRagdoll* C_BasePlayer::GetRepresentativeRagdoll() const +{ + return m_pRagdoll; +} + +IMaterial *C_BasePlayer::GetHeadLabelMaterial( void ) +{ + if ( GetClientVoiceMgr() == NULL ) + return NULL; + + return GetClientVoiceMgr()->GetHeadLabelMaterial(); +} + +bool IsInFreezeCam( void ) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the fog controller data per player. +// Input : &inputdata - +//----------------------------------------------------------------------------- +void C_BasePlayer::FogControllerChanged( bool bSnap ) +{ + if ( m_Local.m_PlayerFog.m_hCtrl ) + { + fogparams_t *pFogParams = &(m_Local.m_PlayerFog.m_hCtrl->m_fog); + + /* + Msg("Updating Fog Target: (%d,%d,%d) %.0f,%.0f -> (%d,%d,%d) %.0f,%.0f (%.2f seconds)\n", + m_CurrentFog.colorPrimary.GetR(), m_CurrentFog.colorPrimary.GetB(), m_CurrentFog.colorPrimary.GetG(), + m_CurrentFog.start.Get(), m_CurrentFog.end.Get(), + pFogParams->colorPrimary.GetR(), pFogParams->colorPrimary.GetB(), pFogParams->colorPrimary.GetG(), + pFogParams->start.Get(), pFogParams->end.Get(), pFogParams->duration.Get() );*/ + + + // Setup the fog color transition. + m_Local.m_PlayerFog.m_OldColor = m_CurrentFog.colorPrimary; + m_Local.m_PlayerFog.m_flOldStart = m_CurrentFog.start; + m_Local.m_PlayerFog.m_flOldEnd = m_CurrentFog.end; + + m_Local.m_PlayerFog.m_NewColor = pFogParams->colorPrimary; + m_Local.m_PlayerFog.m_flNewStart = pFogParams->start; + m_Local.m_PlayerFog.m_flNewEnd = pFogParams->end; + + m_Local.m_PlayerFog.m_flTransitionTime = bSnap ? -1 : gpGlobals->curtime; + + m_CurrentFog = *pFogParams; + + // Update the fog player's local fog data with the fog controller's data if need be. + UpdateFogController(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see that the controllers data is up to date. +//----------------------------------------------------------------------------- +void C_BasePlayer::UpdateFogController( void ) +{ + if ( m_Local.m_PlayerFog.m_hCtrl ) + { + // Don't bother copying while we're transitioning, since it'll be stomped in UpdateFogBlend(); + if ( m_Local.m_PlayerFog.m_flTransitionTime == -1 && (m_hOldFogController == m_Local.m_PlayerFog.m_hCtrl) ) + { + fogparams_t *pFogParams = &(m_Local.m_PlayerFog.m_hCtrl->m_fog); + if ( m_CurrentFog != *pFogParams ) + { + /* + Msg("FORCING UPDATE: (%d,%d,%d) %.0f,%.0f -> (%d,%d,%d) %.0f,%.0f (%.2f seconds)\n", + m_CurrentFog.colorPrimary.GetR(), m_CurrentFog.colorPrimary.GetB(), m_CurrentFog.colorPrimary.GetG(), + m_CurrentFog.start.Get(), m_CurrentFog.end.Get(), + pFogParams->colorPrimary.GetR(), pFogParams->colorPrimary.GetB(), pFogParams->colorPrimary.GetG(), + pFogParams->start.Get(), pFogParams->end.Get(), pFogParams->duration.Get() );*/ + + + m_CurrentFog = *pFogParams; + } + } + } + else + { + if ( m_CurrentFog.farz != -1 || m_CurrentFog.enable != false ) + { + // No fog controller in this level. Use default fog parameters. + m_CurrentFog.farz = -1; + m_CurrentFog.enable = false; + } + } + + // Update the fog blending state - of necessary. + UpdateFogBlend(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void C_BasePlayer::UpdateFogBlend( void ) +{ + // Transition. + if ( m_Local.m_PlayerFog.m_flTransitionTime != -1 ) + { + float flTimeDelta = gpGlobals->curtime - m_Local.m_PlayerFog.m_flTransitionTime; + if ( flTimeDelta < m_CurrentFog.duration ) + { + float flScale = flTimeDelta / m_CurrentFog.duration; + m_CurrentFog.colorPrimary.SetR( ( m_Local.m_PlayerFog.m_NewColor.r * flScale ) + ( m_Local.m_PlayerFog.m_OldColor.r * ( 1.0f - flScale ) ) ); + m_CurrentFog.colorPrimary.SetG( ( m_Local.m_PlayerFog.m_NewColor.g * flScale ) + ( m_Local.m_PlayerFog.m_OldColor.g * ( 1.0f - flScale ) ) ); + m_CurrentFog.colorPrimary.SetB( ( m_Local.m_PlayerFog.m_NewColor.b * flScale ) + ( m_Local.m_PlayerFog.m_OldColor.b * ( 1.0f - flScale ) ) ); + m_CurrentFog.start.Set( ( m_Local.m_PlayerFog.m_flNewStart * flScale ) + ( ( m_Local.m_PlayerFog.m_flOldStart * ( 1.0f - flScale ) ) ) ); + m_CurrentFog.end.Set( ( m_Local.m_PlayerFog.m_flNewEnd * flScale ) + ( ( m_Local.m_PlayerFog.m_flOldEnd * ( 1.0f - flScale ) ) ) ); + } + else + { + // Slam the final fog values. + m_CurrentFog.colorPrimary.SetR( m_Local.m_PlayerFog.m_NewColor.r ); + m_CurrentFog.colorPrimary.SetG( m_Local.m_PlayerFog.m_NewColor.g ); + m_CurrentFog.colorPrimary.SetB( m_Local.m_PlayerFog.m_NewColor.b ); + m_CurrentFog.start.Set( m_Local.m_PlayerFog.m_flNewStart ); + m_CurrentFog.end.Set( m_Local.m_PlayerFog.m_flNewEnd ); + m_Local.m_PlayerFog.m_flTransitionTime = -1; + + /* + Msg("Finished transition to (%d,%d,%d) %.0f,%.0f\n", + m_CurrentFog.colorPrimary.GetR(), m_CurrentFog.colorPrimary.GetB(), m_CurrentFog.colorPrimary.GetG(), + m_CurrentFog.start.Get(), m_CurrentFog.end.Get() );*/ + + } + } +} + +void CC_DumpClientSoundscapeData( const CCommand& args ) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer ) + return; + + Msg("Client Soundscape data dump:\n"); + Msg(" Position: %.2f %.2f %.2f\n", pPlayer->GetAbsOrigin().x, pPlayer->GetAbsOrigin().y, pPlayer->GetAbsOrigin().z ); + Msg(" soundscape index: %d\n", pPlayer->m_Local.m_audio.soundscapeIndex ); + Msg(" entity index: %d\n", pPlayer->m_Local.m_audio.ent.Get() ? pPlayer->m_Local.m_audio.ent->entindex() : -1 ); + if ( pPlayer->m_Local.m_audio.ent.Get() ) + { + Msg(" entity pos: %.2f %.2f %.2f\n", pPlayer->m_Local.m_audio.ent.Get()->GetAbsOrigin().x, pPlayer->m_Local.m_audio.ent.Get()->GetAbsOrigin().y, pPlayer->m_Local.m_audio.ent.Get()->GetAbsOrigin().z ); + if ( pPlayer->m_Local.m_audio.ent.Get()->IsDormant() ) + { + Msg(" ENTITY IS DORMANT\n"); + } + } + bool bFoundOne = false; + for ( int i = 0; i < NUM_AUDIO_LOCAL_SOUNDS; i++ ) + { + if ( pPlayer->m_Local.m_audio.localBits & (1<m_Local.m_audio.localSound[i]; + Msg(" %d: %.2f %.2f %.2f\n", i, vecPos.x,vecPos.y, vecPos.z ); + } + } + + Msg("End dump.\n"); +} +static ConCommand soundscape_dumpclient("soundscape_dumpclient", CC_DumpClientSoundscapeData, "Dumps the client's soundscape data.\n", FCVAR_CHEAT); diff --git a/game/client/c_baseplayer.h b/game/client/c_baseplayer.h new file mode 100644 index 00000000..b3669bcc --- /dev/null +++ b/game/client/c_baseplayer.h @@ -0,0 +1,639 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client-side CBasePlayer. +// +// - Manages the player's flashlight effect. +// +//=============================================================================// + +#ifndef C_BASEPLAYER_H +#define C_BASEPLAYER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_playerlocaldata.h" +#include "c_basecombatcharacter.h" +#include "playerstate.h" +#include "usercmd.h" +#include "shareddefs.h" +#include "timedevent.h" +#include "smartptr.h" +#include "fx_water.h" +#include "hintsystem.h" +#include "soundemittersystem/isoundemittersystembase.h" +#include "c_env_fog_controller.h" + +class C_BaseCombatWeapon; +class C_BaseViewModel; +class C_FuncLadder; +class CFlashlightEffect; + +extern int g_nKillCamMode; +extern int g_nKillCamTarget1; +extern int g_nKillCamTarget2; + +class C_CommandContext +{ +public: + bool needsprocessing; + + CUserCmd cmd; + int command_number; +}; + +class C_PredictionError +{ +public: + float time; + Vector error; +}; + +#define CHASE_CAM_DISTANCE 96.0f +#define WALL_OFFSET 6.0f + + +bool IsInFreezeCam( void ); + +//----------------------------------------------------------------------------- +// Purpose: Base Player class +//----------------------------------------------------------------------------- +class C_BasePlayer : public C_BaseCombatCharacter +{ +public: + DECLARE_CLASS( C_BasePlayer, C_BaseCombatCharacter ); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + DECLARE_INTERPOLATION(); + + C_BasePlayer(); + virtual ~C_BasePlayer(); + + virtual void Spawn( void ); + virtual void SharedSpawn(); // Shared between client and server. + + // IClientEntity overrides. + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + + virtual void PreDataUpdate( DataUpdateType_t updateType ); + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void ReceiveMessage( int classID, bf_read &msg ); + + virtual void OnRestore(); + + virtual void AddEntity( void ); + + virtual void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); + + virtual void GetToolRecordingState( KeyValues *msg ); + + void SetAnimationExtension( const char *pExtension ); + + C_BaseViewModel *GetViewModel( int viewmodelindex = 0 ); + C_BaseCombatWeapon *GetActiveWeapon( void ) const; + const char *GetTracerType( void ); + + // View model prediction setup + virtual void CalcView( Vector &eyeOrigin, QAngle &eyeAngles, float &zNear, float &zFar, float &fov ); + virtual void CalcViewModelView( const Vector& eyeOrigin, const QAngle& eyeAngles); + + + // Handle view smoothing when going up stairs + void SmoothViewOnStairs( Vector& eyeOrigin ); + virtual float CalcRoll (const QAngle& angles, const Vector& velocity, float rollangle, float rollspeed); + void CalcViewRoll( QAngle& eyeAngles ); + void CreateWaterEffects( void ); + + virtual void SetPlayerUnderwater( bool state ); + void UpdateUnderwaterState( void ); + bool IsPlayerUnderwater( void ) { return m_bPlayerUnderwater; } + + virtual Vector Weapon_ShootPosition(); + virtual void Weapon_DropPrimary( void ) {} + + virtual Vector GetAutoaimVector( float flScale ); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + + // Input handling + virtual bool CreateMove( float flInputSampleTime, CUserCmd *pCmd ); + virtual void AvoidPhysicsProps( CUserCmd *pCmd ); + + virtual void PlayerUse( void ); + CBaseEntity *FindUseEntity( void ); + virtual bool IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ); + + // Data handlers + virtual bool IsPlayer( void ) const { return true; } + virtual int GetHealth() const { return m_iHealth; } + + int GetBonusProgress() const { return m_iBonusProgress; } + int GetBonusChallenge() const { return m_iBonusChallenge; } + + // observer mode + virtual int GetObserverMode() const; + virtual CBaseEntity *GetObserverTarget() const; + void SetObserverTarget( EHANDLE hObserverTarget ); + + bool AudioStateIsUnderwater( Vector vecMainViewOrigin ); + + bool IsObserver() const; + bool IsHLTV() const; + void ResetObserverMode(); + bool IsBot( void ) const { return false; } + + // Eye position.. + virtual Vector EyePosition(); + virtual const QAngle &EyeAngles(); // Direction of eyes + void EyePositionAndVectors( Vector *pPosition, Vector *pForward, Vector *pRight, Vector *pUp ); + virtual const QAngle &LocalEyeAngles(); // Direction of eyes + + // This can be overridden to return something other than m_pRagdoll if the mod uses separate + // entities for ragdolls. + virtual IRagdoll* GetRepresentativeRagdoll() const; + + // override the initial bone position for ragdolls + virtual void GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ); + + // Returns eye vectors + void EyeVectors( Vector *pForward, Vector *pRight = NULL, Vector *pUp = NULL ); + void CacheVehicleView( void ); // Calculate and cache the position of the player in the vehicle + + + bool IsSuitEquipped( void ) { return m_Local.m_bWearingSuit; }; + + // Team handlers + virtual void TeamChange( int iNewTeam ); + + // Flashlight + void Flashlight( void ); + void UpdateFlashlight( void ); + + // Weapon selection code + virtual bool IsAllowedToSwitchWeapons( void ) { return !IsObserver(); } + virtual C_BaseCombatWeapon *GetActiveWeaponForSelection( void ); + + // Returns the view model if this is the local player. If you're in third person or + // this is a remote player, it returns the active weapon + // (and its appropriate left/right weapon if this is TF2). + virtual C_BaseAnimating* GetRenderedWeaponModel(); + + virtual bool IsOverridingViewmodel( void ) { return false; }; + virtual int DrawOverriddenViewmodel( C_BaseViewModel *pViewmodel, int flags ) { return 0; }; + + virtual float GetDefaultAnimSpeed( void ) { return 1.0; } + + void SetMaxSpeed( float flMaxSpeed ) { m_flMaxspeed = flMaxSpeed; } + float MaxSpeed() const { return m_flMaxspeed; } + + // Should this object cast shadows? + virtual ShadowType_t ShadowCastType() { return SHADOWS_NONE; } + + virtual bool ShouldReceiveProjectedTextures( int flags ) + { + return false; + } + + + bool IsLocalPlayer( void ) const; + + // Global/static methods + virtual void ThirdPersonSwitch( bool bThirdperson ); + static bool ShouldDrawLocalPlayer(); + static C_BasePlayer *GetLocalPlayer( void ); + int GetUserID( void ); + virtual bool CanSetSoundMixer( void ); + + // Called by the view model if its rendering is being overridden. + virtual bool ViewModel_IsTransparent( void ); + +#if !defined( NO_ENTITY_PREDICTION ) + void AddToPlayerSimulationList( C_BaseEntity *other ); + void SimulatePlayerSimulatedEntities( void ); + void RemoveFromPlayerSimulationList( C_BaseEntity *ent ); + void ClearPlayerSimulationList( void ); +#endif + + virtual void PhysicsSimulate( void ); + virtual unsigned int PhysicsSolidMaskForEntity( void ) const { return MASK_PLAYERSOLID; } + + // Prediction stuff + virtual bool ShouldPredict( void ); + + virtual void PreThink( void ); + virtual void PostThink( void ); + + virtual void ItemPreFrame( void ); + virtual void ItemPostFrame( void ); + virtual void AbortReload( void ); + + virtual void SelectLastItem(void); + virtual void Weapon_SetLast( C_BaseCombatWeapon *pWeapon ); + virtual bool Weapon_ShouldSetLast( C_BaseCombatWeapon *pOldWeapon, C_BaseCombatWeapon *pNewWeapon ) { return true; } + virtual bool Weapon_ShouldSelectItem( C_BaseCombatWeapon *pWeapon ); + virtual bool Weapon_Switch( C_BaseCombatWeapon *pWeapon, int viewmodelindex = 0 ); // Switch to given weapon if has ammo (false if failed) + virtual C_BaseCombatWeapon *GetLastWeapon( void ) { return m_hLastWeapon.Get(); } + void ResetAutoaim( void ); + virtual void SelectItem( const char *pstr, int iSubType = 0 ); + + virtual void UpdateClientData( void ); + + virtual float GetFOV( void ); + int GetDefaultFOV( void ) const; + virtual bool IsZoomed( void ) { return false; } + bool SetFOV( CBaseEntity *pRequester, int FOV, float zoomRate, int iZoomStart = 0 ); + void ClearZoomOwner( void ); + + float GetFOVDistanceAdjustFactor(); + + virtual void ViewPunch( const QAngle &angleOffset ); + void ViewPunchReset( float tolerance = 0 ); + + void UpdateButtonState( int nUserCmdButtonMask ); + int GetImpulse( void ) const; + + virtual void Simulate(); + + virtual bool ShouldInterpolate(); + + virtual bool ShouldDraw(); + virtual int DrawModel( int flags ); + + // Called when not in tactical mode. Allows view to be overriden for things like driving a tank. + virtual void OverrideView( CViewSetup *pSetup ); + + // returns the player name + const char * GetPlayerName(); + virtual const Vector GetPlayerMins( void ) const; // uses local player + virtual const Vector GetPlayerMaxs( void ) const; // uses local player + + // Is the player dead? + bool IsPlayerDead(); + bool IsPoisoned( void ) { return m_Local.m_bPoisoned; } + + C_BaseEntity *GetUseEntity(); + + // Vehicles... + IClientVehicle *GetVehicle(); + + bool IsInAVehicle() const { return ( NULL != m_hVehicle.Get() ) ? true : false; } + virtual void SetVehicleRole( int nRole ); + void LeaveVehicle( void ); + + bool UsingStandardWeaponsInVehicle( void ); + + virtual void SetAnimation( PLAYER_ANIM playerAnim ); + + float GetTimeBase( void ) const; + float GetFinalPredictedTime() const; + + bool IsInVGuiInputMode() const; + bool IsInViewModelVGuiInputMode() const; + + C_CommandContext *GetCommandContext(); + + // Get the command number associated with the current usercmd we're running (if in predicted code). + int CurrentCommandNumber() const; + const CUserCmd *GetCurrentUserCommand() const; + + const QAngle& GetPunchAngle(); + void SetPunchAngle( const QAngle &angle ); + + float GetWaterJumpTime() const; + void SetWaterJumpTime( float flWaterJumpTime ); + float GetSwimSoundTime( void ) const; + void SetSwimSoundTime( float flSwimSoundTime ); + + float GetDeathTime( void ) { return m_flDeathTime; } + + void SetPreviouslyPredictedOrigin( const Vector &vecAbsOrigin ); + const Vector &GetPreviouslyPredictedOrigin() const; + + // CS wants to allow small FOVs for zoomed-in AWPs. + virtual float GetMinFOV() const; + + virtual void DoMuzzleFlash(); + virtual void PlayPlayerJingle(); + + virtual void UpdateStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity ); + virtual void PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ); + virtual surfacedata_t * GetFootstepSurface( const Vector &origin, const char *surfaceName ); + virtual void GetStepSoundVelocities( float *velwalk, float *velrun ); + virtual void SetStepSoundTime( stepsoundtimes_t iStepSoundTime, bool bWalking ); + + // Called by prediction when it detects a prediction correction. + // vDelta is the line from where the client had predicted the player to at the usercmd in question, + // to where the server says the client should be at said usercmd. + void NotePredictionError( const Vector &vDelta ); + + // Called by the renderer to apply the prediction error smoothing. + void GetPredictionErrorSmoothingVector( Vector &vOffset ); + + virtual void ExitLadder() {} + surfacedata_t *GetLadderSurface( const Vector &origin ); + + surfacedata_t *GetSurfaceData( void ) { return m_pSurfaceData; } + + void SetLadderNormal( Vector vecLadderNormal ) { m_vecLadderNormal = vecLadderNormal; } + + // Hints + virtual CHintSystem *Hints( void ) { return NULL; } + bool ShouldShowHints( void ) { return Hints() ? Hints()->ShouldShowHints() : false; } + bool HintMessage( int hint, bool bForce = false, bool bOnlyIfClear = false ) { return Hints() ? Hints()->HintMessage( hint, bForce, bOnlyIfClear ) : false; } + void HintMessage( const char *pMessage ) { if (Hints()) Hints()->HintMessage( pMessage ); } + + virtual IMaterial *GetHeadLabelMaterial( void ); + + // Fog + fogparams_t *GetFogParams( void ) { return &m_CurrentFog; } + void FogControllerChanged( bool bSnap ); + void UpdateFogController( void ); + void UpdateFogBlend( void ); + + void IncrementEFNoInterpParity(); + int GetEFNoInterpParity() const; + + float GetFOVTime( void ){ return m_flFOVTime; } + + virtual void OnAchievementAchieved( int iAchievement ) {} + +protected: + fogparams_t m_CurrentFog; + EHANDLE m_hOldFogController; + +public: + int m_StuckLast; + + // Data for only the local player + CNetworkVarEmbedded( CPlayerLocalData, m_Local ); + + // Data common to all other players, too + CPlayerState pl; + + // Player FOV values + int m_iFOV; // field of view + int m_iFOVStart; // starting value of the FOV changing over time (client only) + float m_flFOVTime; // starting time of the FOV zoom + int m_iDefaultFOV; // default FOV if no other zooms are occurring + EHANDLE m_hZoomOwner; // This is a pointer to the entity currently controlling the player's zoom + // Only this entity can change the zoom state once it has ownership + + // For weapon prediction + bool m_fOnTarget; //Is the crosshair on a target? + + char m_szAnimExtension[32]; + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + int m_nButtons; + + CUserCmd *m_pCurrentCommand; + + // Movement constraints + EHANDLE m_hConstraintEntity; + Vector m_vecConstraintCenter; + float m_flConstraintRadius; + float m_flConstraintWidth; + float m_flConstraintSpeedFactor; + +protected: + + void CalcPlayerView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + void CalcVehicleView(IClientVehicle *pVehicle, Vector& eyeOrigin, QAngle& eyeAngles, + float& zNear, float& zFar, float& fov ); + virtual void CalcObserverView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + virtual Vector GetChaseCamViewOffset( CBaseEntity *target ); + void CalcChaseCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + void CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + virtual void CalcDeathCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + void CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov); + virtual void CalcFreezeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + + // Check to see if we're in vgui input mode... + void DetermineVguiInputMode( CUserCmd *pCmd ); + + // Used by prediction, sets the view angles for the player + virtual void SetLocalViewAngles( const QAngle &viewAngles ); + virtual void SetViewAngles( const QAngle& ang ); + + // used by client side player footsteps + surfacedata_t* GetGroundSurface(); + +protected: + // Did we just enter a vehicle this frame? + bool JustEnteredVehicle(); + +// DATA + int m_iObserverMode; // if in spectator mode != 0 + EHANDLE m_hObserverTarget; // current observer target + float m_flObserverChaseDistance; // last distance to observer traget + Vector m_vecFreezeFrameStart; + float m_flFreezeFrameStartTime; // Time at which we entered freeze frame observer mode + float m_flFreezeFrameDistance; + bool m_bWasFreezeFraming; + float m_flDeathTime; // last time player died + + float m_flStepSoundTime; + +private: + // Make sure no one calls this... + C_BasePlayer& operator=( const C_BasePlayer& src ); + C_BasePlayer( const C_BasePlayer & ); // not defined, not accessible + + // Vehicle stuff. + EHANDLE m_hVehicle; + EHANDLE m_hOldVehicle; + EHANDLE m_hUseEntity; + + float m_flMaxspeed; + + int m_iBonusProgress; + int m_iBonusChallenge; + + CInterpolatedVar< Vector > m_iv_vecViewOffset; + + // Not replicated + Vector m_vecWaterJumpVel; + float m_flWaterJumpTime; // used to be called teleport_time + int m_nImpulse; + + float m_flSwimSoundTime; + Vector m_vecLadderNormal; + + QAngle m_vecOldViewAngles; + + bool m_bWasFrozen; + int m_flPhysics; + + int m_nTickBase; + int m_nFinalPredictedTick; + + EHANDLE m_pCurrentVguiScreen; + + + // Player flashlight dynamic light pointers + CFlashlightEffect *m_pFlashlight; + + typedef CHandle CBaseCombatWeaponHandle; + CNetworkVar( CBaseCombatWeaponHandle, m_hLastWeapon ); + +#if !defined( NO_ENTITY_PREDICTION ) + CUtlVector< CHandle< C_BaseEntity > > m_SimulatedByThisPlayer; +#endif + + // players own view models, left & right hand + CHandle< C_BaseViewModel > m_hViewModel[ MAX_VIEWMODELS ]; + + float m_flOldPlayerZ; + float m_flOldPlayerViewOffsetZ; + + Vector m_vecVehicleViewOrigin; // Used to store the calculated view of the player while riding in a vehicle + QAngle m_vecVehicleViewAngles; // Vehicle angles + float m_flVehicleViewFOV; + int m_nVehicleViewSavedFrame; // Used to mark which frame was the last one the view was calculated for + + // For UI purposes... + int m_iOldAmmo[ MAX_AMMO_TYPES ]; + + C_CommandContext m_CommandContext; + + // For underwater effects + float m_flWaterSurfaceZ; + bool m_bResampleWaterSurface; + TimedEvent m_tWaterParticleTimer; + CSmartPtr m_pWaterEmitter; + + bool m_bPlayerUnderwater; + + friend class CPrediction; + + // HACK FOR TF2 Prediction + friend class CTFGameMovementRecon; + friend class CGameMovement; + friend class CTFGameMovement; + friend class CHL1GameMovement; + friend class CCSGameMovement; + friend class CHL2GameMovement; + friend class CDODGameMovement; + friend class CPortalGameMovement; + + // Accessors for gamemovement + float GetStepSize( void ) const { return m_Local.m_flStepSize; } + + float m_flNextAvoidanceTime; + float m_flAvoidanceRight; + float m_flAvoidanceForward; + float m_flAvoidanceDotForward; + float m_flAvoidanceDotRight; + +protected: + virtual bool IsDucked( void ) const { return m_Local.m_bDucked; } + virtual bool IsDucking( void ) const { return m_Local.m_bDucking; } + virtual float GetFallVelocity( void ) { return m_Local.m_flFallVelocity; } + void ForceSetupBonesAtTimeFakeInterpolation( matrix3x4_t *pBonesOut, float curtimeOffset ); + + float m_flLaggedMovementValue; + + // These are used to smooth out prediction corrections. They're most useful when colliding with + // vphysics objects. The server will be sending constant prediction corrections, and these can help + // the errors not be so jerky. + Vector m_vecPredictionError; + float m_flPredictionErrorTime; + + Vector m_vecPreviouslyPredictedOrigin; // Used to determine if non-gamemovement game code has teleported, or tweaked the player's origin + + char m_szLastPlaceName[MAX_PLACE_NAME_LENGTH]; // received from the server + + // Texture names and surface data, used by CGameMovement + int m_surfaceProps; + surfacedata_t* m_pSurfaceData; + float m_surfaceFriction; + char m_chTextureType; + + bool m_bSentFreezeFrame; + float m_flFreezeZOffset; + byte m_ubEFNoInterpParity; + byte m_ubOldEFNoInterpParity; + +private: + + struct StepSoundCache_t + { + StepSoundCache_t() : m_usSoundNameIndex( 0 ) {} + CSoundParameters m_SoundParameters; + unsigned short m_usSoundNameIndex; + }; + // One for left and one for right side of step + StepSoundCache_t m_StepSoundCache[ 2 ]; + +public: + + const char *GetLastKnownPlaceName( void ) const { return m_szLastPlaceName; } // return the last nav place name the player occupied + + float GetLaggedMovementValue( void ){ return m_flLaggedMovementValue; } + bool ShouldGoSouth( Vector vNPCForward, Vector vNPCRight ); //Such a bad name. + + void SetOldPlayerZ( float flOld ) { m_flOldPlayerZ = flOld; } +}; + +EXTERN_RECV_TABLE(DT_BasePlayer); + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- +inline C_BasePlayer *ToBasePlayer( C_BaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsPlayer() ) + return NULL; + +#if _DEBUG + Assert( dynamic_cast( pEntity ) != NULL ); +#endif + + return static_cast( pEntity ); +} + +inline C_BaseEntity *C_BasePlayer::GetUseEntity() +{ + return m_hUseEntity; +} + + +inline IClientVehicle *C_BasePlayer::GetVehicle() +{ + C_BaseEntity *pVehicleEnt = m_hVehicle.Get(); + return pVehicleEnt ? pVehicleEnt->GetClientVehicle() : NULL; +} + +inline bool C_BasePlayer::IsObserver() const +{ + return (GetObserverMode() != OBS_MODE_NONE); +} + +inline int C_BasePlayer::GetImpulse( void ) const +{ + return m_nImpulse; +} + + +inline C_CommandContext* C_BasePlayer::GetCommandContext() +{ + return &m_CommandContext; +} + +inline int CBasePlayer::CurrentCommandNumber() const +{ + Assert( m_pCurrentCommand ); + return m_pCurrentCommand->command_number; +} + +inline const CUserCmd *CBasePlayer::GetCurrentUserCommand() const +{ + Assert( m_pCurrentCommand ); + return m_pCurrentCommand; +} + +#endif // C_BASEPLAYER_H diff --git a/game/client/c_basetempentity.cpp b/game/client/c_basetempentity.cpp new file mode 100644 index 00000000..8b34e837 --- /dev/null +++ b/game/client/c_basetempentity.cpp @@ -0,0 +1,201 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core Temp Entity client implementation. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS(C_BaseTempEntity, DT_BaseTempEntity, CBaseTempEntity); + +BEGIN_RECV_TABLE_NOBASE(C_BaseTempEntity, DT_BaseTempEntity) +END_RECV_TABLE() + + +// Global list of temp entity classes +C_BaseTempEntity *C_BaseTempEntity::s_pTempEntities = NULL; + +// Global list of dynamic temp entities +C_BaseTempEntity *C_BaseTempEntity::s_pDynamicEntities = NULL; + +//----------------------------------------------------------------------------- +// Purpose: Returns head of list +// Output : CBaseTempEntity * -- head of list +//----------------------------------------------------------------------------- +C_BaseTempEntity *C_BaseTempEntity::GetDynamicList( void ) +{ + return s_pDynamicEntities; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns head of list +// Output : CBaseTempEntity * -- head of list +//----------------------------------------------------------------------------- +C_BaseTempEntity *C_BaseTempEntity::GetList( void ) +{ + return s_pTempEntities; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : +//----------------------------------------------------------------------------- +C_BaseTempEntity::C_BaseTempEntity( void ) +{ + // Add to list + m_pNext = s_pTempEntities; + s_pTempEntities = this; + + m_pNextDynamic = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : +//----------------------------------------------------------------------------- +C_BaseTempEntity::~C_BaseTempEntity( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get next temp ent in chain +// Output : CBaseTempEntity * +//----------------------------------------------------------------------------- +C_BaseTempEntity *C_BaseTempEntity::GetNext( void ) +{ + return m_pNext; +} + +//----------------------------------------------------------------------------- +// Purpose: Get next temp ent in chain +// Output : CBaseTempEntity * +//----------------------------------------------------------------------------- +C_BaseTempEntity *C_BaseTempEntity::GetNextDynamic( void ) +{ + return m_pNextDynamic; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseTempEntity::Precache( void ) +{ + // Nothing... +} + +//----------------------------------------------------------------------------- +// Purpose: Called at startup to allow temp entities to precache any models/sounds that they need +//----------------------------------------------------------------------------- +void C_BaseTempEntity::PrecacheTempEnts( void ) +{ + C_BaseTempEntity *te = GetList(); + while ( te ) + { + te->Precache(); + te = te->GetNext(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called at startup and level load to clear out leftover temp entities +//----------------------------------------------------------------------------- +void C_BaseTempEntity::ClearDynamicTempEnts( void ) +{ + C_BaseTempEntity *next; + C_BaseTempEntity *te = s_pDynamicEntities; + while ( te ) + { + next = te->GetNextDynamic(); + delete te; + te = next; + } + + s_pDynamicEntities = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Called at startup and level load to clear out leftover temp entities +//----------------------------------------------------------------------------- +void C_BaseTempEntity::CheckDynamicTempEnts( void ) +{ + C_BaseTempEntity *next, *newlist = NULL; + C_BaseTempEntity *te = s_pDynamicEntities; + while ( te ) + { + next = te->GetNextDynamic(); + if ( te->ShouldDestroy() ) + { + delete te; + } + else + { + te->m_pNextDynamic = newlist; + newlist = te; + } + te = next; + } + + s_pDynamicEntities = newlist; +} + +//----------------------------------------------------------------------------- +// Purpose: Dynamic/non-singleton temp entities are initialized by +// calling into here. They should be added to a list of C_BaseTempEntities so +// that their memory can be deallocated appropriately. +// Input : *pEnt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseTempEntity::Init( int entnum, int iSerialNum ) +{ + if ( entnum != -1 ) + { + Assert( 0 ); + } + + // Link into dynamic entity list + m_pNextDynamic = s_pDynamicEntities; + s_pDynamicEntities = this; + + return true; +} + + +void C_BaseTempEntity::Release() +{ + Assert( !"C_BaseTempEntity::Release should never be called" ); +} + + +void C_BaseTempEntity::NotifyShouldTransmit( ShouldTransmitState_t state ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_BaseTempEntity::PreDataUpdate( DataUpdateType_t updateType ) +{ + // TE's may or may not implement this +} + + +int C_BaseTempEntity::entindex( void ) const { Assert( 0 ); return 0; } +void C_BaseTempEntity::PostDataUpdate( DataUpdateType_t updateType ) { Assert( 0 ); } +void C_BaseTempEntity::OnPreDataChanged( DataUpdateType_t updateType ) { Assert( 0 ); } +void C_BaseTempEntity::OnDataChanged( DataUpdateType_t updateType ) { Assert( 0 ); } +void C_BaseTempEntity::SetDormant( bool bDormant ) { Assert( 0 ); } +bool C_BaseTempEntity::IsDormant( void ) { Assert( 0 ); return false; }; +void C_BaseTempEntity::ReceiveMessage( int classID, bf_read &msg ) { Assert( 0 ); } +void C_BaseTempEntity::SetDestroyedOnRecreateEntities( void ) { Assert(0); } + +void* C_BaseTempEntity::GetDataTableBasePtr() +{ + return this; +} + diff --git a/game/client/c_basetempentity.h b/game/client/c_basetempentity.h new file mode 100644 index 00000000..8e550388 --- /dev/null +++ b/game/client/c_basetempentity.h @@ -0,0 +1,121 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_BASETEMPENTITY_H +#define C_BASETEMPENTITY_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "client_class.h" +#include "iclientnetworkable.h" +#include "c_recipientfilter.h" + + +//----------------------------------------------------------------------------- +// Purpose: Base class for TEs. All TEs should derive from this and at +// least implement OnDataChanged to be notified when the TE has been received +// from the server +//----------------------------------------------------------------------------- +class C_BaseTempEntity : public IClientUnknown, public IClientNetworkable + +{ +public: + DECLARE_CLASS_NOBASE( C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_BaseTempEntity( void ); + virtual ~C_BaseTempEntity( void ); + + +// IClientUnknown implementation. +public: + + virtual void SetRefEHandle( const CBaseHandle &handle ) { Assert( false ); } + virtual const CBaseHandle& GetRefEHandle() const { return *((CBaseHandle*)0); } + + virtual IClientUnknown* GetIClientUnknown() { return this; } + virtual ICollideable* GetCollideable() { return 0; } + virtual IClientNetworkable* GetClientNetworkable() { return this; } + virtual IClientRenderable* GetClientRenderable() { return 0; } + virtual IClientEntity* GetIClientEntity() { return 0; } + virtual C_BaseEntity* GetBaseEntity() { return 0; } + virtual IClientThinkable* GetClientThinkable() { return 0; } + + +// IClientNetworkable overrides. +public: + + virtual void Release(); + virtual void NotifyShouldTransmit( ShouldTransmitState_t state ); + virtual void PreDataUpdate( DataUpdateType_t updateType ); + virtual void PostDataUpdate( DataUpdateType_t updateType ); + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void SetDormant( bool bDormant ); + virtual bool IsDormant( void ); + virtual int entindex( void ) const; + virtual void ReceiveMessage( int classID, bf_read &msg ); + virtual void* GetDataTableBasePtr(); + virtual void SetDestroyedOnRecreateEntities( void ); + +public: + + // Dummy for CNetworkVars. + void NetworkStateChanged() {} + void NetworkStateChanged( void *pVar ) {} + + virtual bool Init(int entnum, int iSerialNum); + + virtual void Precache( void ); + + // For dynamic entities, return true to allow destruction + virtual bool ShouldDestroy( void ) { return false; }; + + C_BaseTempEntity *GetNext( void ); + + // Get list of tempentities + static C_BaseTempEntity *GetList( void ); + + C_BaseTempEntity *GetNextDynamic( void ); + + // Determine the color modulation amount + void GetColorModulation( float* color ) + { + assert(color); + color[0] = color[1] = color[2] = 1.0f; + } + + // Should this object be able to have shadows cast onto it? + virtual bool ShouldReceiveProjectedTextures( int flags ) { return false; } + +// Static members +public: + // List of dynamically allocated temp entis + static C_BaseTempEntity *GetDynamicList(); + + // Called at startup to allow temp entities to precache any models/sounds that they need + static void PrecacheTempEnts( void ); + + static void ClearDynamicTempEnts( void ); + + static void CheckDynamicTempEnts( void ); + +private: + + // Next in chain + C_BaseTempEntity *m_pNext; + C_BaseTempEntity *m_pNextDynamic; + + // TEs add themselves to this list for the executable. + static C_BaseTempEntity *s_pTempEntities; + static C_BaseTempEntity *s_pDynamicEntities; +}; + + +#endif // C_BASETEMPENTITY_H diff --git a/game/client/c_baseviewmodel.cpp b/game/client/c_baseviewmodel.cpp new file mode 100644 index 00000000..f879867e --- /dev/null +++ b/game/client/c_baseviewmodel.cpp @@ -0,0 +1,432 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client side view model implementation. Responsible for drawing +// the view model. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_baseviewmodel.h" +#include "model_types.h" +#include "hud.h" +#include "view_shared.h" +#include "iviewrender.h" +#include "view.h" +#include "mathlib/vmatrix.h" +#include "cl_animevent.h" +#include "eventlist.h" +#include "tools/bonelist.h" +#include +#include "hltvcamera.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef CSTRIKE_DLL + ConVar cl_righthand( "cl_righthand", "1", FCVAR_ARCHIVE, "Use right-handed view models." ); +#endif + +void PostToolMessage( HTOOLHANDLE hEntity, KeyValues *msg ); + +void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ) +{ + // Presumably, SetUpView has been called so we know our FOV and render origin. + const CViewSetup *pViewSetup = view->GetPlayerViewSetup(); + + float worldx = tan( pViewSetup->fov * M_PI/360.0 ); + float viewx = tan( pViewSetup->fovViewmodel * M_PI/360.0 ); + + // aspect ratio cancels out, so only need one factor + // the difference between the screen coordinates of the 2 systems is the ratio + // of the coefficients of the projection matrices (tan (fov/2) is that coefficient) + float factorX = worldx / viewx; + + float factorY = factorX; + + // Get the coordinates in the viewer's space. + Vector tmp = vOrigin - pViewSetup->origin; + Vector vTransformed( MainViewRight().Dot( tmp ), MainViewUp().Dot( tmp ), MainViewForward().Dot( tmp ) ); + + // Now squash X and Y. + if ( bInverse ) + { + if ( factorX != 0 && factorY != 0 ) + { + vTransformed.x /= factorX; + vTransformed.y /= factorY; + } + else + { + vTransformed.x = 0.0f; + vTransformed.y = 0.0f; + } + } + else + { + vTransformed.x *= factorX; + vTransformed.y *= factorY; + } + + + + // Transform back to world space. + Vector vOut = (MainViewRight() * vTransformed.x) + (MainViewUp() * vTransformed.y) + (MainViewForward() * vTransformed.z); + vOrigin = pViewSetup->origin + vOut; +} + + +void C_BaseViewModel::FormatViewModelAttachment( int nAttachment, matrix3x4_t &attachmentToWorld ) +{ + Vector vecOrigin; + MatrixPosition( attachmentToWorld, vecOrigin ); + ::FormatViewModelAttachment( vecOrigin, false ); + PositionMatrix( vecOrigin, attachmentToWorld ); +} + + +bool C_BaseViewModel::IsViewModel() const +{ + return true; +} + +void C_BaseViewModel::UncorrectViewModelAttachment( Vector &vOrigin ) +{ + // Unformat the attachment. + ::FormatViewModelAttachment( vOrigin, true ); +} + + +//----------------------------------------------------------------------------- +// Purpose +//----------------------------------------------------------------------------- +void C_BaseViewModel::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + // We override sound requests so that we can play them locally on the owning player + if ( ( event == AE_CL_PLAYSOUND ) || ( event == CL_EVENT_SOUND ) ) + { + // Only do this if we're owned by someone + if ( GetOwner() != NULL ) + { + CLocalPlayerFilter filter; + EmitSound( filter, GetOwner()->GetSoundSourceIndex(), options, &GetAbsOrigin() ); + return; + } + } + + // Otherwise pass the event to our associated weapon + C_BaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( pWeapon ) + { + bool bResult = pWeapon->OnFireEvent( this, origin, angles, event, options ); + if ( !bResult ) + { + BaseClass::FireEvent( origin, angles, event, options ); + } + } +} + +bool C_BaseViewModel::Interpolate( float currentTime ) +{ + CStudioHdr *pStudioHdr = GetModelPtr(); + // Make sure we reset our animation information if we've switch sequences + UpdateAnimationParity(); + + bool bret = BaseClass::Interpolate( currentTime ); + + // Hack to extrapolate cycle counter for view model + float elapsed_time = currentTime - m_flAnimTime; + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + // Predicted viewmodels have fixed up interval + if ( GetPredictable() || IsClientCreated() ) + { + Assert( pPlayer ); + float curtime = pPlayer ? pPlayer->GetFinalPredictedTime() : gpGlobals->curtime; + elapsed_time = curtime - m_flAnimTime; + // Adjust for interpolated partial frame + elapsed_time += ( gpGlobals->interpolation_amount * TICK_INTERVAL ); + } + + // Prediction errors? + if ( elapsed_time < 0 ) + { + elapsed_time = 0; + } + + float dt = elapsed_time * GetSequenceCycleRate( pStudioHdr, GetSequence() ); + if ( dt >= 1.0f ) + { + if ( !IsSequenceLooping( GetSequence() ) ) + { + dt = 0.999f; + } + else + { + dt = fmod( dt, 1.0f ); + } + } + + SetCycle( dt ); + return bret; +} + + +inline bool C_BaseViewModel::ShouldFlipViewModel() +{ +#ifdef CSTRIKE_DLL + // If cl_righthand is set, then we want them all right-handed. + CBaseCombatWeapon *pWeapon = m_hWeapon.Get(); + if ( pWeapon ) + { + const FileWeaponInfo_t *pInfo = &pWeapon->GetWpnData(); + return pInfo->m_bAllowFlipping && pInfo->m_bBuiltRightHanded != cl_righthand.GetBool(); + } +#endif + + return false; +} + + +void C_BaseViewModel::ApplyBoneMatrixTransform( matrix3x4_t& transform ) +{ + if ( ShouldFlipViewModel() ) + { + matrix3x4_t viewMatrix, viewMatrixInverse; + + // We could get MATERIAL_VIEW here, but this is called sometimes before the renderer + // has set that matrix. Luckily, this is called AFTER the CViewSetup has been initialized. + const CViewSetup *pSetup = view->GetPlayerViewSetup(); + AngleMatrix( pSetup->angles, pSetup->origin, viewMatrixInverse ); + MatrixInvert( viewMatrixInverse, viewMatrix ); + + // Transform into view space. + matrix3x4_t temp, temp2; + ConcatTransforms( viewMatrix, transform, temp ); + + // Flip it along X. + + // (This is the slower way to do it, and it equates to negating the top row). + //matrix3x4_t mScale; + //SetIdentityMatrix( mScale ); + //mScale[0][0] = 1; + //mScale[1][1] = -1; + //mScale[2][2] = 1; + //ConcatTransforms( mScale, temp, temp2 ); + temp[1][0] = -temp[1][0]; + temp[1][1] = -temp[1][1]; + temp[1][2] = -temp[1][2]; + temp[1][3] = -temp[1][3]; + + // Transform back out of view space. + ConcatTransforms( viewMatrixInverse, temp, transform ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: check if weapon viewmodel should be drawn +//----------------------------------------------------------------------------- +bool C_BaseViewModel::ShouldDraw() +{ + if ( engine->IsHLTV() ) + { + return ( HLTVCamera()->GetMode() == OBS_MODE_IN_EYE && + HLTVCamera()->GetPrimaryTarget() == GetOwner() ); + } + else + { + return BaseClass::ShouldDraw(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Render the weapon. Draw the Viewmodel if the weapon's being carried +// by this player, otherwise draw the worldmodel. +//----------------------------------------------------------------------------- +int C_BaseViewModel::DrawModel( int flags ) +{ + if ( !m_bReadyToDraw ) + return 0; + + if ( flags & STUDIO_RENDER ) + { + // Determine blending amount and tell engine + float blend = (float)( GetFxBlend() / 255.0f ); + + // Totally gone + if ( blend <= 0.0f ) + return 0; + + // Tell engine + render->SetBlend( blend ); + + float color[3]; + GetColorModulation( color ); + render->SetColorModulation( color ); + } + + CMatRenderContextPtr pRenderContext( materials ); + + if ( ShouldFlipViewModel() ) + pRenderContext->CullMode( MATERIAL_CULLMODE_CW ); + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + C_BaseCombatWeapon *pWeapon = GetOwningWeapon(); + int ret; + // If the local player's overriding the viewmodel rendering, let him do it + if ( pPlayer && pPlayer->IsOverridingViewmodel() ) + { + ret = pPlayer->DrawOverriddenViewmodel( this, flags ); + } + else if ( pWeapon && pWeapon->IsOverridingViewmodel() ) + { + ret = pWeapon->DrawOverriddenViewmodel( this, flags ); + } + else + { + ret = BaseClass::DrawModel( flags ); + } + + pRenderContext->CullMode( MATERIAL_CULLMODE_CCW ); + + // Now that we've rendered, reset the animation restart flag + if ( flags & STUDIO_RENDER ) + { + if ( m_nOldAnimationParity != m_nAnimationParity ) + { + m_nOldAnimationParity = m_nAnimationParity; + } + // Tell the weapon itself that we've rendered, in case it wants to do something + if ( pWeapon ) + { + pWeapon->ViewModelDrawn( this ); + } + } + + return ret; +} + +//----------------------------------------------------------------------------- +// Purpose: Called by the player when the player's overriding the viewmodel drawing. Avoids infinite recursion. +//----------------------------------------------------------------------------- +int C_BaseViewModel::DrawOverriddenViewmodel( int flags ) +{ + return BaseClass::DrawModel( flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int C_BaseViewModel::GetFxBlend( void ) +{ + // See if the local player wants to override the viewmodel's rendering + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer && pPlayer->IsOverridingViewmodel() ) + { + pPlayer->ComputeFxBlend(); + return pPlayer->GetFxBlend(); + } + + C_BaseCombatWeapon *pWeapon = GetOwningWeapon(); + if ( pWeapon && pWeapon->IsOverridingViewmodel() ) + { + pWeapon->ComputeFxBlend(); + return pWeapon->GetFxBlend(); + } + + return BaseClass::GetFxBlend(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseViewModel::IsTransparent( void ) +{ + // See if the local player wants to override the viewmodel's rendering + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer && pPlayer->IsOverridingViewmodel() ) + { + return pPlayer->ViewModel_IsTransparent(); + } + + C_BaseCombatWeapon *pWeapon = GetOwningWeapon(); + if ( pWeapon && pWeapon->IsOverridingViewmodel() ) + return pWeapon->ViewModel_IsTransparent(); + + return BaseClass::IsTransparent(); +} + +//----------------------------------------------------------------------------- +// Purpose: If the animation parity of the weapon has changed, we reset cycle to avoid popping +//----------------------------------------------------------------------------- +void C_BaseViewModel::UpdateAnimationParity( void ) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + // If we're predicting, then we don't use animation parity because we change the animations on the clientside + // while predicting. When not predicting, only the server changes the animations, so a parity mismatch + // tells us if we need to reset the animation. + if ( m_nOldAnimationParity != m_nAnimationParity && !GetPredictable() ) + { + float curtime = (pPlayer && IsIntermediateDataAllocated()) ? pPlayer->GetFinalPredictedTime() : gpGlobals->curtime; + // FIXME: this is bad + // Simulate a networked m_flAnimTime and m_flCycle + // FIXME: Do we need the magic 0.1? + SetCycle( 0.0f ); // GetSequenceCycleRate( GetSequence() ) * 0.1; + m_flAnimTime = curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update global map state based on data received +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_BaseViewModel::OnDataChanged( DataUpdateType_t updateType ) +{ + SetPredictionEligible( true ); + BaseClass::OnDataChanged(updateType); +} + +void C_BaseViewModel::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate(updateType); + OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add entity to visible view models list +//----------------------------------------------------------------------------- +void C_BaseViewModel::AddEntity( void ) +{ + // Server says don't interpolate this frame, so set previous info to new info. + if ( IsEffectActive(EF_NOINTERP) ) + { + ResetLatched(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseViewModel::GetBoneControllers(float controllers[MAXSTUDIOBONECTRLS]) +{ + BaseClass::GetBoneControllers( controllers ); + + // Tell the weapon itself that we've rendered, in case it wants to do something + C_BaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( pWeapon ) + { + pWeapon->GetViewmodelBoneControllers( this, controllers ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : RenderGroup_t +//----------------------------------------------------------------------------- +RenderGroup_t C_BaseViewModel::GetRenderGroup() +{ + return RENDER_GROUP_VIEW_MODEL_OPAQUE; +} diff --git a/game/client/c_baseviewmodel.h b/game/client/c_baseviewmodel.h new file mode 100644 index 00000000..3a3b2700 --- /dev/null +++ b/game/client/c_baseviewmodel.h @@ -0,0 +1,19 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client side view model implementation. Responsible for drawing +// the view model. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_BASEVIEWMODEL_H +#define C_BASEVIEWMODEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseanimating.h" +#include "UtlVector.h" +#include "baseviewmodel_shared.h" + +#endif // C_BASEVIEWMODEL_H diff --git a/game/client/c_breakableprop.cpp b/game/client/c_breakableprop.cpp new file mode 100644 index 00000000..8a95ddba --- /dev/null +++ b/game/client/c_breakableprop.cpp @@ -0,0 +1,46 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "model_types.h" +#include "vcollide.h" +#include "vcollide_parse.h" +#include "solidsetdefaults.h" +#include "bone_setup.h" +#include "engine/ivmodelinfo.h" +#include "physics.h" +#include "c_breakableprop.h" +#include "view.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT(C_BreakableProp, DT_BreakableProp, CBreakableProp) +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BreakableProp::C_BreakableProp( void ) +{ + m_takedamage = DAMAGE_YES; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BreakableProp::SetFadeMinMax( float fademin, float fademax ) +{ + m_fadeMinDist = fademin; + m_fadeMaxDist = fademax; +} + +//----------------------------------------------------------------------------- +// Copy fade from another breakable prop +//----------------------------------------------------------------------------- +void C_BreakableProp::CopyFadeFrom( C_BreakableProp *pSource ) +{ + m_flFadeScale = pSource->m_flFadeScale; +} diff --git a/game/client/c_breakableprop.h b/game/client/c_breakableprop.h new file mode 100644 index 00000000..2ad4e586 --- /dev/null +++ b/game/client/c_breakableprop.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_BREAKABLEPROP_H +#define C_BREAKABLEPROP_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_BreakableProp : public C_BaseAnimating +{ + typedef C_BaseAnimating BaseClass; +public: + DECLARE_CLIENTCLASS(); + + C_BreakableProp(); + + virtual void SetFadeMinMax( float fademin, float fademax ); + + // Copy fade from another breakable prop + void CopyFadeFrom( C_BreakableProp *pSource ); +}; + +#endif // C_BREAKABLEPROP_H diff --git a/game/client/c_colorcorrection.cpp b/game/client/c_colorcorrection.cpp new file mode 100644 index 00000000..0b2581a1 --- /dev/null +++ b/game/client/c_colorcorrection.cpp @@ -0,0 +1,156 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Color correction entity with simple radial falloff +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" + +#include "filesystem.h" +#include "cdll_client_int.h" +#include "colorcorrectionmgr.h" +#include "materialsystem/materialsystemutil.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +static ConVar mat_colcorrection_disableentities( "mat_colcorrection_disableentities", "0", FCVAR_NONE, "Disable map color-correction entities" ); + + +//------------------------------------------------------------------------------ +// Purpose : Color correction entity with radial falloff +//------------------------------------------------------------------------------ +class C_ColorCorrection : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_ColorCorrection, C_BaseEntity ); + + DECLARE_CLIENTCLASS(); + + C_ColorCorrection(); + virtual ~C_ColorCorrection(); + + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + + void ClientThink(); + +private: + Vector m_vecOrigin; + + float m_minFalloff; + float m_maxFalloff; + float m_flCurWeight; + char m_netLookupFilename[MAX_PATH]; + + bool m_bEnabled; + + ClientCCHandle_t m_CCHandle; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_ColorCorrection, DT_ColorCorrection, CColorCorrection) + RecvPropVector( RECVINFO(m_vecOrigin) ), + RecvPropFloat( RECVINFO(m_minFalloff) ), + RecvPropFloat( RECVINFO(m_maxFalloff) ), + RecvPropFloat( RECVINFO(m_flCurWeight) ), + RecvPropString( RECVINFO(m_netLookupFilename) ), + RecvPropBool( RECVINFO(m_bEnabled) ), + +END_RECV_TABLE() + + +//------------------------------------------------------------------------------ +// Constructor, destructor +//------------------------------------------------------------------------------ +C_ColorCorrection::C_ColorCorrection() +{ + m_CCHandle = INVALID_CLIENT_CCHANDLE; +} + +C_ColorCorrection::~C_ColorCorrection() +{ + g_pColorCorrectionMgr->RemoveColorCorrection( m_CCHandle ); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_ColorCorrection::OnDataChanged(DataUpdateType_t updateType) +{ + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) + { + char filename[MAX_PATH]; + Q_strncpy( filename, m_netLookupFilename, MAX_PATH ); + + m_CCHandle = g_pColorCorrectionMgr->AddColorCorrection( filename ); + SetNextClientThink( ( m_CCHandle != INVALID_CLIENT_CCHANDLE ) ? CLIENT_THINK_ALWAYS : CLIENT_THINK_NEVER ); + } + } +} + +//------------------------------------------------------------------------------ +// We don't draw... +//------------------------------------------------------------------------------ +bool C_ColorCorrection::ShouldDraw() +{ + return false; +} + +void C_ColorCorrection::ClientThink() +{ + if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) + return; + + if ( mat_colcorrection_disableentities.GetInt() ) + { + // Allow the colorcorrectionui panel (or user) to turn off color-correction entities + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, 0.0f ); + return; + } + + if( !m_bEnabled && m_flCurWeight == 0.0f ) + { + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, 0.0f ); + return; + } + + CBaseEntity *pPlayer = UTIL_PlayerByIndex(1); + if( !pPlayer ) + return; + + Vector playerOrigin = pPlayer->GetAbsOrigin(); + + float weight = 0; + if ( ( m_minFalloff != -1 ) && ( m_maxFalloff != -1 ) && m_minFalloff != m_maxFalloff ) + { + float dist = (playerOrigin - m_vecOrigin).Length(); + weight = (dist-m_minFalloff) / (m_maxFalloff-m_minFalloff); + if ( weight<0.0f ) weight = 0.0f; + if ( weight>1.0f ) weight = 1.0f; + } + + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_flCurWeight * ( 1.0 - weight ) ); + + BaseClass::ClientThink(); +} + + + + + + + + + + + + + diff --git a/game/client/c_colorcorrectionvolume.cpp b/game/client/c_colorcorrectionvolume.cpp new file mode 100644 index 00000000..85eda5b7 --- /dev/null +++ b/game/client/c_colorcorrectionvolume.cpp @@ -0,0 +1,119 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Color correction entity. +// + // $NoKeywords: $ +//===========================================================================// +#include "cbase.h" + +#include "filesystem.h" +#include "cdll_client_int.h" +#include "materialsystem/materialsystemutil.h" +#include "colorcorrectionmgr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------ +// FIXME: This really should inherit from something more lightweight +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Purpose : Shadow control entity +//------------------------------------------------------------------------------ +class C_ColorCorrectionVolume : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_ColorCorrectionVolume, C_BaseEntity ); + + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_ColorCorrectionVolume(); + virtual ~C_ColorCorrectionVolume(); + + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + + void ClientThink(); + +private: + float m_Weight; + char m_lookupFilename[MAX_PATH]; + + ClientCCHandle_t m_CCHandle; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_ColorCorrectionVolume, DT_ColorCorrectionVolume, CColorCorrectionVolume) + RecvPropFloat( RECVINFO(m_Weight) ), + RecvPropString( RECVINFO(m_lookupFilename) ), +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_ColorCorrectionVolume ) + DEFINE_PRED_FIELD( m_Weight, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() + + +//------------------------------------------------------------------------------ +// Constructor, destructor +//------------------------------------------------------------------------------ +C_ColorCorrectionVolume::C_ColorCorrectionVolume() +{ + m_CCHandle = INVALID_CLIENT_CCHANDLE; +} + +C_ColorCorrectionVolume::~C_ColorCorrectionVolume() +{ + g_pColorCorrectionMgr->RemoveColorCorrection( m_CCHandle ); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_ColorCorrectionVolume::OnDataChanged(DataUpdateType_t updateType) +{ + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) + { + char filename[MAX_PATH]; + Q_strncpy( filename, m_lookupFilename, MAX_PATH ); + + m_CCHandle = g_pColorCorrectionMgr->AddColorCorrection( filename ); + SetNextClientThink( ( m_CCHandle != INVALID_CLIENT_CCHANDLE ) ? CLIENT_THINK_ALWAYS : CLIENT_THINK_NEVER ); + } + } +} + +//------------------------------------------------------------------------------ +// We don't draw... +//------------------------------------------------------------------------------ +bool C_ColorCorrectionVolume::ShouldDraw() +{ + return false; +} + +void C_ColorCorrectionVolume::ClientThink() +{ + Vector entityPosition = GetAbsOrigin(); + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_Weight ); +} + + + + + + + + + + + + + diff --git a/game/client/c_dynamiclight.cpp b/game/client/c_dynamiclight.cpp new file mode 100644 index 00000000..0214faff --- /dev/null +++ b/game/client/c_dynamiclight.cpp @@ -0,0 +1,237 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Dynamic light +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "dlight.h" +#include "iefx.h" +#include "IViewRender.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#if HL2_EPISODIC +// In Episodic we unify the NO_WORLD_ILLUMINATION lights to use +// the more efficient elight structure instead. This should theoretically +// be extended to other projects but may have unintended consequences +// and bears more thorough testing. +// +// For an earlier iteration on this technique see changelist 214433, +// which had a specific flag for use of elights. +#define DLIGHT_NO_WORLD_USES_ELIGHT 1 +#endif + + +//----------------------------------------------------------------------------- +// A dynamic light, with the goofy hack needed for spotlights +//----------------------------------------------------------------------------- +class C_DynamicLight : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_DynamicLight, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_DynamicLight(); + +public: + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + void ClientThink( void ); + void Release( void ); + + unsigned char m_Flags; + unsigned char m_LightStyle; + + float m_Radius; + int m_Exponent; + float m_InnerAngle; + float m_OuterAngle; + float m_SpotRadius; + +private: + dlight_t* m_pDynamicLight; + dlight_t* m_pSpotlightEnd; + + + inline bool ShouldBeElight() { return (m_Flags & DLIGHT_NO_WORLD_ILLUMINATION); } +}; + +IMPLEMENT_CLIENTCLASS_DT(C_DynamicLight, DT_DynamicLight, CDynamicLight) + RecvPropInt (RECVINFO(m_Flags)), + RecvPropInt (RECVINFO(m_LightStyle)), + RecvPropFloat (RECVINFO(m_Radius)), + RecvPropInt (RECVINFO(m_Exponent)), + RecvPropFloat (RECVINFO(m_InnerAngle)), + RecvPropFloat (RECVINFO(m_OuterAngle)), + RecvPropFloat (RECVINFO(m_SpotRadius)), +END_RECV_TABLE() + + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +C_DynamicLight::C_DynamicLight(void) : m_pSpotlightEnd(0), m_pDynamicLight(0) +{ +} + + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +void C_DynamicLight::OnDataChanged(DataUpdateType_t updateType) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink(gpGlobals->curtime + 0.05); + } + + BaseClass::OnDataChanged( updateType ); +} + + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +bool C_DynamicLight::ShouldDraw() +{ + return false; +} + +//------------------------------------------------------------------------------ +// Purpose : Disable drawing of this light when entity perishes +//------------------------------------------------------------------------------ +void C_DynamicLight::Release() +{ + if (m_pDynamicLight) + { + m_pDynamicLight->die = gpGlobals->curtime; + m_pDynamicLight = 0; + } + + if (m_pSpotlightEnd) + { + m_pSpotlightEnd->die = gpGlobals->curtime; + m_pSpotlightEnd = 0; + } + + BaseClass::Release(); +} + + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +void C_DynamicLight::ClientThink(void) +{ + Vector forward; + AngleVectors( GetAbsAngles(), &forward ); + + if ( (m_Flags & DLIGHT_NO_MODEL_ILLUMINATION) == 0 ) + { + // Deal with the model light + if ( !m_pDynamicLight || (m_pDynamicLight->key != index) ) + { +#if DLIGHT_NO_WORLD_USES_ELIGHT + m_pDynamicLight = ShouldBeElight() != 0 + ? effects->CL_AllocElight( index ) + : effects->CL_AllocDlight( index ); +#else + m_pDynamicLight = effects->CL_AllocDlight( index ); +#endif + Assert (m_pDynamicLight); + m_pDynamicLight->minlight = 0; + } + + m_pDynamicLight->style = m_LightStyle; + m_pDynamicLight->radius = m_Radius; + m_pDynamicLight->flags = m_Flags; + if ( m_OuterAngle > 0 ) + m_pDynamicLight->flags |= DLIGHT_NO_WORLD_ILLUMINATION; + m_pDynamicLight->color.r = m_clrRender->r; + m_pDynamicLight->color.g = m_clrRender->g; + m_pDynamicLight->color.b = m_clrRender->b; + m_pDynamicLight->color.exponent = m_Exponent; // this makes it match the world + m_pDynamicLight->origin = GetAbsOrigin(); + m_pDynamicLight->m_InnerAngle = m_InnerAngle; + m_pDynamicLight->m_OuterAngle = m_OuterAngle; + m_pDynamicLight->die = gpGlobals->curtime + 1e6; + m_pDynamicLight->m_Direction = forward; + } + else + { + // In this case, the m_Flags could have changed; which is how we turn the light off + if (m_pDynamicLight) + { + m_pDynamicLight->die = gpGlobals->curtime; + m_pDynamicLight = 0; + } + } + +#if DLIGHT_NO_WORLD_USES_ELIGHT + if (( m_OuterAngle > 0 ) && !ShouldBeElight()) +#else + if (( m_OuterAngle > 0 ) && ((m_Flags & DLIGHT_NO_WORLD_ILLUMINATION) == 0)) +#endif + { + // Raycast to where the endpoint goes + // Deal with the environment light + if ( !m_pSpotlightEnd || (m_pSpotlightEnd->key != -index) ) + { + m_pSpotlightEnd = effects->CL_AllocDlight( -index ); + Assert (m_pSpotlightEnd); + } + + // Trace a line outward, don't use hitboxes (too slow) + Vector end; + VectorMA( GetAbsOrigin(), m_Radius, forward, end ); + + trace_t pm; + C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace + UTIL_TraceLine( GetAbsOrigin(), end, MASK_NPCWORLDSTATIC, NULL, COLLISION_GROUP_NONE, &pm ); + C_BaseEntity::PopEnableAbsRecomputations(); + VectorCopy( pm.endpos, m_pSpotlightEnd->origin ); + + if (pm.fraction == 1.0f) + { + m_pSpotlightEnd->die = gpGlobals->curtime; + m_pSpotlightEnd = 0; + } + else + { + float falloff = 1.0 - pm.fraction; + falloff *= falloff; + + m_pSpotlightEnd->style = m_LightStyle; + m_pSpotlightEnd->flags = DLIGHT_NO_MODEL_ILLUMINATION | (m_Flags & DLIGHT_DISPLACEMENT_MASK); + m_pSpotlightEnd->radius = m_SpotRadius; // * falloff; + m_pSpotlightEnd->die = gpGlobals->curtime + 1e6; + m_pSpotlightEnd->color.r = m_clrRender->r * falloff; + m_pSpotlightEnd->color.g = m_clrRender->g * falloff; + m_pSpotlightEnd->color.b = m_clrRender->b * falloff; + m_pSpotlightEnd->color.exponent = m_Exponent; + + // For bumped lighting + m_pSpotlightEnd->m_Direction = forward; + + // Update list of surfaces we influence + render->TouchLight( m_pSpotlightEnd ); + } + } + else + { + // In this case, the m_Flags could have changed; which is how we turn the light off + if (m_pSpotlightEnd) + { + m_pSpotlightEnd->die = gpGlobals->curtime; + m_pSpotlightEnd = 0; + } + } + + SetNextClientThink(gpGlobals->curtime + 0.001); +} + diff --git a/game/client/c_effects.cpp b/game/client/c_effects.cpp new file mode 100644 index 00000000..6932bbe9 --- /dev/null +++ b/game/client/c_effects.cpp @@ -0,0 +1,2220 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "c_tracer.h" +#include "view.h" +#include "initializer.h" +#include "particles_simple.h" +#include "env_wind_shared.h" +#include "engine/IEngineTrace.h" +#include "engine/ivmodelinfo.h" +#include "precipitation_shared.h" +#include "fx_water.h" +#include "c_world.h" +#include "iviewrender.h" +#include "engine/IVDebugOverlay.h" +#include "ClientEffectPrecacheSystem.h" +#include "collisionutils.h" +#include "tier0/vprof.h" +#include "viewrender.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar cl_winddir ( "cl_winddir", "0", FCVAR_CHEAT, "Weather effects wind direction angle" ); +ConVar cl_windspeed ( "cl_windspeed", "0", FCVAR_CHEAT, "Weather effects wind speed scalar" ); + +Vector g_vSplashColor( 0.5, 0.5, 0.5 ); +float g_flSplashScale = 0.15; +float g_flSplashLifetime = 0.5f; +float g_flSplashAlpha = 0.3f; +ConVar r_RainSplashPercentage( "r_RainSplashPercentage", "20", FCVAR_CHEAT ); // N% chance of a rain particle making a splash. + + +float GUST_INTERVAL_MIN = 1; +float GUST_INTERVAL_MAX = 2; + +float GUST_LIFETIME_MIN = 1; +float GUST_LIFETIME_MAX = 3; + +float MIN_SCREENSPACE_RAIN_WIDTH = 1; + +#ifndef _XBOX +ConVar r_RainHack( "r_RainHack", "0", FCVAR_CHEAT ); +ConVar r_RainRadius( "r_RainRadius", "1500", FCVAR_CHEAT ); +ConVar r_RainSideVel( "r_RainSideVel", "130", FCVAR_CHEAT, "How much sideways velocity rain gets." ); + +ConVar r_RainSimulate( "r_RainSimulate", "1", FCVAR_CHEAT, "Enable/disable rain simulation." ); +ConVar r_DrawRain( "r_DrawRain", "1", FCVAR_CHEAT, "Enable/disable rain rendering." ); +ConVar r_RainProfile( "r_RainProfile", "0", FCVAR_CHEAT, "Enable/disable rain profiling." ); + + +//Precahce the effects +CLIENTEFFECT_REGISTER_BEGIN( PrecachePrecipitation ) +CLIENTEFFECT_MATERIAL( "particle/rain" ) +CLIENTEFFECT_MATERIAL( "particle/snow" ) +CLIENTEFFECT_REGISTER_END() + +//----------------------------------------------------------------------------- +// Precipitation particle type +//----------------------------------------------------------------------------- + +class CPrecipitationParticle +{ +public: + Vector m_Pos; + Vector m_Velocity; + float m_SpawnTime; // Note: Tweak with this to change lifetime + float m_Mass; + float m_Ramp; + + float m_flCurLifetime; + float m_flMaxLifetime; +}; + + +class CClient_Precipitation; +static CUtlVector g_Precipitations; + +//=========== +// Snow fall +//=========== +class CSnowFallManager; +static CSnowFallManager *s_pSnowFallMgr = NULL; +bool SnowFallManagerCreate( CClient_Precipitation *pSnowEntity ); +void SnowFallManagerDestroy( void ); + +class AshDebrisEffect : public CSimpleEmitter +{ +public: + AshDebrisEffect( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + static AshDebrisEffect* Create( const char *pDebugName ); + + virtual float UpdateAlpha( const SimpleParticle *pParticle ); + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ); + +private: + AshDebrisEffect( const AshDebrisEffect & ); +}; + +//----------------------------------------------------------------------------- +// Precipitation base entity +//----------------------------------------------------------------------------- + +class CClient_Precipitation : public C_BaseEntity +{ +class CPrecipitationEffect; +friend class CClient_Precipitation::CPrecipitationEffect; + +public: + DECLARE_CLASS( CClient_Precipitation, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + CClient_Precipitation(); + virtual ~CClient_Precipitation(); + + // Inherited from C_BaseEntity + virtual void Precache( ); + + void Render(); + +private: + + // Creates a single particle + CPrecipitationParticle* CreateParticle(); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink(); + + void Simulate( float dt ); + + // Renders the particle + void RenderParticle( CPrecipitationParticle* pParticle, CMeshBuilder &mb ); + + void CreateWaterSplashes(); + + // Emits the actual particles + void EmitParticles( float fTimeDelta ); + + // Computes where we're gonna emit + bool ComputeEmissionArea( Vector& origin, Vector2D& size ); + + // Gets the tracer width and speed + float GetWidth() const; + float GetLength() const; + float GetSpeed() const; + + // Gets the remaining lifetime of the particle + float GetRemainingLifetime( CPrecipitationParticle* pParticle ) const; + + // Computes the wind vector + static void ComputeWindVector( ); + + // simulation methods + bool SimulateRain( CPrecipitationParticle* pParticle, float dt ); + bool SimulateSnow( CPrecipitationParticle* pParticle, float dt ); + + void CreateAshParticle( void ); + void CreateRainOrSnowParticle( Vector vSpawnPosition, Vector vVelocity ); + + // Information helpful in creating and rendering particles + IMaterial *m_MatHandle; // material used + + float m_Color[4]; // precip color + float m_Lifetime; // Precip lifetime + float m_InitialRamp; // Initial ramp value + float m_Speed; // Precip speed + float m_Width; // Tracer width + float m_Remainder; // particles we should render next time + PrecipitationType_t m_nPrecipType; // Precip type + float m_flHalfScreenWidth; // Precalculated each frame. + + float m_flDensity; + + // Some state used in rendering and simulation + // Used to modify the rain density and wind from the console + static ConVar s_raindensity; + static ConVar s_rainwidth; + static ConVar s_rainlength; + static ConVar s_rainspeed; + + static Vector s_WindVector; // Stores the wind speed vector + + CUtlLinkedList m_Particles; + CUtlVector m_Splashes; + + CSmartPtr m_pAshEmitter; + TimedEvent m_tAshParticleTimer; + TimedEvent m_tAshParticleTraceTimer; + bool m_bActiveAshEmitter; + Vector m_vAshSpawnOrigin; + + int m_iAshCount; + +private: + CClient_Precipitation( const CClient_Precipitation & ); // not defined, not accessible +}; + + +// Just receive the normal data table stuff +IMPLEMENT_CLIENTCLASS_DT(CClient_Precipitation, DT_Precipitation, CPrecipitation) + RecvPropInt( RECVINFO( m_nPrecipType ) ) +END_RECV_TABLE() + +static ConVar r_SnowEnable( "r_SnowEnable", "1", FCVAR_CHEAT, "Snow Enable" ); +static ConVar r_SnowParticles( "r_SnowParticles", "500", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowInsideRadius( "r_SnowInsideRadius", "256", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowOutsideRadius( "r_SnowOutsideRadius", "1024", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowSpeedScale( "r_SnowSpeedScale", "1", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowPosScale( "r_SnowPosScale", "1", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowFallSpeed( "r_SnowFallSpeed", "1.5", FCVAR_CHEAT, "Snow fall speed scale." ); +static ConVar r_SnowWindScale( "r_SnowWindScale", "0.0035", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowDebugBox( "r_SnowDebugBox", "0", FCVAR_CHEAT, "Snow Debug Boxes." ); +static ConVar r_SnowZoomOffset( "r_SnowZoomOffset", "384.0f", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowZoomRadius( "r_SnowZoomRadius", "512.0f", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowStartAlpha( "r_SnowStartAlpha", "25", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowEndAlpha( "r_SnowEndAlpha", "255", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowColorRed( "r_SnowColorRed", "150", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowColorGreen( "r_SnowColorGreen", "175", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowColorBlue( "r_SnowColorBlue", "200", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowStartSize( "r_SnowStartSize", "1", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowEndSize( "r_SnowEndSize", "0", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowRayLength( "r_SnowRayLength", "8192.0f", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowRayRadius( "r_SnowRayRadius", "256", FCVAR_CHEAT, "Snow." ); +static ConVar r_SnowRayEnable( "r_SnowRayEnable", "1", FCVAR_CHEAT, "Snow." ); + +void DrawPrecipitation() +{ + for ( int i=0; i < g_Precipitations.Count(); i++ ) + { + g_Precipitations[i]->Render(); + } +} + + +//----------------------------------------------------------------------------- +// determines if a weather particle has hit something other than air +//----------------------------------------------------------------------------- +static bool IsInAir( const Vector& position ) +{ + int contents = enginetrace->GetPointContents( position ); + return (contents & CONTENTS_SOLID) == 0; +} + + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- + +ConVar CClient_Precipitation::s_raindensity( "r_raindensity","0.001", FCVAR_CHEAT); +ConVar CClient_Precipitation::s_rainwidth( "r_rainwidth", "0.5", FCVAR_CHEAT ); +ConVar CClient_Precipitation::s_rainlength( "r_rainlength", "0.1f", FCVAR_CHEAT ); +ConVar CClient_Precipitation::s_rainspeed( "r_rainspeed", "600.0f", FCVAR_CHEAT ); +ConVar r_rainalpha( "r_rainalpha", "0.4", FCVAR_CHEAT ); +ConVar r_rainalphapow( "r_rainalphapow", "0.8", FCVAR_CHEAT ); + + +Vector CClient_Precipitation::s_WindVector; // Stores the wind speed vector + + +void CClient_Precipitation::OnDataChanged( DataUpdateType_t updateType ) +{ + // Simulate every frame. + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL ) + { + SnowFallManagerCreate( this ); + } + } + + m_flDensity = RemapVal( m_clrRender->a, 0, 255, 0, 0.001 ); + + BaseClass::OnDataChanged( updateType ); +} + + +void CClient_Precipitation::ClientThink() +{ + Simulate( gpGlobals->frametime ); +} + + +//----------------------------------------------------------------------------- +// +// Utility methods for the various simulation functions +// +//----------------------------------------------------------------------------- +inline bool CClient_Precipitation::SimulateRain( CPrecipitationParticle* pParticle, float dt ) +{ + if (GetRemainingLifetime( pParticle ) < 0.0f) + return false; + + Vector vOldPos = pParticle->m_Pos; + + // Update position + VectorMA( pParticle->m_Pos, dt, pParticle->m_Velocity, + pParticle->m_Pos ); + + // wind blows rain around + for ( int i = 0 ; i < 2 ; i++ ) + { + if ( pParticle->m_Velocity[i] < s_WindVector[i] ) + { + pParticle->m_Velocity[i] += ( 5 / pParticle->m_Mass ); + + // clamp + if ( pParticle->m_Velocity[i] > s_WindVector[i] ) + pParticle->m_Velocity[i] = s_WindVector[i]; + } + else if (pParticle->m_Velocity[i] > s_WindVector[i] ) + { + pParticle->m_Velocity[i] -= ( 5 / pParticle->m_Mass ); + + // clamp. + if ( pParticle->m_Velocity[i] < s_WindVector[i] ) + pParticle->m_Velocity[i] = s_WindVector[i]; + } + } + + // No longer in the air? punt. + if ( !IsInAir( pParticle->m_Pos ) ) + { + // Possibly make a splash if we hit a water surface and it's in front of the view. + if ( m_Splashes.Count() < 20 ) + { + if ( RandomInt( 0, 100 ) < r_RainSplashPercentage.GetInt() ) + { + trace_t trace; + UTIL_TraceLine(vOldPos, pParticle->m_Pos, MASK_WATER, NULL, COLLISION_GROUP_NONE, &trace); + if( trace.fraction < 1 ) + { + m_Splashes.AddToTail( trace.endpos ); + } + } + } + + // Tell the framework it's time to remove the particle from the list + return false; + } + + // We still want this particle + return true; +} + + +inline bool CClient_Precipitation::SimulateSnow( CPrecipitationParticle* pParticle, float dt ) +{ + if ( IsInAir( pParticle->m_Pos ) ) + { + // Update position + VectorMA( pParticle->m_Pos, dt, pParticle->m_Velocity, + pParticle->m_Pos ); + + // wind blows rain around + for ( int i = 0 ; i < 2 ; i++ ) + { + if ( pParticle->m_Velocity[i] < s_WindVector[i] ) + { + pParticle->m_Velocity[i] += ( 5.0f / pParticle->m_Mass ); + + // accelerating flakes get a trail + pParticle->m_Ramp = 0.5f; + + // clamp + if ( pParticle->m_Velocity[i] > s_WindVector[i] ) + pParticle->m_Velocity[i] = s_WindVector[i]; + } + else if (pParticle->m_Velocity[i] > s_WindVector[i] ) + { + pParticle->m_Velocity[i] -= ( 5.0f / pParticle->m_Mass ); + + // accelerating flakes get a trail + pParticle->m_Ramp = 0.5f; + + // clamp. + if ( pParticle->m_Velocity[i] < s_WindVector[i] ) + pParticle->m_Velocity[i] = s_WindVector[i]; + } + } + + return true; + } + + + // Kill the particle immediately! + return false; +} + + +void CClient_Precipitation::Simulate( float dt ) +{ + // NOTE: When client-side prechaching works, we need to remove this + Precache(); + + m_flHalfScreenWidth = (float)ScreenWidth() / 2; + + // Our sim methods needs dt and wind vector + if ( dt ) + { + ComputeWindVector( ); + } + + if ( m_nPrecipType == PRECIPITATION_TYPE_ASH ) + { + CreateAshParticle(); + return; + } + + // The snow fall manager handles the simulation. + if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL ) + return; + + // calculate the max amount of time it will take this flake to fall. + // This works if we assume the wind doesn't have a z component + if ( r_RainHack.GetInt() ) + m_Lifetime = (GetClientWorldEntity()->m_WorldMaxs[2] - GetClientWorldEntity()->m_WorldMins[2]) / m_Speed; + else + m_Lifetime = (WorldAlignMaxs()[2] - WorldAlignMins()[2]) / m_Speed; + + + if ( !r_RainSimulate.GetInt() ) + return; + + CFastTimer timer; + timer.Start(); + + // Emit new particles + EmitParticles( dt ); + + // Simulate all the particles. + int iNext; + if ( m_nPrecipType == PRECIPITATION_TYPE_RAIN ) + { + for ( int i=m_Particles.Head(); i != m_Particles.InvalidIndex(); i=iNext ) + { + iNext = m_Particles.Next( i ); + if ( !SimulateRain( &m_Particles[i], dt ) ) + m_Particles.Remove( i ); + } + } + else if ( m_nPrecipType == PRECIPITATION_TYPE_SNOW ) + { + for ( int i=m_Particles.Head(); i != m_Particles.InvalidIndex(); i=iNext ) + { + iNext = m_Particles.Next( i ); + if ( !SimulateSnow( &m_Particles[i], dt ) ) + m_Particles.Remove( i ); + } + } + + if ( r_RainProfile.GetInt() ) + { + timer.End(); + engine->Con_NPrintf( 15, "Rain simulation: %du (%d tracers)", timer.GetDuration().GetMicroseconds(), m_Particles.Count() ); + } +} + + +//----------------------------------------------------------------------------- +// tracer rendering +//----------------------------------------------------------------------------- + +inline void CClient_Precipitation::RenderParticle( CPrecipitationParticle* pParticle, CMeshBuilder &mb ) +{ + float scale; + Vector start, delta; + + if ( m_nPrecipType == PRECIPITATION_TYPE_ASH ) + return; + + if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL ) + return; + + + // make streaks 0.1 seconds long, but prevent from going past end + float lifetimeRemaining = GetRemainingLifetime( pParticle ); + if (lifetimeRemaining >= GetLength()) + scale = GetLength() * pParticle->m_Ramp; + else + scale = lifetimeRemaining * pParticle->m_Ramp; + + // NOTE: We need to do everything in screen space + Vector3DMultiplyPosition( CurrentWorldToViewMatrix(), pParticle->m_Pos, start ); + if ( start.z > -1 ) + return; + + Vector3DMultiply( CurrentWorldToViewMatrix(), pParticle->m_Velocity, delta ); + + // give a spiraling pattern to snow particles + if ( m_nPrecipType == PRECIPITATION_TYPE_SNOW ) + { + Vector spiral, camSpiral; + float s, c; + + if ( pParticle->m_Mass > 1.0f ) + { + SinCos( gpGlobals->curtime * M_PI * (1+pParticle->m_Mass * 0.1f) + + pParticle->m_Mass * 5.0f, &s , &c ); + + // only spiral particles with a mass > 1, so some fall straight down + spiral[0] = 28 * c; + spiral[1] = 28 * s; + spiral[2] = 0.0f; + + Vector3DMultiply( CurrentWorldToViewMatrix(), spiral, camSpiral ); + + // X and Y are measured in world space; need to convert to camera space + VectorAdd( start, camSpiral, start ); + VectorAdd( delta, camSpiral, delta ); + } + + // shrink the trails on spiraling flakes. + pParticle->m_Ramp = 0.3f; + } + + delta[0] *= scale; + delta[1] *= scale; + delta[2] *= scale; + + // See c_tracer.* for this method + float flAlpha = r_rainalpha.GetFloat(); + float flWidth = GetWidth(); + + float flScreenSpaceWidth = flWidth * m_flHalfScreenWidth / -start.z; + if ( flScreenSpaceWidth < MIN_SCREENSPACE_RAIN_WIDTH ) + { + // Make the rain tracer at least the min size, but fade its alpha the smaller it gets. + flAlpha *= flScreenSpaceWidth / MIN_SCREENSPACE_RAIN_WIDTH; + flWidth = MIN_SCREENSPACE_RAIN_WIDTH * -start.z / m_flHalfScreenWidth; + } + flAlpha = pow( flAlpha, r_rainalphapow.GetFloat() ); + + float flColor[4] = { 1, 1, 1, flAlpha }; + Tracer_Draw( &mb, start, delta, flWidth, flColor, 1 ); +} + + +void CClient_Precipitation::CreateWaterSplashes() +{ + for ( int i=0; i < m_Splashes.Count(); i++ ) + { + Vector vSplash = m_Splashes[i]; + + if ( CurrentViewForward().Dot( vSplash - CurrentViewOrigin() ) > 1 ) + { + FX_WaterRipple( vSplash, g_flSplashScale, &g_vSplashColor, g_flSplashLifetime, g_flSplashAlpha ); + } + } + m_Splashes.Purge(); +} + + +void CClient_Precipitation::Render() +{ + if ( !r_DrawRain.GetInt() ) + return; + + // Don't render in monitors or in reflections or refractions. + if ( CurrentViewID() == VIEW_MONITOR ) + return; + + if ( view->GetDrawFlags() & (DF_RENDER_REFLECTION | DF_RENDER_REFRACTION) ) + return; + + if ( m_nPrecipType == PRECIPITATION_TYPE_ASH ) + return; + + if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL ) + return; + + // Create any queued up water splashes. + CreateWaterSplashes(); + + + CFastTimer timer; + timer.Start(); + + CMatRenderContextPtr pRenderContext( materials ); + + // We want to do our calculations in view space. + VMatrix tempView; + pRenderContext->GetMatrix( MATERIAL_VIEW, &tempView ); + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->LoadIdentity(); + + // Force the user clip planes to use the old view matrix + pRenderContext->EnableUserClipTransformOverride( true ); + pRenderContext->UserClipTransform( tempView ); + + // Draw all the rain tracers. + pRenderContext->Bind( m_MatHandle ); + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + if ( pMesh ) + { + CMeshBuilder mb; + mb.Begin( pMesh, MATERIAL_QUADS, m_Particles.Count() ); + + for ( int i=m_Particles.Head(); i != m_Particles.InvalidIndex(); i=m_Particles.Next( i ) ) + { + CPrecipitationParticle *p = &m_Particles[i]; + RenderParticle( p, mb ); + } + + mb.End( false, true ); + } + + pRenderContext->EnableUserClipTransformOverride( false ); + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->LoadMatrix( tempView ); + + if ( r_RainProfile.GetInt() ) + { + timer.End(); + engine->Con_NPrintf( 16, "Rain render : %du", timer.GetDuration().GetMicroseconds() ); + } +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- + +CClient_Precipitation::CClient_Precipitation() : m_Remainder(0.0f) +{ + m_nPrecipType = PRECIPITATION_TYPE_RAIN; + m_MatHandle = INVALID_MATERIAL_HANDLE; + m_flHalfScreenWidth = 1; + + g_Precipitations.AddToTail( this ); +} + +CClient_Precipitation::~CClient_Precipitation() +{ + g_Precipitations.FindAndRemove( this ); + SnowFallManagerDestroy(); +} + +//----------------------------------------------------------------------------- +// Precache data +//----------------------------------------------------------------------------- + +#define SNOW_SPEED 80.0f +#define RAIN_SPEED 425.0f + +#define RAIN_TRACER_WIDTH 0.35f +#define SNOW_TRACER_WIDTH 0.7f + +void CClient_Precipitation::Precache( ) +{ + if ( !m_MatHandle ) + { + // Compute precipitation emission speed + switch( m_nPrecipType ) + { + case PRECIPITATION_TYPE_SNOW: + m_Speed = SNOW_SPEED; + m_MatHandle = materials->FindMaterial( "particle/snow", TEXTURE_GROUP_CLIENT_EFFECTS ); + m_InitialRamp = 0.6f; + m_Width = SNOW_TRACER_WIDTH; + break; + + case PRECIPITATION_TYPE_RAIN: + Assert( m_nPrecipType == PRECIPITATION_TYPE_RAIN ); + m_Speed = RAIN_SPEED; + m_MatHandle = materials->FindMaterial( "particle/rain", TEXTURE_GROUP_CLIENT_EFFECTS ); + m_InitialRamp = 1.0f; + m_Color[3] = 1.0f; // make translucent + m_Width = RAIN_TRACER_WIDTH; + break; + default: + m_InitialRamp = 1.0f; + m_Color[3] = 1.0f; // make translucent + break; + } + + // Store off the color + m_Color[0] = 1.0f; + m_Color[1] = 1.0f; + m_Color[2] = 1.0f; + } +} + + +//----------------------------------------------------------------------------- +// Gets the tracer width and speed +//----------------------------------------------------------------------------- + +inline float CClient_Precipitation::GetWidth() const +{ +// return m_Width; + return s_rainwidth.GetFloat(); +} + +inline float CClient_Precipitation::GetLength() const +{ +// return m_Length; + return s_rainlength.GetFloat(); +} + +inline float CClient_Precipitation::GetSpeed() const +{ +// return m_Speed; + return s_rainspeed.GetFloat(); +} + + +//----------------------------------------------------------------------------- +// Gets the remaining lifetime of the particle +//----------------------------------------------------------------------------- + +inline float CClient_Precipitation::GetRemainingLifetime( CPrecipitationParticle* pParticle ) const +{ + float timeSinceSpawn = gpGlobals->curtime - pParticle->m_SpawnTime; + return m_Lifetime - timeSinceSpawn; +} + +//----------------------------------------------------------------------------- +// Creates a particle +//----------------------------------------------------------------------------- + +inline CPrecipitationParticle* CClient_Precipitation::CreateParticle() +{ + int i = m_Particles.AddToTail(); + CPrecipitationParticle* pParticle = &m_Particles[i]; + + pParticle->m_SpawnTime = gpGlobals->curtime; + pParticle->m_Ramp = m_InitialRamp; + + return pParticle; +} + + +//----------------------------------------------------------------------------- +// Compute the emission area +//----------------------------------------------------------------------------- + +bool CClient_Precipitation::ComputeEmissionArea( Vector& origin, Vector2D& size ) +{ + // FIXME: Compute the precipitation area based on computational power + float emissionSize = r_RainRadius.GetFloat(); // size of box to emit particles in + + Vector vMins = WorldAlignMins(); + Vector vMaxs = WorldAlignMaxs(); + if ( r_RainHack.GetInt() ) + { + vMins = GetClientWorldEntity()->m_WorldMins; + vMaxs = GetClientWorldEntity()->m_WorldMaxs; + } + + // calculate a volume around the player to snow in. Intersect this big magic + // box around the player with the volume of the current environmental ent. + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer ) + return false; + + // Determine how much time it'll take a falling particle to hit the player + float emissionHeight = min( vMaxs[2], pPlayer->GetAbsOrigin()[2] + 512 ); + float distToFall = emissionHeight - pPlayer->GetAbsOrigin()[2]; + float fallTime = distToFall / GetSpeed(); + + // Based on the windspeed, figure out the center point of the emission + Vector2D center; + center[0] = pPlayer->GetAbsOrigin()[0] - fallTime * s_WindVector[0]; + center[1] = pPlayer->GetAbsOrigin()[1] - fallTime * s_WindVector[1]; + + Vector2D lobound, hibound; + lobound[0] = center[0] - emissionSize * 0.5f; + lobound[1] = center[1] - emissionSize * 0.5f; + hibound[0] = lobound[0] + emissionSize; + hibound[1] = lobound[1] + emissionSize; + + // Cull non-intersecting. + if ( ( vMaxs[0] < lobound[0] ) || ( vMaxs[1] < lobound[1] ) || + ( vMins[0] > hibound[0] ) || ( vMins[1] > hibound[1] ) ) + return false; + + origin[0] = max( vMins[0], lobound[0] ); + origin[1] = max( vMins[1], lobound[1] ); + origin[2] = emissionHeight; + + hibound[0] = min( vMaxs[0], hibound[0] ); + hibound[1] = min( vMaxs[1], hibound[1] ); + + size[0] = hibound[0] - origin[0]; + size[1] = hibound[1] - origin[1]; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pDebugName - +// Output : AshDebrisEffect* +//----------------------------------------------------------------------------- +AshDebrisEffect* AshDebrisEffect::Create( const char *pDebugName ) +{ + return new AshDebrisEffect( pDebugName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// timeDelta - +// Output : float +//----------------------------------------------------------------------------- +float AshDebrisEffect::UpdateAlpha( const SimpleParticle *pParticle ) +{ + return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) ); +} + +#define ASH_PARTICLE_NOISE 0x4 + +float AshDebrisEffect::UpdateRoll( SimpleParticle *pParticle, float timeDelta ) +{ + float flRoll = CSimpleEmitter::UpdateRoll(pParticle, timeDelta ); + + if ( pParticle->m_iFlags & ASH_PARTICLE_NOISE ) + { + Vector vTempEntVel = pParticle->m_vecVelocity; + float fastFreq = gpGlobals->curtime * 1.5; + + float s, c; + SinCos( fastFreq, &s, &c ); + + pParticle->m_Pos = ( pParticle->m_Pos + Vector( + vTempEntVel[0] * timeDelta * s, + vTempEntVel[1] * timeDelta * s, 0 ) ); + } + + return flRoll; +} + +void CClient_Precipitation::CreateAshParticle( void ) +{ + // Make sure the emitter is setup + if ( m_pAshEmitter == NULL ) + { + if ( ( m_pAshEmitter = AshDebrisEffect::Create( "ashtray" ) ) == NULL ) + return; + + m_tAshParticleTimer.Init( 192 ); + m_tAshParticleTraceTimer.Init( 15 ); + m_bActiveAshEmitter = false; + m_iAshCount = 0; + } + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( pPlayer == NULL ) + return; + + Vector vForward; + pPlayer->GetVectors( &vForward, NULL, NULL ); + vForward.z = 0.0f; + + float curTime = gpGlobals->frametime; + + Vector vPushOrigin; + + Vector absmins = WorldAlignMins(); + Vector absmaxs = WorldAlignMaxs(); + + //15 Traces a second. + while ( m_tAshParticleTraceTimer.NextEvent( curTime ) ) + { + trace_t tr; + + Vector vTraceStart = pPlayer->EyePosition(); + Vector vTraceEnd = pPlayer->EyePosition() + vForward * MAX_TRACE_LENGTH; + + UTIL_TraceLine( vTraceStart, vTraceEnd, MASK_SHOT_HULL & (~CONTENTS_GRATE), pPlayer, COLLISION_GROUP_NONE, &tr ); + + //debugoverlay->AddLineOverlay( vTraceStart, tr.endpos, 255, 0, 0, 0, 0.2 ); + + if ( tr.fraction != 1.0f ) + { + trace_t tr2; + + UTIL_TraceModel( vTraceStart, tr.endpos, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), this, COLLISION_GROUP_NONE, &tr2 ); + + if ( tr2.m_pEnt == this ) + { + m_bActiveAshEmitter = true; + + if ( tr2.startsolid == false ) + { + m_vAshSpawnOrigin = tr2.endpos + vForward * 256; + } + else + { + m_vAshSpawnOrigin = vTraceStart; + } + } + else + { + m_bActiveAshEmitter = false; + } + } + } + + if ( m_bActiveAshEmitter == false ) + return; + + Vector vecVelocity = pPlayer->GetAbsVelocity(); + + + float flVelocity = VectorNormalize( vecVelocity ); + Vector offset = m_vAshSpawnOrigin; + + m_pAshEmitter->SetSortOrigin( offset ); + + PMaterialHandle hMaterial[4]; + hMaterial[0] = ParticleMgr()->GetPMaterial( "effects/fleck_ash1" ); + hMaterial[1] = ParticleMgr()->GetPMaterial( "effects/fleck_ash2" ); + hMaterial[2] = ParticleMgr()->GetPMaterial( "effects/fleck_ash3" ); + hMaterial[3] = ParticleMgr()->GetPMaterial( "effects/ember_swirling001" ); + + SimpleParticle *pParticle; + + Vector vSpawnOrigin = vec3_origin; + + if ( flVelocity > 0 ) + { + vSpawnOrigin = ( vForward * 256 ) + ( vecVelocity * ( flVelocity * 2 ) ); + } + + // Add as many particles as we need + while ( m_tAshParticleTimer.NextEvent( curTime ) ) + { + int iRandomAltitude = RandomInt( 0, 128 ); + + offset = m_vAshSpawnOrigin + vSpawnOrigin + RandomVector( -256, 256 ); + offset.z = m_vAshSpawnOrigin.z + iRandomAltitude; + + if ( offset[0] > absmaxs[0] + || offset[1] > absmaxs[1] + || offset[2] > absmaxs[2] + || offset[0] < absmins[0] + || offset[1] < absmins[1] + || offset[2] < absmins[2] ) + continue; + + m_iAshCount++; + + bool bEmberTime = false; + + if ( m_iAshCount >= 250 ) + { + bEmberTime = true; + m_iAshCount = 0; + } + + int iRandom = random->RandomInt(0,2); + + if ( bEmberTime == true ) + { + offset = m_vAshSpawnOrigin + (vForward * 256) + RandomVector( -128, 128 ); + offset.z = pPlayer->EyePosition().z + RandomFloat( -16, 64 ); + + iRandom = 3; + } + + pParticle = (SimpleParticle *) m_pAshEmitter->AddParticle( sizeof(SimpleParticle), hMaterial[iRandom], offset ); + + if (pParticle == NULL) + continue; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = RemapVal( iRandomAltitude, 0, 128, 4, 8 ); + + if ( bEmberTime == true ) + { + Vector vGoal = pPlayer->EyePosition() + RandomVector( -64, 64 ); + Vector vDir = vGoal - offset; + VectorNormalize( vDir ); + + pParticle->m_vecVelocity = vDir * 75; + pParticle->m_flDieTime = 2.5f; + } + else + { + pParticle->m_vecVelocity = Vector( RandomFloat( -20.0f, 20.0f ), RandomFloat( -20.0f, 20.0f ), RandomFloat( -10, -15 ) ); + } + + float color = random->RandomInt( 125, 225 ); + pParticle->m_uchColor[0] = color; + pParticle->m_uchColor[1] = color; + pParticle->m_uchColor[2] = color; + + pParticle->m_uchStartSize = 1; + pParticle->m_uchEndSize = 1.5; + + pParticle->m_uchStartAlpha = 255; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -0.15f, 0.15f ); + + pParticle->m_iFlags = SIMPLE_PARTICLE_FLAG_WINDBLOWN; + + if ( random->RandomInt( 0, 10 ) <= 1 ) + { + pParticle->m_iFlags |= ASH_PARTICLE_NOISE; + } + } +} + +void CClient_Precipitation::CreateRainOrSnowParticle( Vector vSpawnPosition, Vector vVelocity ) +{ + // Create the particle + CPrecipitationParticle* p = CreateParticle(); + if (!p) + return; + + VectorCopy( vVelocity, p->m_Velocity ); + p->m_Pos = vSpawnPosition; + + p->m_Velocity[ 0 ] += random->RandomFloat(-r_RainSideVel.GetInt(), r_RainSideVel.GetInt()); + p->m_Velocity[ 1 ] += random->RandomFloat(-r_RainSideVel.GetInt(), r_RainSideVel.GetInt()); + + p->m_Mass = random->RandomFloat( 0.5, 1.5 ); +} + +//----------------------------------------------------------------------------- +// emit the precipitation particles +//----------------------------------------------------------------------------- + +void CClient_Precipitation::EmitParticles( float fTimeDelta ) +{ + Vector2D size; + Vector vel, org; + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer ) + return; + Vector vPlayerCenter = pPlayer->WorldSpaceCenter(); + + // Compute where to emit + if (!ComputeEmissionArea( org, size )) + return; + + // clamp this to prevent creating a bunch of rain or snow at one time. + if( fTimeDelta > 0.075f ) + fTimeDelta = 0.075f; + + // FIXME: Compute the precipitation density based on computational power + float density = m_flDensity; + + if (density > 0.01f) + density = 0.01f; + + // Compute number of particles to emit based on precip density and emission area and dt + float fParticles = size[0] * size[1] * density * fTimeDelta + m_Remainder; + int cParticles = (int)fParticles; + m_Remainder = fParticles - cParticles; + + // calculate the max amount of time it will take this flake to fall. + // This works if we assume the wind doesn't have a z component + VectorCopy( s_WindVector, vel ); + vel[2] -= GetSpeed(); + + // Emit all the particles + for ( int i = 0 ; i < cParticles ; i++ ) + { + Vector vParticlePos = org; + vParticlePos[ 0 ] += size[ 0 ] * random->RandomFloat(0, 1); + vParticlePos[ 1 ] += size[ 1 ] * random->RandomFloat(0, 1); + + // Figure out where the particle should lie in Z by tracing a line from the player's height up to the + // desired height and making sure it doesn't hit a wall. + Vector vPlayerHeight = vParticlePos; + vPlayerHeight.z = vPlayerCenter.z; + + trace_t trace; + UTIL_TraceLine( vPlayerHeight, vParticlePos, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace ); + if ( trace.fraction < 1 ) + { + // If we hit a brush, then don't spawn the particle. + if ( trace.surface.flags & SURF_SKY ) + { + vParticlePos = trace.endpos; + } + else + { + continue; + } + } + + CreateRainOrSnowParticle( vParticlePos, vel ); + } +} + + +//----------------------------------------------------------------------------- +// Computes the wind vector +//----------------------------------------------------------------------------- + +void CClient_Precipitation::ComputeWindVector( ) +{ + // Compute the wind direction + QAngle windangle( 0, cl_winddir.GetFloat(), 0 ); // used to turn wind yaw direction into a vector + + // Randomize the wind angle and speed slightly to get us a little variation + windangle[1] = windangle[1] + random->RandomFloat( -10, 10 ); + float windspeed = cl_windspeed.GetFloat() * (1.0 + random->RandomFloat( -0.2, 0.2 )); + + AngleVectors( windangle, &s_WindVector ); + VectorScale( s_WindVector, windspeed, s_WindVector ); +} + + +CHandle g_pPrecipHackEnt; + +class CPrecipHack : public CAutoGameSystemPerFrame +{ +public: + CPrecipHack( char const *name ) : CAutoGameSystemPerFrame( name ) + { + m_bLevelInitted = false; + } + + virtual void LevelInitPostEntity() + { + if ( r_RainHack.GetInt() ) + { + CClient_Precipitation *pPrecipHackEnt = new CClient_Precipitation; + pPrecipHackEnt->InitializeAsClientEntity( NULL, RENDER_GROUP_TRANSLUCENT_ENTITY ); + g_pPrecipHackEnt = pPrecipHackEnt; + } + m_bLevelInitted = true; + } + + virtual void LevelShutdownPreEntity() + { + if ( r_RainHack.GetInt() && g_pPrecipHackEnt ) + { + g_pPrecipHackEnt->Release(); + } + m_bLevelInitted = false; + } + + virtual void Update( float frametime ) + { + // Handle changes to the cvar at runtime. + if ( m_bLevelInitted ) + { + if ( r_RainHack.GetInt() && !g_pPrecipHackEnt ) + LevelInitPostEntity(); + else if ( !r_RainHack.GetInt() && g_pPrecipHackEnt ) + LevelShutdownPreEntity(); + } + } + + bool m_bLevelInitted; +}; +CPrecipHack g_PrecipHack( "CPrecipHack" ); + +#else + +void DrawPrecipitation() +{ +} + +#endif // _XBOX + +//----------------------------------------------------------------------------- +// EnvWind - global wind info +//----------------------------------------------------------------------------- +class C_EnvWind : public C_BaseEntity +{ +public: + C_EnvWind(); + + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_EnvWind, C_BaseEntity ); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool ShouldDraw( void ) { return false; } + + virtual void ClientThink( ); + +private: + C_EnvWind( const C_EnvWind & ); + + CEnvWindShared m_EnvWindShared; +}; + +// Receive datatables +BEGIN_RECV_TABLE_NOBASE(CEnvWindShared, DT_EnvWindShared) + RecvPropInt (RECVINFO(m_iMinWind)), + RecvPropInt (RECVINFO(m_iMaxWind)), + RecvPropInt (RECVINFO(m_iMinGust)), + RecvPropInt (RECVINFO(m_iMaxGust)), + RecvPropFloat (RECVINFO(m_flMinGustDelay)), + RecvPropFloat (RECVINFO(m_flMaxGustDelay)), + RecvPropInt (RECVINFO(m_iGustDirChange)), + RecvPropInt (RECVINFO(m_iWindSeed)), + RecvPropInt (RECVINFO(m_iInitialWindDir)), + RecvPropFloat (RECVINFO(m_flInitialWindSpeed)), + RecvPropFloat (RECVINFO(m_flStartTime)), + RecvPropFloat (RECVINFO(m_flGustDuration)), +// RecvPropInt (RECVINFO(m_iszGustSound)), +END_RECV_TABLE() + +IMPLEMENT_CLIENTCLASS_DT( C_EnvWind, DT_EnvWind, CEnvWind ) + RecvPropDataTable(RECVINFO_DT(m_EnvWindShared), 0, &REFERENCE_RECV_TABLE(DT_EnvWindShared)), +END_RECV_TABLE() + + +C_EnvWind::C_EnvWind() +{ +} + +//----------------------------------------------------------------------------- +// Post data update! +//----------------------------------------------------------------------------- +void C_EnvWind::OnDataChanged( DataUpdateType_t updateType ) +{ + // Whenever we get an update, reset the entire state. + // Note that the fields have already been stored by the datatables, + // but there's still work to be done in the init block + m_EnvWindShared.Init( entindex(), m_EnvWindShared.m_iWindSeed, + m_EnvWindShared.m_flStartTime, m_EnvWindShared.m_iInitialWindDir, + m_EnvWindShared.m_flInitialWindSpeed ); + + SetNextClientThink(0.0f); + + BaseClass::OnDataChanged( updateType ); +} + +void C_EnvWind::ClientThink( ) +{ + // Update the wind speed + float flNextThink = m_EnvWindShared.WindThink( gpGlobals->curtime ); + SetNextClientThink(flNextThink); +} + + + +//================================================== +// EmberParticle +//================================================== + +class CEmberEmitter : public CSimpleEmitter +{ +public: + CEmberEmitter( const char *pDebugName ); + static CSmartPtr Create( const char *pDebugName ); + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ); + virtual Vector UpdateColor( const SimpleParticle *pParticle ); + +private: + CEmberEmitter( const CEmberEmitter & ); +}; + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +// Output : Vector +//----------------------------------------------------------------------------- +CEmberEmitter::CEmberEmitter( const char *pDebugName ) : CSimpleEmitter( pDebugName ) +{ +} + + +CSmartPtr CEmberEmitter::Create( const char *pDebugName ) +{ + return new CEmberEmitter( pDebugName ); +} + + +void CEmberEmitter::UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) +{ + float speed = VectorNormalize( pParticle->m_vecVelocity ); + Vector offset; + + speed -= ( 1.0f * timeDelta ); + + offset.Random( -0.025f, 0.025f ); + offset[2] = 0.0f; + + pParticle->m_vecVelocity += offset; + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= speed; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// timeDelta - +//----------------------------------------------------------------------------- +Vector CEmberEmitter::UpdateColor( const SimpleParticle *pParticle ) +{ + Vector color; + float ramp = 1.0f - ( pParticle->m_flLifetime / pParticle->m_flDieTime ); + + color[0] = ( (float) pParticle->m_uchColor[0] * ramp ) / 255.0f; + color[1] = ( (float) pParticle->m_uchColor[1] * ramp ) / 255.0f; + color[2] = ( (float) pParticle->m_uchColor[2] * ramp ) / 255.0f; + + return color; +} + +//================================================== +// C_Embers +//================================================== + +class C_Embers : public C_BaseEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_Embers, C_BaseEntity ); + + C_Embers(); + ~C_Embers(); + + void Start( void ); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool ShouldDraw( void ); + virtual void AddEntity( void ); + + //Server-side + int m_nDensity; + int m_nLifetime; + int m_nSpeed; + bool m_bEmit; + +protected: + + void SpawnEmber( void ); + + PMaterialHandle m_hMaterial; + TimedEvent m_tParticleSpawn; + CSmartPtr m_pEmitter; + +}; + +//Receive datatable +IMPLEMENT_CLIENTCLASS_DT( C_Embers, DT_Embers, CEmbers ) + RecvPropInt( RECVINFO( m_nDensity ) ), + RecvPropInt( RECVINFO( m_nLifetime ) ), + RecvPropInt( RECVINFO( m_nSpeed ) ), + RecvPropInt( RECVINFO( m_bEmit ) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +C_Embers::C_Embers() +{ + m_pEmitter = CEmberEmitter::Create( "C_Embers" ); +} + +C_Embers::~C_Embers() +{ +} + +void C_Embers::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + m_pEmitter->SetSortOrigin( GetAbsOrigin() ); + + Start(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_Embers::ShouldDraw() +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Embers::Start( void ) +{ + //Various setup info + m_tParticleSpawn.Init( m_nDensity ); + + m_hMaterial = m_pEmitter->GetPMaterial( "particle/fire" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Embers::AddEntity( void ) +{ + if ( m_bEmit == false ) + return; + + float tempDelta = gpGlobals->frametime; + + while( m_tParticleSpawn.NextEvent( tempDelta ) ) + { + SpawnEmber(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Embers::SpawnEmber( void ) +{ + Vector offset, mins, maxs; + + modelinfo->GetModelBounds( GetModel(), mins, maxs ); + + //Setup our spawn position + offset[0] = random->RandomFloat( mins[0], maxs[0] ); + offset[1] = random->RandomFloat( mins[1], maxs[1] ); + offset[2] = random->RandomFloat( mins[2], maxs[2] ); + + //Spawn the particle + SimpleParticle *sParticle = (SimpleParticle *) m_pEmitter->AddParticle( sizeof( SimpleParticle ), m_hMaterial, offset ); + + if (sParticle == NULL) + return; + + float cScale = random->RandomFloat( 0.75f, 1.0f ); + + //Set it up + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = m_nLifetime; + + sParticle->m_uchColor[0] = m_clrRender->r * cScale; + sParticle->m_uchColor[1] = m_clrRender->g * cScale; + sParticle->m_uchColor[2] = m_clrRender->b * cScale; + sParticle->m_uchStartAlpha = 255; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = 1; + sParticle->m_uchEndSize = 0; + sParticle->m_flRollDelta = 0; + sParticle->m_flRoll = 0; + + //Set the velocity + Vector velocity; + + AngleVectors( GetAbsAngles(), &velocity ); + + sParticle->m_vecVelocity = velocity * m_nSpeed; + + sParticle->m_vecVelocity[0] += random->RandomFloat( -(m_nSpeed/8), (m_nSpeed/8) ); + sParticle->m_vecVelocity[1] += random->RandomFloat( -(m_nSpeed/8), (m_nSpeed/8) ); + sParticle->m_vecVelocity[2] += random->RandomFloat( -(m_nSpeed/8), (m_nSpeed/8) ); + + UpdateVisibility(); +} + +//----------------------------------------------------------------------------- +// Quadratic spline beam effect +//----------------------------------------------------------------------------- +#include "beamdraw.h" + +class C_QuadraticBeam : public C_BaseEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_QuadraticBeam, C_BaseEntity ); + + //virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool ShouldDraw( void ) { return true; } + virtual int DrawModel( int ); + + virtual void GetRenderBounds( Vector& mins, Vector& maxs ) + { + ClearBounds( mins, maxs ); + AddPointToBounds( vec3_origin, mins, maxs ); + AddPointToBounds( m_targetPosition, mins, maxs ); + AddPointToBounds( m_controlPosition, mins, maxs ); + mins -= GetRenderOrigin(); + maxs -= GetRenderOrigin(); + } + +protected: + + Vector m_targetPosition; + Vector m_controlPosition; + float m_scrollRate; + float m_flWidth; +}; + +//Receive datatable +IMPLEMENT_CLIENTCLASS_DT( C_QuadraticBeam, DT_QuadraticBeam, CEnvQuadraticBeam ) + RecvPropVector( RECVINFO(m_targetPosition) ), + RecvPropVector( RECVINFO(m_controlPosition) ), + RecvPropFloat( RECVINFO(m_scrollRate) ), + RecvPropFloat( RECVINFO(m_flWidth) ), +END_RECV_TABLE() + +Vector Color32ToVector( const color32 &color ) +{ + return Vector( color.r * (1.0/255.0f), color.g * (1.0/255.0f), color.b * (1.0/255.0f) ); +} + +int C_QuadraticBeam::DrawModel( int ) +{ + Draw_SetSpriteTexture( GetModel(), 0, GetRenderMode() ); + Vector color = Color32ToVector( GetRenderColor() ); + DrawBeamQuadratic( GetRenderOrigin(), m_controlPosition, m_targetPosition, m_flWidth, color, gpGlobals->curtime*m_scrollRate ); + return 1; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +class SnowFallEffect : public CSimpleEmitter +{ +public: + + SnowFallEffect( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + static SnowFallEffect* Create( const char *pDebugName ) + { + return new SnowFallEffect( pDebugName ); + } + + void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) + { + float flSpeed = VectorNormalize( pParticle->m_vecVelocity ); + flSpeed -= timeDelta; + + pParticle->m_vecVelocity.x += RandomFloat( -0.025f, 0.025f ); + pParticle->m_vecVelocity.y += RandomFloat( -0.025f, 0.025f ); + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= flSpeed; + + Vector vecWindVelocity; + GetWindspeedAtTime( gpGlobals->curtime, vecWindVelocity ); + pParticle->m_vecVelocity += ( vecWindVelocity * r_SnowWindScale.GetFloat() ); + } + + void SimulateParticles( CParticleSimulateIterator *pIterator ) + { + float timeDelta = pIterator->GetTimeDelta(); + + SimpleParticle *pParticle = (SimpleParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + //Update velocity + UpdateVelocity( pParticle, timeDelta ); + pParticle->m_Pos += pParticle->m_vecVelocity * timeDelta; + + //Should this particle die? + pParticle->m_flLifetime += timeDelta; + UpdateRoll( pParticle, timeDelta ); + + if ( pParticle->m_flLifetime >= pParticle->m_flDieTime ) + { + pIterator->RemoveParticle( pParticle ); + } + else if ( !IsInAir( pParticle->m_Pos ) ) + { + pIterator->RemoveParticle( pParticle ); + } + + pParticle = (SimpleParticle*)pIterator->GetNext(); + } + } + + int GetParticleCount( void ) + { + return GetBinding().GetNumActiveParticles(); + } + + void SetBounds( const Vector &vecMin, const Vector &vecMax ) + { + GetBinding().SetBBox( vecMin, vecMax, true ); + } + + bool IsTransparent( void ) { return false; } + +private: + + SnowFallEffect( const SnowFallEffect & ); +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +class CSnowFallManager : public C_BaseEntity +{ +public: + + CSnowFallManager(); + ~CSnowFallManager(); + + bool CreateEmitter( void ); + + void SpawnClientEntity( void ); + void ClientThink(); + + void AddSnowFallEntity( CClient_Precipitation *pSnowEntity ); + + // Snow Effect + enum + { + SNOWFALL_NONE = 0, + SNOWFALL_AROUND_PLAYER, + SNOWFALL_IN_ENTITY, + }; + + bool IsTransparent( void ) { return false; } + +private: + + bool CreateSnowFallEmitter( void ); + void CreateSnowFall( void ); + void CreateSnowFallParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale ); + void CreateOutsideVolumeSnowParticles( float flCurrentTime, float flRadius, float flZoomScale ); + void CreateInsideVolumeSnowParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale ); + void CreateSnowParticlesSphere( float flRadius ); + void CreateSnowParticlesRay( float flRadius, const Vector &vecEyePos, const Vector &vecForward ); + void CreateSnowFallParticle( const Vector &vecParticleSpawn, int iBBox ); + + int StandingInSnowVolume( Vector &vecPoint ); + void FindSnowVolumes( Vector &vecCenter, float flRadius, Vector &vecEyePos, Vector &vecForward ); + + void UpdateBounds( const Vector &vecSnowMin, const Vector &vecSnowMax ); + +private: + + enum { MAX_SNOW_PARTICLES = 500 }; + enum { MAX_SNOW_LIST = 32 }; + + TimedEvent m_tSnowFallParticleTimer; + TimedEvent m_tSnowFallParticleTraceTimer; + + int m_iSnowFallArea; + CSmartPtr m_pSnowFallEmitter; + Vector m_vecSnowFallEmitOrigin; + float m_flSnowRadius; + + Vector m_vecMin; + Vector m_vecMax; + + int m_nActiveSnowCount; + int m_aActiveSnow[MAX_SNOW_LIST]; + + bool m_bRayParticles; + + typedef struct SnowFall_t + { + PMaterialHandle m_hMaterial; + CClient_Precipitation *m_pEntity; + SnowFallEffect *m_pEffect; + Vector m_vecMin; + Vector m_vecMax; + }; + + CUtlVector m_aSnow; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSnowFallManager::CSnowFallManager( void ) +{ + m_iSnowFallArea = SNOWFALL_NONE; + m_pSnowFallEmitter = NULL; + m_vecSnowFallEmitOrigin.Init(); + m_flSnowRadius = 0.0f; + m_vecMin.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + m_vecMax.Init( FLT_MIN, FLT_MIN, FLT_MIN ); + m_nActiveSnowCount = 0; + m_aSnow.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSnowFallManager::~CSnowFallManager( void ) +{ + m_aSnow.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSnowFallManager::CreateEmitter( void ) +{ + return CreateSnowFallEmitter(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSnowFallManager::SpawnClientEntity( void ) +{ + m_tSnowFallParticleTimer.Init( 500 ); + m_tSnowFallParticleTraceTimer.Init( 6 ); + m_iSnowFallArea = SNOWFALL_NONE; + + // Have the Snow Fall Manager think for all the snow fall entities. + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSnowFallManager::CreateSnowFallEmitter( void ) +{ + if ( ( m_pSnowFallEmitter = SnowFallEffect::Create( "snowfall" ) ) == NULL ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSnowFallManager::ClientThink( void ) +{ + if ( !r_SnowEnable.GetBool() ) + return; + + // Make sure we have a snow fall emitter. + if ( !m_pSnowFallEmitter ) + { + if ( !CreateSnowFallEmitter() ) + return; + } + + CreateSnowFall(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSnowEntity - +//----------------------------------------------------------------------------- +void CSnowFallManager::AddSnowFallEntity( CClient_Precipitation *pSnowEntity ) +{ + if ( !pSnowEntity ) + return; + + int nSnowCount = m_aSnow.Count(); + int iSnow = 0; + for ( iSnow = 0; iSnow < nSnowCount; ++iSnow ) + { + if ( m_aSnow[iSnow].m_pEntity == pSnowEntity ) + break; + } + + if ( iSnow != nSnowCount ) + return; + + iSnow = m_aSnow.AddToTail(); + m_aSnow[iSnow].m_pEntity = pSnowEntity; + m_aSnow[iSnow].m_pEffect = SnowFallEffect::Create( "snowfall" ); + m_aSnow[iSnow].m_hMaterial = ParticleMgr()->GetPMaterial( "particle/snow" ); + + VectorCopy( pSnowEntity->WorldAlignMins(), m_aSnow[iSnow].m_vecMin ); + VectorCopy( pSnowEntity->WorldAlignMaxs(), m_aSnow[iSnow].m_vecMax ); + + UpdateBounds( m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSnowFallManager::UpdateBounds( const Vector &vecSnowMin, const Vector &vecSnowMax ) +{ + int iAxis = 0; + for ( iAxis = 0; iAxis < 3; ++iAxis ) + { + if ( vecSnowMin[iAxis] < m_vecMin[iAxis] ) + { + m_vecMin[iAxis] = vecSnowMin[iAxis]; + } + + if ( vecSnowMax[iAxis] > m_vecMax[iAxis] ) + { + m_vecMax[iAxis] = vecSnowMax[iAxis]; + } + } + + Assert( m_pSnowFallEmitter ); + m_pSnowFallEmitter->SetBounds( m_vecMin, m_vecMax ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecPoint - +// Output : int +//----------------------------------------------------------------------------- +int CSnowFallManager::StandingInSnowVolume( Vector &vecPoint ) +{ + trace_t traceSnow; + + int nSnowCount = m_aSnow.Count(); + int iSnow = 0; + for ( iSnow = 0; iSnow < nSnowCount; ++iSnow ) + { + UTIL_TraceModel( vecPoint, vecPoint, vec3_origin, vec3_origin, static_cast( m_aSnow[iSnow].m_pEntity ), COLLISION_GROUP_NONE, &traceSnow ); + if ( traceSnow.startsolid ) + return iSnow; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecCenter - +// flRadius - +//----------------------------------------------------------------------------- +void CSnowFallManager::FindSnowVolumes( Vector &vecCenter, float flRadius, Vector &vecEyePos, Vector &vecForward ) +{ + // Reset. + m_nActiveSnowCount = 0; + m_bRayParticles = false; + + int nSnowCount = m_aSnow.Count(); + int iSnow = 0; + for ( iSnow = 0; iSnow < nSnowCount; ++iSnow ) + { + // Check to see if the volume is in the PVS. + bool bInPVS = g_pClientLeafSystem->IsRenderableInPVS( m_aSnow[iSnow].m_pEntity->GetClientRenderable() ); + if ( !bInPVS ) + continue; + + // Check to see if a snow volume is inside the given radius. + if ( IsBoxIntersectingSphere( m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax, vecCenter, flRadius ) ) + { + m_aActiveSnow[m_nActiveSnowCount] = iSnow; + ++m_nActiveSnowCount; + if ( m_nActiveSnowCount >= MAX_SNOW_LIST ) + { + DevWarning( 1, "Max Active Snow Volume Count!\n" ); + break; + } + } + // Check to see if a snow volume is outside of the sphere radius, but is along line-of-sight. + else + { + CBaseTrace trace; + Vector vecNewForward; + vecNewForward = vecForward * r_SnowRayLength.GetFloat(); + vecNewForward.z = 0.0f; + IntersectRayWithBox( vecEyePos, vecNewForward, m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax, 0.325f, &trace ); + if ( trace.fraction < 1.0f ) + { + m_aActiveSnow[m_nActiveSnowCount] = iSnow; + ++m_nActiveSnowCount; + if ( m_nActiveSnowCount >= MAX_SNOW_LIST ) + { + DevWarning( 1, "Max Active Snow Volume Count!\n" ); + break; + } + + m_bRayParticles = true; + } + } + } + + // Debugging code! +#ifdef _DEBUG + if ( r_SnowDebugBox.GetFloat() != 0.0f ) + { + for ( iSnow = 0; iSnow < m_nActiveSnowCount; ++iSnow ) + { + Vector vecCenter, vecMin, vecMax; + vecCenter = ( m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax ) * 0.5; + vecMin = m_aSnow[iSnow].m_vecMin - vecCenter; + vecMax = m_aSnow[iSnow].m_vecMax - vecCenter; + debugoverlay->AddBoxOverlay( vecCenter, vecMin, vecMax, QAngle( 0, 0, 0 ), 200, 0, 0, 25, r_SnowDebugBox.GetFloat() ); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSnowFallManager::CreateSnowFall( void ) +{ +#if 1 + VPROF_BUDGET( "SnowFall", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); +#endif + + // Check to see if we have a local player before starting the snow around a local player. + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer == NULL ) + return; + + // Get the current frame time. + float flCurrentTime = gpGlobals->frametime; + + // Get the players data to determine where the snow emitter should reside. + VectorCopy( pPlayer->EyePosition(), m_vecSnowFallEmitOrigin ); + Vector vecForward; + pPlayer->GetVectors( &vecForward, NULL, NULL ); + vecForward.z = 0.0f; + Vector vecVelocity = pPlayer->GetAbsVelocity(); + float flSpeed = VectorNormalize( vecVelocity ); + m_vecSnowFallEmitOrigin += ( vecForward * ( 64.0f + ( flSpeed * 0.4f * r_SnowPosScale.GetFloat() ) ) ); + m_vecSnowFallEmitOrigin += ( vecVelocity * ( flSpeed * 1.25f * r_SnowSpeedScale.GetFloat() ) ); + + // Check to see if the player is zoomed. + bool bZoomed = ( pPlayer->GetFOV() != pPlayer->GetDefaultFOV() ); + float flZoomScale = 1.0f; + if ( bZoomed ) + { + flZoomScale = pPlayer->GetDefaultFOV() / pPlayer->GetFOV(); + flZoomScale *= 0.5f; + } + + // Time to test for a snow volume yet? (Only do this 6 times a second!) + if ( m_tSnowFallParticleTraceTimer.NextEvent( flCurrentTime ) ) + { + // Reset the active snow emitter. + m_iSnowFallArea = SNOWFALL_NONE; + + // Set the trace start and the emit origin. + Vector vecTraceStart; + VectorCopy( pPlayer->EyePosition(), vecTraceStart ); + + int iSnowVolume = StandingInSnowVolume( vecTraceStart ); + if ( iSnowVolume != -1 ) + { + m_flSnowRadius = r_SnowInsideRadius.GetFloat() + ( flSpeed * 0.5f ); + m_iSnowFallArea = SNOWFALL_AROUND_PLAYER; + } + else + { + m_flSnowRadius = r_SnowOutsideRadius.GetFloat(); + } + + float flRadius = m_flSnowRadius; + if ( bZoomed ) + { + if ( m_iSnowFallArea == SNOWFALL_AROUND_PLAYER ) + { + flRadius = r_SnowOutsideRadius.GetFloat() * flZoomScale; + } + else + { + flRadius *= flZoomScale; + } + } + + FindSnowVolumes( m_vecSnowFallEmitOrigin, flRadius, pPlayer->EyePosition(), vecForward ); + if ( m_nActiveSnowCount != 0 && m_iSnowFallArea != SNOWFALL_AROUND_PLAYER ) + { + // We found an active snow emitter. + m_iSnowFallArea = SNOWFALL_IN_ENTITY; + + } + } + + if ( m_iSnowFallArea == SNOWFALL_NONE ) + return; + + // Set the origin in the snow emitter. + m_pSnowFallEmitter->SetSortOrigin( m_vecSnowFallEmitOrigin ); + + // Create snow fall particles. + CreateSnowFallParticles( flCurrentTime, m_flSnowRadius, pPlayer->EyePosition(), vecForward, flZoomScale ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flCurrentTime - +// flRadius - +// &vecEyePos - +// &vecForward - +// flZoomScale - +//----------------------------------------------------------------------------- +void CSnowFallManager::CreateSnowFallParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale ) +{ + // Outside of a snow volume. + if ( m_iSnowFallArea == SNOWFALL_IN_ENTITY ) + { + CreateOutsideVolumeSnowParticles( flCurrentTime, flRadius, flZoomScale ); + } + // Inside of a snow volume. + else + { + CreateInsideVolumeSnowParticles( flCurrentTime, flRadius, vecEyePos, vecForward, flZoomScale ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flCurrentTime - +// flRadius - +// flZoomScale - +//----------------------------------------------------------------------------- +void CSnowFallManager::CreateOutsideVolumeSnowParticles( float flCurrentTime, float flRadius, float flZoomScale ) +{ + Vector vecParticleSpawn; + + // Outside of a snow volume. + int iSnow = 0; + float flRadiusScaled = flRadius * flZoomScale; + float flRadius2 = flRadiusScaled * flRadiusScaled; + + // Add as many particles as we need + while ( m_tSnowFallParticleTimer.NextEvent( flCurrentTime ) ) + { + // Check for a max particle count. + if ( m_pSnowFallEmitter->GetParticleCount() >= r_SnowParticles.GetInt() ) + continue; + + vecParticleSpawn.x = RandomFloat( m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.x, m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.x ); + vecParticleSpawn.y = RandomFloat( m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.y, m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.y ); + vecParticleSpawn.z = RandomFloat( m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.z, m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.z ); + + float flDistance2 = ( m_vecSnowFallEmitOrigin - vecParticleSpawn ).LengthSqr(); + if ( flDistance2 < flRadius2 ) + { + CreateSnowFallParticle( vecParticleSpawn, m_aActiveSnow[iSnow] ); + } + + iSnow = ( iSnow + 1 ) % m_nActiveSnowCount; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flCurrentTime - +// flRadius - +// &vecEyePos - +// &vecForward - +// flZoomScale - +//----------------------------------------------------------------------------- +void CSnowFallManager::CreateInsideVolumeSnowParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale ) +{ + Vector vecParticleSpawn; + + // Check/Setup for zoom. + bool bZoomed = ( flZoomScale > 1.0f ); + float flZoomRadius = 0.0f; + Vector vecZoomEmitOrigin; + if ( bZoomed ) + { + vecZoomEmitOrigin = m_vecSnowFallEmitOrigin + ( vecForward * ( r_SnowZoomOffset.GetFloat() * flZoomScale ) ); + flZoomRadius = flRadius * flZoomScale; + } + + int iIndex = 0; + + // Add as many particles as we need + while ( m_tSnowFallParticleTimer.NextEvent( flCurrentTime ) ) + { + // Check for a max particle count. + if ( m_pSnowFallEmitter->GetParticleCount() >= r_SnowParticles.GetInt() ) + continue; + + // Create particle inside of sphere. + if ( iIndex > 0 ) + { + CreateSnowParticlesSphere( flZoomRadius ); + CreateSnowParticlesRay( flZoomRadius, vecEyePos, vecForward ); + } + else + { + CreateSnowParticlesSphere( flRadius ); + CreateSnowParticlesRay( flRadius, vecEyePos, vecForward ); + } + + // Increment if zoomed. + if ( bZoomed ) + { + iIndex = ( iIndex + 1 ) % 3; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flRadius - +//----------------------------------------------------------------------------- +void CSnowFallManager::CreateSnowParticlesSphere( float flRadius ) +{ + Vector vecParticleSpawn; + + vecParticleSpawn.x = m_vecSnowFallEmitOrigin.x + RandomFloat( -flRadius, flRadius ); + vecParticleSpawn.y = m_vecSnowFallEmitOrigin.y + RandomFloat( -flRadius, flRadius ); + vecParticleSpawn.z = m_vecSnowFallEmitOrigin.z + RandomFloat( -flRadius, flRadius ); + + int iSnow = 0; + for ( iSnow = 0; iSnow < m_nActiveSnowCount; ++iSnow ) + { + if ( ( vecParticleSpawn.x < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.x ) || ( vecParticleSpawn.x > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.x ) ) + continue; + if ( ( vecParticleSpawn.y < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.y ) || ( vecParticleSpawn.y > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.y ) ) + continue; + if ( ( vecParticleSpawn.z < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.z ) || ( vecParticleSpawn.z > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.z ) ) + continue; + + break; + } + + if ( iSnow == m_nActiveSnowCount ) + return; + + CreateSnowFallParticle( vecParticleSpawn, m_aActiveSnow[iSnow] ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecEyePos - +// &vecForward - +//----------------------------------------------------------------------------- +void CSnowFallManager::CreateSnowParticlesRay( float flRadius, const Vector &vecEyePos, const Vector &vecForward ) +{ + // Check to see if we should create particles along line-of-sight. + if ( !m_bRayParticles && r_SnowRayEnable.GetBool() ) + return; + + Vector vecParticleSpawn; + + // Create a particle down the player's view beyond the radius. + float flRayRadius = r_SnowRayRadius.GetFloat(); + + Vector vecNewForward; + vecNewForward = vecForward * RandomFloat( flRadius, r_SnowRayLength.GetFloat() ); + + vecParticleSpawn.x = vecEyePos.x + vecNewForward.x; + vecParticleSpawn.y = vecEyePos.y + vecNewForward.y; + vecParticleSpawn.z = vecEyePos.z + RandomFloat( 72, flRayRadius ); + vecParticleSpawn.x += RandomFloat( -flRayRadius, flRayRadius ); + vecParticleSpawn.y += RandomFloat( -flRayRadius, flRayRadius ); + + int iSnow = 0; + for ( iSnow = 0; iSnow < m_nActiveSnowCount; ++iSnow ) + { + if ( ( vecParticleSpawn.x < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.x ) || ( vecParticleSpawn.x > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.x ) ) + continue; + if ( ( vecParticleSpawn.y < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.y ) || ( vecParticleSpawn.y > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.y ) ) + continue; + if ( ( vecParticleSpawn.z < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.z ) || ( vecParticleSpawn.z > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.z ) ) + continue; + + break; + } + + if ( iSnow == m_nActiveSnowCount ) + return; + + CreateSnowFallParticle( vecParticleSpawn, m_aActiveSnow[iSnow] ); +} + +void CSnowFallManager::CreateSnowFallParticle( const Vector &vecParticleSpawn, int iSnow ) +{ + SimpleParticle *pParticle = ( SimpleParticle* )m_pSnowFallEmitter->AddParticle( sizeof( SimpleParticle ), m_aSnow[iSnow].m_hMaterial, vecParticleSpawn ); + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_vecVelocity = Vector( RandomFloat( -5.0f, 5.0f ), RandomFloat( -5.0f, 5.0f ), ( RandomFloat( -25, -35 ) * r_SnowFallSpeed.GetFloat() ) ); + pParticle->m_flDieTime = fabs( ( vecParticleSpawn.z - m_aSnow[iSnow].m_vecMin.z ) / ( pParticle->m_vecVelocity.z - 0.1 ) ); + + // Probably want to put the color in the snow entity. +// pParticle->m_uchColor[0] = 150;//color; +// pParticle->m_uchColor[1] = 175;//color; +// pParticle->m_uchColor[2] = 200;//color; + pParticle->m_uchColor[0] = r_SnowColorRed.GetInt(); + pParticle->m_uchColor[1] = r_SnowColorGreen.GetInt(); + pParticle->m_uchColor[2] = r_SnowColorBlue.GetInt(); + + pParticle->m_uchStartSize = r_SnowStartSize.GetInt(); + pParticle->m_uchEndSize = r_SnowEndSize.GetInt(); + +// pParticle->m_uchStartAlpha = 255; + pParticle->m_uchStartAlpha = r_SnowStartAlpha.GetInt(); + pParticle->m_uchEndAlpha = r_SnowEndAlpha.GetInt(); + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -0.15f, 0.15f ); + + pParticle->m_iFlags = SIMPLE_PARTICLE_FLAG_WINDBLOWN; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool SnowFallManagerCreate( CClient_Precipitation *pSnowEntity ) +{ + if ( !s_pSnowFallMgr ) + { + s_pSnowFallMgr = new CSnowFallManager(); + s_pSnowFallMgr->CreateEmitter(); + s_pSnowFallMgr->InitializeAsClientEntity( NULL, RENDER_GROUP_OTHER ); + if ( !s_pSnowFallMgr ) + return false; + } + + s_pSnowFallMgr->AddSnowFallEntity( pSnowEntity ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SnowFallManagerDestroy( void ) +{ + if ( s_pSnowFallMgr ) + { + delete s_pSnowFallMgr; + s_pSnowFallMgr = NULL; + } +} diff --git a/game/client/c_effects.h b/game/client/c_effects.h new file mode 100644 index 00000000..9b83b3cd --- /dev/null +++ b/game/client/c_effects.h @@ -0,0 +1,18 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_EFFECTS_H +#define C_EFFECTS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Draw rain effects. +void DrawPrecipitation(); + + +#endif // C_EFFECTS_H diff --git a/game/client/c_entitydissolve.cpp b/game/client/c_entitydissolve.cpp new file mode 100644 index 00000000..ff4294b8 --- /dev/null +++ b/game/client/c_entitydissolve.cpp @@ -0,0 +1,773 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "IViewRender.h" +#include "view.h" +#include "studio.h" +#include "bone_setup.h" +#include "model_types.h" +#include "beamdraw.h" +#include "engine/ivdebugoverlay.h" +#include "iviewrender_beams.h" +#include "fx.h" +#include "IEffects.h" +#include "c_entitydissolve.h" +#include "movevars_shared.h" +#include "ClientEffectPrecacheSystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectBuild ) +CLIENTEFFECT_MATERIAL( "effects/tesla_glow_noz" ) +CLIENTEFFECT_MATERIAL( "effects/spark" ) +CLIENTEFFECT_MATERIAL( "effects/combinemuzzle2" ) +CLIENTEFFECT_REGISTER_END() + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_DT( C_EntityDissolve, DT_EntityDissolve, CEntityDissolve ) + RecvPropTime(RECVINFO(m_flStartTime)), + RecvPropFloat(RECVINFO(m_flFadeOutStart)), + RecvPropFloat(RECVINFO(m_flFadeOutLength)), + RecvPropFloat(RECVINFO(m_flFadeOutModelStart)), + RecvPropFloat(RECVINFO(m_flFadeOutModelLength)), + RecvPropFloat(RECVINFO(m_flFadeInStart)), + RecvPropFloat(RECVINFO(m_flFadeInLength)), + RecvPropInt(RECVINFO(m_nDissolveType)), + RecvPropVector( RECVINFO( m_vDissolverOrigin) ), + RecvPropInt( RECVINFO( m_nMagnitude ) ), +END_RECV_TABLE() + +extern PMaterialHandle g_Material_Spark; +PMaterialHandle g_Material_AR2Glow = NULL; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_EntityDissolve::C_EntityDissolve( void ) +{ + m_bLinkedToServerEnt = true; + m_pController = false; + m_bCoreExplode = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityDissolve::GetRenderBounds( Vector& theMins, Vector& theMaxs ) +{ + if ( GetMoveParent() ) + { + GetMoveParent()->GetRenderBounds( theMins, theMaxs ); + } + else + { + theMins = GetAbsOrigin(); + theMaxs = theMaxs; + } +} + +//----------------------------------------------------------------------------- +// On data changed +//----------------------------------------------------------------------------- +void C_EntityDissolve::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + if ( updateType == DATA_UPDATE_CREATED ) + { + m_flNextSparkTime = m_flStartTime; + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +} + +//----------------------------------------------------------------------------- +// Cleanup +//----------------------------------------------------------------------------- +void C_EntityDissolve::UpdateOnRemove( void ) +{ + if ( m_pController ) + { + physenv->DestroyMotionController( m_pController ); + m_pController = NULL; + } + + BaseClass::UpdateOnRemove(); +} + +//------------------------------------------------------------------------------ +// Apply the forces to the entity +//------------------------------------------------------------------------------ +IMotionEvent::simresult_e C_EntityDissolve::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) +{ + linear.Init(); + angular.Init(); + + // Make it zero g + linear.z -= -1.02 * sv_gravity.GetFloat(); + + Vector vel; + AngularImpulse angVel; + pObject->GetVelocity( &vel, &angVel ); + vel += linear * deltaTime; // account for gravity scale + + Vector unitVel = vel; + Vector unitAngVel = angVel; + + float speed = VectorNormalize( unitVel ); +// float angSpeed = VectorNormalize( unitAngVel ); + +// float speedScale = 0.0; +// float angSpeedScale = 0.0; + + float flLinearLimit = 50; + float flLinearLimitDelta = 40; + if ( speed > flLinearLimit ) + { + float flDeltaVel = (flLinearLimit - speed) / deltaTime; + if ( flLinearLimitDelta != 0.0f ) + { + float flMaxDeltaVel = -flLinearLimitDelta / deltaTime; + if ( flDeltaVel < flMaxDeltaVel ) + { + flDeltaVel = flMaxDeltaVel; + } + } + VectorMA( linear, flDeltaVel, unitVel, linear ); + } + + return SIM_GLOBAL_ACCELERATION; +} + + +//----------------------------------------------------------------------------- +// Tesla effect +//----------------------------------------------------------------------------- +static void FX_BuildTesla( C_BaseEntity *pEntity, Vector &vecOrigin, Vector &vecEnd ) +{ + BeamInfo_t beamInfo; + beamInfo.m_pStartEnt = pEntity; + beamInfo.m_nStartAttachment = 0; + beamInfo.m_pEndEnt = NULL; + beamInfo.m_nEndAttachment = 0; + beamInfo.m_nType = TE_BEAMTESLA; + beamInfo.m_vecStart = vecOrigin; + beamInfo.m_vecEnd = vecEnd; + beamInfo.m_pszModelName = "sprites/lgtning.vmt"; + beamInfo.m_flHaloScale = 0.0; + beamInfo.m_flLife = random->RandomFloat( 0.25f, 1.0f ); + beamInfo.m_flWidth = random->RandomFloat( 8.0f, 14.0f ); + beamInfo.m_flEndWidth = 1.0f; + beamInfo.m_flFadeLength = 0.5f; + beamInfo.m_flAmplitude = 24; + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 150.0f; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 30.0; + beamInfo.m_flRed = 255.0; + beamInfo.m_flGreen = 255.0; + beamInfo.m_flBlue = 255.0; + beamInfo.m_nSegments = 18; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = 0; //FBEAM_ONLYNOISEONCE; + + beams->CreateBeamEntPoint( beamInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: Tesla effect +//----------------------------------------------------------------------------- +void C_EntityDissolve::BuildTeslaEffect( mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, bool bRandom, float flYawOffset ) +{ + Vector vecOrigin; + QAngle vecAngles; + MatrixGetColumn( hitboxToWorld, 3, vecOrigin ); + MatrixAngles( hitboxToWorld, vecAngles.Base() ); + C_BaseEntity *pEntity = GetMoveParent(); + + // Make a couple of tries at it + int iTries = -1; + Vector vecForward; + trace_t tr; + do + { + iTries++; + + // Some beams are deliberatly aimed around the point, the rest are random. + if ( !bRandom ) + { + QAngle vecTemp = vecAngles; + vecTemp[YAW] += flYawOffset; + AngleVectors( vecTemp, &vecForward ); + + // Randomly angle it up or down + vecForward.z = RandomFloat( -1, 1 ); + } + else + { + vecForward = RandomVector( -1, 1 ); + } + + UTIL_TraceLine( vecOrigin, vecOrigin + (vecForward * 192), MASK_SHOT, pEntity, COLLISION_GROUP_NONE, &tr ); + } while ( tr.fraction >= 1.0 && iTries < 3 ); + + Vector vecEnd = tr.endpos - (vecForward * 8); + + // Only spark & glow if we hit something + if ( tr.fraction < 1.0 ) + { + if ( !EffectOccluded( tr.endpos ) ) + { + // Move it towards the camera + Vector vecFlash = tr.endpos; + Vector vecForward; + AngleVectors( MainViewAngles(), &vecForward ); + vecFlash -= (vecForward * 8); + + g_pEffects->EnergySplash( vecFlash, -vecForward, false ); + + // End glow + CSmartPtr pSimple = CSimpleEmitter::Create( "dust" ); + pSimple->SetSortOrigin( vecFlash ); + SimpleParticle *pParticle; + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/tesla_glow_noz" ), vecFlash ); + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = RandomFloat( 0.5, 1 ); + pParticle->m_vecVelocity = vec3_origin; + Vector color( 1,1,1 ); + float colorRamp = RandomFloat( 0.75f, 1.25f ); + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + pParticle->m_uchStartSize = RandomFloat( 6,13 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize - 2; + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 10; + pParticle->m_flRoll = RandomFloat( 0,360 ); + pParticle->m_flRollDelta = 0; + } + } + } + + // Build the tesla + FX_BuildTesla( pEntity, vecOrigin, tr.endpos ); +} + +//----------------------------------------------------------------------------- +// Sorts the components of a vector +//----------------------------------------------------------------------------- +static inline void SortAbsVectorComponents( const Vector& src, int* pVecIdx ) +{ + Vector absVec( fabs(src[0]), fabs(src[1]), fabs(src[2]) ); + + int maxIdx = (absVec[0] > absVec[1]) ? 0 : 1; + if (absVec[2] > absVec[maxIdx]) + { + maxIdx = 2; + } + + // always choose something right-handed.... + switch( maxIdx ) + { + case 0: + pVecIdx[0] = 1; + pVecIdx[1] = 2; + pVecIdx[2] = 0; + break; + case 1: + pVecIdx[0] = 2; + pVecIdx[1] = 0; + pVecIdx[2] = 1; + break; + case 2: + pVecIdx[0] = 0; + pVecIdx[1] = 1; + pVecIdx[2] = 2; + break; + } +} + +//----------------------------------------------------------------------------- +// Compute the bounding box's center, size, and basis +//----------------------------------------------------------------------------- +void C_EntityDissolve::ComputeRenderInfo( mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, + Vector *pVecAbsOrigin, Vector *pXVec, Vector *pYVec ) +{ + // Compute the center of the hitbox in worldspace + Vector vecHitboxCenter; + VectorAdd( pHitBox->bbmin, pHitBox->bbmax, vecHitboxCenter ); + vecHitboxCenter *= 0.5f; + VectorTransform( vecHitboxCenter, hitboxToWorld, *pVecAbsOrigin ); + + // Get the object's basis + Vector vec[3]; + MatrixGetColumn( hitboxToWorld, 0, vec[0] ); + MatrixGetColumn( hitboxToWorld, 1, vec[1] ); + MatrixGetColumn( hitboxToWorld, 2, vec[2] ); +// vec[1] *= -1.0f; + + Vector vecViewDir; + VectorSubtract( CurrentViewOrigin(), *pVecAbsOrigin, vecViewDir ); + VectorNormalize( vecViewDir ); + + // Project the shadow casting direction into the space of the hitbox + Vector localViewDir; + localViewDir[0] = DotProduct( vec[0], vecViewDir ); + localViewDir[1] = DotProduct( vec[1], vecViewDir ); + localViewDir[2] = DotProduct( vec[2], vecViewDir ); + + // Figure out which vector has the largest component perpendicular + // to the view direction... + // Sort by how perpendicular it is + int vecIdx[3]; + SortAbsVectorComponents( localViewDir, vecIdx ); + + // Here's our hitbox basis vectors; namely the ones that are + // most perpendicular to the view direction + *pXVec = vec[vecIdx[0]]; + *pYVec = vec[vecIdx[1]]; + + // Project them into a plane perpendicular to the view direction + *pXVec -= vecViewDir * DotProduct( vecViewDir, *pXVec ); + *pYVec -= vecViewDir * DotProduct( vecViewDir, *pYVec ); + VectorNormalize( *pXVec ); + VectorNormalize( *pYVec ); + + // Compute the hitbox size + Vector boxSize; + VectorSubtract( pHitBox->bbmax, pHitBox->bbmin, boxSize ); + + // We project the two longest sides into the vectors perpendicular + // to the projection direction, then add in the projection of the perp direction + Vector2D size( boxSize[vecIdx[0]], boxSize[vecIdx[1]] ); + size.x *= fabs( DotProduct( vec[vecIdx[0]], *pXVec ) ); + size.y *= fabs( DotProduct( vec[vecIdx[1]], *pYVec ) ); + + // Add the third component into x and y + size.x += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], *pXVec ) ); + size.y += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], *pYVec ) ); + + // Bloat a bit, since the shadow wants to extend outside the model a bit + size *= 2.0f; + + // Clamp the minimum size + Vector2DMax( size, Vector2D(10.0f, 10.0f), size ); + + // Factor the size into the xvec + yvec + (*pXVec) *= size.x * 0.5f; + (*pYVec) *= size.y * 0.5f; +} + + +//----------------------------------------------------------------------------- +// Sparks! +//----------------------------------------------------------------------------- +void C_EntityDissolve::DoSparks( mstudiohitboxset_t *set, matrix3x4_t *hitboxbones[MAXSTUDIOBONES] ) +{ + if ( m_flNextSparkTime > gpGlobals->curtime ) + return; + + float dt = m_flStartTime + m_flFadeOutStart - gpGlobals->curtime; + dt = clamp( dt, 0.0f, m_flFadeOutStart ); + + float flNextTime; + if (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) + { + flNextTime = SimpleSplineRemapVal( dt, 0.0f, m_flFadeOutStart, 2.0f * TICK_INTERVAL, 0.4f ); + } + else + { + // m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT); + flNextTime = SimpleSplineRemapVal( dt, 0.0f, m_flFadeOutStart, 0.3f, 1.0f ); + } + + m_flNextSparkTime = gpGlobals->curtime + flNextTime; + + // Send out beams around us + int iNumBeamsAround = 2; + int iNumRandomBeams = 1; + int iTotalBeams = iNumBeamsAround + iNumRandomBeams; + float flYawOffset = RandomFloat(0,360); + for ( int i = 0; i < iTotalBeams; i++ ) + { + int nHitbox = random->RandomInt( 0, set->numhitboxes - 1 ); + mstudiobbox_t *pBox = set->pHitbox(nHitbox); + + float flActualYawOffset = 0; + bool bRandom = ( i >= iNumBeamsAround ); + if ( !bRandom ) + { + flActualYawOffset = anglemod( flYawOffset + ((360 / iTotalBeams) * i) ); + } + + BuildTeslaEffect( pBox, *hitboxbones[pBox->bone], bRandom, flActualYawOffset ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityDissolve::SetupEmitter( void ) +{ + if ( !m_pEmitter ) + { + m_pEmitter = CSimpleEmitter::Create( "C_EntityDissolve" ); + m_pEmitter->SetSortOrigin( GetAbsOrigin() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float C_EntityDissolve::GetFadeInPercentage( void ) +{ + float dt = gpGlobals->curtime - m_flStartTime; + + if ( dt > m_flFadeOutStart ) + return 1.0f; + + if ( dt < m_flFadeInStart ) + return 0.0f; + + if ( (dt > m_flFadeInStart) && (dt < m_flFadeInStart + m_flFadeInLength) ) + { + dt -= m_flFadeInStart; + + return ( dt / m_flFadeInLength ); + } + + return 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float C_EntityDissolve::GetFadeOutPercentage( void ) +{ + float dt = gpGlobals->curtime - m_flStartTime; + + if ( dt < m_flFadeInStart ) + return 1.0f; + + if ( dt > m_flFadeOutStart ) + { + dt -= m_flFadeOutStart; + + if ( dt > m_flFadeOutLength ) + return 0.0f; + + return 1.0f - ( dt / m_flFadeOutLength ); + } + + return 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float C_EntityDissolve::GetModelFadeOutPercentage( void ) +{ + float dt = gpGlobals->curtime - m_flStartTime; + + if ( dt < m_flFadeOutModelStart ) + return 1.0f; + + if ( dt > m_flFadeOutModelStart ) + { + dt -= m_flFadeOutModelStart; + + if ( dt > m_flFadeOutModelLength ) + return 0.0f; + + return 1.0f - ( dt / m_flFadeOutModelLength ); + } + + return 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityDissolve::ClientThink( void ) +{ + C_BaseAnimating *pAnimating = GetMoveParent() ? GetMoveParent()->GetBaseAnimating() : NULL; + if (!pAnimating) + return; + + // NOTE: IsRagdoll means *client-side* ragdoll. We shouldn't be trying to fight + // the server ragdoll (or any server physics) on the client + if (( !m_pController ) && ( m_nDissolveType == ENTITY_DISSOLVE_NORMAL ) && pAnimating->IsRagdoll()) + { + IPhysicsObject *ppList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int nCount = pAnimating->VPhysicsGetObjectList( ppList, ARRAYSIZE(ppList) ); + if ( nCount > 0 ) + { + m_pController = physenv->CreateMotionController( this ); + for ( int i = 0; i < nCount; ++i ) + { + m_pController->AttachObject( ppList[i], true ); + } + } + } + + color32 color; + + color.r = color.g = color.b = ( 1.0f - GetFadeInPercentage() ) * 255.0f; + color.a = GetModelFadeOutPercentage() * 255.0f; + + // Setup the entity fade + pAnimating->SetRenderMode( kRenderTransColor ); + pAnimating->SetRenderColor( color.r, color.g, color.b, color.a ); + + if ( GetModelFadeOutPercentage() <= 0.2f ) + { + m_bCoreExplode = true; + } + + // If we're dead, fade out + if ( GetFadeOutPercentage() <= 0.0f ) + { + // Do NOT remove from the client entity list. It'll confuse the local network backdoor, and the entity will never get destroyed + // because when the server says to destroy it, the client won't be able to find it. + // ClientEntityList().RemoveEntity( GetClientHandle() ); + + partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() ); + + RemoveFromLeafSystem(); + + //FIXME: Ick! + //Adrian: I'll assume we don't need the ragdoll either so I'll remove that too. + if ( m_bLinkedToServerEnt == false ) + { + Release(); + + C_ClientRagdoll *pRagdoll = dynamic_cast ( pAnimating ); + + if ( pRagdoll ) + { + pRagdoll->ReleaseRagdoll(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flags - +// Output : int +//----------------------------------------------------------------------------- +int C_EntityDissolve::DrawModel( int flags ) +{ + // See if we should draw + if ( gpGlobals->frametime == 0 || m_bReadyToDraw == false ) + return 0; + + C_BaseAnimating *pAnimating = GetMoveParent() ? GetMoveParent()->GetBaseAnimating() : NULL; + if ( pAnimating == NULL ) + return 0; + + matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; + if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) == false ) + return 0; + + studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() ); + if ( pStudioHdr == NULL ) + return false; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() ); + if ( set == NULL ) + return false; + + // Make sure the emitter is setup properly + SetupEmitter(); + + // Get fade percentages for the effect + float fadeInPerc = GetFadeInPercentage(); + float fadeOutPerc = GetFadeOutPercentage(); + + float fadePerc = ( fadeInPerc >= 1.0f ) ? fadeOutPerc : fadeInPerc; + + Vector vecSkew = vec3_origin; + + // Do extra effects under certain circumstances + if ( ( fadePerc < 0.99f ) && ( (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) ) ) + { + DoSparks( set, hitboxbones ); + } + + // Skew the particles in front or in back of their targets + vecSkew = CurrentViewForward() * ( 8.0f - ( ( 1.0f - fadePerc ) * 32.0f ) ); + + float spriteScale = ( ( gpGlobals->curtime - m_flStartTime ) / m_flFadeOutLength ); + spriteScale = clamp( spriteScale, 0.75f, 1.0f ); + + // Cache off this material reference + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = ParticleMgr()->GetPMaterial( "effects/spark" ); + } + + if ( g_Material_AR2Glow == NULL ) + { + g_Material_AR2Glow = ParticleMgr()->GetPMaterial( "effects/combinemuzzle2" ); + } + + SimpleParticle *sParticle; + + for ( int i = 0; i < set->numhitboxes; ++i ) + { + Vector vecAbsOrigin, xvec, yvec; + mstudiobbox_t *pBox = set->pHitbox(i); + ComputeRenderInfo( pBox, *hitboxbones[pBox->bone], &vecAbsOrigin, &xvec, &yvec ); + + Vector offset; + Vector xDir, yDir; + + xDir = xvec; + float xScale = VectorNormalize( xDir ) * 0.75f; + + yDir = yvec; + float yScale = VectorNormalize( yDir ) * 0.75f; + + int numParticles = clamp( 3.0f * fadePerc, 0, 3 ); + + int iTempParts = 2; + + if ( m_nDissolveType == ENTITY_DISSOLVE_CORE ) + { + if ( m_bCoreExplode == true ) + { + numParticles = 15; + iTempParts = 20; + } + } + + for ( int j = 0; j < iTempParts; j++ ) + { + // Skew the origin + offset = xDir * Helper_RandomFloat( -xScale*0.5f, xScale*0.5f ) + yDir * Helper_RandomFloat( -yScale*0.5f, yScale*0.5f ); + offset += vecSkew; + + if ( random->RandomInt( 0, 2 ) != 0 ) + continue; + + sParticle = (SimpleParticle *) m_pEmitter->AddParticle( sizeof(SimpleParticle), g_Material_Spark, vecAbsOrigin + offset ); + + if ( sParticle == NULL ) + return 1; + + sParticle->m_vecVelocity = Vector( Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( 16.0f, 64.0f ) ); + + if ( m_nDissolveType == ENTITY_DISSOLVE_CORE ) + { + if ( m_bCoreExplode == true ) + { + Vector vDirection = (vecAbsOrigin + offset) - m_vDissolverOrigin; + VectorNormalize( vDirection ); + sParticle->m_vecVelocity = vDirection * m_nMagnitude; + } + } + + if ( sParticle->m_vecVelocity.z > 0 ) + { + sParticle->m_uchStartSize = random->RandomFloat( 4, 6 ) * spriteScale; + } + else + { + sParticle->m_uchStartSize = 2 * spriteScale; + } + + sParticle->m_flDieTime = random->RandomFloat( 0.4f, 0.5f ); + + // If we're the last particles, last longer + if ( numParticles == 0 ) + { + sParticle->m_flDieTime *= 2.0f; + sParticle->m_uchStartSize = 2 * spriteScale; + sParticle->m_flRollDelta = Helper_RandomFloat( -4.0f, 4.0f ); + + if ( m_nDissolveType == ENTITY_DISSOLVE_CORE ) + { + if ( m_bCoreExplode == true ) + { + sParticle->m_flDieTime *= 2.0f; + sParticle->m_flRollDelta = Helper_RandomFloat( -1.0f, 1.0f ); + } + } + } + else + { + sParticle->m_flRollDelta = Helper_RandomFloat( -8.0f, 8.0f ); + } + + sParticle->m_flLifetime = 0.0f; + + sParticle->m_flRoll = Helper_RandomInt( 0, 360 ); + + float alpha = 255; + + sParticle->m_uchColor[0] = alpha; + sParticle->m_uchColor[1] = alpha; + sParticle->m_uchColor[2] = alpha; + sParticle->m_uchStartAlpha = alpha; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchEndSize = 0; + } + + for ( int j = 0; j < numParticles; j++ ) + { + offset = xDir * Helper_RandomFloat( -xScale*0.5f, xScale*0.5f ) + yDir * Helper_RandomFloat( -yScale*0.5f, yScale*0.5f ); + offset += vecSkew; + + sParticle = (SimpleParticle *) m_pEmitter->AddParticle( sizeof(SimpleParticle), g_Material_AR2Glow, vecAbsOrigin + offset ); + + if ( sParticle == NULL ) + return 1; + + sParticle->m_vecVelocity = Vector( Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( -64.0f, 128.0f ) ); + sParticle->m_uchStartSize = random->RandomFloat( 8, 12 ) * spriteScale; + sParticle->m_flDieTime = 0.1f; + sParticle->m_flLifetime = 0.0f; + + sParticle->m_flRoll = Helper_RandomInt( 0, 360 ); + sParticle->m_flRollDelta = Helper_RandomFloat( -2.0f, 2.0f ); + + float alpha = 255; + + sParticle->m_uchColor[0] = alpha; + sParticle->m_uchColor[1] = alpha; + sParticle->m_uchColor[2] = alpha; + sParticle->m_uchStartAlpha = alpha; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchEndSize = 0; + + if ( m_nDissolveType == ENTITY_DISSOLVE_CORE ) + { + if ( m_bCoreExplode == true ) + { + Vector vDirection = (vecAbsOrigin + offset) - m_vDissolverOrigin; + + VectorNormalize( vDirection ); + + sParticle->m_vecVelocity = vDirection * m_nMagnitude; + + sParticle->m_flDieTime = 0.5f; + } + } + } + } + + return 1; +} diff --git a/game/client/c_entitydissolve.h b/game/client/c_entitydissolve.h new file mode 100644 index 00000000..d25fda0f --- /dev/null +++ b/game/client/c_entitydissolve.h @@ -0,0 +1,77 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_ENTITY_DISSOLVE_H +#define C_ENTITY_DISSOLVE_H + +#include "cbase.h" + +//----------------------------------------------------------------------------- +// Entity Dissolve, client-side implementation +//----------------------------------------------------------------------------- +class C_EntityDissolve : public C_BaseEntity, public IMotionEvent +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_EntityDissolve, C_BaseEntity ); + + C_EntityDissolve( void ); + + // Inherited from C_BaseEntity + virtual void GetRenderBounds( Vector& theMins, Vector& theMaxs ); + virtual int DrawModel( int flags ); + virtual bool ShouldDraw() { return true; } + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void UpdateOnRemove( void ); + + // Inherited from IMotionEvent + virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); + + void SetupEmitter( void ); + + void ClientThink( void ); + + void SetServerLinkState( bool state ) { m_bLinkedToServerEnt = state; } + + float m_flStartTime; + float m_flFadeOutStart; + float m_flFadeOutLength; + float m_flFadeOutModelStart; + float m_flFadeOutModelLength; + float m_flFadeInStart; + float m_flFadeInLength; + int m_nDissolveType; + float m_flNextSparkTime; + + Vector m_vDissolverOrigin; + int m_nMagnitude; + + bool m_bCoreExplode; + +protected: + + float GetFadeInPercentage( void ); // Fade in amount (entity fading to black) + float GetFadeOutPercentage( void ); // Fade out amount (particles fading away) + float GetModelFadeOutPercentage( void );// Mode fade out amount + + // Compute the bounding box's center, size, and basis + void ComputeRenderInfo( mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, + Vector *pVecAbsOrigin, Vector *pXVec, Vector *pYVec ); + void BuildTeslaEffect( mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, bool bRandom, float flYawOffset ); + + void DoSparks( mstudiohitboxset_t *set, matrix3x4_t *hitboxbones[MAXSTUDIOBONES] ); + +private: + + CSmartPtr m_pEmitter; + + bool m_bLinkedToServerEnt; + IPhysicsMotionController *m_pController; +}; + +#endif // C_ENTITY_DISSOLVE_H + diff --git a/game/client/c_entityparticletrail.cpp b/game/client/c_entityparticletrail.cpp new file mode 100644 index 00000000..adbee3b1 --- /dev/null +++ b/game/client/c_entityparticletrail.cpp @@ -0,0 +1,250 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "baseparticleentity.h" +#include "entityparticletrail_shared.h" +#include "particlemgr.h" +#include "particle_util.h" +#include "particles_simple.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Entity particle trail, client-side implementation +//----------------------------------------------------------------------------- +class C_EntityParticleTrail : public C_BaseParticleEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_EntityParticleTrail, C_BaseParticleEntity ); + + C_EntityParticleTrail( ); + ~C_EntityParticleTrail( ); + +// C_BaseEntity + virtual void OnDataChanged( DataUpdateType_t updateType ); + +// IParticleEffect + void Update( float fTimeDelta ); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + +private: + + C_EntityParticleTrail( const C_EntityParticleTrail & ); // not defined, not accessible + + void Start( ); + void AddParticle( float flInitialDeltaTime, const Vector &vecMins, const Vector &vecMaxs, const matrix3x4_t &boxToWorld ); + + int m_iMaterialName; + EntityParticleTrailInfo_t m_Info; + EHANDLE m_hConstraintEntity; + + PMaterialHandle m_hMaterial; + TimedEvent m_teParticleSpawn; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_DT( C_EntityParticleTrail, DT_EntityParticleTrail, CEntityParticleTrail ) + RecvPropInt(RECVINFO(m_iMaterialName)), + RecvPropDataTable( RECVINFO_DT( m_Info ), 0, &REFERENCE_RECV_TABLE(DT_EntityParticleTrailInfo) ), + RecvPropEHandle(RECVINFO(m_hConstraintEntity)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_EntityParticleTrail::C_EntityParticleTrail( void ) +{ +} + +C_EntityParticleTrail::~C_EntityParticleTrail() +{ + ParticleMgr()->RemoveEffect( &m_ParticleEffect ); +} + + + +//----------------------------------------------------------------------------- +// On data changed +//----------------------------------------------------------------------------- +void C_EntityParticleTrail::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + if ( updateType == DATA_UPDATE_CREATED ) + { + Start( ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_EntityParticleTrail::Start( ) +{ + if( ParticleMgr()->AddEffect( &m_ParticleEffect, this ) == false ) + return; + + const char *pMaterialName = GetMaterialNameFromIndex( m_iMaterialName ); + if ( !pMaterialName ) + return; + + m_hMaterial = ParticleMgr()->GetPMaterial( pMaterialName ); + m_teParticleSpawn.Init( 150 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityParticleTrail::AddParticle( float flInitialDeltaTime, const Vector &vecMins, const Vector &vecMaxs, const matrix3x4_t &boxToWorld ) +{ + // Select a random point somewhere in the hitboxes of the entity. + Vector vecLocalPosition, vecWorldPosition; + vecLocalPosition.x = Lerp( random->RandomFloat( 0.0f, 1.0f ), vecMins.x, vecMaxs.x ); + vecLocalPosition.y = Lerp( random->RandomFloat( 0.0f, 1.0f ), vecMins.y, vecMaxs.y ); + vecLocalPosition.z = Lerp( random->RandomFloat( 0.0f, 1.0f ), vecMins.z, vecMaxs.z ); + VectorTransform( vecLocalPosition, boxToWorld, vecWorldPosition ); + + // Don't emit the particle unless it's inside the model + if ( m_hConstraintEntity.Get() ) + { + Ray_t ray; + trace_t tr; + ray.Init( vecWorldPosition, vecWorldPosition ); + enginetrace->ClipRayToEntity( ray, MASK_ALL, m_hConstraintEntity, &tr ); + + if ( !tr.startsolid ) + return; + } + + // Make a new particle + SimpleParticle *pParticle = (SimpleParticle *)m_ParticleEffect.AddParticle( sizeof(SimpleParticle), m_hMaterial ); + if ( pParticle == NULL ) + return; + + pParticle->m_Pos = vecWorldPosition; + pParticle->m_flRoll = Helper_RandomInt( 0, 360 ); + pParticle->m_flRollDelta = Helper_RandomFloat( -2.0f, 2.0f ); + + pParticle->m_flLifetime = flInitialDeltaTime; + pParticle->m_flDieTime = m_Info.m_flLifetime; + + pParticle->m_uchColor[0] = 64; + pParticle->m_uchColor[1] = 140; + pParticle->m_uchColor[2] = 225; + pParticle->m_uchStartAlpha = Helper_RandomInt( 64, 64 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = m_Info.m_flStartSize; + pParticle->m_uchEndSize = m_Info.m_flEndSize; + + pParticle->m_vecVelocity = vec3_origin; + VectorMA( pParticle->m_Pos, flInitialDeltaTime, pParticle->m_vecVelocity, pParticle->m_Pos ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_EntityParticleTrail::Update( float fTimeDelta ) +{ + float tempDelta = fTimeDelta; + studiohdr_t *pStudioHdr; + mstudiohitboxset_t *set; + matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; + + C_BaseEntity *pMoveParent = GetMoveParent(); + if ( !pMoveParent ) + return; + + C_BaseAnimating *pAnimating = pMoveParent->GetBaseAnimating(); + if (!pAnimating) + goto trailNoHitboxes; + + if ( !pAnimating->HitboxToWorldTransforms( hitboxbones ) ) + goto trailNoHitboxes; + + pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() ); + if (!pStudioHdr) + goto trailNoHitboxes; + + set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() ); + if ( !set ) + goto trailNoHitboxes; + + //Add new particles + while ( m_teParticleSpawn.NextEvent( tempDelta ) ) + { + int nHitbox = random->RandomInt( 0, set->numhitboxes - 1 ); + mstudiobbox_t *pBox = set->pHitbox(nHitbox); + + AddParticle( tempDelta, pBox->bbmin, pBox->bbmax, *hitboxbones[pBox->bone] ); + } + return; + +trailNoHitboxes: + while ( m_teParticleSpawn.NextEvent( tempDelta ) ) + { + AddParticle( tempDelta, pMoveParent->CollisionProp()->OBBMins(), pMoveParent->CollisionProp()->OBBMaxs(), pMoveParent->EntityToWorldTransform() ); + } +} + + +inline void C_EntityParticleTrail::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const SimpleParticle *pParticle = (const SimpleParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + float t = pParticle->m_flLifetime / pParticle->m_flDieTime; + + // Render + Vector tPos; + TransformParticle( ParticleMgr()->GetModelView(), pParticle->m_Pos, tPos ); + float sortKey = tPos.z; + + Vector color = Vector( pParticle->m_uchColor[0] / 255.0f, pParticle->m_uchColor[1] / 255.0f, pParticle->m_uchColor[2] / 255.0f ); + float alpha = Lerp( t, pParticle->m_uchStartAlpha / 255.0f, pParticle->m_uchEndAlpha / 255.0f ); + float flSize = Lerp( t, pParticle->m_uchStartSize, pParticle->m_uchEndSize ); + + // Render it + RenderParticle_ColorSize( pIterator->GetParticleDraw(), tPos, color, alpha, flSize ); + + pParticle = (const SimpleParticle*)pIterator->GetNext( sortKey ); + } +} + + +inline void C_EntityParticleTrail::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + SimpleParticle *pParticle = (SimpleParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Update position + float flTimeDelta = pIterator->GetTimeDelta(); + pParticle->m_Pos += pParticle->m_vecVelocity * flTimeDelta; + + // NOTE: I'm overloading "die time" to be the actual start time. + pParticle->m_flLifetime += flTimeDelta; + + if ( pParticle->m_flLifetime >= pParticle->m_flDieTime ) + pIterator->RemoveParticle( pParticle ); + + pParticle = (SimpleParticle*)pIterator->GetNext(); + } +} + diff --git a/game/client/c_env_fog_controller.cpp b/game/client/c_env_fog_controller.cpp new file mode 100644 index 00000000..bcacaed5 --- /dev/null +++ b/game/client/c_env_fog_controller.cpp @@ -0,0 +1,46 @@ +//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ==== +// +// An entity that allows level designer control over the fog parameters. +// +//============================================================================= + +#include "cbase.h" +#include "c_env_fog_controller.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_NETWORKCLASS_ALIASED( FogController, DT_FogController ) + +//----------------------------------------------------------------------------- +// Datatable +//----------------------------------------------------------------------------- +BEGIN_NETWORK_TABLE_NOBASE( CFogController, DT_FogController ) + // fog data + RecvPropInt( RECVINFO( m_fog.enable ) ), + RecvPropInt( RECVINFO( m_fog.blend ) ), + RecvPropVector( RECVINFO( m_fog.dirPrimary ) ), + RecvPropInt( RECVINFO( m_fog.colorPrimary ) ), + RecvPropInt( RECVINFO( m_fog.colorSecondary ) ), + RecvPropFloat( RECVINFO( m_fog.start ) ), + RecvPropFloat( RECVINFO( m_fog.end ) ), + RecvPropFloat( RECVINFO( m_fog.farz ) ), + RecvPropFloat( RECVINFO( m_fog.maxdensity ) ), + + RecvPropInt( RECVINFO( m_fog.colorPrimaryLerpTo ) ), + RecvPropInt( RECVINFO( m_fog.colorSecondaryLerpTo ) ), + RecvPropFloat( RECVINFO( m_fog.startLerpTo ) ), + RecvPropFloat( RECVINFO( m_fog.endLerpTo ) ), + RecvPropFloat( RECVINFO( m_fog.lerptime ) ), + RecvPropFloat( RECVINFO( m_fog.duration ) ), +END_NETWORK_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_FogController::C_FogController() +{ + // Make sure that old maps without fog fields don't get wacked out fog values. + m_fog.enable = false; + m_fog.maxdensity = 1.0f; +} diff --git a/game/client/c_env_fog_controller.h b/game/client/c_env_fog_controller.h new file mode 100644 index 00000000..f73bde1f --- /dev/null +++ b/game/client/c_env_fog_controller.h @@ -0,0 +1,33 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_ENV_FOG_CONTROLLER_H +#define C_ENV_FOG_CONTROLLER_H + +#define CFogController C_FogController + +//============================================================================= +// +// Class Fog Controller: +// Compares a set of integer inputs to the one main input +// Outputs true if they are all equivalant, false otherwise +// +class C_FogController : public C_BaseEntity +{ +public: + DECLARE_NETWORKCLASS(); + DECLARE_CLASS( C_FogController, C_BaseEntity ); + + C_FogController(); + +public: + + fogparams_t m_fog; +}; + + +#endif // C_ENV_FOG_CONTROLLER_H \ No newline at end of file diff --git a/game/client/c_env_particlescript.cpp b/game/client/c_env_particlescript.cpp new file mode 100644 index 00000000..6c04a37f --- /dev/null +++ b/game/client/c_env_particlescript.cpp @@ -0,0 +1,304 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "c_baseanimating.h" +#include "particlemgr.h" +#include "materialsystem/imaterialvar.h" +#include "cl_animevent.h" +#include "particle_util.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// An entity which emits other entities at points +//----------------------------------------------------------------------------- +class C_EnvParticleScript : public C_BaseAnimating, public IParticleEffect +{ +public: + DECLARE_CLASS( C_EnvParticleScript, C_BaseAnimating ); + DECLARE_CLIENTCLASS(); + + C_EnvParticleScript(); + +// IParticleEffect overrides. +public: + virtual bool ShouldSimulate() const { return m_bSimulate; } + virtual void SetShouldSimulate( bool bSim ) { m_bSimulate = bSim; } + + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + virtual const Vector &GetSortOrigin(); + +// C_BaseAnimating overrides +public: + // NOTE: Ths enclosed particle effect binding will do all the drawing + // But we have to return true, unlike other particle systems, for the animation events to work + virtual bool ShouldDraw() { return true; } + virtual int DrawModel( int flags ) { return 0; } + virtual int GetFxBlend( void ) { return 0; } + + virtual void FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ); + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + +private: + + // Creates, destroys particles attached to an attachment + void CreateParticle( const char *pAttachmentName, const char *pSpriteName ); + void DestroyAllParticles( const char *pAttachmentName ); + void DestroyAllParticles( ); + +private: + struct ParticleScriptParticle_t : public Particle + { + int m_nAttachment; + float m_flSize; + }; + + CParticleEffectBinding m_ParticleEffect; + float m_flMaxParticleSize; + int m_nOldSequence; + float m_flSequenceScale; + bool m_bSimulate; +}; + +REGISTER_EFFECT( C_EnvParticleScript ); + +//----------------------------------------------------------------------------- +// Datatable +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_DT( C_EnvParticleScript, DT_EnvParticleScript, CEnvParticleScript ) + RecvPropFloat( RECVINFO(m_flSequenceScale) ), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +C_EnvParticleScript::C_EnvParticleScript() +{ + m_flMaxParticleSize = 0.0f; + m_bSimulate = true; +} + + +//----------------------------------------------------------------------------- +// Check for changed sequence numbers +//----------------------------------------------------------------------------- +void C_EnvParticleScript::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + m_nOldSequence = GetSequence(); +} + + +//----------------------------------------------------------------------------- +// Starts up the particle system +//----------------------------------------------------------------------------- +void C_EnvParticleScript::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if(updateType == DATA_UPDATE_CREATED) + { + ParticleMgr()->AddEffect( &m_ParticleEffect, this ); + } + + if ( m_nOldSequence != GetSequence() ) + { + DestroyAllParticles(); + } +} + + +//----------------------------------------------------------------------------- +// Creates, destroys particles attached to an attachment +//----------------------------------------------------------------------------- +void C_EnvParticleScript::CreateParticle( const char *pAttachmentName, const char *pSpriteName ) +{ + // Find the attachment + int nAttachment = LookupAttachment( pAttachmentName ); + if ( nAttachment <= 0 ) + return; + + // Get the sprite materials + PMaterialHandle hMat = m_ParticleEffect.FindOrAddMaterial( pSpriteName ); + ParticleScriptParticle_t *pParticle = + (ParticleScriptParticle_t*)m_ParticleEffect.AddParticle(sizeof(ParticleScriptParticle_t), hMat); + + if ( pParticle == NULL ) + return; + + // Get the sprite size from the material's materialvars + bool bFound = false; + IMaterialVar *pMaterialVar = NULL; + IMaterial *pMaterial = ParticleMgr()->PMaterialToIMaterial( hMat ); + if ( pMaterial ) + { + pMaterialVar = pMaterial->FindVar( "$spritesize", &bFound, false ); + } + + if ( bFound ) + { + pParticle->m_flSize = pMaterialVar->GetFloatValue(); + } + else + { + pParticle->m_flSize = 100.0f; + } + + // Make sure the particle cull size reflects our particles + if ( pParticle->m_flSize > m_flMaxParticleSize ) + { + m_flMaxParticleSize = pParticle->m_flSize; + m_ParticleEffect.SetParticleCullRadius( m_flMaxParticleSize ); + } + + // Place the particle on the attachment specified + pParticle->m_nAttachment = nAttachment; + QAngle vecAngles; + GetAttachment( nAttachment, pParticle->m_Pos, vecAngles ); + + if ( m_flSequenceScale != 1.0f ) + { + pParticle->m_Pos -= GetAbsOrigin(); + pParticle->m_Pos *= m_flSequenceScale; + pParticle->m_Pos += GetAbsOrigin(); + } +} + +void C_EnvParticleScript::DestroyAllParticles( const char *pAttachmentName ) +{ + int nAttachment = LookupAttachment( pAttachmentName ); + if ( nAttachment <= 0 ) + return; + + int nCount = m_ParticleEffect.GetNumActiveParticles(); + Particle** ppParticles = (Particle**)stackalloc( nCount * sizeof(Particle*) ); + int nActualCount = m_ParticleEffect.GetActiveParticleList( nCount, ppParticles ); + Assert( nActualCount == nCount ); + + for ( int i = 0; i < nActualCount; ++i ) + { + ParticleScriptParticle_t *pParticle = (ParticleScriptParticle_t*)ppParticles[i]; + if ( pParticle->m_nAttachment == nAttachment ) + { + // Mark for deletion + pParticle->m_nAttachment = -1; + } + } +} + +void C_EnvParticleScript::DestroyAllParticles( ) +{ + int nCount = m_ParticleEffect.GetNumActiveParticles(); + Particle** ppParticles = (Particle**)stackalloc( nCount * sizeof(Particle*) ); + int nActualCount = m_ParticleEffect.GetActiveParticleList( nCount, ppParticles ); + Assert( nActualCount == nCount ); + + for ( int i = 0; i < nActualCount; ++i ) + { + ParticleScriptParticle_t *pParticle = (ParticleScriptParticle_t*)ppParticles[i]; + + // Mark for deletion + pParticle->m_nAttachment = -1; + } +} + + +//----------------------------------------------------------------------------- +// The animation events will create particles on the attachment points +//----------------------------------------------------------------------------- +void C_EnvParticleScript::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + // Handle events to create + destroy particles + switch( event ) + { + case CL_EVENT_SPRITEGROUP_CREATE: + { + char pAttachmentName[256]; + char pSpriteName[256]; + int nArgs = sscanf( options, "%255s %255s", pAttachmentName, pSpriteName ); + if ( nArgs == 2 ) + { + CreateParticle( pAttachmentName, pSpriteName ); + } + } + return; + + case CL_EVENT_SPRITEGROUP_DESTROY: + { + char pAttachmentName[256]; + int nArgs = sscanf( options, "%255s", pAttachmentName ); + if ( nArgs == 1 ) + { + DestroyAllParticles( pAttachmentName ); + } + } + return; + } + + // Fall back + BaseClass::FireEvent( origin, angles, event, options ); +} + + +//----------------------------------------------------------------------------- +// Simulate the particles +//----------------------------------------------------------------------------- +void C_EnvParticleScript::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const ParticleScriptParticle_t* pParticle = (const ParticleScriptParticle_t*)pIterator->GetFirst(); + while ( pParticle ) + { + Vector vecRenderPos; + TransformParticle( ParticleMgr()->GetModelView(), pParticle->m_Pos, vecRenderPos ); + float sortKey = vecRenderPos.z; + + Vector color( 1, 1, 1 ); + RenderParticle_ColorSize( pIterator->GetParticleDraw(), vecRenderPos, color, 1.0f, pParticle->m_flSize ); + + pParticle = (const ParticleScriptParticle_t*)pIterator->GetNext( sortKey ); + } +} + +void C_EnvParticleScript::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + ParticleScriptParticle_t* pParticle = (ParticleScriptParticle_t*)pIterator->GetFirst(); + while ( pParticle ) + { + // Here's how we retire particles + if ( pParticle->m_nAttachment == -1 ) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + // Move the particle to the attachment point + QAngle vecAngles; + GetAttachment( pParticle->m_nAttachment, pParticle->m_Pos, vecAngles ); + + if ( m_flSequenceScale != 1.0f ) + { + pParticle->m_Pos -= GetAbsOrigin(); + pParticle->m_Pos *= m_flSequenceScale; + pParticle->m_Pos += GetAbsOrigin(); + } + } + + pParticle = (ParticleScriptParticle_t*)pIterator->GetNext(); + } +} + +const Vector &C_EnvParticleScript::GetSortOrigin() +{ + return GetAbsOrigin(); +} diff --git a/game/client/c_env_projectedtexture.cpp b/game/client/c_env_projectedtexture.cpp new file mode 100644 index 00000000..3ba5e2b9 --- /dev/null +++ b/game/client/c_env_projectedtexture.cpp @@ -0,0 +1,236 @@ +//====== Copyright © 1996-2003, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "shareddefs.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterial.h" +#include "view.h" +#include "iviewrender.h" +#include "view_shared.h" +#include "texture_group_names.h" +#include "tier0/icommandline.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT ); +static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.0005", FCVAR_CHEAT ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_EnvProjectedTexture : public C_BaseEntity +{ + DECLARE_CLASS( C_EnvProjectedTexture, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + void ShutDownLightHandle( void ); + + virtual void Simulate(); + + void UpdateLight( bool bForceUpdate ); + + C_EnvProjectedTexture(); + ~C_EnvProjectedTexture(); + +private: + + ClientShadowHandle_t m_LightHandle; + + EHANDLE m_hTargetEntity; + + bool m_bState; + float m_flLightFOV; + bool m_bEnableShadows; + bool m_bLightOnlyTarget; + bool m_bLightWorld; + bool m_bCameraSpace; + Vector m_LinearFloatLightColor; + float m_flAmbient; + float m_flNearZ; + float m_flFarZ; + char m_SpotlightTextureName[ MAX_PATH ]; + int m_nSpotlightTextureFrame; + int m_nShadowQuality; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_EnvProjectedTexture, DT_EnvProjectedTexture, CEnvProjectedTexture ) + RecvPropEHandle( RECVINFO( m_hTargetEntity ) ), + RecvPropBool( RECVINFO( m_bState ) ), + RecvPropFloat( RECVINFO( m_flLightFOV ) ), + RecvPropBool( RECVINFO( m_bEnableShadows ) ), + RecvPropBool( RECVINFO( m_bLightOnlyTarget ) ), + RecvPropBool( RECVINFO( m_bLightWorld ) ), + RecvPropBool( RECVINFO( m_bCameraSpace ) ), + RecvPropVector( RECVINFO( m_LinearFloatLightColor ) ), + RecvPropFloat( RECVINFO( m_flAmbient ) ), + RecvPropString( RECVINFO( m_SpotlightTextureName ) ), + RecvPropInt( RECVINFO( m_nSpotlightTextureFrame ) ), + RecvPropFloat( RECVINFO( m_flNearZ ) ), + RecvPropFloat( RECVINFO( m_flFarZ ) ), + RecvPropInt( RECVINFO( m_nShadowQuality ) ), +END_RECV_TABLE() + +C_EnvProjectedTexture::C_EnvProjectedTexture( void ) +{ + m_LightHandle = CLIENTSHADOW_INVALID_HANDLE; +} + +C_EnvProjectedTexture::~C_EnvProjectedTexture( void ) +{ + ShutDownLightHandle(); +} + +void C_EnvProjectedTexture::ShutDownLightHandle( void ) +{ + // Clear out the light + if( m_LightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + g_pClientShadowMgr->DestroyFlashlight( m_LightHandle ); + m_LightHandle = CLIENTSHADOW_INVALID_HANDLE; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_EnvProjectedTexture::OnDataChanged( DataUpdateType_t updateType ) +{ + UpdateLight( true ); + BaseClass::OnDataChanged( updateType ); +} + +void C_EnvProjectedTexture::UpdateLight( bool bForceUpdate ) +{ + if ( m_bState == false ) + { + if ( m_LightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + ShutDownLightHandle(); + } + + return; + } + + Vector vForward, vRight, vUp, vPos = GetAbsOrigin(); + FlashlightState_t state; + + if ( m_hTargetEntity != NULL ) + { + if ( m_bCameraSpace ) + { + const QAngle &angles = GetLocalAngles(); + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if( pPlayer ) + { + const QAngle playerAngles = pPlayer->GetAbsAngles(); + + Vector vPlayerForward, vPlayerRight, vPlayerUp; + AngleVectors( playerAngles, &vPlayerForward, &vPlayerRight, &vPlayerUp ); + + matrix3x4_t mRotMatrix; + AngleMatrix( angles, mRotMatrix ); + + VectorITransform( vPlayerForward, mRotMatrix, vForward ); + VectorITransform( vPlayerRight, mRotMatrix, vRight ); + VectorITransform( vPlayerUp, mRotMatrix, vUp ); + + float dist = (m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin()).Length(); + vPos = m_hTargetEntity->GetAbsOrigin() - vForward*dist; + + VectorNormalize( vForward ); + VectorNormalize( vRight ); + VectorNormalize( vUp ); + } + } + else + { + vForward = m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin(); + VectorNormalize( vForward ); + + // JasonM - unimplemented + Assert (0); + + //Quaternion q = DirectionToOrientation( dir ); + + + // + // JasonM - set up vRight, vUp + // + +// VectorNormalize( vRight ); +// VectorNormalize( vUp ); + } + } + else + { + AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp ); + } + + state.m_fHorizontalFOVDegrees = m_flLightFOV; + state.m_fVerticalFOVDegrees = m_flLightFOV; + + state.m_vecLightOrigin = vPos; + BasisToQuaternion( vForward, vRight, vUp, state.m_quatOrientation ); + + state.m_fQuadraticAtten = 0.0; + state.m_fLinearAtten = 100; + state.m_fConstantAtten = 0.0f; + state.m_Color[0] = m_LinearFloatLightColor.x; + state.m_Color[1] = m_LinearFloatLightColor.y; + state.m_Color[2] = m_LinearFloatLightColor.z; + state.m_Color[3] = 0.0f; // fixme: need to make ambient work m_flAmbient; + state.m_NearZ = m_flNearZ; + state.m_FarZ = m_flFarZ; + state.m_flShadowSlopeScaleDepthBias = mat_slopescaledepthbias_shadowmap.GetFloat(); + state.m_flShadowDepthBias = mat_depthbias_shadowmap.GetFloat(); + state.m_bEnableShadows = m_bEnableShadows; + state.m_pSpotlightTexture = materials->FindTexture( m_SpotlightTextureName, TEXTURE_GROUP_OTHER, false ); + state.m_nSpotlightTextureFrame = m_nSpotlightTextureFrame; + + state.m_nShadowQuality = m_nShadowQuality; // Allow entity to affect shadow quality + + if( m_LightHandle == CLIENTSHADOW_INVALID_HANDLE ) + { + m_LightHandle = g_pClientShadowMgr->CreateFlashlight( state ); + } + else + { + if ( m_hTargetEntity != NULL || bForceUpdate == true ) + { + g_pClientShadowMgr->UpdateFlashlightState( m_LightHandle, state ); + } + } + + if( m_bLightOnlyTarget ) + { + g_pClientShadowMgr->SetFlashlightTarget( m_LightHandle, m_hTargetEntity ); + } + else + { + g_pClientShadowMgr->SetFlashlightTarget( m_LightHandle, NULL ); + } + + g_pClientShadowMgr->SetFlashlightLightWorld( m_LightHandle, m_bLightWorld ); + + if ( bForceUpdate == false ) + { + g_pClientShadowMgr->UpdateProjectedTexture( m_LightHandle, true ); + } +} + +void C_EnvProjectedTexture::Simulate( void ) +{ + UpdateLight( false ); + + BaseClass::Simulate(); +} + diff --git a/game/client/c_env_screenoverlay.cpp b/game/client/c_env_screenoverlay.cpp new file mode 100644 index 00000000..f9ee517d --- /dev/null +++ b/game/client/c_env_screenoverlay.cpp @@ -0,0 +1,327 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "shareddefs.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterial.h" +#include "view.h" +#include "iviewrender.h" +#include "view_shared.h" +#include "texture_group_names.h" +#include "tier0/icommandline.h" +#include "keyvalues.h" +#include "ScreenSpaceEffects.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_EnvScreenOverlay : public C_BaseEntity +{ + DECLARE_CLASS( C_EnvScreenOverlay, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + void PreDataUpdate( DataUpdateType_t updateType ); + void PostDataUpdate( DataUpdateType_t updateType ); + + void HandleOverlaySwitch( void ); + void StartOverlays( void ); + void StopOverlays( void ); + void StartCurrentOverlay( void ); + void ClientThink( void ); + +protected: + char m_iszOverlayNames[ MAX_SCREEN_OVERLAYS ][255]; + float m_flOverlayTimes[ MAX_SCREEN_OVERLAYS ]; + float m_flStartTime; + int m_iDesiredOverlay; + bool m_bIsActive; + bool m_bWasActive; + int m_iCachedDesiredOverlay; + int m_iCurrentOverlay; + float m_flCurrentOverlayTime; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_EnvScreenOverlay, DT_EnvScreenOverlay, CEnvScreenOverlay ) + RecvPropArray( RecvPropString( RECVINFO( m_iszOverlayNames[0]) ), m_iszOverlayNames ), + RecvPropArray( RecvPropFloat( RECVINFO( m_flOverlayTimes[0] ) ), m_flOverlayTimes ), + RecvPropFloat( RECVINFO( m_flStartTime ) ), + RecvPropInt( RECVINFO( m_iDesiredOverlay ) ), + RecvPropBool( RECVINFO( m_bIsActive ) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_EnvScreenOverlay::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); + + m_bWasActive = m_bIsActive; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EnvScreenOverlay::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + // If we have a start time now, start the overlays going + if ( m_bIsActive && m_flStartTime > 0 && view->GetScreenOverlayMaterial() == NULL ) + { + StartOverlays(); + } + + if ( m_flStartTime == -1 ) + { + StopOverlays(); + } + + HandleOverlaySwitch(); + + if ( updateType == DATA_UPDATE_CREATED && + CommandLine()->FindParm( "-makereslists" ) ) + { + for ( int i = 0; i < MAX_SCREEN_OVERLAYS; ++i ) + { + if ( m_iszOverlayNames[ i ] && m_iszOverlayNames[ i ][ 0 ] ) + { + materials->FindMaterial( m_iszOverlayNames[ i ], TEXTURE_GROUP_CLIENT_EFFECTS, false ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EnvScreenOverlay::StopOverlays( void ) +{ + SetNextClientThink( CLIENT_THINK_NEVER ); + + if ( m_bWasActive && !m_bIsActive ) + { + view->SetScreenOverlayMaterial( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EnvScreenOverlay::StartOverlays( void ) +{ + m_iCurrentOverlay = 0; + m_flCurrentOverlayTime = 0; + m_iCachedDesiredOverlay = 0; + SetNextClientThink( CLIENT_THINK_ALWAYS ); + + StartCurrentOverlay(); + HandleOverlaySwitch(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EnvScreenOverlay::HandleOverlaySwitch( void ) +{ + if( m_iCachedDesiredOverlay != m_iDesiredOverlay ) + { + m_iCurrentOverlay = m_iDesiredOverlay; + m_iCachedDesiredOverlay = m_iDesiredOverlay; + StartCurrentOverlay(); + } +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EnvScreenOverlay::StartCurrentOverlay( void ) +{ + if ( m_iCurrentOverlay == MAX_SCREEN_OVERLAYS || !m_iszOverlayNames[m_iCurrentOverlay] || !m_iszOverlayNames[m_iCurrentOverlay][0] ) + { + // Hit the end of our overlays, so stop. + m_flStartTime = 0; + StopOverlays(); + return; + } + + if ( m_flOverlayTimes[m_iCurrentOverlay] == -1 ) + m_flCurrentOverlayTime = -1; + else + m_flCurrentOverlayTime = gpGlobals->curtime + m_flOverlayTimes[m_iCurrentOverlay]; + + // Bring up the current overlay + IMaterial *pMaterial = materials->FindMaterial( m_iszOverlayNames[m_iCurrentOverlay], TEXTURE_GROUP_CLIENT_EFFECTS, false ); + if ( !IsErrorMaterial( pMaterial ) ) + { + view->SetScreenOverlayMaterial( pMaterial ); + } + else + { + Warning("env_screenoverlay couldn't find overlay %s.\n", m_iszOverlayNames[m_iCurrentOverlay] ); + StopOverlays(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EnvScreenOverlay::ClientThink( void ) +{ + // If the current overlay's run out, go to the next one + if ( m_flCurrentOverlayTime != -1 && m_flCurrentOverlayTime < gpGlobals->curtime ) + { + m_iCurrentOverlay++; + StartCurrentOverlay(); + } +} + +// Effect types +enum +{ + SCREENEFFECT_EP2_ADVISOR_STUN, + SCREENEFFECT_EP1_INTRO, + SCREENEFFECT_EP2_GROGGY, +}; + +// ============================================================================ +// Screenspace effect +// ============================================================================ + +class C_EnvScreenEffect : public C_BaseEntity +{ + DECLARE_CLASS( C_EnvScreenEffect, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + virtual void ReceiveMessage( int classID, bf_read &msg ); + +private: + float m_flDuration; + int m_nType; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_EnvScreenEffect, DT_EnvScreenEffect, CEnvScreenEffect ) + RecvPropFloat( RECVINFO( m_flDuration ) ), + RecvPropInt( RECVINFO( m_nType ) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +// Input : classID - +// &msg - +//----------------------------------------------------------------------------- +void C_EnvScreenEffect::ReceiveMessage( int classID, bf_read &msg ) +{ + // Make sure our IDs match + if ( classID != GetClientClass()->m_ClassID ) + { + // Message is for subclass + BaseClass::ReceiveMessage( classID, msg ); + return; + } + + int messageType = msg.ReadByte(); + switch( messageType ) + { + // Effect turning on + case 0: // FIXME: Declare + { + // Create a keyvalue block to set these params + KeyValues *pKeys = new KeyValues( "keys" ); + if ( pKeys == NULL ) + return; + + if ( m_nType == SCREENEFFECT_EP1_INTRO ) + { + if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + { + return; + } + + // Set our keys + pKeys->SetFloat( "duration", m_flDuration ); + pKeys->SetInt( "fadeout", 0 ); + + g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "episodic_intro", pKeys ); + g_pScreenSpaceEffects->EnableScreenSpaceEffect( "episodic_intro" ); + } + else if ( m_nType == SCREENEFFECT_EP2_ADVISOR_STUN ) + { + // Set our keys + pKeys->SetFloat( "duration", m_flDuration ); + + g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "episodic_stun", pKeys ); + g_pScreenSpaceEffects->EnableScreenSpaceEffect( "episodic_stun" ); + } + else if ( m_nType == SCREENEFFECT_EP2_GROGGY ) + { + if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + return; + + // Set our keys + pKeys->SetFloat( "duration", m_flDuration ); + pKeys->SetInt( "fadeout", 0 ); + + g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "ep2_groggy", pKeys ); + g_pScreenSpaceEffects->EnableScreenSpaceEffect( "ep2_groggy" ); + } + + pKeys->deleteThis(); + } + break; + + // Effect turning off + case 1: // FIXME: Declare + + if ( m_nType == SCREENEFFECT_EP1_INTRO ) + { + if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + { + return; + } + // Create a keyvalue block to set these params + KeyValues *pKeys = new KeyValues( "keys" ); + if ( pKeys == NULL ) + return; + + // Set our keys + pKeys->SetFloat( "duration", m_flDuration ); + pKeys->SetInt( "fadeout", 1 ); + + g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "episodic_intro", pKeys ); + } + else if ( m_nType == SCREENEFFECT_EP2_ADVISOR_STUN ) + { + g_pScreenSpaceEffects->DisableScreenSpaceEffect( "episodic_stun" ); + } + else if ( m_nType == SCREENEFFECT_EP2_GROGGY ) + { + if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + { + return; + } + // Create a keyvalue block to set these params + KeyValues *pKeys = new KeyValues( "keys" ); + if ( pKeys == NULL ) + return; + + // Set our keys + pKeys->SetFloat( "duration", m_flDuration ); + pKeys->SetInt( "fadeout", 1 ); + + g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "ep2_groggy", pKeys ); + } + + break; + } +} diff --git a/game/client/c_env_tonemap_controller.cpp b/game/client/c_env_tonemap_controller.cpp new file mode 100644 index 00000000..f9d8d4f7 --- /dev/null +++ b/game/client/c_env_tonemap_controller.cpp @@ -0,0 +1,97 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#include "cbase.h" + +extern bool g_bUseCustomAutoExposureMin; +extern bool g_bUseCustomAutoExposureMax; +extern bool g_bUseCustomBloomScale; +extern float g_flCustomAutoExposureMin; +extern float g_flCustomAutoExposureMax; +extern float g_flCustomBloomScale; +extern float g_flCustomBloomScaleMinimum; + +EHANDLE g_hTonemapControllerInUse = NULL; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_EnvTonemapController : public C_BaseEntity +{ + DECLARE_CLASS( C_EnvTonemapController, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_EnvTonemapController(); + ~C_EnvTonemapController(); + virtual void OnDataChanged( DataUpdateType_t updateType ); + +private: + bool m_bUseCustomAutoExposureMin; + bool m_bUseCustomAutoExposureMax; + bool m_bUseCustomBloomScale; + float m_flCustomAutoExposureMin; + float m_flCustomAutoExposureMax; + float m_flCustomBloomScale; + float m_flCustomBloomScaleMinimum; +private: + C_EnvTonemapController( const C_EnvTonemapController & ); +}; + +IMPLEMENT_CLIENTCLASS_DT( C_EnvTonemapController, DT_EnvTonemapController, CEnvTonemapController ) + RecvPropInt( RECVINFO(m_bUseCustomAutoExposureMin) ), + RecvPropInt( RECVINFO(m_bUseCustomAutoExposureMax) ), + RecvPropInt( RECVINFO(m_bUseCustomBloomScale) ), + RecvPropFloat( RECVINFO(m_flCustomAutoExposureMin) ), + RecvPropFloat( RECVINFO(m_flCustomAutoExposureMax) ), + RecvPropFloat( RECVINFO(m_flCustomBloomScale) ), + RecvPropFloat( RECVINFO(m_flCustomBloomScaleMinimum) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_EnvTonemapController::C_EnvTonemapController( void ) +{ + m_bUseCustomAutoExposureMin = false; + m_bUseCustomAutoExposureMax = false; + m_bUseCustomBloomScale = false; + m_flCustomAutoExposureMin = 0; + m_flCustomAutoExposureMax = 0; + m_flCustomBloomScale = 0.0f; + m_flCustomBloomScaleMinimum = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_EnvTonemapController::~C_EnvTonemapController( void ) +{ + if ( g_hTonemapControllerInUse == this ) + { + g_bUseCustomAutoExposureMin = false; + g_bUseCustomAutoExposureMax = false; + g_bUseCustomBloomScale = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EnvTonemapController::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged(updateType); + + g_bUseCustomAutoExposureMin = m_bUseCustomAutoExposureMin; + g_bUseCustomAutoExposureMax = m_bUseCustomAutoExposureMax; + g_bUseCustomBloomScale = m_bUseCustomBloomScale; + g_flCustomAutoExposureMin = m_flCustomAutoExposureMin; + g_flCustomAutoExposureMax = m_flCustomAutoExposureMax; + g_flCustomBloomScale = m_flCustomBloomScale; + g_flCustomBloomScaleMinimum = m_flCustomBloomScaleMinimum; + + g_hTonemapControllerInUse = this; +} + diff --git a/game/client/c_fire_smoke.cpp b/game/client/c_fire_smoke.cpp new file mode 100644 index 00000000..4356063b --- /dev/null +++ b/game/client/c_fire_smoke.cpp @@ -0,0 +1,404 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "IViewRender.h" +#include "ClientEffectPrecacheSystem.h" +#include "studio.h" +#include "bone_setup.h" +#include "engine/ivmodelinfo.h" +#include "c_fire_smoke.h" +#include "engine/IEngineSound.h" +#include "iefx.h" +#include "dlight.h" +#include "tier0/ICommandLine.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +CLIENTEFFECT_REGISTER_BEGIN( SmokeStackMaterials ) + CLIENTEFFECT_MATERIAL( "particle/SmokeStack" ) +CLIENTEFFECT_REGISTER_END() + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pRecvProp - +// *pStruct - +// *pVarData - +// *pIn - +// objectID - +//----------------------------------------------------------------------------- +void RecvProxy_Scale( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_FireSmoke *pFireSmoke = (C_FireSmoke *) pStruct; + float scale = pData->m_Value.m_Float; + + //If changed, update our internal information + if ( ( pFireSmoke->m_flScale != scale ) && ( pFireSmoke->m_flScaleEnd != scale ) ) + { + pFireSmoke->m_flScaleStart = pFireSmoke->m_flScaleRegister; + pFireSmoke->m_flScaleEnd = scale; + } + + pFireSmoke->m_flScale = scale; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pRecvProp - +// *pStruct - +// *pVarData - +// *pIn - +// objectID - +//----------------------------------------------------------------------------- +void RecvProxy_ScaleTime( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_FireSmoke *pFireSmoke = (C_FireSmoke *) pStruct; + float time = pData->m_Value.m_Float; + + //If changed, update our internal information + //if ( pFireSmoke->m_flScaleTime != time ) + { + if ( time == -1.0f ) + { + pFireSmoke->m_flScaleTimeStart = Helper_GetTime()-1.0f; + pFireSmoke->m_flScaleTimeEnd = pFireSmoke->m_flScaleTimeStart; + } + else + { + pFireSmoke->m_flScaleTimeStart = Helper_GetTime(); + pFireSmoke->m_flScaleTimeEnd = Helper_GetTime() + time; + } + } + + pFireSmoke->m_flScaleTime = time; +} + +//Receive datatable +IMPLEMENT_CLIENTCLASS_DT( C_FireSmoke, DT_FireSmoke, CFireSmoke ) + RecvPropFloat( RECVINFO( m_flStartScale )), + RecvPropFloat( RECVINFO( m_flScale ), 0, RecvProxy_Scale ), + RecvPropFloat( RECVINFO( m_flScaleTime ), 0, RecvProxy_ScaleTime ), + RecvPropInt( RECVINFO( m_nFlags ) ), + RecvPropInt( RECVINFO( m_nFlameModelIndex ) ), + RecvPropInt( RECVINFO( m_nFlameFromAboveModelIndex ) ), +END_RECV_TABLE() + +//================================================== +// C_FireSmoke +//================================================== + +C_FireSmoke::C_FireSmoke() +{ +} + +C_FireSmoke::~C_FireSmoke() +{ + + // Shut down our effect if we have it + if ( m_hEffect ) + { + m_hEffect->StopEmission(false, false , true); + m_hEffect = NULL; + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::Simulate( void ) +{ +} + +#define FLAME_ALPHA_START 0.9f +#define FLAME_ALPHA_END 1.0f + +#define FLAME_TRANS_START 0.75f + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::AddFlames( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_FireSmoke::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + + if ( updateType == DATA_UPDATE_CREATED ) + { + Start(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::UpdateEffects( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_FireSmoke::ShouldDraw() +{ + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::Start( void ) +{ + const char *lpszEffectName; + int nSize = (int) floor( m_flStartScale / 36.0f ); + switch ( nSize ) + { + case 0: + lpszEffectName = ( m_nFlags & bitsFIRESMOKE_SMOKE ) ? "env_fire_tiny_smoke" : "env_fire_tiny"; + break; + + case 1: + lpszEffectName = ( m_nFlags & bitsFIRESMOKE_SMOKE ) ? "env_fire_small_smoke" : "env_fire_small"; + break; + + case 2: + lpszEffectName = ( m_nFlags & bitsFIRESMOKE_SMOKE ) ? "env_fire_medium_smoke" : "env_fire_medium"; + break; + + case 3: + default: + lpszEffectName = ( m_nFlags & bitsFIRESMOKE_SMOKE ) ? "env_fire_large_smoke" : "env_fire_large"; + break; + } + + // Create the effect of the correct size + m_hEffect = ParticleProp()->Create( lpszEffectName, PATTACH_ABSORIGIN ); + +} + + +//----------------------------------------------------------------------------- +// Purpose: FIXME: what's the right way to do this? +//----------------------------------------------------------------------------- +void C_FireSmoke::StartClientOnly( void ) +{ + Start(); + + ClientEntityList().AddNonNetworkableEntity( this ); + CollisionProp()->CreatePartitionHandle(); + AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW ); + AddToLeafSystem(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::RemoveClientOnly(void) +{ + ClientThinkList()->RemoveThinkable( GetClientHandle() ); + + // Remove from the client entity list. + ClientEntityList().RemoveEntity( GetClientHandle() ); + + partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() ); + + RemoveFromLeafSystem(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::UpdateAnimation( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::UpdateFlames( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::UpdateScale( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::Update( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_FireSmoke::FindClipPlane( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn smoke (...duh) +//----------------------------------------------------------------------------- + +void C_FireSmoke::SpawnSmoke( void ) +{ +} + + +IMPLEMENT_CLIENTCLASS_DT( C_EntityFlame, DT_EntityFlame, CEntityFlame ) + RecvPropEHandle(RECVINFO(m_hEntAttached)), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_EntityFlame::C_EntityFlame( void ) : +m_hEffect( NULL ) +{ + m_hOldAttached = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_EntityFlame::~C_EntityFlame( void ) +{ + StopEffect(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityFlame::StopEffect( void ) +{ + if ( m_hEffect ) + { + ParticleProp()->StopEmission( m_hEffect, true ); + m_hEffect->SetControlPointEntity( 0, NULL ); + m_hEffect->SetControlPointEntity( 1, NULL ); + m_hEffect = NULL; + } + + if ( m_hEntAttached ) + { + m_hEntAttached->RemoveFlag( FL_ONFIRE ); + m_hEntAttached->SetEffectEntity( NULL ); + m_hEntAttached->StopSound( "General.BurningFlesh" ); + m_hEntAttached->StopSound( "General.BurningObject" ); + + + m_hEntAttached = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityFlame::UpdateOnRemove( void ) +{ + StopEffect(); + BaseClass::UpdateOnRemove(); +} + +void C_EntityFlame::CreateEffect( void ) +{ + if ( m_hEffect ) + { + ParticleProp()->StopEmission( m_hEffect, true ); + m_hEffect->SetControlPointEntity( 0, NULL ); + m_hEffect->SetControlPointEntity( 1, NULL ); + m_hEffect = NULL; + } + + m_hEffect = ParticleProp()->Create( "burning_character", PATTACH_ABSORIGIN_FOLLOW ); + if ( m_hEffect ) + { + C_BaseEntity *pEntity = m_hEntAttached; + m_hOldAttached = m_hEntAttached; + + ParticleProp()->AddControlPoint( m_hEffect, 1, pEntity, PATTACH_ABSORIGIN_FOLLOW ); + m_hEffect->SetControlPoint( 0, GetAbsOrigin() ); + m_hEffect->SetControlPoint( 1, GetAbsOrigin() ); + m_hEffect->SetControlPointEntity( 0, pEntity ); + m_hEffect->SetControlPointEntity( 1, pEntity ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityFlame::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + CreateEffect(); + } + + // FIXME: This is a bit of a shady path + if ( updateType == DATA_UPDATE_DATATABLE_CHANGED ) + { + // If our owner changed, then recreate the effect + if ( m_hEntAttached != m_hOldAttached ) + { + CreateEffect(); + } + } + + BaseClass::OnDataChanged( updateType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityFlame::Simulate( void ) +{ + if ( gpGlobals->frametime <= 0.0f ) + return; + +#ifdef HL2_EPISODIC + + if ( IsEffectActive(EF_BRIGHTLIGHT) || IsEffectActive(EF_DIMLIGHT) ) + { + dlight_t *dl = effects->CL_AllocDlight ( index ); + dl->origin = GetAbsOrigin(); + dl->origin[2] += 16; + dl->color.r = 254; + dl->color.g = 174; + dl->color.b = 10; + dl->radius = random->RandomFloat(400,431); + dl->die = gpGlobals->curtime + 0.001; + } + +#endif // HL2_EPISODIC +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EntityFlame::ClientThink( void ) +{ + StopEffect(); + Release(); +} diff --git a/game/client/c_fire_smoke.h b/game/client/c_fire_smoke.h new file mode 100644 index 00000000..f42ae41b --- /dev/null +++ b/game/client/c_fire_smoke.h @@ -0,0 +1,302 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef C_FIRE_SMOKE_H +#define C_FIRE_SMOKE_H + +#include "particles_simple.h" +#include "tempent.h" +#include "glow_overlay.h" +#include "view.h" +#include "particle_litsmokeemitter.h" + +class CFireOverlay; + +class C_FireSprite : public C_Sprite +{ + DECLARE_CLASS( C_FireSprite, C_Sprite ); + +private: + virtual int DrawModel( int flags ) + { + if ( m_bFadeFromAbove ) + { + // The sprites become less visible the more you look down or up at them + Vector vToPos = GetLocalOrigin() - CurrentViewOrigin(); + VectorNormalize( vToPos ); + + float fUpAmount = vToPos.z; + + int iAlpha = 255; + + if ( fUpAmount < -0.75f ) + iAlpha = 0; + else if ( fUpAmount < -0.65f ) + iAlpha = 255 - (int)( ( fUpAmount + 0.65f ) * 10.0f * -255.0f ); + else if ( fUpAmount > 0.85f ) + iAlpha = 0; + else if ( fUpAmount > 0.75f ) + iAlpha = 255 - (int)( ( fUpAmount - 0.75f ) * 10.0f * 255.0f ); + + SetColor( iAlpha, iAlpha, iAlpha ); + } + + return BaseClass::DrawModel( flags ); + } + +public: + Vector m_vecMoveDir; + bool m_bFadeFromAbove; +}; + +class C_FireFromAboveSprite : public C_Sprite +{ + DECLARE_CLASS( C_FireFromAboveSprite, C_Sprite ); + + virtual int DrawModel( int flags ) + { + // The sprites become more visible the more you look down or up at them + Vector vToPos = GetLocalOrigin() - CurrentViewOrigin(); + VectorNormalize( vToPos ); + + float fUpAmount = vToPos.z; + + int iAlpha = 0; + + if ( fUpAmount < -0.85f ) + iAlpha = 255; + else if ( fUpAmount < -0.65f ) + iAlpha = (int)( ( fUpAmount + 0.65f ) * 5.0f * -255.0f ); + else if ( fUpAmount > 0.75f ) + iAlpha = 255; + else if ( fUpAmount > 0.55f ) + iAlpha = (int)( ( fUpAmount - 0.55f ) * 5.0f * 255.0f ); + + SetColor( iAlpha, iAlpha, iAlpha ); + + return BaseClass::DrawModel( flags ); + } +}; + +#ifdef _XBOX +// XBox reduces the flame count +#define NUM_CHILD_FLAMES 1 +#else +#define NUM_CHILD_FLAMES 4 +#endif + +#define SMOKE_RISE_RATE 92.0f +#define SMOKE_LIFETIME 2.0f +#define EMBER_LIFETIME 2.0f + +#define FLAME_CHILD_SPREAD 64.0f +#define FLAME_SOURCE_HEIGHT 128.0f +#define FLAME_FROM_ABOVE_SOURCE_HEIGHT 32.0f + +//================================================== +// C_FireSmoke +//================================================== + +//NOTENOTE: Mirrored in dlls/fire_smoke.h +#define bitsFIRESMOKE_NONE 0x00000000 +#define bitsFIRESMOKE_ACTIVE 0x00000001 +#define bitsFIRESMOKE_SMOKE 0x00000002 +#define bitsFIRESMOKE_SMOKE_COLLISION 0x00000004 +#define bitsFIRESMOKE_GLOW 0x00000008 +#define bitsFIRESMOKE_VISIBLE_FROM_ABOVE 0x00000010 + +#define OVERLAY_MAX_VISIBLE_RANGE 512.0f + + +class C_FireSmoke : public C_BaseEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_FireSmoke, C_BaseEntity ); + + C_FireSmoke(); + ~C_FireSmoke(); + + void Start( void ); + void Simulate( void ); + + void StartClientOnly( void ); + void RemoveClientOnly( void ); + +protected: + void Update( void ); + void UpdateAnimation( void ); + void UpdateScale( void ); + void UpdateFlames( void ); + void AddFlames( void ); + void SpawnSmoke( void ); + void FindClipPlane( void ); + +//C_BaseEntity +public: + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool ShouldDraw(); + + float GetScale( void ) const { return m_flScaleRegister; } + +//From the server +public: + float m_flStartScale; + float m_flScale; + float m_flScaleTime; + int m_nFlags; + int m_nFlameModelIndex; + int m_nFlameFromAboveModelIndex; + +//Client-side only +public: + float m_flScaleRegister; + float m_flScaleStart; + float m_flScaleEnd; + float m_flScaleTimeStart; + float m_flScaleTimeEnd; + float m_flChildFlameSpread; + + VPlane m_planeClip; + float m_flClipPerc; + bool m_bClipTested; + + bool m_bFadingOut; + +protected: + + void UpdateEffects( void ); + + //CSmartPtr m_pEmberEmitter; + CSmartPtr m_pSmokeEmitter; + + C_FireSprite m_entFlames[NUM_CHILD_FLAMES]; + C_FireFromAboveSprite m_entFlamesFromAbove[NUM_CHILD_FLAMES]; + float m_entFlameScales[NUM_CHILD_FLAMES]; + + TimedEvent m_tParticleSpawn; + + CFireOverlay *m_pFireOverlay; + + // New Particle Fire Effect + CNewParticleEffect *m_hEffect; +private: + C_FireSmoke( const C_FireSmoke & ); +}; + +//Fire overlay +class CFireOverlay : public CGlowOverlay +{ +public: + + //Constructor + CFireOverlay( C_FireSmoke *owner ) + { + m_pOwner = owner; + m_flScale = 0.0f; + m_nGUID = random->RandomInt( -999999, 999999 ); + } + + //----------------------------------------------------------------------------- + // Purpose: Generate a flicker value + // Output : scalar value + //----------------------------------------------------------------------------- + float GetFlickerScale( void ) + { + float result = 0.0f; + + float time = Helper_GetTime() + m_nGUID; + + result = sin( time * 1000.0f ); + result += 0.5f * sin( time * 2000.0f ); + result -= 0.5f * cos( time * 8000.0f ); + + return result; + } + + //----------------------------------------------------------------------------- + // Purpose: Update the overlay + //----------------------------------------------------------------------------- + virtual bool Update( void ) + { + if ( m_pOwner == NULL ) + return false; + + float scale = m_pOwner->GetScale(); + float dscale = scale - m_flScale; + + m_vPos[2] += dscale * FLAME_SOURCE_HEIGHT; + m_flScale = scale; + + scale *= 0.75f; + + float flickerScale = GetFlickerScale(); + + float newScale = scale + ( scale * flickerScale * 0.1f ); + + m_Sprites[0].m_flHorzSize = ( newScale * 0.2f ) + ( m_Sprites[0].m_flHorzSize * 0.8f ); + m_Sprites[0].m_flVertSize = m_Sprites[0].m_flHorzSize * 1.5f; + + float cameraDistance = ( CurrentViewOrigin() - (m_pOwner->GetAbsOrigin())).Length(); + + C_BasePlayer *local = C_BasePlayer::GetLocalPlayer(); + if ( local ) + { + cameraDistance *= local->GetFOVDistanceAdjustFactor(); + } + + if ( cameraDistance > OVERLAY_MAX_VISIBLE_RANGE ) + cameraDistance = OVERLAY_MAX_VISIBLE_RANGE; + + float alpha = 1.0f - ( cameraDistance / OVERLAY_MAX_VISIBLE_RANGE ); + + Vector newColor = m_vBaseColors[0] + ( m_vBaseColors[0] * flickerScale * 0.5f ); + m_Sprites[0].m_vColor = ( newColor * 0.1f ) + ( m_Sprites[0].m_vColor * 0.9f ) * alpha; + + return true; + } + +public: + + C_FireSmoke *m_pOwner; + Vector m_vBaseColors[MAX_SUN_LAYERS]; + float m_flScale; + int m_nGUID; +}; + +// +// Entity flame, client-side implementation +// + +#define NUM_FLAMELETS 5 + +class C_EntityFlame : public C_BaseEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_EntityFlame, C_BaseEntity ); + + C_EntityFlame( void ); + ~C_EntityFlame( void ); + + virtual void Simulate( void ); + virtual void UpdateOnRemove( void ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink( void ); + + CNewParticleEffect *m_hEffect; + EHANDLE m_hEntAttached; // The entity that we are burning (attached to). + EHANDLE m_hOldAttached; + +protected: + + void CreateEffect( void ); + void StopEffect( void ); +}; + +#endif //C_FIRE_SMOKE_H \ No newline at end of file diff --git a/game/client/c_fish.cpp b/game/client/c_fish.cpp new file mode 100644 index 00000000..4aec194e --- /dev/null +++ b/game/client/c_fish.cpp @@ -0,0 +1,352 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// c_fish.cpp +// Simple fish client-side logic +// Author: Michael S. Booth, April 2005 + +#include "cbase.h" +#include +#include "engine/IVDebugOverlay.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); + + +ConVar FishDebug( "fish_debug", "0", FCVAR_CHEAT, "Show debug info for fish" ); + + +//----------------------------------------------------------------------------- +/** + * Client-side fish entity + */ +class C_Fish : public C_BaseAnimating +{ +public: + DECLARE_CLASS( C_Fish, C_BaseAnimating ); + DECLARE_CLIENTCLASS(); + + virtual void Spawn( void ); + virtual void ClientThink(); + + virtual void OnDataChanged( DataUpdateType_t type ); + +private: + friend void RecvProxy_FishOriginX( const CRecvProxyData *pData, void *pStruct, void *pOut ); + friend void RecvProxy_FishOriginY( const CRecvProxyData *pData, void *pStruct, void *pOut ); + + Vector m_pos; ///< local position + Vector m_vel; ///< local velocity + QAngle m_angles; ///< local angles + + int m_localLifeState; ///< our version of m_lifeState + + float m_deathDepth; ///< water depth when we died + float m_deathAngle; ///< angle to float at when dead + float m_buoyancy; ///< so each fish floats at a different rate when dead + + CountdownTimer m_wiggleTimer; ///< for simulating swimming motions + float m_wigglePhase; ///< where in the wiggle sinusoid we are + float m_wiggleRate; ///< the speed of our wiggling + + Vector m_actualPos; ///< position from server + QAngle m_actualAngles; ///< angles from server + + Vector m_poolOrigin; + float m_waterLevel; ///< Z coordinate of water surface + + bool m_gotUpdate; ///< true after we have received a network update + + enum { MAX_ERROR_HISTORY = 20 }; + float m_errorHistory[ MAX_ERROR_HISTORY ]; ///< error history samples + int m_errorHistoryIndex; + int m_errorHistoryCount; + float m_averageError; +}; + + +//----------------------------------------------------------------------------- +void RecvProxy_FishOriginX( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_Fish *fish = (C_Fish *)pStruct; + float *out = (float *)pOut; + + *out = pData->m_Value.m_Float + fish->m_poolOrigin.x; +} + +void RecvProxy_FishOriginY( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_Fish *fish = (C_Fish *)pStruct; + float *out = (float *)pOut; + + *out = pData->m_Value.m_Float + fish->m_poolOrigin.y; +} + + +IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_Fish, DT_CFish, CFish ) + + RecvPropVector( RECVINFO(m_poolOrigin) ), + + RecvPropFloat( RECVINFO_NAME( m_actualPos.x, m_x ), 0, RecvProxy_FishOriginX ), + RecvPropFloat( RECVINFO_NAME( m_actualPos.y, m_y ), 0, RecvProxy_FishOriginY ), + RecvPropFloat( RECVINFO_NAME( m_actualPos.z, m_z ) ), + + RecvPropFloat( RECVINFO_NAME( m_actualAngles.y, m_angle ) ), + + RecvPropInt( RECVINFO(m_nModelIndex) ), + RecvPropInt( RECVINFO(m_lifeState) ), + + RecvPropFloat( RECVINFO(m_waterLevel) ), ///< get this from the server in case we die when slightly out of the water due to error correction + +END_RECV_TABLE() + + + +//----------------------------------------------------------------------------- +void C_Fish::Spawn( void ) +{ + BaseClass::Spawn(); + + m_angles = QAngle( 0, 0, 0 ); + m_actualAngles = m_angles; + + m_vel = Vector( 0, 0, 0 ); + m_gotUpdate = false; + m_localLifeState = LIFE_ALIVE; + m_buoyancy = RandomFloat( 0.4f, 1.0f ); + + m_errorHistoryIndex = 0; + m_errorHistoryCount = 0; + m_averageError = 0.0f; + + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + + +//----------------------------------------------------------------------------- +void C_Fish::ClientThink() +{ + if (FishDebug.GetBool()) + { + debugoverlay->AddLineOverlay( m_pos, m_actualPos, 255, 0, 0, true, 0.1f ); + switch( m_localLifeState ) + { + case LIFE_DYING: + debugoverlay->AddTextOverlay( m_pos, 0.1f, "DYING" ); + break; + + case LIFE_DEAD: + debugoverlay->AddTextOverlay( m_pos, 0.1f, "DEAD" ); + break; + } + } + + float deltaT = gpGlobals->frametime; + + + // check if we just died + if (m_localLifeState == LIFE_ALIVE && m_lifeState != LIFE_ALIVE) + { + // we have died + m_localLifeState = LIFE_DYING; + + m_deathDepth = m_pos.z; + + // determine surface float angle + m_deathAngle = RandomFloat( 87.0f, 93.0f ) * ((RandomInt( 0, 100 ) < 50) ? 1.0f : -1.0f); + } + + + switch( m_localLifeState ) + { + case LIFE_DYING: + { + // depth parameter + float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth); + t *= t; + + // roll onto side + m_angles.z = m_deathAngle * t; + + // float to surface + const float fudge = 2.0f; + if (m_pos.z < m_waterLevel - fudge) + { + m_vel.z += (1.0f - t) * m_buoyancy * deltaT; + } + else + { + m_localLifeState = LIFE_DEAD; + } + + break; + } + + case LIFE_DEAD: + { + // depth parameter + float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth); + t *= t; + + // roll onto side + m_angles.z = m_deathAngle * t; + + // keep near water surface + const float sub = 0.5f; + m_vel.z += 10.0f * (m_waterLevel - m_pos.z - sub) * deltaT; + + // bob on surface + const float rollAmp = 5.0f; + const float rollFreq = 2.33f; + m_angles.z += rollAmp * sin( rollFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; + + const float rollAmp2 = 7.0f; + const float rollFreq2 = 4.0f; + m_angles.x += rollAmp2 * sin( rollFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; + + const float bobAmp = 0.75f; + const float bobFreq = 4.0f; + m_vel.z += bobAmp * sin( bobFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; + + const float bobAmp2 = 0.75f; + const float bobFreq2 = 3.333f; + m_vel.z += bobAmp2 * sin( bobFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT; + + // decay movement speed to zero + const float drag = 1.0f; + m_vel.z -= drag * m_vel.z * deltaT; + + break; + } + + case LIFE_ALIVE: + { + // use server-side Z coordinate directly + m_pos.z = m_actualPos.z; + + // use server-side angles + m_angles = m_actualAngles; + + // fishy wiggle based on movement + if (!m_wiggleTimer.IsElapsed()) + { + float swimPower = 1.0f - (m_wiggleTimer.GetElapsedTime() / m_wiggleTimer.GetCountdownDuration()); + const float amp = 6.0f * swimPower; + float wiggle = amp * sin( m_wigglePhase ); + + m_wigglePhase += m_wiggleRate * deltaT; + + // wiggle decay + const float wiggleDecay = 5.0f; + m_wiggleRate -= wiggleDecay * deltaT; + + m_angles.y += wiggle; + } + + break; + } + } + + // compute error between our local position and actual server position + Vector error = m_actualPos - m_pos; + error.z = 0.0f; + float errorLen = error.Length(); + + if (m_localLifeState == LIFE_ALIVE) + { + // if error is far above average, start swimming + const float wiggleThreshold = 2.0f; + if (errorLen - m_averageError > wiggleThreshold) + { + // if error is large, we must have started swimming + const float swimTime = 5.0f; + m_wiggleTimer.Start( swimTime ); + + m_wiggleRate = 2.0f * errorLen; + + const float maxWiggleRate = 30.0f; + if (m_wiggleRate > maxWiggleRate) + { + m_wiggleRate = maxWiggleRate; + } + } + + // update average error + m_errorHistory[ m_errorHistoryIndex++ ] = errorLen; + if (m_errorHistoryIndex >= MAX_ERROR_HISTORY) + { + m_errorHistoryIndex = 0; + m_errorHistoryCount = MAX_ERROR_HISTORY; + } + else if (m_errorHistoryCount < MAX_ERROR_HISTORY) + { + ++m_errorHistoryCount; + } + + m_averageError = 0.0f; + if (m_errorHistoryCount) + { + for( int r=0; r 1.0f) + { + errorT = 1.0f; + } + + // we want a nonlinear spring force for tracking + errorT *= errorT; + + // as fish move faster, their error increases - use a stiffer spring when fast, and a weak one when slow + const float trackRate = 0.0f + errorT * 115.0f; + m_vel.x += trackRate * error.x * deltaT; + m_vel.y += trackRate * error.y * deltaT; + + const float trackDrag = 2.0f + errorT * 6.0f; + m_vel.x -= trackDrag * m_vel.x * deltaT; + m_vel.y -= trackDrag * m_vel.y * deltaT; + + + // euler integration + m_pos += m_vel * deltaT; + + SetNetworkOrigin( m_pos ); + SetAbsOrigin( m_pos ); + + SetNetworkAngles( m_angles ); + SetAbsAngles( m_angles ); +} + + +//----------------------------------------------------------------------------- +void C_Fish::OnDataChanged( DataUpdateType_t type ) +{ + //if (!m_gotUpdate) + + if (type == DATA_UPDATE_CREATED) + { + // initial update + m_gotUpdate = true; + + m_pos = m_actualPos; + m_vel = Vector( 0, 0, 0 ); + + return; + } +} + diff --git a/game/client/c_forcefeedback.cpp b/game/client/c_forcefeedback.cpp new file mode 100644 index 00000000..6c0e0075 --- /dev/null +++ b/game/client/c_forcefeedback.cpp @@ -0,0 +1,301 @@ +#include "cbase.h" +#include "forcefeedback.h" +#include "hud_macros.h" +#include "input.h" + +#define FF_CLIENT_FLAG 0x8000 + +class FFParams +{ +public: + FORCEFEEDBACK_t m_nEffectType; + FFBaseParams_t m_BaseParams; +}; + +struct FFEffectInfo_t +{ + FORCEFEEDBACK_t effectType; + char const *name; +}; + +#define DECLARE_FFEFFECT( name ) { name, #name } + +static FFEffectInfo_t g_EffectTypes[] = +{ + DECLARE_FFEFFECT( FORCE_FEEDBACK_SHOT_SINGLE ), + DECLARE_FFEFFECT( FORCE_FEEDBACK_SHOT_DOUBLE ), + DECLARE_FFEFFECT( FORCE_FEEDBACK_TAKEDAMAGE ), + DECLARE_FFEFFECT( FORCE_FEEDBACK_SCREENSHAKE ), + DECLARE_FFEFFECT( FORCE_FEEDBACK_SKIDDING ), + DECLARE_FFEFFECT( FORCE_FEEDBACK_BREAKING ) +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : effect - +// Output : char const +//----------------------------------------------------------------------------- +char const *NameForForceFeedbackEffect( FORCEFEEDBACK_t effect ) +{ + int c = ARRAYSIZE( g_EffectTypes ); + if ( (int)effect < 0 || (int)effect >= c ) + return "???"; + + const FFEffectInfo_t& info = g_EffectTypes[ (int)effect ]; + Assert( info.effectType == effect ); + return info.name; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : FORCEFEEDBACK_t +//----------------------------------------------------------------------------- +FORCEFEEDBACK_t ForceFeedbackEffectForName( const char *name ) +{ + int c = ARRAYSIZE( g_EffectTypes ); + for ( int i = 0 ; i < c; ++i ) + { + const FFEffectInfo_t& info = g_EffectTypes[ i ]; + + if ( !Q_stricmp( info.name, name ) ) + return info.effectType; + } + + return ( FORCEFEEDBACK_t )-1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CForceFeedback : public IForceFeedback, public CAutoGameSystem +{ +public: + virtual bool Init(); + virtual void Shutdown(); + + // API + virtual void StopAllEffects( CBasePlayer *player ); + virtual void StopEffect( CBasePlayer *player, FORCEFEEDBACK_t effect ); + virtual void StartEffect( CBasePlayer *player, FORCEFEEDBACK_t effect, const FFBaseParams_t& params ); + + virtual void PauseAll( CBasePlayer *player ); + virtual void ResumeAll( CBasePlayer *player ); + + void MsgFunc_ForceFeedback( bf_read &msg ); + +private: + + void Internal_StopAllEffects(); + void Internal_StopEffect( FORCEFEEDBACK_t effect ); + void Internal_StartEffect( FORCEFEEDBACK_t, const FFBaseParams_t& params ); + void Internal_PauseAll(); + void Internal_ResumeAll(); +}; + +static CForceFeedback g_ForceFeedbackSingleton; +IForceFeedback *forcefeedback = &g_ForceFeedbackSingleton; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &msg - +//----------------------------------------------------------------------------- +void __MsgFunc_ForceFeedback( bf_read &msg ) +{ + g_ForceFeedbackSingleton.MsgFunc_ForceFeedback( msg ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CForceFeedback::Init() +{ + HOOK_MESSAGE( ForceFeedback ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CForceFeedback::Shutdown() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +//----------------------------------------------------------------------------- +void CForceFeedback::StopAllEffects( CBasePlayer *player ) +{ + if ( !player ) + return; + + Internal_StopAllEffects(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// effect - +//----------------------------------------------------------------------------- +void CForceFeedback::StopEffect( CBasePlayer *player, FORCEFEEDBACK_t effect ) +{ + if ( !player ) + return; + + Internal_StopEffect( effect ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// effect - +// params - +//----------------------------------------------------------------------------- +void CForceFeedback::StartEffect( CBasePlayer *player, FORCEFEEDBACK_t effect, const FFBaseParams_t& params ) +{ + if ( !player ) + { + return; + } + + Internal_StartEffect( effect, params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +//----------------------------------------------------------------------------- +void CForceFeedback::PauseAll( CBasePlayer *player ) +{ + if ( !player ) + return; + + Internal_PauseAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +//----------------------------------------------------------------------------- +void CForceFeedback::ResumeAll( CBasePlayer *player ) +{ + if ( !player ) + return; + + Internal_ResumeAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CForceFeedback::Internal_StopAllEffects() +{ + input->ForceFeedback_StopAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : heffect - +//----------------------------------------------------------------------------- +void CForceFeedback::Internal_StopEffect( FORCEFEEDBACK_t effect ) +{ + input->ForceFeedback_Stop( effect ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : effect - +//----------------------------------------------------------------------------- +void CForceFeedback::Internal_StartEffect( FORCEFEEDBACK_t effect, const FFBaseParams_t& params) +{ + char const *name = NameForForceFeedbackEffect( effect ); + Msg( "Starting FF effect '%s'\n", name ); + + FFParams p; + p.m_nEffectType = effect; + p.m_BaseParams = params; + + input->ForceFeedback_Start( effect, params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CForceFeedback::Internal_PauseAll() +{ + input->ForceFeedback_Pause(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CForceFeedback::Internal_ResumeAll() +{ + input->ForceFeedback_Resume(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszName - +// iSize - +// *pbuf - +//----------------------------------------------------------------------------- +void CForceFeedback::MsgFunc_ForceFeedback( bf_read &msg ) +{ + byte msgType = msg.ReadByte(); + + switch ( msgType ) + { + default: + { + Warning( "Bad parse in MsgFunc_ForceFeedback!\n" ); + } + break; + case FFMSG_STOPALL: + { + Internal_StopAllEffects(); + } + break; + case FFMSG_START: + { + FORCEFEEDBACK_t effectType = (FORCEFEEDBACK_t)msg.ReadByte(); + + FFBaseParams_t params; + params.m_flDirection = 360.0f * ( (byte)msg.ReadByte() / 255.0f ); + params.m_flDuration = (float)msg.ReadLong() / 1000.0f; + params.m_flGain = ( (byte)msg.ReadByte() / 255.0f ); + params.m_nPriority = msg.ReadByte(); + params.m_bSolo = msg.ReadByte() == 0 ? false : true; + + if ( effectType >= 0 && effectType < NUM_FORCE_FEEDBACK_PRESETS ) + { + Internal_StartEffect( effectType, params ); + } + else + { + Warning( "Bad parse in MsgFunc_ForceFeedback, FFMSG_START (%i)!\n", effectType ); + } + } + break; + case FFMSG_STOP: + { + FORCEFEEDBACK_t effectType = (FORCEFEEDBACK_t)msg.ReadByte(); + + Internal_StopEffect( effectType ); + } + break; + case FFMSG_PAUSE: + { + Internal_PauseAll(); + } + break; + case FFMSG_RESUME: + { + Internal_ResumeAll(); + } + break; + } +} diff --git a/game/client/c_func_areaportalwindow.cpp b/game/client/c_func_areaportalwindow.cpp new file mode 100644 index 00000000..88929fb1 --- /dev/null +++ b/game/client/c_func_areaportalwindow.cpp @@ -0,0 +1,140 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "view.h" +#include "model_types.h" +#include "IVRenderView.h" +#include "engine/ivmodelinfo.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define VIEWER_PADDING 80.0f + +class C_FuncAreaPortalWindow : public C_BaseEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_FuncAreaPortalWindow, C_BaseEntity ); + + +// Overrides. +public: + + virtual void ComputeFxBlend(); + virtual bool IsTransparent(); + virtual int DrawModel( int flags ); + virtual bool ShouldReceiveProjectedTextures( int flags ); + +private: + + float GetDistanceBlend(); + + + +public: + float m_flFadeStartDist; // Distance at which it starts fading (when <= this, alpha=m_flTranslucencyLimit). + float m_flFadeDist; // Distance at which it becomes solid. + + // 0-1 value - minimum translucency it's allowed to get to. + float m_flTranslucencyLimit; + + int m_iBackgroundModelIndex; +}; + + + +IMPLEMENT_CLIENTCLASS_DT( C_FuncAreaPortalWindow, DT_FuncAreaPortalWindow, CFuncAreaPortalWindow ) + RecvPropFloat( RECVINFO( m_flFadeStartDist ) ), + RecvPropFloat( RECVINFO( m_flFadeDist ) ), + RecvPropFloat( RECVINFO( m_flTranslucencyLimit ) ), + RecvPropInt( RECVINFO( m_iBackgroundModelIndex ) ) +END_RECV_TABLE() + + + +void C_FuncAreaPortalWindow::ComputeFxBlend() +{ + // We reset our blend down below so pass anything except 0 to the renderer. + m_nRenderFXBlend = 255; + +#ifdef _DEBUG + m_nFXComputeFrame = gpGlobals->framecount; +#endif + +} + + +bool C_FuncAreaPortalWindow::IsTransparent() +{ + return true; +} + + +int C_FuncAreaPortalWindow::DrawModel( int flags ) +{ + if ( !m_bReadyToDraw ) + return 0; + + if( !GetModel() ) + return 0; + + // Make sure we're a brush model. + int modelType = modelinfo->GetModelType( GetModel() ); + if( modelType != mod_brush ) + return 0; + + // Draw the fading version. + render->SetBlend( GetDistanceBlend() ); + render->DrawBrushModel( + this, + (model_t *)GetModel(), + GetAbsOrigin(), + GetAbsAngles(), + !!(flags & STUDIO_TRANSPARENCY) ); + + // Draw the optional foreground model next. + // Only use the alpha in the texture from the thing in the front. + if (m_iBackgroundModelIndex >= 0) + { + render->SetBlend( 1 ); + model_t *pBackground = ( model_t * )modelinfo->GetModel( m_iBackgroundModelIndex ); + if( pBackground && modelinfo->GetModelType( pBackground ) == mod_brush ) + { + render->DrawBrushModel( + this, + pBackground, + GetAbsOrigin(), + GetAbsAngles(), + !!(flags & STUDIO_TRANSPARENCY) ); + } + } + + return 1; +} + + +float C_FuncAreaPortalWindow::GetDistanceBlend() +{ + // Get the viewer's distance to us. + float flDist = CollisionProp()->CalcDistanceFromPoint( CurrentViewOrigin() ); + C_BasePlayer *local = C_BasePlayer::GetLocalPlayer(); + if ( local ) + { + flDist *= local->GetFOVDistanceAdjustFactor(); + } + + return RemapValClamped( flDist, m_flFadeStartDist, m_flFadeDist, m_flTranslucencyLimit, 1 ); +} + +bool C_FuncAreaPortalWindow::ShouldReceiveProjectedTextures( int flags ) +{ + return false; +} + + diff --git a/game/client/c_func_breakablesurf.cpp b/game/client/c_func_breakablesurf.cpp new file mode 100644 index 00000000..c27cbe26 --- /dev/null +++ b/game/client/c_func_breakablesurf.cpp @@ -0,0 +1,1336 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "particles_simple.h" +#include "IViewRender.h" +#include "ProxyEntity.h" +#include "materialsystem/IMaterialVar.h" +#include "model_types.h" +#include "engine/ivmodelinfo.h" +#include "ClientEffectPrecacheSystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MAX_NUM_PANELS 16 + +extern IVDebugOverlay *debugoverlay; + + +enum WinSide_t +{ + WIN_SIDE_BOTTOM, + WIN_SIDE_RIGHT, + WIN_SIDE_TOP, + WIN_SIDE_LEFT, +}; + +enum WinEdge_t +{ + EDGE_NOT = -1, // No edge + EDGE_NONE, // No edge on both sides /##\ + EDGE_FULL, // Edge on both sides |##| + EDGE_LEFT, // Edge is on left only |##\ + EDGE_RIGHT, // Edge is on right only /##| +}; + +#define STYLE_HIGHLIGHT = -1; + +#define NUM_EDGE_TYPES 4 +#define NUM_EDGE_STYLES 3 + + +//================================================== +// C_BreakableSurface +//================================================== + +//----------------------------------------------------------------------------- +// All the information associated with a particular handle +//----------------------------------------------------------------------------- +struct Panel_t +{ + char m_nWidth; + char m_nHeight; + char m_nSide; + char m_nEdgeType; + char m_nStyle; +}; + +struct EdgeTexture_t +{ + int m_nRenderIndex; + int m_nStyle; + CMaterialReference m_pMaterialEdge; + CTextureReference m_pMaterialEdgeTexture; +}; + +// Bits for m_nPanelBits +#define BITS_PANEL_IS_SOLID (1<<0) +#define BITS_PANEL_IS_STALE (1<<1) + + +class C_BreakableSurface : public C_BaseEntity, public IBrushRenderer +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_BreakableSurface, C_BaseEntity ); + DECLARE_DATADESC(); + + int m_nNumWide; + int m_nNumHigh; + float m_flPanelWidth; + float m_flPanelHeight; + Vector m_vNormal; + Vector m_vCorner; + bool m_bIsBroken; + int m_nSurfaceType; + + + // This is the texture we're going to use to multiply by the cracked base texture + ITexture* m_pCurrentDetailTexture; + + // Stores linked list of edges to render + CUtlLinkedList< Panel_t, unsigned short > m_RenderList; + + + C_BreakableSurface(); + ~C_BreakableSurface(); + +public: + void InitMaterial(WinEdge_t nEdgeType, int nEdgeStyle, char const* pMaterialName); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + + bool IsTransparent( void ); + bool HavePanel(int nWidth, int nHeight); + bool RenderBrushModelSurface( IClientEntity* pBaseEntity, IBrushSurface* pBrushSurface ); + int DrawModel( int flags ); + void DrawSolidBlocks( IBrushSurface* pBrushSurface ); + + virtual void OnRestore(); + + virtual bool ShouldReceiveProjectedTextures( int flags ); + +private: + // One bit per pane + CNetworkArray( bool, m_RawPanelBitVec, MAX_NUM_PANELS * MAX_NUM_PANELS ); + bool m_PrevRawPanelBitVec[ MAX_NUM_PANELS * MAX_NUM_PANELS ]; + + // 2 bits of flags and 2 bits of edge type + byte m_nPanelBits[MAX_NUM_PANELS][MAX_NUM_PANELS]; //UNDONE: allocate this dynamically? + CMaterialReference m_pMaterialBox; + EdgeTexture_t m_pSolid; + EdgeTexture_t m_pEdge[NUM_EDGE_TYPES][NUM_EDGE_STYLES]; + + inline bool InLegalRange(int nWidth, int nHeight); + inline bool IsPanelSolid(int nWidth, int nHeight); + inline bool IsPanelStale(int nWidth, int nHeight); + inline void SetPanelSolid(int nWidth, int nHeight, bool value); + inline void SetPanelStale(int nWidth, int nHeight, bool value); + + void DrawOneEdge( IBrushSurface* pBrushSurface, IMesh* pMesh, + CMeshBuilder *pMeshBuilder, const Vector &vStartPos, + const Vector &vWStep, const Vector &vHstep, WinSide_t nEdge); + void DrawOneHighlight( IBrushSurface* pBrushSurface, IMesh* pMesh, + CMeshBuilder *pMeshBuilder, const Vector &vStartPos, + const Vector &vWStep, const Vector &vHstep, WinSide_t nEdge); + void DrawOneBlock(IBrushSurface* pBrushSurface, IMesh* pMesh, + CMeshBuilder *pMeshBuilder, const Vector &vPosition, + const Vector &vWidth, const Vector &vHeight); + + void DrawRenderList( IBrushSurface* pBrushSurface); + void DrawRenderListHighlights( IBrushSurface* pBrushSurface ); + int FindRenderPanel(int nWidth, int nHeight, WinSide_t nSide); + void AddToRenderList(int nWidth, int nHeight, WinSide_t nSide, WinEdge_t nEdgeType, int forceStyle); + int FindFirstRenderTexture(WinEdge_t nEdgeType, int nStyle); + + inline void SetStyleType( int w, int h, int type ) + { + Assert( type < NUM_EDGE_STYLES ); + Assert( type >= 0 ); + // Clear old value + m_nPanelBits[ w ][ h ] &= ( ~0x03 << 2 ); + // Insert new value + m_nPanelBits[ w ][ h ] |= ( type << 2 ); + } + + inline int GetStyleType( int w, int h ) + { + int value = m_nPanelBits[ w ][ h ]; + value = ( value >> 2 ) & 0x03; + Assert( value < NUM_EDGE_STYLES ); + return value; + } + + // Gets at the cracked version of the material + void FindCrackedMaterial(); + + CMaterialReference m_pCrackedMaterial; + CTextureReference m_pMaterialBoxTexture; + + + void UpdateEdgeType(int nWidth, int nHeight, int forceStyle = -1 ); +}; + +BEGIN_DATADESC( C_BreakableSurface ) + + DEFINE_ARRAY( m_nPanelBits, FIELD_CHARACTER, MAX_NUM_PANELS * MAX_NUM_PANELS ), + +// DEFINE_FIELD( m_nNumWide, FIELD_INTEGER ), +// DEFINE_FIELD( m_nNumHigh, FIELD_INTEGER ), +// DEFINE_FIELD( m_flPanelWidth, FIELD_FLOAT ), +// DEFINE_FIELD( m_flPanelHeight, FIELD_FLOAT ), +// DEFINE_FIELD( m_vNormal, FIELD_VECTOR ), +// DEFINE_FIELD( m_vCorner, FIELD_VECTOR ), +// DEFINE_FIELD( m_bIsBroken, FIELD_BOOLEAN ), +// DEFINE_FIELD( m_nSurfaceType, FIELD_INTEGER ), + // DEFINE_FIELD( m_pCurrentDetailTexture, ITexture* ), + // DEFINE_FIELD( m_RenderList, CUtlLinkedList < Panel_t , unsigned short > ), + // DEFINE_FIELD( m_pMaterialBox, CMaterialReference ), + // DEFINE_FIELD( m_pSolid, EdgeTexture_t ), + // DEFINE_ARRAY( m_pEdge, EdgeTexture_t, NUM_EDGE_TYPES][NUM_EDGE_STYLES ), + // DEFINE_FIELD( m_pCrackedMaterial, CMaterialReference ), + // DEFINE_FIELD( m_pMaterialBoxTexture, CTextureReference ), + +END_DATADESC() + +bool C_BreakableSurface::InLegalRange(int nWidth, int nHeight) +{ + return (nWidth < m_nNumWide && nHeight < m_nNumHigh && + nWidth >=0 && nHeight >= 0 ); +} + +bool C_BreakableSurface::IsPanelSolid(int nWidth, int nHeight) +{ + return ( BITS_PANEL_IS_SOLID & m_nPanelBits[nWidth][nHeight] )!=0 ; +} + +bool C_BreakableSurface::IsPanelStale(int nWidth, int nHeight) +{ + return ( BITS_PANEL_IS_STALE & m_nPanelBits[nWidth][nHeight] )!=0 ; +} + +void C_BreakableSurface::SetPanelSolid(int nWidth, int nHeight, bool value) +{ + if ( !InLegalRange( nWidth, nHeight ) ) + return; + + if ( value ) + { + m_nPanelBits[nWidth][nHeight] |= BITS_PANEL_IS_SOLID; + } + else + { + m_nPanelBits[nWidth][nHeight] &= ~BITS_PANEL_IS_SOLID; + } +} + +void C_BreakableSurface::SetPanelStale(int nWidth, int nHeight, bool value) +{ + if ( !InLegalRange( nWidth, nHeight) ) + return; + + if ( value ) + { + m_nPanelBits[nWidth][nHeight] |= BITS_PANEL_IS_STALE; + } + else + { + m_nPanelBits[nWidth][nHeight] &= ~BITS_PANEL_IS_STALE; + } +} + +void C_BreakableSurface::OnRestore() +{ + BaseClass::OnRestore(); + + // FIXME: This restores the general state, but not the random edge bits + // those would need to be serialized separately... + // traverse everthing and restore bits + // Initialize panels + for (int w=0;wGetModelMaterialCount( const_cast(GetModel()) ); + if( materialCount != 1 ) + { + Warning( "Encountered func_breakablesurf that has a material applied to more than one surface!\n" ); + m_pCrackedMaterial.Init( "debug/debugempty", TEXTURE_GROUP_OTHER ); + return; + } + + // Get at the first material; even if there are more than one. + IMaterial* pMaterial; + modelinfo->GetModelMaterials( const_cast(GetModel()), 1, &pMaterial ); + + // The material should point to a cracked version of itself + bool foundVar; + IMaterialVar* pCrackName = pMaterial->FindVar( "$crackmaterial", &foundVar, false ); + if (foundVar) + { + m_pCrackedMaterial.Init( pCrackName->GetStringValue(), TEXTURE_GROUP_CLIENT_EFFECTS ); + } + else + { + m_pCrackedMaterial.Init( pMaterial ); + } +} + + +//----------------------------------------------------------------------------- +// Gets at the base texture +//----------------------------------------------------------------------------- + +static ITexture* GetBaseTexture( IMaterial* pMaterial ) +{ + bool foundVar; + IMaterialVar* pTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); + if (!foundVar) + return 0; + + return pTextureVar->GetTextureValue(); +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::InitMaterial(WinEdge_t nEdgeType, int nEdgeStyle, char const* pMaterialName) +{ + m_pEdge[nEdgeType][nEdgeStyle].m_nRenderIndex = m_RenderList.InvalidIndex(); + m_pEdge[nEdgeType][nEdgeStyle].m_nStyle = nEdgeStyle; + m_pEdge[nEdgeType][nEdgeStyle].m_pMaterialEdge.Init(pMaterialName, TEXTURE_GROUP_CLIENT_EFFECTS); + m_pEdge[nEdgeType][nEdgeStyle].m_pMaterialEdgeTexture.Init( GetBaseTexture( m_pEdge[nEdgeType][nEdgeStyle].m_pMaterialEdge ) ); +} + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +C_BreakableSurface::C_BreakableSurface() +{ + m_vNormal.Init(); + m_vCorner.Init(); + m_bIsBroken = false; + + m_pCurrentDetailTexture = NULL; + + Q_memset( m_PrevRawPanelBitVec, 0xff, sizeof( m_PrevRawPanelBitVec ) ); +} + +C_BreakableSurface::~C_BreakableSurface() +{ +} + +void C_BreakableSurface::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + // Initialize panels + m_nNumWide = MAX_NUM_PANELS; + m_nNumHigh = MAX_NUM_PANELS; + for (int w=0;wInstallBrushSurfaceRenderer( this ); + + // If it's broken, always draw it translucent + BaseClass::DrawModel( m_bIsBroken ? flags | STUDIO_TRANSPARENCY : flags ); + + // Remove our nonstandard brush surface renderer... + render->InstallBrushSurfaceRenderer( 0 ); + + return 0; +} + +bool C_BreakableSurface::RenderBrushModelSurface( IClientEntity* pBaseEntity, IBrushSurface* pBrushSurface ) +{ + // If tile draw highlight for grout + if (m_nSurfaceType == SHATTERSURFACE_TILE) + { + DrawRenderListHighlights(pBrushSurface); + } + DrawSolidBlocks(pBrushSurface); + DrawRenderList(pBrushSurface); + + // Don't draw decals + return false; +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::DrawRenderList(IBrushSurface* pBrushSurface) +{ + // Get width and height steps + QAngle vAngles; + VectorAngles(-1*m_vNormal,vAngles); + Vector vWidthStep,vHeightStep; + AngleVectors(vAngles,NULL,&vWidthStep,&vHeightStep); + vWidthStep *= m_flPanelWidth; + vHeightStep *= m_flPanelHeight; + + CMeshBuilder pMeshBuilder; + IMesh* pMesh = NULL; + int nCurStyle = -1; + int nCurEdgeType = -1; + CMatRenderContextPtr pRenderContext( materials ); + for( unsigned short i = m_RenderList.Head(); i != m_RenderList.InvalidIndex(); i = m_RenderList.Next(i) ) + { + + if (nCurStyle != m_RenderList[i].m_nStyle || + nCurEdgeType != m_RenderList[i].m_nEdgeType ) + { + nCurStyle = m_RenderList[i].m_nStyle; + nCurEdgeType = m_RenderList[i].m_nEdgeType; + + m_pCurrentDetailTexture = m_pEdge[nCurEdgeType][nCurStyle].m_pMaterialEdgeTexture; + pRenderContext->Flush(false); + pRenderContext->Bind(m_pCrackedMaterial, (IClientRenderable*)this); + pMesh = pRenderContext->GetDynamicMesh( ); + } + + Vector vRenderPos = m_vCorner + + (m_RenderList[i].m_nWidth*vWidthStep) + + (m_RenderList[i].m_nHeight*vHeightStep); + + DrawOneEdge(pBrushSurface, pMesh,&pMeshBuilder,vRenderPos,vWidthStep,vHeightStep,(WinSide_t)m_RenderList[i].m_nSide); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::DrawRenderListHighlights(IBrushSurface* pBrushSurface) +{ + // Get width and height steps + QAngle vAngles; + VectorAngles(-1*m_vNormal,vAngles); + Vector vWidthStep,vHeightStep; + AngleVectors(vAngles,NULL,&vWidthStep,&vHeightStep); + vWidthStep *= m_flPanelWidth; + vHeightStep *= m_flPanelHeight; + + + CMeshBuilder pMeshBuilder; + IMesh* pMesh = NULL; + int nCurStyle = -1; + int nCurEdgeType = -1; + CMatRenderContextPtr pRenderContext( materials ); + for( unsigned short i = m_RenderList.Head(); i != m_RenderList.InvalidIndex(); i = m_RenderList.Next(i) ) + { + + if (nCurStyle != m_RenderList[i].m_nStyle || + nCurEdgeType != m_RenderList[i].m_nEdgeType ) + { + nCurStyle = m_RenderList[i].m_nStyle; + nCurEdgeType = m_RenderList[i].m_nEdgeType; + + IMaterial *pMat = m_pEdge[nCurEdgeType][nCurStyle].m_pMaterialEdge; + pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMat ); + } + + Vector vRenderPos = m_vCorner + + (m_RenderList[i].m_nWidth*vWidthStep) + + (m_RenderList[i].m_nHeight*vHeightStep) + + (0.30*m_vNormal); + + DrawOneHighlight(pBrushSurface, pMesh,&pMeshBuilder,vRenderPos,vWidthStep,vHeightStep,(WinSide_t)m_RenderList[i].m_nSide); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +bool C_BreakableSurface::HavePanel(int nWidth, int nHeight) +{ + // If I'm off the edge, always give support + if (!InLegalRange(nWidth,nHeight)) + { + return true; + } + return (IsPanelSolid(nWidth,nHeight)); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::UpdateEdgeType(int nWidth, int nHeight, int forceStyle /*=-1*/ ) +{ + Assert( forceStyle < NUM_EDGE_STYLES ); + + // ----------------------------------- + // Check edge conditions + // ----------------------------------- + if (!InLegalRange(nWidth,nHeight)) + { + return; + } + + // ---------------------------------- + // If solid has no edges + // ---------------------------------- + if (IsPanelSolid(nWidth,nHeight)) + { + return; + } + + // Panel is no longer stale + SetPanelStale(nWidth, nHeight,false); + + // ---------------------------------- + // Set edge type base on neighbors + // ---------------------------------- + bool bUp = HavePanel(nWidth, nHeight+1); + bool bDown = HavePanel(nWidth, nHeight-1); + bool bLeft = HavePanel(nWidth-1, nHeight ); + bool bRight = HavePanel(nWidth+1, nHeight ); + + bool bUpLeft = HavePanel(nWidth-1, nHeight+1); + bool bUpRight = HavePanel(nWidth+1, nHeight+1); + bool bDownLeft = HavePanel(nWidth-1, nHeight-1); + bool bDownRight = HavePanel(nWidth+1, nHeight-1); + + //------------- + // Top + //------------- + if (bUp) + { + bool bLeftEdge = !bLeft && bUpLeft; + bool bRightEdge = !bRight && bUpRight; + + if (bLeftEdge && bRightEdge) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_TOP, EDGE_FULL, forceStyle ); + } + else if (bLeftEdge) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_TOP, EDGE_LEFT, forceStyle ); + } + else if (bRightEdge) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_TOP, EDGE_RIGHT, forceStyle ); + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_TOP, EDGE_NONE, forceStyle ); + } + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_TOP, EDGE_NOT, forceStyle ); + } + //------------- + // Bottom + //------------- + if (bDown) + { + bool bLeftEdge = !bLeft && bDownLeft; + bool bRightEdge = !bRight && bDownRight; + + if (bLeftEdge && bRightEdge) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_BOTTOM, EDGE_FULL, forceStyle ); + } + else if (bLeftEdge) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_BOTTOM, EDGE_RIGHT, forceStyle ); + } + else if (bRightEdge) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_BOTTOM, EDGE_LEFT, forceStyle ); + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_BOTTOM, EDGE_NONE, forceStyle ); + } + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_BOTTOM, EDGE_NOT, forceStyle ); + } + //------------- + // Left + //------------- + if (bLeft) + { + bool bTop = !bUp && bUpLeft; + bool bBottom = !bDown && bDownLeft; + + if (bTop && bBottom) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_LEFT, EDGE_FULL, forceStyle ); + } + else if (bTop) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_LEFT, EDGE_RIGHT, forceStyle ); + } + else if (bBottom) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_LEFT, EDGE_LEFT, forceStyle ); + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_LEFT, EDGE_NONE, forceStyle ); + } + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_LEFT, EDGE_NOT, forceStyle ); + } + //------------- + // Right + //------------- + if (bRight) + { + bool bTop = !bUp && bUpRight; + bool bBottom = !bDown && bDownRight; + + if (bTop && bBottom) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_RIGHT, EDGE_FULL, forceStyle ); + } + else if (bTop) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_RIGHT, EDGE_LEFT, forceStyle ); + } + else if (bBottom) + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_RIGHT, EDGE_RIGHT, forceStyle ); + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_RIGHT, EDGE_NONE, forceStyle ); + } + } + else + { + AddToRenderList(nWidth, nHeight, WIN_SIDE_RIGHT, EDGE_NOT, forceStyle ); + } +} + +//-------------------------------------------------------------------------------- +// Purpose : Return index to panel in render list that meets these qualifications +// Input : +// Output : +//-------------------------------------------------------------------------------- +int C_BreakableSurface::FindRenderPanel(int nWidth, int nHeight, WinSide_t nWinSide) +{ + for( unsigned short i = m_RenderList.Head(); i != m_RenderList.InvalidIndex(); i = m_RenderList.Next(i) ) + { + if (m_RenderList[i].m_nSide == nWinSide && + m_RenderList[i].m_nWidth == nWidth && + m_RenderList[i].m_nHeight == nHeight) + { + return i; + } + } + return m_RenderList.InvalidIndex(); +} + +//---------------------------------------------------------------------------------- +// Purpose : Returns first element in render list with the same edge type and style +// Input : +// Output : +//---------------------------------------------------------------------------------- +int C_BreakableSurface::FindFirstRenderTexture(WinEdge_t nEdgeType, int nStyle) +{ + for( unsigned short i = m_RenderList.Head(); i != m_RenderList.InvalidIndex(); i = m_RenderList.Next(i) ) + { + if (m_RenderList[i].m_nStyle == nStyle && + m_RenderList[i].m_nEdgeType == nEdgeType ) + { + return i; + } + } + return m_RenderList.InvalidIndex(); +} + +//------------------------------------------------------------------------------ +// Purpose : Add a edge to be rendered to the render list +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::AddToRenderList(int nWidth, int nHeight, WinSide_t nSide, WinEdge_t nEdgeType, int forceStyle ) +{ + // ----------------------------------------------------- + // Try to find old panel + int nOldPanelIndex = FindRenderPanel(nWidth,nHeight,nSide); + + // ----------------------------------------------------- + // If I have an old panel, get it's style and remove it + // otherwise randomly chose a style + int nStyle; + if (m_RenderList.IsValidIndex(nOldPanelIndex) ) + { + nStyle = m_RenderList[nOldPanelIndex].m_nStyle; + m_RenderList.Remove(nOldPanelIndex); + } + else + { + nStyle = random->RandomInt(0,NUM_EDGE_STYLES-1); + } + + if ( forceStyle != -1 ) + { + nStyle = forceStyle; + } + + // ----------------------------------------------------- + // If my new panel has an edge, add it to render list + if (nEdgeType != EDGE_NOT) + { + // Renderlist is sorted by texture type. Find first element + // that shares the same texture as the new panel + unsigned short nTexIndex = FindFirstRenderTexture(nEdgeType, nStyle); + + // If texture was already in list, add after last use + unsigned short nNewIndex; + if (m_RenderList.IsValidIndex(nTexIndex)) + { + nNewIndex = m_RenderList.InsertAfter(nTexIndex); + } + // Otherwise add to send of render list + else + { + nNewIndex = m_RenderList.AddToTail(); + } + + // Now fill out my data + m_RenderList[nNewIndex].m_nHeight = nHeight; + m_RenderList[nNewIndex].m_nWidth = nWidth; + m_RenderList[nNewIndex].m_nEdgeType = nEdgeType; + m_RenderList[nNewIndex].m_nSide = nSide; + m_RenderList[nNewIndex].m_nStyle = nStyle; + + Assert( nStyle < NUM_EDGE_STYLES ); + SetStyleType( nWidth, nHeight, nStyle ); + } +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::DrawSolidBlocks(IBrushSurface* pBrushSurface) +{ + CMatRenderContextPtr pRenderContext( materials ); + + m_pCurrentDetailTexture = m_pMaterialBoxTexture; + + // Gotta flush (in a non-stalling way) because we effectively + // have a new material due to the new base texture + pRenderContext->Flush(false); + pRenderContext->Bind(m_pCrackedMaterial, (IClientRenderable*)this); + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + CMeshBuilder pMeshBuilder; + + // --------------- + // Create panels + // --------------- + QAngle vAngles; + VectorAngles(-1*m_vNormal,vAngles); + Vector vWidthStep,vHeightStep; + AngleVectors(vAngles,NULL,&vWidthStep,&vHeightStep); + vWidthStep *= m_flPanelWidth; + vHeightStep *= m_flPanelHeight; + + Vector vCurPos = m_vCorner; + for (int width=0;width 0) + { + vCurPos = m_vCorner + vWidthStep*width + vHeightStep*(height-nHCount); + DrawOneBlock(pBrushSurface, pMesh, &pMeshBuilder, vCurPos,vWidthStep,vHeightStep*nHCount); + nHCount = 0; + } + } + if (nHCount) + { + vCurPos = m_vCorner + vWidthStep*width + vHeightStep*(height-nHCount); + DrawOneBlock(pBrushSurface, pMesh, &pMeshBuilder, vCurPos,vWidthStep,vHeightStep*nHCount); + } + } +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::DrawOneBlock(IBrushSurface* pBrushSurface, IMesh* pMesh, + CMeshBuilder *pMeshBuilder, const Vector &vCurPos, const Vector &vWidthStep, + const Vector &vHeightStep) +{ + pMeshBuilder->Begin( pMesh, MATERIAL_QUADS, 1 ); + + Vector2D texCoord, lightCoord; + pBrushSurface->ComputeTextureCoordinate( vCurPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vCurPos, lightCoord ); + + pMeshBuilder->Position3f( vCurPos.x, vCurPos.y, vCurPos.z ); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2f( 0, 0, 1 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->AdvanceVertex(); + + Vector vNextPos = vCurPos + vWidthStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x, vNextPos.y, vNextPos.z ); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2f( 0, 0, 0 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->AdvanceVertex(); + + vNextPos = vNextPos + vHeightStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x , vNextPos.y, vNextPos.z ); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2f( 0, 1, 0 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->AdvanceVertex(); + + vNextPos = vNextPos - vWidthStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x, vNextPos.y , vNextPos.z); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2f( 0, 1, 1 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->AdvanceVertex(); + + pMeshBuilder->End(); + pMesh->Draw(); +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::DrawOneEdge( IBrushSurface* pBrushSurface, IMesh* pMesh, + CMeshBuilder *pMeshBuilder, const Vector &vStartPos, const Vector &vWStep, + const Vector &vHStep, WinSide_t nEdge ) +{ + pMeshBuilder->Begin( pMesh, MATERIAL_QUADS, 1 ); + + Vector2D texCoord, lightCoord; + pBrushSurface->ComputeTextureCoordinate( vStartPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vStartPos, lightCoord ); + + pMeshBuilder->Position3f( vStartPos.x, vStartPos.y, vStartPos.z); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + Vector vNextPos = vStartPos + vWStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x , vNextPos.y , vNextPos.z); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + vNextPos = vNextPos + vHStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x, vNextPos.y, vNextPos.z ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + vNextPos = vNextPos - vWStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x, vNextPos.y, vNextPos.z ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + pMeshBuilder->TexCoord2fv( 2, texCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + pMeshBuilder->End(); + pMesh->Draw(); +} + + + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_BreakableSurface::DrawOneHighlight( IBrushSurface* pBrushSurface, IMesh* pMesh, + CMeshBuilder *pMeshBuilder, const Vector &vStartPos, const Vector &vWStep, + const Vector &vHStep, WinSide_t nEdge ) +{ + Vector vColor = Vector(0.41,0.35,0.24); + + pMeshBuilder->Begin( pMesh, MATERIAL_QUADS, 1 ); + + Vector2D texCoord, lightCoord; + pBrushSurface->ComputeTextureCoordinate( vStartPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vStartPos, lightCoord ); + + pMeshBuilder->Position3f( vStartPos.x, vStartPos.y, vStartPos.z); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4f( vColor[0], vColor[1], vColor[2], 1.0); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + Vector vNextPos = vStartPos + vWStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x , vNextPos.y , vNextPos.z); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4f( vColor[0], vColor[1], vColor[2], 1.0); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + vNextPos = vNextPos + vHStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x, vNextPos.y, vNextPos.z ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4f( vColor[0], vColor[1], vColor[2], 1.0); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + vNextPos = vNextPos - vWStep; + + pBrushSurface->ComputeTextureCoordinate( vNextPos, texCoord ); + pBrushSurface->ComputeLightmapCoordinate( vNextPos, lightCoord ); + + pMeshBuilder->Position3f( vNextPos.x, vNextPos.y, vNextPos.z ); + pMeshBuilder->Normal3fv( m_vNormal.Base() ); + pMeshBuilder->Color4f( vColor[0], vColor[1], vColor[2], 1.0); + pMeshBuilder->TexCoord2fv( 1, lightCoord.Base() ); + switch (nEdge) + { + case WIN_SIDE_RIGHT: + pMeshBuilder->TexCoord2f( 0, 0.0f, 0.0f ); + break; + case WIN_SIDE_BOTTOM: + pMeshBuilder->TexCoord2f( 0, 1.0f, 0.0f ); + break; + case WIN_SIDE_LEFT: + pMeshBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + break; + case WIN_SIDE_TOP: + pMeshBuilder->TexCoord2f( 0, 0.0f, 1.0f ); + break; + } + pMeshBuilder->AdvanceVertex(); + + pMeshBuilder->End(); + pMesh->Draw(); +} + +bool C_BreakableSurface::ShouldReceiveProjectedTextures( int flags ) +{ + return false; +} + + +//------------------------------------------------------------------------------ +// A material proxy that resets the texture to use the original surface texture +//------------------------------------------------------------------------------ +class CBreakableSurfaceProxy : public CEntityMaterialProxy +{ +public: + CBreakableSurfaceProxy(); + virtual ~CBreakableSurfaceProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( C_BaseEntity *pC_BaseEntity ); + virtual IMaterial *CBreakableSurfaceProxy::GetMaterial(); + +private: + // get at the material whose texture we're going to steal + void AcquireSourceMaterial( C_BaseEntity* pEnt ); + + IMaterialVar* m_BaseTextureVar; +}; + +CBreakableSurfaceProxy::CBreakableSurfaceProxy() +{ + m_BaseTextureVar = NULL; +} + +CBreakableSurfaceProxy::~CBreakableSurfaceProxy() +{ +} + + +bool CBreakableSurfaceProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool foundVar; + m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); + return foundVar; +} + +void CBreakableSurfaceProxy::OnBind( C_BaseEntity *pC_BaseEntity ) +{ + C_BreakableSurface *pEnt = dynamic_cast< C_BreakableSurface * >(pC_BaseEntity); + if( !pEnt ) + { + return; + } + + // Use the current base texture specified by the suface + m_BaseTextureVar->SetTextureValue( pEnt->m_pCurrentDetailTexture ); +} + +IMaterial *CBreakableSurfaceProxy::GetMaterial() +{ + if ( !m_BaseTextureVar ) + return NULL; + + return m_BaseTextureVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CBreakableSurfaceProxy, IMaterialProxy, "BreakableSurface" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/c_func_conveyor.cpp b/game/client/c_func_conveyor.cpp new file mode 100644 index 00000000..85a69b21 --- /dev/null +++ b/game/client/c_func_conveyor.cpp @@ -0,0 +1,158 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" +#include "materialsystem/IMaterialProxy.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" +#include "FunctionProxy.h" +#include +#include "mathlib/VMatrix.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + +class C_FuncConveyor : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_FuncConveyor, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_FuncConveyor(); + + float GetConveyorSpeed() { return m_flConveyorSpeed; } + +private: + float m_flConveyorSpeed; +}; + + +IMPLEMENT_CLIENTCLASS_DT( C_FuncConveyor, DT_FuncConveyor, CFuncConveyor ) + RecvPropFloat( RECVINFO( m_flConveyorSpeed ) ), +END_RECV_TABLE() + + +C_FuncConveyor::C_FuncConveyor() +{ + m_flConveyorSpeed = 0.0; +} + + +class CConveyorMaterialProxy : public IMaterialProxy +{ +public: + CConveyorMaterialProxy(); + virtual ~CConveyorMaterialProxy(); + + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pC_BaseEntity ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +private: + C_BaseEntity *BindArgToEntity( void *pArg ); + + IMaterialVar *m_pTextureScrollVar; +}; + +CConveyorMaterialProxy::CConveyorMaterialProxy() +{ + m_pTextureScrollVar = NULL; +} + +CConveyorMaterialProxy::~CConveyorMaterialProxy() +{ +} + + +bool CConveyorMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + char const* pScrollVarName = pKeyValues->GetString( "textureScrollVar" ); + if( !pScrollVarName ) + return false; + + bool foundVar; + m_pTextureScrollVar = pMaterial->FindVar( pScrollVarName, &foundVar, false ); + if( !foundVar ) + return false; + + return true; +} + +C_BaseEntity *CConveyorMaterialProxy::BindArgToEntity( void *pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + return pRend->GetIClientUnknown()->GetBaseEntity(); +} + +void CConveyorMaterialProxy::OnBind( void *pC_BaseEntity ) +{ + if( !pC_BaseEntity ) + return; + + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + + if ( !pEntity ) + return; + + C_FuncConveyor *pConveyor = dynamic_cast(pEntity); + + if ( !pConveyor ) + return; + + if ( !m_pTextureScrollVar ) + { + return; + } + + float flConveyorSpeed = pConveyor->GetConveyorSpeed(); + float flRate = abs( flConveyorSpeed ) / 128.0; + float flAngle = (flConveyorSpeed >= 0) ? 180 : 0; + + float sOffset = gpGlobals->curtime * cos( flAngle * ( M_PI / 180.0f ) ) * flRate; + float tOffset = gpGlobals->curtime * sin( flAngle * ( M_PI / 180.0f ) ) * flRate; + + // make sure that we are positive + if( sOffset < 0.0f ) + { + sOffset += 1.0f + -( int )sOffset; + } + if( tOffset < 0.0f ) + { + tOffset += 1.0f + -( int )tOffset; + } + + // make sure that we are in a [0,1] range + sOffset = sOffset - ( int )sOffset; + tOffset = tOffset - ( int )tOffset; + + if (m_pTextureScrollVar->GetType() == MATERIAL_VAR_TYPE_MATRIX) + { + VMatrix mat; + MatrixBuildTranslation( mat, sOffset, tOffset, 0.0f ); + m_pTextureScrollVar->SetMatrixValue( mat ); + } + else + { + m_pTextureScrollVar->SetVecValue( sOffset, tOffset, 0.0f ); + } + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CConveyorMaterialProxy::GetMaterial() +{ + return m_pTextureScrollVar ? m_pTextureScrollVar->GetOwningMaterial() : NULL; +} + +EXPOSE_INTERFACE( CConveyorMaterialProxy, IMaterialProxy, "ConveyorScroll" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/c_func_dust.cpp b/game/client/c_func_dust.cpp new file mode 100644 index 00000000..0b96cc99 --- /dev/null +++ b/game/client/c_func_dust.cpp @@ -0,0 +1,365 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "fx.h" +#include "c_func_dust.h" +#include "func_dust_shared.h" +#include "c_te_particlesystem.h" +#include "env_wind_shared.h" +#include "engine/IEngineTrace.h" +#include "tier0/vprof.h" +#include "ClientEffectPrecacheSystem.h" +#include "particles_ez.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_Func_Dust, DT_Func_Dust, CFunc_Dust ) + RecvPropInt( RECVINFO(m_Color) ), + RecvPropInt( RECVINFO(m_SpawnRate) ), + RecvPropFloat( RECVINFO(m_flSizeMin) ), + RecvPropFloat( RECVINFO(m_flSizeMax) ), + RecvPropInt( RECVINFO(m_LifetimeMin) ), + RecvPropInt( RECVINFO(m_LifetimeMax) ), + RecvPropInt( RECVINFO(m_DustFlags) ), + RecvPropInt( RECVINFO(m_SpeedMax) ), + RecvPropInt( RECVINFO(m_DistMax) ), + RecvPropInt( RECVINFO( m_nModelIndex ) ), + RecvPropFloat( RECVINFO( m_FallSpeed ) ), + RecvPropDataTable( RECVINFO_DT( m_Collision ), 0, &REFERENCE_RECV_TABLE(DT_CollisionProperty) ), +END_RECV_TABLE() + + + +// ------------------------------------------------------------------------------------ // +// CDustEffect implementation. +// ------------------------------------------------------------------------------------ // +#define DUST_ACCEL 50 + + +void CDustEffect::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const CFuncDustParticle *pParticle = (const CFuncDustParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Velocity. + float flAlpha; + if( m_pDust->m_DustFlags & DUSTFLAGS_FROZEN ) + { + flAlpha = 1; + } + else + { + // Alpha. + float flAngle = (pParticle->m_flLifetime / pParticle->m_flDieTime) * M_PI * 2; + flAlpha = sin( flAngle - (M_PI * 0.5f) ) * 0.5f + 0.5f; + } + + Vector tPos; + TransformParticle( ParticleMgr()->GetModelView(), pParticle->m_Pos, tPos ); + float sortKey = (int) tPos.z; + + if( -tPos.z <= m_pDust->m_DistMax ) + { + flAlpha *= 1 + (tPos.z / m_pDust->m_DistMax); + + // Draw it. + float flSize = pParticle->m_flSize; + if( m_pDust->m_DustFlags & DUSTFLAGS_SCALEMOTES ) + flSize *= -tPos.z; + + RenderParticle_Color255Size( + pIterator->GetParticleDraw(), + tPos, + Vector( m_pDust->m_Color.r, m_pDust->m_Color.g, m_pDust->m_Color.b ), + flAlpha * m_pDust->m_Color.a, + flSize + ); + } + + pParticle = (const CFuncDustParticle*)pIterator->GetNext( sortKey ); + } +} + +void CDustEffect::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + Vector vecWind; + GetWindspeedAtTime( gpGlobals->curtime, vecWind ); + + + CFuncDustParticle *pParticle = (CFuncDustParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Velocity. + if( !(m_pDust->m_DustFlags & DUSTFLAGS_FROZEN) ) + { + // Kill the particle? + pParticle->m_flLifetime += pIterator->GetTimeDelta(); + if( pParticle->m_flLifetime >= pParticle->m_flDieTime ) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + for ( int i = 0 ; i < 2 ; i++ ) + { + if ( pParticle->m_vVelocity[i] < vecWind[i] ) + { + pParticle->m_vVelocity[i] += ( gpGlobals->frametime * DUST_ACCEL ); + + // clamp + if ( pParticle->m_vVelocity[i] > vecWind[i] ) + pParticle->m_vVelocity[i] = vecWind[i]; + } + else if (pParticle->m_vVelocity[i] > vecWind[i] ) + { + pParticle->m_vVelocity[i] -= ( gpGlobals->frametime * DUST_ACCEL ); + + // clamp. + if ( pParticle->m_vVelocity[i] < vecWind[i] ) + pParticle->m_vVelocity[i] = vecWind[i]; + } + } + + // Apply velocity. + pParticle->m_Pos.MulAdd( pParticle->m_Pos, pParticle->m_vVelocity, pIterator->GetTimeDelta() ); + } + } + + pParticle = (CFuncDustParticle*)pIterator->GetNext(); + } +} + + +// ------------------------------------------------------------------------------------ // +// C_Func_Dust implementation. +// ------------------------------------------------------------------------------------ // + +C_Func_Dust::C_Func_Dust() : m_Effect( "C_Func_Dust" ) +{ + m_Effect.m_pDust = this; + m_Effect.SetDynamicallyAllocated( false ); // So it doesn't try to delete itself. +} + + +C_Func_Dust::~C_Func_Dust() +{ +} + +void C_Func_Dust::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if( updateType == DATA_UPDATE_CREATED ) + { + m_hMaterial = m_Effect.GetPMaterial( "particle/sparkles" ); + + m_Effect.SetSortOrigin( WorldSpaceCenter( ) ); + + // Let us think each frame. + SetNextClientThink( CLIENT_THINK_ALWAYS ); + + // If we're setup to be frozen, just make a bunch of particles initially. + if( m_DustFlags & DUSTFLAGS_FROZEN ) + { + for( int i=0; i < m_SpawnRate; i++ ) + { + AttemptSpawnNewParticle(); + } + } + } + + m_Spawner.Init( m_SpawnRate ); // N particles per second +} + + +void C_Func_Dust::ClientThink() +{ + // If frozen, don't make new particles. + if( m_DustFlags & DUSTFLAGS_FROZEN ) + return; + + // Spawn particles? + if( m_DustFlags & DUSTFLAGS_ON ) + { + float flDelta = min( gpGlobals->frametime, 0.1f ); + while( m_Spawner.NextEvent( flDelta ) ) + { + AttemptSpawnNewParticle(); + } + } + + // Tell the particle manager our bbox. + Vector vWorldMins, vWorldMaxs; + CollisionProp()->WorldSpaceAABB( &vWorldMins, &vWorldMaxs ); + vWorldMins -= Vector( m_flSizeMax, m_flSizeMax, m_flSizeMax ); + vWorldMaxs += Vector( m_flSizeMax, m_flSizeMax, m_flSizeMax ); + m_Effect.GetBinding().SetBBox( vWorldMins, vWorldMaxs ); +} + + +bool C_Func_Dust::ShouldDraw() +{ + return false; +} + + +void C_Func_Dust::AttemptSpawnNewParticle() +{ + // Find a random spot inside our bmodel. + static int nTests=10; + + for( int iTest=0; iTest < nTests; iTest++ ) + { + Vector vPercent = RandomVector( 0, 1 ); + Vector vTest = WorldAlignMins() + (WorldAlignMaxs() - WorldAlignMins()) * vPercent; + + int contents = enginetrace->GetPointContents_Collideable( GetCollideable(), vTest ); + if( contents & CONTENTS_SOLID ) + { + CFuncDustParticle *pParticle = (CFuncDustParticle*)m_Effect.AddParticle( 10, m_hMaterial, vTest ); + if( pParticle ) + { + pParticle->m_vVelocity = RandomVector( -m_SpeedMax, m_SpeedMax ); + pParticle->m_vVelocity.z -= m_FallSpeed; + + pParticle->m_flLifetime = 0; + pParticle->m_flDieTime = RemapVal( rand(), 0, RAND_MAX, m_LifetimeMin, m_LifetimeMax ); + + if( m_DustFlags & DUSTFLAGS_SCALEMOTES ) + pParticle->m_flSize = RemapVal( rand(), 0, RAND_MAX, m_flSizeMin/10000.0f, m_flSizeMax/10000.0f ); + else + pParticle->m_flSize = RemapVal( rand(), 0, RAND_MAX, m_flSizeMin, m_flSizeMax ); + + pParticle->m_Color = m_Color; + } + + break; + } + } +} + +// +// Dust +// + +//----------------------------------------------------------------------------- +// Spew out dust! +//----------------------------------------------------------------------------- +void FX_Dust( const Vector &vecOrigin, const Vector &vecDirection, float flSize, float flSpeed ) +{ + VPROF_BUDGET( "FX_Dust", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + int numPuffs = (flSize*0.5f); + + if ( numPuffs < 1 ) + numPuffs = 1; + if ( numPuffs > 32 ) + numPuffs = 32; + + float speed = flSpeed * 0.1f; + + if ( speed < 0 ) + speed = 1.0f; + if (speed > 48.0f ) + speed = 48.0f; + + //FIXME: Better sampling area + Vector offset = vecOrigin + ( vecDirection * flSize ); + + //Find area ambient light color and use it to tint smoke + Vector worldLight = WorldGetLightForPoint( offset, true ); + + // Throw puffs + SimpleParticle particle; + for ( int i = 0; i < numPuffs; i++ ) + { + offset.Random( -(flSize*0.25f), flSize*0.25f ); + offset += vecOrigin + ( vecDirection * flSize ); + + particle.m_Pos = offset; + particle.m_flLifetime = 0.0f; + particle.m_flDieTime = random->RandomFloat( 0.4f, 1.0f ); + + particle.m_vecVelocity = vecDirection * random->RandomFloat( speed*0.5f, speed ) * i; + particle.m_vecVelocity[2] = 0.0f; + + int color = random->RandomInt( 48, 64 ); + + particle.m_uchColor[0] = (color+16) + ( worldLight[0] * (float) color ); + particle.m_uchColor[1] = (color+8) + ( worldLight[1] * (float) color ); + particle.m_uchColor[2] = color + ( worldLight[2] * (float) color ); + + particle.m_uchStartAlpha= random->RandomInt( 64, 128 ); + particle.m_uchEndAlpha = 0; + particle.m_uchStartSize = random->RandomInt( 2, 8 ); + particle.m_uchEndSize = random->RandomInt( 24, 48 ); + particle.m_flRoll = random->RandomInt( 0, 360 ); + particle.m_flRollDelta = random->RandomFloat( -0.5f, 0.5f ); + + AddSimpleParticle( &particle, g_Mat_DustPuff[random->RandomInt(0,1)] ); + } +} + + +class C_TEDust: public C_TEParticleSystem +{ +public: + DECLARE_CLASS( C_TEDust, C_TEParticleSystem ); + DECLARE_CLIENTCLASS(); + + C_TEDust(); + virtual ~C_TEDust(); + +public: + virtual void PostDataUpdate( DataUpdateType_t updateType ); + virtual bool ShouldDraw() { return true; } + +public: + + float m_flSize; + float m_flSpeed; + Vector m_vecDirection; + +protected: + void GetDustColor( Vector &color ); +}; + +IMPLEMENT_CLIENTCLASS_EVENT_DT( C_TEDust, DT_TEDust, CTEDust ) + RecvPropFloat(RECVINFO(m_flSize)), + RecvPropFloat(RECVINFO(m_flSpeed)), + RecvPropVector(RECVINFO(m_vecDirection)), +END_RECV_TABLE() + +//================================================== +// C_TEDust +//================================================== + +C_TEDust::C_TEDust() +{ +} + +C_TEDust::~C_TEDust() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bNewEntity - whether or not to start a new entity +//----------------------------------------------------------------------------- +void C_TEDust::PostDataUpdate( DataUpdateType_t updateType ) +{ + FX_Dust( m_vecOrigin, m_vecDirection, m_flSize, m_flSpeed ); +} + + +void TE_Dust( IRecipientFilter& filter, float delay, + const Vector &pos, const Vector &dir, float size, float speed ) +{ + FX_Dust( pos, dir, size, speed ); +} diff --git a/game/client/c_func_dust.h b/game/client/c_func_dust.h new file mode 100644 index 00000000..3358c999 --- /dev/null +++ b/game/client/c_func_dust.h @@ -0,0 +1,112 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_FUNC_DUST_H +#define C_FUNC_DUST_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "c_baseentity.h" +#include "particles_simple.h" +#include "particle_util.h" +#include "bspflags.h" + + + +// ------------------------------------------------------------------------------------ // +// CDustEffect particle renderer. +// ------------------------------------------------------------------------------------ // + +class C_Func_Dust; + +class CFuncDustParticle : public Particle +{ +public: + Vector m_vVelocity; + float m_flLifetime; + float m_flDieTime; + float m_flSize; + color32 m_Color; +}; + +class CDustEffect : public CParticleEffect +{ +public: + CDustEffect( const char *pDebugName ) : CParticleEffect( pDebugName ) {} + + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + C_Func_Dust *m_pDust; + +private: + CDustEffect( const CDustEffect & ); // not defined, not accessible +}; + + +// ------------------------------------------------------------------------------------ // +// C_Func_Dust class. +// ------------------------------------------------------------------------------------ // + +class C_Func_Dust : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_Func_Dust, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_Func_Dust(); + virtual ~C_Func_Dust(); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink(); + virtual bool ShouldDraw(); + + +private: + + void AttemptSpawnNewParticle(); + + + +// Vars from server. +public: + + color32 m_Color; + int m_SpawnRate; + + float m_flSizeMin; + float m_flSizeMax; + + int m_SpeedMax; + + int m_LifetimeMin; + int m_LifetimeMax; + + int m_DistMax; + + float m_FallSpeed; // extra 'gravity' + + +public: + + int m_DustFlags; // Combination of DUSTFLAGS_ + + + +public: + CDustEffect m_Effect; + PMaterialHandle m_hMaterial; + TimedEvent m_Spawner; + +private: + C_Func_Dust( const C_Func_Dust & ); // not defined, not accessible +}; + + + +#endif // C_FUNC_DUST_H diff --git a/game/client/c_func_lod.cpp b/game/client/c_func_lod.cpp new file mode 100644 index 00000000..2ad1ebed --- /dev/null +++ b/game/client/c_func_lod.cpp @@ -0,0 +1,67 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "view.h" +#include "iviewrender.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_Func_LOD : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_Func_LOD, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_Func_LOD(); + +// C_BaseEntity overrides. +public: + + unsigned char GetClientSideFade(); + +public: +// Replicated vars from the server. +// These are documented in the server-side entity. +public: + float m_fDisappearDist; +}; + + +ConVar lod_TransitionDist("lod_TransitionDist", "800"); + + +// ------------------------------------------------------------------------- // +// Tables. +// ------------------------------------------------------------------------- // + +// Datatable.. +IMPLEMENT_CLIENTCLASS_DT(C_Func_LOD, DT_Func_LOD, CFunc_LOD) + RecvPropFloat(RECVINFO(m_fDisappearDist)), +END_RECV_TABLE() + + + +// ------------------------------------------------------------------------- // +// C_Func_LOD implementation. +// ------------------------------------------------------------------------- // + +C_Func_LOD::C_Func_LOD() +{ + m_fDisappearDist = 5000.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate a fade. +//----------------------------------------------------------------------------- +unsigned char C_Func_LOD::GetClientSideFade() +{ + return UTIL_ComputeEntityFade( this, m_fDisappearDist, m_fDisappearDist + lod_TransitionDist.GetFloat(), 1.0f ); +} + + diff --git a/game/client/c_func_occluder.cpp b/game/client/c_func_occluder.cpp new file mode 100644 index 00000000..3f885d96 --- /dev/null +++ b/game/client/c_func_occluder.cpp @@ -0,0 +1,51 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_FuncOccluder : public C_BaseEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_FuncOccluder, C_BaseEntity ); + +// Overrides. +public: + virtual bool ShouldDraw(); + virtual int DrawModel( int flags ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + +private: + int m_nOccluderIndex; + bool m_bActive; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_FuncOccluder, DT_FuncOccluder, CFuncOccluder ) + RecvPropBool( RECVINFO( m_bActive ) ), + RecvPropInt( RECVINFO(m_nOccluderIndex) ), +END_RECV_TABLE() + + +void C_FuncOccluder::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + engine->ActivateOccluder( m_nOccluderIndex, m_bActive ); +} + +bool C_FuncOccluder::ShouldDraw() +{ + return false; +} + +int C_FuncOccluder::DrawModel( int flags ) +{ + Assert(0); + return 0; +} diff --git a/game/client/c_func_reflective_glass.cpp b/game/client/c_func_reflective_glass.cpp new file mode 100644 index 00000000..211a38e1 --- /dev/null +++ b/game/client/c_func_reflective_glass.cpp @@ -0,0 +1,118 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "view_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_FuncReflectiveGlass : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_FuncReflectiveGlass, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + +// C_BaseEntity. +public: + C_FuncReflectiveGlass(); + virtual ~C_FuncReflectiveGlass(); + + virtual bool ShouldDraw(); + + C_FuncReflectiveGlass *m_pNext; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_FuncReflectiveGlass, DT_FuncReflectiveGlass, CFuncReflectiveGlass ) +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +C_EntityClassList g_ReflectiveGlassList; +C_FuncReflectiveGlass *C_EntityClassList::m_pClassList = NULL; + +C_FuncReflectiveGlass* GetReflectiveGlassList() +{ + return g_ReflectiveGlassList.m_pClassList; +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +C_FuncReflectiveGlass::C_FuncReflectiveGlass() +{ + g_ReflectiveGlassList.Insert( this ); +} + +C_FuncReflectiveGlass::~C_FuncReflectiveGlass() +{ + g_ReflectiveGlassList.Remove( this ); +} + + +bool C_FuncReflectiveGlass::ShouldDraw() +{ + return true; +} + + +//----------------------------------------------------------------------------- +// Do we have reflective glass in view? +//----------------------------------------------------------------------------- +bool IsReflectiveGlassInView( const CViewSetup& view, cplane_t &plane ) +{ + // Early out if no cameras + C_FuncReflectiveGlass *pReflectiveGlass = GetReflectiveGlassList(); + if ( !pReflectiveGlass ) + return false; + + Frustum_t frustum; + GeneratePerspectiveFrustum( view.origin, view.angles, view.zNear, view.zFar, view.fov, view.m_flAspectRatio, frustum ); + + cplane_t localPlane; + Vector vecOrigin, vecWorld, vecDelta, vecForward; + AngleVectors( view.angles, &vecForward, NULL, NULL ); + + for ( ; pReflectiveGlass != NULL; pReflectiveGlass = pReflectiveGlass->m_pNext ) + { + if ( pReflectiveGlass->IsDormant() ) + continue; + + Vector vecMins, vecMaxs; + pReflectiveGlass->GetRenderBoundsWorldspace( vecMins, vecMaxs ); + if ( R_CullBox( vecMins, vecMaxs, frustum ) ) + continue; + + const model_t *pModel = pReflectiveGlass->GetModel(); + const matrix3x4_t& mat = pReflectiveGlass->EntityToWorldTransform(); + + int nCount = modelinfo->GetBrushModelPlaneCount( pModel ); + for ( int i = 0; i < nCount; ++i ) + { + modelinfo->GetBrushModelPlane( pModel, i, localPlane, &vecOrigin ); + + MatrixTransformPlane( mat, localPlane, plane ); // Transform to world space + VectorTransform( vecOrigin, mat, vecWorld ); + + if ( view.origin.Dot( plane.normal ) <= plane.dist ) // Check for view behind plane + continue; + + VectorSubtract( vecWorld, view.origin, vecDelta ); // Backface cull + if ( vecDelta.Dot( plane.normal ) >= 0 ) + continue; + + return true; + } + } + + return false; +} + + + diff --git a/game/client/c_func_reflective_glass.h b/game/client/c_func_reflective_glass.h new file mode 100644 index 00000000..fb8f5be8 --- /dev/null +++ b/game/client/c_func_reflective_glass.h @@ -0,0 +1,27 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef C_FUNC_REFLECTIVE_GLASS +#define C_FUNC_REFLECTIVE_GLASS + +#ifdef _WIN32 +#pragma once +#endif + +struct cplane_t; +class CViewSetup; + + +//----------------------------------------------------------------------------- +// Do we have reflective glass in view? If so, what's the reflection plane? +//----------------------------------------------------------------------------- +bool IsReflectiveGlassInView( const CViewSetup& view, cplane_t &plane ); + + +#endif // C_FUNC_REFLECTIVE_GLASS + + diff --git a/game/client/c_func_rotating.cpp b/game/client/c_func_rotating.cpp new file mode 100644 index 00000000..ef6726f8 --- /dev/null +++ b/game/client/c_func_rotating.cpp @@ -0,0 +1,41 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_FuncRotating : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_FuncRotating, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_FuncRotating(); + +private: +}; + +extern void RecvProxy_SimulationTime( const CRecvProxyData *pData, void *pStruct, void *pOut ); + +IMPLEMENT_CLIENTCLASS_DT( C_FuncRotating, DT_FuncRotating, CFuncRotating ) + RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), + RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ), + RecvPropInt( RECVINFO(m_flSimulationTime), 0, RecvProxy_SimulationTime ), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_FuncRotating::C_FuncRotating() +{ +} diff --git a/game/client/c_func_smokevolume.cpp b/game/client/c_func_smokevolume.cpp new file mode 100644 index 00000000..25084150 --- /dev/null +++ b/game/client/c_func_smokevolume.cpp @@ -0,0 +1,626 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_smoke_trail.h" +#include "smoke_fog_overlay.h" +#include "engine/IEngineTrace.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define SF_EMISSIVE 0x00000001 + +// ------------------------------------------------------------------------- // +// Definitions +// ------------------------------------------------------------------------- // + +static Vector s_FadePlaneDirections[] = +{ + Vector( 1,0,0), + Vector(-1,0,0), + Vector(0, 1,0), + Vector(0,-1,0), + Vector(0,0, 1), + Vector(0,0,-1) +}; +#define NUM_FADE_PLANES (sizeof(s_FadePlaneDirections)/sizeof(s_FadePlaneDirections[0])) + +// ------------------------------------------------------------------------- // +// Classes +// ------------------------------------------------------------------------- // +class C_FuncSmokeVolume : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_FuncSmokeVolume, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_FuncSmokeVolume(); + ~C_FuncSmokeVolume(); + + int IsEmissive( void ) { return ( m_spawnflags & SF_EMISSIVE ); } + +private: + class SmokeGrenadeParticle : public Particle + { + public: + float m_RotationFactor; + float m_CurRotation; + float m_FadeAlpha; // Set as it moves around. + unsigned char m_ColorInterp; // Amount between min and max colors. + unsigned char m_Color[4]; + }; + +// C_BaseEntity. +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + +// IPrototypeAppEffect. +public: + virtual void Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ); + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + virtual void NotifyRemove(); + +private: + // The SmokeEmitter represents a grid in 3D space. + class SmokeParticleInfo + { + public: + SmokeGrenadeParticle *m_pParticle; + int m_TradeIndex; // -1 if not exchanging yet. + float m_TradeClock; // How long since they started trading. + float m_TradeDuration; // How long the trade will take to finish. + float m_FadeAlpha; // Calculated from nearby world geometry. + unsigned char m_Color[4]; + }; + + inline int GetSmokeParticleIndex(int x, int y, int z) + { + Assert( IsValidXYZCoords( x, y, z ) ); + return z*m_xCount*m_yCount+y*m_xCount+x; + } + + inline SmokeParticleInfo *GetSmokeParticleInfo(int x, int y, int z) + { + Assert( IsValidXYZCoords( x, y, z ) ); + return &m_pSmokeParticleInfos[GetSmokeParticleIndex(x,y,z)]; + } + + inline void GetParticleInfoXYZ(int index, int &x, int &y, int &z) + { + Assert( index >= 0 && index < m_xCount * m_yCount * m_zCount ); + z = index / (m_xCount*m_yCount); + int zIndex = z*m_xCount*m_yCount; + y = (index - zIndex) / m_xCount; + int yIndex = y*m_xCount; + x = index - zIndex - yIndex; + Assert( IsValidXYZCoords( x, y, z ) ); + } + + inline bool IsValidXYZCoords(int x, int y, int z) + { + return x >= 0 && y >= 0 && z >= 0 && x < m_xCount && y < m_yCount && z < m_zCount; + } + + inline Vector GetSmokeParticlePos(int x, int y, int z ) + { + return WorldAlignMins() + + Vector( x * m_SpacingRadius * 2 + m_SpacingRadius, + y * m_SpacingRadius * 2 + m_SpacingRadius, + z * m_SpacingRadius * 2 + m_SpacingRadius ); + } + + inline Vector GetSmokeParticlePosIndex(int index) + { + int x, y, z; + GetParticleInfoXYZ(index, x, y, z); + return GetSmokeParticlePos(x, y, z); + } + + // Start filling the smoke volume + void FillVolume(); + +private: +// State variables from server. + color32 m_Color1; + color32 m_Color2; + char m_MaterialName[255]; + float m_ParticleDrawWidth; + float m_ParticleSpacingDistance; + float m_DensityRampSpeed; + float m_RotationSpeed; + float m_MovementSpeed; + float m_Density; + int m_spawnflags; + +private: + C_FuncSmokeVolume( const C_FuncSmokeVolume & ); + + float m_CurrentDensity; + float m_ParticleRadius; + bool m_bStarted; + + PMaterialHandle m_MaterialHandle; + + SmokeParticleInfo *m_pSmokeParticleInfos; + int m_xCount, m_yCount, m_zCount; + float m_SpacingRadius; + + Vector m_MinColor; + Vector m_MaxColor; + + Vector m_vLastOrigin; + QAngle m_vLastAngles; + bool m_bFirstUpdate; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_FuncSmokeVolume, DT_FuncSmokeVolume, CFuncSmokeVolume ) + RecvPropInt( RECVINFO( m_Color1 ), 0, RecvProxy_IntToColor32 ), + RecvPropInt( RECVINFO( m_Color2 ), 0, RecvProxy_IntToColor32 ), + RecvPropString( RECVINFO( m_MaterialName ) ), + RecvPropFloat( RECVINFO( m_ParticleDrawWidth ) ), + RecvPropFloat( RECVINFO( m_ParticleSpacingDistance ) ), + RecvPropFloat( RECVINFO( m_DensityRampSpeed ) ), + RecvPropFloat( RECVINFO( m_RotationSpeed ) ), + RecvPropFloat( RECVINFO( m_MovementSpeed ) ), + RecvPropFloat( RECVINFO( m_Density ) ), + RecvPropInt( RECVINFO( m_spawnflags ) ), + RecvPropDataTable( RECVINFO_DT( m_Collision ), 0, &REFERENCE_RECV_TABLE(DT_CollisionProperty) ), +END_RECV_TABLE() + +// Helpers. +// ------------------------------------------------------------------------- // + +static inline void InterpColor(unsigned char dest[4], unsigned char src1[4], unsigned char src2[4], float percent) +{ + dest[0] = (unsigned char)(src1[0] + (src2[0] - src1[0]) * percent); + dest[1] = (unsigned char)(src1[1] + (src2[1] - src1[1]) * percent); + dest[2] = (unsigned char)(src1[2] + (src2[2] - src1[2]) * percent); +} + + +static inline int GetWorldPointContents(const Vector &vPos) +{ +#if defined(PARTICLEPROTOTYPE_APP) + return 0; +#else + return enginetrace->GetPointContents( vPos ); +#endif +} + +static inline void WorldTraceLine( const Vector &start, const Vector &end, int contentsMask, trace_t *trace ) +{ +#if defined(PARTICLEPROTOTYPE_APP) + trace->fraction = 1; +#else + UTIL_TraceLine(start, end, contentsMask, NULL, COLLISION_GROUP_NONE, trace); +#endif +} + +static inline Vector EngineGetLightForPoint(const Vector &vPos) +{ +#if defined(PARTICLEPROTOTYPE_APP) + return Vector(1,1,1); +#else + return engine->GetLightForPoint(vPos, true); +#endif +} + +static inline Vector& EngineGetVecRenderOrigin() +{ +#if defined(PARTICLEPROTOTYPE_APP) + static Vector dummy(0,0,0); + return dummy; +#else + extern Vector g_vecRenderOrigin; + return g_vecRenderOrigin; +#endif +} + +static inline float& EngineGetSmokeFogOverlayAlpha() +{ +#if defined(PARTICLEPROTOTYPE_APP) + static float dummy; + return dummy; +#else + return g_SmokeFogOverlayAlpha; +#endif +} + +static inline C_BaseEntity* ParticleGetEntity( int index ) +{ +#if defined(PARTICLEPROTOTYPE_APP) + return NULL; +#else + return cl_entitylist->GetEnt( index ); +#endif +} + +// ------------------------------------------------------------------------- // +// C_FuncSmokeVolume +// ------------------------------------------------------------------------- // +C_FuncSmokeVolume::C_FuncSmokeVolume() +{ + m_bFirstUpdate = true; + m_vLastOrigin.Init(); + m_vLastAngles.Init(); + + m_pSmokeParticleInfos = NULL; + m_SpacingRadius = 0.0f;; + m_ParticleRadius = 0.0f; + m_MinColor.Init( 1.0, 1.0, 1.0 ); + m_MaxColor.Init( 1.0, 1.0, 1.0 ); +} + +C_FuncSmokeVolume::~C_FuncSmokeVolume() +{ + delete [] m_pSmokeParticleInfos; +} + +void C_FuncSmokeVolume::OnDataChanged( DataUpdateType_t updateType ) +{ + m_MinColor[0] = ( 1.0f / 255.0f ) * m_Color1.r; + m_MinColor[1] = ( 1.0f / 255.0f ) * m_Color1.g; + m_MinColor[2] = ( 1.0f / 255.0f ) * m_Color1.b; + + m_MaxColor[0] = ( 1.0f / 255.0f ) * m_Color2.r; + m_MaxColor[1] = ( 1.0f / 255.0f ) * m_Color2.g; + m_MaxColor[2] = ( 1.0f / 255.0f ) * m_Color2.b; + + m_ParticleRadius = m_ParticleDrawWidth * 0.5f; + m_SpacingRadius = m_ParticleSpacingDistance * 0.5f; + + m_ParticleEffect.SetParticleCullRadius( m_ParticleRadius ); + +// Warning( "m_Density: %f\n", m_Density ); +// Warning( "m_MovementSpeed: %f\n", m_MovementSpeed ); + + if(updateType == DATA_UPDATE_CREATED) + { + Vector size = WorldAlignMaxs() - WorldAlignMins(); + m_xCount = 0.5f + ( size.x / ( m_SpacingRadius * 2.0f ) ); + m_yCount = 0.5f + ( size.y / ( m_SpacingRadius * 2.0f ) ); + m_zCount = 0.5f + ( size.z / ( m_SpacingRadius * 2.0f ) ); + m_CurrentDensity = m_Density; + + delete [] m_pSmokeParticleInfos; + m_pSmokeParticleInfos = new SmokeParticleInfo[m_xCount * m_yCount * m_zCount]; + Start( ParticleMgr(), NULL ); + } + BaseClass::OnDataChanged( updateType ); +} + +void C_FuncSmokeVolume::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + if( !pParticleMgr->AddEffect( &m_ParticleEffect, this ) ) + return; + + m_MaterialHandle = m_ParticleEffect.FindOrAddMaterial( m_MaterialName ); + FillVolume(); + + m_bStarted = true; +} + + +void C_FuncSmokeVolume::Update( float fTimeDelta ) +{ + // Update our world space bbox if we've moved at all. + // We do this manually because sometimes people make HUGE bboxes, and if they're constantly changing because their + // particles wander outside the current bounds sometimes, it'll be linking them into all the leaves repeatedly. + const Vector &curOrigin = GetAbsOrigin(); + const QAngle &curAngles = GetAbsAngles(); + if ( !VectorsAreEqual( curOrigin, m_vLastOrigin, 0.1 ) || + fabs( curAngles.x - m_vLastAngles.x ) > 0.1 || + fabs( curAngles.y - m_vLastAngles.y ) > 0.1 || + fabs( curAngles.z - m_vLastAngles.z ) > 0.1 || + m_bFirstUpdate ) + { + m_bFirstUpdate = false; + m_vLastAngles = curAngles; + m_vLastOrigin = curOrigin; + + Vector vWorldMins, vWorldMaxs; + CollisionProp()->WorldSpaceAABB( &vWorldMins, &vWorldMaxs ); + vWorldMins -= Vector( m_ParticleRadius, m_ParticleRadius, m_ParticleRadius ); + vWorldMaxs += Vector( m_ParticleRadius, m_ParticleRadius, m_ParticleRadius ); + + m_ParticleEffect.SetBBox( vWorldMins, vWorldMaxs ); + } + + // lerp m_CurrentDensity towards m_Density at a rate of m_DensityRampSpeed + if( m_CurrentDensity < m_Density ) + { + m_CurrentDensity += m_DensityRampSpeed * fTimeDelta; + if( m_CurrentDensity > m_Density ) + { + m_CurrentDensity = m_Density; + } + } + else if( m_CurrentDensity > m_Density ) + { + m_CurrentDensity -= m_DensityRampSpeed * fTimeDelta; + if( m_CurrentDensity < m_Density ) + { + m_CurrentDensity = m_Density; + } + } + + if( m_CurrentDensity == 0.0f ) + { + return; + } + + // This is used to randomize the direction it chooses to move a particle in. + + int offsetLookup[3] = {-1,0,1}; + + float tradeDurationMax = m_ParticleSpacingDistance / ( m_MovementSpeed + 0.1f ); + float tradeDurationMin = tradeDurationMax * 0.5f; + + if ( IS_NAN( tradeDurationMax ) || IS_NAN( tradeDurationMin ) ) + return; + +// Warning( "tradeDuration: [%f,%f]\n", tradeDurationMin, tradeDurationMax ); + + // Update all the moving traders and establish new ones. + int nTotal = m_xCount * m_yCount * m_zCount; + for( int i=0; i < nTotal; i++ ) + { + SmokeParticleInfo *pInfo = &m_pSmokeParticleInfos[i]; + + if(!pInfo->m_pParticle) + continue; + + if(pInfo->m_TradeIndex == -1) + { + pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha; + pInfo->m_pParticle->m_Color[0] = pInfo->m_Color[0]; + pInfo->m_pParticle->m_Color[1] = pInfo->m_Color[1]; + pInfo->m_pParticle->m_Color[2] = pInfo->m_Color[2]; + + // Is there an adjacent one that's not trading? + int x, y, z; + GetParticleInfoXYZ(i, x, y, z); + + int xCountOffset = rand(); + int yCountOffset = rand(); + int zCountOffset = rand(); + + bool bFound = false; + for(int xCount=0; xCount < 3 && !bFound; xCount++) + { + for(int yCount=0; yCount < 3 && !bFound; yCount++) + { + for(int zCount=0; zCount < 3; zCount++) + { + int testX = x + offsetLookup[(xCount+xCountOffset) % 3]; + int testY = y + offsetLookup[(yCount+yCountOffset) % 3]; + int testZ = z + offsetLookup[(zCount+zCountOffset) % 3]; + + if(testX == x && testY == y && testZ == z) + continue; + + if(IsValidXYZCoords(testX, testY, testZ)) + { + SmokeParticleInfo *pOther = GetSmokeParticleInfo(testX, testY, testZ); + if(pOther->m_pParticle && pOther->m_TradeIndex == -1) + { + // Ok, this one is looking to trade also. + pInfo->m_TradeIndex = GetSmokeParticleIndex(testX, testY, testZ); + pOther->m_TradeIndex = i; + pInfo->m_TradeClock = pOther->m_TradeClock = 0; + pOther->m_TradeDuration = pInfo->m_TradeDuration = FRand( tradeDurationMin, tradeDurationMax ); + + bFound = true; + break; + } + } + } + } + } + } + else + { + SmokeParticleInfo *pOther = &m_pSmokeParticleInfos[pInfo->m_TradeIndex]; + assert(pOther->m_TradeIndex == i); + + // This makes sure the trade only gets updated once per frame. + if(pInfo < pOther) + { + // Increment the trade clock.. + pInfo->m_TradeClock = (pOther->m_TradeClock += fTimeDelta); + int x, y, z; + GetParticleInfoXYZ(i, x, y, z); + Vector myPos = GetSmokeParticlePos(x, y, z); + + int otherX, otherY, otherZ; + GetParticleInfoXYZ(pInfo->m_TradeIndex, otherX, otherY, otherZ); + Vector otherPos = GetSmokeParticlePos(otherX, otherY, otherZ); + + // Is the trade finished? + if(pInfo->m_TradeClock >= pInfo->m_TradeDuration) + { + pInfo->m_TradeIndex = pOther->m_TradeIndex = -1; + + pInfo->m_pParticle->m_Pos = otherPos; + pOther->m_pParticle->m_Pos = myPos; + + SmokeGrenadeParticle *temp = pInfo->m_pParticle; + pInfo->m_pParticle = pOther->m_pParticle; + pOther->m_pParticle = temp; + } + else + { + // Ok, move them closer. + float percent = (float)cos(pInfo->m_TradeClock * 2 * 1.57079632f / pInfo->m_TradeDuration); + percent = percent * 0.5 + 0.5; + + pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * (1 - percent); + pOther->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * percent; + + InterpColor(pInfo->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, 1-percent); + InterpColor(pOther->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, percent); + + pInfo->m_pParticle->m_Pos = myPos + (otherPos - myPos) * (1 - percent); + pOther->m_pParticle->m_Pos = myPos + (otherPos - myPos) * percent; + } + } + } + } +} + + +void C_FuncSmokeVolume::RenderParticles( CParticleRenderIterator *pIterator ) +{ + if ( m_CurrentDensity == 0 ) + return; + + const SmokeGrenadeParticle *pParticle = (const SmokeGrenadeParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + Vector renderPos = pParticle->m_Pos; + + // Fade out globally. + float alpha = m_CurrentDensity; + + // Apply the precalculated fade alpha from world geometry. + alpha *= pParticle->m_FadeAlpha; + + // TODO: optimize this whole routine! + Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f); + if ( IsEmissive() ) + { + color.x += pParticle->m_Color[0] / 255.0f; + color.y += pParticle->m_Color[1] / 255.0f; + color.z += pParticle->m_Color[2] / 255.0f; + + color.x = clamp( color.x, 0.0f, 1.0f ); + color.y = clamp( color.y, 0.0f, 1.0f ); + color.z = clamp( color.z, 0.0f, 1.0f ); + } + else + { + color.x *= pParticle->m_Color[0] / 255.0f; + color.y *= pParticle->m_Color[1] / 255.0f; + color.z *= pParticle->m_Color[2] / 255.0f; + } + + Vector tRenderPos; + TransformParticle( ParticleMgr()->GetModelView(), renderPos, tRenderPos ); + float sortKey = 1;//tRenderPos.z; + + RenderParticle_ColorSizeAngle( + pIterator->GetParticleDraw(), + tRenderPos, + color, + alpha * GetAlphaDistanceFade(tRenderPos, 10, 30), // Alpha + m_ParticleRadius, + pParticle->m_CurRotation + ); + + pParticle = (const SmokeGrenadeParticle*)pIterator->GetNext( sortKey ); + } +} + + +void C_FuncSmokeVolume::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + if ( m_CurrentDensity == 0 ) + return; + + SmokeGrenadeParticle *pParticle = (SmokeGrenadeParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + pParticle->m_CurRotation += pParticle->m_RotationFactor * ( M_PI / 180.0f ) * m_RotationSpeed * pIterator->GetTimeDelta(); + pParticle = (SmokeGrenadeParticle*)pIterator->GetNext(); + } +} + + +void C_FuncSmokeVolume::NotifyRemove() +{ + m_xCount = m_yCount = m_zCount = 0; +} + + +void C_FuncSmokeVolume::FillVolume() +{ + Vector vPos; + for(int x=0; x < m_xCount; x++) + { + for(int y=0; y < m_yCount; y++) + { + for(int z=0; z < m_zCount; z++) + { + vPos = GetSmokeParticlePos( x, y, z ); + if(SmokeParticleInfo *pInfo = GetSmokeParticleInfo(x,y,z)) + { + int contents = GetWorldPointContents(vPos); + if(contents & CONTENTS_SOLID) + { + pInfo->m_pParticle = NULL; + } + else + { + SmokeGrenadeParticle *pParticle = + (SmokeGrenadeParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeGrenadeParticle), m_MaterialHandle); + + if(pParticle) + { + pParticle->m_Pos = vPos; + pParticle->m_ColorInterp = (unsigned char)((rand() * 255) / RAND_MAX); + pParticle->m_RotationFactor = FRand( -1.0f, 1.0f ); // Rotation factor. + pParticle->m_CurRotation = FRand( -m_RotationSpeed, m_RotationSpeed ); + } + +#ifdef _DEBUG + int testX, testY, testZ; + int index = GetSmokeParticleIndex(x,y,z); + GetParticleInfoXYZ(index, testX, testY, testZ); + assert(testX == x && testY == y && testZ == z); +#endif + + Vector vColor = EngineGetLightForPoint(vPos); + pInfo->m_Color[0] = LinearToTexture( vColor.x ); + pInfo->m_Color[1] = LinearToTexture( vColor.y ); + pInfo->m_Color[2] = LinearToTexture( vColor.z ); + + // Cast some rays and if it's too close to anything, fade its alpha down. + pInfo->m_FadeAlpha = 1; + + for(int i=0; i < NUM_FADE_PLANES; i++) + { + trace_t trace; + WorldTraceLine(vPos, vPos + s_FadePlaneDirections[i] * 100, MASK_SOLID_BRUSHONLY, &trace); + if(trace.fraction < 1.0f) + { + float dist = DotProduct(trace.plane.normal, vPos) - trace.plane.dist; + if(dist < 0) + { + pInfo->m_FadeAlpha = 0; + } + else if(dist < m_ParticleRadius) + { + float alphaScale = dist / m_ParticleRadius; + alphaScale *= alphaScale * alphaScale; + pInfo->m_FadeAlpha *= alphaScale; + } + } + } + + pInfo->m_pParticle = pParticle; + pInfo->m_TradeIndex = -1; + } + } + } + } + } +} diff --git a/game/client/c_func_tracktrain.cpp b/game/client/c_func_tracktrain.cpp new file mode 100644 index 00000000..d29aef56 --- /dev/null +++ b/game/client/c_func_tracktrain.cpp @@ -0,0 +1,122 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "c_baseentity.h" +#include "soundinfo.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// An entity which emits other entities at points +//----------------------------------------------------------------------------- +class C_FuncTrackTrain : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_FuncTrackTrain, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool GetSoundSpatialization( SpatializationInfo_t& info ); + + virtual bool IsBaseTrain( void ) const { return true; } + + +private: + int m_nLongAxis; + float m_flRadius; + float m_flLineLength; +}; + + +//----------------------------------------------------------------------------- +// Datatable +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_DT( C_FuncTrackTrain, DT_FuncTrackTrain, CFuncTrackTrain ) +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Sound spatialization +//----------------------------------------------------------------------------- +void C_FuncTrackTrain::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if (updateType == DATA_UPDATE_CREATED) + { + // Compute the cross-sectional area and dimension and length of the line segment + int nIndex1, nIndex2; + const Vector &vecOBBSize = CollisionProp()->OBBSize(); + if ( ( vecOBBSize.x > vecOBBSize.y ) && ( vecOBBSize.x > vecOBBSize.z ) ) + { + m_nLongAxis = 0; + nIndex1 = 1; nIndex2 = 2; + } + else if ( vecOBBSize.y > vecOBBSize.z ) + { + m_nLongAxis = 1; + nIndex1 = 0; nIndex2 = 2; + } + else + { + m_nLongAxis = 2; + nIndex1 = 0; nIndex2 = 1; + } + + m_flRadius = sqrt( vecOBBSize[nIndex1] * vecOBBSize[nIndex1] + vecOBBSize[nIndex2] * vecOBBSize[nIndex2] ) * 0.5f; + m_flLineLength = vecOBBSize[m_nLongAxis]; + } +} + + +//----------------------------------------------------------------------------- +// Sound spatialization +//----------------------------------------------------------------------------- +bool C_FuncTrackTrain::GetSoundSpatialization( SpatializationInfo_t& info ) +{ + // Out of PVS + if ( IsDormant() ) + return false; + + if ( info.pflRadius ) + { + *info.pflRadius = m_flRadius; + } + + if ( info.pOrigin ) + { + Vector vecStart, vecEnd, vecWorldDir; + Vector vecDir = vec3_origin; + vecDir[m_nLongAxis] = 1.0f; + VectorRotate( vecDir, EntityToWorldTransform(), vecWorldDir ); + VectorMA( WorldSpaceCenter(), -0.5f * m_flLineLength, vecWorldDir, vecStart ); + VectorMA( vecStart, m_flLineLength, vecWorldDir, vecEnd ); + + float t; + CalcClosestPointOnLine( info.info.vListenerOrigin, vecStart, vecEnd, *info.pOrigin, &t ); + if ( t < 0.0f ) + { + *info.pOrigin = vecStart; + } + else if ( t > 1.0f ) + { + *info.pOrigin = vecEnd; + } + } + + if ( info.pAngles ) + { + VectorCopy( CollisionProp()->GetCollisionAngles(), *info.pAngles ); + } + + return true; +} + + diff --git a/game/client/c_gib.cpp b/game/client/c_gib.cpp new file mode 100644 index 00000000..522a0f52 --- /dev/null +++ b/game/client/c_gib.cpp @@ -0,0 +1,133 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "vcollide_parse.h" +#include "c_gib.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//NOTENOTE: This is not yet coupled with the server-side implementation of CGib +// This is only a client-side version of gibs at the moment + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_Gib::~C_Gib( void ) +{ + VPhysicsDestroyObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszModelName - +// vecOrigin - +// vecForceDir - +// vecAngularImp - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +C_Gib *C_Gib::CreateClientsideGib( const char *pszModelName, Vector vecOrigin, Vector vecForceDir, AngularImpulse vecAngularImp, float flLifetime ) +{ + C_Gib *pGib = new C_Gib; + + if ( pGib == NULL ) + return NULL; + + if ( pGib->InitializeGib( pszModelName, vecOrigin, vecForceDir, vecAngularImp, flLifetime ) == false ) + return NULL; + + return pGib; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszModelName - +// vecOrigin - +// vecForceDir - +// vecAngularImp - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_Gib::InitializeGib( const char *pszModelName, Vector vecOrigin, Vector vecForceDir, AngularImpulse vecAngularImp, float flLifetime ) +{ + if ( InitializeAsClientEntity( pszModelName, RENDER_GROUP_OPAQUE_ENTITY ) == false ) + { + Release(); + return false; + } + + SetAbsOrigin( vecOrigin ); + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + + solid_t tmpSolid; + PhysModelParseSolid( tmpSolid, this, GetModelIndex() ); + + m_pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid ); + + if ( m_pPhysicsObject ) + { + float flForce = m_pPhysicsObject->GetMass(); + vecForceDir *= flForce; + + m_pPhysicsObject->ApplyForceOffset( vecForceDir, GetAbsOrigin() ); + m_pPhysicsObject->SetCallbackFlags( m_pPhysicsObject->GetCallbackFlags() | CALLBACK_GLOBAL_TOUCH | CALLBACK_GLOBAL_TOUCH_STATIC ); + } + else + { + // failed to create a physics object + Release(); + return false; + } + + SetNextClientThink( gpGlobals->curtime + flLifetime ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Gib::ClientThink( void ) +{ + SetRenderMode( kRenderTransAlpha ); + m_nRenderFX = kRenderFxFadeFast; + + if ( m_clrRender->a == 0 ) + { +#ifdef HL2_CLIENT_DLL + s_AntlionGibManager.RemoveGib( this ); +#endif + Release(); + return; + } + + SetNextClientThink( gpGlobals->curtime + 1.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void C_Gib::StartTouch( C_BaseEntity *pOther ) +{ + // Limit the amount of times we can bounce + if ( m_flTouchDelta < gpGlobals->curtime ) + { + HitSurface( pOther ); + m_flTouchDelta = gpGlobals->curtime + 0.1f; + } + + BaseClass::StartTouch( pOther ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void C_Gib::HitSurface( C_BaseEntity *pOther ) +{ + //TODO: Implement splatter or effects in child versions +} diff --git a/game/client/c_gib.h b/game/client/c_gib.h new file mode 100644 index 00000000..6e000056 --- /dev/null +++ b/game/client/c_gib.h @@ -0,0 +1,64 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_GIB_H +#define C_GIB_H +#ifdef _WIN32 +#pragma once +#endif + +#define DEFAULT_GIB_LIFETIME 4.0f + +// Base client gibs + +class C_Gib : public C_BaseAnimating +{ + typedef C_BaseAnimating BaseClass; +public: + + C_Gib::~C_Gib( void ); + + static C_Gib *CreateClientsideGib( const char *pszModelName, Vector vecOrigin, Vector vecForceDir, AngularImpulse vecAngularImp, float flLifetime = DEFAULT_GIB_LIFETIME ); + + bool InitializeGib( const char *pszModelName, Vector vecOrigin, Vector vecForceDir, AngularImpulse vecAngularImp, float flLifetime = DEFAULT_GIB_LIFETIME ); + void ClientThink( void ); + void StartTouch( C_BaseEntity *pOther ); + + virtual void HitSurface( C_BaseEntity *pOther ); + +protected: + + float m_flTouchDelta; // Amount of time that must pass before another touch function can be called +}; + +#ifdef HL2_CLIENT_DLL +class CAntlionGibManager : public CAutoGameSystemPerFrame +{ +public: + CAntlionGibManager( char const *name ) : CAutoGameSystemPerFrame( name ) + { + } + + // Methods of IGameSystem + virtual void Update( float frametime ); + virtual void LevelInitPreEntity( void ); + + void AddGib( C_BaseEntity *pEntity ); + void RemoveGib( C_BaseEntity *pEntity ); + +private: + typedef CHandle CGibHandle; + CUtlLinkedList< CGibHandle > m_LRU; + +}; + + +extern CAntlionGibManager s_AntlionGibManager; + +#endif + + +#endif // C_GIB_H diff --git a/game/client/c_hairball.cpp b/game/client/c_hairball.cpp new file mode 100644 index 00000000..5bfa515a --- /dev/null +++ b/game/client/c_hairball.cpp @@ -0,0 +1,350 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "simple_physics.h" +#include "mathlib/vmatrix.h" +#include "beamdraw.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_Hairball : public C_BaseEntity +{ + DECLARE_CLASS( C_Hairball, C_BaseEntity ); +private: + + class CHairballDelegate : public CSimplePhysics::IHelper + { + public: + virtual void GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ); + virtual void ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ); + + C_Hairball *m_pParent; + }; + + +public: + + C_Hairball(); + + void Init(); + + +// IClientThinkable. +public: + + virtual void ClientThink(); + + +// IClientRenderable. +public: + + virtual int DrawModel( int flags ); + + + +public: + + float m_flSphereRadius; + + int m_nHairs; + int m_nNodesPerHair; + float m_flSpringDist; // = hair length / (m_nNodesPerHair-1) + + CUtlVector m_Nodes; // This is m_nHairs * m_nNodesPerHair large. + CUtlVector m_HairPositions; // Untransformed base hair positions, distributed on the sphere. + CUtlVector m_TransformedHairPositions; // Transformed base hair positions, distributed on the sphere. + + CHairballDelegate m_Delegate; + CSimplePhysics m_Physics; + + IMaterial *m_pMaterial; + + + // Super sophisticated AI. + float m_flSitStillTime; + Vector m_vMoveDir; + + float m_flSpinDuration; + float m_flCurSpinTime; + float m_flSpinRateX, m_flSpinRateY; + + bool m_bFirstThink; +}; + + +void C_Hairball::CHairballDelegate::GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ) +{ + pAccel->Init( 0, 0, -1500 ); +} + + +void C_Hairball::CHairballDelegate::ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ) +{ + int nSegments = m_pParent->m_nNodesPerHair - 1; + float flSpringDistSqr = m_pParent->m_flSpringDist * m_pParent->m_flSpringDist; + + static int nIterations = 1; + for( int iIteration=0; iIteration < nIterations; iIteration++ ) + { + for ( int iHair=0; iHair < m_pParent->m_nHairs; iHair++ ) + { + CSimplePhysics::CNode *pBase = &pNodes[iHair * m_pParent->m_nNodesPerHair]; + + for( int i=0; i < nSegments; i++ ) + { + Vector &vNode1 = pBase[i].m_vPos; + Vector &vNode2 = pBase[i+1].m_vPos; + Vector vTo = vNode1 - vNode2; + + float flDistSqr = vTo.LengthSqr(); + if( flDistSqr > flSpringDistSqr ) + { + float flDist = (float)sqrt( flDistSqr ); + vTo *= 1 - (m_pParent->m_flSpringDist / flDist); + + vNode1 -= vTo * 0.5f; + vNode2 += vTo * 0.5f; + } + } + + // Lock the base of each hair to the right spot. + pBase->m_vPos = m_pParent->m_TransformedHairPositions[iHair]; + } + } +} + + +C_Hairball::C_Hairball() +{ + m_nHairs = 100; + m_nNodesPerHair = 3; + + float flHairLength = 20; + m_flSpringDist = flHairLength / (m_nNodesPerHair - 1); + + m_Nodes.SetSize( m_nHairs * m_nNodesPerHair ); + m_HairPositions.SetSize( m_nHairs ); + m_TransformedHairPositions.SetSize( m_nHairs ); + + m_flSphereRadius = 20; + m_vMoveDir.Init(); + + m_flSpinDuration = 1; + m_flCurSpinTime = 0; + m_flSpinRateX = m_flSpinRateY = 0; + + // Distribute on the sphere (need a better random distribution for the sphere). + for ( int i=0; i < m_HairPositions.Count(); i++ ) + { + float theta = RandomFloat( -M_PI, M_PI ); + float phi = RandomFloat( -M_PI/2, M_PI/2 ); + + float cosPhi = cos( phi ); + + m_HairPositions[i].Init( + cos(theta) * cosPhi * m_flSphereRadius, + sin(theta) * cosPhi * m_flSphereRadius, + sin(phi) * m_flSphereRadius ); + } + + m_Delegate.m_pParent = this; + + m_Physics.Init( 1.0 / 20 ); // NOTE: PLAY WITH THIS FOR EFFICIENCY + m_pMaterial = NULL; + + m_bFirstThink = true; +} + + +void C_Hairball::Init() +{ + ClientEntityList().AddNonNetworkableEntity( this ); + ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS ); + + AddToLeafSystem( RENDER_GROUP_OPAQUE_ENTITY ); + + m_pMaterial = materials->FindMaterial( "cable/cable", TEXTURE_GROUP_OTHER ); + m_flSitStillTime = 5; +} + + +void C_Hairball::ClientThink() +{ + // Do some AI-type stuff.. move the entity around. + //C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + //m_vecAngles = SetAbsAngles( pPlayer->GetAbsAngles() ); // copy player angles. + + Assert( !GetMoveParent() ); + + // Sophisticated AI. + m_flCurSpinTime += gpGlobals->frametime; + if ( m_flCurSpinTime < m_flSpinDuration ) + { + float div = m_flCurSpinTime / m_flSpinDuration; + + QAngle angles = GetLocalAngles(); + + angles.x += m_flSpinRateX * SmoothCurve( div ); + angles.y += m_flSpinRateY * SmoothCurve( div ); + + SetLocalAngles( angles ); + } + else + { + // Flip between stopped and starting. + if ( fabs( m_flSpinRateX ) > 0.01f ) + { + m_flSpinRateX = m_flSpinRateY = 0; + + m_flSpinDuration = RandomFloat( 1, 2 ); + } + else + { + static float flXSpeed = 3; + static float flYSpeed = flXSpeed * 0.1f; + m_flSpinRateX = RandomFloat( -M_PI*flXSpeed, M_PI*flXSpeed ); + m_flSpinRateY = RandomFloat( -M_PI*flYSpeed, M_PI*flYSpeed ); + + m_flSpinDuration = RandomFloat( 1, 4 ); + } + + m_flCurSpinTime = 0; + } + + + if ( m_flSitStillTime > 0 ) + { + m_flSitStillTime -= gpGlobals->frametime; + + if ( m_flSitStillTime <= 0 ) + { + // Shoot out some random lines and find the longest one. + m_vMoveDir.Init( 1, 0, 0 ); + float flLongestFraction = 0; + for ( int i=0; i < 15; i++ ) + { + Vector vDir( RandomFloat( -1, 1 ), RandomFloat( -1, 1 ), RandomFloat( -1, 1 ) ); + VectorNormalize( vDir ); + + trace_t trace; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vDir * 10000, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &trace ); + + if ( trace.fraction != 1.0 ) + { + if ( trace.fraction > flLongestFraction ) + { + flLongestFraction = trace.fraction; + m_vMoveDir = vDir; + } + } + } + + m_vMoveDir *= 650; // set speed. + m_flSitStillTime = -1; // Move in this direction.. + } + } + else + { + // Move in the specified direction. + Vector vEnd = GetAbsOrigin() + m_vMoveDir * gpGlobals->frametime; + + trace_t trace; + UTIL_TraceLine( GetAbsOrigin(), vEnd, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &trace ); + + if ( trace.fraction < 1 ) + { + // Ok, stop moving. + m_flSitStillTime = RandomFloat( 1, 3 ); + } + else + { + SetLocalOrigin( GetLocalOrigin() + m_vMoveDir * gpGlobals->frametime ); + } + } + + + // Transform the base hair positions so we can lock them down. + VMatrix mTransform; + mTransform.SetupMatrixOrgAngles( GetLocalOrigin(), GetLocalAngles() ); + + for ( int i=0; i < m_HairPositions.Count(); i++ ) + { + Vector3DMultiplyPosition( mTransform, m_HairPositions[i], m_TransformedHairPositions[i] ); + } + + if ( m_bFirstThink ) + { + m_bFirstThink = false; + for ( int i=0; i < m_HairPositions.Count(); i++ ) + { + for ( int j=0; j < m_nNodesPerHair; j++ ) + { + m_Nodes[i*m_nNodesPerHair+j].Init( m_TransformedHairPositions[i] ); + } + } + } + + // Simulate the physics and apply constraints. + m_Physics.Simulate( m_Nodes.Base(), m_Nodes.Count(), &m_Delegate, gpGlobals->frametime, 0.98 ); +} + + +int C_Hairball::DrawModel( int flags ) +{ + if ( !m_pMaterial ) + return 0; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + for ( int iHair=0; iHair < m_nHairs; iHair++ ) + { + CSimplePhysics::CNode *pBase = &m_Nodes[iHair * m_nNodesPerHair]; + + CBeamSegDraw beamDraw; + beamDraw.Start( pRenderContext, m_nNodesPerHair-1, m_pMaterial ); + + for ( int i=0; i < m_nNodesPerHair; i++ ) + { + BeamSeg_t seg; + seg.m_vPos = pBase[i].m_vPredicted; + seg.m_vColor.Init( 0, 0, 0 ); + seg.m_flTexCoord = 0; + static float flHairWidth = 1; + seg.m_flWidth = flHairWidth; + seg.m_flAlpha = 0; + + beamDraw.NextSeg( &seg ); + } + + beamDraw.End(); + } + + return 1; +} + + +void CreateHairballCallback() +{ + for ( int i=0; i < 20; i++ ) + { + C_Hairball *pHairball = new C_Hairball; + pHairball->Init(); + + // Put it a short distance in front of the player. + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( !pPlayer ) + return; + + Vector vForward; + AngleVectors( pPlayer->GetAbsAngles(), &vForward ); + pHairball->SetLocalOrigin( pPlayer->GetAbsOrigin() + vForward * 300 + RandomVector( 0, 100 ) ); + } +} + +ConCommand cc_CreateHairball( "CreateHairball", CreateHairballCallback, 0, FCVAR_CHEAT ); + diff --git a/game/client/c_impact_effects.cpp b/game/client/c_impact_effects.cpp new file mode 100644 index 00000000..52ca26d0 --- /dev/null +++ b/game/client/c_impact_effects.cpp @@ -0,0 +1,1414 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "fx.h" +#include "fx_sparks.h" +#include "ClientEffectPrecacheSystem.h" +#include "particle_simple3D.h" +#include "decals.h" +#include "engine/IEngineSound.h" +#include "c_te_particlesystem.h" +#include "engine/ivmodelinfo.h" +#include "particles_ez.h" +#include "c_impact_effects.h" +#include "engine/IStaticPropMgr.h" +#include "tier0/vprof.h" +#include "c_te_effect_dispatch.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//Precahce the effects +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectImpacts ) +CLIENTEFFECT_MATERIAL( "effects/fleck_cement1" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_cement2" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_antlion1" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_antlion2" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_wood1" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_wood2" ) +CLIENTEFFECT_MATERIAL( "effects/blood" ) +CLIENTEFFECT_MATERIAL( "effects/blood2" ) +CLIENTEFFECT_MATERIAL( "sprites/bloodspray" ) +CLIENTEFFECT_MATERIAL( "particle/particle_noisesphere" ) +CLIENTEFFECT_REGISTER_END() + +// Cached handles to commonly used materials +PMaterialHandle g_Mat_Fleck_Wood[2] = { NULL, NULL }; +PMaterialHandle g_Mat_Fleck_Cement[2] = { NULL, NULL }; +PMaterialHandle g_Mat_Fleck_Antlion[2] = { NULL, NULL }; +PMaterialHandle g_Mat_Fleck_Glass[2] = { NULL, NULL }; +PMaterialHandle g_Mat_Fleck_Tile[2] = { NULL, NULL }; +PMaterialHandle g_Mat_DustPuff[2] = { NULL, NULL }; +PMaterialHandle g_Mat_BloodPuff[2] = { NULL, NULL }; +PMaterialHandle g_Mat_SMG_Muzzleflash[4] = { NULL, NULL, NULL, NULL }; +PMaterialHandle g_Mat_Combine_Muzzleflash[3] = { NULL, NULL, NULL }; + +static ConVar fx_drawimpactdebris( "fx_drawimpactdebris", "1", FCVAR_DEVELOPMENTONLY, "Draw impact debris effects." ); +static ConVar fx_drawimpactdust( "fx_drawimpactdust", "1", FCVAR_DEVELOPMENTONLY, "Draw impact dust effects." ); + +void FX_CacheMaterialHandles( void ) +{ + g_Mat_Fleck_Wood[0] = ParticleMgr()->GetPMaterial( "effects/fleck_wood1" ); + g_Mat_Fleck_Wood[1] = ParticleMgr()->GetPMaterial( "effects/fleck_wood2" ); + + g_Mat_Fleck_Cement[0] = ParticleMgr()->GetPMaterial( "effects/fleck_cement1"); + g_Mat_Fleck_Cement[1] = ParticleMgr()->GetPMaterial( "effects/fleck_cement2" ); + + g_Mat_Fleck_Antlion[0] = ParticleMgr()->GetPMaterial( "effects/fleck_antlion1" ); + g_Mat_Fleck_Antlion[1] = ParticleMgr()->GetPMaterial( "effects/fleck_antlion2" ); + + g_Mat_Fleck_Glass[0] = ParticleMgr()->GetPMaterial( "effects/fleck_glass1" ); + g_Mat_Fleck_Glass[1] = ParticleMgr()->GetPMaterial( "effects/fleck_glass2" ); + + g_Mat_Fleck_Tile[0] = ParticleMgr()->GetPMaterial( "effects/fleck_tile1" ); + g_Mat_Fleck_Tile[1] = ParticleMgr()->GetPMaterial( "effects/fleck_tile2" ); + + g_Mat_DustPuff[0] = ParticleMgr()->GetPMaterial( "particle/particle_smokegrenade" ); + g_Mat_DustPuff[1] = ParticleMgr()->GetPMaterial( "particle/particle_noisesphere" ); + + g_Mat_BloodPuff[0] = ParticleMgr()->GetPMaterial( "effects/blood" ); + g_Mat_BloodPuff[1] = ParticleMgr()->GetPMaterial( "effects/blood2" ); + +#ifndef TF_CLIENT_DLL + g_Mat_SMG_Muzzleflash[0] = ParticleMgr()->GetPMaterial( "effects/muzzleflash1" ); + g_Mat_SMG_Muzzleflash[1] = ParticleMgr()->GetPMaterial( "effects/muzzleflash2" ); + g_Mat_SMG_Muzzleflash[2] = ParticleMgr()->GetPMaterial( "effects/muzzleflash3" ); + g_Mat_SMG_Muzzleflash[3] = ParticleMgr()->GetPMaterial( "effects/muzzleflash4" ); + + g_Mat_Combine_Muzzleflash[0] = ParticleMgr()->GetPMaterial( "effects/combinemuzzle1" ); + g_Mat_Combine_Muzzleflash[1] = ParticleMgr()->GetPMaterial( "effects/combinemuzzle2" ); + g_Mat_Combine_Muzzleflash[2] = ParticleMgr()->GetPMaterial( "effects/strider_muzzle" ); +#endif +} + +extern PMaterialHandle g_Material_Spark; + +//----------------------------------------------------------------------------- +// Purpose: Returns the color given trace information +// Input : *trace - trace to get results for +// *color - return color, gamma corrected (0.0f to 1.0f) +//----------------------------------------------------------------------------- +void GetColorForSurface( trace_t *trace, Vector *color ) +{ + Vector baseColor, diffuseColor; + Vector end = trace->startpos + ( ( Vector )trace->endpos - ( Vector )trace->startpos ) * 1.1f; + + if ( trace->DidHitWorld() ) + { + if ( trace->hitbox == 0 ) + { + // If we hit the world, then ask the world for the fleck color + engine->TraceLineMaterialAndLighting( trace->startpos, end, diffuseColor, baseColor ); + } + else + { + // In this case we hit a static prop. + staticpropmgr->GetStaticPropMaterialColorAndLighting( trace, trace->hitbox - 1, diffuseColor, baseColor ); + } + } + else + { + // In this case, we hit an entity. Find out the model associated with it + C_BaseEntity *pEnt = trace->m_pEnt; + if ( !pEnt ) + { + Msg("Couldn't find surface in GetColorForSurface()\n"); + color->x = 255; + color->y = 255; + color->z = 255; + return; + } + + ICollideable *pCollide = pEnt->GetCollideable(); + int modelIndex = pCollide->GetCollisionModelIndex(); + model_t* pModel = const_cast(modelinfo->GetModel( modelIndex )); + + // Ask the model info about what we need to know + modelinfo->GetModelMaterialColorAndLighting( pModel, pCollide->GetCollisionOrigin(), + pCollide->GetCollisionAngles(), trace, diffuseColor, baseColor ); + } + + //Get final light value + color->x = pow( diffuseColor[0], 1.0f/2.2f ) * baseColor[0]; + color->y = pow( diffuseColor[1], 1.0f/2.2f ) * baseColor[1]; + color->z = pow( diffuseColor[2], 1.0f/2.2f ) * baseColor[2]; +} + + +//----------------------------------------------------------------------------- +// This does the actual debris flecks +//----------------------------------------------------------------------------- +#define FLECK_MIN_SPEED 64.0f +#define FLECK_MAX_SPEED 128.0f +#define FLECK_GRAVITY 800.0f +#define FLECK_DAMPEN 0.3f +#define FLECK_ANGULAR_SPRAY 0.6f + +#ifndef _XBOX + +// +// PC ONLY! +// + +static void CreateFleckParticles( const Vector& origin, const Vector &color, trace_t *trace, char materialType, int iScale ) +{ + Vector spawnOffset = trace->endpos + ( trace->plane.normal * 1.0f ); + + CSmartPtr fleckEmitter = CFleckParticles::Create( "FX_DebrisFlecks", spawnOffset, Vector(5,5,5) ); + + if ( !fleckEmitter ) + return; + + // Handle increased scale + float flMaxSpeed = FLECK_MAX_SPEED * iScale; + float flAngularSpray = max( 0.2, FLECK_ANGULAR_SPRAY - ( (float)iScale * 0.2f) ); // More power makes the spray more controlled + // Setup our collision information + fleckEmitter->m_ParticleCollision.Setup( spawnOffset, &trace->plane.normal, flAngularSpray, FLECK_MIN_SPEED, flMaxSpeed, FLECK_GRAVITY, FLECK_DAMPEN ); + + PMaterialHandle *hMaterial; + switch ( materialType ) + { + case CHAR_TEX_WOOD: + hMaterial = g_Mat_Fleck_Wood; + break; + + case CHAR_TEX_CONCRETE: + case CHAR_TEX_TILE: + default: + hMaterial = g_Mat_Fleck_Cement; + break; + } + + Vector dir, end; + + float colorRamp; + + int numFlecks = random->RandomInt( 4, 16 ) * iScale; + + FleckParticle *pFleckParticle; + + //Dump out flecks + int i; + for ( i = 0; i < numFlecks; i++ ) + { + pFleckParticle = (FleckParticle *) fleckEmitter->AddParticle( sizeof(FleckParticle), hMaterial[random->RandomInt(0,1)], spawnOffset ); + + if ( pFleckParticle == NULL ) + break; + + pFleckParticle->m_flLifetime = 0.0f; + pFleckParticle->m_flDieTime = 3.0f; + + dir[0] = trace->plane.normal[0] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + dir[1] = trace->plane.normal[1] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + dir[2] = trace->plane.normal[2] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + + pFleckParticle->m_uchSize = random->RandomInt( 1, 2 ); + + pFleckParticle->m_vecVelocity = dir * ( random->RandomFloat( FLECK_MIN_SPEED, flMaxSpeed) * ( 3 - pFleckParticle->m_uchSize ) ); + + pFleckParticle->m_flRoll = random->RandomFloat( 0, 360 ); + pFleckParticle->m_flRollDelta = random->RandomFloat( 0, 360 ); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pFleckParticle->m_uchColor[0] = min( 1.0f, color[0]*colorRamp )*255.0f; + pFleckParticle->m_uchColor[1] = min( 1.0f, color[1]*colorRamp )*255.0f; + pFleckParticle->m_uchColor[2] = min( 1.0f, color[2]*colorRamp )*255.0f; + } +} + +#endif // _XBOX + +//----------------------------------------------------------------------------- +// Purpose: Debris flecks caused by impacts +// Input : origin - start +// *trace - trace information +// *materialName - material hit +// materialType - type of material hit +//----------------------------------------------------------------------------- +void FX_DebrisFlecks( const Vector& origin, trace_t *tr, char materialType, int iScale, bool bNoFlecks ) +{ + VPROF_BUDGET( "FX_DebrisFlecks", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + if ( !fx_drawimpactdebris.GetBool() ) + return; + +#ifdef _XBOX + + // + // XBox version + // + + Vector offset; + float spread = 0.2f; + + CSmartPtr pSimple = CDustParticle::Create( "dust" ); + pSimple->SetSortOrigin( origin ); + + // Lock the bbox + pSimple->GetBinding().SetBBox( origin - ( Vector( 16, 16, 16 ) * iScale ), origin + ( Vector( 16, 16, 16 ) * iScale ) ); + + // Get the color of the surface we've impacted + Vector color; + float colorRamp; + GetColorForSurface( tr, &color ); + + int i; + SimpleParticle *pParticle; + for ( i = 0; i < 4; i++ ) + { + if ( i == 3 ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[0], origin ); + } + else + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[0], origin ); + } + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += ( tr->plane.normal * random->RandomFloat( 1.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 250, 500 ) * i * 0.5f; + + // scaled + pParticle->m_vecVelocity *= fForce * iScale; + + // Ramp the color + colorRamp = random->RandomFloat( 0.5f, 1.25f ); + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + // scaled + pParticle->m_uchStartSize = (iScale*0.5f) * random->RandomInt( 3, 4 ) * (i+1); + + // scaled + pParticle->m_uchEndSize = (iScale*0.5f) * pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = random->RandomInt( 200, 255 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1.0f, 1.0f ); + } + } + + // Covers the impact spot with flecks + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_DustPuff2, origin ); + + if ( pParticle != NULL ) + { + offset = origin; + offset[0] += random->RandomFloat( -8.0f, 8.0f ); + offset[1] += random->RandomFloat( -8.0f, 8.0f ); + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + spread = 1.0f; + + pParticle->m_vecVelocity.Init(); + + colorRamp = random->RandomFloat( 0.5f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomInt( 4, 8 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = random->RandomInt( 64, 128 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -0.1f, 0.1f ); + } + +#else + + // + // PC version + // + + Vector color; + GetColorForSurface( tr, &color ); + + if ( !bNoFlecks ) + { + CreateFleckParticles( origin, color, tr, materialType, iScale ); + } + + // + // Dust trail + // + Vector offset = tr->endpos + ( tr->plane.normal * 2.0f ); + + SimpleParticle newParticle; + + int i; + for ( i = 0; i < 2; i++ ) + { + newParticle.m_Pos = offset; + + newParticle.m_flLifetime = 0.0f; + newParticle.m_flDieTime = 1.0f; + + Vector dir; + dir[0] = tr->plane.normal[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = tr->plane.normal[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = tr->plane.normal[2] + random->RandomFloat( -0.8f, 0.8f ); + + newParticle.m_uchStartSize = random->RandomInt( 2, 4 ) * iScale; + newParticle.m_uchEndSize = newParticle.m_uchStartSize * 8 * iScale; + + newParticle.m_vecVelocity = dir * random->RandomFloat( 2.0f, 24.0f )*(i+1); + newParticle.m_vecVelocity[2] -= random->RandomFloat( 8.0f, 32.0f )*(i+1); + + newParticle.m_uchStartAlpha = random->RandomInt( 100, 200 ); + newParticle.m_uchEndAlpha = 0; + + newParticle.m_flRoll = random->RandomFloat( 0, 360 ); + newParticle.m_flRollDelta = random->RandomFloat( -1, 1 ); + + float colorRamp = random->RandomFloat( 0.5f, 1.25f ); + + newParticle.m_uchColor[0] = min( 1.0f, color[0]*colorRamp )*255.0f; + newParticle.m_uchColor[1] = min( 1.0f, color[1]*colorRamp )*255.0f; + newParticle.m_uchColor[2] = min( 1.0f, color[2]*colorRamp )*255.0f; + + AddSimpleParticle( &newParticle, g_Mat_DustPuff[0] ); + } + + + for ( i = 0; i < 4; i++ ) + { + newParticle.m_Pos = offset; + + newParticle.m_flLifetime = 0.0f; + newParticle.m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + Vector dir; + dir[0] = tr->plane.normal[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = tr->plane.normal[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = tr->plane.normal[2] + random->RandomFloat( -0.8f, 0.8f ); + + newParticle.m_uchStartSize = random->RandomInt( 1, 4 ); + newParticle.m_uchEndSize = newParticle.m_uchStartSize * 4; + + newParticle.m_vecVelocity = dir * random->RandomFloat( 8.0f, 32.0f ); + newParticle.m_vecVelocity[2] -= random->RandomFloat( 8.0f, 64.0f ); + + newParticle.m_uchStartAlpha = 255; + newParticle.m_uchEndAlpha = 0; + + newParticle.m_flRoll = random->RandomFloat( 0, 360 ); + newParticle.m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + + float colorRamp = random->RandomFloat( 0.5f, 1.25f ); + + newParticle.m_uchColor[0] = min( 1.0f, color[0]*colorRamp )*255.0f; + newParticle.m_uchColor[1] = min( 1.0f, color[1]*colorRamp )*255.0f; + newParticle.m_uchColor[2] = min( 1.0f, color[2]*colorRamp )*255.0f; + + AddSimpleParticle( &newParticle, g_Mat_BloodPuff[0] ); + } + + // + // Bullet hole capper + // + newParticle.m_Pos = offset; + + newParticle.m_flLifetime = 0.0f; + newParticle.m_flDieTime = random->RandomFloat( 1.0f, 1.5f ); + + Vector dir; + dir[0] = tr->plane.normal[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = tr->plane.normal[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = tr->plane.normal[2] + random->RandomFloat( -0.8f, 0.8f ); + + newParticle.m_uchStartSize = random->RandomInt( 4, 8 ); + newParticle.m_uchEndSize = newParticle.m_uchStartSize * 4.0f; + + newParticle.m_vecVelocity = dir * random->RandomFloat( 2.0f, 24.0f ); + newParticle.m_vecVelocity[2] = random->RandomFloat( -2.0f, 2.0f ); + + newParticle.m_uchStartAlpha = random->RandomInt( 100, 200 ); + newParticle.m_uchEndAlpha = 0; + + newParticle.m_flRoll = random->RandomFloat( 0, 360 ); + newParticle.m_flRollDelta = random->RandomFloat( -2, 2 ); + + float colorRamp = random->RandomFloat( 0.5f, 1.25f ); + + newParticle.m_uchColor[0] = min( 1.0f, color[0]*colorRamp )*255.0f; + newParticle.m_uchColor[1] = min( 1.0f, color[1]*colorRamp )*255.0f; + newParticle.m_uchColor[2] = min( 1.0f, color[2]*colorRamp )*255.0f; + + AddSimpleParticle( &newParticle, g_Mat_DustPuff[0] ); + +#endif +} + +#define GLASS_SHARD_MIN_LIFE 2.5f +#define GLASS_SHARD_MAX_LIFE 5.0f +#define GLASS_SHARD_NOISE 0.8 +#define GLASS_SHARD_GRAVITY 800 +#define GLASS_SHARD_DAMPING 0.3 +#define GLASS_SHARD_MIN_SPEED 1 +#define GLASS_SHARD_MAX_SPEED 300 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void FX_GlassImpact( const Vector &pos, const Vector &normal ) +{ + VPROF_BUDGET( "FX_GlassImpact", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pGlassEmitter = CSimple3DEmitter::Create( "FX_GlassImpact" ); + pGlassEmitter->SetSortOrigin( pos ); + + Vector vecColor; + engine->ComputeLighting( pos, NULL, true, vecColor ); + + // HACK: Blend a little toward white to match the materials... + VectorLerp( vecColor, Vector( 1, 1, 1 ), 0.3, vecColor ); + + float flShardSize = random->RandomFloat( 2.0f, 6.0f ); + + unsigned char color[3] = { 200, 200, 210 }; + + // --------------------- + // Create glass shards + // ---------------------- + + int numShards = random->RandomInt( 2, 4 ); + + for ( int i = 0; i < numShards; i++ ) + { + Particle3D *pParticle; + + pParticle = (Particle3D *) pGlassEmitter->AddParticle( sizeof(Particle3D), g_Mat_Fleck_Glass[random->RandomInt(0,1)], pos ); + + if ( pParticle ) + { + pParticle->m_flLifeRemaining = random->RandomFloat(GLASS_SHARD_MIN_LIFE,GLASS_SHARD_MAX_LIFE); + + pParticle->m_vecVelocity[0] = ( normal[0] + random->RandomFloat( -0.8f, 0.8f ) ) * random->RandomFloat( GLASS_SHARD_MIN_SPEED, GLASS_SHARD_MAX_SPEED ); + pParticle->m_vecVelocity[1] = ( normal[1] + random->RandomFloat( -0.8f, 0.8f ) ) * random->RandomFloat( GLASS_SHARD_MIN_SPEED, GLASS_SHARD_MAX_SPEED ); + pParticle->m_vecVelocity[2] = ( normal[2] + random->RandomFloat( -0.8f, 0.8f ) ) * random->RandomFloat( GLASS_SHARD_MIN_SPEED, GLASS_SHARD_MAX_SPEED ); + + pParticle->m_uchSize = flShardSize + random->RandomFloat(-0.5*flShardSize,0.5*flShardSize); + pParticle->m_vAngles = RandomAngle( 0, 360 ); + pParticle->m_flAngSpeed = random->RandomFloat(-800,800); + + pParticle->m_uchFrontColor[0] = (byte)(color[0] * vecColor.x); + pParticle->m_uchFrontColor[1] = (byte)(color[1] * vecColor.y); + pParticle->m_uchFrontColor[2] = (byte)(color[2] * vecColor.z); + pParticle->m_uchBackColor[0] = (byte)(color[0] * vecColor.x); + pParticle->m_uchBackColor[1] = (byte)(color[1] * vecColor.y); + pParticle->m_uchBackColor[2] = (byte)(color[2] * vecColor.z); + } + } + + pGlassEmitter->m_ParticleCollision.Setup( pos, &normal, GLASS_SHARD_NOISE, GLASS_SHARD_MIN_SPEED, GLASS_SHARD_MAX_SPEED, GLASS_SHARD_GRAVITY, GLASS_SHARD_DAMPING ); + + color[0] = 64; + color[1] = 64; + color[2] = 92; + + // --------------------------- + // Dust + // --------------------------- + + Vector dir; + Vector offset = pos + ( normal * 2.0f ); + float colorRamp; + + SimpleParticle newParticle; + + for ( int i = 0; i < 4; i++ ) + { + newParticle.m_Pos = offset; + + newParticle.m_flLifetime= 0.0f; + newParticle.m_flDieTime = random->RandomFloat( 0.1f, 0.25f ); + + dir[0] = normal[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = normal[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = normal[2] + random->RandomFloat( -0.8f, 0.8f ); + + newParticle.m_uchStartSize = random->RandomInt( 1, 4 ); + newParticle.m_uchEndSize = newParticle.m_uchStartSize * 8; + + newParticle.m_vecVelocity = dir * random->RandomFloat( 8.0f, 16.0f )*(i+1); + newParticle.m_vecVelocity[2] -= random->RandomFloat( 16.0f, 32.0f )*(i+1); + + newParticle.m_uchStartAlpha = random->RandomInt( 128, 255 ); + newParticle.m_uchEndAlpha = 0; + + newParticle.m_flRoll = random->RandomFloat( 0, 360 ); + newParticle.m_flRollDelta = random->RandomFloat( -1, 1 ); + + colorRamp = random->RandomFloat( 0.5f, 1.25f ); + + newParticle.m_uchColor[0] = min( 1.0f, color[0]*colorRamp )*255.0f; + newParticle.m_uchColor[1] = min( 1.0f, color[1]*colorRamp )*255.0f; + newParticle.m_uchColor[2] = min( 1.0f, color[2]*colorRamp )*255.0f; + + AddSimpleParticle( &newParticle, g_Mat_BloodPuff[0] ); + } + + // + // Bullet hole capper + // + newParticle.m_Pos = offset; + + newParticle.m_flLifetime = 0.0f; + newParticle.m_flDieTime = random->RandomFloat( 1.0f, 1.5f ); + + dir[0] = normal[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = normal[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = normal[2] + random->RandomFloat( -0.8f, 0.8f ); + + newParticle.m_uchStartSize = random->RandomInt( 4, 8 ); + newParticle.m_uchEndSize = newParticle.m_uchStartSize * 4.0f; + + newParticle.m_vecVelocity = dir * random->RandomFloat( 2.0f, 8.0f ); + newParticle.m_vecVelocity[2] = random->RandomFloat( -2.0f, 2.0f ); + + newParticle.m_uchStartAlpha = random->RandomInt( 32, 64 ); + newParticle.m_uchEndAlpha = 0; + + newParticle.m_flRoll = random->RandomFloat( 0, 360 ); + newParticle.m_flRollDelta = random->RandomFloat( -2, 2 ); + + colorRamp = random->RandomFloat( 0.5f, 1.25f ); + + newParticle.m_uchColor[0] = min( 1.0f, color[0]*colorRamp )*255.0f; + newParticle.m_uchColor[1] = min( 1.0f, color[1]*colorRamp )*255.0f; + newParticle.m_uchColor[2] = min( 1.0f, color[2]*colorRamp )*255.0f; + + AddSimpleParticle( &newParticle, g_Mat_DustPuff[0] ); +} + +void GlassImpactCallback( const CEffectData &data ) +{ + FX_GlassImpact( data.m_vOrigin, data.m_vNormal ); +} + +DECLARE_CLIENT_EFFECT( "GlassImpact", GlassImpactCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &pos - +// *tr - +//----------------------------------------------------------------------------- +void FX_AntlionImpact( const Vector &pos, trace_t *trace ) +{ +#if defined( _X360 ) + return; +#endif // _X360 + + VPROF_BUDGET( "FX_AntlionImpact", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + CSmartPtr fleckEmitter = CSimple3DEmitter::Create( "FX_DebrisFlecks" ); + if ( fleckEmitter == NULL ) + return; + + Vector shotDir = ( trace->startpos - trace->endpos ); + VectorNormalize( shotDir ); + + Vector spawnOffset = trace->endpos + ( shotDir * 2.0f ); + + Vector vWorldMins, vWorldMaxs; + if ( trace->m_pEnt ) + { + float scale = trace->m_pEnt->CollisionProp()->BoundingRadius(); + vWorldMins[0] = spawnOffset[0] - scale; + vWorldMins[1] = spawnOffset[1] - scale; + vWorldMins[2] = spawnOffset[2] - scale; + vWorldMaxs[0] = spawnOffset[0] + scale; + vWorldMaxs[1] = spawnOffset[1] + scale; + vWorldMaxs[2] = spawnOffset[2] + scale; + } + else + { + return; + } + + fleckEmitter->SetSortOrigin( spawnOffset ); + fleckEmitter->GetBinding().SetBBox( spawnOffset-Vector(32,32,32), spawnOffset+Vector(32,32,32), true ); + + // Handle increased scale + float flMaxSpeed = 256.0f; + float flAngularSpray = 1.0f; + + // Setup our collision information + fleckEmitter->m_ParticleCollision.Setup( spawnOffset, &shotDir, flAngularSpray, 8.0f, flMaxSpeed, FLECK_GRAVITY, FLECK_DAMPEN ); + + Vector dir, end; + Vector color = Vector( 1, 0.9, 0.75 ); + float colorRamp; + + int numFlecks = random->RandomInt( 8, 16 ); + + Particle3D *pFleckParticle; + + // Dump out flecks + int i; + for ( i = 0; i < numFlecks; i++ ) + { + pFleckParticle = (Particle3D *) fleckEmitter->AddParticle( sizeof(Particle3D), g_Mat_Fleck_Antlion[random->RandomInt(0,1)], spawnOffset ); + if ( pFleckParticle == NULL ) + break; + + pFleckParticle->m_flLifeRemaining = 3.0f; + + dir[0] = shotDir[0] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + dir[1] = shotDir[1] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + dir[2] = shotDir[2] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + + pFleckParticle->m_uchSize = random->RandomInt( 1, 6 ); + + pFleckParticle->m_vecVelocity = dir * random->RandomFloat( FLECK_MIN_SPEED, flMaxSpeed); + pFleckParticle->m_vAngles.Random( 0, 360 ); + pFleckParticle->m_flAngSpeed = random->RandomFloat(-800,800); + + pFleckParticle->m_uchFrontColor[0] = 255; + pFleckParticle->m_uchFrontColor[1] = 255; + pFleckParticle->m_uchFrontColor[2] = 255; + + pFleckParticle->m_uchBackColor[0] = 128; + pFleckParticle->m_uchBackColor[1] = 128; + pFleckParticle->m_uchBackColor[2] = 128; + } + + // + // Dust trail + // + + SimpleParticle *pParticle; + + CSmartPtr dustEmitter = CSimpleEmitter::Create( "FX_DebrisFlecks" ); + if ( !dustEmitter ) + return; + + Vector offset = trace->endpos + ( shotDir * 4.0f ); + + dustEmitter->SetSortOrigin( offset ); + dustEmitter->GetBinding().SetBBox( spawnOffset-Vector(32,32,32), spawnOffset+Vector(32,32,32), true ); + + for ( i = 0; i < 4; i++ ) + { + pParticle = (SimpleParticle *) dustEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[0], offset ); + + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 1.0f; + + dir[0] = shotDir[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = shotDir[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = shotDir[2] + random->RandomFloat( -0.8f, 0.8f ); + + pParticle->m_uchStartSize = random->RandomInt( 8, 16 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4.0f; + + pParticle->m_vecVelocity = dir * random->RandomFloat( 4.0f, 64.0f ); + + pParticle->m_uchStartAlpha = random->RandomInt( 32, 64); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomFloat( 0, 2.0f*M_PI ); + pParticle->m_flRollDelta = random->RandomFloat( -0.5f, 0.5f ); + + colorRamp = random->RandomFloat( 0.5f, 1.0f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0]*colorRamp )*255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1]*colorRamp )*255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2]*colorRamp )*255.0f; + } + + + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, 0, "FX_AntlionImpact.ShellImpact", &trace->endpos ); +} + +//----------------------------------------------------------------------------- +// Purpose: Spurt out bug blood +// Input : &pos - +// &dir - +//----------------------------------------------------------------------------- +#if defined( _XBOX ) +#define NUM_BUG_BLOOD 16 +#define NUM_BUG_BLOOD2 8 +#define NUM_BUG_SPLATS 8 +#else +#define NUM_BUG_BLOOD 32 +#define NUM_BUG_BLOOD2 16 +#define NUM_BUG_SPLATS 16 +#endif +void FX_BugBlood( Vector &pos, Vector &dir, Vector &vWorldMins, Vector &vWorldMaxs ) +{ + VPROF_BUDGET( "FX_BugBlood", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + CSmartPtr pSimple = CSimpleEmitter::Create( "FX_BugBlood" ); + if ( !pSimple ) + return; + + pSimple->SetSortOrigin( pos ); + pSimple->GetBinding().SetBBox( vWorldMins, vWorldMaxs, true ); + pSimple->GetBinding().SetBBox( pos-Vector(32,32,32), pos+Vector(32,32,32), true ); + + Vector vDir; + vDir[0] = dir[0] + random->RandomFloat( -2.0f, 2.0f ); + vDir[1] = dir[1] + random->RandomFloat( -2.0f, 2.0f ); + vDir[2] = dir[2] + random->RandomFloat( -2.0f, 2.0f ); + + VectorNormalize( vDir ); + + int i; + for ( i = 0; i < NUM_BUG_BLOOD; i++ ) + { + SimpleParticle *sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[0], pos ); + + if ( sParticle == NULL ) + return; + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 0.25f; + + float speed = random->RandomFloat( 32.0f, 150.0f ); + + sParticle->m_vecVelocity = vDir * -speed; + sParticle->m_vecVelocity[2] -= 32.0f; + + sParticle->m_uchColor[0] = 255; + sParticle->m_uchColor[1] = 200; + sParticle->m_uchColor[2] = 32; + sParticle->m_uchStartAlpha = 255; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = random->RandomInt( 1, 2 ); + sParticle->m_uchEndSize = sParticle->m_uchStartSize*random->RandomInt( 1, 4 ); + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + } + + for ( i = 0; i < NUM_BUG_BLOOD2; i++ ) + { + SimpleParticle *sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[1], pos ); + + if ( sParticle == NULL ) + { + return; + } + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + float speed = random->RandomFloat( 8.0f, 255.0f ); + + sParticle->m_vecVelocity = vDir * -speed; + sParticle->m_vecVelocity[2] -= 16.0f; + + sParticle->m_uchColor[0] = 255; + sParticle->m_uchColor[1] = 200; + sParticle->m_uchColor[2] = 32; + sParticle->m_uchStartAlpha = random->RandomInt( 16, 32 ); + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = random->RandomInt( 1, 3 ); + sParticle->m_uchEndSize = sParticle->m_uchStartSize*random->RandomInt( 1, 4 ); + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + } + + Vector offset; + + for ( i = 0; i < NUM_BUG_SPLATS; i++ ) + { + offset.Random( -2, 2 ); + offset += pos; + + SimpleParticle *sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[1], offset ); + + if ( sParticle == NULL ) + { + return; + } + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + float speed = 75.0f * ((i/(float)NUM_BUG_SPLATS)+1); + + sParticle->m_vecVelocity.Random( -16.0f, 16.0f ); + + sParticle->m_vecVelocity += vDir * -speed; + sParticle->m_vecVelocity[2] -= ( 64.0f * ((i/(float)NUM_BUG_SPLATS)+1) ); + + sParticle->m_uchColor[0] = 255; + sParticle->m_uchColor[1] = 200; + sParticle->m_uchColor[2] = 32; + sParticle->m_uchStartAlpha = 255; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = random->RandomInt( 1, 2 ); + sParticle->m_uchEndSize = sParticle->m_uchStartSize*4; + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Blood puff +//----------------------------------------------------------------------------- +void FX_Blood( Vector &pos, Vector &dir, float r, float g, float b, float a ) +{ + VPROF_BUDGET( "FX_Blood", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + // Cloud + CSmartPtr pSimple = CSimpleEmitter::Create( "FX_Blood" ); + if ( !pSimple ) + return; + pSimple->SetSortOrigin( pos ); + + Vector vDir; + + vDir[0] = dir[0] + random->RandomFloat( -1.0f, 1.0f ); + vDir[1] = dir[1] + random->RandomFloat( -1.0f, 1.0f ); + vDir[2] = dir[2] + random->RandomFloat( -1.0f, 1.0f ); + + VectorNormalize( vDir ); + + int i; + for ( i = 0; i < 2; i++ ) + { + SimpleParticle *sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[0], pos ); + + if ( sParticle == NULL ) + { + return; + } + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + float speed = random->RandomFloat( 2.0f, 8.0f ); + + sParticle->m_vecVelocity = vDir * (speed*i); + sParticle->m_vecVelocity[2] += random->RandomFloat( -32.0f, -16.0f ); + + sParticle->m_uchColor[0] = r; + sParticle->m_uchColor[1] = g; + sParticle->m_uchColor[2] = b; + sParticle->m_uchStartAlpha = a; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = 2; + sParticle->m_uchEndSize = sParticle->m_uchStartSize*4; + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + } + + for ( i = 0; i < 2; i++ ) + { + SimpleParticle *sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[1], pos ); + + if ( sParticle == NULL ) + { + return; + } + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 0.5f; + + float speed = random->RandomFloat( 4.0f, 16.0f ); + + sParticle->m_vecVelocity = vDir * (speed*i); + + sParticle->m_uchColor[0] = r; + sParticle->m_uchColor[1] = g; + sParticle->m_uchColor[2] = b; + sParticle->m_uchStartAlpha = 128; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = 2; + sParticle->m_uchEndSize = sParticle->m_uchStartSize*4; + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Dust impact +// Input : &origin - position +// &tr - trace information +//----------------------------------------------------------------------------- +void FX_DustImpact( const Vector &origin, trace_t *tr, int iScale ) +{ + if ( !fx_drawimpactdust.GetBool() ) + return; + +#ifdef _XBOX + + // + // XBox version + // + + VPROF_BUDGET( "FX_DustImpact", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector offset; + float spread = 0.2f; + + CSmartPtr pSimple = CDustParticle::Create( "dust" ); + pSimple->SetSortOrigin( origin ); + pSimple->GetBinding().SetBBox( origin - ( Vector( 32, 32, 32 ) * iScale ), origin + ( Vector( 32, 32, 32 ) * iScale ) ); + + Vector color; + float colorRamp; + GetColorForSurface( tr, &color ); + + int i; + SimpleParticle *pParticle; + for ( i = 0; i < 4; i++ ) + { + // Last puff is gritty (hides end) + if ( i == 3 ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[0], origin ); + } + else + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[0], origin ); + } + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += ( tr->plane.normal * random->RandomFloat( 1.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 250, 500 ) * i; + + // scaled + pParticle->m_vecVelocity *= fForce * iScale; + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + // scaled + pParticle->m_uchStartSize = iScale * random->RandomInt( 3, 4 ) * (i+1); + + // scaled + pParticle->m_uchEndSize = iScale * pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = random->RandomInt( 32, 255 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + + if ( i == 3 ) + { + pParticle->m_flRollDelta = random->RandomFloat( -0.1f, 0.1f ); + pParticle->m_flDieTime = 0.5f; + } + else + { + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + } + } + } + + //Impact hit + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_DustPuff, origin ); + + if ( pParticle != NULL ) + { + offset = origin; + offset[0] += random->RandomFloat( -8.0f, 8.0f ); + offset[1] += random->RandomFloat( -8.0f, 8.0f ); + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + pParticle->m_vecVelocity.Init(); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomInt( 4, 8 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = random->RandomInt( 32, 64 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1.0f, 1.0f ); + } + +#else + FX_DustImpact( origin, tr, (float)iScale ); +#endif // _XBOX +} + +void FX_DustImpact( const Vector &origin, trace_t *tr, float flScale ) +{ + // + // PC version + // + + VPROF_BUDGET( "FX_DustImpact", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector offset; + float spread = 0.2f; + + CSmartPtr pSimple = CDustParticle::Create( "dust" ); + pSimple->SetSortOrigin( origin ); + + SimpleParticle *pParticle; + + Vector color; + float colorRamp; + + GetColorForSurface( tr, &color ); + + int i; + for ( i = 0; i < 4; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[0], origin ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += ( tr->plane.normal * random->RandomFloat( 1.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 250, 500 ) * i; + + // scaled + pParticle->m_vecVelocity *= fForce * flScale; + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + // scaled + pParticle->m_uchStartSize = ( unsigned char )( flScale * random->RandomInt( 3, 4 ) * (i+1) ); + + // scaled + pParticle->m_uchEndSize = ( unsigned char )( flScale * pParticle->m_uchStartSize * 4 ); + + pParticle->m_uchStartAlpha = random->RandomInt( 32, 255 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + } + } + + //Dust specs + for ( i = 0; i < 4; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_BloodPuff[0], origin ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.75f ); + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += ( tr->plane.normal * random->RandomFloat( 1.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 250, 500 ) * i; + + pParticle->m_vecVelocity *= fForce; + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomInt( 2, 4 ) * (i+1); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + } + } + + //Impact hit + for ( i = 0; i < 4; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[0], origin ); + + if ( pParticle != NULL ) + { + offset = origin; + offset[0] += random->RandomFloat( -8.0f, 8.0f ); + offset[1] += random->RandomFloat( -8.0f, 8.0f ); + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + spread = 1.0f; + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += tr->plane.normal; + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 0, 50 ); + + pParticle->m_vecVelocity *= fForce; + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomInt( 1, 4 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = random->RandomInt( 32, 64 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -16.0f, 16.0f ); + } + } +} + +#ifdef _XBOX +extern PMaterialHandle g_Material_Spark; +#endif // _XBOX + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &pos - +// &dir - +// type - +//----------------------------------------------------------------------------- +void FX_GaussExplosion( const Vector &pos, const Vector &dir, int type ) +{ + Vector vDir; + + vDir[0] = dir[0] + random->RandomFloat( -1.0f, 1.0f ); + vDir[1] = dir[1] + random->RandomFloat( -1.0f, 1.0f ); + vDir[2] = dir[2] + random->RandomFloat( -1.0f, 1.0f ); + + VectorNormalize( vDir ); + + int i; + +#if defined(_XBOX) || defined(_X360) + + // + // XBox version + // + CSmartPtr pSparkEmitter = CTrailParticles::Create( "FX_GaussExplosion" ); + if ( pSparkEmitter == NULL ) + { + Assert(0); + return; + } + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = pSparkEmitter->GetPMaterial( "effects/spark" ); + } + + pSparkEmitter->SetSortOrigin( pos ); + pSparkEmitter->m_ParticleCollision.SetGravity( 800.0f ); + pSparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + pSparkEmitter->GetBinding().SetBBox( pos - Vector( 32, 32, 32 ), pos + Vector( 32, 32, 32 ) ); + + int numSparks = random->RandomInt( 8, 16 ); + TrailParticle *pParticle; + + // Dump out sparks + for ( i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) pSparkEmitter->AddParticle( sizeof(TrailParticle), g_Material_Spark, pos ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + + vDir.Random( -0.6f, 0.6f ); + vDir += dir; + VectorNormalize( vDir ); + + pParticle->m_flWidth = random->RandomFloat( 1.0f, 4.0f ); + pParticle->m_flLength = random->RandomFloat( 0.01f, 0.1f ); + pParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + pParticle->m_vecVelocity = vDir * random->RandomFloat( 128, 512 ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + + // End cap + SimpleParticle particle; + + particle.m_Pos = pos; + particle.m_flLifetime = 0.0f; + particle.m_flDieTime = 0.1f; + particle.m_vecVelocity.Init(); + particle.m_flRoll = random->RandomInt( 0, 360 ); + particle.m_flRollDelta = 0.0f; + particle.m_uchColor[0] = 255; + particle.m_uchColor[1] = 255; + particle.m_uchColor[2] = 255; + particle.m_uchStartAlpha = 255; + particle.m_uchEndAlpha = 255; + particle.m_uchStartSize = random->RandomInt( 24, 32 ); + particle.m_uchEndSize = 0; + + AddSimpleParticle( &particle, ParticleMgr()->GetPMaterial( "effects/yellowflare" ) ); + +#else + + // + // PC version + // + CSmartPtr pSparkEmitter = CTrailParticles::Create( "FX_ElectricSpark" ); + + if ( !pSparkEmitter ) + { + Assert(0); + return; + } + + PMaterialHandle hMaterial = pSparkEmitter->GetPMaterial( "effects/spark" ); + + pSparkEmitter->SetSortOrigin( pos ); + + pSparkEmitter->m_ParticleCollision.SetGravity( 800.0f ); + pSparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN|bitsPARTICLE_TRAIL_COLLIDE ); + + //Setup our collision information + pSparkEmitter->m_ParticleCollision.Setup( pos, &vDir, 0.8f, 128, 512, 800, 0.3f ); + + int numSparks = random->RandomInt( 16, 32 ); + TrailParticle *pParticle; + + // Dump out sparks + for ( i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) pSparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, pos ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + + vDir.Random( -0.6f, 0.6f ); + vDir += dir; + VectorNormalize( vDir ); + + pParticle->m_flWidth = random->RandomFloat( 1.0f, 4.0f ); + pParticle->m_flLength = random->RandomFloat( 0.01f, 0.1f ); + pParticle->m_flDieTime = random->RandomFloat( 0.25f, 1.0f ); + + pParticle->m_vecVelocity = vDir * random->RandomFloat( 128, 512 ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + + + FX_ElectricSpark( pos, 1, 1, &vDir ); +#endif +} + +class C_TEGaussExplosion : public C_TEParticleSystem +{ +public: + DECLARE_CLASS( C_TEGaussExplosion, C_TEParticleSystem ); + DECLARE_CLIENTCLASS(); + + C_TEGaussExplosion(); + virtual ~C_TEGaussExplosion(); + +public: + virtual void PostDataUpdate( DataUpdateType_t updateType ); + virtual bool ShouldDraw() { return true; } + +public: + + int m_nType; + Vector m_vecDirection; +}; + +IMPLEMENT_CLIENTCLASS_EVENT_DT( C_TEGaussExplosion, DT_TEGaussExplosion, CTEGaussExplosion ) + RecvPropInt(RECVINFO(m_nType)), + RecvPropVector(RECVINFO(m_vecDirection)), +END_RECV_TABLE() + +//================================================== +// C_TEGaussExplosion +//================================================== + +C_TEGaussExplosion::C_TEGaussExplosion() +{ +} + +C_TEGaussExplosion::~C_TEGaussExplosion() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bNewEntity - whether or not to start a new entity +//----------------------------------------------------------------------------- +void C_TEGaussExplosion::PostDataUpdate( DataUpdateType_t updateType ) +{ + FX_GaussExplosion( m_vecOrigin, m_vecDirection, m_nType ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : filter - +// delay - +// &pos - +// &dir - +// type - +//----------------------------------------------------------------------------- +void TE_GaussExplosion( IRecipientFilter& filter, float delay, const Vector &pos, const Vector &dir, int type ) +{ + FX_GaussExplosion( pos, dir, type ); +} + diff --git a/game/client/c_impact_effects.h b/game/client/c_impact_effects.h new file mode 100644 index 00000000..cc920fc9 --- /dev/null +++ b/game/client/c_impact_effects.h @@ -0,0 +1,108 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_IMPACT_EFFECTS_H +#define C_IMPACT_EFFECTS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: DustParticle emitter +//----------------------------------------------------------------------------- +class CDustParticle : public CSimpleEmitter +{ +public: + + CDustParticle( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + //Create + static CDustParticle *Create( const char *pDebugName="dust" ) + { + return new CDustParticle( pDebugName ); + } + + //Roll + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -8.0f ); + +#ifdef _XBOX + //Cap the minimum roll + if ( fabs( pParticle->m_flRollDelta ) < 0.1f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.1f : -0.1f; + } +#else + if ( fabs( pParticle->m_flRollDelta ) < 0.5f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f; + } +#endif // _XBOX + + return pParticle->m_flRoll; + } + + //Velocity + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) + { + Vector saveVelocity = pParticle->m_vecVelocity; + + //Decellerate + static float dtime; + static float decay; + + if ( dtime != timeDelta ) + { + dtime = timeDelta; + float expected = 0.5; + decay = exp( log( 0.0001f ) * dtime / expected ); + } + + pParticle->m_vecVelocity = pParticle->m_vecVelocity * decay; + +#ifdef _XBOX + //Cap the minimum speed + if ( pParticle->m_vecVelocity.LengthSqr() < (8.0f*8.0f) ) + { + VectorNormalize( saveVelocity ); + pParticle->m_vecVelocity = saveVelocity * 8.0f; + } +#else + if ( pParticle->m_vecVelocity.LengthSqr() < (32.0f*32.0f) ) + { + VectorNormalize( saveVelocity ); + pParticle->m_vecVelocity = saveVelocity * 32.0f; + } +#endif // _XBOX + } + + //Alpha + virtual float UpdateAlpha( const SimpleParticle *pParticle ) + { + float tLifetime = pParticle->m_flLifetime / pParticle->m_flDieTime; + float ramp = 1.0f - tLifetime; + + //Non-linear fade + if ( ramp < 0.75f ) + ramp *= ramp; + + return ramp; + } + +private: + CDustParticle( const CDustParticle & ); // not defined, not accessible +}; + +void GetColorForSurface( trace_t *trace, Vector *color ); + +#include "tier0/memdbgoff.h" + +#endif // C_IMPACT_EFFECTS_H diff --git a/game/client/c_info_overlay_accessor.cpp b/game/client/c_info_overlay_accessor.cpp new file mode 100644 index 00000000..b3d743d4 --- /dev/null +++ b/game/client/c_info_overlay_accessor.cpp @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "materialsystem/IMesh.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// -------------------------------------------------------------------------------- // +// An entity used to access overlays (and change their texture) +// -------------------------------------------------------------------------------- // +class C_InfoOverlayAccessor : public C_BaseEntity +{ +public: + + DECLARE_CLASS( C_InfoOverlayAccessor, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_InfoOverlayAccessor(); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + +private: + + int m_iOverlayID; +}; + +// Expose it to the engine. +IMPLEMENT_CLIENTCLASS(C_InfoOverlayAccessor, DT_InfoOverlayAccessor, CInfoOverlayAccessor); + +BEGIN_RECV_TABLE_NOBASE(C_InfoOverlayAccessor, DT_InfoOverlayAccessor) + RecvPropInt(RECVINFO(m_iTextureFrameIndex)), + RecvPropInt(RECVINFO(m_iOverlayID)), +END_RECV_TABLE() + + +// -------------------------------------------------------------------------------- // +// Functions. +// -------------------------------------------------------------------------------- // + +C_InfoOverlayAccessor::C_InfoOverlayAccessor() +{ +} + +void C_InfoOverlayAccessor::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + // Update overlay's bind proxy + engine->SetOverlayBindProxy( m_iOverlayID, GetClientRenderable() ); + } +} diff --git a/game/client/c_lightglow.cpp b/game/client/c_lightglow.cpp new file mode 100644 index 00000000..2d217215 --- /dev/null +++ b/game/client/c_lightglow.cpp @@ -0,0 +1,215 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "glow_overlay.h" +#include "view.h" +#include "c_pixel_visibility.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_LightGlowOverlay : public CGlowOverlay +{ +public: + + virtual void CalcSpriteColorAndSize( float flDot, CGlowSprite *pSprite, float *flHorzSize, float *flVertSize, Vector *vColor ) + { + *flHorzSize = pSprite->m_flHorzSize; + *flVertSize = pSprite->m_flVertSize; + + Vector viewDir = ( CurrentViewOrigin() - m_vecOrigin ); + float distToViewer = VectorNormalize( viewDir ); + + if ( m_bOneSided ) + { + if ( DotProduct( viewDir, m_vecDirection ) < 0.0f ) + { + *vColor = Vector(0,0,0); + return; + } + } + + float fade; + + // See if we're in the outer fade distance range + if ( m_nOuterMaxDist > m_nMaxDist && distToViewer > m_nMaxDist ) + { + fade = RemapValClamped( distToViewer, m_nMaxDist, m_nOuterMaxDist, 1.0f, 0.0f ); + } + else + { + fade = RemapValClamped( distToViewer, m_nMinDist, m_nMaxDist, 0.0f, 1.0f ); + } + + *vColor = pSprite->m_vColor * fade * m_flGlowObstructionScale; + } + + void SetOrigin( const Vector &origin ) { m_vecOrigin = origin; } + + void SetFadeDistances( int minDist, int maxDist, int outerMaxDist ) + { + m_nMinDist = minDist; + m_nMaxDist = maxDist; + m_nOuterMaxDist = outerMaxDist; + } + + void SetOneSided( bool state = true ) { m_bOneSided = state; } + void SetModulateByDot( bool state = true ) { m_bModulateByDot = state; } + + void SetDirection( const Vector &dir ) { m_vecDirection = dir; VectorNormalize( m_vecDirection ); } + +protected: + + Vector m_vecOrigin; + Vector m_vecDirection; + int m_nMinDist; + int m_nMaxDist; + int m_nOuterMaxDist; + bool m_bOneSided; + bool m_bModulateByDot; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_LightGlow : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_LightGlow, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_LightGlow(); + +// C_BaseEntity overrides. +public: + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void Simulate( void ); + virtual void ClientThink( void ); + +public: + + int m_nHorizontalSize; + int m_nVerticalSize; + int m_nMinDist; + int m_nMaxDist; + int m_nOuterMaxDist; + int m_spawnflags; + C_LightGlowOverlay m_Glow; + + float m_flGlowProxySize; +}; + +static void RecvProxy_HDRColorScale( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_LightGlow *pLightGlow = ( C_LightGlow * )pStruct; + + pLightGlow->m_Glow.m_flHDRColorScale = pData->m_Value.m_Float; +} + +IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_LightGlow, DT_LightGlow, CLightGlow ) + RecvPropInt( RECVINFO(m_clrRender), 0, RecvProxy_IntToColor32 ), + RecvPropInt( RECVINFO( m_nHorizontalSize ) ), + RecvPropInt( RECVINFO( m_nVerticalSize ) ), + RecvPropInt( RECVINFO( m_nMinDist ) ), + RecvPropInt( RECVINFO( m_nMaxDist ) ), + RecvPropInt( RECVINFO( m_nOuterMaxDist ) ), + RecvPropInt( RECVINFO( m_spawnflags ) ), + RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), + RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ), + RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ), + RecvPropFloat(RECVINFO(m_flGlowProxySize)), + RecvPropFloat("HDRColorScale", 0, SIZEOF_IGNORE, 0, RecvProxy_HDRColorScale), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +C_LightGlow::C_LightGlow() : +m_nHorizontalSize( 0 ), m_nVerticalSize( 0 ), m_nMinDist( 0 ), m_nMaxDist( 0 ) +{ + m_Glow.m_bDirectional = false; + m_Glow.m_bInSky = false; +} + +void C_LightGlow::Simulate( void ) +{ + BaseClass::Simulate(); + + m_Glow.m_vPos = GetAbsOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_LightGlow::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + m_Glow.m_vPos = GetAbsOrigin(); + + if ( updateType == DATA_UPDATE_CREATED ) + { + // Setup our flare. + Vector vColor( + m_clrRender->r / 255.0f, + m_clrRender->g / 255.0f, + m_clrRender->b / 255.0f ); + + m_Glow.m_nSprites = 1; + + m_Glow.m_Sprites[0].m_flVertSize = (float) m_nVerticalSize; + m_Glow.m_Sprites[0].m_flHorzSize = (float) m_nHorizontalSize; + m_Glow.m_Sprites[0].m_vColor = vColor; + + m_Glow.SetOrigin( GetAbsOrigin() ); + m_Glow.SetFadeDistances( m_nMinDist, m_nMaxDist, m_nOuterMaxDist ); + m_Glow.m_flProxyRadius = m_flGlowProxySize; + + if ( m_spawnflags & SF_LIGHTGLOW_DIRECTIONAL ) + { + m_Glow.SetOneSided(); + } + + SetNextClientThink( gpGlobals->curtime + RandomFloat(0,3.0) ); + } + else if ( updateType == DATA_UPDATE_DATATABLE_CHANGED ) //Right now only color should change. + { + // Setup our flare. + Vector vColor( + m_clrRender->r / 255.0f, + m_clrRender->g / 255.0f, + m_clrRender->b / 255.0f ); + + m_Glow.m_Sprites[0].m_vColor = vColor; + } + + + Vector forward; + AngleVectors( GetAbsAngles(), &forward, NULL, NULL ); + + m_Glow.SetDirection( forward ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_LightGlow::ClientThink( void ) +{ + Vector mins = GetAbsOrigin(); + if ( engine->IsBoxVisible( mins, mins ) ) + { + m_Glow.Activate(); + } + else + { + m_Glow.Deactivate(); + } + + SetNextClientThink( gpGlobals->curtime + RandomFloat(1.0,3.0) ); +} diff --git a/game/client/c_movie_explosion.cpp b/game/client/c_movie_explosion.cpp new file mode 100644 index 00000000..07a000af --- /dev/null +++ b/game/client/c_movie_explosion.cpp @@ -0,0 +1,218 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "particle_prototype.h" +#include "particle_util.h" +#include "baseparticleentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ------------------------------------------------------------------------- // +// Definitions +// ------------------------------------------------------------------------- // +#define NUM_MOVIEEXPLOSION_EMITTERS 50 +#define EXPLOSION_EMITTER_LIFETIME 3 +#define EMITTED_PARTICLE_LIFETIME 1 + + +// ------------------------------------------------------------------------- // +// Classes +// ------------------------------------------------------------------------- // +class MovieExplosionEmitter +{ +public: + Vector m_Pos; + Vector m_Velocity; + float m_Lifetime; + TimedEvent m_ParticleSpawn; +}; + + +class C_MovieExplosion : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_MovieExplosion, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_MovieExplosion(); + ~C_MovieExplosion(); + +// C_BaseEntity. +public: + virtual void OnDataChanged(DataUpdateType_t updateType); + +// IPrototypeAppEffect. +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + +public: + MovieExplosionEmitter m_Emitters[NUM_MOVIEEXPLOSION_EMITTERS]; + float m_EmitterLifetime; + + CParticleMgr *m_pParticleMgr; + PMaterialHandle m_iFireballMaterial; + + // Setup for temporary usage in SimulateAndRender. + float m_EmitterAlpha; + +private: + C_MovieExplosion( const C_MovieExplosion & ); + +}; + +// Expose to the particle app. +EXPOSE_PROTOTYPE_EFFECT(MovieExplosion, C_MovieExplosion); + +IMPLEMENT_CLIENTCLASS_DT(C_MovieExplosion, DT_MovieExplosion, MovieExplosion) +END_RECV_TABLE() + + + +// ------------------------------------------------------------------------- // +// C_MovieExplosion +// ------------------------------------------------------------------------- // +C_MovieExplosion::C_MovieExplosion() +{ + m_pParticleMgr = NULL; +} + + +C_MovieExplosion::~C_MovieExplosion() +{ + if(m_pParticleMgr) + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); +} + + +void C_MovieExplosion::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if(updateType == DATA_UPDATE_CREATED) + { + Start( ParticleMgr(), NULL ); + } +} + + +void C_MovieExplosion::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) +{ + if(!pParticleMgr->AddEffect(&m_ParticleEffect, this)) + return; + + // Setup our emitters. + for(int iEmitter=0; iEmitter < NUM_MOVIEEXPLOSION_EMITTERS; iEmitter++) + { + MovieExplosionEmitter *pEmitter = &m_Emitters[iEmitter]; + pEmitter->m_Velocity = RandomVector(-1, 1) * 200; + + pEmitter->m_Pos = GetAbsOrigin(); + + pEmitter->m_Lifetime = 0; + pEmitter->m_ParticleSpawn.Init(15); + } + m_EmitterLifetime = 0; + + // Get our materials. + m_iFireballMaterial = m_ParticleEffect.FindOrAddMaterial("particle/particle_sphere"); + + m_pParticleMgr = pParticleMgr; +} + + +void C_MovieExplosion::Update(float fTimeDelta) +{ + if(!m_pParticleMgr) + return; + + m_EmitterLifetime += fTimeDelta; + if(m_EmitterLifetime > EXPLOSION_EMITTER_LIFETIME) + return; + + m_EmitterAlpha = (float)sin(m_EmitterLifetime * 3.14159f / EXPLOSION_EMITTER_LIFETIME); + + // Simulate the emitters and have them spit out particles. + for(int iEmitter=0; iEmitter < NUM_MOVIEEXPLOSION_EMITTERS; iEmitter++) + { + MovieExplosionEmitter *pEmitter = &m_Emitters[iEmitter]; + + pEmitter->m_Pos = pEmitter->m_Pos + pEmitter->m_Velocity * fTimeDelta; + pEmitter->m_Velocity = pEmitter->m_Velocity * 0.9; + + float tempDelta = fTimeDelta; + while(pEmitter->m_ParticleSpawn.NextEvent(tempDelta)) + { + StandardParticle_t *pParticle = + (StandardParticle_t*)m_ParticleEffect.AddParticle( sizeof(StandardParticle_t), m_iFireballMaterial); + + if(pParticle) + { + pParticle->m_Pos = pEmitter->m_Pos; + pParticle->m_Velocity = pEmitter->m_Velocity * 0.2f + RandomVector(-20, 20); + } + } + } +} + + +void C_MovieExplosion::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const StandardParticle_t *pParticle = (const StandardParticle_t*)pIterator->GetFirst(); + while ( pParticle ) + { + // Draw. + Vector tPos; + TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos); + float sortKey = tPos.z; + + float lifetimePercent = pParticle->m_Lifetime / EMITTED_PARTICLE_LIFETIME; + Vector color; + color.x = sin(lifetimePercent * 3.14159); + color.y = color.x * 0.5f; + color.z = 0; + RenderParticle_ColorSize( + pIterator->GetParticleDraw(), + tPos, + color, + m_EmitterAlpha * sin(3.14159 * lifetimePercent), + 10); + + pParticle = (const StandardParticle_t*)pIterator->GetNext( sortKey ); + } +} + +void C_MovieExplosion::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + StandardParticle_t *pParticle = (StandardParticle_t*)pIterator->GetFirst(); + while ( pParticle ) + { + // Update its lifetime. + pParticle->m_Lifetime += pIterator->GetTimeDelta(); + if(pParticle->m_Lifetime > 1) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + // Move it (this comes after rendering to make it clear that moving the particle here won't change + // its rendering for this frame since m_TransformedPos has already been set). + pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * pIterator->GetTimeDelta(); + } + + pParticle = (StandardParticle_t*)pIterator->GetNext(); + } +} + diff --git a/game/client/c_particle_fire.cpp b/game/client/c_particle_fire.cpp new file mode 100644 index 00000000..2c428ff1 --- /dev/null +++ b/game/client/c_particle_fire.cpp @@ -0,0 +1,339 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "particle_prototype.h" +#include "particle_util.h" +#include "baseparticleentity.h" +#include "engine/IEngineTrace.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ------------------------------------------------------------------------- // +// Defines. +// ------------------------------------------------------------------------- // + +#define MAX_FIRE_EMITTERS 128 +#define FIRE_PARTICLE_LIFETIME 2 + +Vector g_FireSpreadDirection(0,0,1); + + +class FireRamp +{ +public: + FireRamp(const Vector &s, const Vector &e) + { + m_Start=s; + m_End=e; + } + + Vector m_Start; + Vector m_End; +}; + +FireRamp g_FireRamps[] = +{ + FireRamp(Vector(1,0,0), Vector(1,1,0)), + FireRamp(Vector(0.5,0.5,0), Vector(0,0,0)) +}; +#define NUM_FIRE_RAMPS (sizeof(g_FireRamps) / sizeof(g_FireRamps[0])) + + +#define NUM_FIREGRID_OFFSETS 8 +Vector g_Offsets[NUM_FIREGRID_OFFSETS] = +{ + Vector(-1,-1,-1), + Vector( 1,-1,-1), + Vector(-1, 1,-1), + Vector( 1, 1,-1), + + Vector(-1,-1, 1), + Vector( 1,-1, 1), + Vector(-1, 1, 1), + Vector( 1, 1, 1), +}; + +// If you follow g_Offset[index], you can follow g_Offsets[GetOppositeOffset(index)] to get back. +inline int GetOppositeOffset(int offset) +{ + return NUM_FIREGRID_OFFSETS - offset - 1; +} + + +// ------------------------------------------------------------------------- // +// Classes. +// ------------------------------------------------------------------------- // + +class C_ParticleFire : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_ParticleFire, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_ParticleFire(); + ~C_ParticleFire(); + + class FireEmitter + { + public: + Vector m_Pos; + TimedEvent m_SpawnEvent; + float m_Lifetime; // How long it's been emitting. + unsigned char m_DirectionsTested; // 1 bit for each of g_Offsets. + }; + + class FireParticle : public Particle + { + public: + Vector m_StartPos; // The particle moves from m_StartPos to (m_StartPos+m_Direction) over its lifetime. + Vector m_Direction; + + float m_Lifetime; + float m_SpinAngle; + unsigned char m_iRamp; // Which fire ramp are we using? + }; + + +// C_BaseEntity. +public: + + virtual void OnDataChanged( DataUpdateType_t updateType ); + + +// IPrototypeAppEffect. +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + +public: + CParticleMgr *m_pParticleMgr; + PMaterialHandle m_MaterialHandle; + + // Controls where the initial fire goes. + Vector m_vOrigin; + Vector m_vDirection; + + TimedEvent m_EmitterSpawn; + FireEmitter m_Emitters[MAX_FIRE_EMITTERS]; + int m_nEmitters; + +private: + C_ParticleFire( const C_ParticleFire & ); +}; + + +// ------------------------------------------------------------------------- // +// Tables. +// ------------------------------------------------------------------------- // + +// Expose to the particle app. +EXPOSE_PROTOTYPE_EFFECT(ParticleFire, C_ParticleFire); + + +// Datatable.. +IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_ParticleFire, DT_ParticleFire, CParticleFire) + RecvPropVector(RECVINFO(m_vOrigin)), + RecvPropVector(RECVINFO(m_vDirection)), +END_RECV_TABLE() + + + +// ------------------------------------------------------------------------- // +// C_FireSmoke implementation. +// ------------------------------------------------------------------------- // +C_ParticleFire::C_ParticleFire() +{ + m_pParticleMgr = NULL; + m_MaterialHandle = INVALID_MATERIAL_HANDLE; +} + + +C_ParticleFire::~C_ParticleFire() +{ + if(m_pParticleMgr) + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); +} + + +void C_ParticleFire::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if(updateType == DATA_UPDATE_CREATED) + { + Start(ParticleMgr(), NULL); + } +} + + +void C_ParticleFire::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) +{ + m_pParticleMgr = pParticleMgr; + m_pParticleMgr->AddEffect( &m_ParticleEffect, this ); + m_MaterialHandle = m_ParticleEffect.FindOrAddMaterial("particle/particle_fire"); + + // Start + m_nEmitters = 0; + m_EmitterSpawn.Init(15); +} + +static float fireSpreadDist = 15; +static float size = 20; + +void C_ParticleFire::Update(float fTimeDelta) +{ + if(!m_pParticleMgr) + { + assert(false); + return; + } + + + // Add new emitters. + if(m_nEmitters < MAX_FIRE_EMITTERS) + { + float tempDelta = fTimeDelta; + while(m_EmitterSpawn.NextEvent(tempDelta)) + { + FireEmitter *pEmitter = NULL; + + if(m_nEmitters == 0) + { + // Make the first emitter. + trace_t trace; + UTIL_TraceLine(m_vOrigin, m_vOrigin+m_vDirection*1000, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); + if(trace.fraction < 1) + { + pEmitter = &m_Emitters[m_nEmitters]; + pEmitter->m_Pos = trace.endpos + trace.plane.normal * (size - 1); + pEmitter->m_DirectionsTested = 0; + } + } + else + { + static int nTries = 50; + for(int iTry=0; iTry < nTries; iTry++) + { + FireEmitter *pSourceEmitter = &m_Emitters[rand() % m_nEmitters]; + + int iOffset = rand() % NUM_FIREGRID_OFFSETS; + if(pSourceEmitter->m_DirectionsTested & (1 << iOffset)) + continue; + + // Test the corners of the new cube. If some points are solid and some are not, then + // we can put fire here. + Vector basePos = pSourceEmitter->m_Pos + g_Offsets[iOffset] * fireSpreadDist; + int nSolidCorners = 0; + for(int iCorner=0; iCorner < NUM_FIREGRID_OFFSETS; iCorner++) + { + Vector vCorner = basePos + g_Offsets[iCorner]*fireSpreadDist; + if ( enginetrace->GetPointContents(vCorner) & CONTENTS_SOLID ) + ++nSolidCorners; + } + + // Don't test this square again. + pSourceEmitter->m_DirectionsTested |= 1 << iOffset; + + if(nSolidCorners != 0 && nSolidCorners != NUM_FIREGRID_OFFSETS) + { + pEmitter = &m_Emitters[m_nEmitters]; + pEmitter->m_Pos = basePos; + pEmitter->m_DirectionsTested = 1 << GetOppositeOffset(iOffset); + } + } + } + + if(pEmitter) + { + pEmitter->m_Lifetime = 0; + pEmitter->m_SpawnEvent.Init(1); + ++m_nEmitters; + } + } + } + + // Spawn particles out of the emitters. + for(int i=0; i < m_nEmitters; i++) + { + FireEmitter *pEmitter = &m_Emitters[i]; + + float tempDelta = fTimeDelta; + while(pEmitter->m_SpawnEvent.NextEvent(tempDelta)) + { + FireParticle *pParticle = (FireParticle*)m_ParticleEffect.AddParticle(sizeof(FireParticle), m_MaterialHandle); + if(pParticle) + { + static float particleSpeed = 15; + pParticle->m_StartPos = pEmitter->m_Pos; + pParticle->m_Direction = g_FireSpreadDirection * particleSpeed + RandomVector(0, particleSpeed*0.5); + pParticle->m_iRamp = rand() % NUM_FIRE_RAMPS; + pParticle->m_Lifetime = 0; + } + } + } +} + + +void C_ParticleFire::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const FireParticle *pParticle = (const FireParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + float smooth01 = 1 - (cos(pParticle->m_Lifetime * 3.14159 / FIRE_PARTICLE_LIFETIME) * 0.5 + 0.5); + float smooth00 = 1 - (cos(pParticle->m_Lifetime * 3.14159 * 2 / FIRE_PARTICLE_LIFETIME) * 0.5 + 0.5); + + FireRamp *pRamp = &g_FireRamps[pParticle->m_iRamp]; + Vector curColor = pRamp->m_Start + (pRamp->m_End - pRamp->m_Start) * smooth01; + + // Render. + Vector tPos; + TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos); + float sortKey = (int)tPos.z; + + RenderParticle_ColorSize( + pIterator->GetParticleDraw(), + tPos, + curColor, + smooth00, + size); + + pParticle = (const FireParticle*)pIterator->GetNext( sortKey ); + } +} + + +void C_ParticleFire::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + FireParticle *pParticle = (FireParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Should this particle die? + pParticle->m_Lifetime += pIterator->GetTimeDelta(); + if(pParticle->m_Lifetime > FIRE_PARTICLE_LIFETIME) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + float smooth01 = 1 - (cos(pParticle->m_Lifetime * 3.14159 / FIRE_PARTICLE_LIFETIME) * 0.5 + 0.5); + pParticle->m_Pos = pParticle->m_StartPos + pParticle->m_Direction * smooth01; + } + + pParticle = (FireParticle*)pIterator->GetNext(); + } +} + + diff --git a/game/client/c_particle_smokegrenade.cpp b/game/client/c_particle_smokegrenade.cpp new file mode 100644 index 00000000..73e841e1 --- /dev/null +++ b/game/client/c_particle_smokegrenade.cpp @@ -0,0 +1,1021 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "c_smoke_trail.h" +#include "smoke_fog_overlay.h" +#include "engine/IEngineTrace.h" +#include "view.h" +#include "dlight.h" +#include "iefx.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" + +#if CSTRIKE_DLL +#include "c_cs_player.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ------------------------------------------------------------------------- // +// Definitions +// ------------------------------------------------------------------------- // + +static Vector s_FadePlaneDirections[] = +{ + Vector( 1,0,0), + Vector(-1,0,0), + Vector(0, 1,0), + Vector(0,-1,0), + Vector(0,0, 1), + Vector(0,0,-1) +}; +#define NUM_FADE_PLANES (sizeof(s_FadePlaneDirections)/sizeof(s_FadePlaneDirections[0])) + +// This is used to randomize the direction it chooses to move a particle in. +int g_OffsetLookup[3] = {-1,0,1}; + + +// ------------------------------------------------------------------------- // +// Classes +// ------------------------------------------------------------------------- // +class C_ParticleSmokeGrenade : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_ParticleSmokeGrenade, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_ParticleSmokeGrenade(); + ~C_ParticleSmokeGrenade(); + +private: + + class SmokeGrenadeParticle : public Particle + { + public: + float m_RotationSpeed; + float m_CurRotation; + float m_FadeAlpha; // Set as it moves around. + unsigned char m_ColorInterp; // Amount between min and max colors. + unsigned char m_Color[4]; + }; + + +public: + + // Optional call. It will use defaults if you don't call this. + void SetParams( + ); + + // Call this to move the source.. + void SetPos(const Vector &pos); + + +// C_BaseEntity. +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + + virtual void CleanupToolRecordingState( KeyValues *msg ); + +// IPrototypeAppEffect. +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + virtual void NotifyRemove(); + virtual void GetParticlePosition( Particle *pParticle, Vector& worldpos ); + virtual void ClientThink(); + + +// Proxies. +public: + + static void RecvProxy_CurrentStage( const CRecvProxyData *pData, void *pStruct, void *pOut ); + + +private: + + // The SmokeEmitter represents a grid in 3D space. + class SmokeParticleInfo + { + public: + SmokeGrenadeParticle *m_pParticle; + int m_TradeIndex; // -1 if not exchanging yet. + float m_TradeClock; // How long since they started trading. + float m_TradeDuration; // How long the trade will take to finish. + float m_FadeAlpha; // Calculated from nearby world geometry. + unsigned char m_Color[4]; + }; + + void ApplyDynamicLight( const Vector &vParticlePos, Vector &color ); + void UpdateDynamicLightList( const Vector &vMins, const Vector &vMaxs ); + + void UpdateSmokeTrail( float fTimeDelta ); + + void UpdateParticleAndFindTrade( int iParticle, float fTimeDelta ); + void UpdateParticleDuringTrade( int iParticle, float flTimeDelta ); + + inline int GetSmokeParticleIndex(int x, int y, int z) {return z*m_xCount*m_yCount+y*m_yCount+x;} + inline SmokeParticleInfo* GetSmokeParticleInfo(int x, int y, int z) {return &m_SmokeParticleInfos[GetSmokeParticleIndex(x,y,z)];} + inline void GetParticleInfoXYZ(int index, int &x, int &y, int &z) + { + z = index / (m_xCount*m_yCount); + int zIndex = z*m_xCount*m_yCount; + y = (index - zIndex) / m_yCount; + int yIndex = y*m_yCount; + x = index - zIndex - yIndex; + } + + inline bool IsValidXYZCoords(int x, int y, int z) + { + return x >= 0 && y >= 0 && z >= 0 && x < m_xCount && y < m_yCount && z < m_zCount; + } + + inline Vector GetSmokeParticlePos(int x, int y, int z) + { + return m_SmokeBasePos + + Vector( ((float)x / (m_xCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius, + ((float)y / (m_yCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius, + ((float)z / (m_zCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius); + } + + inline Vector GetSmokeParticlePosIndex(int index) + { + int x, y, z; + GetParticleInfoXYZ(index, x, y, z); + return GetSmokeParticlePos(x, y, z); + } + + inline const Vector& GetPos() { return GetAbsOrigin(); } + + // Start filling the smoke volume (and stop the smoke trail). + void FillVolume(); + + +// State variables from server. +public: + + unsigned char m_CurrentStage; + Vector m_SmokeBasePos; + + // What time the effect was initially created + float m_flSpawnTime; + + // It will fade out during this time. + float m_FadeStartTime; + float m_FadeEndTime; + float m_FadeAlpha; // Calculated from the fade start/end times each frame. + + // Used during rendering.. active dlights. + class CActiveLight + { + public: + Vector m_vColor; + Vector m_vOrigin; + float m_flRadiusSqr; + }; + CActiveLight m_ActiveLights[MAX_DLIGHTS]; + int m_nActiveLights; + + +private: + C_ParticleSmokeGrenade( const C_ParticleSmokeGrenade & ); + + bool m_bStarted; + bool m_bVolumeFilled; + PMaterialHandle m_MaterialHandles[NUM_MATERIAL_HANDLES]; + + SmokeParticleInfo m_SmokeParticleInfos[NUM_PARTICLES_PER_DIMENSION*NUM_PARTICLES_PER_DIMENSION*NUM_PARTICLES_PER_DIMENSION]; + int m_xCount, m_yCount, m_zCount; + float m_SpacingRadius; + + Vector m_MinColor; + Vector m_MaxColor; + + float m_ExpandTimeCounter; // How long since we started expanding. + float m_ExpandRadius; // How large is our radius. + + C_SmokeTrail m_SmokeTrail; +}; + + +// Expose to the particle app. +EXPOSE_PROTOTYPE_EFFECT(SmokeGrenade, C_ParticleSmokeGrenade); + + +// Datatable.. +IMPLEMENT_CLIENTCLASS_DT(C_ParticleSmokeGrenade, DT_ParticleSmokeGrenade, ParticleSmokeGrenade) + RecvPropTime(RECVINFO(m_flSpawnTime)), + RecvPropFloat(RECVINFO(m_FadeStartTime)), + RecvPropFloat(RECVINFO(m_FadeEndTime)), + RecvPropInt(RECVINFO(m_CurrentStage), 0, &C_ParticleSmokeGrenade::RecvProxy_CurrentStage), +END_RECV_TABLE() + + +// ------------------------------------------------------------------------- // +// Helpers. +// ------------------------------------------------------------------------- // + +static inline void InterpColor(unsigned char dest[4], unsigned char src1[4], unsigned char src2[4], float percent) +{ + dest[0] = (unsigned char)(src1[0] + (src2[0] - src1[0]) * percent); + dest[1] = (unsigned char)(src1[1] + (src2[1] - src1[1]) * percent); + dest[2] = (unsigned char)(src1[2] + (src2[2] - src1[2]) * percent); +} + + +static inline int GetWorldPointContents(const Vector &vPos) +{ + #if defined(PARTICLEPROTOTYPE_APP) + return 0; + #else + return enginetrace->GetPointContents( vPos ); + #endif +} + +static inline void WorldTraceLine( const Vector &start, const Vector &end, int contentsMask, trace_t *trace ) +{ + #if defined(PARTICLEPROTOTYPE_APP) + trace->fraction = 1; + #else + UTIL_TraceLine(start, end, contentsMask, NULL, COLLISION_GROUP_NONE, trace); + #endif +} + +static inline Vector EngineGetLightForPoint(const Vector &vPos) +{ + #if defined(PARTICLEPROTOTYPE_APP) + return Vector(1,1,1); + #else + return engine->GetLightForPoint(vPos, true); + #endif +} + +static inline const Vector& EngineGetVecRenderOrigin() +{ + #if defined(PARTICLEPROTOTYPE_APP) + static Vector dummy(0,0,0); + return dummy; + #else + return CurrentViewOrigin(); + #endif +} + +static inline float& EngineGetSmokeFogOverlayAlpha() +{ + #if defined(PARTICLEPROTOTYPE_APP) + static float dummy; + return dummy; + #else + return g_SmokeFogOverlayAlpha; + #endif +} + +static inline C_BaseEntity* ParticleGetEntity(int index) +{ + #if defined(PARTICLEPROTOTYPE_APP) + return NULL; + #else + return cl_entitylist->GetEnt(index); + #endif +} + + + +// ------------------------------------------------------------------------- // +// ParticleMovieExplosion +// ------------------------------------------------------------------------- // +C_ParticleSmokeGrenade::C_ParticleSmokeGrenade() +{ + memset(m_MaterialHandles, 0, sizeof(m_MaterialHandles)); + + m_MinColor.Init(0.5, 0.5, 0.5); + m_MaxColor.Init(0.6, 0.6, 0.6 ); + + m_nActiveLights = 0; + m_ExpandRadius = 0; + m_ExpandTimeCounter = 0; + m_FadeStartTime = 0; + m_FadeEndTime = 0; + m_flSpawnTime = 0; + m_bVolumeFilled = false; + m_CurrentStage = 0; + + m_bStarted = false; +} + + +C_ParticleSmokeGrenade::~C_ParticleSmokeGrenade() +{ + ParticleMgr()->RemoveEffect( &m_ParticleEffect ); +} + + +void C_ParticleSmokeGrenade::SetParams( + ) +{ +} + + +void C_ParticleSmokeGrenade::OnDataChanged( DataUpdateType_t updateType ) +{ + C_BaseEntity::OnDataChanged(updateType); + + if(updateType == DATA_UPDATE_CREATED ) + { + Start(ParticleMgr(), NULL); + } +} + + +void C_ParticleSmokeGrenade::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) +{ + if(!pParticleMgr->AddEffect( &m_ParticleEffect, this )) + return; + + m_SmokeTrail.Start(pParticleMgr, pArgs); + + m_SmokeTrail.m_ParticleLifetime = 0.5; + m_SmokeTrail.SetSpawnRate(40); + m_SmokeTrail.m_MinSpeed = 0; + m_SmokeTrail.m_MaxSpeed = 0; + m_SmokeTrail.m_StartSize = 3; + m_SmokeTrail.m_EndSize = 10; + m_SmokeTrail.m_SpawnRadius = 0; + + m_SmokeTrail.SetLocalOrigin( GetAbsOrigin() ); + + for(int i=0; i < NUM_MATERIAL_HANDLES; i++) + { + char str[256]; + Q_snprintf(str, sizeof( str ), "particle/particle_smokegrenade%d", i+1); + m_MaterialHandles[i] = m_ParticleEffect.FindOrAddMaterial(str); + } + + if( m_CurrentStage == 2 ) + { + FillVolume(); + } + + // Go straight into "fill volume" mode if they want. + if(pArgs) + { + if(pArgs->FindArg("-FillVolume")) + { + FillVolume(); + } + } + + m_bStarted = true; + SetNextClientThink( CLIENT_THINK_ALWAYS ); + +#if CSTRIKE_DLL + C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer(); + + if ( pPlayer ) + { + pPlayer->m_SmokeGrenades.AddToTail( this ); + } +#endif + +} + + +void C_ParticleSmokeGrenade::ClientThink() +{ + if ( m_CurrentStage == 1 ) + { + // Add our influence to the global smoke fog alpha. + + float testDist = (MainViewOrigin() - m_SmokeBasePos).Length(); + + float fadeEnd = m_ExpandRadius; + + // The center of the smoke cloud that always gives full fog overlay + float flCoreDistance = fadeEnd * 0.15; + + if(testDist < fadeEnd) + { + if( testDist < flCoreDistance ) + { + EngineGetSmokeFogOverlayAlpha() += m_FadeAlpha; + } + else + { + EngineGetSmokeFogOverlayAlpha() += (1 - ( testDist - flCoreDistance ) / ( fadeEnd - flCoreDistance ) ) * m_FadeAlpha; + } + } + } +} + + +void C_ParticleSmokeGrenade::UpdateSmokeTrail( float fTimeDelta ) +{ + C_BaseEntity *pAimEnt = GetFollowedEntity(); + if ( pAimEnt ) + { + Vector forward, right, up; + + // Update the smoke particle color. + if(m_CurrentStage == 0) + { + m_SmokeTrail.m_StartColor = EngineGetLightForPoint(GetAbsOrigin()) * 0.5f; + m_SmokeTrail.m_EndColor = m_SmokeTrail.m_StartColor; + } + + // Spin the smoke trail. + AngleVectors(pAimEnt->GetAbsAngles(), &forward, &right, &up); + m_SmokeTrail.m_VelocityOffset = forward * 30 + GetAbsVelocity(); + + m_SmokeTrail.SetLocalOrigin( GetAbsOrigin() ); + m_SmokeTrail.Update(fTimeDelta); + } +} + + +inline void C_ParticleSmokeGrenade::UpdateParticleDuringTrade( int iParticle, float fTimeDelta ) +{ + SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[iParticle]; + SmokeParticleInfo *pOther = &m_SmokeParticleInfos[pInfo->m_TradeIndex]; + Assert(pOther->m_TradeIndex == iParticle); + + // This makes sure the trade only gets updated once per frame. + if(pInfo < pOther) + { + // Increment the trade clock.. + pInfo->m_TradeClock = (pOther->m_TradeClock += fTimeDelta); + int x, y, z; + GetParticleInfoXYZ(iParticle, x, y, z); + Vector myPos = GetSmokeParticlePos(x, y, z) - m_SmokeBasePos; + + int otherX, otherY, otherZ; + GetParticleInfoXYZ(pInfo->m_TradeIndex, otherX, otherY, otherZ); + Vector otherPos = GetSmokeParticlePos(otherX, otherY, otherZ) - m_SmokeBasePos; + + // Is the trade finished? + if(pInfo->m_TradeClock >= pInfo->m_TradeDuration) + { + pInfo->m_TradeIndex = pOther->m_TradeIndex = -1; + + pInfo->m_pParticle->m_Pos = otherPos; + pOther->m_pParticle->m_Pos = myPos; + + SmokeGrenadeParticle *temp = pInfo->m_pParticle; + pInfo->m_pParticle = pOther->m_pParticle; + pOther->m_pParticle = temp; + } + else + { + // Ok, move them closer. + float percent = (float)cos(pInfo->m_TradeClock * 2 * 1.57079632f / pInfo->m_TradeDuration); + percent = percent * 0.5 + 0.5; + + pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * (1 - percent); + pOther->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * percent; + + InterpColor(pInfo->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, 1-percent); + InterpColor(pOther->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, percent); + + pInfo->m_pParticle->m_Pos = myPos + (otherPos - myPos) * (1 - percent); + pOther->m_pParticle->m_Pos = myPos + (otherPos - myPos) * percent; + } + } +} + + +void C_ParticleSmokeGrenade::UpdateParticleAndFindTrade( int iParticle, float fTimeDelta ) +{ + SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[iParticle]; + + pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha; + pInfo->m_pParticle->m_Color[0] = pInfo->m_Color[0]; + pInfo->m_pParticle->m_Color[1] = pInfo->m_Color[1]; + pInfo->m_pParticle->m_Color[2] = pInfo->m_Color[2]; + + // Is there an adjacent one that's not trading? + int x, y, z; + GetParticleInfoXYZ(iParticle, x, y, z); + + int xCountOffset = rand(); + int yCountOffset = rand(); + int zCountOffset = rand(); + + bool bFound = false; + for(int xCount=0; xCount < 3 && !bFound; xCount++) + { + for(int yCount=0; yCount < 3 && !bFound; yCount++) + { + for(int zCount=0; zCount < 3; zCount++) + { + int testX = x + g_OffsetLookup[(xCount+xCountOffset) % 3]; + int testY = y + g_OffsetLookup[(yCount+yCountOffset) % 3]; + int testZ = z + g_OffsetLookup[(zCount+zCountOffset) % 3]; + + if(testX == x && testY == y && testZ == z) + continue; + + if(IsValidXYZCoords(testX, testY, testZ)) + { + SmokeParticleInfo *pOther = GetSmokeParticleInfo(testX, testY, testZ); + if(pOther->m_pParticle && pOther->m_TradeIndex == -1) + { + // Ok, this one is looking to trade also. + pInfo->m_TradeIndex = GetSmokeParticleIndex(testX, testY, testZ); + pOther->m_TradeIndex = iParticle; + pInfo->m_TradeClock = pOther->m_TradeClock = 0; + pInfo->m_TradeDuration = FRand(TRADE_DURATION_MIN, TRADE_DURATION_MAX); + + bFound = true; + break; + } + } + } + } + } +} + + +void C_ParticleSmokeGrenade::Update(float fTimeDelta) +{ + float flLifetime = gpGlobals->curtime - m_flSpawnTime; + + // Update the smoke trail. + UpdateSmokeTrail( fTimeDelta ); + + // Update our fade alpha. + if(flLifetime < m_FadeStartTime) + { + m_FadeAlpha = 1; + } + else if(flLifetime < m_FadeEndTime) + { + float fadePercent = (flLifetime - m_FadeStartTime) / (m_FadeEndTime - m_FadeStartTime); + m_FadeAlpha = cos(fadePercent * 3.14159) * 0.5 + 0.5; + } + else + { + m_FadeAlpha = 0; + } + + // Scale by the amount the sphere has grown. + m_FadeAlpha *= m_ExpandRadius / (m_SpacingRadius*2); + + + // Update our bbox. + Vector vMins = m_SmokeBasePos - Vector( m_SpacingRadius, m_SpacingRadius, m_SpacingRadius ); + Vector vMaxs = m_SmokeBasePos + Vector( m_SpacingRadius, m_SpacingRadius, m_SpacingRadius ); + m_ParticleEffect.SetBBox( vMins, vMaxs ); + + + // Update the current light list. + UpdateDynamicLightList( vMins, vMaxs ); + + + if(m_CurrentStage == 1) + { + // Update the expanding sphere. + m_ExpandTimeCounter = flLifetime; + if(m_ExpandTimeCounter > SMOKESPHERE_EXPAND_TIME) + m_ExpandTimeCounter = SMOKESPHERE_EXPAND_TIME; + + m_ExpandRadius = (m_SpacingRadius*2) * (float)sin(m_ExpandTimeCounter * M_PI * 0.5 / SMOKESPHERE_EXPAND_TIME); + + // Update all the moving traders and establish new ones. + int nTotal = m_xCount * m_yCount * m_zCount; + for(int i=0; i < nTotal; i++) + { + SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[i]; + + if(!pInfo->m_pParticle) + continue; + + if(pInfo->m_TradeIndex == -1) + { + UpdateParticleAndFindTrade( i, fTimeDelta ); + } + else + { + UpdateParticleDuringTrade( i, fTimeDelta ); + } + } + } + + m_SmokeBasePos = GetPos(); +} + + +void C_ParticleSmokeGrenade::UpdateDynamicLightList( const Vector &vMins, const Vector &vMaxs ) +{ + dlight_t *lights[MAX_DLIGHTS]; + int nLights = effects->CL_GetActiveDLights( lights ); + m_nActiveLights = 0; + for ( int i=0; i < nLights; i++ ) + { + dlight_t *pIn = lights[i]; + if ( pIn->origin.x + pIn->radius <= vMins.x || + pIn->origin.y + pIn->radius <= vMins.y || + pIn->origin.z + pIn->radius <= vMins.z || + pIn->origin.x - pIn->radius >= vMaxs.x || + pIn->origin.y - pIn->radius >= vMaxs.y || + pIn->origin.z - pIn->radius >= vMaxs.z ) + { + } + else + { + CActiveLight *pOut = &m_ActiveLights[m_nActiveLights]; + if ( (pIn->color.r != 0 || pIn->color.g != 0 || pIn->color.b != 0) && pIn->color.exponent != 0 ) + { + ColorRGBExp32ToVector( pIn->color, pOut->m_vColor ); + pOut->m_vColor /= 255.0f; + pOut->m_flRadiusSqr = (pIn->radius + SMOKEPARTICLE_SIZE) * (pIn->radius + SMOKEPARTICLE_SIZE); + pOut->m_vOrigin = pIn->origin; + ++m_nActiveLights; + } + } + } +} + + +inline void C_ParticleSmokeGrenade::ApplyDynamicLight( const Vector &vParticlePos, Vector &color ) +{ + if ( m_nActiveLights ) + { + for ( int i=0; i < m_nActiveLights; i++ ) + { + CActiveLight *pLight = &m_ActiveLights[i]; + + float flDistSqr = (vParticlePos - pLight->m_vOrigin).LengthSqr(); + if ( flDistSqr < pLight->m_flRadiusSqr ) + { + color += pLight->m_vColor * (1 - flDistSqr / pLight->m_flRadiusSqr) * 0.1f; + } + } + + // Rescale the color.. + float flMax = max( color.x, max( color.y, color.z ) ); + if ( flMax > 1 ) + { + color /= flMax; + } + } +} + + +void C_ParticleSmokeGrenade::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const SmokeGrenadeParticle *pParticle = (const SmokeGrenadeParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + Vector vWorldSpacePos = m_SmokeBasePos + pParticle->m_Pos; + + float sortKey; + + // Draw. + float len = pParticle->m_Pos.Length(); + if ( len > m_ExpandRadius ) + { + Vector vTemp; + TransformParticle(ParticleMgr()->GetModelView(), vWorldSpacePos, vTemp); + sortKey = vTemp.z; + } + else + { + // This smooths out the growing sphere. Rather than having particles appear in one spot as the sphere + // expands, they stay at the borders. + Vector renderPos; + if(len > m_ExpandRadius * 0.5f) + { + renderPos = m_SmokeBasePos + (pParticle->m_Pos * (m_ExpandRadius * 0.5f)) / len; + } + else + { + renderPos = vWorldSpacePos; + } + + // Figure out the alpha based on where it is in the sphere. + float alpha = 1 - len / m_ExpandRadius; + + // This changes the ramp to be very solid in the core, then taper off. + static float testCutoff=0.7; + if(alpha > testCutoff) + { + alpha = 1; + } + else + { + // at testCutoff it's 1, at 0, it's 0 + alpha = alpha / testCutoff; + } + + // Fade out globally. + alpha *= m_FadeAlpha; + + // Apply the precalculated fade alpha from world geometry. + alpha *= pParticle->m_FadeAlpha; + + // TODO: optimize this whole routine! + Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f); + color.x *= pParticle->m_Color[0] / 255.0f; + color.y *= pParticle->m_Color[1] / 255.0f; + color.z *= pParticle->m_Color[2] / 255.0f; + + // Lighting. + ApplyDynamicLight( renderPos, color ); + + Vector tRenderPos; + TransformParticle(ParticleMgr()->GetModelView(), renderPos, tRenderPos); + sortKey = tRenderPos.z; + + RenderParticle_ColorSizeAngle( + pIterator->GetParticleDraw(), + tRenderPos, + color, + alpha * GetAlphaDistanceFade(tRenderPos, 100, 200), // Alpha + SMOKEPARTICLE_SIZE, + pParticle->m_CurRotation + ); + } + + pParticle = (SmokeGrenadeParticle*)pIterator->GetNext( sortKey ); + } +} + + +void C_ParticleSmokeGrenade::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + SmokeGrenadeParticle *pParticle = (SmokeGrenadeParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + pParticle->m_CurRotation += pParticle->m_RotationSpeed * pIterator->GetTimeDelta(); + pParticle = (SmokeGrenadeParticle*)pIterator->GetNext(); + } +} + + +void C_ParticleSmokeGrenade::NotifyRemove() +{ + m_xCount = m_yCount = m_zCount = 0; + +#if CSTRIKE_DLL + C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer(); + + if ( pPlayer ) + { + pPlayer->m_SmokeGrenades.FindAndRemove( this ); + } +#endif + +} + + +void C_ParticleSmokeGrenade::GetParticlePosition( Particle *pParticle, Vector& worldpos ) +{ + worldpos = pParticle->m_Pos + m_SmokeBasePos; +} + + +void C_ParticleSmokeGrenade::RecvProxy_CurrentStage( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_ParticleSmokeGrenade *pGrenade = (C_ParticleSmokeGrenade*)pStruct; + Assert( pOut == &pGrenade->m_CurrentStage ); + + if ( pGrenade && pGrenade->m_CurrentStage == 0 && pData->m_Value.m_Int == 1 ) + { + if( pGrenade->m_bStarted ) + pGrenade->FillVolume(); + else + pGrenade->m_CurrentStage = 2; + } +} + +void C_ParticleSmokeGrenade::FillVolume() +{ + m_CurrentStage = 1; + m_SmokeBasePos = GetPos(); + m_SmokeTrail.SetEmit(false); + m_ExpandTimeCounter = m_ExpandRadius = 0; + m_bVolumeFilled = true; + + // Spawn all of our particles. + float overlap = SMOKEPARTICLE_OVERLAP; + + m_SpacingRadius = (SMOKEGRENADE_PARTICLERADIUS - overlap) * NUM_PARTICLES_PER_DIMENSION * 0.5f; + m_xCount = m_yCount = m_zCount = NUM_PARTICLES_PER_DIMENSION; + + float invNumPerDimX = 1.0f / (m_xCount-1); + float invNumPerDimY = 1.0f / (m_yCount-1); + float invNumPerDimZ = 1.0f / (m_zCount-1); + + Vector vPos; + for(int x=0; x < m_xCount; x++) + { + vPos.x = m_SmokeBasePos.x + ((float)x * invNumPerDimX) * m_SpacingRadius * 2 - m_SpacingRadius; + + for(int y=0; y < m_yCount; y++) + { + vPos.y = m_SmokeBasePos.y + ((float)y * invNumPerDimY) * m_SpacingRadius * 2 - m_SpacingRadius; + + for(int z=0; z < m_zCount; z++) + { + vPos.z = m_SmokeBasePos.z + ((float)z * invNumPerDimZ) * m_SpacingRadius * 2 - m_SpacingRadius; + + // Don't spawn and simulate particles that are inside a wall + int contents = enginetrace->GetPointContents( vPos ); + + if( contents & CONTENTS_SOLID ) + { + continue; + } + + if(SmokeParticleInfo *pInfo = GetSmokeParticleInfo(x,y,z)) + { + // MD 11/10/03: disabled this because we weren't getting coverage near the ground. + // If we want it back in certain cases, we can make it a flag. + /*int contents = GetWorldPointContents(vPos); + if(false && (contents & CONTENTS_SOLID)) + { + pInfo->m_pParticle = NULL; + } + else + */ + { + SmokeGrenadeParticle *pParticle = + (SmokeGrenadeParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeGrenadeParticle), m_MaterialHandles[rand() % NUM_MATERIAL_HANDLES]); + + if(pParticle) + { + pParticle->m_Pos = vPos - m_SmokeBasePos; // store its position in local space + pParticle->m_ColorInterp = (unsigned char)((rand() * 255) / RAND_MAX); + pParticle->m_RotationSpeed = FRand(-ROTATION_SPEED, ROTATION_SPEED); // Rotation speed. + pParticle->m_CurRotation = FRand(-6, 6); + } + + #ifdef _DEBUG + int testX, testY, testZ; + int index = GetSmokeParticleIndex(x,y,z); + GetParticleInfoXYZ(index, testX, testY, testZ); + assert(testX == x && testY == y && testZ == z); + #endif + + Vector vColor = EngineGetLightForPoint(vPos); + pInfo->m_Color[0] = (unsigned char)(vColor.x * 255.9f); + pInfo->m_Color[1] = (unsigned char)(vColor.y * 255.9f); + pInfo->m_Color[2] = (unsigned char)(vColor.z * 255.9f); + + // Cast some rays and if it's too close to anything, fade its alpha down. + pInfo->m_FadeAlpha = 1; + + /*for(int i=0; i < NUM_FADE_PLANES; i++) + { + trace_t trace; + WorldTraceLine(vPos, vPos + s_FadePlaneDirections[i] * 100, MASK_SOLID_BRUSHONLY, &trace); + if(trace.fraction < 1.0f) + { + float dist = DotProduct(trace.plane.normal, vPos) - trace.plane.dist; + if(dist < 0) + { + pInfo->m_FadeAlpha = 0; + } + else if(dist < SMOKEPARTICLE_SIZE) + { + float alphaScale = dist / SMOKEPARTICLE_SIZE; + alphaScale *= alphaScale * alphaScale; + pInfo->m_FadeAlpha *= alphaScale; + } + } + }*/ + + pInfo->m_pParticle = pParticle; + pInfo->m_TradeIndex = -1; + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// This is called after sending this entity's recording state +//----------------------------------------------------------------------------- +void C_ParticleSmokeGrenade::CleanupToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + BaseClass::CleanupToolRecordingState( msg ); + m_SmokeTrail.CleanupToolRecordingState( msg ); + + // Generally, this is used to allow the entity to clean up + // allocated state it put into the message, but here we're going + // to use it to send particle system messages because we + // know the grenade has been recorded at this point + if ( !clienttools->IsInRecordingMode() ) + return; + + // NOTE: Particle system destruction message will be sent by the particle effect itself. + if ( m_bVolumeFilled && GetToolParticleEffectId() == TOOLPARTICLESYSTEMID_INVALID ) + { + // Needed for retriggering of the smoke grenade + m_bVolumeFilled = false; + + int nId = AllocateToolParticleEffectId(); + + KeyValues *msg = new KeyValues( "OldParticleSystem_Create" ); + msg->SetString( "name", "C_ParticleSmokeGrenade" ); + msg->SetInt( "id", nId ); + msg->SetFloat( "time", gpGlobals->curtime ); + + KeyValues *pEmitter = msg->FindKey( "DmeSpriteEmitter", true ); + pEmitter->SetInt( "count", NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION ); + pEmitter->SetFloat( "duration", 0 ); + pEmitter->SetString( "material", "particle/particle_smokegrenade1" ); + pEmitter->SetInt( "active", true ); + + KeyValues *pInitializers = pEmitter->FindKey( "initializers", true ); + + KeyValues *pPosition = pInitializers->FindKey( "DmeVoxelPositionInitializer", true ); + pPosition->SetFloat( "centerx", m_SmokeBasePos.x ); + pPosition->SetFloat( "centery", m_SmokeBasePos.y ); + pPosition->SetFloat( "centerz", m_SmokeBasePos.z ); + pPosition->SetFloat( "particlesPerDimension", m_xCount ); + pPosition->SetFloat( "particleSpacing", m_SpacingRadius ); + + KeyValues *pLifetime = pInitializers->FindKey( "DmeRandomLifetimeInitializer", true ); + pLifetime->SetFloat( "minLifetime", m_FadeEndTime ); + pLifetime->SetFloat( "maxLifetime", m_FadeEndTime ); + + KeyValues *pVelocity = pInitializers->FindKey( "DmeAttachmentVelocityInitializer", true ); + pVelocity->SetPtr( "entindex", (void*)entindex() ); + pVelocity->SetFloat( "minRandomSpeed", 10 ); + pVelocity->SetFloat( "maxRandomSpeed", 20 ); + + KeyValues *pRoll = pInitializers->FindKey( "DmeRandomRollInitializer", true ); + pRoll->SetFloat( "minRoll", -6.0f ); + pRoll->SetFloat( "maxRoll", 6.0f ); + + KeyValues *pRollSpeed = pInitializers->FindKey( "DmeRandomRollSpeedInitializer", true ); + pRollSpeed->SetFloat( "minRollSpeed", -ROTATION_SPEED ); + pRollSpeed->SetFloat( "maxRollSpeed", ROTATION_SPEED ); + + KeyValues *pColor = pInitializers->FindKey( "DmeRandomInterpolatedColorInitializer", true ); + Color c1( + clamp( m_MinColor.x * 255.0f, 0, 255 ), + clamp( m_MinColor.y * 255.0f, 0, 255 ), + clamp( m_MinColor.z * 255.0f, 0, 255 ), 255 ); + Color c2( + clamp( m_MaxColor.x * 255.0f, 0, 255 ), + clamp( m_MaxColor.y * 255.0f, 0, 255 ), + clamp( m_MaxColor.z * 255.0f, 0, 255 ), 255 ); + pColor->SetColor( "color1", c1 ); + pColor->SetColor( "color2", c2 ); + + KeyValues *pAlpha = pInitializers->FindKey( "DmeRandomAlphaInitializer", true ); + pAlpha->SetInt( "minStartAlpha", 255 ); + pAlpha->SetInt( "maxStartAlpha", 255 ); + pAlpha->SetInt( "minEndAlpha", 0 ); + pAlpha->SetInt( "maxEndAlpha", 0 ); + + KeyValues *pSize = pInitializers->FindKey( "DmeRandomSizeInitializer", true ); + pSize->SetFloat( "minStartSize", SMOKEPARTICLE_SIZE ); + pSize->SetFloat( "maxStartSize", SMOKEPARTICLE_SIZE ); + pSize->SetFloat( "minEndSize", SMOKEPARTICLE_SIZE ); + pSize->SetFloat( "maxEndSize", SMOKEPARTICLE_SIZE ); + + pInitializers->FindKey( "DmeSolidKillInitializer", true ); + + KeyValues *pUpdaters = pEmitter->FindKey( "updaters", true ); + + pUpdaters->FindKey( "DmeRollUpdater", true ); + pUpdaters->FindKey( "DmeColorUpdater", true ); + + KeyValues *pAlphaCosineUpdater = pUpdaters->FindKey( "DmeAlphaCosineUpdater", true ); + pAlphaCosineUpdater->SetFloat( "duration", m_FadeEndTime - m_FadeStartTime ); + + pUpdaters->FindKey( "DmeColorDynamicLightUpdater", true ); + + KeyValues *pSmokeGrenadeUpdater = pUpdaters->FindKey( "DmeSmokeGrenadeUpdater", true ); + pSmokeGrenadeUpdater->SetFloat( "centerx", m_SmokeBasePos.x ); + pSmokeGrenadeUpdater->SetFloat( "centery", m_SmokeBasePos.y ); + pSmokeGrenadeUpdater->SetFloat( "centerz", m_SmokeBasePos.z ); + pSmokeGrenadeUpdater->SetFloat( "particlesPerDimension", m_xCount ); + pSmokeGrenadeUpdater->SetFloat( "particleSpacing", m_SpacingRadius ); + pSmokeGrenadeUpdater->SetFloat( "radiusExpandTime", SMOKESPHERE_EXPAND_TIME ); + pSmokeGrenadeUpdater->SetFloat( "cutoffFraction", 0.7f ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + + diff --git a/game/client/c_particle_system.cpp b/game/client/c_particle_system.cpp new file mode 100644 index 00000000..2b86d160 --- /dev/null +++ b/game/client/c_particle_system.cpp @@ -0,0 +1,235 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "particles/particles.h" +#include "c_te_effect_dispatch.h" +#include "particles_new.h" +#include "networkstringtable_clientdll.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: An entity that spawns and controls a particle system +//----------------------------------------------------------------------------- +class C_ParticleSystem : public C_BaseEntity +{ + DECLARE_CLASS( C_ParticleSystem, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + void PreDataUpdate( DataUpdateType_t updateType ); + void PostDataUpdate( DataUpdateType_t updateType ); + void ClientThink( void ); + +protected: + int m_iEffectIndex; + bool m_bActive; + bool m_bOldActive; + float m_flStartTime; // Time at which the effect started + + enum { kMAXCONTROLPOINTS = 63 }; ///< actually one less than the total number of cpoints since 0 is assumed to be me + + + EHANDLE m_hControlPointEnts[kMAXCONTROLPOINTS]; + // SendPropArray3( SENDINFO_ARRAY3(m_iControlPointParents), SendPropInt( SENDINFO_ARRAY(m_iControlPointParents), 3, SPROP_UNSIGNED ) ), + unsigned char m_iControlPointParents[kMAXCONTROLPOINTS]; +}; + +IMPLEMENT_CLIENTCLASS(C_ParticleSystem, DT_ParticleSystem, CParticleSystem); + +BEGIN_RECV_TABLE_NOBASE( C_ParticleSystem, DT_ParticleSystem ) + RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), + RecvPropEHandle( RECVINFO(m_hOwnerEntity) ), + RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ), + RecvPropInt( RECVINFO( m_iParentAttachment ) ), + RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ), + + RecvPropInt( RECVINFO( m_iEffectIndex ) ), + RecvPropBool( RECVINFO( m_bActive ) ), + RecvPropFloat( RECVINFO( m_flStartTime ) ), + + RecvPropArray3( RECVINFO_ARRAY(m_hControlPointEnts), RecvPropEHandle( RECVINFO( m_hControlPointEnts[0] ) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iControlPointParents), RecvPropInt( RECVINFO(m_iControlPointParents[0]))), +END_RECV_TABLE(); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_ParticleSystem::PreDataUpdate( DataUpdateType_t updateType ) +{ + m_bOldActive = m_bActive; + + BaseClass::PreDataUpdate( updateType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_ParticleSystem::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + // Always restart if just created and updated + // FIXME: Does this play fairly with PVS? + if ( updateType == DATA_UPDATE_CREATED ) + { + if ( m_bActive ) + { + // Delayed here so that we don't get invalid abs queries on level init with active particle systems + SetNextClientThink( gpGlobals->curtime ); + } + } + else + { + if ( m_bOldActive != m_bActive ) + { + if ( m_bActive ) + { + // Delayed here so that we don't get invalid abs queries on level init with active particle systems + SetNextClientThink( gpGlobals->curtime ); + } + else + { + ParticleProp()->StopEmission(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_ParticleSystem::ClientThink( void ) +{ + if ( m_bActive ) + { + const char *pszName = GetParticleSystemNameFromIndex( m_iEffectIndex ); + if ( pszName && pszName[0] ) + { + CNewParticleEffect *pEffect = ParticleProp()->Create( pszName, PATTACH_ABSORIGIN_FOLLOW ); + AssertMsg1( pEffect, "Particle system couldn't make %s", pszName ); + if (pEffect) + { + for ( int i = 0 ; i < kMAXCONTROLPOINTS ; ++i ) + { + CBaseEntity *pOnEntity = m_hControlPointEnts[i].Get(); + if ( pOnEntity ) + { + ParticleProp()->AddControlPoint( pEffect, i + 1, pOnEntity, PATTACH_ABSORIGIN_FOLLOW ); + } + + AssertMsg2( m_iControlPointParents[i] >= 0 && m_iControlPointParents[i] <= kMAXCONTROLPOINTS , + "Particle system specified bogus control point parent (%d) for point %d.", + m_iControlPointParents[i], i ); + + if (m_iControlPointParents[i] != 0) + { + pEffect->SetControlPointParent(i+1, m_iControlPointParents[i]); + } + } + + // NOTE: What we really want here is to compare our lifetime and that of our children and see if this delta is + // already past the end of it, denoting that we're finished. In that case, just destroy us and be done. -- jdw + + // TODO: This can go when the SkipToTime code below goes + ParticleProp()->OnParticleSystemUpdated( pEffect, 0.0f ); + + // Skip the effect ahead if we're restarting it + float flTimeDelta = gpGlobals->curtime - m_flStartTime; + if ( flTimeDelta > 0.01f ) + { + VPROF_BUDGET( "C_ParticleSystem::ClientThink SkipToTime", "Particle Simulation" ); + pEffect->SkipToTime( flTimeDelta ); + } + } + } + } +} + + +//====================================================================================================================== +// PARTICLE SYSTEM DISPATCH EFFECT +//====================================================================================================================== +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ParticleEffectCallback( const CEffectData &data ) +{ + if ( SuppressingParticleEffects() ) + return; // this needs to be before using data.m_nHitBox, since that may be a serialized value that's past the end of the current particle system string table + + const char *pszName = GetParticleSystemNameFromIndex( data.m_nHitBox ); + + if ( data.m_fFlags & PARTICLE_DISPATCH_FROM_ENTITY ) + { + if ( data.m_hEntity.Get() ) + { + C_BaseEntity *pEnt = C_BaseEntity::Instance( data.m_hEntity ); + if ( pEnt && !pEnt->IsDormant() ) + { + if ( data.m_fFlags & PARTICLE_DISPATCH_RESET_PARTICLES ) + { + pEnt->ParticleProp()->StopEmission(); + } + + CSmartPtr pEffect = pEnt->ParticleProp()->Create( pszName, (ParticleAttachment_t)data.m_nDamageType, data.m_nAttachmentIndex ); + AssertMsg2( pEffect.IsValid() && pEffect->IsValid(), "%s could not create particle effect %s", + C_BaseEntity::Instance( data.m_hEntity )->GetDebugName(), pszName ); + if ( pEffect.IsValid() && pEffect->IsValid() ) + { + if ( (ParticleAttachment_t)data.m_nDamageType == PATTACH_CUSTOMORIGIN ) + { + pEffect->SetSortOrigin( data.m_vOrigin ); + pEffect->SetControlPoint( 0, data.m_vOrigin ); + pEffect->SetControlPoint( 1, data.m_vStart ); + Vector vecForward, vecRight, vecUp; + AngleVectors( data.m_vAngles, &vecForward, &vecRight, &vecUp ); + pEffect->SetControlPointOrientation( 0, vecForward, vecRight, vecUp ); + } + } + } + } + } + else + { + CSmartPtr pEffect = CNewParticleEffect::Create( NULL, pszName ); + if ( pEffect->IsValid() ) + { + pEffect->SetSortOrigin( data.m_vOrigin ); + pEffect->SetControlPoint( 0, data.m_vOrigin ); + pEffect->SetControlPoint( 1, data.m_vStart ); + Vector vecForward, vecRight, vecUp; + AngleVectors( data.m_vAngles, &vecForward, &vecRight, &vecUp ); + pEffect->SetControlPointOrientation( 0, vecForward, vecRight, vecUp ); + } + } +} + +DECLARE_CLIENT_EFFECT( "ParticleEffect", ParticleEffectCallback ); + + +//====================================================================================================================== +// PARTICLE SYSTEM STOP EFFECT +//====================================================================================================================== +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ParticleEffectStopCallback( const CEffectData &data ) +{ + if ( data.m_hEntity.Get() ) + { + C_BaseEntity *pEnt = C_BaseEntity::Instance( data.m_hEntity ); + if ( pEnt ) + { + pEnt->ParticleProp()->StopEmission(); + } + } +} + +DECLARE_CLIENT_EFFECT( "ParticleEffectStop", ParticleEffectStopCallback ); diff --git a/game/client/c_physbox.cpp b/game/client/c_physbox.cpp new file mode 100644 index 00000000..26374d30 --- /dev/null +++ b/game/client/c_physbox.cpp @@ -0,0 +1,35 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_physbox.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT(C_PhysBox, DT_PhysBox, CPhysBox) + RecvPropFloat(RECVINFO(m_mass), 0), // Test.. +END_RECV_TABLE() + + +C_PhysBox::C_PhysBox() +{ +} + +//----------------------------------------------------------------------------- +// Should this object cast shadows? +//----------------------------------------------------------------------------- +ShadowType_t C_PhysBox::ShadowCastType() +{ + if (IsEffectActive(EF_NODRAW | EF_NOSHADOW)) + return SHADOWS_NONE; + return SHADOWS_RENDER_TO_TEXTURE; +} + +C_PhysBox::~C_PhysBox() +{ +} + diff --git a/game/client/c_physbox.h b/game/client/c_physbox.h new file mode 100644 index 00000000..0c7f0604 --- /dev/null +++ b/game/client/c_physbox.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + + + +// Client-side CBasePlayer + +#ifndef C_PHYSBOX_H +#define C_PHYSBOX_H +#pragma once + + +#include "c_baseentity.h" + + +class C_PhysBox : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_PhysBox, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_PhysBox(); + virtual ~C_PhysBox(); + virtual ShadowType_t ShadowCastType(); + +public: + float m_mass; // TEST.. +}; + + +#endif + + + diff --git a/game/client/c_physicsprop.cpp b/game/client/c_physicsprop.cpp new file mode 100644 index 00000000..5c20259e --- /dev/null +++ b/game/client/c_physicsprop.cpp @@ -0,0 +1,95 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "model_types.h" +#include "vcollide.h" +#include "vcollide_parse.h" +#include "solidsetdefaults.h" +#include "bone_setup.h" +#include "engine/ivmodelinfo.h" +#include "physics.h" +#include "view.h" +#include "clienteffectprecachesystem.h" +#include "c_physicsprop.h" +#include "tier0/vprof.h" +#include "ivrenderview.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT(C_PhysicsProp, DT_PhysicsProp, CPhysicsProp) + RecvPropBool( RECVINFO( m_bAwake ) ), +END_RECV_TABLE() + +ConVar r_PhysPropStaticLighting( "r_PhysPropStaticLighting", "1" ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_PhysicsProp::C_PhysicsProp( void ) +{ + m_pPhysicsObject = NULL; + m_takedamage = DAMAGE_YES; + + // default true so static lighting will get recomputed when we go to sleep + m_bAwakeLastTime = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_PhysicsProp::~C_PhysicsProp( void ) +{ +} + + +// @MULTICORE (toml 9/18/2006): this visualization will need to be implemented elsewhere +ConVar r_visualizeproplightcaching( "r_visualizeproplightcaching", "0" ); + +//----------------------------------------------------------------------------- +// Purpose: Draws the object +// Input : flags - +//----------------------------------------------------------------------------- +bool C_PhysicsProp::OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ) +{ + CreateModelInstance(); + + if ( r_PhysPropStaticLighting.GetBool() && m_bAwakeLastTime != m_bAwake ) + { + if ( m_bAwakeLastTime && !m_bAwake ) + { + // transition to sleep, bake lighting now, once + if ( !modelrender->RecomputeStaticLighting( GetModelInstance() ) ) + { + // not valid for drawing + return false; + } + + if ( r_visualizeproplightcaching.GetBool() ) + { + float color[] = { 0.0f, 1.0f, 0.0f, 1.0f }; + render->SetColorModulation( color ); + } + } + else if ( r_visualizeproplightcaching.GetBool() ) + { + float color[] = { 1.0f, 0.0f, 0.0f, 1.0f }; + render->SetColorModulation( color ); + } + } + + if ( !m_bAwake && r_PhysPropStaticLighting.GetBool() ) + { + // going to sleep, have static lighting + pInfo->flags |= STUDIO_STATIC_LIGHTING; + } + + // track state + m_bAwakeLastTime = m_bAwake; + + return true; +} diff --git a/game/client/c_physicsprop.h b/game/client/c_physicsprop.h new file mode 100644 index 00000000..f6365e8c --- /dev/null +++ b/game/client/c_physicsprop.h @@ -0,0 +1,34 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_PHYSICSPROP_H +#define C_PHYSICSPROP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_breakableprop.h" +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_PhysicsProp : public C_BreakableProp +{ + typedef C_BreakableProp BaseClass; +public: + DECLARE_CLIENTCLASS(); + + C_PhysicsProp(); + ~C_PhysicsProp(); + + virtual bool OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ); + +protected: + // Networked vars. + bool m_bAwake; + bool m_bAwakeLastTime; +}; + +#endif // C_PHYSICSPROP_H diff --git a/game/client/c_physmagnet.cpp b/game/client/c_physmagnet.cpp new file mode 100644 index 00000000..d6d20732 --- /dev/null +++ b/game/client/c_physmagnet.cpp @@ -0,0 +1,134 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "c_baseentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_PhysMagnet : public C_BaseAnimating +{ + DECLARE_CLASS( C_PhysMagnet, C_BaseAnimating ); +public: + DECLARE_CLIENTCLASS(); + + C_PhysMagnet(); + virtual ~C_PhysMagnet(); + + void PostDataUpdate( DataUpdateType_t updateType ); + bool GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const; + +public: + // Data received from the server + CUtlVector< int > m_aAttachedObjectsFromServer; + + // Private list of entities on the magnet + CUtlVector< EHANDLE > m_aAttachedObjects; +}; + +//----------------------------------------------------------------------------- +// Purpose: RecvProxy that converts the Magnet's attached object entindexes to handles +//----------------------------------------------------------------------------- +void RecvProxy_MagnetAttachedObjectList( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_PhysMagnet *pMagnet = (C_PhysMagnet*)pOut; + pMagnet->m_aAttachedObjectsFromServer[pData->m_iElement] = pData->m_Value.m_Int; +} + + +void RecvProxyArrayLength_MagnetAttachedArray( void *pStruct, int objectID, int currentArrayLength ) +{ + C_PhysMagnet *pMagnet = (C_PhysMagnet*)pStruct; + + if ( pMagnet->m_aAttachedObjectsFromServer.Size() != currentArrayLength ) + pMagnet->m_aAttachedObjectsFromServer.SetSize( currentArrayLength ); +} + +IMPLEMENT_CLIENTCLASS_DT(C_PhysMagnet, DT_PhysMagnet, CPhysMagnet) + + // ROBIN: Disabled because we don't need it anymore + /* + RecvPropArray2( + RecvProxyArrayLength_MagnetAttachedArray, + RecvPropInt( "magnetattached_array_element", 0, SIZEOF_IGNORE, 0, RecvProxy_MagnetAttachedObjectList ), + 128, + 0, + "magnetattached_array" + ) + */ + +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_PhysMagnet::C_PhysMagnet() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_PhysMagnet::~C_PhysMagnet() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PhysMagnet::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + /* + // First, detect any entities removed from the magnet and restore their shadows + int iCount = m_aAttachedObjects.Count(); + int iServerCount = m_aAttachedObjectsFromServer.Count(); + for ( int i = 0; i < iCount; i++ ) + { + int iEntIndex = m_aAttachedObjects[i]->entindex(); + for ( int j = 0; j < iServerCount; j++ ) + { + if ( iEntIndex == m_aAttachedObjectsFromServer[j] ) + break; + } + + if ( j == iServerCount ) + { + // Ok, a previously attached object is no longer attached + m_aAttachedObjects[i]->SetShadowUseOtherEntity( NULL ); + m_aAttachedObjects.Remove(i); + } + } + + // Make sure newly attached entities have vertical shadows too + for ( i = 0; i < iServerCount; i++ ) + { + C_BaseEntity *pEntity = cl_entitylist->GetEnt( m_aAttachedObjectsFromServer[i] ); + if ( m_aAttachedObjects.Find( pEntity ) == m_aAttachedObjects.InvalidIndex() ) + { + pEntity->SetShadowUseOtherEntity( this ); + m_aAttachedObjects.AddToTail( pEntity ); + } + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Return a per-entity shadow cast direction +//----------------------------------------------------------------------------- +bool C_PhysMagnet::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const +{ + // Magnets shadow is more vertical than others + //Vector vecDown = g_pClientShadowMgr->GetShadowDirection() - Vector(0,0,1); + //VectorNormalize( vecDown ); + //*pDirection = vecDown; + *pDirection = Vector(0,0,-1); + return true; +} \ No newline at end of file diff --git a/game/client/c_pixel_visibility.cpp b/game/client/c_pixel_visibility.cpp new file mode 100644 index 00000000..df62360b --- /dev/null +++ b/game/client/c_pixel_visibility.cpp @@ -0,0 +1,855 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "cbase.h" +#include "c_pixel_visibility.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterial.h" +#include "ClientEffectPrecacheSystem.h" +#include "view.h" +#include "utlmultilist.h" +#include "vprof.h" + +static void PixelvisDrawChanged( IConVar *pPixelvisVar, const char *pOld, float flOldValue ); + +ConVar r_pixelvisibility_partial( "r_pixelvisibility_partial", "1" ); +ConVar r_dopixelvisibility( "r_dopixelvisibility", "1" ); +ConVar r_drawpixelvisibility( "r_drawpixelvisibility", "0", 0, "Show the occlusion proxies", PixelvisDrawChanged ); +ConVar r_pixelvisibility_spew( "r_pixelvisibility_spew", "0" ); + +extern ConVar building_cubemaps; + +#ifndef _X360 +const float MIN_PROXY_PIXELS = 5.0f; +#else +const float MIN_PROXY_PIXELS = 25.0f; +#endif + +float PixelVisibility_DrawProxy( IMatRenderContext *pRenderContext, OcclusionQueryObjectHandle_t queryHandle, Vector origin, float scale, float proxyAspect, IMaterial *pMaterial, bool screenspace ) +{ + Vector point; + + // don't expand this with distance to fit pixels or the sprite will poke through + // only expand the parts perpendicular to the view + float forwardScale = scale; + // draw a pyramid of points touching a sphere of radius "scale" at origin + float pixelsPerUnit = pRenderContext->ComputePixelDiameterOfSphere( origin, 1.0f ); + pixelsPerUnit = max( pixelsPerUnit, 1e-4f ); + if ( screenspace ) + { + // Force this to be the size of a sphere of diameter "scale" at some reference distance (1.0 unit) + float pixelsPerUnit2 = pRenderContext->ComputePixelDiameterOfSphere( CurrentViewOrigin() + CurrentViewForward()*1.0f, scale*0.5f ); + // force drawing of "scale" pixels + scale = pixelsPerUnit2 / pixelsPerUnit; + } + else + { + float pixels = scale * pixelsPerUnit; + + // make the radius larger to ensure a minimum screen space size of the proxy geometry + if ( pixels < MIN_PROXY_PIXELS ) + { + scale = MIN_PROXY_PIXELS / pixelsPerUnit; + } + } + + // collapses the pyramid to a plane - so this could be a quad instead + Vector dir = origin - CurrentViewOrigin(); + VectorNormalize(dir); + origin -= dir * forwardScale; + forwardScale = 0.0f; + // + + Vector verts[5]; + const float sqrt2 = 0.707106781f; // sqrt(2) - keeps all vectors the same length from origin + scale *= sqrt2; + float scale45x = scale; + float scale45y = scale / proxyAspect; + verts[0] = origin - CurrentViewForward() * forwardScale; // the apex of the pyramid + verts[1] = origin + CurrentViewUp() * scale45y - CurrentViewRight() * scale45x; // these four form the base + verts[2] = origin + CurrentViewUp() * scale45y + CurrentViewRight() * scale45x; // the pyramid is a sprite with a point that + verts[3] = origin - CurrentViewUp() * scale45y + CurrentViewRight() * scale45x; // pokes back toward the camera through any nearby + verts[4] = origin - CurrentViewUp() * scale45y - CurrentViewRight() * scale45x; // geometry + + // get screen coords of edges + Vector screen[4]; + for ( int i = 0; i < 4; i++ ) + { + extern int ScreenTransform( const Vector& point, Vector& screen ); + if ( ScreenTransform( verts[i+1], screen[i] ) ) + return -1; + } + + // compute area and screen-clipped area + float w = screen[1].x - screen[0].x; + float h = screen[0].y - screen[3].y; + float ws = min(1.0f, screen[1].x) - max(-1.0f, screen[0].x); + float hs = min(1.0f, screen[0].y) - max(-1.0f, screen[3].y); + float area = w*h; // area can be zero when we ALT-TAB + float areaClipped = ws*hs; + float ratio = 0.0f; + if ( area != 0 ) + { + // compute the ratio of the area not clipped by the frustum to total area + ratio = areaClipped / area; + ratio = clamp(ratio, 0.0f, 1.0f); + } + + pRenderContext->BeginOcclusionQueryDrawing( queryHandle ); + CMeshBuilder meshBuilder; + IMesh* pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, pMaterial ); + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 4 ); + // draw a pyramid + for ( int i = 0; i < 4; i++ ) + { + int a = i+1; + int b = (a%4)+1; + meshBuilder.Position3fv( verts[0].Base() ); + meshBuilder.AdvanceVertex(); + meshBuilder.Position3fv( verts[a].Base() ); + meshBuilder.AdvanceVertex(); + meshBuilder.Position3fv( verts[b].Base() ); + meshBuilder.AdvanceVertex(); + } + meshBuilder.End(); + pMesh->Draw(); + + // sprite/quad proxy +#if 0 + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + VectorMA (origin, -scale, CurrentViewUp(), point); + VectorMA (point, -scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + VectorMA (origin, scale, CurrentViewUp(), point); + VectorMA (point, -scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + VectorMA (origin, scale, CurrentViewUp(), point); + VectorMA (point, scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + VectorMA (origin, -scale, CurrentViewUp(), point); + VectorMA (point, scale, CurrentViewRight(), point); + meshBuilder.Position3fv (point.Base()); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +#endif + pRenderContext->EndOcclusionQueryDrawing( queryHandle ); + + // fraction clipped by frustum + return ratio; +} + +class CPixelVisSet +{ +public: + void Init( const pixelvis_queryparams_t ¶ms ); + void MarkActive(); + bool IsActive(); + CPixelVisSet() + { + frameIssued = 0; + serial = 0; + queryList = 0xFFFF; + sizeIsScreenSpace = false; + } + +public: + float proxySize; + float proxyAspect; + float fadeTimeInv; + unsigned short queryList; + unsigned short serial; + bool sizeIsScreenSpace; +private: + int frameIssued; +}; + + +void CPixelVisSet::Init( const pixelvis_queryparams_t ¶ms ) +{ + Assert( params.bSetup ); + proxySize = params.proxySize; + proxyAspect = params.proxyAspect; + if ( params.fadeTime > 0.0f ) + { + fadeTimeInv = 1.0f / params.fadeTime; + } + else + { + // fade in over 0.125 seconds + fadeTimeInv = 1.0f / 0.125f; + } + frameIssued = 0; + sizeIsScreenSpace = params.bSizeInScreenspace; +} + +void CPixelVisSet::MarkActive() +{ + frameIssued = gpGlobals->framecount; +} + +bool CPixelVisSet::IsActive() +{ + return (gpGlobals->framecount - frameIssued) > 1 ? false : true; +} + +class CPixelVisibilityQuery +{ +public: + CPixelVisibilityQuery(); + ~CPixelVisibilityQuery(); + bool IsValid(); + bool IsForView( int viewID ); + bool IsActive(); + float GetFractionVisible( float fadeTimeInv ); + void IssueQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace ); + void IssueCountingQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace ); + void ResetOcclusionQueries(); + void SetView( int viewID ) + { + m_viewID = viewID; + m_brightnessTarget = 0.0f; + m_clipFraction = 1.0f; + m_frameIssued = -1; + m_failed = false; + m_wasQueriedThisFrame = false; + m_hasValidQueryResults = false; + } + +public: + Vector m_origin; + int m_frameIssued; +private: + float m_brightnessTarget; + float m_clipFraction; + OcclusionQueryObjectHandle_t m_queryHandle; + OcclusionQueryObjectHandle_t m_queryHandleCount; + unsigned short m_wasQueriedThisFrame : 1; + unsigned short m_failed : 1; + unsigned short m_hasValidQueryResults : 1; + unsigned short m_pad : 13; + unsigned short m_viewID; + + friend void PixelVisibility_ShiftVisibilityViews( int iSourceViewID, int iDestViewID ); //need direct access to private data to make shifting smooth +}; + +CPixelVisibilityQuery::CPixelVisibilityQuery() +{ + CMatRenderContextPtr pRenderContext( materials ); + SetView( 0xFFFF ); + m_queryHandle = pRenderContext->CreateOcclusionQueryObject(); + m_queryHandleCount = pRenderContext->CreateOcclusionQueryObject(); +} + +CPixelVisibilityQuery::~CPixelVisibilityQuery() +{ + CMatRenderContextPtr pRenderContext( materials ); + if ( m_queryHandle != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE ) + { + pRenderContext->DestroyOcclusionQueryObject( m_queryHandle ); + } + if ( m_queryHandleCount != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE ) + { + pRenderContext->DestroyOcclusionQueryObject( m_queryHandleCount ); + } +} + +void CPixelVisibilityQuery::ResetOcclusionQueries() +{ + // NOTE: Since we're keeping the CPixelVisibilityQuery objects around in a pool + // and not actually deleting them, this means that our material system occlusion queries are + // not being deleted either. Which means that if a CPixelVisibilityQuery is + // put into the free list and then immediately re-used, then we have an opportunity for + // a bug: What can happen on the first frame of the material system query + // is that if the query isn't done yet, it will use the last queried value + // which will happen to be set to the value of the last query done + // for the previous CPixelVisSet the CPixelVisibilityQuery happened to be associated with + // which makes queries have an invalid value for the first frame + + // This will mark the occlusion query objects as not ever having been read from before + CMatRenderContextPtr pRenderContext( materials ); + if ( m_queryHandle != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE ) + { + pRenderContext->ResetOcclusionQueryObject( m_queryHandle ); + } + if ( m_queryHandleCount != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE ) + { + pRenderContext->ResetOcclusionQueryObject( m_queryHandleCount ); + } +} + +bool CPixelVisibilityQuery::IsValid() +{ + return (m_queryHandle != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE) ? true : false; +} +bool CPixelVisibilityQuery::IsForView( int viewID ) +{ + return m_viewID == viewID ? true : false; +} + +bool CPixelVisibilityQuery::IsActive() +{ + return (gpGlobals->framecount - m_frameIssued) > 1 ? false : true; +} + +float CPixelVisibilityQuery::GetFractionVisible( float fadeTimeInv ) +{ + if ( !IsValid() ) + return 0.0f; + + if ( !m_wasQueriedThisFrame ) + { + CMatRenderContextPtr pRenderContext( materials ); + m_wasQueriedThisFrame = true; + int pixels = -1; + int pixelsPossible = -1; + if ( r_pixelvisibility_partial.GetBool() ) + { + if ( m_frameIssued != -1 ) + { + pixelsPossible = pRenderContext->OcclusionQuery_GetNumPixelsRendered( m_queryHandleCount ); + pixels = pRenderContext->OcclusionQuery_GetNumPixelsRendered( m_queryHandle ); + } + + if ( r_pixelvisibility_spew.GetBool() && CurrentViewID() == 0 ) + { + DevMsg( 1, "Pixels visible: %d (qh:%d) Pixels possible: %d (qh:%d) (frame:%d)\n", pixels, m_queryHandle, pixelsPossible, m_queryHandleCount, gpGlobals->framecount ); + } + + if ( pixels < 0 || pixelsPossible < 0 ) + { + m_failed = ( m_frameIssued >= 0 ) ? true : false; + return m_brightnessTarget * m_clipFraction; + } + m_hasValidQueryResults = true; + + if ( pixelsPossible > 0 ) + { + float target = (float)pixels / (float)pixelsPossible; + target = (target >= 0.95f) ? 1.0f : (target < 0.0f) ? 0.0f : target; + float rate = gpGlobals->frametime * fadeTimeInv; + m_brightnessTarget = Approach( target, m_brightnessTarget, rate ); // fade in / out + } + else + { + m_brightnessTarget = 0.0f; + } + } + else + { + if ( m_frameIssued != -1 ) + { + pixels = pRenderContext->OcclusionQuery_GetNumPixelsRendered( m_queryHandle ); + } + + if ( r_pixelvisibility_spew.GetBool() && CurrentViewID() == 0 ) + { + DevMsg( 1, "Pixels visible: %d (qh:%d) (frame:%d)\n", pixels, m_queryHandle, gpGlobals->framecount ); + } + + if ( pixels < 0 ) + { + m_failed = ( m_frameIssued >= 0 ) ? true : false; + return m_brightnessTarget * m_clipFraction; + } + m_hasValidQueryResults = true; + if ( m_frameIssued == gpGlobals->framecount-1 ) + { + float rate = gpGlobals->frametime * fadeTimeInv; + float target = 0.0f; + if ( pixels > 0 ) + { + // fade in slower than you fade out + rate *= 0.5f; + target = 1.0f; + } + m_brightnessTarget = Approach( target, m_brightnessTarget, rate ); // fade in / out + } + else + { + m_brightnessTarget = 0.0f; + } + } + } + + return m_brightnessTarget * m_clipFraction; +} + +void CPixelVisibilityQuery::IssueQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace ) +{ + if ( !m_failed ) + { + Assert( IsValid() ); + + if ( r_pixelvisibility_spew.GetBool() && CurrentViewID() == 0 ) + { + DevMsg( 1, "Draw Proxy: qh:%d org:<%d,%d,%d> (frame:%d)\n", m_queryHandle, (int)m_origin[0], (int)m_origin[1], (int)m_origin[2], gpGlobals->framecount ); + } + + m_clipFraction = PixelVisibility_DrawProxy( pRenderContext, m_queryHandle, m_origin, proxySize, proxyAspect, pMaterial, sizeIsScreenSpace ); + if ( m_clipFraction < 0 ) + { + // NOTE: In this case, the proxy wasn't issued cause it was offscreen + // can't set the m_frameissued field since that would cause it to get marked as failed + m_clipFraction = 0; + m_wasQueriedThisFrame = false; + m_failed = false; + return; + } + } +#ifndef PORTAL // FIXME: In portal we query visibility multiple times per frame because of portal renders! + Assert(m_frameIssued != gpGlobals->framecount); +#endif + + m_frameIssued = gpGlobals->framecount; + m_wasQueriedThisFrame = false; + m_failed = false; +} + +void CPixelVisibilityQuery::IssueCountingQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace ) +{ + if ( !m_failed ) + { + Assert( IsValid() ); +#if 0 + // this centers it on the screen. + // This is nice because it makes the glows fade as they get partially clipped by the view frustum + // But it introduces sub-pixel errors (off by one row/column of pixels) so the glows shimmer + // UNDONE: Compute an offset center coord that matches sub-pixel coords with the real glow position + // UNDONE: Or frustum clip the sphere/geometry and fade based on proxy size + Vector origin = m_origin - CurrentViewOrigin(); + float dot = DotProduct(CurrentViewForward(), origin); + origin = CurrentViewOrigin() + dot * CurrentViewForward(); +#endif + PixelVisibility_DrawProxy( pRenderContext, m_queryHandleCount, m_origin, proxySize, proxyAspect, pMaterial, sizeIsScreenSpace ); + } +} + +//Precache the effects +CLIENTEFFECT_REGISTER_BEGIN( PrecacheOcclusionProxy ) +CLIENTEFFECT_MATERIAL( "engine/occlusionproxy" ) +CLIENTEFFECT_MATERIAL( "engine/occlusionproxy_countdraw" ) +CLIENTEFFECT_REGISTER_END() + +class CPixelVisibilitySystem : public CAutoGameSystem +{ +public: + + // GameSystem: Level init, shutdown + virtual void LevelInitPreEntity(); + virtual void LevelShutdownPostEntity(); + + // locals + CPixelVisibilitySystem(); + float GetFractionVisible( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle ); + void EndView(); + void EndScene(); + unsigned short FindQueryForView( CPixelVisSet *pSet, int viewID ); + unsigned short FindOrCreateQueryForView( CPixelVisSet *pSet, int viewID ); + + void DeleteUnusedQueries( CPixelVisSet *pSet, bool bDeleteAll ); + void DeleteUnusedSets( bool bDeleteAll ); + void ShowQueries( bool show ); + unsigned short AllocQuery(); + unsigned short AllocSet(); + void FreeSet( unsigned short node ); + CPixelVisSet *FindOrCreatePixelVisSet( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle ); + bool SupportsOcclusion() { return m_hwCanTestGlows; } + void DebugInfo() + { + Msg("Pixel vis system using %d sets total (%d in free list), %d queries total (%d in free list)\n", + m_setList.TotalCount(), m_setList.Count(m_freeSetsList), m_queryList.TotalCount(), m_queryList.Count( m_freeQueriesList ) ); + } + +private: + CUtlMultiList< CPixelVisSet, unsigned short > m_setList; + CUtlMultiList m_queryList; + unsigned short m_freeQueriesList; + unsigned short m_activeSetsList; + unsigned short m_freeSetsList; + unsigned short m_pad0; + + IMaterial *m_pProxyMaterial; + IMaterial *m_pDrawMaterial; + bool m_hwCanTestGlows; + bool m_drawQueries; + + + friend void PixelVisibility_ShiftVisibilityViews( int iSourceViewID, int iDestViewID ); //need direct access to private data to make shifting smooth +}; + +static CPixelVisibilitySystem g_PixelVisibilitySystem; + +CPixelVisibilitySystem::CPixelVisibilitySystem() : CAutoGameSystem( "CPixelVisibilitySystem" ) +{ + m_hwCanTestGlows = true; + m_drawQueries = false; +} +// Level init, shutdown +void CPixelVisibilitySystem::LevelInitPreEntity() +{ + m_hwCanTestGlows = r_dopixelvisibility.GetBool() && engine->GetDXSupportLevel() >= 80; + if ( m_hwCanTestGlows ) + { + CMatRenderContextPtr pRenderContext( materials ); + + OcclusionQueryObjectHandle_t query = pRenderContext->CreateOcclusionQueryObject(); + if ( query != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE ) + { + pRenderContext->DestroyOcclusionQueryObject( query ); + } + else + { + m_hwCanTestGlows = false; + } + } + + m_pProxyMaterial = materials->FindMaterial("engine/occlusionproxy", TEXTURE_GROUP_CLIENT_EFFECTS); + m_pProxyMaterial->IncrementReferenceCount(); + m_pDrawMaterial = materials->FindMaterial("engine/occlusionproxy_countdraw", TEXTURE_GROUP_CLIENT_EFFECTS); + m_pDrawMaterial->IncrementReferenceCount(); + m_freeQueriesList = m_queryList.CreateList(); + m_activeSetsList = m_setList.CreateList(); + m_freeSetsList = m_setList.CreateList(); +} + +void CPixelVisibilitySystem::LevelShutdownPostEntity() +{ + m_pProxyMaterial->DecrementReferenceCount(); + m_pProxyMaterial = NULL; + m_pDrawMaterial->DecrementReferenceCount(); + m_pDrawMaterial = NULL; + DeleteUnusedSets(true); + m_setList.Purge(); + m_queryList.Purge(); + m_freeQueriesList = m_queryList.InvalidIndex(); + m_activeSetsList = m_setList.InvalidIndex(); + m_freeSetsList = m_setList.InvalidIndex(); +} + +float CPixelVisibilitySystem::GetFractionVisible( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle ) +{ + if ( !m_hwCanTestGlows || building_cubemaps.GetBool() ) + { + return GlowSightDistance( params.position, true ) > 0 ? 1.0f : 0.0f; + } + if ( CurrentViewID() < 0 ) + return 0.0f; + + CPixelVisSet *pSet = FindOrCreatePixelVisSet( params, queryHandle ); + Assert( pSet ); + unsigned short node = FindOrCreateQueryForView( pSet, CurrentViewID() ); + m_queryList[node].m_origin = params.position; + float fraction = m_queryList[node].GetFractionVisible( pSet->fadeTimeInv ); + pSet->MarkActive(); + return fraction; +} + +void CPixelVisibilitySystem::EndView() +{ + if ( !PixelVisibility_IsAvailable() && CurrentViewID() >= 0 ) + return; + + if ( m_setList.Head( m_activeSetsList ) == m_setList.InvalidIndex() ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + IMaterial *pProxy = m_drawQueries ? m_pDrawMaterial : m_pProxyMaterial; + pRenderContext->Bind( pProxy ); + + // BUGBUG: If you draw both queries, the measure query fails for some reason. + if ( r_pixelvisibility_partial.GetBool() && !m_drawQueries ) + { + pRenderContext->DepthRange( 0.0f, 0.01f ); + unsigned short node = m_setList.Head( m_activeSetsList ); + while( node != m_setList.InvalidIndex() ) + { + CPixelVisSet *pSet = &m_setList[node]; + unsigned short queryNode = FindQueryForView( pSet, CurrentViewID() ); + if ( queryNode != m_queryList.InvalidIndex() ) + { + m_queryList[queryNode].IssueCountingQuery( pRenderContext, pSet->proxySize, pSet->proxyAspect, pProxy, pSet->sizeIsScreenSpace ); + } + node = m_setList.Next( node ); + } + pRenderContext->DepthRange( 0.0f, 1.0f ); + } + + { + unsigned short node = m_setList.Head( m_activeSetsList ); + while( node != m_setList.InvalidIndex() ) + { + CPixelVisSet *pSet = &m_setList[node]; + unsigned short queryNode = FindQueryForView( pSet, CurrentViewID() ); + if ( queryNode != m_queryList.InvalidIndex() ) + { + m_queryList[queryNode].IssueQuery( pRenderContext, pSet->proxySize, pSet->proxyAspect, pProxy, pSet->sizeIsScreenSpace ); + } + node = m_setList.Next( node ); + } + } +} + +void CPixelVisibilitySystem::EndScene() +{ + DeleteUnusedSets(false); +} + +unsigned short CPixelVisibilitySystem::FindQueryForView( CPixelVisSet *pSet, int viewID ) +{ + unsigned short node = m_queryList.Head( pSet->queryList ); + while ( node != m_queryList.InvalidIndex() ) + { + if ( m_queryList[node].IsForView( viewID ) ) + return node; + node = m_queryList.Next( node ); + } + return m_queryList.InvalidIndex(); +} +unsigned short CPixelVisibilitySystem::FindOrCreateQueryForView( CPixelVisSet *pSet, int viewID ) +{ + unsigned short node = FindQueryForView( pSet, viewID ); + if ( node != m_queryList.InvalidIndex() ) + return node; + + node = AllocQuery(); + m_queryList.LinkToHead( pSet->queryList, node ); + m_queryList[node].SetView( viewID ); + return node; +} + + +void CPixelVisibilitySystem::DeleteUnusedQueries( CPixelVisSet *pSet, bool bDeleteAll ) +{ + unsigned short node = m_queryList.Head( pSet->queryList ); + while ( node != m_queryList.InvalidIndex() ) + { + unsigned short next = m_queryList.Next( node ); + if ( bDeleteAll || !m_queryList[node].IsActive() ) + { + m_queryList.Unlink( pSet->queryList, node); + m_queryList.LinkToHead( m_freeQueriesList, node ); + } + node = next; + } +} +void CPixelVisibilitySystem::DeleteUnusedSets( bool bDeleteAll ) +{ + unsigned short node = m_setList.Head( m_activeSetsList ); + while ( node != m_setList.InvalidIndex() ) + { + unsigned short next = m_setList.Next( node ); + CPixelVisSet *pSet = &m_setList[node]; + if ( bDeleteAll || !m_setList[node].IsActive() ) + { + DeleteUnusedQueries( pSet, true ); + } + else + { + DeleteUnusedQueries( pSet, false ); + } + if ( m_queryList.Head(pSet->queryList) == m_queryList.InvalidIndex() ) + { + FreeSet( node ); + } + node = next; + } +} + +void CPixelVisibilitySystem::ShowQueries( bool show ) +{ + m_drawQueries = show; +} + +unsigned short CPixelVisibilitySystem::AllocQuery() +{ + unsigned short node = m_queryList.Head(m_freeQueriesList); + if ( node != m_queryList.InvalidIndex() ) + { + m_queryList.Unlink( m_freeQueriesList, node ); + m_queryList[node].ResetOcclusionQueries(); + } + else + { + node = m_queryList.Alloc(); + } + return node; +} + +unsigned short CPixelVisibilitySystem::AllocSet() +{ + unsigned short node = m_setList.Head(m_freeSetsList); + if ( node != m_setList.InvalidIndex() ) + { + m_setList.Unlink( m_freeSetsList, node ); + } + else + { + node = m_setList.Alloc(); + m_setList[node].queryList = m_queryList.CreateList(); + } + m_setList.LinkToHead( m_activeSetsList, node ); + return node; +} + +void CPixelVisibilitySystem::FreeSet( unsigned short node ) +{ + m_setList.Unlink( m_activeSetsList, node ); + m_setList.LinkToHead( m_freeSetsList, node ); + m_setList[node].serial++; +} + +CPixelVisSet *CPixelVisibilitySystem::FindOrCreatePixelVisSet( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle ) +{ + if ( queryHandle[0] ) + { + unsigned short handle = queryHandle[0] & 0xFFFF; + handle--; + unsigned short serial = queryHandle[0] >> 16; + if ( m_setList.IsValidIndex(handle) && m_setList[handle].serial == serial ) + { + return &m_setList[handle]; + } + } + + unsigned short node = AllocSet(); + m_setList[node].Init( params ); + unsigned int out = m_setList[node].serial; + unsigned short nodeHandle = node + 1; + out <<= 16; + out |= nodeHandle; + queryHandle[0] = out; + return &m_setList[node]; +} + + +void PixelvisDrawChanged( IConVar *pPixelvisVar, const char *pOld, float flOldValue ) +{ + ConVarRef var( pPixelvisVar ); + g_PixelVisibilitySystem.ShowQueries( var.GetBool() ); +} + +class CTraceFilterGlow : public CTraceFilterSimple +{ +public: + DECLARE_CLASS( CTraceFilterGlow, CTraceFilterSimple ); + + CTraceFilterGlow( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple(passentity, collisionGroup) {} + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + IClientUnknown *pUnk = (IClientUnknown*)pHandleEntity; + ICollideable *pCollide = pUnk->GetCollideable(); + if ( pCollide->GetSolid() != SOLID_VPHYSICS && pCollide->GetSolid() != SOLID_BSP ) + return false; + return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); + } +}; +float GlowSightDistance( const Vector &glowOrigin, bool bShouldTrace ) +{ + float dist = (glowOrigin - CurrentViewOrigin()).Length(); + C_BasePlayer *local = C_BasePlayer::GetLocalPlayer(); + if ( local ) + { + dist *= local->GetFOVDistanceAdjustFactor(); + } + + if ( bShouldTrace ) + { + Vector end = glowOrigin; + // HACKHACK: trace 4" from destination in case the glow is inside some parent object + // allow a little error... + if ( dist > 4 ) + { + end -= CurrentViewForward()*4; + } + int traceFlags = MASK_OPAQUE|CONTENTS_MONSTER|CONTENTS_DEBRIS; + + CTraceFilterGlow filter(NULL, COLLISION_GROUP_NONE); + trace_t tr; + UTIL_TraceLine( CurrentViewOrigin(), end, traceFlags, &filter, &tr ); + if ( tr.fraction != 1.0f ) + return -1; + } + + return dist; +} + +void PixelVisibility_EndCurrentView() +{ + g_PixelVisibilitySystem.EndView(); +} + +void PixelVisibility_EndScene() +{ + g_PixelVisibilitySystem.EndScene(); +} + +float PixelVisibility_FractionVisible( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle ) +{ + if ( !queryHandle ) + { + return GlowSightDistance( params.position, true ) > 0.0f ? 1.0f : 0.0f; + } + else + { + return g_PixelVisibilitySystem.GetFractionVisible( params, queryHandle ); + } +} + +bool PixelVisibility_IsAvailable() +{ + return r_dopixelvisibility.GetBool() && g_PixelVisibilitySystem.SupportsOcclusion(); +} + +//this originally called a class function of CPixelVisibiltySystem to keep the work clean, but that function needed friend access to CPixelVisibilityQuery +//and I didn't want to make the whole class a friend or shift all the functions and class declarations around in this file +void PixelVisibility_ShiftVisibilityViews( int iSourceViewID, int iDestViewID ) +{ + unsigned short node = g_PixelVisibilitySystem.m_setList.Head( g_PixelVisibilitySystem.m_activeSetsList ); + while ( node != g_PixelVisibilitySystem.m_setList.InvalidIndex() ) + { + unsigned short next = g_PixelVisibilitySystem.m_setList.Next( node ); + CPixelVisSet *pSet = &g_PixelVisibilitySystem.m_setList[node]; + + unsigned short iSourceQueryNode = g_PixelVisibilitySystem.FindQueryForView( pSet, iSourceViewID ); + unsigned short iDestQueryNode = g_PixelVisibilitySystem.FindQueryForView( pSet, iDestViewID ); + + if( iDestQueryNode != g_PixelVisibilitySystem.m_queryList.InvalidIndex() ) + { + //delete the destination if found + g_PixelVisibilitySystem.m_queryList.Unlink( pSet->queryList, iDestQueryNode ); + g_PixelVisibilitySystem.m_queryList.LinkToHead( g_PixelVisibilitySystem.m_freeQueriesList, iDestQueryNode ); + + if ( g_PixelVisibilitySystem.m_queryList.Head(pSet->queryList) == g_PixelVisibilitySystem.m_queryList.InvalidIndex() ) + { + g_PixelVisibilitySystem.FreeSet( node ); + } + } + + if( iSourceQueryNode != g_PixelVisibilitySystem.m_queryList.InvalidIndex() ) + { + //make the source believe it's the destination + g_PixelVisibilitySystem.m_queryList[iSourceQueryNode].m_viewID = iDestViewID; + } + + node = next; + } +} + +CON_COMMAND( pixelvis_debug, "Dump debug info" ) +{ + g_PixelVisibilitySystem.DebugInfo(); +} diff --git a/game/client/c_pixel_visibility.h b/game/client/c_pixel_visibility.h new file mode 100644 index 00000000..9bc0cc5b --- /dev/null +++ b/game/client/c_pixel_visibility.h @@ -0,0 +1,55 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_PIXEL_VISIBILITY_H +#define C_PIXEL_VISIBILITY_H +#ifdef _WIN32 +#pragma once +#endif + + +const float PIXELVIS_DEFAULT_PROXY_SIZE = 2.0f; +const float PIXELVIS_DEFAULT_FADE_TIME = 0.0625f; + +typedef int pixelvis_handle_t; +struct pixelvis_queryparams_t +{ + pixelvis_queryparams_t() + { + bSetup = false; + } + + void Init( const Vector &origin, float proxySizeIn = PIXELVIS_DEFAULT_PROXY_SIZE, float proxyAspectIn = 1.0f, float fadeTimeIn = PIXELVIS_DEFAULT_FADE_TIME ) + { + position = origin; + proxySize = proxySizeIn; + proxyAspect = proxyAspectIn; + fadeTime = fadeTimeIn; + bSetup = true; + bSizeInScreenspace = false; + } + + Vector position; + float proxySize; + float proxyAspect; + float fadeTime; + bool bSetup; + bool bSizeInScreenspace; +}; + +float PixelVisibility_FractionVisible( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle ); +float StandardGlowBlend( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle, int rendermode, int renderfx, int alpha, float *pscale ); + +void PixelVisibility_ShiftVisibilityViews( int iSourceViewID, int iDestViewID ); //mainly needed by portal mod to avoid a pop in visibility when teleporting the player + +void PixelVisibility_EndCurrentView(); +void PixelVisibility_EndScene(); +float GlowSightDistance( const Vector &glowOrigin, bool bShouldTrace ); + +// returns true if the video hardware is doing the tests, false is traceline is doing so. +bool PixelVisibility_IsAvailable(); + +#endif // C_PIXEL_VISIBILITY_H diff --git a/game/client/c_plasma.cpp b/game/client/c_plasma.cpp new file mode 100644 index 00000000..f9bf76b1 --- /dev/null +++ b/game/client/c_plasma.cpp @@ -0,0 +1,476 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "particles_simple.h" +#include "tempent.h" +#include "iefx.h" +#include "decals.h" +#include "IViewRender.h" +#include "engine/ivmodelinfo.h" +#include "view.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define NUM_CHILD_FLAMES 6 +#define CHILD_SPREAD 40 + +//================================================== +// C_Plasma +//================================================== + +//NOTENOTE: Mirrored in dlls/fire_smoke.h +#define bitsFIRESMOKE_NONE 0x00000000 +#define bitsFIRESMOKE_ACTIVE 0x00000001 + + +class C_PlasmaSprite : public C_Sprite +{ + DECLARE_CLASS( C_PlasmaSprite, C_Sprite ); + +public: + Vector m_vecMoveDir; +}; + + +class C_Plasma : public C_BaseEntity +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_Plasma, C_BaseEntity ); + + C_Plasma(); + ~C_Plasma(); + + void AddEntity( void ); + +protected: + void Update( void ); + void UpdateAnimation( void ); + void UpdateScale( void ); + void UpdateFlames( void ); + void AddFlames( void ); + void Start( void ); + + float GetFlickerScale( void ); + +//C_BaseEntity +public: + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool ShouldDraw(); + +//From the server +public: + float m_flStartScale; + float m_flScale; + float m_flScaleTime; + int m_nFlags; + int m_nPlasmaModelIndex; + int m_nPlasmaModelIndex2; + int m_nGlowModelIndex; + +//Client-side only +public: + float m_flScaleRegister; + float m_flScaleStart; + float m_flScaleEnd; + float m_flScaleTimeStart; + float m_flScaleTimeEnd; + + VPlane m_planeClip; + bool m_bClipTested; + +protected: + C_PlasmaSprite m_entFlames[NUM_CHILD_FLAMES]; + float m_entFlameScales[NUM_CHILD_FLAMES]; + + C_Sprite m_entGlow; + float m_flGlowScale; + + TimedEvent m_tParticleSpawn; + TimedEvent m_tDecalSpawn; + +private: + C_Plasma( const C_Plasma & ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pRecvProp - +// *pStruct - +// *pVarData - +// *pIn - +// objectID - +//----------------------------------------------------------------------------- +void RecvProxy_PlasmaScale( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_Plasma *pPlasmaSmoke = (C_Plasma *) pStruct; + float scale = pData->m_Value.m_Float; + + //If changed, update our internal information + if ( pPlasmaSmoke->m_flScale != scale ) + { + pPlasmaSmoke->m_flScaleStart = pPlasmaSmoke->m_flScaleRegister; + pPlasmaSmoke->m_flScaleEnd = scale; + + pPlasmaSmoke->m_flScale = scale; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pRecvProp - +// *pStruct - +// *pVarData - +// *pIn - +// objectID - +//----------------------------------------------------------------------------- +void RecvProxy_PlasmaScaleTime( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_Plasma *pPlasmaSmoke = (C_Plasma *) pStruct; + float time = pData->m_Value.m_Float; + + //If changed, update our internal information + if ( pPlasmaSmoke->m_flScaleTime != time ) + { + if ( time == -1.0f ) + { + pPlasmaSmoke->m_flScaleTimeStart = gpGlobals->curtime-1.0f; + pPlasmaSmoke->m_flScaleTimeEnd = pPlasmaSmoke->m_flScaleTimeStart; + } + else + { + pPlasmaSmoke->m_flScaleTimeStart = gpGlobals->curtime; + pPlasmaSmoke->m_flScaleTimeEnd = gpGlobals->curtime + time; + } + + pPlasmaSmoke->m_flScaleTime = time; + } + + +} + +//Receive datatable +IMPLEMENT_CLIENTCLASS_DT( C_Plasma, DT_Plasma, CPlasma ) + RecvPropFloat( RECVINFO( m_flStartScale )), + RecvPropFloat( RECVINFO( m_flScale ), 0, RecvProxy_PlasmaScale ), + RecvPropFloat( RECVINFO( m_flScaleTime ), 0, RecvProxy_PlasmaScaleTime ), + RecvPropInt( RECVINFO( m_nFlags ) ), + RecvPropInt( RECVINFO( m_nPlasmaModelIndex ) ), + RecvPropInt( RECVINFO( m_nPlasmaModelIndex2 ) ), + RecvPropInt( RECVINFO( m_nGlowModelIndex ) ), +END_RECV_TABLE() + +//================================================== +// C_Plasma +//================================================== + +C_Plasma::C_Plasma() +{ + //Server-side + m_flStartScale = 0.0f; + m_flScale = 0.0f; + m_flScaleTime = 0.0f; + m_nFlags = bitsFIRESMOKE_NONE; + m_nPlasmaModelIndex = 0; + m_nPlasmaModelIndex2 = 0; + m_nGlowModelIndex = 0; + + //Client-side + m_flScaleRegister = 0.0f; + m_flScaleStart = 0.0f; + m_flScaleEnd = 0.0f; + m_flScaleTimeStart = 0.0f; + m_flScaleTimeEnd = 0.0f; + m_flGlowScale = 0.0f; + m_bClipTested = false; + + m_entGlow.Clear(); + + //Clear all child flames + for ( int i = 0; i < NUM_CHILD_FLAMES; i++ ) + { + m_entFlames[i].Clear(); + } +} + +C_Plasma::~C_Plasma() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float C_Plasma::GetFlickerScale( void ) +{ + float result = 0.0f; + + result = sin( gpGlobals->curtime * 10000.0f ); + result += 0.5f * sin( gpGlobals->curtime * 2000.0f ); + result -= 0.5f * cos( gpGlobals->curtime * 8000.0f ); + + return result * 0.1f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Plasma::AddEntity( void ) +{ + //Only do this if we're active + if ( ( m_nFlags & bitsFIRESMOKE_ACTIVE ) == false ) + return; + + Update(); + AddFlames(); + + float dScale = m_flScaleRegister - m_flGlowScale; + m_flGlowScale = m_flScaleRegister; + + // Note: Sprite renderer assumes scale of 0.0 is 1.0 + m_entGlow.SetScale( max( 0.0000001f, (m_flScaleRegister*1.5f) + GetFlickerScale() ) ); + m_entGlow.SetLocalOriginDim( Z_INDEX, m_entGlow.GetLocalOriginDim( Z_INDEX ) + ( dScale * 32.0f ) ); +} + +#define FLAME_ALPHA_START 0.8f +#define FLAME_ALPHA_END 1.0f + +#define FLAME_TRANS_START 0.75f + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Plasma::AddFlames( void ) +{ + Vector viewDir = GetAbsOrigin() - CurrentViewOrigin(); + VectorNormalize(viewDir); + float dot = viewDir.Dot( Vector( 0, 0, 1 ) ); //NOTENOTE: Flames always point up + float alpha = 1.0f; + + dot = fabs( dot ); + + if ( dot < FLAME_ALPHA_START ) + { + alpha = 1.0f; + } + + for ( int i = 0; i < NUM_CHILD_FLAMES; i++ ) + { + if ( m_entFlames[i].GetScale() > 0.0f ) + { + m_entFlames[i].SetRenderColor( ( 255.0f * alpha ), ( 255.0f * alpha ), ( 255.0f * alpha ) ); + m_entFlames[i].SetBrightness( 255.0f * alpha ); + } + + m_entFlames[i].AddToLeafSystem(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_Plasma::OnDataChanged( DataUpdateType_t updateType ) +{ + C_BaseEntity::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + Start(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_Plasma::ShouldDraw() +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Plasma::Start( void ) +{ + //Various setup info + m_tParticleSpawn.Init( 10.0f ); + m_tDecalSpawn.Init( 20.0f); + + QAngle offset; + int maxFrames; + + // Setup the child flames + int i; + for ( i = 0; i < NUM_CHILD_FLAMES; i++ ) + { + //Setup our offset angles + offset[0] = 0.0f; + offset[1] = random->RandomFloat( 0, 360 ); + offset[2] = 0.0f; + + AngleVectors( offset, &m_entFlames[i].m_vecMoveDir ); + + int nModelIndex = ( i % 2 ) ? m_nPlasmaModelIndex : m_nPlasmaModelIndex2; + + model_t *pModel = (model_t *) modelinfo->GetModel( nModelIndex ); + maxFrames = modelinfo->GetModelFrameCount( pModel ); + + // Setup all the information for the client entity + m_entFlames[i].SetModelByIndex( nModelIndex ); + m_entFlames[i].SetLocalOrigin( GetLocalOrigin() ); + m_entFlames[i].m_flFrame = random->RandomInt( 0.0f, maxFrames ); + m_entFlames[i].m_flSpriteFramerate = (float) random->RandomInt( 15, 20 ); + m_entFlames[i].SetScale( m_flStartScale ); + m_entFlames[i].SetRenderMode( kRenderTransAddFrameBlend ); + m_entFlames[i].m_nRenderFX = kRenderFxNone; + m_entFlames[i].SetRenderColor( 255, 255, 255, 255 ); + m_entFlames[i].SetBrightness( 255 ); + m_entFlames[i].index = -1; + + if ( i == 0 ) + { + m_entFlameScales[i] = 1.0f; + } + else + { + //Keep a scale offset + m_entFlameScales[i] = 1.0f - ( ( (float) i / (float) NUM_CHILD_FLAMES ) ); + } + } + + // Setup the glow + m_entGlow.SetModelByIndex( m_nGlowModelIndex ); + m_entGlow.SetLocalOrigin( GetLocalOrigin() ); + m_entGlow.SetScale( m_flStartScale ); + m_entGlow.SetRenderMode( kRenderTransAdd ); + m_entGlow.m_nRenderFX = kRenderFxNone; + m_entGlow.SetRenderColor( 255, 255, 255, 255 ); + m_entGlow.SetBrightness( 255 ); + m_entGlow.index = -1; + + m_flGlowScale = m_flStartScale; + + m_entGlow.AddToLeafSystem( RENDER_GROUP_TRANSLUCENT_ENTITY ); + + for( i=0; i < NUM_CHILD_FLAMES; i++ ) + m_entFlames[i].AddToLeafSystem( RENDER_GROUP_TRANSLUCENT_ENTITY ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Plasma::UpdateAnimation( void ) +{ + int numFrames; + float frametime = gpGlobals->frametime; + + for ( int i = 0; i < NUM_CHILD_FLAMES; i++ ) + { + m_entFlames[i].m_flFrame += m_entFlames[i].m_flSpriteFramerate * frametime; + + numFrames = modelinfo->GetModelFrameCount( m_entFlames[i].GetModel() ); + + if ( m_entFlames[i].m_flFrame >= numFrames ) + { + m_entFlames[i].m_flFrame = m_entFlames[i].m_flFrame - (int)(m_entFlames[i].m_flFrame); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Plasma::UpdateFlames( void ) +{ + for ( int i = 0; i < NUM_CHILD_FLAMES; i++ ) + { + float newScale = m_flScaleRegister * m_entFlameScales[i]; + float dScale = newScale - m_entFlames[i].GetScale(); + + Vector dir; + + dir[2] = 0.0f; + VectorNormalize( dir ); + dir[2] = 0.0f; + + Vector offset = GetAbsOrigin(); + offset[2] = m_entFlames[i].GetAbsOrigin()[2]; + + // Note: Sprite render assumes 0 scale means 1.0 + m_entFlames[i].SetScale ( max(0.000001,newScale) ); + + if ( i != 0 ) + { + m_entFlames[i].SetLocalOrigin( offset + ( m_entFlames[i].m_vecMoveDir * ((m_entFlames[i].GetScale())*CHILD_SPREAD) ) ); + } + + Assert( !m_entFlames[i].GetMoveParent() ); + m_entFlames[i].SetLocalOriginDim( Z_INDEX, m_entFlames[i].GetLocalOriginDim( Z_INDEX ) + ( dScale * 64.0f ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_Plasma::UpdateScale( void ) +{ + float time = gpGlobals->curtime; + + if ( m_flScaleRegister != m_flScaleEnd ) + { + //See if we're done scaling + if ( time > m_flScaleTimeEnd ) + { + m_flScaleRegister = m_flStartScale = m_flScaleEnd; + } + else + { + //Lerp the scale and set it + float timeFraction = 1.0f - ( m_flScaleTimeEnd - time ) / ( m_flScaleTimeEnd - m_flScaleTimeStart ); + float newScale = m_flScaleStart + ( ( m_flScaleEnd - m_flScaleStart ) * timeFraction ); + + m_flScaleRegister = m_flStartScale = newScale; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_Plasma::Update( void ) +{ + //Update all our parts + UpdateScale(); + UpdateAnimation(); + UpdateFlames(); + + if (m_flScaleRegister > 0.1) + { + float tempDelta = gpGlobals->frametime; + while( m_tDecalSpawn.NextEvent( tempDelta ) ) + { + // Add decal to floor + C_BaseEntity *ent = cl_entitylist->GetEnt( 0 ); + if ( ent ) + { + int index = decalsystem->GetDecalIndexForName( "PlasmaGlowFade" ); + if ( index >= 0 ) + { + effects->DecalShoot( index, 0, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), GetAbsOrigin(), 0, 0 ); + } + } + } + } +} + diff --git a/game/client/c_playerlocaldata.h b/game/client/c_playerlocaldata.h new file mode 100644 index 00000000..86936c53 --- /dev/null +++ b/game/client/c_playerlocaldata.h @@ -0,0 +1,80 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Defines the player specific data that is sent only to the player +// to whom it belongs. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_PLAYERLOCALDATA_H +#define C_PLAYERLOCALDATA_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basetypes.h" +#include "mathlib/vector.h" +#include "playernet_vars.h" + +//----------------------------------------------------------------------------- +// Purpose: Player specific data ( sent only to local player, too ) +//----------------------------------------------------------------------------- +class CPlayerLocalData +{ +public: + DECLARE_PREDICTABLE(); + DECLARE_CLASS_NOBASE( CPlayerLocalData ); + DECLARE_EMBEDDED_NETWORKVAR(); + + CPlayerLocalData() : + m_iv_vecPunchAngle( "CPlayerLocalData::m_iv_vecPunchAngle" ), + m_iv_vecPunchAngleVel( "CPlayerLocalData::m_iv_vecPunchAngleVel" ) + { + m_iv_vecPunchAngle.Setup( &m_vecPunchAngle.m_Value, LATCH_SIMULATION_VAR ); + m_iv_vecPunchAngleVel.Setup( &m_vecPunchAngleVel.m_Value, LATCH_SIMULATION_VAR ); + m_flFOVRate = 0; + } + + unsigned char m_chAreaBits[MAX_AREA_STATE_BYTES]; // Area visibility flags. + unsigned char m_chAreaPortalBits[MAX_AREA_PORTAL_STATE_BYTES];// Area portal visibility flags. + + int m_iHideHUD; // bitfields containing sections of the HUD to hide + + float m_flFOVRate; // rate at which the FOV changes + + + bool m_bDucked; + bool m_bDucking; + bool m_bInDuckJump; + float m_flDucktime; + float m_flDuckJumpTime; + float m_flJumpTime; + int m_nStepside; + float m_flFallVelocity; + int m_nOldButtons; + // Base velocity that was passed in to server physics so + // client can predict conveyors correctly. Server zeroes it, so we need to store here, too. + Vector m_vecClientBaseVelocity; + CNetworkQAngle( m_vecPunchAngle ); // auto-decaying view angle adjustment + CInterpolatedVar< QAngle > m_iv_vecPunchAngle; + + CNetworkQAngle( m_vecPunchAngleVel ); // velocity of auto-decaying view angle adjustment + CInterpolatedVar< QAngle > m_iv_vecPunchAngleVel; + bool m_bDrawViewmodel; + bool m_bWearingSuit; + bool m_bPoisoned; + float m_flStepSize; + bool m_bAllowAutoMovement; + + // 3d skybox + sky3dparams_t m_skybox3d; + // fog params + fogplayerparams_t m_PlayerFog; + // audio environment + audioparams_t m_audio; + + bool m_bSlowMovement; + +}; + +#endif // C_PLAYERLOCALDATA_H diff --git a/game/client/c_playerresource.cpp b/game/client/c_playerresource.cpp new file mode 100644 index 00000000..eef353de --- /dev/null +++ b/game/client/c_playerresource.cpp @@ -0,0 +1,314 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Entity that propagates general data needed by clients for every player. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_playerresource.h" +#include "c_team.h" +#include "gamestringpool.h" + +#ifdef HL2MP +#include "hl2mp_gamerules.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +const float PLAYER_RESOURCE_THINK_INTERVAL = 0.2f; +#define PLAYER_UNCONNECTED_NAME "unconnected" + +IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_PlayerResource, DT_PlayerResource, CPlayerResource) + RecvPropArray3( RECVINFO_ARRAY(m_iPing), RecvPropInt( RECVINFO(m_iPing[0]))), + RecvPropArray3( RECVINFO_ARRAY(m_iScore), RecvPropInt( RECVINFO(m_iScore[0]))), + RecvPropArray3( RECVINFO_ARRAY(m_iDeaths), RecvPropInt( RECVINFO(m_iDeaths[0]))), + RecvPropArray3( RECVINFO_ARRAY(m_bConnected), RecvPropInt( RECVINFO(m_bConnected[0]))), + RecvPropArray3( RECVINFO_ARRAY(m_iTeam), RecvPropInt( RECVINFO(m_iTeam[0]))), + RecvPropArray3( RECVINFO_ARRAY(m_bAlive), RecvPropInt( RECVINFO(m_bAlive[0]))), + RecvPropArray3( RECVINFO_ARRAY(m_iHealth), RecvPropInt( RECVINFO(m_iHealth[0]))), +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_PlayerResource ) + + DEFINE_PRED_ARRAY( m_szName, FIELD_STRING, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iScore, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iDeaths, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_bConnected, FIELD_BOOLEAN, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iTeam, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_bAlive, FIELD_BOOLEAN, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iHealth, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + +END_PREDICTION_DATA() + +C_PlayerResource *g_PR; + +IGameResources * GameResources( void ) { return g_PR; } + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_PlayerResource::C_PlayerResource() +{ + memset( m_iPing, 0, sizeof( m_iPing ) ); +// memset( m_iPacketloss, 0, sizeof( m_iPacketloss ) ); + memset( m_iScore, 0, sizeof( m_iScore ) ); + memset( m_iDeaths, 0, sizeof( m_iDeaths ) ); + memset( m_bConnected, 0, sizeof( m_bConnected ) ); + memset( m_iTeam, 0, sizeof( m_iTeam ) ); + memset( m_bAlive, 0, sizeof( m_bAlive ) ); + memset( m_iHealth, 0, sizeof( m_iHealth ) ); + + for ( int i=0; icurtime + PLAYER_RESOURCE_THINK_INTERVAL ); + } +} + +void C_PlayerResource::UpdatePlayerName( int slot ) +{ + if ( slot < 1 || slot > MAX_PLAYERS ) + { + Error( "UpdatePlayerName with bogus slot %d\n", slot ); + return; + } + player_info_t sPlayerInfo; + if ( IsConnected( slot ) && engine->GetPlayerInfo( slot, &sPlayerInfo ) ) + { + m_szName[slot] = AllocPooledString( sPlayerInfo.name ); + } + else + { + m_szName[slot] = AllocPooledString( PLAYER_UNCONNECTED_NAME ); + } +} + +void C_PlayerResource::ClientThink() +{ + BaseClass::ClientThink(); + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + UpdatePlayerName( i ); + } + + SetNextClientThink( gpGlobals->curtime + PLAYER_RESOURCE_THINK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *C_PlayerResource::GetPlayerName( int iIndex ) +{ + if ( iIndex < 1 || iIndex > MAX_PLAYERS ) + { + Assert( false ); + return "ERRORNAME"; + } + + if ( !IsConnected( iIndex ) ) + return PLAYER_UNCONNECTED_NAME; + + // X360TBD: Network - figure out why the name isn't set + if ( !m_szName[ iIndex ] || !Q_stricmp( m_szName[ iIndex ], PLAYER_UNCONNECTED_NAME ) ) + { + // If you get a full "reset" uncompressed update from server, then you can have NULLNAME show up in the scoreboard + UpdatePlayerName( iIndex ); + } + + // This gets updated in ClientThink, so it could be up to 1 second out of date, oh well. + return m_szName[iIndex]; +} + +bool C_PlayerResource::IsAlive(int iIndex ) +{ + return m_bAlive[iIndex]; +} + +int C_PlayerResource::GetTeam(int iIndex ) +{ + if ( iIndex < 1 || iIndex > MAX_PLAYERS ) + { + Assert( false ); + return 0; + } + else + { + return m_iTeam[iIndex]; + } +} + +const char * C_PlayerResource::GetTeamName(int index) +{ + C_Team *team = GetGlobalTeam( index ); + + if ( !team ) + return "Unknown"; + + return team->Get_Name(); +} + +int C_PlayerResource::GetTeamScore(int index) +{ + C_Team *team = GetGlobalTeam( index ); + + if ( !team ) + return 0; + + return team->Get_Score(); +} + +int C_PlayerResource::GetFrags(int index ) +{ + return 666; +} + +bool C_PlayerResource::IsLocalPlayer(int index) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( !pPlayer ) + return false; + + return ( index == pPlayer->entindex() ); +} + + +bool C_PlayerResource::IsHLTV(int index) +{ + if ( !IsConnected( index ) ) + return false; + + player_info_t sPlayerInfo; + + if ( engine->GetPlayerInfo( index, &sPlayerInfo ) ) + { + return sPlayerInfo.ishltv; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_PlayerResource::IsFakePlayer( int iIndex ) +{ + if ( !IsConnected( iIndex ) ) + return false; + + // Yuck, make sure it's up to date + player_info_t sPlayerInfo; + if ( engine->GetPlayerInfo( iIndex, &sPlayerInfo ) ) + { + return sPlayerInfo.fakeplayer; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_PlayerResource::GetPing( int iIndex ) +{ + if ( !IsConnected( iIndex ) ) + return 0; + + return m_iPing[iIndex]; +} + +//----------------------------------------------------------------------------- +// Purpose: +/*----------------------------------------------------------------------------- +int C_PlayerResource::GetPacketloss( int iIndex ) +{ + if ( !IsConnected( iIndex ) ) + return 0; + + return m_iPacketloss[iIndex]; +}*/ + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_PlayerResource::GetPlayerScore( int iIndex ) +{ + if ( !IsConnected( iIndex ) ) + return 0; + + return m_iScore[iIndex]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_PlayerResource::GetDeaths( int iIndex ) +{ + if ( !IsConnected( iIndex ) ) + return 0; + + return m_iDeaths[iIndex]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_PlayerResource::GetHealth( int iIndex ) +{ + if ( !IsConnected( iIndex ) ) + return 0; + + return m_iHealth[iIndex]; +} + +const Color &C_PlayerResource::GetTeamColor(int index ) +{ + if ( index < 0 || index >= MAX_TEAMS ) + { + Assert( false ); + static Color blah; + return blah; + } + else + { + return m_Colors[index]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_PlayerResource::IsConnected( int iIndex ) +{ + if ( iIndex < 1 || iIndex > MAX_PLAYERS ) + return false; + else + return m_bConnected[iIndex]; +} diff --git a/game/client/c_playerresource.h b/game/client/c_playerresource.h new file mode 100644 index 00000000..af1bfd3d --- /dev/null +++ b/game/client/c_playerresource.h @@ -0,0 +1,74 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Entity that propagates general data needed by clients for every player. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_PLAYERRESOURCE_H +#define C_PLAYERRESOURCE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "shareddefs.h" +#include "const.h" +#include "c_baseentity.h" +#include + +class C_PlayerResource : public C_BaseEntity, public IGameResources +{ + DECLARE_CLASS( C_PlayerResource, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_PlayerResource(); + virtual ~C_PlayerResource(); + +public : // IGameResources intreface + + // Team data access + virtual int GetTeamScore( int index ); + virtual const char *GetTeamName( int index ); + virtual const Color&GetTeamColor( int index ); + + // Player data access + virtual bool IsConnected( int index ); + virtual bool IsAlive( int index ); + virtual bool IsFakePlayer( int index ); + virtual bool IsLocalPlayer( int index ); + virtual bool IsHLTV(int index); + + virtual const char *GetPlayerName( int index ); + virtual int GetPing( int index ); +// virtual int GetPacketloss( int index ); + virtual int GetPlayerScore( int index ); + virtual int GetDeaths( int index ); + virtual int GetTeam( int index ); + virtual int GetFrags( int index ); + virtual int GetHealth( int index ); + + virtual void ClientThink(); + virtual void OnDataChanged(DataUpdateType_t updateType); + +protected: + void UpdatePlayerName( int slot ); + + // Data for each player that's propagated to all clients + // Stored in individual arrays so they can be sent down via datatables + string_t m_szName[MAX_PLAYERS+1]; + int m_iPing[MAX_PLAYERS+1]; + int m_iScore[MAX_PLAYERS+1]; + int m_iDeaths[MAX_PLAYERS+1]; + bool m_bConnected[MAX_PLAYERS+1]; + int m_iTeam[MAX_PLAYERS+1]; + bool m_bAlive[MAX_PLAYERS+1]; + int m_iHealth[MAX_PLAYERS+1]; + Color m_Colors[MAX_TEAMS]; + +}; + +extern C_PlayerResource *g_PR; + +#endif // C_PLAYERRESOURCE_H diff --git a/game/client/c_point_camera.cpp b/game/client/c_point_camera.cpp new file mode 100644 index 00000000..f8d048e2 --- /dev/null +++ b/game/client/c_point_camera.cpp @@ -0,0 +1,116 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "C_Point_Camera.h" +#include "toolframework/itoolframework.h" +#include "toolframework_client.h" +#include "tier1/keyvalues.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT( C_PointCamera, DT_PointCamera, CPointCamera ) + RecvPropFloat( RECVINFO( m_FOV ) ), + RecvPropFloat( RECVINFO( m_Resolution ) ), + RecvPropInt( RECVINFO( m_bFogEnable ) ), + RecvPropInt( RECVINFO( m_FogColor ) ), + RecvPropFloat( RECVINFO( m_flFogStart ) ), + RecvPropFloat( RECVINFO( m_flFogEnd ) ), + RecvPropFloat( RECVINFO( m_flFogMaxDensity ) ), + RecvPropInt( RECVINFO( m_bActive ) ), + RecvPropInt( RECVINFO( m_bUseScreenAspectRatio ) ), +END_RECV_TABLE() + +C_EntityClassList g_PointCameraList; +C_PointCamera *C_EntityClassList::m_pClassList = NULL; + +C_PointCamera* GetPointCameraList() +{ + return g_PointCameraList.m_pClassList; +} + +C_PointCamera::C_PointCamera() +{ + m_bActive = false; + m_bFogEnable = false; + + g_PointCameraList.Insert( this ); +} + +C_PointCamera::~C_PointCamera() +{ + g_PointCameraList.Remove( this ); +} + +bool C_PointCamera::ShouldDraw() +{ + return false; +} + +float C_PointCamera::GetFOV() +{ + return m_FOV; +} + +float C_PointCamera::GetResolution() +{ + return m_Resolution; +} + +bool C_PointCamera::IsFogEnabled() +{ + return m_bFogEnable; +} + +void C_PointCamera::GetFogColor( unsigned char &r, unsigned char &g, unsigned char &b ) +{ + r = m_FogColor.r; + g = m_FogColor.g; + b = m_FogColor.b; +} + +float C_PointCamera::GetFogStart() +{ + return m_flFogStart; +} + +float C_PointCamera::GetFogEnd() +{ + return m_flFogEnd; +} + +float C_PointCamera::GetFogMaxDensity() +{ + return m_flFogMaxDensity; +} + +bool C_PointCamera::IsActive() +{ + return m_bActive; +} + + +void C_PointCamera::GetToolRecordingState( KeyValues *msg ) +{ + BaseClass::GetToolRecordingState( msg ); + + unsigned char r, g, b; + static MonitorRecordingState_t state; + state.m_bActive = IsActive() && !IsDormant(); + state.m_flFOV = GetFOV(); + state.m_bFogEnabled = IsFogEnabled(); + state.m_flFogStart = GetFogStart(); + state.m_flFogEnd = GetFogEnd(); + GetFogColor( r, g, b ); + state.m_FogColor.SetColor( r, g, b, 255 ); + + msg->SetPtr( "monitor", &state ); +} + + diff --git a/game/client/c_point_camera.h b/game/client/c_point_camera.h new file mode 100644 index 00000000..ca16a799 --- /dev/null +++ b/game/client/c_point_camera.h @@ -0,0 +1,60 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_POINTCAMERA_H +#define C_POINTCAMERA_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" +#include "basetypes.h" + +class C_PointCamera : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_PointCamera, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + +public: + C_PointCamera(); + ~C_PointCamera(); + + bool IsActive(); + + // C_BaseEntity. + virtual bool ShouldDraw(); + + float GetFOV(); + float GetResolution(); + bool IsFogEnabled(); + void GetFogColor( unsigned char &r, unsigned char &g, unsigned char &b ); + float GetFogStart(); + float GetFogMaxDensity(); + float GetFogEnd(); + bool UseScreenAspectRatio() const { return m_bUseScreenAspectRatio; } + + virtual void GetToolRecordingState( KeyValues *msg ); + +private: + float m_FOV; + float m_Resolution; + bool m_bFogEnable; + color32 m_FogColor; + float m_flFogStart; + float m_flFogEnd; + float m_flFogMaxDensity; + bool m_bActive; + bool m_bUseScreenAspectRatio; + +public: + C_PointCamera *m_pNext; +}; + +C_PointCamera *GetPointCameraList(); + +#endif // C_POINTCAMERA_H diff --git a/game/client/c_point_commentary_node.cpp b/game/client/c_point_commentary_node.cpp new file mode 100644 index 00000000..5b5cb27e --- /dev/null +++ b/game/client/c_point_commentary_node.cpp @@ -0,0 +1,586 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#include "cbase.h" +#include "c_baseentity.h" +#include "hud.h" +#include "hudelement.h" +#include "clientmode.h" +#include +#include +#include +#include +#include +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "soundenvelope.h" +#include "convar.h" +#include "hud_closecaption.h" +#include "in_buttons.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MAX_SPEAKER_NAME 256 +#define MAX_COUNT_STRING 64 + +extern ConVar english; +extern ConVar closecaption; +class C_PointCommentaryNode; + +CUtlVector< CHandle > g_CommentaryNodes; +bool IsInCommentaryMode( void ) +{ + return (g_CommentaryNodes.Count() > 0); +} + +static bool g_bTracingVsCommentaryNodes = false; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CHudCommentary : public CHudElement, public vgui::Panel +{ + DECLARE_CLASS_SIMPLE( CHudCommentary, vgui::Panel ); +public: + CHudCommentary( const char *name ); + + virtual void Init( void ); + virtual void VidInit( void ); + virtual void LevelInit( void ) { g_CommentaryNodes.Purge(); } + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + void StartCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); + void StopCommentary( void ); + bool IsTheActiveNode( C_PointCommentaryNode *pNode ) { return (pNode == m_hActiveNode); } + + // vgui overrides + virtual void Paint( void ); + virtual bool ShouldDraw( void ); + +private: + CHandle m_hActiveNode; + bool m_bShouldPaint; + float m_flStartTime; + float m_flEndTime; + wchar_t m_szSpeakers[MAX_SPEAKER_NAME]; + wchar_t m_szCount[MAX_COUNT_STRING]; + CMaterialReference m_matIcon; + bool m_bHiding; + + // Painting + CPanelAnimationVarAliasType( int, m_iBarX, "bar_xpos", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iBarY, "bar_ypos", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iBarTall, "bar_height", "16", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iBarWide, "bar_width", "16", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iSpeakersX, "speaker_xpos", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iSpeakersY, "speaker_ypos", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iCountXFR, "count_xpos_from_right", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iCountY, "count_ypos", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iIconX, "icon_xpos", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iIconY, "icon_ypos", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iIconWide, "icon_width", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iIconTall, "icon_height", "8", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_nIconTextureId, "icon_texture", "vgui/hud/icon_commentary", "textureid" ); + + CPanelAnimationVar( bool, m_bUseScriptBGColor, "use_script_bgcolor", "0" ); + CPanelAnimationVar( Color, m_BackgroundColor, "BackgroundColor", "0 0 0 0" ); + CPanelAnimationVar( Color, m_BGOverrideColor, "BackgroundOverrideColor", "Panel.BgColor" ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_PointCommentaryNode : public C_BaseAnimating +{ + DECLARE_CLASS( C_PointCommentaryNode, C_BaseAnimating ); +public: + DECLARE_CLIENTCLASS(); + DECLARE_DATADESC(); + + virtual void OnPreDataChanged( DataUpdateType_t type ); + virtual void OnDataChanged( DataUpdateType_t type ); + + void OnRestore( void ) + { + BaseClass::OnRestore(); + + if ( m_bActive ) + { + StopLoopingSounds(); + m_bRestartAfterRestore = true; + } + + AddAndLockCommentaryHudGroup(); + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + virtual void SetDormant( bool bDormant ) + { + if ( !IsDormant() && bDormant ) + { + RemoveAndUnlockCommentaryHudGroup(); + } + + BaseClass::SetDormant( bDormant ); + } + + //----------------------------------------------------------------------------- + // Cleanup + //----------------------------------------------------------------------------- + void UpdateOnRemove( void ) + { + RemoveAndUnlockCommentaryHudGroup(); + + StopLoopingSounds(); + BaseClass::UpdateOnRemove(); + } + + void StopLoopingSounds( void ); + + virtual bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ); + + void AddAndLockCommentaryHudGroup( void ) + { + if ( !g_CommentaryNodes.Count() ) + { + int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "commentary" ); + gHUD.LockRenderGroup( iRenderGroup ); + } + + if ( g_CommentaryNodes.Find(this) == g_CommentaryNodes.InvalidIndex() ) + { + g_CommentaryNodes.AddToTail( this ); + } + } + + void RemoveAndUnlockCommentaryHudGroup( void ) + { + g_CommentaryNodes.FindAndRemove( this ); + + if ( !g_CommentaryNodes.Count() ) + { + int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "commentary" ); + gHUD.UnlockRenderGroup( iRenderGroup ); + } + } + +public: + // Data received from the server + bool m_bActive; + bool m_bWasActive; + float m_flStartTime; + char m_iszCommentaryFile[MAX_PATH]; + char m_iszCommentaryFileNoHDR[MAX_PATH]; + char m_iszSpeakers[MAX_SPEAKER_NAME]; + int m_iNodeNumber; + int m_iNodeNumberMax; + CSoundPatch *m_sndCommentary; + EHANDLE m_hViewPosition; + bool m_bRestartAfterRestore; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_PointCommentaryNode, DT_PointCommentaryNode, CPointCommentaryNode) + RecvPropBool( RECVINFO( m_bActive ) ), + RecvPropTime( RECVINFO( m_flStartTime ) ), + RecvPropString( RECVINFO(m_iszCommentaryFile) ), + RecvPropString( RECVINFO(m_iszCommentaryFileNoHDR) ), + RecvPropString( RECVINFO(m_iszSpeakers) ), + RecvPropInt( RECVINFO( m_iNodeNumber ) ), + RecvPropInt( RECVINFO( m_iNodeNumberMax ) ), + RecvPropEHandle( RECVINFO(m_hViewPosition) ), +END_RECV_TABLE() + +BEGIN_DATADESC( C_PointCommentaryNode ) + DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bWasActive, FIELD_BOOLEAN ), + DEFINE_SOUNDPATCH( m_sndCommentary ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + m_bWasActive = m_bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + AddAndLockCommentaryHudGroup(); + } + + if ( m_bWasActive == m_bActive && !m_bRestartAfterRestore ) + return; + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( m_bActive && pPlayer ) + { + // Use the HDR / Non-HDR version based on whether we're running HDR or not + char *pszCommentaryFile; + if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE && m_iszCommentaryFileNoHDR && m_iszCommentaryFileNoHDR[0] ) + { + pszCommentaryFile = m_iszCommentaryFileNoHDR; + } + else + { + pszCommentaryFile = m_iszCommentaryFile; + } + if ( !pszCommentaryFile || !pszCommentaryFile[0] ) + { + engine->ServerCmd( "commentary_finishnode\n" ); + return; + } + + EmitSound_t es; + es.m_nChannel = CHAN_STATIC; + es.m_pSoundName = pszCommentaryFile; + es.m_SoundLevel = SNDLVL_GUNFIRE; + es.m_nFlags = SND_SHOULDPAUSE; + + CBaseEntity *pSoundEntity; + if ( m_hViewPosition ) + { + pSoundEntity = m_hViewPosition; + } + else if ( render->GetViewEntity() ) + { + pSoundEntity = cl_entitylist->GetEnt( render->GetViewEntity() ); + es.m_SoundLevel = SNDLVL_NONE; + } + else + { + pSoundEntity = pPlayer; + } + CSingleUserRecipientFilter filter( pPlayer ); + m_sndCommentary = (CSoundEnvelopeController::GetController()).SoundCreate( filter, pSoundEntity->entindex(), es ); + if ( m_sndCommentary ) + { + (CSoundEnvelopeController::GetController()).SoundSetCloseCaptionDuration( m_sndCommentary, -1 ); + (CSoundEnvelopeController::GetController()).Play( m_sndCommentary, 1.0f, 100, m_flStartTime ); + } + + // Get the duration so we know when it finishes + float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if ( pHudCloseCaption ) + { + // This is where we play the commentary close caption (and lock the other captions out). + // Also, if close captions are off we force a caption in non-English + if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) + { + // Clear the close caption element in preparation + pHudCloseCaption->Reset(); + + // Process the commentary caption + pHudCloseCaption->ProcessCaptionDirect( pszCommentaryFile, flDuration ); + + // Find the close caption hud element & lock it + pHudCloseCaption->Lock(); + } + } + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartCommentary( this, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); + } + else if ( m_bWasActive ) + { + StopLoopingSounds(); + + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + if ( pHudCommentary->IsTheActiveNode(this) ) + { + pHudCommentary->StopCommentary(); + } + } + + m_bRestartAfterRestore = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Shut down the commentary +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StopLoopingSounds( void ) +{ + if ( m_sndCommentary != NULL ) + { + (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndCommentary ); + m_sndCommentary = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: No client side trace collisions +//----------------------------------------------------------------------------- +bool C_PointCommentaryNode::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) +{ + if ( !g_bTracingVsCommentaryNodes ) + return false; + + return BaseClass::TestCollision( ray, mask, trace ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool IsNodeUnderCrosshair( C_BasePlayer *pPlayer ) +{ + // See if the player's looking at a commentary node + trace_t tr; + Vector vecSrc = pPlayer->EyePosition(); + Vector vecForward; + AngleVectors( pPlayer->EyeAngles(), &vecForward ); + + g_bTracingVsCommentaryNodes = true; + UTIL_TraceLine( vecSrc, vecSrc + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + g_bTracingVsCommentaryNodes = false; + + if ( !tr.m_pEnt ) + return false; + + return dynamic_cast(tr.m_pEnt); +} + +//=================================================================================================================== +// COMMENTARY HUD ELEMENT +//=================================================================================================================== +DECLARE_HUDELEMENT( CHudCommentary ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHudCommentary::CHudCommentary( const char *name ) : vgui::Panel( NULL, "HudCommentary" ), CHudElement( name ) +{ + vgui::Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + SetPaintBorderEnabled( false ); + SetHiddenBits( HIDEHUD_PLAYERDEAD ); + + m_hActiveNode = NULL; + m_bShouldPaint = true; +} + +void CHudCommentary::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_bUseScriptBGColor ) + { + SetBgColor( m_BGOverrideColor ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::Paint() +{ + float flDuration = (m_flEndTime - m_flStartTime); + float flPercentage = clamp( ( gpGlobals->curtime - m_flStartTime ) / flDuration, 0, 1 ); + + if ( !m_hActiveNode ) + { + if ( !m_bHiding ) + { + m_bHiding = true; + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HideCommentary" ); + + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if ( pHudCloseCaption ) + { + pHudCloseCaption->Reset(); + } + } + } + else + { + // Detect the end of the commentary + if ( flPercentage >= 1 && m_hActiveNode ) + { + m_hActiveNode = NULL; + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HideCommentary" ); + + engine->ServerCmd( "commentary_finishnode\n" ); + } + } + + if ( !m_bShouldPaint ) + return; + + int x, y, wide, tall; + GetBounds( x, y, wide, tall ); + + int xOffset = m_iBarX; + int yOffset = m_iBarY; + + // Find our fade based on our time shown + Color clr = Color( 255, 170, 0, GetAlpha() ); + + // Draw the progress bar + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset+m_iBarWide, yOffset+m_iBarTall ); + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawFilledRect( xOffset+2, yOffset+2, xOffset+(int)(flPercentage*m_iBarWide)-2, yOffset+m_iBarTall-2 ); + + // Draw the speaker names + // Get our scheme and font information + vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); + vgui::HFont hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !hFont ) + { + hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + vgui::surface()->DrawSetTextFont( hFont ); + vgui::surface()->DrawSetTextColor( clr ); + vgui::surface()->DrawSetTextPos( m_iSpeakersX, m_iSpeakersY ); + vgui::surface()->DrawPrintText( m_szSpeakers, wcslen(m_szSpeakers) ); + + if ( COMMENTARY_BUTTONS & IN_ATTACK ) + { + int iY = m_iBarY + m_iBarTall + YRES(4); + wchar_t wzFinal[512] = L""; + + wchar_t *pszText = g_pVGuiLocalize->Find( "#Commentary_PrimaryAttack" ); + if ( pszText ) + { + UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) ); + vgui::surface()->DrawSetTextPos( m_iSpeakersX, iY ); + vgui::surface()->DrawPrintText( wzFinal, wcslen(wzFinal) ); + } + + pszText = g_pVGuiLocalize->Find( "#Commentary_SecondaryAttack" ); + if ( pszText ) + { + int w, h; + UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) ); + vgui::surface()->GetTextSize( hFont, wzFinal, w, h ); + vgui::surface()->DrawSetTextPos( m_iBarX + m_iBarWide - w, iY ); + vgui::surface()->DrawPrintText( wzFinal, wcslen(wzFinal) ); + } + } + + // Draw the commentary count + // Determine our text size, and move that far in from the right hand size (plus the offset) + int iCountWide, iCountTall; + vgui::surface()->GetTextSize( hFont, m_szCount, iCountWide, iCountTall ); + vgui::surface()->DrawSetTextPos( wide - m_iCountXFR - iCountWide, m_iCountY ); + vgui::surface()->DrawPrintText( m_szCount, wcslen(m_szCount) ); + + // Draw the icon + vgui::surface()->DrawSetColor( Color(255,170,0,GetAlpha()) ); + vgui::surface()->DrawSetTexture(m_nIconTextureId); + vgui::surface()->DrawTexturedRect( m_iIconX, m_iIconY, m_iIconWide, m_iIconTall ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CHudCommentary::ShouldDraw() +{ + return ( m_hActiveNode || GetAlpha() > 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::Init( void ) +{ + m_matIcon.Init( "vgui/hud/icon_commentary", TEXTURE_GROUP_VGUI ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::VidInit( void ) +{ + SetAlpha(0); + StopCommentary(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ) +{ + if ( (flEndTime - flStartTime) <= 0 ) + return; + + m_hActiveNode = pNode; + m_flStartTime = flStartTime; + m_flEndTime = flEndTime; + m_bHiding = false; + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof(m_szSpeakers) ); + + // Don't draw the element itself if closecaptions are on (and captions are always on in non-english mode) + ConVarRef pCVar( "closecaption" ); + if ( pCVar.IsValid() ) + { + m_bShouldPaint = ( !pCVar.GetBool() && english.GetBool() ); + } + else + { + m_bShouldPaint = true; + } + SetPaintBackgroundEnabled( m_bShouldPaint ); + + char sz[MAX_COUNT_STRING]; + Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax ); + g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) ); + + // If the commentary just started, play the commentary fade in. + if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" ); + } + else + { + // We're reloading a savegame that has an active commentary going in it. Don't fade in. + SetAlpha( 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StopCommentary( void ) +{ + m_hActiveNode = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CommentaryModeShouldSwallowInput( C_BasePlayer *pPlayer ) +{ + if ( !IsInCommentaryMode() ) + return false; + + if ( pPlayer->m_nButtons & COMMENTARY_BUTTONS ) + { + // Always steal the secondary attack + if ( pPlayer->m_nButtons & IN_ATTACK2 ) + return true; + + // See if there's any nodes ahead of us. + if ( IsNodeUnderCrosshair( pPlayer ) ) + return true; + } + + return false; +} \ No newline at end of file diff --git a/game/client/c_prop_vehicle.cpp b/game/client/c_prop_vehicle.cpp new file mode 100644 index 00000000..1c3295df --- /dev/null +++ b/game/client/c_prop_vehicle.cpp @@ -0,0 +1,372 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "cbase.h" +#include "c_prop_vehicle.h" +#include "hud.h" +#include +#include +#include "view.h" +#include "engine/IVDebugOverlay.h" +#include "movevars_shared.h" +#include "iviewrender.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +int ScreenTransform( const Vector& point, Vector& screen ); + +extern ConVar default_fov; +extern ConVar joy_response_move_vehicle; + + +IMPLEMENT_CLIENTCLASS_DT(C_PropVehicleDriveable, DT_PropVehicleDriveable, CPropVehicleDriveable) + RecvPropEHandle( RECVINFO(m_hPlayer) ), + RecvPropInt( RECVINFO( m_nSpeed ) ), + RecvPropInt( RECVINFO( m_nRPM ) ), + RecvPropFloat( RECVINFO( m_flThrottle ) ), + RecvPropInt( RECVINFO( m_nBoostTimeLeft ) ), + RecvPropInt( RECVINFO( m_nHasBoost ) ), + RecvPropInt( RECVINFO( m_nScannerDisabledWeapons ) ), + RecvPropInt( RECVINFO( m_nScannerDisabledVehicle ) ), + RecvPropInt( RECVINFO( m_bEnterAnimOn ) ), + RecvPropInt( RECVINFO( m_bExitAnimOn ) ), + RecvPropInt( RECVINFO( m_bUnableToFire ) ), + RecvPropVector( RECVINFO( m_vecEyeExitEndpoint ) ), + RecvPropBool( RECVINFO( m_bHasGun ) ), + RecvPropVector( RECVINFO( m_vecGunCrosshair ) ), +END_RECV_TABLE() + + +BEGIN_DATADESC( C_PropVehicleDriveable ) + DEFINE_EMBEDDED( m_ViewSmoothingData ), +END_DATADESC() + +ConVar r_VehicleViewClamp( "r_VehicleViewClamp", "1", FCVAR_CHEAT ); + +#define ROLL_CURVE_ZERO 20 // roll less than this is clamped to zero +#define ROLL_CURVE_LINEAR 90 // roll greater than this is copied out + +#define PITCH_CURVE_ZERO 10 // pitch less than this is clamped to zero +#define PITCH_CURVE_LINEAR 45 // pitch greater than this is copied out + // spline in between + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +C_PropVehicleDriveable::C_PropVehicleDriveable() : + m_iv_vecGunCrosshair( "C_PropVehicleDriveable::m_iv_vecGunCrosshair" ) + +{ + m_hPrevPlayer = NULL; + + memset( &m_ViewSmoothingData, 0, sizeof( m_ViewSmoothingData ) ); + + m_ViewSmoothingData.pVehicle = this; + m_ViewSmoothingData.bClampEyeAngles = true; + m_ViewSmoothingData.bDampenEyePosition = true; + + m_ViewSmoothingData.flPitchCurveZero = PITCH_CURVE_ZERO; + m_ViewSmoothingData.flPitchCurveLinear = PITCH_CURVE_LINEAR; + m_ViewSmoothingData.flRollCurveZero = ROLL_CURVE_ZERO; + m_ViewSmoothingData.flRollCurveLinear = ROLL_CURVE_LINEAR; + + m_ViewSmoothingData.flFOV = m_flFOV = default_fov.GetFloat(); + + AddVar( &m_vecGunCrosshair, &m_iv_vecGunCrosshair, LATCH_SIMULATION_VAR ); +} + +//----------------------------------------------------------------------------- +// Purpose: De-constructor +//----------------------------------------------------------------------------- +C_PropVehicleDriveable::~C_PropVehicleDriveable() +{ +} + + +//----------------------------------------------------------------------------- +// By default all driveable vehicles use the curve defined by the convar. +//----------------------------------------------------------------------------- +int C_PropVehicleDriveable::GetJoystickResponseCurve() const +{ + return joy_response_move_vehicle.GetInt(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BaseCombatCharacter *C_PropVehicleDriveable::GetPassenger( int nRole ) +{ + if ( nRole == VEHICLE_ROLE_DRIVER ) + return m_hPlayer.Get(); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Returns the role of the passenger +//----------------------------------------------------------------------------- +int C_PropVehicleDriveable::GetPassengerRole( C_BaseCombatCharacter *pPassenger ) +{ + if ( m_hPlayer.Get() == pPassenger ) + return VEHICLE_ROLE_DRIVER; + + return VEHICLE_ROLE_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + m_hPrevPlayer = m_hPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if ( m_hPlayer && !m_hPrevPlayer ) + { + OnEnteredVehicle( m_hPlayer ); + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } + else if ( !m_hPlayer && m_hPrevPlayer ) + { + // They have just exited the vehicle. + // Sometimes we never reach the end of our exit anim, such as if the + // animation doesn't have fadeout 0 specified in the QC, so we fail to + // catch it in VehicleViewSmoothing. Catch it here instead. + m_ViewSmoothingData.bWasRunningAnim = false; + SetNextClientThink( CLIENT_THINK_NEVER ); + } +} + +//----------------------------------------------------------------------------- +// Should this object cast render-to-texture shadows? +//----------------------------------------------------------------------------- +ShadowType_t C_PropVehicleDriveable::ShadowCastType() +{ + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( !pStudioHdr ) + return SHADOWS_NONE; + + if ( IsEffectActive(EF_NODRAW | EF_NOSHADOW) ) + return SHADOWS_NONE; + + // Always use render-to-texture. We'll always the dirty bits in our think function + return SHADOWS_RENDER_TO_TEXTURE; +} + + +//----------------------------------------------------------------------------- +// Mark the shadow as dirty while the vehicle is being driven +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::ClientThink( void ) +{ + // The vehicle is always dirty owing to pose parameters while it's being driven. + g_pClientShadowMgr->MarkRenderToTextureShadowDirty( GetShadowHandle() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Modify the player view/camera while in a vehicle +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles, float *pFOV /*=NULL*/ ) +{ + SharedVehicleViewSmoothing( m_hPlayer, + pAbsOrigin, pAbsAngles, + m_bEnterAnimOn, m_bExitAnimOn, + m_vecEyeExitEndpoint, + &m_ViewSmoothingData, + pFOV ); +} + + +//----------------------------------------------------------------------------- +// Futzes with the clip planes +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::GetVehicleClipPlanes( float &flZNear, float &flZFar ) const +{ + // FIXME: Need something a better long-term, this fixes the buggy. + flZNear = 6; +} + + +//----------------------------------------------------------------------------- +// Renders hud elements +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Simply used to return intensity value based upon current timer passed in +//----------------------------------------------------------------------------- +int GetFlashColorIntensity( int LowIntensity, int HighIntensity, bool Dimming, int Increment, int Timer ) +{ + if ( Dimming ) + return ( HighIntensity - Timer * Increment ); + else + return ( LowIntensity + Timer * Increment ); +} + +#define TRIANGULATED_CROSSHAIR 1 + +void C_PropVehicleDriveable::DrawHudElements( ) +{ + CHudTexture *pIcon; + int iIconX, iIconY; + + if (m_bHasGun) + { + // draw crosshairs for vehicle gun + pIcon = gHUD.GetIcon( "gunhair" ); + + if ( pIcon != NULL ) + { + float x, y; + Vector screen; + + x = ScreenWidth()/2; + y = ScreenHeight()/2; + + #if TRIANGULATED_CROSSHAIR + ScreenTransform( m_vecGunCrosshair, screen ); + x += 0.5 * screen[0] * ScreenWidth() + 0.5; + y -= 0.5 * screen[1] * ScreenHeight() + 0.5; + #endif + + x -= pIcon->Width() / 2; + y -= pIcon->Height() / 2; + + Color clr = ( m_bUnableToFire ) ? gHUD.m_clrCaution : gHUD.m_clrNormal; + pIcon->DrawSelf( x, y, clr ); + } + + if ( m_nScannerDisabledWeapons ) + { + // Draw icons for scanners "weps disabled" + pIcon = gHUD.GetIcon( "dmg_bio" ); + if ( pIcon ) + { + iIconY = 467 - pIcon->Height() / 2; + iIconX = 385; + if ( !m_bScannerWepIcon ) + { + pIcon->DrawSelf( XRES(iIconX), YRES(iIconY), Color( 0, 0, 255, 255 ) ); + m_bScannerWepIcon = true; + m_iScannerWepFlashTimer = 0; + m_bScannerWepDim = true; + } + else + { + pIcon->DrawSelf( XRES(iIconX), YRES(iIconY), Color( 0, 0, GetFlashColorIntensity(55, 255, m_bScannerWepDim, 10, m_iScannerWepFlashTimer), 255 ) ); + m_iScannerWepFlashTimer++; + m_iScannerWepFlashTimer %= 20; + if(!m_iScannerWepFlashTimer) + m_bScannerWepDim ^= 1; + } + } + } + } + + if ( m_nScannerDisabledVehicle ) + { + // Draw icons for scanners "vehicle disabled" + pIcon = gHUD.GetIcon( "dmg_bio" ); + if ( pIcon ) + { + iIconY = 467 - pIcon->Height() / 2; + iIconX = 410; + if ( !m_bScannerVehicleIcon ) + { + pIcon->DrawSelf( XRES(iIconX), YRES(iIconY), Color( 0, 0, 255, 255 ) ); + m_bScannerVehicleIcon = true; + m_iScannerVehicleFlashTimer = 0; + m_bScannerVehicleDim = true; + } + else + { + pIcon->DrawSelf( XRES(iIconX), YRES(iIconY), Color( 0, 0, GetFlashColorIntensity(55, 255, m_bScannerVehicleDim, 10, m_iScannerVehicleFlashTimer), 255 ) ); + m_iScannerVehicleFlashTimer++; + m_iScannerVehicleFlashTimer %= 20; + if(!m_iScannerVehicleFlashTimer) + m_bScannerVehicleDim ^= 1; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::RestrictView( float *pYawBounds, float *pPitchBounds, + float *pRollBounds, QAngle &vecViewAngles ) +{ + int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); + Vector vehicleEyeOrigin; + QAngle vehicleEyeAngles; + GetAttachmentLocal( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); + + // Limit the yaw. + if ( pYawBounds ) + { + float flAngleDiff = AngleDiff( vecViewAngles.y, vehicleEyeAngles.y ); + flAngleDiff = clamp( flAngleDiff, pYawBounds[0], pYawBounds[1] ); + vecViewAngles.y = vehicleEyeAngles.y + flAngleDiff; + } + + // Limit the pitch. + if ( pPitchBounds ) + { + float flAngleDiff = AngleDiff( vecViewAngles.x, vehicleEyeAngles.x ); + flAngleDiff = clamp( flAngleDiff, pPitchBounds[0], pPitchBounds[1] ); + vecViewAngles.x = vehicleEyeAngles.x + flAngleDiff; + } + + // Limit the roll. + if ( pRollBounds ) + { + float flAngleDiff = AngleDiff( vecViewAngles.z, vehicleEyeAngles.z ); + flAngleDiff = clamp( flAngleDiff, pRollBounds[0], pRollBounds[1] ); + vecViewAngles.z = vehicleEyeAngles.z + flAngleDiff; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ) +{ + if ( r_VehicleViewClamp.GetInt() ) + { + float pitchBounds[2] = { -85.0f, 25.0f }; + RestrictView( NULL, pitchBounds, NULL, pCmd->viewangles ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropVehicleDriveable::OnEnteredVehicle( C_BaseCombatCharacter *pPassenger ) +{ +} + diff --git a/game/client/c_prop_vehicle.h b/game/client/c_prop_vehicle.h new file mode 100644 index 00000000..279e7fb9 --- /dev/null +++ b/game/client/c_prop_vehicle.h @@ -0,0 +1,129 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_PROP_VEHICLE_H +#define C_PROP_VEHICLE_H +#pragma once + +#include "IClientVehicle.h" +#include "vehicle_viewblend_shared.h" +class C_PropVehicleDriveable : public C_BaseAnimating, public IClientVehicle +{ + + DECLARE_CLASS( C_PropVehicleDriveable, C_BaseAnimating ); + +public: + + DECLARE_CLIENTCLASS(); + DECLARE_INTERPOLATION(); + DECLARE_DATADESC(); + + C_PropVehicleDriveable(); + ~C_PropVehicleDriveable(); + +// IVehicle overrides. +public: + + virtual C_BaseCombatCharacter* GetPassenger( int nRole ); + virtual int GetPassengerRole( C_BaseCombatCharacter *pEnt ); + virtual bool IsPassengerUsingStandardWeapons( int nRole = VEHICLE_ROLE_DRIVER ) { return false; } + virtual void GetVehicleViewPosition( int nRole, Vector *pOrigin, QAngle *pAngles, float *pFOV = NULL ); + + virtual void SetupMove( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) {} + virtual void ProcessMovement( C_BasePlayer *pPlayer, CMoveData *pMoveData ) {} + virtual void FinishMove( C_BasePlayer *player, CUserCmd *ucmd, CMoveData *move ) {} + + virtual void ItemPostFrame( C_BasePlayer *pPlayer ) {} + +// IClientVehicle overrides. +public: + + virtual void GetVehicleFOV( float &flFOV ) { flFOV = m_flFOV; } + virtual void DrawHudElements(); + virtual void UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ); + virtual void DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ); + virtual void GetVehicleClipPlanes( float &flZNear, float &flZFar ) const; + +#ifdef HL2_CLIENT_DLL + virtual int GetPrimaryAmmoType() const { return -1; } + virtual int GetPrimaryAmmoCount() const { return -1; } + virtual int GetPrimaryAmmoClip() const { return -1; } + virtual bool PrimaryAmmoUsesClips() const { return false; } +#endif + + virtual bool IsPredicted() const { return false; } + virtual int GetJoystickResponseCurve() const; + +// C_BaseEntity overrides. +public: + + virtual IClientVehicle* GetClientVehicle() { return this; } + virtual C_BaseEntity *GetVehicleEnt() { return this; } + virtual bool IsSelfAnimating() { return false; }; + + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + + // Should this object cast render-to-texture shadows? + virtual ShadowType_t ShadowCastType(); + + // Mark the shadow as dirty while the vehicle is being driven + virtual void ClientThink( void ); + +// C_PropVehicleDriveable +public: + + bool IsRunningEnterExitAnim( void ) { return m_bEnterAnimOn || m_bExitAnimOn; } + +protected: + + virtual void OnEnteredVehicle( C_BaseCombatCharacter *pPassenger ); + virtual void RestrictView( float *pYawBounds, float *pPitchBounds, float *pRollBounds, QAngle &vecViewAngles ); + virtual void SetVehicleFOV( float flFOV ) { m_flFOV = flFOV; } + +protected: + + CHandle m_hPlayer; + int m_nSpeed; + int m_nRPM; + float m_flThrottle; + int m_nBoostTimeLeft; + int m_nHasBoost; + int m_nScannerDisabledWeapons; + int m_nScannerDisabledVehicle; + + // timers/flags for flashing icons on hud + int m_iFlashTimer; + bool m_bLockedDim; + bool m_bLockedIcon; + + int m_iScannerWepFlashTimer; + bool m_bScannerWepDim; + bool m_bScannerWepIcon; + + int m_iScannerVehicleFlashTimer; + bool m_bScannerVehicleDim; + bool m_bScannerVehicleIcon; + + float m_flSequenceChangeTime; + bool m_bEnterAnimOn; + bool m_bExitAnimOn; + float m_flFOV; + + Vector m_vecGunCrosshair; + CInterpolatedVar m_iv_vecGunCrosshair; + Vector m_vecEyeExitEndpoint; + bool m_bHasGun; + bool m_bUnableToFire; + + // Used to smooth view entry + CHandle m_hPrevPlayer; + + ViewSmoothingData_t m_ViewSmoothingData; +}; + + +#endif // C_PROP_VEHICLE_H diff --git a/game/client/c_props.cpp b/game/client/c_props.cpp new file mode 100644 index 00000000..494c9705 --- /dev/null +++ b/game/client/c_props.cpp @@ -0,0 +1,267 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "cbase.h" +#include "c_physicsprop.h" +#include "c_physbox.h" +#include "c_props.h" + +#define CPhysBox C_PhysBox +#define CPhysicsProp C_PhysicsProp + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_NETWORKCLASS_ALIASED( DynamicProp, DT_DynamicProp ) + +BEGIN_NETWORK_TABLE( CDynamicProp, DT_DynamicProp ) + RecvPropBool(RECVINFO(m_bUseHitboxesForRenderBox)), +END_NETWORK_TABLE() + +C_DynamicProp::C_DynamicProp( void ) +{ + m_iCachedFrameCount = -1; +} + +C_DynamicProp::~C_DynamicProp( void ) +{ +} + +bool C_DynamicProp::TestBoneFollowers( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) +{ + // UNDONE: There is no list of the bone followers that is networked to the client + // so instead we do a search for solid stuff here. This is not really great - a list would be + // preferable. + CBaseEntity *pList[128]; + Vector mins, maxs; + CollisionProp()->WorldSpaceAABB( &mins, &maxs ); + int count = UTIL_EntitiesInBox( pList, ARRAYSIZE(pList), mins, maxs, 0, PARTITION_CLIENT_SOLID_EDICTS ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i]->GetOwnerEntity() == this ) + { + if ( pList[i]->TestCollision(ray, fContentsMask, tr) ) + { + return true; + } + } + } + return false; +} + +bool C_DynamicProp::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) +{ + if ( IsSolidFlagSet(FSOLID_NOT_SOLID) ) + { + // if this entity is marked non-solid and custom test it must have bone followers + if ( IsSolidFlagSet( FSOLID_CUSTOMBOXTEST ) && IsSolidFlagSet( FSOLID_CUSTOMRAYTEST )) + { + return TestBoneFollowers( ray, fContentsMask, tr ); + } + } + return BaseClass::TestCollision( ray, fContentsMask, tr ); +} + +//----------------------------------------------------------------------------- +// implements these so ragdolls can handle frustum culling & leaf visibility +//----------------------------------------------------------------------------- +void C_DynamicProp::GetRenderBounds( Vector& theMins, Vector& theMaxs ) +{ + if ( m_bUseHitboxesForRenderBox ) + { + if ( GetModel() ) + { + studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( GetModel() ); + if ( !pStudioHdr || GetSequence() == -1 ) + { + theMins = vec3_origin; + theMaxs = vec3_origin; + return; + } + + // Only recompute if it's a new frame + if ( gpGlobals->framecount != m_iCachedFrameCount ) + { + ComputeEntitySpaceHitboxSurroundingBox( &m_vecCachedRenderMins, &m_vecCachedRenderMaxs ); + m_iCachedFrameCount = gpGlobals->framecount; + } + + theMins = m_vecCachedRenderMins; + theMaxs = m_vecCachedRenderMaxs; + return; + } + } + + BaseClass::GetRenderBounds( theMins, theMaxs ); +} + +unsigned int C_DynamicProp::ComputeClientSideAnimationFlags() +{ + if ( GetSequence() != -1 ) + { + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( GetSequenceCycleRate(pStudioHdr, GetSequence()) != 0.0f ) + { + return BaseClass::ComputeClientSideAnimationFlags(); + } + } + + // no sequence or no cycle rate, don't do any per-frame calcs + return 0; +} + +// ------------------------------------------------------------------------------------------ // +// ------------------------------------------------------------------------------------------ // +class C_BasePropDoor : public C_DynamicProp +{ + DECLARE_CLASS( C_BasePropDoor, C_DynamicProp ); +public: + DECLARE_CLIENTCLASS(); + + // constructor, destructor + C_BasePropDoor( void ); + virtual ~C_BasePropDoor( void ); + + virtual void OnDataChanged( DataUpdateType_t type ); + + virtual bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ); + +private: + C_BasePropDoor( const C_BasePropDoor & ); +}; + +IMPLEMENT_CLIENTCLASS_DT(C_BasePropDoor, DT_BasePropDoor, CBasePropDoor) +END_RECV_TABLE() + +C_BasePropDoor::C_BasePropDoor( void ) +{ +} + +C_BasePropDoor::~C_BasePropDoor( void ) +{ +} + +void C_BasePropDoor::OnDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnDataChanged( type ); + + if ( type == DATA_UPDATE_CREATED ) + { + SetSolid(SOLID_VPHYSICS); + VPhysicsInitShadow( false, false ); + } + else if ( VPhysicsGetObject() ) + { + VPhysicsGetObject()->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, TICK_INTERVAL ); + } +} + +bool C_BasePropDoor::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) +{ + if ( !VPhysicsGetObject() ) + return false; + + CStudioHdr *pStudioHdr = GetModelPtr( ); + if (!pStudioHdr) + return false; + + physcollision->TraceBox( ray, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles(), &trace ); + + if ( trace.DidHit() ) + { + trace.surface.surfaceProps = VPhysicsGetObject()->GetMaterialIndex(); + return true; + } + + return false; +} + +// ------------------------------------------------------------------------------------------ // +// Special version of func_physbox. +// ------------------------------------------------------------------------------------------ // +#ifndef _XBOX +class CPhysBoxMultiplayer : public CPhysBox, public IMultiplayerPhysics +{ +public: + DECLARE_CLASS( CPhysBoxMultiplayer, CPhysBox ); + + virtual int GetMultiplayerPhysicsMode() + { + return m_iPhysicsMode; + } + + virtual float GetMass() + { + return m_fMass; + } + + virtual bool IsAsleep() + { + Assert ( 0 ); + return true; + } + + CNetworkVar( int, m_iPhysicsMode ); // One of the PHYSICS_MULTIPLAYER_ defines. + CNetworkVar( float, m_fMass ); + + DECLARE_CLIENTCLASS(); +}; + +IMPLEMENT_CLIENTCLASS_DT( CPhysBoxMultiplayer, DT_PhysBoxMultiplayer, CPhysBoxMultiplayer ) + RecvPropInt( RECVINFO( m_iPhysicsMode ) ), + RecvPropFloat( RECVINFO( m_fMass ) ), +END_RECV_TABLE() + + +class CPhysicsPropMultiplayer : public CPhysicsProp, public IMultiplayerPhysics +{ + DECLARE_CLASS( CPhysicsPropMultiplayer, CPhysicsProp ); + + virtual int GetMultiplayerPhysicsMode() + { + Assert( m_iPhysicsMode != PHYSICS_MULTIPLAYER_CLIENTSIDE ); + Assert( m_iPhysicsMode != PHYSICS_MULTIPLAYER_AUTODETECT ); + return m_iPhysicsMode; + } + + virtual float GetMass() + { + return m_fMass; + } + + virtual bool IsAsleep() + { + return !m_bAwake; + } + + virtual void ComputeWorldSpaceSurroundingBox( Vector *mins, Vector *maxs ) + { + Assert( mins != NULL && maxs != NULL ); + if ( !mins || !maxs ) + return; + + // Take our saved collision bounds, and transform into world space + TransformAABB( EntityToWorldTransform(), m_collisionMins, m_collisionMaxs, *mins, *maxs ); + } + + CNetworkVar( int, m_iPhysicsMode ); // One of the PHYSICS_MULTIPLAYER_ defines. + CNetworkVar( float, m_fMass ); + CNetworkVector( m_collisionMins ); + CNetworkVector( m_collisionMaxs ); + + DECLARE_CLIENTCLASS(); +}; + +IMPLEMENT_CLIENTCLASS_DT( CPhysicsPropMultiplayer, DT_PhysicsPropMultiplayer, CPhysicsPropMultiplayer ) + RecvPropInt( RECVINFO( m_iPhysicsMode ) ), + RecvPropFloat( RECVINFO( m_fMass ) ), + RecvPropVector( RECVINFO( m_collisionMins ) ), + RecvPropVector( RECVINFO( m_collisionMaxs ) ), +END_RECV_TABLE() +#endif diff --git a/game/client/c_props.h b/game/client/c_props.h new file mode 100644 index 00000000..57c0bf74 --- /dev/null +++ b/game/client/c_props.h @@ -0,0 +1,45 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef C_PROPS_H +#define C_PROPS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_breakableprop.h" +#include "props_shared.h" + +#define CDynamicProp C_DynamicProp + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_DynamicProp : public C_BreakableProp +{ + DECLARE_CLASS( C_DynamicProp, C_BreakableProp ); +public: + DECLARE_NETWORKCLASS(); + + // constructor, destructor + C_DynamicProp( void ); + ~C_DynamicProp( void ); + + void GetRenderBounds( Vector& theMins, Vector& theMaxs ); + unsigned int ComputeClientSideAnimationFlags(); + bool TestBoneFollowers( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); + bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); + +private: + C_DynamicProp( const C_DynamicProp & ); + + bool m_bUseHitboxesForRenderBox; + int m_iCachedFrameCount; + Vector m_vecCachedRenderMins; + Vector m_vecCachedRenderMaxs; +}; + +#endif // C_PROPS_H diff --git a/game/client/c_ragdoll_manager.cpp b/game/client/c_ragdoll_manager.cpp new file mode 100644 index 00000000..c8def859 --- /dev/null +++ b/game/client/c_ragdoll_manager.cpp @@ -0,0 +1,51 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "ragdoll_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_RagdollManager : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_RagdollManager, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_RagdollManager(); + +// C_BaseEntity overrides. +public: + + virtual void OnDataChanged( DataUpdateType_t updateType ); + +public: + + int m_iCurrentMaxRagdollCount; +}; + +IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_RagdollManager, DT_RagdollManager, CRagdollManager ) + RecvPropInt( RECVINFO( m_iCurrentMaxRagdollCount ) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +C_RagdollManager::C_RagdollManager() +{ + m_iCurrentMaxRagdollCount = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_RagdollManager::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + s_RagdollLRU.SetMaxRagdollCount( m_iCurrentMaxRagdollCount ); +} diff --git a/game/client/c_recipientfilter.cpp b/game/client/c_recipientfilter.cpp new file mode 100644 index 00000000..56861660 --- /dev/null +++ b/game/client/c_recipientfilter.cpp @@ -0,0 +1,218 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "prediction.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static IPredictionSystem g_RecipientFilterPredictionSystem; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_RecipientFilter::C_RecipientFilter() +{ + Reset(); +} + +C_RecipientFilter::~C_RecipientFilter() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : src - +//----------------------------------------------------------------------------- +void C_RecipientFilter::CopyFrom( const C_RecipientFilter& src ) +{ + m_bReliable = src.IsReliable(); + m_bInitMessage = src.IsInitMessage(); + + m_bUsingPredictionRules = src.IsUsingPredictionRules(); + m_bIgnorePredictionCull = src.IgnorePredictionCull(); + + int c = src.GetRecipientCount(); + for ( int i = 0; i < c; ++i ) + { + m_Recipients.AddToTail( src.GetRecipientIndex( i ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_RecipientFilter::Reset( void ) +{ + m_bReliable = false; + m_Recipients.RemoveAll(); + m_bUsingPredictionRules = false; + m_bIgnorePredictionCull = false; +} + +void C_RecipientFilter::MakeReliable( void ) +{ + m_bReliable = true; +} + +bool C_RecipientFilter::IsReliable( void ) const +{ + return m_bReliable; +} + +int C_RecipientFilter::GetRecipientCount( void ) const +{ + return m_Recipients.Size(); +} + +int C_RecipientFilter::GetRecipientIndex( int slot ) const +{ + if ( slot < 0 || slot >= GetRecipientCount() ) + return -1; + + return m_Recipients[ slot ]; +} + +void C_RecipientFilter::AddAllPlayers( void ) +{ + if ( !C_BasePlayer::GetLocalPlayer() ) + return; + + m_Recipients.RemoveAll(); + AddRecipient( C_BasePlayer::GetLocalPlayer() ); +} + +void C_RecipientFilter::AddRecipient( C_BasePlayer *player ) +{ + Assert( player ); + + int index = player->index; + + // If we're predicting and this is not the first time we've predicted this sound + // then don't send it to the local player again. + if ( m_bUsingPredictionRules ) + { + Assert( player == C_BasePlayer::GetLocalPlayer() ); + Assert( prediction->InPrediction() ); + + // Only add local player if this is the first time doing prediction + if ( !g_RecipientFilterPredictionSystem.CanPredict() ) + { + return; + } + } + + // Already in list + if ( m_Recipients.Find( index ) != m_Recipients.InvalidIndex() ) + return; + + // this is a client side filter, only add the local player + if ( !player->IsLocalPlayer() ) + return; + + m_Recipients.AddToTail( index ); +} + +void C_RecipientFilter::RemoveRecipient( C_BasePlayer *player ) +{ + if ( !player ) + return; + + int index = player->index; + + // Remove it if it's in the list + m_Recipients.FindAndRemove( index ); +} + +void C_RecipientFilter::AddRecipientsByTeam( C_Team *team ) +{ + AddAllPlayers(); +} + +void C_RecipientFilter::RemoveRecipientsByTeam( C_Team *team ) +{ + Assert ( 0 ); +} + +void C_RecipientFilter::AddPlayersFromBitMask( CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( !pPlayer ) + return; + + // only add the local player on client side + if ( !playerbits[ pPlayer->index ] ) + return; + + AddRecipient( pPlayer ); +} + +void C_RecipientFilter::AddRecipientsByPVS( const Vector& origin ) +{ + AddAllPlayers(); +} + +void C_RecipientFilter::AddRecipientsByPAS( const Vector& origin ) +{ + AddAllPlayers(); +} + +void C_RecipientFilter::UsePredictionRules( void ) +{ + if ( m_bUsingPredictionRules ) + return; + + if ( !prediction->InPrediction() ) + { + Assert( 0 ); + return; + } + + C_BasePlayer *local = C_BasePlayer::GetLocalPlayer(); + if ( !local ) + { + Assert( 0 ); + return; + } + + m_bUsingPredictionRules = true; + + // Cull list now, if needed + int c = GetRecipientCount(); + if ( c == 0 ) + return; + + if ( !g_RecipientFilterPredictionSystem.CanPredict() ) + { + RemoveRecipient( local ); + } +} + +bool C_RecipientFilter::IsUsingPredictionRules( void ) const +{ + return m_bUsingPredictionRules; +} + +bool C_RecipientFilter::IgnorePredictionCull( void ) const +{ + return m_bIgnorePredictionCull; +} + +void C_RecipientFilter::SetIgnorePredictionCull( bool ignore ) +{ + m_bIgnorePredictionCull = ignore; +} + +CLocalPlayerFilter::CLocalPlayerFilter() +{ + if ( C_BasePlayer::GetLocalPlayer() ) + { + AddRecipient( C_BasePlayer::GetLocalPlayer() ); + } +} \ No newline at end of file diff --git a/game/client/c_recipientfilter.h b/game/client/c_recipientfilter.h new file mode 100644 index 00000000..dff59c3d --- /dev/null +++ b/game/client/c_recipientfilter.h @@ -0,0 +1,178 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_RECIPIENTFILTER_H +#define C_RECIPIENTFILTER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "irecipientfilter.h" +#include "utlvector.h" +#include "c_baseentity.h" +#include "soundflags.h" +#include "bitvec.h" + +class C_BasePlayer; +class C_Team; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_RecipientFilter : public IRecipientFilter +{ +public: + C_RecipientFilter(); + virtual ~C_RecipientFilter(); + + virtual bool IsReliable( void ) const; + + virtual int GetRecipientCount( void ) const; + virtual int GetRecipientIndex( int slot ) const; + + virtual bool IsInitMessage( void ) const { return false; }; + +public: + + void CopyFrom( const C_RecipientFilter& src ); + + void Reset( void ); + + void MakeReliable( void ); + + void AddAllPlayers( void ); + void AddRecipientsByPVS( const Vector& origin ); + void AddRecipientsByPAS( const Vector& origin ); + void AddRecipient( C_BasePlayer *player ); + void RemoveRecipient( C_BasePlayer *player ); + void AddRecipientsByTeam( C_Team *team ); + void RemoveRecipientsByTeam( C_Team *team ); + + void UsePredictionRules( void ); + bool IsUsingPredictionRules( void ) const; + + bool IgnorePredictionCull( void ) const; + void SetIgnorePredictionCull( bool ignore ); + + void AddPlayersFromBitMask( CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ); + +private: + + bool m_bReliable; + bool m_bInitMessage; + CUtlVector< int > m_Recipients; + // If using prediction rules, the filter itself suppresses local player + bool m_bUsingPredictionRules; + // If ignoring prediction cull, then external systems can determine + // whether this is a special case where culling should not occur + bool m_bIgnorePredictionCull; +}; + +//----------------------------------------------------------------------------- +// Purpose: Simple class to create a filter for a single player +//----------------------------------------------------------------------------- +class CSingleUserRecipientFilter : public C_RecipientFilter +{ +public: + CSingleUserRecipientFilter( C_BasePlayer *player ) + { + AddRecipient( player ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Simple class to create a filter for all players, unreliable +//----------------------------------------------------------------------------- +class CBroadcastRecipientFilter : public C_RecipientFilter +{ +public: + CBroadcastRecipientFilter( void ) + { + AddAllPlayers(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Simple class to create a filter for all players, reliable +//----------------------------------------------------------------------------- +class CReliableBroadcastRecipientFilter : public CBroadcastRecipientFilter +{ +public: + CReliableBroadcastRecipientFilter( void ) + { + MakeReliable(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Simple class to create a filter for a single player +//----------------------------------------------------------------------------- +class CPASFilter : public C_RecipientFilter +{ +public: + CPASFilter( const Vector& origin ) + { + AddRecipientsByPAS( origin ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPASAttenuationFilter : public CPASFilter +{ +public: + CPASAttenuationFilter( C_BaseEntity *entity, float attenuation = ATTN_NORM ) : + CPASFilter( entity->GetAbsOrigin() ) + { + } + + CPASAttenuationFilter( const Vector& origin, float attenuation = ATTN_NORM ) : + CPASFilter( origin ) + { + } + + CPASAttenuationFilter( C_BaseEntity *entity, const char *lookupSound ) : + CPASFilter( entity->GetAbsOrigin() ) + { + } + + CPASAttenuationFilter( const Vector& origin, const char *lookupSound ) : + CPASFilter( origin ) + { + } + + CPASAttenuationFilter( C_BaseEntity *entity, const char *lookupSound, HSOUNDSCRIPTHANDLE& handle ) : + CPASFilter( entity->GetAbsOrigin() ) + { + } + + CPASAttenuationFilter( const Vector& origin, const char *lookupSound, HSOUNDSCRIPTHANDLE& handle ) : + CPASFilter( origin ) + { + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Simple class to create a filter for a single player +//----------------------------------------------------------------------------- +class CPVSFilter : public C_RecipientFilter +{ +public: + CPVSFilter( const Vector& origin ) + { + AddRecipientsByPVS( origin ); + } +}; + +class CLocalPlayerFilter : public C_RecipientFilter +{ +public: + CLocalPlayerFilter( void ); +}; + +#endif // C_RECIPIENTFILTER_H diff --git a/game/client/c_rope.cpp b/game/client/c_rope.cpp new file mode 100644 index 00000000..8a562090 --- /dev/null +++ b/game/client/c_rope.cpp @@ -0,0 +1,1931 @@ +//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=====================================================================================// +#include "cbase.h" +#include "c_rope.h" +#include "beamdraw.h" +#include "view.h" +#include "env_wind_shared.h" +#include "input.h" +#include "rope_helpers.h" +#include "engine/ivmodelinfo.h" +#include "tier0/vprof.h" +#include "c_te_effect_dispatch.h" +#include "collisionutils.h" +#include +#include +#include "utllinkedlist.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "tier1/callqueue.h" +#include "tier1/memstack.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void RecvProxy_RecomputeSprings( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + // Have the regular proxy store the data. + RecvProxy_Int32ToInt32( pData, pStruct, pOut ); + + C_RopeKeyframe *pRope = (C_RopeKeyframe*)pStruct; + pRope->RecomputeSprings(); +} + + +IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_RopeKeyframe, DT_RopeKeyframe, CRopeKeyframe ) + RecvPropInt( RECVINFO(m_iRopeMaterialModelIndex) ), + RecvPropEHandle( RECVINFO(m_hStartPoint) ), + RecvPropEHandle( RECVINFO(m_hEndPoint) ), + RecvPropInt( RECVINFO(m_iStartAttachment) ), + RecvPropInt( RECVINFO(m_iEndAttachment) ), + + RecvPropInt( RECVINFO(m_fLockedPoints) ), + RecvPropInt( RECVINFO(m_Slack), 0, RecvProxy_RecomputeSprings ), + RecvPropInt( RECVINFO(m_RopeLength), 0, RecvProxy_RecomputeSprings ), + RecvPropInt( RECVINFO(m_RopeFlags) ), + RecvPropFloat( RECVINFO(m_TextureScale) ), + RecvPropInt( RECVINFO(m_nSegments) ), + RecvPropBool( RECVINFO(m_bConstrainBetweenEndpoints) ), + RecvPropInt( RECVINFO(m_Subdiv) ), + + RecvPropFloat( RECVINFO(m_Width) ), + RecvPropFloat( RECVINFO(m_flScrollSpeed) ), + RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), + RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ), + + RecvPropInt( RECVINFO( m_iParentAttachment ) ), +END_RECV_TABLE() + +#define ROPE_IMPULSE_SCALE 20 +#define ROPE_IMPULSE_DECAY 0.95 + +static ConVar rope_shake( "rope_shake", "0" ); +static ConVar rope_subdiv( "rope_subdiv", "2", 0, "Rope subdivision amount", true, 0, true, MAX_ROPE_SUBDIVS ); +static ConVar rope_collide( "rope_collide", "1", 0, "Collide rope with the world" ); + +static ConVar rope_smooth( "rope_smooth", "1", 0, "Do an antialiasing effect on ropes" ); +static ConVar rope_smooth_enlarge( "rope_smooth_enlarge", "1.4", 0, "How much to enlarge ropes in screen space for antialiasing effect" ); + +static ConVar rope_smooth_minwidth( "rope_smooth_minwidth", "0.3", 0, "When using smoothing, this is the min screenspace width it lets a rope shrink to" ); +static ConVar rope_smooth_minalpha( "rope_smooth_minalpha", "0.2", 0, "Alpha for rope antialiasing effect" ); + +static ConVar rope_smooth_maxalphawidth( "rope_smooth_maxalphawidth", "1.75" ); +static ConVar rope_smooth_maxalpha( "rope_smooth_maxalpha", "0.5", 0, "Alpha for rope antialiasing effect" ); + +static ConVar mat_fullbright( "mat_fullbright", "0", FCVAR_CHEAT ); // get it from the engine +static ConVar r_drawropes( "r_drawropes", "1", FCVAR_CHEAT ); +static ConVar r_ropetranslucent( "r_ropetranslucent", "1"); + +static ConVar rope_wind_dist( "rope_wind_dist", "1000", 0, "Don't use CPU applying small wind gusts to ropes when they're past this distance." ); +static ConVar rope_averagelight( "rope_averagelight", "1", 0, "Makes ropes use average of cubemap lighting instead of max intensity." ); + + +static ConVar rope_rendersolid( "rope_rendersolid", "1" ); + +static ConVar rope_solid_minwidth( "rope_solid_minwidth", "0.3" ); +static ConVar rope_solid_maxwidth( "rope_solid_maxwidth", "1" ); + +static ConVar rope_solid_minalpha( "rope_solid_minalpha", "0.0" ); +static ConVar rope_solid_maxalpha( "rope_solid_maxalpha", "1" ); + + +static CCycleCount g_RopeCollideTicks; +static CCycleCount g_RopeDrawTicks; +static CCycleCount g_RopeSimulateTicks; +static int g_nRopePointsSimulated; + +// Active ropes. +CUtlLinkedList g_Ropes; + + +static Vector g_FullBright_LightValues[ROPE_MAX_SEGMENTS]; +class CFullBrightLightValuesInit +{ +public: + CFullBrightLightValuesInit() + { + for( int i=0; i < ROPE_MAX_SEGMENTS; i++ ) + g_FullBright_LightValues[i].Init( 1, 1, 1 ); + } +} g_FullBrightLightValuesInit; + +// Precalculated info for rope subdivision. +static Vector g_RopeSubdivs[MAX_ROPE_SUBDIVS][MAX_ROPE_SUBDIVS]; +class CSubdivInit +{ +public: + CSubdivInit() + { + for ( int iSubdiv=0; iSubdiv < MAX_ROPE_SUBDIVS; iSubdiv++ ) + { + for( int i=0; i <= iSubdiv; i++ ) + { + float t = (float)(i+1) / (iSubdiv+1); + g_RopeSubdivs[iSubdiv][i].Init( t, t*t, t*t*t ); + } + } + } +} g_SubdivInit; + +//interesting barbed-wire-looking effect +static int g_nBarbedSubdivs = 3; +static Vector g_BarbedSubdivs[MAX_ROPE_SUBDIVS] = { Vector(1.5, 1.5*1.5, 1.5*1.5*1.5), + Vector(-0.5, -0.5 * -0.5, -0.5*-0.5*-0.5), + Vector(0.5, 0.5*0.5, 0.5*0.5*0.5) }; + +// This can be exposed through the entity if we ever care. +static float g_flLockAmount = 0.1; +static float g_flLockFalloff = 0.3; + + + + +class CQueuedRopeMemoryManager +{ +public: + CQueuedRopeMemoryManager( void ) + { + m_nCurrentStack = 0; + MEM_ALLOC_CREDIT(); + m_QueuedRopeMemory[0].Init( 131072, 0, 16384 ); + m_QueuedRopeMemory[1].Init( 131072, 0, 16384 ); + } + ~CQueuedRopeMemoryManager( void ) + { + m_QueuedRopeMemory[0].FreeAll( true ); + m_QueuedRopeMemory[1].FreeAll( true ); + for( int i = 0; i != 2; ++i ) + { + for( int j = m_DeleteOnSwitch[i].Count(); --j >= 0; ) + { + delete []m_DeleteOnSwitch[i].Element(j); + } + + m_DeleteOnSwitch[i].RemoveAll(); + } + } + + void SwitchStack( void ) + { + m_nCurrentStack = 1 - m_nCurrentStack; + m_QueuedRopeMemory[m_nCurrentStack].FreeAll( false ); + + for( int i = m_DeleteOnSwitch[m_nCurrentStack].Count(); --i >= 0; ) + { + delete []m_DeleteOnSwitch[m_nCurrentStack].Element(i); + } + m_DeleteOnSwitch[m_nCurrentStack].RemoveAll(); + } + + inline void *Alloc( size_t bytes ) + { + MEM_ALLOC_CREDIT(); + void *pReturn = m_QueuedRopeMemory[m_nCurrentStack].Alloc( bytes, false ); + if( pReturn == NULL ) + { + int iMaxSize = m_QueuedRopeMemory[m_nCurrentStack].GetMaxSize(); + Warning( "Overflowed rope queued rendering memory stack. Needed %d, have %d/%d\n", bytes, iMaxSize - m_QueuedRopeMemory[m_nCurrentStack].GetUsed(), iMaxSize ); + pReturn = new uint8 [bytes]; + m_DeleteOnSwitch[m_nCurrentStack].AddToTail( pReturn ); + } + return pReturn; + } + + CMemoryStack m_QueuedRopeMemory[2]; + int m_nCurrentStack; + CUtlVector m_DeleteOnSwitch[2]; //when we overflow the stack, we do new/delete +}; + +//============================================================================= +// +// Rope mananger. +// +struct RopeSegData_t +{ + int m_nSegmentCount; + BeamSeg_t m_Segments[MAX_ROPE_SEGMENTS]; + float m_BackWidths[MAX_ROPE_SEGMENTS]; + + // If this is less than rope_solid_minwidth and rope_solid_minalpha is 0, then we can avoid drawing.. + float m_flMaxBackWidth; +}; + +class CRopeManager : public IRopeManager +{ +public: + + CRopeManager(); + ~CRopeManager(); + + void ResetRenderCache( void ); + void AddToRenderCache( C_RopeKeyframe *pRope ); + void DrawRenderCache( bool bShadowDepth ); + void OnRenderStart( void ) + { + m_QueuedModeMemory.SwitchStack(); + } + +private: + struct RopeRenderData_t; +public: + void DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_t *pRenderCache, int nRenderCacheCount, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedData ); + + void ResetSegmentCache( int nMaxSegments ); + RopeSegData_t *GetNextSegmentFromCache( void ); + + enum { MAX_ROPE_RENDERCACHE = 128 }; + + void RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope ); + +private: + + void RenderNonSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount ); + void RenderSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount, bool bRenderNonSolid ); + +private: + + struct RopeRenderData_t + { + IMaterial *m_pSolidMaterial; + IMaterial *m_pBackMaterial; + int m_nCacheCount; + C_RopeKeyframe *m_aCache[MAX_ROPE_RENDERCACHE]; + }; + + CUtlVector m_aRenderCache; + int m_nSegmentCacheCount; + CUtlVector m_aSegmentCache; + CThreadFastMutex m_RenderCacheMutex; //there's only any contention during the switch from r_queued_ropes on to off + + //in queued material system mode we need to store off data for later use. + CQueuedRopeMemoryManager m_QueuedModeMemory; + + IMaterial* m_pDepthWriteMaterial; + + + struct RopeQueuedRenderCache_t + { + RopeRenderData_t *pCaches; + int iCacheCount; + RopeQueuedRenderCache_t( void ) : pCaches(NULL), iCacheCount(0) { }; + }; + + CUtlLinkedList m_RopeQueuedRenderCaches; +}; + +static CRopeManager s_RopeManager; + +IRopeManager *RopeManager() +{ + return &s_RopeManager; +} + + +inline bool ShouldUseFakeAA( IMaterial *pBackMaterial ) +{ + return pBackMaterial && rope_smooth.GetInt() && engine->GetDXSupportLevel() > 70 && !g_pMaterialSystemHardwareConfig->IsAAEnabled(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRopeManager::CRopeManager() +{ + m_aRenderCache.Purge(); + m_aSegmentCache.Purge(); + m_nSegmentCacheCount = 0; + m_pDepthWriteMaterial = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRopeManager::~CRopeManager() +{ + m_aRenderCache.Purge(); + m_aSegmentCache.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRopeManager::ResetRenderCache( void ) +{ + int nRenderCacheCount = m_aRenderCache.Count(); + for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache ) + { + m_aRenderCache[iRenderCache].m_nCacheCount = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRopeManager::AddToRenderCache( C_RopeKeyframe *pRope ) +{ + if( !pRope->GetSolidMaterial() ) + { + return; + } + + // Find the current rope list. + int iRenderCache = 0; + int nRenderCacheCount = m_aRenderCache.Count(); + for ( ; iRenderCache < nRenderCacheCount; ++iRenderCache ) + { + if ( ( pRope->GetSolidMaterial() == m_aRenderCache[iRenderCache].m_pSolidMaterial ) && + ( pRope->GetBackMaterial() == m_aRenderCache[iRenderCache].m_pBackMaterial ) ) + break; + } + + // A full rope list should have been generate in CreateRenderCache + // If we didn't find one, then allocate the mofo. + if ( iRenderCache == nRenderCacheCount ) + { + int iRenderCache = m_aRenderCache.AddToTail(); + m_aRenderCache[iRenderCache].m_pSolidMaterial = pRope->GetSolidMaterial(); + m_aRenderCache[iRenderCache].m_pBackMaterial = pRope->GetBackMaterial(); + m_aRenderCache[iRenderCache].m_nCacheCount = 0; + } + + if ( m_aRenderCache[iRenderCache].m_nCacheCount >= MAX_ROPE_RENDERCACHE ) + { + Warning( "CRopeManager::AddToRenderCache count to large for cache!\n" ); + return; + } + + m_aRenderCache[iRenderCache].m_aCache[m_aRenderCache[iRenderCache].m_nCacheCount] = pRope; + ++m_aRenderCache[iRenderCache].m_nCacheCount; +} + +void CRopeManager::DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_t *pRenderCache, int nRenderCacheCount, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedData ) +{ + VPROF_BUDGET( "CRopeManager::DrawRenderCache", VPROF_BUDGETGROUP_ROPES ); + AUTO_LOCK( m_RenderCacheMutex ); //contention cases: Toggling from queued mode on to off. Rope deletion from the cache. + + // Check to see if we want to render the ropes. + if( !r_drawropes.GetBool() ) + return; + + if ( bShadowDepth && !m_pDepthWriteMaterial && g_pMaterialSystem ) + { + KeyValues *pVMTKeyValues = new KeyValues( "DepthWrite" ); + pVMTKeyValues->SetInt( "$no_fullbright", 1 ); + pVMTKeyValues->SetInt( "$alphatest", 0 ); + pVMTKeyValues->SetInt( "$nocull", 1 ); + m_pDepthWriteMaterial = g_pMaterialSystem->FindProceduralMaterial( "__DepthWrite01", TEXTURE_GROUP_OTHER, pVMTKeyValues ); + } + + CMatRenderContextPtr pRenderContext( materials ); + + C_RopeKeyframe::BuildRopeQueuedData_t stackQueuedData; + Vector vStackPredictedPositions[MAX_ROPE_SEGMENTS]; + + for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache ) + { + int nCacheCount = pRenderCache[iRenderCache].m_nCacheCount; + + if ( nCacheCount == 0 ) + continue; + + ResetSegmentCache( nCacheCount ); + + for ( int iCache = 0; iCache < nCacheCount; ++iCache ) + { + C_RopeKeyframe *pRope = pRenderCache[iRenderCache].m_aCache[iCache]; + if ( pRope ) + { + RopeSegData_t *pRopeSegment = GetNextSegmentFromCache(); + + if( pBuildRopeQueuedData ) + { + pRope->BuildRope( pRopeSegment, vCurrentViewForward, vCurrentViewOrigin, pBuildRopeQueuedData ); + ++pBuildRopeQueuedData; + } + else + { + //to unify the BuildRope code, emulate the queued data + stackQueuedData.m_iNodeCount = pRope->m_RopePhysics.NumNodes(); + stackQueuedData.m_pLightValues = pRope->m_LightValues; + stackQueuedData.m_vColorMod = pRope->m_vColorMod; + stackQueuedData.m_pPredictedPositions = vStackPredictedPositions; + stackQueuedData.m_RopeLength = pRope->m_RopeLength; + stackQueuedData.m_Slack = pRope->m_Slack; + for( int i = 0; i != stackQueuedData.m_iNodeCount; ++i ) + { + vStackPredictedPositions[i] = pRope->m_RopePhysics.GetNode( i )->m_vPredicted; + } + + pRope->BuildRope( pRopeSegment, vCurrentViewForward, vCurrentViewOrigin, &stackQueuedData ); + } + } + else + { + if( pBuildRopeQueuedData ) + { + //we should only be here if a rope was in the queue and then deleted. We still have it's relevant data (and need to skip over it). + ++pBuildRopeQueuedData; + } + } + } + + int nVertCount = 0; + int nIndexCount = 0; + for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache ) + { + nVertCount += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 ); + nIndexCount += ( ( m_aSegmentCache[iSegmentCache].m_nSegmentCount - 1 ) * 6 ); + } + + // Render the non-solid portion of the ropes. + bool bRenderNonSolid = !bShadowDepth && ShouldUseFakeAA( pRenderCache[iRenderCache].m_pBackMaterial ); + if ( bRenderNonSolid ) + { + RenderNonSolidRopes( pRenderContext, pRenderCache[iRenderCache].m_pBackMaterial, nVertCount, nIndexCount ); + } + + // Render the solid portion of the ropes. + if ( rope_rendersolid.GetInt() ) + { + if ( bShadowDepth ) + RenderSolidRopes( pRenderContext, m_pDepthWriteMaterial, nVertCount, nIndexCount, bRenderNonSolid ); + else + RenderSolidRopes( pRenderContext, pRenderCache[iRenderCache].m_pSolidMaterial, nVertCount, nIndexCount, bRenderNonSolid ); + } + } + ResetSegmentCache( 0 ); + + if( pBuildRopeQueuedData && (m_RopeQueuedRenderCaches.Count() != 0) ) + m_RopeQueuedRenderCaches.Remove( m_RopeQueuedRenderCaches.Head() ); +} + +ConVar r_queued_ropes( "r_queued_ropes", "1" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRopeManager::DrawRenderCache( bool bShadowDepth ) +{ + int iRenderCacheCount = m_aRenderCache.Count(); + + if( iRenderCacheCount == 0 ) + return; + + Vector vForward = CurrentViewForward(); + Vector vOrigin = CurrentViewOrigin(); + + ICallQueue *pCallQueue; + if( r_queued_ropes.GetBool() && (pCallQueue = materials->GetRenderContext()->GetCallQueue()) != NULL ) + { + //material queue available and desired + CRopeManager::RopeRenderData_t *pRenderCache = m_aRenderCache.Base(); + AUTO_LOCK( m_RenderCacheMutex ); + + int iRopeCount = 0; + int iNodeCount = 0; + for( int i = 0; i != iRenderCacheCount; ++i ) + { + CRopeManager::RopeRenderData_t *pCache = &pRenderCache[i]; + int iCacheCount = pCache->m_nCacheCount; + iRopeCount += iCacheCount; + for( int j = 0; j != iCacheCount; ++j ) + { + C_RopeKeyframe *pRope = pCache->m_aCache[j]; + if( pRope ) + iNodeCount += pRope->m_RopePhysics.NumNodes(); + else + --iRopeCount; + } + } + + if( iRopeCount == 0 ) + return; //nothing to draw + + size_t iMemoryNeeded = (iRenderCacheCount * sizeof(CRopeManager::RopeRenderData_t)) + + (iRopeCount * sizeof(C_RopeKeyframe::BuildRopeQueuedData_t)) + + (iNodeCount * (sizeof(Vector) * 2)); + + void *pMemory = m_QueuedModeMemory.Alloc( iMemoryNeeded ); + + CRopeManager::RopeRenderData_t *pRenderCachesStart = (CRopeManager::RopeRenderData_t *)pMemory; + C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedDataStart = (C_RopeKeyframe::BuildRopeQueuedData_t *)(pRenderCachesStart + iRenderCacheCount); + Vector *pVectorDataStart = (Vector *)(pBuildRopeQueuedDataStart + iRopeCount); + + //memcpy( pRenderCachesStart, m_aRenderCache.Base(), iRenderCacheCount * sizeof( CRopeManager::RopeRenderData_t ) ); + + RopeQueuedRenderCache_t cache; + cache.pCaches = pRenderCachesStart; + cache.iCacheCount = iRenderCacheCount; + m_RopeQueuedRenderCaches.AddToTail( cache ); + + C_RopeKeyframe::BuildRopeQueuedData_t *pWriteRopeQueuedData = pBuildRopeQueuedDataStart; + Vector *pVectorWrite = (Vector *)pVectorDataStart; + + //Setup the rest of our data. This writes to two separate areas of memory at the same time. One area for the C_RopeKeyframe::BuildRopeQueuedData_t array, the other for mini-arrays of vector data + for( int i = 0; i != iRenderCacheCount; ++i ) + { + CRopeManager::RopeRenderData_t *pReadCache = &pRenderCache[i]; + CRopeManager::RopeRenderData_t *pWriteCache = &pRenderCachesStart[i]; + int iCacheCount = pReadCache->m_nCacheCount; + pWriteCache->m_nCacheCount = 0; + pWriteCache->m_pSolidMaterial = pReadCache->m_pSolidMaterial; + pWriteCache->m_pBackMaterial = pReadCache->m_pBackMaterial; + for( int j = 0; j != iCacheCount; ++j ) + { + C_RopeKeyframe *pRope = pReadCache->m_aCache[j]; + if( pRope == NULL ) + continue; + + pWriteCache->m_aCache[pWriteCache->m_nCacheCount] = pRope; + ++pWriteCache->m_nCacheCount; + + int iNodes = pRope->m_RopePhysics.NumNodes(); + + //setup the C_RopeKeyframe::BuildRopeQueuedData_t struct + pWriteRopeQueuedData->m_iNodeCount = pRope->m_RopePhysics.NumNodes(); + pWriteRopeQueuedData->m_vColorMod = pRope->m_vColorMod; + pWriteRopeQueuedData->m_RopeLength = pRope->m_RopeLength; + pWriteRopeQueuedData->m_Slack = pRope->m_Slack; + pWriteRopeQueuedData->m_pPredictedPositions = pVectorWrite; + pWriteRopeQueuedData->m_pLightValues = pVectorWrite + iNodes; + ++pWriteRopeQueuedData; + + //make two arrays, one of predicted positions followed immediately by light values + for( int k = 0; k != iNodes; ++k ) + { + pVectorWrite[0] = pRope->m_RopePhysics.GetNode( k )->m_vPredicted; + pVectorWrite[iNodes] = pRope->m_LightValues[k]; + ++pVectorWrite; + } + pVectorWrite += iNodes; //so we don't overwrite the light values with the next rope's predicted positions + } + } + Assert( ((void *)pVectorWrite == (void *)(((uint8 *)pMemory) + iMemoryNeeded)) && ((void *)pWriteRopeQueuedData == (void *)pVectorDataStart)); + pCallQueue->QueueCall( this, &CRopeManager::DrawRenderCache_NonQueued, bShadowDepth, pRenderCachesStart, iRenderCacheCount, vForward, vOrigin, pBuildRopeQueuedDataStart ); + } + else + { + DrawRenderCache_NonQueued( bShadowDepth, m_aRenderCache.Base(), iRenderCacheCount, vForward, vOrigin, NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRopeManager::RenderNonSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount ) +{ + // Render the solid portion of the ropes. + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertCount, nIndexCount ); + + CBeamSegDraw beamSegment; + + int nVerts = 0; + for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache ) + { + int nSegmentCount = m_aSegmentCache[iSegmentCache].m_nSegmentCount; + beamSegment.Start( pRenderContext, nSegmentCount, pMaterial, &meshBuilder, nVerts ); + for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment ) + { + beamSegment.NextSeg( &m_aSegmentCache[iSegmentCache].m_Segments[iSegment] ); + } + beamSegment.End(); + nVerts += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 ); + } + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRopeManager::RenderSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount, bool bRenderNonSolid ) +{ + // Render the solid portion of the ropes. + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertCount, nIndexCount ); + + CBeamSegDraw beamSegment; + + if ( bRenderNonSolid ) + { + int nVerts = 0; + for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache ) + { + RopeSegData_t *pSegData = &m_aSegmentCache[iSegmentCache]; + + // If it's all going to be 0 alpha, then just skip drawing this one. + if ( rope_solid_minalpha.GetFloat() == 0.0 && pSegData->m_flMaxBackWidth <= rope_solid_minwidth.GetFloat() ) + continue; + + int nSegmentCount = m_aSegmentCache[iSegmentCache].m_nSegmentCount; + beamSegment.Start( pRenderContext, nSegmentCount, pMaterial, &meshBuilder, nVerts ); + for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment ) + { + BeamSeg_t *pSeg = &m_aSegmentCache[iSegmentCache].m_Segments[iSegment]; + pSeg->m_flWidth = m_aSegmentCache[iSegmentCache].m_BackWidths[iSegment]; + + // To avoid aliasing, the "solid" version of the rope on xbox is just "more solid", + // and it has its own values controlling its alpha. + pSeg->m_flAlpha = RemapVal( pSeg->m_flWidth, + rope_solid_minwidth.GetFloat(), + rope_solid_maxwidth.GetFloat(), + rope_solid_minalpha.GetFloat(), + rope_solid_maxalpha.GetFloat() ); + + pSeg->m_flAlpha = clamp( pSeg->m_flAlpha, 0.0f, 1.0f ); + + beamSegment.NextSeg( &m_aSegmentCache[iSegmentCache].m_Segments[iSegment] ); + } + beamSegment.End(); + nVerts += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 ); + } + } + else + { + int nVerts = 0; + for ( int iSegmentCache = 0; iSegmentCache < m_nSegmentCacheCount; ++iSegmentCache ) + { + int nSegmentCount = m_aSegmentCache[iSegmentCache].m_nSegmentCount; + beamSegment.Start( pRenderContext, nSegmentCount, pMaterial, &meshBuilder, nVerts ); + for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment ) + { + beamSegment.NextSeg( &m_aSegmentCache[iSegmentCache].m_Segments[iSegment] ); + } + beamSegment.End(); + nVerts += ( m_aSegmentCache[iSegmentCache].m_nSegmentCount * 2 ); + } + } + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRopeManager::ResetSegmentCache( int nMaxSegments ) +{ + MEM_ALLOC_CREDIT(); + m_nSegmentCacheCount = 0; + if ( nMaxSegments ) + m_aSegmentCache.EnsureCount( nMaxSegments ); + else + m_aSegmentCache.Purge(); + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +RopeSegData_t *CRopeManager::GetNextSegmentFromCache( void ) +{ + if ( m_nSegmentCacheCount >= m_aSegmentCache.Count() ) + { + Warning( "CRopeManager::GetNextSegmentFromCache too many segments for cache!\n" ); + return NULL; + } + + ++m_nSegmentCacheCount; + return &m_aSegmentCache[m_nSegmentCacheCount-1]; +} + + + +void CRopeManager::RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope ) +{ + //remove this rope from queued render caches + AUTO_LOCK( m_RenderCacheMutex ); + int index = m_RopeQueuedRenderCaches.Head(); + while( m_RopeQueuedRenderCaches.IsValidIndex( index ) ) + { + RopeQueuedRenderCache_t &RenderCacheData = m_RopeQueuedRenderCaches[index]; + for( int i = 0; i != RenderCacheData.iCacheCount; ++i ) + { + RopeRenderData_t *pCache = &RenderCacheData.pCaches[i]; + for( int j = 0; j != pCache->m_nCacheCount; ++j ) + { + if( pCache->m_aCache[j] == pRope ) + { + pCache->m_aCache[j] = NULL; + } + } + } + + index = m_RopeQueuedRenderCaches.Next( index ); + } +} + +//============================================================================= + +// ------------------------------------------------------------------------------------ // +// Global functions. +// ------------------------------------------------------------------------------------ // + +void Rope_ResetCounters() +{ + g_RopeCollideTicks.Init(); + g_RopeDrawTicks.Init(); + g_RopeSimulateTicks.Init(); + g_nRopePointsSimulated = 0; +} + + +// ------------------------------------------------------------------------------------ // +// This handles the rope shake command. +// ------------------------------------------------------------------------------------ // + +void ShakeRopesCallback( const CEffectData &data ) +{ + Vector vCenter = data.m_vOrigin; + float flRadius = data.m_flRadius; + float flMagnitude = data.m_flMagnitude; + + // Now find any nearby ropes and shake them. + FOR_EACH_LL( g_Ropes, i ) + { + C_RopeKeyframe *pRope = g_Ropes[i]; + + pRope->ShakeRope( vCenter, flRadius, flMagnitude ); + } +} + +DECLARE_CLIENT_EFFECT( "ShakeRopes", ShakeRopesCallback ); + + + + +// ------------------------------------------------------------------------------------ // +// C_RopeKeyframe::CPhysicsDelegate +// ------------------------------------------------------------------------------------ // +#define WIND_FORCE_FACTOR 10 + +void C_RopeKeyframe::CPhysicsDelegate::GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ) +{ + // Gravity. + if ( !( m_pKeyframe->GetRopeFlags() & ROPE_NO_GRAVITY ) ) + { + pAccel->Init( ROPE_GRAVITY ); + } + + if( !m_pKeyframe->m_LinksTouchingSomething[iNode] && m_pKeyframe->m_bApplyWind) + { + Vector vecWindVel; + GetWindspeedAtTime(gpGlobals->curtime, vecWindVel); + if ( vecWindVel.LengthSqr() > 0 ) + { + Vector vecWindAccel; + VectorMA( *pAccel, WIND_FORCE_FACTOR, vecWindVel, *pAccel ); + } + else + { + if (m_pKeyframe->m_flCurrentGustTimer < m_pKeyframe->m_flCurrentGustLifetime ) + { + float div = m_pKeyframe->m_flCurrentGustTimer / m_pKeyframe->m_flCurrentGustLifetime; + float scale = 1 - cos( div * M_PI ); + + *pAccel += m_pKeyframe->m_vWindDir * scale; + } + } + } + + // HACK.. shake the rope around. + static float scale=15000; + if( rope_shake.GetInt() ) + { + *pAccel += RandomVector( -scale, scale ); + } + + // Apply any instananeous forces and reset + *pAccel += ROPE_IMPULSE_SCALE * m_pKeyframe->m_flImpulse; + m_pKeyframe->m_flImpulse *= ROPE_IMPULSE_DECAY; +} + + +void LockNodeDirection( + CSimplePhysics::CNode *pNodes, + int parity, + int nFalloffNodes, + float flLockAmount, + float flLockFalloff, + const Vector &vIdealDir ) +{ + for ( int i=0; i < nFalloffNodes; i++ ) + { + Vector &v0 = pNodes[i*parity].m_vPos; + Vector &v1 = pNodes[(i+1)*parity].m_vPos; + + Vector vDir = v1 - v0; + float len = vDir.Length(); + if ( len > 0.0001f ) + { + vDir /= len; + + Vector vActual; + VectorLerp( vDir, vIdealDir, flLockAmount, vActual ); + v1 = v0 + vActual * len; + + flLockAmount *= flLockFalloff; + } + } +} + + +void C_RopeKeyframe::CPhysicsDelegate::ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ) +{ + VPROF( "CPhysicsDelegate::ApplyConstraints" ); + + CTraceFilterWorldOnly traceFilter; + + // Collide with the world. + if( ((m_pKeyframe->m_RopeFlags & ROPE_COLLIDE) && + rope_collide.GetInt()) || + (rope_collide.GetInt() == 2) ) + { + CTimeAdder adder( &g_RopeCollideTicks ); + + for( int i=0; i < nNodes; i++ ) + { + CSimplePhysics::CNode *pNode = &pNodes[i]; + + int iIteration; + int nIterations = 10; + for( iIteration=0; iIteration < nIterations; iIteration++ ) + { + trace_t trace; + UTIL_TraceHull( pNode->m_vPrevPos, pNode->m_vPos, + Vector(-2,-2,-2), Vector(2,2,2), MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); + + if( trace.fraction == 1 ) + break; + + if( trace.fraction == 0 || trace.allsolid || trace.startsolid ) + { + m_pKeyframe->m_LinksTouchingSomething[i] = true; + pNode->m_vPos = pNode->m_vPrevPos; + break; + } + + // Apply some friction. + static float flSlowFactor = 0.3f; + pNode->m_vPos -= (pNode->m_vPos - pNode->m_vPrevPos) * flSlowFactor; + + // Move it out along the face normal. + float distBehind = trace.plane.normal.Dot( pNode->m_vPos ) - trace.plane.dist; + pNode->m_vPos += trace.plane.normal * (-distBehind + 2.2); + m_pKeyframe->m_LinksTouchingSomething[i] = true; + } + + if( iIteration == nIterations ) + pNodes[i].m_vPos = pNodes[i].m_vPrevPos; + } + } + + // Lock the endpoints. + QAngle angles; + if( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_START_POINT ) + { + m_pKeyframe->GetEndPointAttachment( 0, pNodes[0].m_vPos, angles ); + if (( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_START_DIRECTION ) && (nNodes > 3)) + { + Vector forward; + AngleVectors( angles, &forward ); + + int parity = 1; + int nFalloffNodes = min( 2, nNodes - 2 ); + LockNodeDirection( pNodes, parity, nFalloffNodes, g_flLockAmount, g_flLockFalloff, forward ); + } + } + + if( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_END_POINT ) + { + m_pKeyframe->GetEndPointAttachment( 1, pNodes[nNodes-1].m_vPos, angles ); + if( m_pKeyframe->m_fLockedPoints & ROPE_LOCK_END_DIRECTION && (nNodes > 3)) + { + Vector forward; + AngleVectors( angles, &forward ); + + int parity = -1; + int nFalloffNodes = min( 2, nNodes - 2 ); + LockNodeDirection( &pNodes[nNodes-1], parity, nFalloffNodes, g_flLockAmount, g_flLockFalloff, forward ); + } + } +} + + +// ------------------------------------------------------------------------------------ // +// C_RopeKeyframe +// ------------------------------------------------------------------------------------ // + +C_RopeKeyframe::C_RopeKeyframe() +{ + m_bEndPointAttachmentPositionsDirty = true; + m_bEndPointAttachmentAnglesDirty = true; + m_PhysicsDelegate.m_pKeyframe = this; + m_pMaterial = NULL; + m_bPhysicsInitted = false; + m_RopeFlags = 0; + m_TextureHeight = 1; + m_hStartPoint = m_hEndPoint = NULL; + m_iStartAttachment = m_iEndAttachment = 0; + m_vColorMod.Init( 1, 1, 1 ); + m_nLinksTouchingSomething = 0; + m_Subdiv = 255; // default to using the cvar + + m_fLockedPoints = 0; + m_fPrevLockedPoints = 0; + + m_iForcePointMoveCounter = 0; + m_flCurScroll = m_flScrollSpeed = 0; + m_TextureScale = 4; // 4:1 + m_flImpulse.Init(); + + g_Ropes.AddToTail( this ); +} + + +C_RopeKeyframe::~C_RopeKeyframe() +{ + s_RopeManager.RemoveRopeFromQueuedRenderCaches( this ); + g_Ropes.FindAndRemove( this ); +} + + +C_RopeKeyframe* C_RopeKeyframe::Create( + C_BaseEntity *pStartEnt, + C_BaseEntity *pEndEnt, + int iStartAttachment, + int iEndAttachment, + float ropeWidth, + const char *pMaterialName, + int numSegments, + int ropeFlags + ) +{ + C_RopeKeyframe *pRope = new C_RopeKeyframe; + + pRope->InitializeAsClientEntity( NULL, RENDER_GROUP_OPAQUE_ENTITY ); + + if ( pStartEnt ) + { + pRope->m_hStartPoint = pStartEnt; + pRope->m_fLockedPoints |= ROPE_LOCK_START_POINT; + } + + if ( pEndEnt ) + { + pRope->m_hEndPoint = pEndEnt; + pRope->m_fLockedPoints |= ROPE_LOCK_END_POINT; + } + + pRope->m_iStartAttachment = iStartAttachment; + pRope->m_iEndAttachment = iEndAttachment; + pRope->m_Width = ropeWidth; + pRope->m_nSegments = clamp( numSegments, 2, ROPE_MAX_SEGMENTS ); + pRope->m_RopeFlags = ropeFlags; + + pRope->FinishInit( pMaterialName ); + return pRope; +} + + +C_RopeKeyframe* C_RopeKeyframe::CreateFromKeyValues( C_BaseAnimating *pEnt, KeyValues *pValues ) +{ + C_RopeKeyframe *pRope = C_RopeKeyframe::Create( + pEnt, + pEnt, + pEnt->LookupAttachment( pValues->GetString( "StartAttachment" ) ), + pEnt->LookupAttachment( pValues->GetString( "EndAttachment" ) ), + pValues->GetFloat( "Width", 0.5 ), + pValues->GetString( "Material" ), + pValues->GetInt( "NumSegments" ), + 0 ); + + if ( pRope ) + { + if ( pValues->GetInt( "Gravity", 1 ) == 0 ) + { + pRope->m_RopeFlags |= ROPE_NO_GRAVITY; + } + + pRope->m_RopeLength = pValues->GetInt( "Length" ); + pRope->m_TextureScale = pValues->GetFloat( "TextureScale", pRope->m_TextureScale ); + pRope->m_Slack = 0; + pRope->m_RopeFlags |= ROPE_SIMULATE; + } + + return pRope; +} + + +int C_RopeKeyframe::GetRopesIntersectingAABB( C_RopeKeyframe **pRopes, int nMaxRopes, const Vector &vAbsMin, const Vector &vAbsMax ) +{ + if ( nMaxRopes == 0 ) + return 0; + + int nRopes = 0; + FOR_EACH_LL( g_Ropes, i ) + { + C_RopeKeyframe *pRope = g_Ropes[i]; + + Vector v1, v2; + if ( pRope->GetEndPointPos( 0, v1 ) && pRope->GetEndPointPos( 1, v2 ) ) + { + if ( IsBoxIntersectingRay( v1, v2-v1, vAbsMin, vAbsMax, 0.1f ) ) + { + pRopes[nRopes++] = pRope; + if ( nRopes == nMaxRopes ) + break; + } + } + } + + return nRopes; +} + + +void C_RopeKeyframe::SetSlack( int slack ) +{ + m_Slack = slack; + RecomputeSprings(); +} + + +void C_RopeKeyframe::SetRopeFlags( int flags ) +{ + m_RopeFlags = flags; + UpdateVisibility(); +} + + +int C_RopeKeyframe::GetRopeFlags() const +{ + return m_RopeFlags; +} + + +void C_RopeKeyframe::SetupHangDistance( float flHangDist ) +{ + C_BaseEntity *pEnt1 = m_hStartPoint; + C_BaseEntity *pEnt2 = m_hEndPoint; + if ( !pEnt1 || !pEnt2 ) + return; + + QAngle dummyAngles; + + // Calculate starting conditions so we can force it to hang down N inches. + Vector v1 = pEnt1->GetAbsOrigin(); + pEnt1->GetAttachment( m_iStartAttachment, v1, dummyAngles ); + + Vector v2 = pEnt2->GetAbsOrigin(); + pEnt2->GetAttachment( m_iEndAttachment, v2, dummyAngles ); + + float flSlack, flLen; + CalcRopeStartingConditions( v1, v2, ROPE_MAX_SEGMENTS, flHangDist, &flLen, &flSlack ); + + m_RopeLength = (int)flLen; + m_Slack = (int)flSlack; + + RecomputeSprings(); +} + + +void C_RopeKeyframe::SetStartEntity( C_BaseEntity *pEnt ) +{ + m_hStartPoint = pEnt; +} + + +void C_RopeKeyframe::SetEndEntity( C_BaseEntity *pEnt ) +{ + m_hEndPoint = pEnt; +} + + +C_BaseEntity* C_RopeKeyframe::GetStartEntity() const +{ + return m_hStartPoint; +} + + +C_BaseEntity* C_RopeKeyframe::GetEndEntity() const +{ + return m_hEndPoint; +} + + +CSimplePhysics::IHelper* C_RopeKeyframe::HookPhysics( CSimplePhysics::IHelper *pHook ) +{ + m_RopePhysics.SetDelegate( pHook ); + return &m_PhysicsDelegate; +} + + +void C_RopeKeyframe::SetColorMod( const Vector &vColorMod ) +{ + m_vColorMod = vColorMod; +} + + +void C_RopeKeyframe::RecomputeSprings() +{ + m_RopePhysics.ResetSpringLength( + (m_RopeLength + m_Slack + ROPESLACK_FUDGEFACTOR) / (m_RopePhysics.NumNodes() - 1) ); +} + + +void C_RopeKeyframe::ShakeRope( const Vector &vCenter, float flRadius, float flMagnitude ) +{ + // Sum up whatever it would apply to all of our points. + for ( int i=0; i < m_nSegments; i++ ) + { + CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i ); + + float flDist = (pNode->m_vPos - vCenter).Length(); + + float flShakeAmount = 1.0f - flDist / flRadius; + if ( flShakeAmount >= 0 ) + { + m_flImpulse.z += flShakeAmount * flMagnitude; + } + } +} + + +void C_RopeKeyframe::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + m_bNewDataThisFrame = true; + + if( updateType != DATA_UPDATE_CREATED ) + return; + + // Figure out the material name. + char str[512]; + const model_t *pModel = modelinfo->GetModel( m_iRopeMaterialModelIndex ); + if ( pModel ) + { + Q_strncpy( str, modelinfo->GetModelName( pModel ), sizeof( str ) ); + + // Get rid of the extension because the material system doesn't want it. + char *pExt = Q_stristr( str, ".vmt" ); + if ( pExt ) + pExt[0] = 0; + } + else + { + Q_strncpy( str, "asdf", sizeof( str ) ); + } + + FinishInit( str ); +} + + +void C_RopeKeyframe::FinishInit( const char *pMaterialName ) +{ + // Get the material from the material system. + m_pMaterial = materials->FindMaterial( pMaterialName, TEXTURE_GROUP_OTHER ); + if( m_pMaterial ) + m_TextureHeight = m_pMaterial->GetMappingHeight(); + else + m_TextureHeight = 1; + + char backName[512]; + Q_snprintf( backName, sizeof( backName ), "%s_back", pMaterialName ); + + m_pBackMaterial = materials->FindMaterial( backName, TEXTURE_GROUP_OTHER, false ); + if ( IsErrorMaterial( m_pBackMaterial ) ) + m_pBackMaterial = NULL; + + if ( m_pBackMaterial ) + m_pBackMaterial->GetMappingWidth(); + + // Init rope physics. + m_nSegments = clamp( m_nSegments, 2, ROPE_MAX_SEGMENTS ); + m_RopePhysics.SetNumNodes( m_nSegments ); + + SetCollisionBounds( Vector( -10, -10, -10 ), Vector( 10, 10, 10 ) ); + + // We want to think every frame. + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +void C_RopeKeyframe::RunRopeSimulation( float flSeconds ) +{ + // First, forget about links touching things. + for ( int i=0; i < m_nSegments; i++ ) + m_LinksTouchingSomething[i] = false; + + // Simulate, and it will mark which links touched things. + m_RopePhysics.Simulate( flSeconds ); + + // Now count how many links touched something. + m_nLinksTouchingSomething = 0; + for ( int i=0; i < m_nSegments; i++ ) + { + if ( m_LinksTouchingSomething[i] ) + ++m_nLinksTouchingSomething; + } +} + +Vector C_RopeKeyframe::ConstrainNode( const Vector &vNormal, const Vector &vNodePosition, const Vector &vMidpiont, float fNormalLength ) +{ + // Get triangle edges formed + Vector vMidpointToNode = vNodePosition - vMidpiont; + Vector vMidpointToNodeProjected = vMidpointToNode.Dot( vNormal ) * vNormal; + float fMidpointToNodeLengh = VectorNormalize( vMidpointToNode ); + float fMidpointToNodeProjectedLengh = VectorNormalize( vMidpointToNodeProjected ); + + // See if it's past an endpoint + if ( fMidpointToNodeProjectedLengh < fNormalLength + 1.0f ) + return vNodePosition; + + // Apply the ratio between the triangles + return vMidpiont + vMidpointToNode * fMidpointToNodeLengh * ( fNormalLength / fMidpointToNodeProjectedLengh ); +} + +void C_RopeKeyframe::ConstrainNodesBetweenEndpoints( void ) +{ + if ( !m_bConstrainBetweenEndpoints ) + return; + + // Get midpoint and normals + Vector vMidpiont = ( m_vCachedEndPointAttachmentPos[ 0 ] + m_vCachedEndPointAttachmentPos[ 1 ] ) / 2.0f; + Vector vNormal = vMidpiont - m_vCachedEndPointAttachmentPos[ 0 ]; + float fNormalLength = VectorNormalize( vNormal ); + + // Loop through all the middle segments and ensure their positions are constrained between the endpoints + for ( int i = 1; i < m_RopePhysics.NumNodes() - 1; ++i ) + { + // Fix the current position + m_RopePhysics.GetNode( i )->m_vPos = ConstrainNode( vNormal, m_RopePhysics.GetNode( i )->m_vPos, vMidpiont, fNormalLength ); + + // Fix the predicted position + m_RopePhysics.GetNode( i )->m_vPredicted = ConstrainNode( vNormal, m_RopePhysics.GetNode( i )->m_vPredicted, vMidpiont, fNormalLength ); + } +} + +void C_RopeKeyframe::ClientThink() +{ + // Only recalculate the endpoint attachments once per frame. + m_bEndPointAttachmentPositionsDirty = true; + m_bEndPointAttachmentAnglesDirty = true; + + if( !r_drawropes.GetBool() ) + return; + + if( !InitRopePhysics() ) // init if not already + return; + + if( !DetectRestingState( m_bApplyWind ) ) + { + // Update the simulation. + CTimeAdder adder( &g_RopeSimulateTicks ); + + RunRopeSimulation( gpGlobals->frametime ); + + g_nRopePointsSimulated += m_RopePhysics.NumNodes(); + + m_bNewDataThisFrame = false; + + // Setup a new wind gust? + m_flCurrentGustTimer += gpGlobals->frametime; + m_flTimeToNextGust -= gpGlobals->frametime; + if( m_flTimeToNextGust <= 0 ) + { + m_vWindDir = RandomVector( -1, 1 ); + VectorNormalize( m_vWindDir ); + + static float basicScale = 50; + m_vWindDir *= basicScale; + m_vWindDir *= RandomFloat( -1.0f, 1.0f ); + + m_flCurrentGustTimer = 0; + m_flCurrentGustLifetime = RandomFloat( 2.0f, 3.0f ); + + m_flTimeToNextGust = RandomFloat( 3.0f, 4.0f ); + } + + UpdateBBox(); + } +} + + +int C_RopeKeyframe::DrawModel( int flags ) +{ + VPROF_BUDGET( "C_RopeKeyframe::DrawModel", VPROF_BUDGETGROUP_ROPES ); + if( !InitRopePhysics() ) + return 0; + + if ( !m_bReadyToDraw ) + return 0; + + // Resize the rope + if( m_RopeFlags & ROPE_RESIZE ) + { + RecomputeSprings(); + } + + // If our start & end entities have models, but are nodraw, then we don't draw + if ( m_hStartPoint && m_hStartPoint->IsDormant() && m_hEndPoint && m_hEndPoint->IsDormant() ) + { + // Check models because rope endpoints are point entities + if ( m_hStartPoint->GetModelIndex() && m_hEndPoint->GetModelIndex() ) + return 0; + } + + ConstrainNodesBetweenEndpoints(); + + RopeManager()->AddToRenderCache( this ); + return 1; +} + +bool C_RopeKeyframe::ShouldDraw() +{ + if( !r_ropetranslucent.GetBool() ) + return false; + + if( !(m_RopeFlags & ROPE_SIMULATE) ) + return false; + + return true; +} + +const Vector& C_RopeKeyframe::WorldSpaceCenter( ) const +{ + return GetAbsOrigin(); +} + +bool C_RopeKeyframe::GetAttachment( int number, matrix3x4_t &matrix ) +{ + int nNodes = m_RopePhysics.NumNodes(); + if ( (number != ROPE_ATTACHMENT_START_POINT && number != ROPE_ATTACHMENT_END_POINT) || nNodes < 2 ) + return false; + + // Now setup the orientation based on the last segment. + Vector vForward, origin; + if ( number == ROPE_ATTACHMENT_START_POINT ) + { + origin = m_RopePhysics.GetNode( 0 )->m_vPredicted; + vForward = m_RopePhysics.GetNode( 0 )->m_vPredicted - m_RopePhysics.GetNode( 1 )->m_vPredicted; + } + else + { + origin = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted; + vForward = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted - m_RopePhysics.GetNode( nNodes-2 )->m_vPredicted; + } + VectorMatrix( vForward, matrix ); + PositionMatrix( origin, matrix ); + return true; +} + +bool C_RopeKeyframe::GetAttachment( int number, Vector &origin ) +{ + int nNodes = m_RopePhysics.NumNodes(); + if ( (number != ROPE_ATTACHMENT_START_POINT && number != ROPE_ATTACHMENT_END_POINT) || nNodes < 2 ) + return false; + + // Now setup the orientation based on the last segment. + if ( number == ROPE_ATTACHMENT_START_POINT ) + { + origin = m_RopePhysics.GetNode( 0 )->m_vPredicted; + } + else + { + origin = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted; + } + return true; +} + +bool C_RopeKeyframe::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ) +{ + Assert(0); + return false; +} + +bool C_RopeKeyframe::GetAttachment( int number, Vector &origin, QAngle &angles ) +{ + int nNodes = m_RopePhysics.NumNodes(); + if ( (number == ROPE_ATTACHMENT_START_POINT || number == ROPE_ATTACHMENT_END_POINT) && nNodes >= 2 ) + { + // Now setup the orientation based on the last segment. + Vector vForward; + if ( number == ROPE_ATTACHMENT_START_POINT ) + { + origin = m_RopePhysics.GetNode( 0 )->m_vPredicted; + vForward = m_RopePhysics.GetNode( 0 )->m_vPredicted - m_RopePhysics.GetNode( 1 )->m_vPredicted; + } + else + { + origin = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted; + vForward = m_RopePhysics.GetNode( nNodes-1 )->m_vPredicted - m_RopePhysics.GetNode( nNodes-2 )->m_vPredicted; + } + VectorAngles( vForward, angles ); + + return true; + } + + return false; +} + +bool C_RopeKeyframe::AnyPointsMoved() +{ + for( int i=0; i < m_RopePhysics.NumNodes(); i++ ) + { + CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i ); + float flMoveDistSqr = (pNode->m_vPos - pNode->m_vPrevPos).LengthSqr(); + if( flMoveDistSqr > 0.03f ) + return true; + } + + if( --m_iForcePointMoveCounter > 0 ) + return true; + + return false; +} + + +inline bool C_RopeKeyframe::DidEndPointMove( int iPt ) +{ + // If this point isn't locked anyway, just break out. + if( !( m_fLockedPoints & (1 << iPt) ) ) + return false; + + bool bOld = m_bPrevEndPointPos[iPt]; + Vector vOld = m_vPrevEndPointPos[iPt]; + + m_bPrevEndPointPos[iPt] = GetEndPointPos( iPt, m_vPrevEndPointPos[iPt] ); + + // If it wasn't and isn't attached to anything, don't register a change. + if( !bOld && !m_bPrevEndPointPos[iPt] ) + return true; + + // Register a change if the endpoint moves. + if( !VectorsAreEqual( vOld, m_vPrevEndPointPos[iPt], 0.1 ) ) + return true; + + return false; +} + + +bool C_RopeKeyframe::DetectRestingState( bool &bApplyWind ) +{ + bApplyWind = false; + + if( m_fPrevLockedPoints != m_fLockedPoints ) + { + // Force it to move the points for some number of frames when they get detached or + // after we get new data. This allows them to accelerate from gravity. + m_iForcePointMoveCounter = 10; + m_fPrevLockedPoints = m_fLockedPoints; + return false; + } + + if( m_bNewDataThisFrame ) + { + // Simulate if anything about us changed this frame, such as our position due to hierarchy. + // FIXME: this won't work when hierarchy is client side + return false; + } + + // Make sure our attachment points haven't moved. + if( DidEndPointMove( 0 ) || DidEndPointMove( 1 ) ) + return false; + + // See how close we are to the line. + Vector &vEnd1 = m_RopePhysics.GetFirstNode()->m_vPos; + Vector &vEnd2 = m_RopePhysics.GetLastNode()->m_vPos; + + if ( !( m_RopeFlags & ROPE_NO_WIND ) ) + { + // Don't apply wind if more than half of the nodes are touching something. + float flDist1 = CalcDistanceToLineSegment( MainViewOrigin(), vEnd1, vEnd2 ); + if( m_nLinksTouchingSomething < (m_RopePhysics.NumNodes() >> 1) ) + bApplyWind = flDist1 < rope_wind_dist.GetFloat(); + } + + if ( m_flPreviousImpulse != m_flImpulse ) + { + m_flPreviousImpulse = m_flImpulse; + return false; + } + + return !AnyPointsMoved() && !bApplyWind && !rope_shake.GetInt(); +} + +// simple struct to precompute basis for catmull rom splines for faster evaluation +struct catmull_t +{ + Vector t3; + Vector t2; + Vector t; + Vector c; +}; + +// bake out the terms of the catmull rom spline +void Catmull_Rom_Spline_Matrix( const Vector &p1, const Vector &p2, const Vector &p3, const Vector &p4, catmull_t &output ) +{ + output.t3 = 0.5f * ((-1*p1) + (3*p2) + (-3*p3) + p4); // 0.5 t^3 * [ (-1*p1) + ( 3*p2) + (-3*p3) + p4 ] + output.t2 = 0.5f * ((2*p1) + (-5*p2) + (4*p3) - p4); // 0.5 t^2 * [ ( 2*p1) + (-5*p2) + ( 4*p3) - p4 ] + output.t = 0.5f * ((-1*p1) + p3); // 0.5 t * [ (-1*p1) + p3 ] + output.c = p2; // p2 +} + +// evaluate one point on the spline, t is a vector of (t, t^2, t^3) +inline void Catmull_Rom_Eval( const catmull_t &spline, const Vector &t, Vector &output ) +{ + Assert(spline.c.IsValid()); + Assert(spline.t.IsValid()); + Assert(spline.t2.IsValid()); + Assert(spline.t3.IsValid()); + output = spline.c + (t.x * spline.t) + (t.y*spline.t2) + (t.z * spline.t3); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_RopeKeyframe::BuildRope( RopeSegData_t *pSegmentData, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pQueuedData ) +{ + if ( !pSegmentData ) + return; + + // Get the lighting values. + Vector *pLightValues = ( mat_fullbright.GetInt() == 1 ) ? g_FullBright_LightValues : pQueuedData->m_pLightValues; + + // Update the rope subdivisions if necessary. + int nSubdivCount; + Vector *pSubdivVecList = GetRopeSubdivVectors( &nSubdivCount ); + + int nSegmentCount = 0; + int iPrevNode = 0; + const float subdivScale = 1.0f / (nSubdivCount+1); + const int nodeCount = pQueuedData->m_iNodeCount; + const int lastNode = nodeCount-1; + catmull_t spline; + + Vector *pPredictedPositions = pQueuedData->m_pPredictedPositions; + Vector vColorMod = pQueuedData->m_vColorMod; + + for( int iNode = 0; iNode < nodeCount; ++iNode ) + { + pSegmentData->m_Segments[nSegmentCount].m_vPos = pPredictedPositions[iNode]; + pSegmentData->m_Segments[nSegmentCount].m_vColor = pLightValues[iNode] * vColorMod; + ++nSegmentCount; + + if ( iNode < lastNode ) + { + // Draw a midpoint to the next segment. + int iNext = iNode + 1; + int iNextNext = iNode + 2; + if ( iNext >= nodeCount ) + { + iNext = iNextNext = lastNode; + } + else if ( iNextNext >= nodeCount ) + { + iNextNext = lastNode; + } + + Vector vecColorInc = subdivScale * ( ( pLightValues[iNode+1] - pLightValues[iNode] ) * vColorMod ); + // precompute spline basis + Catmull_Rom_Spline_Matrix( pPredictedPositions[iPrevNode], pPredictedPositions[iNode], + pPredictedPositions[iNext], pPredictedPositions[iNextNext], spline ); + for( int iSubdiv = 0; iSubdiv < nSubdivCount; ++iSubdiv ) + { + pSegmentData->m_Segments[nSegmentCount].m_vColor = pSegmentData->m_Segments[nSegmentCount-1].m_vColor + vecColorInc; + // simple eval using precomputed basis + Catmull_Rom_Eval( spline, pSubdivVecList[iSubdiv], pSegmentData->m_Segments[nSegmentCount].m_vPos ); + + ++nSegmentCount; + Assert( nSegmentCount <= MAX_ROPE_SEGMENTS ); + } + + iPrevNode = iNode; + } + } + pSegmentData->m_nSegmentCount = nSegmentCount; + pSegmentData->m_flMaxBackWidth = 0; + + // Figure out texture scale. + float flPixelsPerInch = 4.0f / m_TextureScale; + float flTotalTexCoord = flPixelsPerInch * ( pQueuedData->m_RopeLength + pQueuedData->m_Slack + ROPESLACK_FUDGEFACTOR ); + int nTotalPoints = ( nodeCount - 1 ) * nSubdivCount + 1; + float flActualInc = ( flTotalTexCoord / nTotalPoints ) / ( float )m_TextureHeight; + + // First draw a translucent rope underneath the solid rope for an antialiasing effect. + if ( ShouldUseFakeAA( m_pBackMaterial ) ) + { + // Compute screen width + float flScreenWidth = ScreenWidth(); + float flHalfScreenWidth = flScreenWidth / 2.0f; + + float flExtraScreenSpaceWidth = rope_smooth_enlarge.GetFloat(); + + float flMinAlpha = rope_smooth_minalpha.GetFloat(); + float flMaxAlpha = rope_smooth_maxalpha.GetFloat(); + + float flMinScreenSpaceWidth = rope_smooth_minwidth.GetFloat(); + float flMaxAlphaScreenSpaceWidth = rope_smooth_maxalphawidth.GetFloat(); + + float flTexCoord = m_flCurScroll; + for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment ) + { + pSegmentData->m_Segments[iSegment].m_flTexCoord = flTexCoord; + + // Right here, we need to specify a width that will be 1 pixel larger in screen space. + float zCoord = vCurrentViewForward.Dot( pSegmentData->m_Segments[iSegment].m_vPos - vCurrentViewOrigin ); + zCoord = max( zCoord, 0.1f ); + + float flScreenSpaceWidth = m_Width * flHalfScreenWidth / zCoord; + if ( flScreenSpaceWidth < flMinScreenSpaceWidth ) + { + pSegmentData->m_Segments[iSegment].m_flAlpha = flMinAlpha; + pSegmentData->m_Segments[iSegment].m_flWidth = flMinScreenSpaceWidth * zCoord / flHalfScreenWidth; + pSegmentData->m_BackWidths[iSegment] = 0.0f; + } + else + { + if ( flScreenSpaceWidth > flMaxAlphaScreenSpaceWidth ) + { + pSegmentData->m_Segments[iSegment].m_flAlpha = flMaxAlpha; + } + else + { + pSegmentData->m_Segments[iSegment].m_flAlpha = RemapVal( flScreenSpaceWidth, flMinScreenSpaceWidth, flMaxAlphaScreenSpaceWidth, flMinAlpha, flMaxAlpha ); + } + + pSegmentData->m_Segments[iSegment].m_flWidth = m_Width; + pSegmentData->m_BackWidths[iSegment] = m_Width - ( zCoord * flExtraScreenSpaceWidth ) / flScreenWidth; + if ( pSegmentData->m_BackWidths[iSegment] < 0.0f ) + { + pSegmentData->m_BackWidths[iSegment] = 0.0f; + } + else + { + pSegmentData->m_flMaxBackWidth = max( pSegmentData->m_flMaxBackWidth, pSegmentData->m_BackWidths[iSegment] ); + } + } + + // Get the next texture coordinate. + flTexCoord += flActualInc; + } + } + else + { + float flTexCoord = m_flCurScroll; + + // Build the data with no smoothing. + for ( int iSegment = 0; iSegment < nSegmentCount; ++iSegment ) + { + pSegmentData->m_Segments[iSegment].m_flTexCoord = flTexCoord; + pSegmentData->m_Segments[iSegment].m_flAlpha = 0.3f; + pSegmentData->m_Segments[iSegment].m_flWidth = m_Width; + pSegmentData->m_BackWidths[iSegment] = -1.0f; + + // Get the next texture coordinate. + flTexCoord += flActualInc; + } + } +} + +void C_RopeKeyframe::UpdateBBox() +{ + Vector &vStart = m_RopePhysics.GetFirstNode()->m_vPos; + Vector &vEnd = m_RopePhysics.GetLastNode()->m_vPos; + + Vector mins, maxs; + + VectorMin( vStart, vEnd, mins ); + VectorMax( vStart, vEnd, maxs ); + + for( int i=1; i < m_RopePhysics.NumNodes()-1; i++ ) + { + const Vector &vPos = m_RopePhysics.GetNode(i)->m_vPos; + AddPointToBounds( vPos, mins, maxs ); + } + + mins -= GetAbsOrigin(); + maxs -= GetAbsOrigin(); + SetCollisionBounds( mins, maxs ); +} + + +bool C_RopeKeyframe::InitRopePhysics() +{ + if( !(m_RopeFlags & ROPE_SIMULATE) ) + return 0; + + if( m_bPhysicsInitted ) + { + return true; + } + + // Must have both entities to work. + m_bPrevEndPointPos[0] = GetEndPointPos( 0, m_vPrevEndPointPos[0] ); + if( !m_bPrevEndPointPos[0] ) + return false; + + // They're allowed to not have an end attachment point so the rope can dangle. + m_bPrevEndPointPos[1] = GetEndPointPos( 1, m_vPrevEndPointPos[1] ); + if( !m_bPrevEndPointPos[1] ) + m_vPrevEndPointPos[1] = m_vPrevEndPointPos[0]; + + const Vector &vStart = m_vPrevEndPointPos[0]; + const Vector &vAttached = m_vPrevEndPointPos[1]; + + m_RopePhysics.SetupSimulation( 0, &m_PhysicsDelegate ); + RecomputeSprings(); + m_RopePhysics.Restart(); + + // Initialize the positions of the nodes. + for( int i=0; i < m_RopePhysics.NumNodes(); i++ ) + { + CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i ); + float t = (float)i / (m_RopePhysics.NumNodes() - 1); + + VectorLerp( vStart, vAttached, t, pNode->m_vPos ); + pNode->m_vPrevPos = pNode->m_vPos; + } + + // Simulate for a bit to let it sag. + if ( m_RopeFlags & ROPE_INITIAL_HANG ) + { + RunRopeSimulation( 5 ); + } + + CalcLightValues(); + + // Set our bounds for visibility. + UpdateBBox(); + + m_flTimeToNextGust = RandomFloat( 1.0f, 3.0f ); + m_bPhysicsInitted = true; + + return true; +} + + +bool C_RopeKeyframe::CalculateEndPointAttachment( C_BaseEntity *pEnt, int iAttachment, Vector &vPos, QAngle *pAngles ) +{ + VPROF_BUDGET( "C_RopeKeyframe::CalculateEndPointAttachment", VPROF_BUDGETGROUP_ROPES ); + + if( !pEnt ) + return false; + + if ( m_RopeFlags & ROPE_PLAYER_WPN_ATTACH ) + { + C_BasePlayer *pPlayer = dynamic_cast< C_BasePlayer* >( pEnt ); + if ( pPlayer ) + { + C_BaseAnimating *pModel = pPlayer->GetRenderedWeaponModel(); + if ( !pModel ) + return false; + + int iAttachment = pModel->LookupAttachment( "buff_attach" ); + if ( pAngles ) + return pModel->GetAttachment( iAttachment, vPos, *pAngles ); + return pModel->GetAttachment( iAttachment, vPos ); + } + } + + if( iAttachment > 0 ) + { + bool bOk; + if ( pAngles ) + { + bOk = pEnt->GetAttachment( iAttachment, vPos, *pAngles ); + } + else + { + bOk = pEnt->GetAttachment( iAttachment, vPos ); + } + if ( bOk ) + return true; + } + + vPos = pEnt->WorldSpaceCenter( ); + if ( pAngles ) + { + *pAngles = pEnt->GetAbsAngles(); + } + return true; +} + +bool C_RopeKeyframe::GetEndPointPos( int iPt, Vector &vPos ) +{ + // By caching the results here, we avoid doing this a bunch of times per frame. + if ( m_bEndPointAttachmentPositionsDirty ) + { + CalculateEndPointAttachment( m_hStartPoint, m_iStartAttachment, m_vCachedEndPointAttachmentPos[0], NULL ); + CalculateEndPointAttachment( m_hEndPoint, m_iEndAttachment, m_vCachedEndPointAttachmentPos[1], NULL ); + m_bEndPointAttachmentPositionsDirty = false; + } + + Assert( iPt == 0 || iPt == 1 ); + vPos = m_vCachedEndPointAttachmentPos[iPt]; + return true; +} + +bool C_RopeKeyframe::GetEndPointAttachment( int iPt, Vector &vPos, QAngle &angle ) +{ + // By caching the results here, we avoid doing this a bunch of times per frame. + if ( m_bEndPointAttachmentPositionsDirty || m_bEndPointAttachmentAnglesDirty ) + { + CalculateEndPointAttachment( m_hStartPoint, m_iStartAttachment, m_vCachedEndPointAttachmentPos[0], &m_vCachedEndPointAttachmentAngle[0] ); + CalculateEndPointAttachment( m_hEndPoint, m_iEndAttachment, m_vCachedEndPointAttachmentPos[1], &m_vCachedEndPointAttachmentAngle[1] ); + m_bEndPointAttachmentPositionsDirty = false; + m_bEndPointAttachmentAnglesDirty = false; + } + + Assert( iPt == 0 || iPt == 1 ); + vPos = m_vCachedEndPointAttachmentPos[iPt]; + angle = m_vCachedEndPointAttachmentAngle[iPt]; + return true; +} + + +// Look at the global cvar and recalculate rope subdivision data if necessary. +Vector *C_RopeKeyframe::GetRopeSubdivVectors( int *nSubdivs ) +{ + if( m_RopeFlags & ROPE_BARBED ) + { + *nSubdivs = g_nBarbedSubdivs; + return g_BarbedSubdivs; + } + else + { + int subdiv = m_Subdiv; + if ( subdiv == 255 ) + { + subdiv = rope_subdiv.GetInt(); + } + + if ( subdiv >= MAX_ROPE_SUBDIVS ) + subdiv = MAX_ROPE_SUBDIVS-1; + + *nSubdivs = subdiv; + return g_RopeSubdivs[subdiv]; + } +} + + +void C_RopeKeyframe::CalcLightValues() +{ + Vector boxColors[6]; + + for( int i=0; i < m_RopePhysics.NumNodes(); i++ ) + { + const Vector &vPos = m_RopePhysics.GetNode(i)->m_vPredicted; + engine->ComputeLighting( vPos, NULL, true, m_LightValues[i], boxColors ); + + if ( !rope_averagelight.GetInt() ) + { + // The engine averages the lighting across the 6 box faces, but we would rather just get the MAX intensity + // since we do our own half-lambert lighting in the rope shader to simulate directionality. + // + // So here, we take the average of all the incoming light, and scale it to use the max intensity of all the box sides. + float flMaxIntensity = 0; + for ( int iSide=0; iSide < 6; iSide++ ) + { + float flLen = boxColors[iSide].Length(); + flMaxIntensity = max( flMaxIntensity, flLen ); + } + + VectorNormalize( m_LightValues[i] ); + m_LightValues[i] *= flMaxIntensity; + float flMax = max( m_LightValues[i].x, max( m_LightValues[i].y, m_LightValues[i].z ) ); + if ( flMax > 1 ) + m_LightValues[i] /= flMax; + } + } +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_RopeKeyframe::ReceiveMessage( int classID, bf_read &msg ) +{ + if ( classID != GetClientClass()->m_ClassID ) + { + // message is for subclass + BaseClass::ReceiveMessage( classID, msg ); + return; + } + + // Read instantaneous fore data + m_flImpulse.x = msg.ReadFloat(); + m_flImpulse.y = msg.ReadFloat(); + m_flImpulse.z = msg.ReadFloat(); +} + diff --git a/game/client/c_rope.h b/game/client/c_rope.h new file mode 100644 index 00000000..cf7b8723 --- /dev/null +++ b/game/client/c_rope.h @@ -0,0 +1,258 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_ROPE_H +#define C_ROPE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" +#include "rope_physics.h" +#include "materialsystem/imaterial.h" +#include "rope_shared.h" +#include "bitvec.h" + + +class KeyValues; +class C_BaseAnimating; +struct RopeSegData_t; + +#define MAX_ROPE_SUBDIVS 8 +#define MAX_ROPE_SEGMENTS (ROPE_MAX_SEGMENTS+(ROPE_MAX_SEGMENTS-1)*MAX_ROPE_SUBDIVS) + +//============================================================================= +class C_RopeKeyframe : public C_BaseEntity +{ +public: + + DECLARE_CLASS( C_RopeKeyframe, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + +private: + + class CPhysicsDelegate : public CSimplePhysics::IHelper + { + public: + virtual void GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ); + virtual void ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ); + + C_RopeKeyframe *m_pKeyframe; + }; + + friend class CPhysicsDelegate; + + +public: + + C_RopeKeyframe(); + ~C_RopeKeyframe(); + + // This can be used for client-only ropes. + static C_RopeKeyframe* Create( + C_BaseEntity *pStartEnt, + C_BaseEntity *pEndEnt, + int iStartAttachment=0, + int iEndAttachment=0, + float ropeWidth = 2, + const char *pMaterialName = "cable/cable", // Note: whoever creates the rope must + // use PrecacheModel for whatever material + // it specifies here. + int numSegments = 5, + int ropeFlags = ROPE_SIMULATE + ); + + // Create a client-only rope and initialize it with the parameters from the KeyValues. + static C_RopeKeyframe* CreateFromKeyValues( C_BaseAnimating *pEnt, KeyValues *pValues ); + + // Find ropes (with both endpoints connected) that intersect this AABB. This is just an approximation. + static int GetRopesIntersectingAABB( C_RopeKeyframe **pRopes, int nMaxRopes, const Vector &vAbsMin, const Vector &vAbsMax ); + + // Set the slack. + void SetSlack( int slack ); + + void SetRopeFlags( int flags ); + int GetRopeFlags() const; + + void SetupHangDistance( float flHangDist ); + + // Change which entities the rope is connected to. + void SetStartEntity( C_BaseEntity *pEnt ); + void SetEndEntity( C_BaseEntity *pEnt ); + + C_BaseEntity* GetStartEntity() const; + C_BaseEntity* GetEndEntity() const; + + // Hook the physics. Pass in your own implementation of CSimplePhysics::IHelper. The + // default implementation is returned so you can call through to it if you want. + CSimplePhysics::IHelper* HookPhysics( CSimplePhysics::IHelper *pHook ); + + // Attach to things (you can also just lock the endpoints down yourself if you hook the physics). + + // Client-only right now. This could be moved to the server if there was a good reason. + void SetColorMod( const Vector &vColorMod ); + + // Use this when rope length and slack change to recompute the spring length. + void RecomputeSprings(); + + void ShakeRope( const Vector &vCenter, float flRadius, float flMagnitude ); + + // Get the attachment position of one of the endpoints. + bool GetEndPointPos( int iPt, Vector &vPos ); + + // Get the rope material data. + IMaterial *GetSolidMaterial( void ) { return m_pMaterial; } + IMaterial *GetBackMaterial( void ) { return m_pBackMaterial; } + + struct BuildRopeQueuedData_t + { + Vector *m_pPredictedPositions; + Vector *m_pLightValues; + int m_iNodeCount; + Vector m_vColorMod; + float m_RopeLength; + float m_Slack; + }; + + void BuildRope( RopeSegData_t *pRopeSegment, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, BuildRopeQueuedData_t *pQueuedData ); + +// C_BaseEntity overrides. +public: + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink(); + virtual int DrawModel( int flags ); + virtual bool ShouldDraw(); + virtual const Vector& WorldSpaceCenter() const; + + // Specify ROPE_ATTACHMENT_START_POINT or ROPE_ATTACHMENT_END_POINT for the attachment. + virtual bool GetAttachment( int number, Vector &origin, QAngle &angles ); + virtual bool GetAttachment( int number, matrix3x4_t &matrix ); + virtual bool GetAttachment( int number, Vector &origin ); + virtual bool GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ); + +private: + + void FinishInit( const char *pMaterialName ); + + void RunRopeSimulation( float flSeconds ); + Vector ConstrainNode( const Vector &vNormal, const Vector &vNodePosition, const Vector &vMidpiont, float fNormalLength ); + void ConstrainNodesBetweenEndpoints( void ); + + bool AnyPointsMoved(); + + bool DidEndPointMove( int iPt ); + bool DetectRestingState( bool &bApplyWind ); + + void UpdateBBox(); + bool InitRopePhysics(); + + bool GetEndPointAttachment( int iPt, Vector &vPos, QAngle &angle ); + + Vector *GetRopeSubdivVectors( int *nSubdivs ); + void CalcLightValues(); + + void ReceiveMessage( int classID, bf_read &msg ); + bool CalculateEndPointAttachment( C_BaseEntity *pEnt, int iAttachment, Vector &vPos, QAngle *pAngles ); + + +private: + // Track which links touched something last frame. Used to prevent wind from gusting on them. + CBitVec m_LinksTouchingSomething; + int m_nLinksTouchingSomething; + bool m_bApplyWind; + int m_fPrevLockedPoints; // Which points are locked down. + int m_iForcePointMoveCounter; + + // Used to control resting state. + bool m_bPrevEndPointPos[2]; + Vector m_vPrevEndPointPos[2]; + + float m_flCurScroll; // for scrolling texture. + float m_flScrollSpeed; + + int m_RopeFlags; // Combo of ROPE_ flags. + int m_iRopeMaterialModelIndex; // Index of sprite model with the rope's material. + + CRopePhysics m_RopePhysics; + Vector m_LightValues[ROPE_MAX_SEGMENTS]; // light info when the rope is created. + + int m_nSegments; // Number of segments. + + EHANDLE m_hStartPoint; // StartPoint/EndPoint are entities + EHANDLE m_hEndPoint; + short m_iStartAttachment; // StartAttachment/EndAttachment are attachment points. + short m_iEndAttachment; + + unsigned char m_Subdiv; // Number of subdivions in between segments. + + int m_RopeLength; // Length of the rope, used for tension. + int m_Slack; // Extra length the rope is given. + float m_TextureScale; // pixels per inch + + int m_fLockedPoints; // Which points are locked down. + + float m_Width; + + CPhysicsDelegate m_PhysicsDelegate; + + IMaterial *m_pMaterial; + IMaterial *m_pBackMaterial; // Optional translucent background material for the rope to help reduce aliasing. + + int m_TextureHeight; // Texture height, for texture scale calculations. + + // Instantaneous force + Vector m_flImpulse; + Vector m_flPreviousImpulse; + + // Simulated wind gusts. + float m_flCurrentGustTimer; + float m_flCurrentGustLifetime; // How long will the current gust last? + + float m_flTimeToNextGust; // When will the next wind gust be? + Vector m_vWindDir; // What direction does the current gust go in? + + Vector m_vColorMod; // Color modulation on all verts? + + Vector m_vCachedEndPointAttachmentPos[2]; + QAngle m_vCachedEndPointAttachmentAngle[2]; + + // In network table, can't bit-compress + bool m_bConstrainBetweenEndpoints; // Simulated segment points won't stretch beyond the endpoints + + bool m_bEndPointAttachmentPositionsDirty : 1; + bool m_bEndPointAttachmentAnglesDirty : 1; + bool m_bNewDataThisFrame : 1; // Set to true in OnDataChanged so that we simulate that frame + bool m_bPhysicsInitted : 1; // It waits until all required entities are + // present to start simulating and rendering. + + friend class CRopeManager; +}; + + +// Profiling info. +void Rope_ResetCounters(); +//void Rope_ShowRSpeeds(); + +//============================================================================= +// +// Rope Manager +// +abstract_class IRopeManager +{ +public: + virtual ~IRopeManager() {} + virtual void ResetRenderCache( void ) = 0; + virtual void AddToRenderCache( C_RopeKeyframe *pRope ) = 0; + virtual void DrawRenderCache( bool bShadowDepth ) = 0; + virtual void OnRenderStart( void ) = 0; +}; + +IRopeManager *RopeManager(); + +#endif // C_ROPE_H diff --git a/game/client/c_rumble.cpp b/game/client/c_rumble.cpp new file mode 100644 index 00000000..4347f8b1 --- /dev/null +++ b/game/client/c_rumble.cpp @@ -0,0 +1,826 @@ +//======= Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Rumble effects mixer for XBox +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "c_rumble.h" +#include "rumble_shared.h" +#include "inputsystem/iinputsystem.h" + +ConVar cl_rumblescale( "cl_rumblescale", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Scale sensitivity of rumble effects (0 to 1.0)" ); +ConVar cl_debugrumble( "cl_debugrumble", "0", FCVAR_ARCHIVE, "Turn on rumble debugging spew" ); + +#define MAX_RUMBLE_CHANNELS 3 // Max concurrent rumble effects + +#define NUM_WAVE_SAMPLES 30 // Effects play at 10hz + +typedef struct +{ + float amplitude_left[NUM_WAVE_SAMPLES]; + float amplitude_right[NUM_WAVE_SAMPLES]; + int numSamples; +} RumbleWaveform_t; + +//========================================================= +// Structure for a rumble effect channel. This is akin to +// a sound channel that is playing a sound. +//========================================================= +typedef struct +{ + float starttime; // When did this effect start playing? (gpGlobals->curtime) + int waveformIndex; // Type of effect waveform used (an enum from rumble_shared.h) + int priority; // How important this effect is (for making replacement decisions) + bool in_use; // Is this channel in use?? (true if effect is currently playing, false if done or otherwise available) + unsigned char rumbleFlags; // Flags pertaining to the effect currently playing on this channel. + float scale; // Some effects are updated while they are running. +} RumbleChannel_t; + +//========================================================= +// This structure contains parameters necessary to generate +// a sine or sawtooth waveform. +//========================================================= +typedef struct tagWaveGenParams +{ + float cycles; // AKA frequency + float amplitudescale; + bool leftChannel; // If false, generating for the right channel + + float maxAmplitude; // Clamping + float minAmplitude; + + void Set( float c_cycles, float c_amplitudescale, bool c_leftChannel, float c_minAmplitude, float c_maxAmplitude ) + { + cycles = c_cycles; + amplitudescale = c_amplitudescale; + leftChannel = c_leftChannel; + minAmplitude = c_minAmplitude; + maxAmplitude = c_maxAmplitude; + } + + // CTOR + tagWaveGenParams( float c_cycles, float c_amplitudescale, bool c_leftChannel, float c_minAmplitude, float c_maxAmplitude ) + { + Set( c_cycles, c_amplitudescale, c_leftChannel, c_minAmplitude, c_maxAmplitude ); + } + +} WaveGenParams_t; + +//--------------------------------------------------------- +//--------------------------------------------------------- +void TerminateWaveform( RumbleWaveform_t *pWaveform, int samples ) +{ + if( samples <= NUM_WAVE_SAMPLES ) + { + pWaveform->numSamples = samples; + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void EaseInWaveform( RumbleWaveform_t *pWaveform, int samples, bool left ) +{ + float step = 1.0f / ((float)samples); + float factor = 0.0f; + + for( int i = 0 ; i < samples ; i++ ) + { + if( left ) + { + pWaveform->amplitude_left[i] *= factor; + } + else + { + pWaveform->amplitude_right[i] *= factor; + } + + factor += step; + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void EaseOutWaveform( RumbleWaveform_t *pWaveform, int samples, bool left ) +{ + float step = 1.0f / ((float)samples); + float factor = 0.0f; + + int i = NUM_WAVE_SAMPLES - 1; + + for( int j = 0 ; j < samples ; j++ ) + { + if( left ) + { + pWaveform->amplitude_left[i] *= factor; + } + else + { + pWaveform->amplitude_right[i] *= factor; + } + + factor += step; + i--; + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void GenerateSawtoothEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t ¶ms ) +{ + float delta = params.maxAmplitude - params.minAmplitude; + int waveLength = NUM_WAVE_SAMPLES / params.cycles; + float vstep = (delta / waveLength); + + float amplitude = params.minAmplitude; + + for( int i = 0 ; i < NUM_WAVE_SAMPLES ; i++ ) + { + if( params.leftChannel ) + { + pWaveform->amplitude_left[i] = amplitude; + } + else + { + pWaveform->amplitude_right[i] = amplitude; + } + + amplitude += vstep; + + if( amplitude > params.maxAmplitude ) + { + amplitude = params.minAmplitude; + } + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void GenerateSquareWaveEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t ¶ms ) +{ + int i = 0; + int j; + + int steps = ((float)NUM_WAVE_SAMPLES) / (params.cycles*2.0f); + + while( i < NUM_WAVE_SAMPLES ) + { + for( j = 0 ; j < steps ; j++ ) + { + if( params.leftChannel ) + { + pWaveform->amplitude_left[i++] = params.minAmplitude; + } + else + { + pWaveform->amplitude_right[i++] = params.minAmplitude; + } + } + for( j = 0 ; j < steps ; j++ ) + { + if( params.leftChannel ) + { + pWaveform->amplitude_left[i++] = params.maxAmplitude; + } + else + { + pWaveform->amplitude_right[i++] = params.maxAmplitude; + } + } + } +} + +//--------------------------------------------------------- +// If you pass a numSamples, this wave will only be that many +// samples long. +//--------------------------------------------------------- +void GenerateFlatEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t ¶ms ) +{ + for( int i = 0 ; i < NUM_WAVE_SAMPLES ; i++ ) + { + if( params.leftChannel ) + { + pWaveform->amplitude_left[i] = params.maxAmplitude; + } + else + { + pWaveform->amplitude_right[i] = params.maxAmplitude; + } + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void GenerateSineWaveEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t ¶ms ) +{ + float step = (360.0f * (params.cycles * 0.5f) ) / ((float)NUM_WAVE_SAMPLES); + float degrees = 180.0f + step; // 180 to start at 0 + + for( int i = 0 ; i < NUM_WAVE_SAMPLES ; i++ ) + { + float radians = DEG2RAD(degrees); + float value = fabs( sin(radians) ); + + value *= params.amplitudescale; + + if( value < params.minAmplitude ) + value = params.minAmplitude; + + if( value > params.maxAmplitude ) + value = params.maxAmplitude; + + if( params.leftChannel ) + { + pWaveform->amplitude_left[i] = value; + } + else + { + pWaveform->amplitude_right[i] = value; + } + + degrees += step; + } +} + +//========================================================= +//========================================================= +class CRumbleEffects +{ +public: + CRumbleEffects() + { + Init(); + } + + void Init(); + void SetOutputEnabled( bool bEnable ); + void StartEffect( unsigned char effectIndex, unsigned char rumbleData, unsigned char rumbleFlags ); + void StopEffect( int effectIndex ); + void StopAllEffects(); + void ComputeAmplitudes( RumbleChannel_t *pChannel, float curtime, float *pLeft, float *pRight ); + void UpdateEffects( float curtime ); + void UpdateScreenShakeRumble( float shake, float balance ); + + RumbleChannel_t *FindExistingChannel( int index ); + RumbleChannel_t *FindAvailableChannel( int priority ); + +public: + RumbleChannel_t m_Channels[ MAX_RUMBLE_CHANNELS ]; + + RumbleWaveform_t m_Waveforms[ NUM_RUMBLE_EFFECTS ]; + + float m_flScreenShake; + bool m_bOutputEnabled; +}; + + +CRumbleEffects g_RumbleEffects; + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CRumbleEffects::Init() +{ + SetOutputEnabled( true ); + + int i; + + for( i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ ) + { + m_Channels[i].in_use = false; + m_Channels[i].priority = 0; + } + + // Every effect defaults to this many samples. Call TerminateWaveform() to trim these. + for ( i = 0 ; i < NUM_RUMBLE_EFFECTS ; i++ ) + { + m_Waveforms[i].numSamples = NUM_WAVE_SAMPLES; + } + + // Jeep Idle + WaveGenParams_t params( 1, 1.0f, false, 0.0f, 0.15f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_JEEP_ENGINE_LOOP], params ); + + // Pistol + params.Set( 1, 1.0f, false, 0.0f, 0.5f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_PISTOL], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_PISTOL], 3 ); + + // SMG1 + params.Set( 1, 1.0f, true, 0.0f, 0.2f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_SMG1], params ); + params.Set( 1, 1.0f, false, 0.0f, 0.4f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_SMG1], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_SMG1], 3 ); + + // AR2 + params.Set( 1, 1.0f, true, 0.0f, 0.5f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2], params ); + params.Set( 1, 1.0f, false, 0.0f, 0.3f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_AR2], 3 ); + + // AR2 Alt + params.Set( 1, 1.0f, true, 0.0, 0.5f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], params ); + EaseInWaveform( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], 5, true ); + params.Set( 1, 1.0f, false, 0.0, 0.7f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], params ); + EaseInWaveform( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], 5, false ); + TerminateWaveform( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], 7 ); + + // 357 + params.Set( 1, 1.0f, true, 0.0f, 0.75f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_357], params ); + params.Set( 1, 1.0f, false, 0.0f, 0.75f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_357], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_357], 2 ); + + // Shotgun + params.Set( 1, 1.0f, true, 0.0f, 0.8f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_SINGLE], params ); + params.Set( 1, 1.0f, false, 0.0f, 0.8f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_SINGLE], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_SHOTGUN_SINGLE], 3 ); + + params.Set( 1, 1.0f, true, 0.0f, 1.0f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_DOUBLE], params ); + params.Set( 1, 1.0f, false, 0.0f, 1.0f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_DOUBLE], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_SHOTGUN_DOUBLE], 3 ); + + // RPG Missile + params.Set( 1, 1.0f, false, 0.0f, 0.3f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_RPG_MISSILE], params ); + EaseOutWaveform( &m_Waveforms[RUMBLE_RPG_MISSILE], 30, false ); + TerminateWaveform( &m_Waveforms[RUMBLE_RPG_MISSILE], 6 ); + + // Physcannon open forks + params.Set( 1, 1.0f, false, 0.0f, 0.25f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_PHYSCANNON_OPEN], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_PHYSCANNON_OPEN], 4 ); + + // Physcannon holding something + params.Set( 1, 1.0f, true, 0.0f, 0.2f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_PHYSCANNON_LOW], params ); + params.Set( 6, 1.0f, false, 0.0f, 0.25f ); + GenerateSquareWaveEffect( &m_Waveforms[RUMBLE_PHYSCANNON_LOW], params ); + + // Crowbar + params.Set( 1, 1.0f, false, 0.0f, 0.35f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_CROWBAR_SWING], params ); + EaseOutWaveform( &m_Waveforms[RUMBLE_CROWBAR_SWING], 30, false ); + TerminateWaveform( &m_Waveforms[RUMBLE_CROWBAR_SWING], 4 ); + + // Airboat gun + params.Set( 1, 1.0f, false, 0.0f, 0.4f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_AIRBOAT_GUN], params ); + params.Set( 12, 1.0f, true, 0.0f, 0.5f ); + GenerateSawtoothEffect( &m_Waveforms[RUMBLE_AIRBOAT_GUN], params ); + + // Generic flat effects. + params.Set( 1, 1.0f, true, 0.0f, 1.0f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_LEFT], params ); + + params.Set( 1, 1.0f, false, 0.0f, 1.0f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_RIGHT], params ); + + params.Set( 1, 1.0f, true, 0.0f, 1.0f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_BOTH], params ); + params.Set( 1, 1.0f, false, 0.0f, 1.0f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_BOTH], params ); + + // Impact from a long fall + params.Set( 1, 1.0f, false, 0.0f, 0.5f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_LONG], params ); + params.Set( 1, 1.0f, true, 0.0f, 0.5f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_LONG], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_FALL_LONG], 3 ); + + // Impact from a short fall + params.Set( 1, 1.0f, false, 0.0f, 0.3f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_SHORT], params ); + params.Set( 1, 1.0f, true, 0.0f, 0.3f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_SHORT], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_FALL_SHORT], 2 ); + + // Portalgun left (blue) shot + params.Set( 1, 1.0f, true, 0.0f, 0.3f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_PORTALGUN_LEFT], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_PORTALGUN_LEFT], 2 ); + + // Portalgun right (red) shot + params.Set( 1, 1.0f, false, 0.0f, 0.3f ); + GenerateFlatEffect( &m_Waveforms[RUMBLE_PORTALGUN_RIGHT], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_PORTALGUN_RIGHT], 2 ); + + // Portal failed to place feedback + params.Set( 12, 1.0f, true, 0.0f, 1.0f ); + GenerateSquareWaveEffect( &m_Waveforms[RUMBLE_PORTAL_PLACEMENT_FAILURE], params ); + params.Set( 12, 1.0f, false, 0.0f, 1.0f ); + GenerateSquareWaveEffect( &m_Waveforms[RUMBLE_PORTAL_PLACEMENT_FAILURE], params ); + TerminateWaveform( &m_Waveforms[RUMBLE_PORTAL_PLACEMENT_FAILURE], 6 ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +RumbleChannel_t *CRumbleEffects::FindExistingChannel( int index ) +{ + RumbleChannel_t *pChannel; + + for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ ) + { + pChannel = &m_Channels[i]; + + if( pChannel->in_use && pChannel->waveformIndex == index ) + { + // This effect is already playing. Provide this channel for the + // effect to be re-started on. + return pChannel; + } + } + + return NULL; +} + +//--------------------------------------------------------- +// priority - the priority of the effect we want to play. +//--------------------------------------------------------- +RumbleChannel_t *CRumbleEffects::FindAvailableChannel( int priority ) +{ + RumbleChannel_t *pChannel; + int i; + + for( i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ ) + { + pChannel = &m_Channels[i]; + + if( !pChannel->in_use ) + { + return pChannel; + } + } + + int lowestPriority = priority; + RumbleChannel_t *pBestChannel = NULL; + float oldestChannel = FLT_MAX; + + // All channels already in use. Find a channel to slam. + for( i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ ) + { + pChannel = &m_Channels[i]; + + if( (pChannel->rumbleFlags & RUMBLE_FLAG_LOOP) ) + continue; + + if( pChannel->priority < lowestPriority ) + { + // Always happily slam a lower priority sound. + pBestChannel = pChannel; + lowestPriority = pChannel->priority; + } + else if( pChannel->priority == lowestPriority ) + { + // Priority is the same, so replace the oldest. + if( pBestChannel ) + { + // If we already have a channel of the same priority to discard, make sure we discard the oldest. + float age = gpGlobals->curtime - pChannel->starttime; + + if( age > oldestChannel ) + { + pBestChannel = pChannel; + oldestChannel = age; + } + } + else + { + // Take it. + pBestChannel = pChannel; + oldestChannel = gpGlobals->curtime - pChannel->starttime; + } + } + } + + return pBestChannel; // Can still be NULL if we couldn't find a channel to slam. +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CRumbleEffects::SetOutputEnabled( bool bEnable ) +{ + m_bOutputEnabled = bEnable; + + if( !bEnable ) + { + // Tell the hardware to shut down motors right now, in case this gets called + // and some other process blocks us before the next rumble system update. + m_flScreenShake = 0.0f; + + inputsystem->StopRumble(); + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CRumbleEffects::StartEffect( unsigned char effectIndex, unsigned char rumbleData, unsigned char rumbleFlags ) +{ + if( effectIndex == RUMBLE_STOP_ALL ) + { + StopAllEffects(); + return; + } + + if( rumbleFlags & RUMBLE_FLAG_STOP ) + { + StopEffect( effectIndex ); + return; + } + + int priority = 1; + RumbleChannel_t *pChannel = NULL; + + if( (rumbleFlags & RUMBLE_FLAG_RESTART) ) + { + // Try to find any active instance of this effect and replace it. + pChannel = FindExistingChannel( effectIndex ); + } + + if( (rumbleFlags & RUMBLE_FLAG_ONLYONE) ) + { + pChannel = FindExistingChannel( effectIndex ); + + if( pChannel ) + { + // Bail out. An instance of this effect is already playing. + return; + } + } + + if( (rumbleFlags & RUMBLE_FLAG_UPDATE_SCALE) ) + { + pChannel = FindExistingChannel( effectIndex ); + if( pChannel ) + { + pChannel->scale = ((float)rumbleData) / 100.0f; + } + + // It's possible to return without finding a rumble to update. + // This means you tried to update a rumble you never started. + return; + } + + if( !pChannel ) + { + pChannel = FindAvailableChannel( priority ); + } + + if( pChannel ) + { + pChannel->waveformIndex = effectIndex; + pChannel->priority = 1; + pChannel->starttime = gpGlobals->curtime; + pChannel->in_use = true; + pChannel->rumbleFlags = rumbleFlags; + + if( rumbleFlags & RUMBLE_FLAG_INITIAL_SCALE ) + { + pChannel->scale = ((float)rumbleData) / 100.0f; + } + else + { + pChannel->scale = 1.0f; + } + } + + if( (rumbleFlags & RUMBLE_FLAG_RANDOM_AMPLITUDE) ) + { + pChannel->scale = random->RandomFloat( 0.1f, 1.0f ); + } +} + +//--------------------------------------------------------- +// Find all playing effects of this type and stop them. +//--------------------------------------------------------- +void CRumbleEffects::StopEffect( int effectIndex ) +{ + for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ ) + { + if( m_Channels[i].in_use && m_Channels[i].waveformIndex == effectIndex ) + { + m_Channels[i].in_use = false; + } + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CRumbleEffects::StopAllEffects() +{ + for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ ) + { + m_Channels[i].in_use = false; + } + + m_flScreenShake = 0.0f; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CRumbleEffects::ComputeAmplitudes( RumbleChannel_t *pChannel, float curtime, float *pLeft, float *pRight ) +{ + // How long has this waveform been playing? + float elapsed = curtime - pChannel->starttime; + + if( elapsed >= (NUM_WAVE_SAMPLES/10) ) + { + if( (pChannel->rumbleFlags & RUMBLE_FLAG_LOOP) ) + { + // This effect loops. Just fixup the start time and recompute elapsed. + pChannel->starttime = curtime; + elapsed = curtime - pChannel->starttime; + } + else + { + // This effect is done! Should it loop? + *pLeft = 0; + *pRight = 0; + pChannel->in_use = false; + return; + } + } + + // Figure out which sample we're playing FROM. + int seconds = ((int) elapsed); + int sample = (int)(elapsed*10.0f); + + // Get the fraction bit. + float fraction = elapsed - seconds; + + float left, right; + + if( sample == m_Waveforms[pChannel->waveformIndex].numSamples ) + { + // This effect is done. Send zeroes to the mixer for this + // final frame and then turn the channel off. (Unless it loops!) + + if( (pChannel->rumbleFlags & RUMBLE_FLAG_LOOP) ) + { + // Loop this effect + pChannel->starttime = gpGlobals->curtime; + + // Send the first sample. + left = m_Waveforms[pChannel->waveformIndex].amplitude_left[0]; + right = m_Waveforms[pChannel->waveformIndex].amplitude_right[0]; + } + else + { + left = 0.0f; + right = 0.0f; + pChannel->in_use = false; + } + } + else + { + // Use values for the last sample that we have passed + left = m_Waveforms[pChannel->waveformIndex].amplitude_left[sample]; + right = m_Waveforms[pChannel->waveformIndex].amplitude_right[sample]; + } + + left *= pChannel->scale; + right *= pChannel->scale; + + if( cl_debugrumble.GetBool() ) + { + Msg("Seconds:%d Fraction:%f Sample:%d L:%f R:%f\n", seconds, fraction, sample, left, right ); + } + + if( !m_bOutputEnabled ) + { + // Send zeroes to stop any current rumbling, and to keep it silenced. + left = 0; + right = 0; + } + + *pLeft = left; + *pRight = right; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CRumbleEffects::UpdateScreenShakeRumble( float shake, float balance ) +{ + if( m_bOutputEnabled ) + { + m_flScreenShake = shake; + } + else + { + // Silence + m_flScreenShake = 0.0f; + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CRumbleEffects::UpdateEffects( float curtime ) +{ + float fLeftMotor = 0.0f; + float fRightMotor = 0.0f; + + for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ ) + { + // Expire old channels + RumbleChannel_t *pChannel = & m_Channels[i]; + + if( pChannel->in_use ) + { + float left, right; + + ComputeAmplitudes( pChannel, curtime, &left, &right ); + + fLeftMotor += left; + fRightMotor += right; + } + } + + // Add in any screenshake + float shakeLeft = 0.0f; + float shakeRight = 0.0f; + if( m_flScreenShake != 0.0f ) + { + if( m_flScreenShake < 0.0f ) + { + shakeLeft = fabs( m_flScreenShake ); + } + else + { + shakeRight = m_flScreenShake; + } + } + + fLeftMotor += shakeLeft; + fRightMotor += shakeRight; + + fLeftMotor *= cl_rumblescale.GetFloat(); + fRightMotor *= cl_rumblescale.GetFloat(); + + if( engine->IsPaused() ) + { + // Send nothing when paused. + fLeftMotor = 0.0f; + fRightMotor = 0.0f; + } + + inputsystem->SetRumble( fLeftMotor, fRightMotor ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void StopAllRumbleEffects( void ) +{ + g_RumbleEffects.StopAllEffects(); + + inputsystem->StopRumble(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void RumbleEffect( unsigned char effectIndex, unsigned char rumbleData, unsigned char rumbleFlags ) +{ + g_RumbleEffects.StartEffect( effectIndex, rumbleData, rumbleFlags ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void UpdateRumbleEffects() +{ + C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); + if( !localPlayer || !localPlayer->IsAlive() ) + { + StopAllRumbleEffects(); + return; + } + + g_RumbleEffects.UpdateEffects( gpGlobals->curtime ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void UpdateScreenShakeRumble( float shake, float balance ) +{ + C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); + if( !localPlayer || !localPlayer->IsAlive() ) + { + return; + } + + g_RumbleEffects.UpdateScreenShakeRumble( shake, balance ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void EnableRumbleOutput( bool bEnable ) +{ + g_RumbleEffects.SetOutputEnabled( bEnable ); +} diff --git a/game/client/c_rumble.h b/game/client/c_rumble.h new file mode 100644 index 00000000..59ed8d27 --- /dev/null +++ b/game/client/c_rumble.h @@ -0,0 +1,18 @@ +//======= Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Rumble effects mixer for XBox +// +// $NoKeywords: $ +// +//=============================================================================// +#pragma once +#ifndef C_RUMBLE_H +#define C_RUMBLE_H + +extern void RumbleEffect( unsigned char effectIndex, unsigned char rumbleData, unsigned char rumbleFlags ); +extern void UpdateRumbleEffects(); +extern void UpdateScreenShakeRumble( float shake, float balance = 0 ); +extern void EnableRumbleOutput( bool bEnable ); + +#endif//C_RUMBLE_H + diff --git a/game/client/c_sceneentity.cpp b/game/client/c_sceneentity.cpp new file mode 100644 index 00000000..10f65024 --- /dev/null +++ b/game/client/c_sceneentity.cpp @@ -0,0 +1,1125 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "networkstringtable_clientdll.h" +#include "dt_utlvector_recv.h" +#include "choreoevent.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "choreoscene.h" +#include "filesystem.h" +#include "ichoreoeventcallback.h" +#include "scenefilecache/ISceneFileCache.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "tier2/tier2.h" +#include "hud_closecaption.h" + +#include "c_sceneentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Decodes animtime and notes when it changes +// Input : *pStruct - ( C_BaseEntity * ) used to flag animtime is changine +// *pVarData - +// *pIn - +// objectID - +//----------------------------------------------------------------------------- +void RecvProxy_ForcedClientTime( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_SceneEntity *pScene = reinterpret_cast< C_SceneEntity * >( pStruct ); + *(float *)pOut = pData->m_Value.m_Float; + pScene->OnResetClientTime(); +} + +#if defined( CSceneEntity ) +#undef CSceneEntity +#endif + +IMPLEMENT_CLIENTCLASS_DT(C_SceneEntity, DT_SceneEntity, CSceneEntity) + RecvPropInt(RECVINFO(m_nSceneStringIndex)), + RecvPropBool(RECVINFO(m_bIsPlayingBack)), + RecvPropBool(RECVINFO(m_bPaused)), + RecvPropBool(RECVINFO(m_bMultiplayer)), + RecvPropFloat(RECVINFO(m_flForceClientTime), 0, RecvProxy_ForcedClientTime ), + RecvPropUtlVector( + RECVINFO_UTLVECTOR( m_hActorList ), + MAX_ACTORS_IN_SCENE, + RecvPropEHandle(NULL, 0, 0)), +END_RECV_TABLE() + +C_SceneEntity::C_SceneEntity( void ) +{ + m_pScene = NULL; + m_bMultiplayer = false; + + m_hOwner = NULL; + m_bClientOnly = false; +} + +C_SceneEntity::~C_SceneEntity( void ) +{ + UnloadScene(); +} + +void C_SceneEntity::OnResetClientTime() +{ + m_flCurrentTime = m_flForceClientTime; +} + +char const *C_SceneEntity::GetSceneFileName() +{ + return g_pStringTableClientSideChoreoScenes->GetString( m_nSceneStringIndex ); +} + +ConVar mp_usehwmvcds( "mp_usehwmvcds", "0", NULL, "Enable the use of the hw morph vcd(s). (-1 = never, 1 = always, 0 = based upon GPU)" ); // -1 = never, 0 = if hasfastvertextextures, 1 = always +bool UseHWMorphVCDs() +{ + if ( mp_usehwmvcds.GetInt() == 0 ) + return g_pMaterialSystemHardwareConfig->HasFastVertexTextures(); + return mp_usehwmvcds.GetInt() > 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_SceneEntity::GetHWMorphSceneFileName( const char *pFilename, char *pHWMFilename ) +{ + // Are we even using hardware morph? + if ( !UseHWMorphVCDs() ) + return false; + + // Multi-player only! + if ( !m_bMultiplayer ) + return false; + + // Do we have a valid filename? + if ( !( pFilename && pFilename[0] ) ) + return false; + + // Check to see if we already have an player/hwm/* filename. + if ( ( V_strstr( pFilename, "/high" ) != NULL ) || ( V_strstr( pFilename, "\\high" ) != NULL ) ) + { + V_strcpy( pHWMFilename, pFilename ); + return true; + } + + // Find the hardware morph scene name and pass that along as well. + char szScene[MAX_PATH]; + V_strcpy( szScene, pFilename ); + + char szSceneHWM[MAX_PATH]; + szSceneHWM[0] = '\0'; + + char *pszToken = strtok( szScene, "/\\" ); + while ( pszToken != NULL ) + { + if ( !V_stricmp( pszToken, "low" ) ) + { + V_strcat( szSceneHWM, "high", sizeof( szSceneHWM ) ); + } + else + { + V_strcat( szSceneHWM, pszToken, sizeof( szSceneHWM ) ); + } + + pszToken = strtok( NULL, "/\\" ); + if ( pszToken != NULL ) + { + V_strcat( szSceneHWM, "\\", sizeof( szSceneHWM ) ); + } + } + + V_strcpy( pHWMFilename, szSceneHWM ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SceneEntity::ResetActorFlexesForScene() +{ + int nActorCount = m_pScene->GetNumActors(); + for( int iActor = 0; iActor < nActorCount; ++iActor ) + { + CChoreoActor *pChoreoActor = m_pScene->GetActor( iActor ); + if ( !pChoreoActor ) + continue; + + C_BaseFlex *pFlexActor = FindNamedActor( pChoreoActor ); + if ( !pFlexActor ) + continue; + + CStudioHdr *pStudioHdr = pFlexActor->GetModelPtr(); + if ( !pStudioHdr ) + continue; + + if ( pStudioHdr->numflexdesc() == 0 ) + continue; + + // Reset the flex weights to their starting position. + LocalFlexController_t iController; + for ( iController = LocalFlexController_t(0); iController < pStudioHdr->numflexcontrollers(); ++iController ) + { + pFlexActor->SetFlexWeight( iController, 0.0f ); + } + + // Reset the prediction interpolation values. + pFlexActor->m_iv_flexWeight.Reset(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SceneEntity::StopClientOnlyScene() +{ + if ( m_pScene ) + { + m_pScene->ResetSimulation(); + + if ( m_hOwner.Get() ) + { + m_hOwner->RemoveChoreoScene( m_pScene ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SceneEntity::SetupClientOnlyScene( const char *pszFilename, C_BaseFlex *pOwner /* = NULL */, bool bMultiplayer /* = false */ ) +{ + m_bIsPlayingBack = true; + m_bMultiplayer = bMultiplayer; + m_hOwner = pOwner; + m_bClientOnly = true; + + char szFilename[128]; + Assert( V_strlen( pszFilename ) < 128 ); + V_strcpy( szFilename, pszFilename ); + + char szSceneHWM[128]; + if ( GetHWMorphSceneFileName( szFilename, szSceneHWM ) ) + { + V_strcpy( szFilename, szSceneHWM ); + } + + Assert( szFilename && szFilename[ 0 ] ); + if ( szFilename && szFilename[ 0 ] ) + { + LoadSceneFromFile( szFilename ); + Assert( m_pScene ); + + // Should handle gestures and sequences client side. + if ( m_bMultiplayer ) + { + if ( m_pScene ) + { + int types[6]; + types[0] = CChoreoEvent::FLEXANIMATION; + types[1] = CChoreoEvent::EXPRESSION; + types[2] = CChoreoEvent::GESTURE; + types[3] = CChoreoEvent::SEQUENCE; + types[4] = CChoreoEvent::SPEAK; + types[5] = CChoreoEvent::LOOP; + m_pScene->RemoveEventsExceptTypes( types, 6 ); + } + + PrefetchAnimBlocks( m_pScene ); + } + else + { + if ( m_pScene ) + { + int types[ 2 ]; + types[ 0 ] = CChoreoEvent::FLEXANIMATION; + types[ 1 ] = CChoreoEvent::EXPRESSION; + m_pScene->RemoveEventsExceptTypes( types, 2 ); + } + } + + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } + + if ( m_hOwner.Get() ) + { + Assert( m_pScene ); + + if ( m_pScene ) + { + ClearSceneEvents( m_pScene, false ); + + if ( m_bIsPlayingBack ) + { + m_pScene->ResetSimulation(); + m_hOwner->StartChoreoScene( m_pScene ); + } + else + { + m_pScene->ResetSimulation(); + m_hOwner->RemoveChoreoScene( m_pScene ); + } + + // Reset the flex weights when we start a new scene. This is normally done on the player model, but since + // we don't have a player here yet - we need to do this! + ResetActorFlexesForScene(); + } + } + else + { + for( int i = 0; i < m_hActorList.Count() ; ++i ) + { + C_BaseFlex *actor = m_hActorList[ i ].Get(); + if ( !actor ) + continue; + + Assert( m_pScene ); + + if ( m_pScene ) + { + ClearSceneEvents( m_pScene, false ); + + if ( m_bIsPlayingBack ) + { + m_pScene->ResetSimulation(); + actor->StartChoreoScene( m_pScene ); + } + else + { + m_pScene->ResetSimulation(); + actor->RemoveChoreoScene( m_pScene ); + } + } + } + } +} + +void C_SceneEntity::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + char const *str = GetSceneFileName(); + char szFilename[MAX_PATH]; + Assert( V_strlen( str ) < MAX_PATH ); + V_strcpy( szFilename, str ); + + char szSceneHWM[MAX_PATH]; + if ( GetHWMorphSceneFileName( szFilename, szSceneHWM ) ) + { + V_strcpy( szFilename, szSceneHWM ); + } + + if ( updateType == DATA_UPDATE_CREATED ) + { + Assert( szFilename && szFilename[ 0 ] ); + if ( szFilename && szFilename[ 0 ] ) + { + LoadSceneFromFile( szFilename ); + + // Kill everything except flex events + Assert( m_pScene ); + + // Should handle gestures and sequences clientside. + if ( m_bMultiplayer ) + { + if ( m_pScene ) + { + int types[6]; + types[0] = CChoreoEvent::FLEXANIMATION; + types[1] = CChoreoEvent::EXPRESSION; + types[2] = CChoreoEvent::GESTURE; + types[3] = CChoreoEvent::SEQUENCE; + types[4] = CChoreoEvent::SPEAK; + types[5] = CChoreoEvent::LOOP; + m_pScene->RemoveEventsExceptTypes( types, 6 ); + } + + PrefetchAnimBlocks( m_pScene ); + } + else + { + if ( m_pScene ) + { + int types[ 2 ]; + types[ 0 ] = CChoreoEvent::FLEXANIMATION; + types[ 1 ] = CChoreoEvent::EXPRESSION; + m_pScene->RemoveEventsExceptTypes( types, 2 ); + } + } + + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } + } + + // Playback state changed... + if ( m_bWasPlaying != m_bIsPlayingBack ) + { + for(int i = 0; i < m_hActorList.Count() ; ++i ) + { + C_BaseFlex *actor = m_hActorList[ i ].Get(); + if ( !actor ) + continue; + + Assert( m_pScene ); + + if ( m_pScene ) + { + ClearSceneEvents( m_pScene, false ); + + if ( m_bIsPlayingBack ) + { + m_pScene->ResetSimulation(); + actor->StartChoreoScene( m_pScene ); + } + else + { + m_pScene->ResetSimulation(); + actor->RemoveChoreoScene( m_pScene ); + } + } + } + } +} + +void C_SceneEntity::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); + + m_bWasPlaying = m_bIsPlayingBack; +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame that an event is active (Start/EndEvent as also +// called) +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void C_SceneEntity::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + return; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Called for events that are part of a pause condition +// Input : *event - +// Output : Returns true on event completed, false on non-completion. +//----------------------------------------------------------------------------- +bool C_SceneEntity::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + return true; +} + +C_BaseFlex *C_SceneEntity::FindNamedActor( CChoreoActor *pChoreoActor ) +{ + if ( !m_pScene ) + return NULL; + + if ( m_hOwner.Get() != NULL ) + { + return m_hOwner.Get(); + } + + int idx = m_pScene->FindActorIndex( pChoreoActor ); + if ( idx < 0 || idx >= m_hActorList.Count() ) + return NULL; + + return m_hActorList[ idx ].Get(); +} + +//----------------------------------------------------------------------------- +// Purpose: All events are leading edge triggered +// Input : currenttime - +// *event - +//----------------------------------------------------------------------------- +void C_SceneEntity::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event ); + + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + { + Scene_Printf( "%s : %8.2f: ignored %s\n", GetSceneFileName(), currenttime, event->GetDescription() ); + return; + } + + + C_BaseFlex *pActor = NULL; + CChoreoActor *actor = event->GetActor(); + if ( actor ) + { + pActor = FindNamedActor( actor ); + if ( NULL == pActor ) + { + // This can occur if we haven't been networked an actor yet... we need to queue it so that we can + // fire off the start event as soon as we have the actor resident on the client. + QueueStartEvent( currenttime, scene, event ); + return; + } + } + + Scene_Printf( "%s : %8.2f: start %s\n", GetSceneFileName(), currenttime, event->GetDescription() ); + + switch ( event->GetType() ) + { + case CChoreoEvent::FLEXANIMATION: + { + if ( pActor ) + { + DispatchStartFlexAnimation( scene, pActor, event ); + } + } + break; + case CChoreoEvent::EXPRESSION: + { + if ( pActor ) + { + DispatchStartExpression( scene, pActor, event ); + } + } + break; + case CChoreoEvent::GESTURE: + { + // Verify data. + Assert( m_bMultiplayer ); + Assert( scene != NULL ); + Assert( event != NULL ); + + if ( pActor ) + { + DispatchStartGesture( scene, pActor, event ); + } + } + break; + case CChoreoEvent::SEQUENCE: + { + // Verify data. + Assert( m_bMultiplayer ); + Assert( scene != NULL ); + Assert( event != NULL ); + + if ( pActor ) + { + DispatchStartSequence( scene, pActor, event ); + } + } + break; + case CChoreoEvent::LOOP: + { + // Verify data. + Assert( m_bMultiplayer ); + Assert( scene != NULL ); + Assert( event != NULL ); + + DispatchProcessLoop( scene, event ); + } + case CChoreoEvent::SPEAK: + { + if ( IsClientOnly() && pActor ) + { + // FIXME: dB hack. soundlevel needs to be moved into inside of wav? + soundlevel_t iSoundlevel = SNDLVL_TALKING; + if ( event->GetParameters2() ) + { + iSoundlevel = (soundlevel_t)atoi( event->GetParameters2() ); + if ( iSoundlevel == SNDLVL_NONE ) + { + iSoundlevel = SNDLVL_TALKING; + } + } + + DispatchStartSpeak( scene, pActor, event, iSoundlevel ); + } + } + break; + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +// *event - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchProcessLoop( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::LOOP ); + + float backtime = (float)atof( event->GetParameters() ); + + bool process = true; + int counter = event->GetLoopCount(); + if ( counter != -1 ) + { + int remaining = event->GetNumLoopsRemaining(); + if ( remaining <= 0 ) + { + process = false; + } + else + { + event->SetNumLoopsRemaining( --remaining ); + } + } + + if ( !process ) + return; + + scene->LoopToTime( backtime ); + SetCurrentTime( backtime, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Playback sound file that contains phonemes +// Input : *actor - +// *parameters - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchStartSpeak( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event, soundlevel_t iSoundlevel ) +{ + // Emit sound + if ( IsClientOnly() && actor ) + { + CSingleUserRecipientFilter filter( C_BasePlayer::GetLocalPlayer() ); + + float time_in_past = m_flCurrentTime - event->GetStartTime() ; + float soundtime = gpGlobals->curtime - time_in_past; + + EmitSound_t es; + es.m_nChannel = CHAN_VOICE; + es.m_flVolume = 1; + es.m_SoundLevel = iSoundlevel; + es.m_flSoundTime = soundtime; + + // No CC since we do it manually + // FIXME: This will change + es.m_bEmitCloseCaption = false; + es.m_pSoundName = event->GetParameters(); + + EmitSound( filter, actor->entindex(), es ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + + // Close captioning only on master token no matter what... + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + bool validtoken = event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ); + if ( validtoken ) + { + CRC32_t tokenCRC; + CRC32_Init( &tokenCRC ); + + char lowercase[ 256 ]; + Q_strncpy( lowercase, tok, sizeof( lowercase ) ); + Q_strlower( lowercase ); + + CRC32_ProcessBuffer( &tokenCRC, lowercase, Q_strlen( lowercase ) ); + CRC32_Final( &tokenCRC ); + + float endtime = event->GetLastSlaveEndTime(); + float durationShort = event->GetDuration(); + float durationLong = endtime - event->GetStartTime(); + float duration = max( durationShort, durationLong ); + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->ProcessCaption( lowercase, duration ); + } + } + + } + } +} + +void C_SceneEntity::DispatchEndSpeak( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) +{ + if ( IsClientOnly() ) + { + actor->RemoveSceneEvent( scene, event, false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : currenttime - +// *event - +//----------------------------------------------------------------------------- +void C_SceneEntity::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event ); + + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + { + return; + } + + C_BaseFlex *pActor = NULL; + CChoreoActor *actor = event->GetActor(); + if ( actor ) + { + pActor = FindNamedActor( actor ); + } + + Scene_Printf( "%s : %8.2f: finish %s\n", GetSceneFileName(), currenttime, event->GetDescription() ); + + switch ( event->GetType() ) + { + case CChoreoEvent::FLEXANIMATION: + { + if ( pActor ) + { + DispatchEndFlexAnimation( scene, pActor, event ); + } + } + break; + case CChoreoEvent::EXPRESSION: + { + if ( pActor ) + { + DispatchEndExpression( scene, pActor, event ); + } + } + break; + case CChoreoEvent::GESTURE: + { + if ( pActor ) + { + DispatchEndGesture( scene, pActor, event ); + } + } + break; + case CChoreoEvent::SEQUENCE: + { + if ( pActor ) + { + DispatchEndSequence( scene, pActor, event ); + } + } + break; + case CChoreoEvent::SPEAK: + { + if ( IsClientOnly() && pActor ) + { + DispatchEndSpeak( scene, pActor, event ); + } + } + break; + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Binary compiled VCDs get their strings from a pool +//----------------------------------------------------------------------------- +class CChoreoStringPool : public IChoreoStringPool +{ +public: + short FindOrAddString( const char *pString ) + { + // huh?, no compilation at run time, only fetches + Assert( 0 ); + return -1; + } + + bool GetString( short stringId, char *buff, int buffSize ) + { + // fetch from compiled pool + const char *pString = scenefilecache->GetSceneString( stringId ); + if ( !pString ) + { + V_strncpy( buff, "", buffSize ); + return false; + } + V_strncpy( buff, pString, buffSize ); + return true; + } +}; +CChoreoStringPool g_ChoreoStringPool; + +CChoreoScene *C_SceneEntity::LoadScene( const char *filename ) +{ + char loadfile[ 512 ]; + Q_strncpy( loadfile, filename, sizeof( loadfile ) ); + Q_SetExtension( loadfile, ".vcd", sizeof( loadfile ) ); + Q_FixSlashes( loadfile ); + + char *pBuffer = NULL; + size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile ); + if ( bufsize <= 0 ) + return NULL; + + pBuffer = new char[ bufsize ]; + if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) ) + { + delete[] pBuffer; + return NULL; + } + + CChoreoScene *pScene; + if ( IsBufferBinaryVCD( pBuffer, bufsize ) ) + { + pScene = new CChoreoScene( this ); + CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY ); + if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) ) + { + Warning( "Unable to restore binary scene '%s'\n", loadfile ); + delete pScene; + pScene = NULL; + } + else + { + pScene->SetPrintFunc( Scene_Printf ); + pScene->SetEventCallbackInterface( this ); + } + } + else + { + g_TokenProcessor.SetBuffer( pBuffer ); + pScene = ChoreoLoadScene( loadfile, this, &g_TokenProcessor, Scene_Printf ); + } + + delete[] pBuffer; + return pScene; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void C_SceneEntity::LoadSceneFromFile( const char *filename ) +{ + UnloadScene(); + m_pScene = LoadScene( filename ); +} + +void C_SceneEntity::ClearSceneEvents( CChoreoScene *scene, bool canceled ) +{ + if ( !m_pScene ) + return; + + Scene_Printf( "%s : %8.2f: clearing events\n", GetSceneFileName(), m_flCurrentTime ); + + int i; + for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) + { + C_BaseFlex *pActor = FindNamedActor( m_pScene->GetActor( i ) ); + if ( !pActor ) + continue; + + // Clear any existing expressions + pActor->ClearSceneEvents( scene, canceled ); + } + + WipeQueuedEvents(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SceneEntity::UnloadScene( void ) +{ + WipeQueuedEvents(); + + if ( m_pScene ) + { + ClearSceneEvents( m_pScene, false ); + for ( int i = 0 ; i < m_pScene->GetNumActors(); i++ ) + { + C_BaseFlex *pTestActor = FindNamedActor( m_pScene->GetActor( i ) ); + + if ( !pTestActor ) + continue; + + pTestActor->RemoveChoreoScene( m_pScene ); + } + } + delete m_pScene; + m_pScene = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *event - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchStartFlexAnimation( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) +{ + actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *event - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchEndFlexAnimation( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) +{ + actor->RemoveSceneEvent( scene, event, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *event - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchStartExpression( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) +{ + actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *event - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchEndExpression( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) +{ + actor->RemoveSceneEvent( scene, event, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *parameters - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchStartGesture( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) +{ + // Ingore null gestures + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + return; + + actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchStartSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) +{ + actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchEndSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) +{ + actor->RemoveSceneEvent( scene, event, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *parameters - +//----------------------------------------------------------------------------- +void C_SceneEntity::DispatchEndGesture( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) +{ + // Ingore null gestures + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + return; + + actor->RemoveSceneEvent( scene, event, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SceneEntity::DoThink( float frametime ) +{ + if ( !m_pScene ) + return; + + if ( !m_bIsPlayingBack ) + { + WipeQueuedEvents(); + return; + } + + CheckQueuedEvents(); + + if ( m_bPaused ) + { + return; + } + + // Msg( "CL: %d, %f for %s\n", gpGlobals->tickcount, m_flCurrentTime, m_pScene->GetFilename() ); + + // Tell scene to go + m_pScene->Think( m_flCurrentTime ); + // Drive simulation time for scene + m_flCurrentTime += gpGlobals->frametime; +} + +void C_SceneEntity::ClientThink() +{ + DoThink( gpGlobals->frametime ); +} + +void C_SceneEntity::CheckQueuedEvents() +{ +// Check for duplicates + CUtlVector< QueuedEvents_t > events; + events = m_QueuedEvents; + m_QueuedEvents.RemoveAll(); + + int c = events.Count(); + for ( int i = 0; i < c; ++i ) + { + const QueuedEvents_t& check = events[ i ]; + + // Retry starting this event + StartEvent( check.starttime, check.scene, check.event ); + } +} + +void C_SceneEntity::WipeQueuedEvents() +{ + m_QueuedEvents.Purge(); +} + +void C_SceneEntity::QueueStartEvent( float starttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + // Check for duplicates + int c = m_QueuedEvents.Count(); + for ( int i = 0; i < c; ++i ) + { + const QueuedEvents_t& check = m_QueuedEvents[ i ]; + if ( check.scene == scene && + check.event == event ) + return; + } + + QueuedEvents_t qe; + qe.scene = scene; + qe.event = event; + qe.starttime = starttime; + m_QueuedEvents.AddToTail( qe ); +} + +//----------------------------------------------------------------------------- +// Purpose: Resets time such that the client version of the .vcd is also updated, if appropriate +// Input : t - +// forceClientSync - unused for now, we may want to reenable this at some point +//----------------------------------------------------------------------------- +void C_SceneEntity::SetCurrentTime( float t, bool forceClientSync ) +{ + m_flCurrentTime = t; + m_flForceClientTime = t; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SceneEntity::PrefetchAnimBlocks( CChoreoScene *pScene ) +{ + Assert( pScene && m_bMultiplayer ); + if ( !pScene || !m_bMultiplayer ) + return; + + // Build a fast lookup, too + CUtlMap actorMap( 0, 0, DefLessFunc( CChoreoActor* ) ); + + int nSpew = 0; + int nResident = 0; + int nChecked = 0; + + // Iterate events and precache necessary resources + for ( int i = 0; i < pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *pEvent = pScene->GetEvent( i ); + if ( !pEvent ) + continue; + + // load any necessary data + switch ( pEvent->GetType() ) + { + default: + break; + case CChoreoEvent::SEQUENCE: + case CChoreoEvent::GESTURE: + { + CChoreoActor *pActor = pEvent->GetActor(); + if ( pActor ) + { + CBaseFlex *pFlex = NULL; + int idx = actorMap.Find( pActor ); + if ( idx == actorMap.InvalidIndex() ) + { + pFlex = FindNamedActor( pActor ); + idx = actorMap.Insert( pActor, pFlex ); + } + else + { + pFlex = actorMap[ idx ]; + } + + if ( pFlex ) + { + int iSequence = pFlex->LookupSequence( pEvent->GetParameters() ); + if ( iSequence >= 0 ) + { + CStudioHdr *pStudioHdr = pFlex->GetModelPtr(); + if ( pStudioHdr ) + { + // Now look up the animblock + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + for ( int i = 0 ; i < seqdesc.groupsize[ 0 ] ; ++i ) + { + for ( int j = 0; j < seqdesc.groupsize[ 1 ]; ++j ) + { + int iAnimation = seqdesc.anim( i, j ); + int iBaseAnimation = pStudioHdr->iRelativeAnim( iSequence, iAnimation ); + mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( iBaseAnimation ); + + ++nChecked; + + if ( nSpew != 0 ) + { + Msg( "%s checking block %d\n", pStudioHdr->pszName(), animdesc.animblock ); + } + + // Async load the animation + int iFrame = 0; + const mstudioanim_t *panim = animdesc.pAnim( &iFrame ); + if ( panim ) + { + ++nResident; + if ( nSpew > 1 ) + { + Msg( "%s:%s[%i:%i] was resident\n", pStudioHdr->pszName(), animdesc.pszName(), i, j ); + } + } + else + { + if ( nSpew != 0 ) + { + Msg( "%s:%s[%i:%i] async load\n", pStudioHdr->pszName(), animdesc.pszName(), i, j ); + } + } + } + } + } + } + } + } + break; + } + } + } + + if ( !nSpew || nChecked <= 0 ) + return; + + Msg( "%d of %d animations resident\n", nResident, nChecked ); +} \ No newline at end of file diff --git a/game/client/c_sceneentity.h b/game/client/c_sceneentity.h new file mode 100644 index 00000000..49d3c6f2 --- /dev/null +++ b/game/client/c_sceneentity.h @@ -0,0 +1,115 @@ +//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_SCENEENTITY_H +#define C_SCENEENTITY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ichoreoeventcallback.h" + +class C_SceneEntity : public C_BaseEntity, public IChoreoEventCallback +{ + friend class CChoreoEventCallback; + +public: + DECLARE_CLASS( C_SceneEntity, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_SceneEntity( void ); + ~C_SceneEntity( void ); + + // From IChoreoEventCallback + virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual void EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual void ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual bool CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + virtual void PreDataUpdate( DataUpdateType_t updateType ); + + virtual void StopClientOnlyScene(); + virtual void SetupClientOnlyScene( const char *pszFilename, C_BaseFlex *pOwner = NULL , bool bMultiplayer = false ); + + virtual void ClientThink(); + + void OnResetClientTime(); + + CHandle< C_BaseFlex > GetActor( int i ){ return ( i < m_hActorList.Count() ) ? m_hActorList[i] : NULL; } + + virtual void DispatchStartSpeak( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event, soundlevel_t iSoundlevel ); + virtual void DispatchEndSpeak( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + + bool IsClientOnly( void ){ return m_bClientOnly; } + +private: + + void ResetActorFlexesForScene(); + + // Scene load/unload + CChoreoScene *LoadScene( const char *filename ); + void LoadSceneFromFile( const char *filename ); + void UnloadScene( void ); + void PrefetchAnimBlocks( CChoreoScene *pScene ); + + C_BaseFlex *FindNamedActor( CChoreoActor *pChoreoActor ); + + virtual void DispatchStartFlexAnimation( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + virtual void DispatchEndFlexAnimation( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + virtual void DispatchStartExpression( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + virtual void DispatchEndExpression( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + virtual void DispatchStartGesture( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + virtual void DispatchEndGesture( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + virtual void DispatchStartSequence( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + virtual void DispatchEndSequence( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ); + void DispatchProcessLoop( CChoreoScene *scene, CChoreoEvent *event ); + + char const *GetSceneFileName(); + + void DoThink( float frametime ); + + void ClearSceneEvents( CChoreoScene *scene, bool canceled ); + void SetCurrentTime( float t, bool forceClientSync ); + + bool GetHWMorphSceneFileName( const char *pFilename, char *pHWMFilename ); + +private: + + void CheckQueuedEvents(); + void WipeQueuedEvents(); + void QueueStartEvent( float starttime, CChoreoScene *scene, CChoreoEvent *event ); + + bool m_bIsPlayingBack; + bool m_bPaused; + bool m_bMultiplayer; + float m_flCurrentTime; + float m_flForceClientTime; + int m_nSceneStringIndex; + bool m_bClientOnly; + + CHandle< C_BaseFlex > m_hOwner; // if set, this overrides the m_hActorList in FindNamedActor() + + CUtlVector< CHandle< C_BaseFlex > > m_hActorList; + +private: + bool m_bWasPlaying; + + CChoreoScene *m_pScene; + + struct QueuedEvents_t + { + float starttime; + CChoreoScene *scene; + CChoreoEvent *event; + }; + + CUtlVector< QueuedEvents_t > m_QueuedEvents; +}; + +#endif // C_SCENEENTITY_H diff --git a/game/client/c_shadowcontrol.cpp b/game/client/c_shadowcontrol.cpp new file mode 100644 index 00000000..485a9bc3 --- /dev/null +++ b/game/client/c_shadowcontrol.cpp @@ -0,0 +1,66 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Shadow control entity. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------ +// FIXME: This really should inherit from something more lightweight +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Purpose : Shadow control entity +//------------------------------------------------------------------------------ +class C_ShadowControl : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_ShadowControl, C_BaseEntity ); + + DECLARE_CLIENTCLASS(); + + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + +private: + Vector m_shadowDirection; + color32 m_shadowColor; + float m_flShadowMaxDist; + bool m_bDisableShadows; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_ShadowControl, DT_ShadowControl, CShadowControl) + RecvPropVector(RECVINFO(m_shadowDirection)), + RecvPropInt(RECVINFO(m_shadowColor)), + RecvPropFloat(RECVINFO(m_flShadowMaxDist)), + RecvPropBool(RECVINFO(m_bDisableShadows)), +END_RECV_TABLE() + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_ShadowControl::OnDataChanged(DataUpdateType_t updateType) +{ + // Set the color, direction, distance... + g_pClientShadowMgr->SetShadowDirection( m_shadowDirection ); + g_pClientShadowMgr->SetShadowColor( m_shadowColor.r, m_shadowColor.g, m_shadowColor.b ); + g_pClientShadowMgr->SetShadowDistance( m_flShadowMaxDist ); + g_pClientShadowMgr->SetShadowsDisabled( m_bDisableShadows ); +} + +//------------------------------------------------------------------------------ +// We don't draw... +//------------------------------------------------------------------------------ +bool C_ShadowControl::ShouldDraw() +{ + return false; +} + diff --git a/game/client/c_slideshow_display.cpp b/game/client/c_slideshow_display.cpp new file mode 100644 index 00000000..71a995f8 --- /dev/null +++ b/game/client/c_slideshow_display.cpp @@ -0,0 +1,377 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "c_slideshow_display.h" +#include "c_te_legacytempents.h" +#include "tempent.h" +#include "engine/IEngineSound.h" +#include "dlight.h" +#include "iefx.h" +#include "soundemittersystem/isoundemittersystembase.h" +#include "filesystem.h" +#include "KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#define SLIDESHOW_LIST_BUFFER_MAX 8192 + + +enum SlideshowCycleTypes +{ + SLIDESHOW_CYCLE_RANDOM, + SLIDESHOW_CYCLE_FORWARD, + SLIDESHOW_CYCLE_BACKWARD, + + SLIDESHOW_CYCLE_TOTAL +}; + + +CUtlVector< C_SlideshowDisplay* > g_SlideshowDisplays; + + +IMPLEMENT_CLIENTCLASS_DT(C_SlideshowDisplay, DT_SlideshowDisplay, CSlideshowDisplay) + RecvPropBool( RECVINFO(m_bEnabled) ), + RecvPropString( RECVINFO( m_szDisplayText ) ), + RecvPropString( RECVINFO( m_szSlideshowDirectory ) ), + RecvPropArray3( RECVINFO_ARRAY(m_chCurrentSlideLists), RecvPropInt( RECVINFO(m_chCurrentSlideLists[0]) ) ), + RecvPropFloat( RECVINFO(m_fMinSlideTime) ), + RecvPropFloat( RECVINFO(m_fMaxSlideTime) ), + RecvPropInt( RECVINFO(m_iCycleType) ), + RecvPropBool( RECVINFO(m_bNoListRepeats) ), +END_RECV_TABLE() + + +C_SlideshowDisplay::C_SlideshowDisplay() +{ + g_SlideshowDisplays.AddToTail( this ); +} + +C_SlideshowDisplay::~C_SlideshowDisplay() +{ + g_SlideshowDisplays.FindAndRemove( this ); +} + +void C_SlideshowDisplay::Spawn( void ) +{ + BaseClass::Spawn(); + + m_NextSlideTime = 0; + + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +void C_SlideshowDisplay::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + BuildSlideShowImagesList(); + } + else if ( updateType == DATA_UPDATE_DATATABLE_CHANGED ) + { + m_iCurrentSlideList = 0; + m_iCurrentSlide = 0; + } +} + +int C_SlideshowDisplay::GetMaterialIndex( int iSlideIndex ) +{ + if ( !m_SlideMaterialLists[ 0 ] ) + return 0; + + return m_SlideMaterialLists[ 0 ]->iSlideMaterials[ iSlideIndex ]; +} + +int C_SlideshowDisplay::NumMaterials( void ) +{ + if ( !m_SlideMaterialLists[ 0 ] ) + return 0; + + return m_SlideMaterialLists[ 0 ]->iSlideMaterials.Count(); +} + +void C_SlideshowDisplay::ClientThink( void ) +{ + BaseClass::ClientThink(); + + if ( !m_bEnabled ) + return; + + // Check if it's time for the next slide + if ( m_NextSlideTime > gpGlobals->curtime ) + return; + + // Set the time to cycle to the next slide + m_NextSlideTime = gpGlobals->curtime + RandomFloat( m_fMinSlideTime, m_fMaxSlideTime ); + + // Get the amount of items to pick from + int iNumCurrentSlideLists; + for ( iNumCurrentSlideLists = 0; iNumCurrentSlideLists < 16; ++iNumCurrentSlideLists ) + { + if ( m_chCurrentSlideLists[ iNumCurrentSlideLists ] == (unsigned char)-1 ) + break; + } + + // Bail if no slide lists are selected + if ( iNumCurrentSlideLists == 0 ) + return; + + // Cycle the list + switch ( m_iCycleType ) + { + case SLIDESHOW_CYCLE_RANDOM: + { + int iOldSlideList = m_iCurrentSlideList; + m_iCurrentSlideList = RandomInt( 0, iNumCurrentSlideLists - 1 ); + + // Prevent repeats if we don't want them + if ( m_bNoListRepeats && iNumCurrentSlideLists > 1 && m_iCurrentSlideList == iOldSlideList ) + { + ++m_iCurrentSlideList; + + if ( m_iCurrentSlideList >= iNumCurrentSlideLists ) + m_iCurrentSlideList = 0; + } + + break; + } + + case SLIDESHOW_CYCLE_FORWARD: + if ( m_iCurrentSlideList >= iNumCurrentSlideLists ) + m_iCurrentSlideList = 0; + break; + + case SLIDESHOW_CYCLE_BACKWARD: + if ( m_iCurrentSlideList < 0 ) + m_iCurrentSlideList = iNumCurrentSlideLists - 1; + break; + } + + SlideMaterialList_t *pSlideMaterialList = m_SlideMaterialLists[ m_chCurrentSlideLists[ m_iCurrentSlideList ] ]; + + // Cycle in the list + switch ( m_iCycleType ) + { + case SLIDESHOW_CYCLE_RANDOM: + m_iCurrentSlide = RandomInt( 0, pSlideMaterialList->iSlideMaterials.Count() - 1 ); + break; + + case SLIDESHOW_CYCLE_FORWARD: + ++m_iCurrentSlide; + if ( m_iCurrentSlide >= pSlideMaterialList->iSlideMaterials.Count() ) + { + ++m_iCurrentSlideList; + if ( m_iCurrentSlideList >= iNumCurrentSlideLists ) + m_iCurrentSlideList = 0; + pSlideMaterialList = m_SlideMaterialLists[ m_chCurrentSlideLists[ m_iCurrentSlideList ] ]; + m_iCurrentSlide = 0; + } + break; + + case SLIDESHOW_CYCLE_BACKWARD: + --m_iCurrentSlide; + if ( m_iCurrentSlide < 0 ) + { + --m_iCurrentSlideList; + if ( m_iCurrentSlideList < 0 ) + m_iCurrentSlideList = iNumCurrentSlideLists - 1; + pSlideMaterialList = m_SlideMaterialLists[ m_chCurrentSlideLists[ m_iCurrentSlideList ] ]; + m_iCurrentSlide = pSlideMaterialList->iSlideMaterials.Count() - 1; + } + break; + } + + // Set the current material to what we've cycled to + m_iCurrentMaterialIndex = pSlideMaterialList->iSlideMaterials[ m_iCurrentSlide ]; + m_iCurrentSlideIndex = pSlideMaterialList->iSlideIndex[ m_iCurrentSlide ]; +} + +void C_SlideshowDisplay::BuildSlideShowImagesList( void ) +{ + FileFindHandle_t matHandle; + char szDirectory[_MAX_PATH]; + char szMatFileName[_MAX_PATH] = {'\0'}; + char szFileBuffer[ SLIDESHOW_LIST_BUFFER_MAX ]; + char *pchCurrentLine = NULL; + + if ( IsX360() ) + { + Q_snprintf( szDirectory, sizeof( szDirectory ), "materials/vgui/%s/slides.txt", m_szSlideshowDirectory ); + + FileHandle_t fh = g_pFullFileSystem->Open( szDirectory, "rt" ); + if ( !fh ) + { + DevWarning( "Couldn't read slideshow image file %s!", szDirectory ); + return; + } + + int iFileSize = min( g_pFullFileSystem->Size( fh ), SLIDESHOW_LIST_BUFFER_MAX ); + + int iBytesRead = g_pFullFileSystem->Read( szFileBuffer, iFileSize, fh ); + g_pFullFileSystem->Close( fh ); + + // Ensure we don't write outside of our buffer + if ( iBytesRead > iFileSize ) + iBytesRead = iFileSize; + szFileBuffer[ iBytesRead ] = '\0'; + + pchCurrentLine = szFileBuffer; + + // Seek to end of first line + char *pchNextLine = pchCurrentLine; + while ( *pchNextLine != '\0' && *pchNextLine != '\n' && *pchNextLine != ' ' ) + ++pchNextLine; + + if ( *pchNextLine != '\0' ) + { + // Mark end of string + *pchNextLine = '\0'; + + // Seek to start of next string + ++pchNextLine; + while ( *pchNextLine != '\0' && ( *pchNextLine == '\n' || *pchNextLine == ' ' ) ) + ++pchNextLine; + } + + Q_strncpy( szMatFileName, pchCurrentLine, sizeof(szMatFileName) ); + pchCurrentLine = pchNextLine; + } + else + { + Q_snprintf( szDirectory, sizeof( szDirectory ), "materials/vgui/%s/*.vmt", m_szSlideshowDirectory ); + const char *pMatFileName = g_pFullFileSystem->FindFirst( szDirectory, &matHandle ); + + if ( pMatFileName ) + Q_strncpy( szMatFileName, pMatFileName, sizeof(szMatFileName) ); + } + + int iSlideIndex = 0; + + while ( szMatFileName[ 0 ] ) + { + char szFileName[_MAX_PATH]; + Q_snprintf( szFileName, sizeof( szFileName ), "vgui/%s/%s", m_szSlideshowDirectory, szMatFileName ); + szFileName[ Q_strlen( szFileName ) - 4 ] = '\0'; + + int iMatIndex = ::GetMaterialIndex( szFileName ); + + // Get material keywords + char szFullFileName[_MAX_PATH]; + Q_snprintf( szFullFileName, sizeof( szFullFileName ), "materials/vgui/%s/%s", m_szSlideshowDirectory, szMatFileName ); + + KeyValues *pMaterialKeys = new KeyValues( "material" ); + bool bLoaded = pMaterialKeys->LoadFromFile( g_pFullFileSystem, szFullFileName, NULL ); + + if ( bLoaded ) + { + char szKeywords[ 256 ]; + Q_strcpy( szKeywords, pMaterialKeys->GetString( "%keywords", "" ) ); + + char *pchKeyword = szKeywords; + + while ( pchKeyword[ 0 ] != '\0' ) + { + char *pNextKeyword = pchKeyword; + + // Skip commas and spaces + while ( pNextKeyword[ 0 ] != '\0' && pNextKeyword[ 0 ] != ',' ) + ++pNextKeyword; + + if ( pNextKeyword[ 0 ] != '\0' ) + { + pNextKeyword[ 0 ] = '\0'; + ++pNextKeyword; + + while ( pNextKeyword[ 0 ] != '\0' && ( pNextKeyword[ 0 ] == ',' || pNextKeyword[ 0 ] == ' ' ) ) + ++pNextKeyword; + } + + // Find the list with the current keyword + int iList; + for ( iList = 0; iList < m_SlideMaterialLists.Count(); ++iList ) + { + if ( Q_strcmp( m_SlideMaterialLists[ iList ]->szSlideKeyword, pchKeyword ) == 0 ) + break; + } + + if ( iList >= m_SlideMaterialLists.Count() ) + { + // Couldn't find the list, so create it + iList = m_SlideMaterialLists.AddToTail( new SlideMaterialList_t ); + Q_strcpy( m_SlideMaterialLists[ iList ]->szSlideKeyword, pchKeyword ); + } + + // Add material index to this list + m_SlideMaterialLists[ iList ]->iSlideMaterials.AddToTail( iMatIndex ); + m_SlideMaterialLists[ iList ]->iSlideIndex.AddToTail( iSlideIndex ); + + pchKeyword = pNextKeyword; + } + } + + // Find the generic list + int iList; + for ( iList = 0; iList < m_SlideMaterialLists.Count(); ++iList ) + { + if ( Q_strcmp( m_SlideMaterialLists[ iList ]->szSlideKeyword, "" ) == 0 ) + break; + } + + if ( iList >= m_SlideMaterialLists.Count() ) + { + // Couldn't find the generic list, so create it + iList = m_SlideMaterialLists.AddToHead( new SlideMaterialList_t ); + Q_strcpy( m_SlideMaterialLists[ iList ]->szSlideKeyword, "" ); + } + + // Add material index to this list + m_SlideMaterialLists[ iList ]->iSlideMaterials.AddToTail( iMatIndex ); + m_SlideMaterialLists[ iList ]->iSlideIndex.AddToTail( iSlideIndex ); + + if ( IsX360() ) + { + // Seek to end of first line + char *pchNextLine = pchCurrentLine; + while ( *pchNextLine != '\0' && *pchNextLine != '\n' && *pchNextLine != ' ' ) + ++pchNextLine; + + if ( *pchNextLine != '\0' ) + { + // Mark end of string + *pchNextLine = '\0'; + + // Seek to start of next string + ++pchNextLine; + while ( *pchNextLine != '\0' && ( *pchNextLine == '\n' || *pchNextLine == ' ' ) ) + ++pchNextLine; + } + + Q_strncpy( szMatFileName, pchCurrentLine, sizeof(szMatFileName) ); + pchCurrentLine = pchNextLine; + } + else + { + const char *pMatFileName = g_pFullFileSystem->FindNext( matHandle ); + + if ( pMatFileName ) + Q_strncpy( szMatFileName, pMatFileName, sizeof(szMatFileName) ); + else + szMatFileName[ 0 ] = '\0'; + } + + ++iSlideIndex; + } + + if ( !IsX360() ) + { + g_pFullFileSystem->FindClose( matHandle ); + } +} \ No newline at end of file diff --git a/game/client/c_slideshow_display.h b/game/client/c_slideshow_display.h new file mode 100644 index 00000000..259c89ac --- /dev/null +++ b/game/client/c_slideshow_display.h @@ -0,0 +1,76 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef C_SLIDESHOW_DISPLAY_H +#define C_SLIDESHOW_DISPLAY_H + +#include "cbase.h" +#include "utlvector.h" + + +struct SlideMaterialList_t +{ + char szSlideKeyword[64]; + CUtlVector iSlideMaterials; + CUtlVector iSlideIndex; +}; + + +class C_SlideshowDisplay : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_SlideshowDisplay, CBaseEntity ); + DECLARE_CLIENTCLASS(); + + C_SlideshowDisplay(); + virtual ~C_SlideshowDisplay(); + + void Spawn( void ); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + + void ClientThink( void ); + + bool IsEnabled( void ) { return m_bEnabled; } + + void GetDisplayText( char *pchText ) { Q_strcpy( pchText, m_szDisplayText ); } + int CurrentMaterialIndex( void ) { return m_iCurrentMaterialIndex; } + int GetMaterialIndex( int iSlideIndex ); + int NumMaterials( void ); + int CurrentSlideIndex( void ) { return m_iCurrentSlideIndex; } + +private: + + void BuildSlideShowImagesList( void ); + +private: + + bool m_bEnabled; + + char m_szDisplayText[ 128 ]; + + char m_szSlideshowDirectory[ 128 ]; + + CUtlVector m_SlideMaterialLists; + unsigned char m_chCurrentSlideLists[ 16 ]; + int m_iCurrentMaterialIndex; + int m_iCurrentSlideIndex; + + float m_fMinSlideTime; + float m_fMaxSlideTime; + + float m_NextSlideTime; + + int m_iCycleType; + bool m_bNoListRepeats; + int m_iCurrentSlideList; + int m_iCurrentSlide; +}; + +extern CUtlVector< C_SlideshowDisplay* > g_SlideshowDisplays; + +#endif //C_SLIDESHOW_STATS_DISPLAY_H \ No newline at end of file diff --git a/game/client/c_smoke_trail.cpp b/game/client/c_smoke_trail.cpp new file mode 100644 index 00000000..f30c4e49 --- /dev/null +++ b/game/client/c_smoke_trail.cpp @@ -0,0 +1,2011 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "cbase.h" +#include "c_smoke_trail.h" +#include "fx.h" +#include "engine/IVDebugOverlay.h" +#include "engine/ienginesound.h" +#include "c_te_effect_dispatch.h" +#include "glow_overlay.h" +#include "fx_explosion.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "view.h" +#include "ClientEffectPrecacheSystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// +// CRocketTrailParticle +// + +class CRocketTrailParticle : public CSimpleEmitter +{ +public: + + CRocketTrailParticle( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + //Create + static CRocketTrailParticle *Create( const char *pDebugName ) + { + return new CRocketTrailParticle( pDebugName ); + } + + //Roll + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -8.0f ); + + //Cap the minimum roll + if ( fabs( pParticle->m_flRollDelta ) < 0.5f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f; + } + + return pParticle->m_flRoll; + } + + //Alpha + virtual float UpdateAlpha( const SimpleParticle *pParticle ) + { + return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) ); + } + +private: + CRocketTrailParticle( const CRocketTrailParticle & ); +}; + +// +// CSmokeParticle +// + +class CSmokeParticle : public CSimpleEmitter +{ +public: + + CSmokeParticle( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + //Create + static CSmokeParticle *Create( const char *pDebugName ) + { + return new CSmokeParticle( pDebugName ); + } + + //Alpha + virtual float UpdateAlpha( const SimpleParticle *pParticle ) + { + return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) ); + } + + //Color + virtual Vector UpdateColor( const SimpleParticle *pParticle ) + { + Vector color; + + float tLifetime = pParticle->m_flLifetime / pParticle->m_flDieTime; + float ramp = 1.0f - tLifetime; + + color[0] = ( (float) pParticle->m_uchColor[0] * ramp ) / 255.0f; + color[1] = ( (float) pParticle->m_uchColor[1] * ramp ) / 255.0f; + color[2] = ( (float) pParticle->m_uchColor[2] * ramp ) / 255.0f; + + return color; + } + + //Roll + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -8.0f ); + + //Cap the minimum roll + if ( fabs( pParticle->m_flRollDelta ) < 0.5f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f; + } + + return pParticle->m_flRoll; + } + +private: + CSmokeParticle( const CSmokeParticle & ); +}; + +// Datatable.. this can have all the smoketrail parameters when we need it to. +IMPLEMENT_CLIENTCLASS_DT(C_SmokeTrail, DT_SmokeTrail, SmokeTrail) + RecvPropFloat(RECVINFO(m_SpawnRate)), + RecvPropVector(RECVINFO(m_StartColor)), + RecvPropVector(RECVINFO(m_EndColor)), + RecvPropFloat(RECVINFO(m_ParticleLifetime)), + RecvPropFloat(RECVINFO(m_StopEmitTime)), + RecvPropFloat(RECVINFO(m_MinSpeed)), + RecvPropFloat(RECVINFO(m_MaxSpeed)), + RecvPropFloat(RECVINFO(m_MinDirectedSpeed)), + RecvPropFloat(RECVINFO(m_MaxDirectedSpeed)), + RecvPropFloat(RECVINFO(m_StartSize)), + RecvPropFloat(RECVINFO(m_EndSize)), + RecvPropFloat(RECVINFO(m_SpawnRadius)), + RecvPropInt(RECVINFO(m_bEmit)), + RecvPropInt(RECVINFO(m_nAttachment)), + RecvPropFloat(RECVINFO(m_Opacity)), +END_RECV_TABLE() + +// ------------------------------------------------------------------------- // +// ParticleMovieExplosion +// ------------------------------------------------------------------------- // +C_SmokeTrail::C_SmokeTrail() +{ + m_MaterialHandle[0] = NULL; + m_MaterialHandle[1] = NULL; + + m_SpawnRate = 10; + m_ParticleSpawn.Init(10); + m_StartColor.Init(0.5, 0.5, 0.5); + m_EndColor.Init(0,0,0); + m_ParticleLifetime = 5; + m_StopEmitTime = 0; // No end time + m_MinSpeed = 2; + m_MaxSpeed = 4; + m_MinDirectedSpeed = m_MaxDirectedSpeed = 0; + m_StartSize = 35; + m_EndSize = 55; + m_SpawnRadius = 2; + m_VelocityOffset.Init(); + m_Opacity = 0.5f; + + m_bEmit = true; + + m_nAttachment = -1; + + m_pSmokeEmitter = NULL; + m_pParticleMgr = NULL; +} + +C_SmokeTrail::~C_SmokeTrail() +{ + if ( ToolsEnabled() && clienttools->IsInRecordingMode() && m_pSmokeEmitter.IsValid() && m_pSmokeEmitter->GetToolParticleEffectId() != TOOLPARTICLESYSTEMID_INVALID ) + { + KeyValues *msg = new KeyValues( "OldParticleSystem_ActivateEmitter" ); + msg->SetInt( "id", m_pSmokeEmitter->GetToolParticleEffectId() ); + msg->SetInt( "emitter", 0 ); + msg->SetInt( "active", false ); + msg->SetFloat( "time", gpGlobals->curtime ); + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } + + if ( m_pParticleMgr ) + { + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SmokeTrail::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + C_BaseEntity *pEnt = pAttachedTo->GetBaseEntity(); + if (pEnt && (m_nAttachment > 0)) + { + pEnt->GetAttachment( m_nAttachment, *pAbsOrigin, *pAbsAngles ); + } + else + { + BaseClass::GetAimEntOrigin( pAttachedTo, pAbsOrigin, pAbsAngles ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bEmit - +//----------------------------------------------------------------------------- +void C_SmokeTrail::SetEmit(bool bEmit) +{ + m_bEmit = bEmit; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rate - +//----------------------------------------------------------------------------- +void C_SmokeTrail::SetSpawnRate(float rate) +{ + m_SpawnRate = rate; + m_ParticleSpawn.Init(rate); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_SmokeTrail::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if ( updateType == DATA_UPDATE_CREATED ) + { + Start( ParticleMgr(), NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_SmokeTrail::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + if(!pParticleMgr->AddEffect( &m_ParticleEffect, this )) + return; + + m_pParticleMgr = pParticleMgr; + m_pSmokeEmitter = CSmokeParticle::Create("smokeTrail"); + + if ( !m_pSmokeEmitter ) + { + Assert( false ); + return; + } + + m_pSmokeEmitter->SetSortOrigin( GetAbsOrigin() ); + m_pSmokeEmitter->SetNearClip( 64.0f, 128.0f ); + + m_MaterialHandle[0] = g_Mat_DustPuff[0]; + m_MaterialHandle[1] = g_Mat_DustPuff[1]; + + m_ParticleSpawn.Init( m_SpawnRate ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_SmokeTrail::Update( float fTimeDelta ) +{ + if ( !m_pSmokeEmitter ) + return; + + Vector offsetColor; + + // Add new particles + if ( !m_bEmit ) + return; + + if ( ( m_StopEmitTime != 0 ) && ( m_StopEmitTime <= gpGlobals->curtime ) ) + return; + + float tempDelta = fTimeDelta; + + SimpleParticle *pParticle; + Vector offset; + + Vector vecOrigin; + VectorMA( GetAbsOrigin(), -fTimeDelta, GetAbsVelocity(), vecOrigin ); + + Vector vecForward; + GetVectors( &vecForward, NULL, NULL ); + + while( m_ParticleSpawn.NextEvent( tempDelta ) ) + { + float fldt = fTimeDelta - tempDelta; + + offset.Random( -m_SpawnRadius, m_SpawnRadius ); + offset += vecOrigin; + VectorMA( offset, fldt, GetAbsVelocity(), offset ); + + pParticle = (SimpleParticle *) m_pSmokeEmitter->AddParticle( sizeof( SimpleParticle ), m_MaterialHandle[random->RandomInt(0,1)], offset ); + + if ( pParticle == NULL ) + continue; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = m_ParticleLifetime; + + pParticle->m_vecVelocity.Random( -1.0f, 1.0f ); + pParticle->m_vecVelocity *= random->RandomFloat( m_MinSpeed, m_MaxSpeed ); + + pParticle->m_vecVelocity = pParticle->m_vecVelocity + GetAbsVelocity(); + + float flDirectedVel = random->RandomFloat( m_MinDirectedSpeed, m_MaxDirectedSpeed ); + VectorMA( pParticle->m_vecVelocity, flDirectedVel, vecForward, pParticle->m_vecVelocity ); + + offsetColor = m_StartColor; + float flMaxVal = max( m_StartColor[0], m_StartColor[1] ); + if ( flMaxVal < m_StartColor[2] ) + { + flMaxVal = m_StartColor[2]; + } + offsetColor /= flMaxVal; + + offsetColor *= random->RandomFloat( -0.2f, 0.2f ); + offsetColor += m_StartColor; + + offsetColor[0] = clamp( offsetColor[0], 0.0f, 1.0f ); + offsetColor[1] = clamp( offsetColor[1], 0.0f, 1.0f ); + offsetColor[2] = clamp( offsetColor[2], 0.0f, 1.0f ); + + pParticle->m_uchColor[0] = offsetColor[0]*255.0f; + pParticle->m_uchColor[1] = offsetColor[1]*255.0f; + pParticle->m_uchColor[2] = offsetColor[2]*255.0f; + + pParticle->m_uchStartSize = m_StartSize; + pParticle->m_uchEndSize = m_EndSize; + + float alpha = random->RandomFloat( m_Opacity*0.75f, m_Opacity*1.25f ); + alpha = clamp( alpha, 0.0f, 1.0f ); + + pParticle->m_uchStartAlpha = alpha * 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1.0f, 1.0f ); + } +} + + +void C_SmokeTrail::RenderParticles( CParticleRenderIterator *pIterator ) +{ +} + + +void C_SmokeTrail::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ +} + + +//----------------------------------------------------------------------------- +// This is called after sending this entity's recording state +//----------------------------------------------------------------------------- + +void C_SmokeTrail::CleanupToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + BaseClass::CleanupToolRecordingState( msg ); + + // Generally, this is used to allow the entity to clean up + // allocated state it put into the message, but here we're going + // to use it to send particle system messages because we + // know the grenade has been recorded at this point + if ( !clienttools->IsInRecordingMode() || !m_pSmokeEmitter.IsValid() ) + return; + + // For now, we can't record smoketrails that don't have a moveparent + C_BaseEntity *pEnt = GetMoveParent(); + if ( !pEnt ) + return; + + bool bEmitterActive = m_bEmit && ( ( m_StopEmitTime == 0 ) || ( m_StopEmitTime > gpGlobals->curtime ) ); + + // NOTE: Particle system destruction message will be sent by the particle effect itself. + if ( m_pSmokeEmitter->GetToolParticleEffectId() == TOOLPARTICLESYSTEMID_INVALID ) + { + int nId = m_pSmokeEmitter->AllocateToolParticleEffectId(); + + KeyValues *msg = new KeyValues( "OldParticleSystem_Create" ); + msg->SetString( "name", "C_SmokeTrail" ); + msg->SetInt( "id", nId ); + msg->SetFloat( "time", gpGlobals->curtime ); + + KeyValues *pRandomEmitter = msg->FindKey( "DmeRandomEmitter", true ); + pRandomEmitter->SetInt( "count", m_SpawnRate ); // particles per second, when duration is < 0 + pRandomEmitter->SetFloat( "duration", -1 ); + pRandomEmitter->SetInt( "active", bEmitterActive ); + + KeyValues *pEmitterParent1 = pRandomEmitter->FindKey( "emitter1", true ); + pEmitterParent1->SetFloat( "randomamount", 0.5f ); + KeyValues *pEmitterParent2 = pRandomEmitter->FindKey( "emitter2", true ); + pEmitterParent2->SetFloat( "randomamount", 0.5f ); + + KeyValues *pEmitter = pEmitterParent1->FindKey( "DmeSpriteEmitter", true ); + pEmitter->SetString( "material", "particle/particle_smokegrenade" ); + + KeyValues *pInitializers = pEmitter->FindKey( "initializers", true ); + + // FIXME: Until we can interpolate ent logs during emission, this can't work + KeyValues *pPosition = pInitializers->FindKey( "DmePositionPointToEntityInitializer", true ); + pPosition->SetPtr( "entindex", (void*)pEnt->entindex() ); + pPosition->SetInt( "attachmentIndex", m_nAttachment ); + pPosition->SetFloat( "randomDist", m_SpawnRadius ); + pPosition->SetFloat( "startx", pEnt->GetAbsOrigin().x ); + pPosition->SetFloat( "starty", pEnt->GetAbsOrigin().y ); + pPosition->SetFloat( "startz", pEnt->GetAbsOrigin().z ); + + KeyValues *pLifetime = pInitializers->FindKey( "DmeRandomLifetimeInitializer", true ); + pLifetime->SetFloat( "minLifetime", m_ParticleLifetime ); + pLifetime->SetFloat( "maxLifetime", m_ParticleLifetime ); + + KeyValues *pVelocity = pInitializers->FindKey( "DmeAttachmentVelocityInitializer", true ); + pVelocity->SetPtr( "entindex", (void*)entindex() ); + pVelocity->SetFloat( "minAttachmentSpeed", m_MinDirectedSpeed ); + pVelocity->SetFloat( "maxAttachmentSpeed", m_MaxDirectedSpeed ); + pVelocity->SetFloat( "minRandomSpeed", m_MinSpeed ); + pVelocity->SetFloat( "maxRandomSpeed", m_MaxSpeed ); + + KeyValues *pRoll = pInitializers->FindKey( "DmeRandomRollInitializer", true ); + pRoll->SetFloat( "minRoll", 0.0f ); + pRoll->SetFloat( "maxRoll", 360.0f ); + + KeyValues *pRollSpeed = pInitializers->FindKey( "DmeRandomRollSpeedInitializer", true ); + pRollSpeed->SetFloat( "minRollSpeed", -1.0f ); + pRollSpeed->SetFloat( "maxRollSpeed", 1.0f ); + + KeyValues *pColor = pInitializers->FindKey( "DmeRandomValueColorInitializer", true ); + Color c( + clamp( m_StartColor.x * 255.0f, 0, 255 ), + clamp( m_StartColor.y * 255.0f, 0, 255 ), + clamp( m_StartColor.z * 255.0f, 0, 255 ), 255 ); + pColor->SetColor( "startColor", c ); + pColor->SetFloat( "minStartValueDelta", -0.2f ); + pColor->SetFloat( "maxStartValueDelta", 0.2f ); + pColor->SetColor( "endColor", Color( 0, 0, 0, 255 ) ); + + KeyValues *pAlpha = pInitializers->FindKey( "DmeRandomAlphaInitializer", true ); + int nMinAlpha = 255 * m_Opacity * 0.75f; + int nMaxAlpha = 255 * m_Opacity * 1.25f; + pAlpha->SetInt( "minStartAlpha", 0 ); + pAlpha->SetInt( "maxStartAlpha", 0 ); + pAlpha->SetInt( "minEndAlpha", clamp( nMinAlpha, 0, 255 ) ); + pAlpha->SetInt( "maxEndAlpha", clamp( nMaxAlpha, 0, 255 ) ); + + KeyValues *pSize = pInitializers->FindKey( "DmeRandomSizeInitializer", true ); + pSize->SetFloat( "minStartSize", m_StartSize ); + pSize->SetFloat( "maxStartSize", m_StartSize ); + pSize->SetFloat( "minEndSize", m_EndSize ); + pSize->SetFloat( "maxEndSize", m_EndSize ); + + KeyValues *pUpdaters = pEmitter->FindKey( "updaters", true ); + + pUpdaters->FindKey( "DmePositionVelocityUpdater", true ); + pUpdaters->FindKey( "DmeRollUpdater", true ); + + KeyValues *pRollSpeedUpdater = pUpdaters->FindKey( "DmeRollSpeedAttenuateUpdater", true ); + pRollSpeedUpdater->SetFloat( "attenuation", 1.0f - 8.0f / 30.0f ); + pRollSpeedUpdater->SetFloat( "attenuationTme", 1.0f / 30.0f ); + pRollSpeedUpdater->SetFloat( "minRollSpeed", 0.5f ); + + pUpdaters->FindKey( "DmeAlphaSineUpdater", true ); + pUpdaters->FindKey( "DmeColorUpdater", true ); + pUpdaters->FindKey( "DmeSizeUpdater", true ); + + KeyValues *pEmitter2 = pEmitter->MakeCopy(); + pEmitter2->SetString( "material", "particle/particle_noisesphere" ); + pEmitterParent2->AddSubKey( pEmitter2 ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } + else + { + KeyValues *msg = new KeyValues( "OldParticleSystem_ActivateEmitter" ); + msg->SetInt( "id", m_pSmokeEmitter->GetToolParticleEffectId() ); + msg->SetInt( "emitter", 0 ); + msg->SetInt( "active", bEmitterActive ); + msg->SetFloat( "time", gpGlobals->curtime ); + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//================================================== +// RocketTrail +//================================================== + +// Expose to the particle app. +EXPOSE_PROTOTYPE_EFFECT(RocketTrail, C_RocketTrail); + +// Datatable.. this can have all the smoketrail parameters when we need it to. +IMPLEMENT_CLIENTCLASS_DT(C_RocketTrail, DT_RocketTrail, RocketTrail) + RecvPropFloat(RECVINFO(m_SpawnRate)), + RecvPropVector(RECVINFO(m_StartColor)), + RecvPropVector(RECVINFO(m_EndColor)), + RecvPropFloat(RECVINFO(m_ParticleLifetime)), + RecvPropFloat(RECVINFO(m_StopEmitTime)), + RecvPropFloat(RECVINFO(m_MinSpeed)), + RecvPropFloat(RECVINFO(m_MaxSpeed)), + RecvPropFloat(RECVINFO(m_StartSize)), + RecvPropFloat(RECVINFO(m_EndSize)), + RecvPropFloat(RECVINFO(m_SpawnRadius)), + RecvPropInt(RECVINFO(m_bEmit)), + RecvPropInt(RECVINFO(m_nAttachment)), + RecvPropFloat(RECVINFO(m_Opacity)), + RecvPropInt(RECVINFO(m_bDamaged)), + RecvPropFloat(RECVINFO(m_flFlareScale)), +END_RECV_TABLE() + +// ------------------------------------------------------------------------- // +// ParticleMovieExplosion +// ------------------------------------------------------------------------- // +C_RocketTrail::C_RocketTrail() +{ + m_MaterialHandle[0] = NULL; + m_MaterialHandle[1] = NULL; + + m_SpawnRate = 10; + m_ParticleSpawn.Init(10); + m_StartColor.Init(0.5, 0.5, 0.5); + m_EndColor.Init(0,0,0); + m_ParticleLifetime = 5; + m_StopEmitTime = 0; // No end time + m_MinSpeed = 2; + m_MaxSpeed = 4; + m_StartSize = 35; + m_EndSize = 55; + m_SpawnRadius = 2; + m_VelocityOffset.Init(); + m_Opacity = 0.5f; + + m_bEmit = true; + m_bDamaged = false; + + m_nAttachment = -1; + + m_pRocketEmitter = NULL; + m_pParticleMgr = NULL; +} + +C_RocketTrail::~C_RocketTrail() +{ + if ( m_pParticleMgr ) + { + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_RocketTrail::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + C_BaseEntity *pEnt = pAttachedTo->GetBaseEntity(); + if (pEnt && (m_nAttachment > 0)) + { + pEnt->GetAttachment( m_nAttachment, *pAbsOrigin, *pAbsAngles ); + } + else + { + BaseClass::GetAimEntOrigin( pAttachedTo, pAbsOrigin, pAbsAngles ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bEmit - +//----------------------------------------------------------------------------- +void C_RocketTrail::SetEmit(bool bEmit) +{ + m_bEmit = bEmit; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rate - +//----------------------------------------------------------------------------- +void C_RocketTrail::SetSpawnRate(float rate) +{ + m_SpawnRate = rate; + m_ParticleSpawn.Init(rate); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_RocketTrail::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if ( updateType == DATA_UPDATE_CREATED ) + { + Start( ParticleMgr(), NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_RocketTrail::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + if(!pParticleMgr->AddEffect( &m_ParticleEffect, this )) + return; + + m_pParticleMgr = pParticleMgr; + m_pRocketEmitter = CRocketTrailParticle::Create("smokeTrail"); + if ( !m_pRocketEmitter ) + { + Assert( false ); + return; + } + + m_pRocketEmitter->SetSortOrigin( GetAbsOrigin() ); + m_pRocketEmitter->SetNearClip( 64.0f, 128.0f ); + + m_MaterialHandle[0] = g_Mat_DustPuff[0]; + m_MaterialHandle[1] = g_Mat_DustPuff[1]; + + m_ParticleSpawn.Init( m_SpawnRate ); + + m_vecLastPosition = GetAbsOrigin(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_RocketTrail::Update( float fTimeDelta ) +{ + if ( !m_pRocketEmitter ) + return; + + if ( gpGlobals->frametime == 0.0f ) + return; + + CSmartPtr pSimple = CSimpleEmitter::Create( "MuzzleFlash" ); + pSimple->SetSortOrigin( GetAbsOrigin() ); + + SimpleParticle *pParticle; + Vector forward, offset; + + AngleVectors( GetAbsAngles(), &forward ); + + forward.Negate(); + + float flScale = random->RandomFloat( m_flFlareScale-0.5f, m_flFlareScale+0.5f ); + + // + // Flash + // + + int i; + + for ( i = 1; i < 9; i++ ) + { + offset = GetAbsOrigin() + (forward * (i*2.0f*m_flFlareScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/muzzleflash%d", random->RandomInt(1,4) ) ), offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.01f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 128; + + pParticle->m_uchStartSize = (random->RandomFloat( 5.0f, 6.0f ) * (12-(i))/9) * flScale; + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + // Add new particles (undamaged version) + if ( m_bEmit ) + { + Vector moveDiff = GetAbsOrigin() - m_vecLastPosition; + float moveLength = VectorNormalize( moveDiff ); + + int numPuffs = moveLength / ( m_StartSize / 2.0f ); + + //debugoverlay->AddLineOverlay( m_vecLastPosition, GetAbsOrigin(), 255, 0, 0, true, 2.0f ); + + //FIXME: More rational cap here, perhaps + if ( numPuffs > 50 ) + numPuffs = 50; + + Vector offsetColor; + float step = moveLength / numPuffs; + + //Fill in the gaps + for ( i = 1; i < numPuffs+1; i++ ) + { + offset = m_vecLastPosition + ( moveDiff * step * i ); + + //debugoverlay->AddBoxOverlay( offset, -Vector(2,2,2), Vector(2,2,2), vec3_angle, i*4, i*4, i*4, true, 4.0f ); + + pParticle = (SimpleParticle *) m_pRocketEmitter->AddParticle( sizeof( SimpleParticle ), m_MaterialHandle[random->RandomInt(0,1)], offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = m_ParticleLifetime + random->RandomFloat(m_ParticleLifetime*0.9f,m_ParticleLifetime*1.1f); + + pParticle->m_vecVelocity.Random( -1.0f, 1.0f ); + pParticle->m_vecVelocity *= random->RandomFloat( m_MinSpeed, m_MaxSpeed ); + + offsetColor = m_StartColor * random->RandomFloat( 0.75f, 1.25f ); + + offsetColor[0] = clamp( offsetColor[0], 0.0f, 1.0f ); + offsetColor[1] = clamp( offsetColor[1], 0.0f, 1.0f ); + offsetColor[2] = clamp( offsetColor[2], 0.0f, 1.0f ); + + pParticle->m_uchColor[0] = offsetColor[0]*255.0f; + pParticle->m_uchColor[1] = offsetColor[1]*255.0f; + pParticle->m_uchColor[2] = offsetColor[2]*255.0f; + + pParticle->m_uchStartSize = m_StartSize * random->RandomFloat( 0.75f, 1.25f ); + pParticle->m_uchEndSize = m_EndSize * random->RandomFloat( 1.0f, 1.25f ); + + float alpha = random->RandomFloat( m_Opacity*0.75f, m_Opacity*1.25f ); + + if ( alpha > 1.0f ) + alpha = 1.0f; + if ( alpha < 0.0f ) + alpha = 0.0f; + + pParticle->m_uchStartAlpha = alpha * 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + } + } + } + + if ( m_bDamaged ) + { + SimpleParticle *pParticle; + Vector offset; + Vector offsetColor; + + CSmartPtr pEmitter = CEmberEffect::Create("C_RocketTrail::damaged"); + + pEmitter->SetSortOrigin( GetAbsOrigin() ); + + PMaterialHandle flameMaterial = m_pRocketEmitter->GetPMaterial( VarArgs( "sprites/flamelet%d", random->RandomInt( 1, 4 ) ) ); + + // Flames from the rocket + for ( i = 0; i < 8; i++ ) + { + offset = RandomVector( -8, 8 ) + GetAbsOrigin(); + + pParticle = (SimpleParticle *) pEmitter->AddParticle( sizeof( SimpleParticle ), flameMaterial, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.25f; + + pParticle->m_vecVelocity.Random( -1.0f, 1.0f ); + pParticle->m_vecVelocity *= random->RandomFloat( 32, 128 ); + + offsetColor = m_StartColor * random->RandomFloat( 0.75f, 1.25f ); + + offsetColor[0] = clamp( offsetColor[0], 0.0f, 1.0f ); + offsetColor[1] = clamp( offsetColor[1], 0.0f, 1.0f ); + offsetColor[2] = clamp( offsetColor[2], 0.0f, 1.0f ); + + pParticle->m_uchColor[0] = offsetColor[0]*255.0f; + pParticle->m_uchColor[1] = offsetColor[1]*255.0f; + pParticle->m_uchColor[2] = offsetColor[2]*255.0f; + + pParticle->m_uchStartSize = 8.0f; + pParticle->m_uchEndSize = 32.0f; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + } + } + } + + m_vecLastPosition = GetAbsOrigin(); +} + +void C_RocketTrail::RenderParticles( CParticleRenderIterator *pIterator ) +{ +} + +void C_RocketTrail::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ +} + +SporeEffect::SporeEffect( const char *pDebugName ) : CSimpleEmitter( pDebugName ) +{ +} + + +SporeEffect* SporeEffect::Create( const char *pDebugName ) +{ + return new SporeEffect( pDebugName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +// Output : Vector +//----------------------------------------------------------------------------- +void SporeEffect::UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) +{ + float speed = VectorNormalize( pParticle->m_vecVelocity ); + Vector offset; + + speed -= ( 64.0f * timeDelta ); + + offset.Random( -0.5f, 0.5f ); + + pParticle->m_vecVelocity += offset; + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= speed; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// timeDelta - +//----------------------------------------------------------------------------- +Vector SporeEffect::UpdateColor( const SimpleParticle *pParticle ) +{ + Vector color; + float ramp = ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) );//1.0f - ( pParticle->m_flLifetime / pParticle->m_flDieTime ); + + color[0] = ( (float) pParticle->m_uchColor[0] * ramp ) / 255.0f; + color[1] = ( (float) pParticle->m_uchColor[1] * ramp ) / 255.0f; + color[2] = ( (float) pParticle->m_uchColor[2] * ramp ) / 255.0f; + + return color; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// timeDelta - +// Output : float +//----------------------------------------------------------------------------- +float SporeEffect::UpdateAlpha( const SimpleParticle *pParticle ) +{ + return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) ); +} + +//================================================== +// C_SporeExplosion +//================================================== + +EXPOSE_PROTOTYPE_EFFECT( SporeExplosion, C_SporeExplosion ); + +IMPLEMENT_CLIENTCLASS_DT( C_SporeExplosion, DT_SporeExplosion, SporeExplosion ) + RecvPropFloat(RECVINFO(m_flSpawnRate)), + RecvPropFloat(RECVINFO(m_flParticleLifetime)), + RecvPropFloat(RECVINFO(m_flStartSize)), + RecvPropFloat(RECVINFO(m_flEndSize)), + RecvPropFloat(RECVINFO(m_flSpawnRadius)), + RecvPropBool(RECVINFO(m_bEmit)), + RecvPropBool(RECVINFO(m_bDontRemove)), +END_RECV_TABLE() + +C_SporeExplosion::C_SporeExplosion( void ) +{ + m_pParticleMgr = NULL; + + m_flSpawnRate = 32; + m_flParticleLifetime = 5; + m_flStartSize = 32; + m_flEndSize = 64; + m_flSpawnRadius = 32; + m_pSporeEffect = NULL; + + m_teParticleSpawn.Init( 32 ); + + m_bEmit = true; + m_bDontRemove = false; +} + +C_SporeExplosion::~C_SporeExplosion() +{ + if ( m_pParticleMgr != NULL ) + { + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_SporeExplosion::OnDataChanged( DataUpdateType_t updateType ) +{ + C_BaseEntity::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + m_flPreviousSpawnRate = m_flSpawnRate; + m_teParticleSpawn.Init( m_flSpawnRate ); + Start( ParticleMgr(), NULL ); + } + else if( m_bEmit ) + { + // Just been turned on by the server. + m_flPreviousSpawnRate = m_flSpawnRate; + m_teParticleSpawn.Init( m_flSpawnRate ); + } + + m_pSporeEffect->SetDontRemove( m_bDontRemove ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_SporeExplosion::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + //Add us into the effect manager + if( pParticleMgr->AddEffect( &m_ParticleEffect, this ) == false ) + return; + + //Create our main effect + m_pSporeEffect = SporeEffect::Create( "C_SporeExplosion" ); + + if ( m_pSporeEffect == NULL ) + return; + + m_hMaterial = m_pSporeEffect->GetPMaterial( "particle/fire" ); + + m_pSporeEffect->SetSortOrigin( GetAbsOrigin() ); + m_pSporeEffect->SetNearClip( 64, 128 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SporeExplosion::AddParticles( void ) +{ + //Spores + Vector offset; + Vector dir; + + //Get our direction + AngleVectors( GetAbsAngles(), &dir ); + + SimpleParticle *sParticle; + + for ( int i = 0; i < 4; i++ ) + { + //Add small particle to the effect's origin + offset.Random( -m_flSpawnRadius, m_flSpawnRadius ); + sParticle = (SimpleParticle *) m_pSporeEffect->AddParticle( sizeof(SimpleParticle), m_hMaterial, GetAbsOrigin()+offset ); + + if ( sParticle == NULL ) + return; + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 2.0f; + + sParticle->m_flRoll = 0; + sParticle->m_flRollDelta = 0; + + sParticle->m_uchColor[0] = 225; + sParticle->m_uchColor[1] = 140; + sParticle->m_uchColor[2] = 64; + sParticle->m_uchStartAlpha = Helper_RandomInt( 128, 255 ); + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = Helper_RandomInt( 1, 2 ); + sParticle->m_uchEndSize = 1; + + sParticle->m_vecVelocity = dir * Helper_RandomFloat( 128.0f, 256.0f ); + } + + //Add smokey bits + offset.Random( -(m_flSpawnRadius * 0.5), (m_flSpawnRadius * 0.5) ); + sParticle = (SimpleParticle *) m_pSporeEffect->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[1], GetAbsOrigin()+offset ); + + if ( sParticle == NULL ) + return; + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 1.0f; + + sParticle->m_flRoll = Helper_RandomFloat( 0, 360 ); + sParticle->m_flRollDelta = Helper_RandomFloat( -2.0f, 2.0f ); + + sParticle->m_uchColor[0] = 225; + sParticle->m_uchColor[1] = 140; + sParticle->m_uchColor[2] = 64; + sParticle->m_uchStartAlpha = Helper_RandomInt( 32, 64 ); + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = m_flStartSize; + sParticle->m_uchEndSize = m_flEndSize; + + sParticle->m_vecVelocity = dir * Helper_RandomFloat( 64.0f, 128.0f ); +} + + +ConVar cl_sporeclipdistance( "cl_sporeclipdistance", "512", FCVAR_CHEAT | FCVAR_CLIENTDLL ); +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_SporeExplosion::Update( float fTimeDelta ) +{ + if( m_bEmit ) + { + float tempDelta = fTimeDelta; + + float flDist = (MainViewOrigin() - GetAbsOrigin()).Length(); + + //Lower the spawnrate by half if we're far away from it. + if ( cl_sporeclipdistance.GetFloat() <= flDist ) + { + if ( m_flSpawnRate == m_flPreviousSpawnRate ) + { + m_flPreviousSpawnRate = m_flSpawnRate * 0.5f; + m_teParticleSpawn.ResetRate( m_flPreviousSpawnRate ); + } + } + else + { + if ( m_flSpawnRate != m_flPreviousSpawnRate ) + { + m_flPreviousSpawnRate = m_flSpawnRate; + m_teParticleSpawn.ResetRate( m_flPreviousSpawnRate ); + } + } + + while ( m_teParticleSpawn.NextEvent( tempDelta ) ) + { + AddParticles(); + } + } +} + + +void C_SporeExplosion::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + StandardParticle_t *pParticle = (StandardParticle_t*)pIterator->GetFirst(); + while ( pParticle ) + { + pParticle->m_Lifetime += pIterator->GetTimeDelta(); + + if( pParticle->m_Lifetime > m_flParticleLifetime ) + { + pIterator->RemoveParticle( pParticle ); + } + + pParticle = (StandardParticle_t*)pIterator->GetNext(); + } +} + +void C_SporeExplosion::RenderParticles( CParticleRenderIterator *pIterator ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RPGShotDownCallback( const CEffectData &data ) +{ + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "Missile.ShotDown", &data.m_vOrigin ); + + if ( CExplosionOverlay *pOverlay = new CExplosionOverlay ) + { + pOverlay->m_flLifetime = 0; + pOverlay->m_vPos = data.m_vOrigin; + pOverlay->m_nSprites = 1; + + pOverlay->m_vBaseColors[0].Init( 1.0f, 0.9f, 0.7f ); + + pOverlay->m_Sprites[0].m_flHorzSize = 0.01f; + pOverlay->m_Sprites[0].m_flVertSize = pOverlay->m_Sprites[0].m_flHorzSize*0.5f; + + pOverlay->Activate(); + } +} + +DECLARE_CLIENT_EFFECT( "RPGShotDown", RPGShotDownCallback ); + + + +//================================================== +// C_SporeTrail +//================================================== + +class C_SporeTrail : public C_BaseParticleEntity +{ +public: + DECLARE_CLASS( C_SporeTrail, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_SporeTrail( void ); + virtual ~C_SporeTrail( void ); + +public: + void SetEmit( bool bEmit ); + + +// C_BaseEntity +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ); + +// IPrototypeAppEffect +public: + virtual void Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ); + +// IParticleEffect +public: + virtual void Update( float fTimeDelta ); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + virtual void StartRender( VMatrix &effectMatrix ); + +public: + Vector m_vecEndColor; + + float m_flSpawnRate; + float m_flParticleLifetime; + float m_flStartSize; + float m_flEndSize; + float m_flSpawnRadius; + + Vector m_vecVelocityOffset; + + bool m_bEmit; + +private: + C_SporeTrail( const C_SporeTrail & ); + + void AddParticles( void ); + + PMaterialHandle m_hMaterial; + TimedEvent m_teParticleSpawn; + //CSmartPtr m_pSmokeEffect; + + Vector m_vecPos; + Vector m_vecLastPos; // This is stored so we can spawn particles in between the previous and new position + // to eliminate holes in the trail. + + VMatrix m_mAttachmentMatrix; + CParticleMgr *m_pParticleMgr; +}; + + + +//================================================== +// C_SporeTrail +//================================================== + +IMPLEMENT_CLIENTCLASS_DT( C_SporeTrail, DT_SporeTrail, SporeTrail ) + RecvPropFloat(RECVINFO(m_flSpawnRate)), + RecvPropVector(RECVINFO(m_vecEndColor)), + RecvPropFloat(RECVINFO(m_flParticleLifetime)), + RecvPropFloat(RECVINFO(m_flStartSize)), + RecvPropFloat(RECVINFO(m_flEndSize)), + RecvPropFloat(RECVINFO(m_flSpawnRadius)), + RecvPropInt(RECVINFO(m_bEmit)), +END_RECV_TABLE() + +C_SporeTrail::C_SporeTrail( void ) +{ + m_pParticleMgr = NULL; + //m_pSmokeEffect = SporeSmokeEffect::Create( "C_SporeTrail" ); + + m_flSpawnRate = 10; + m_flParticleLifetime = 5; + m_flStartSize = 35; + m_flEndSize = 55; + m_flSpawnRadius = 2; + + m_teParticleSpawn.Init( 5 ); + m_vecEndColor.Init(); + m_vecPos.Init(); + m_vecLastPos.Init(); + m_vecVelocityOffset.Init(); + + m_bEmit = true; +} + +C_SporeTrail::~C_SporeTrail() +{ + if( m_pParticleMgr ) + { + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bEmit - +//----------------------------------------------------------------------------- +void C_SporeTrail::SetEmit( bool bEmit ) +{ + m_bEmit = bEmit; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_SporeTrail::OnDataChanged( DataUpdateType_t updateType ) +{ + C_BaseEntity::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + Start( ParticleMgr(), NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_SporeTrail::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + if( pParticleMgr->AddEffect( &m_ParticleEffect, this ) == false ) + return; + + m_hMaterial = g_Mat_DustPuff[1]; + m_pParticleMgr = pParticleMgr; + m_teParticleSpawn.Init( 64 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SporeTrail::AddParticles( void ) +{ + Vector offset = RandomVector( -4.0f, 4.0f ); + + //Make a new particle + SimpleParticle *sParticle = (SimpleParticle *) m_ParticleEffect.AddParticle( sizeof(SimpleParticle), m_hMaterial );//m_pSmokeEffect->AddParticle( sizeof(SimpleParticle), m_hMaterial, GetAbsOrigin()+offset ); + + if ( sParticle == NULL ) + return; + + sParticle->m_Pos = offset; + sParticle->m_flRoll = Helper_RandomInt( 0, 360 ); + sParticle->m_flRollDelta = Helper_RandomFloat( -2.0f, 2.0f ); + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 0.5f; + + sParticle->m_uchColor[0] = 225; + sParticle->m_uchColor[1] = 140; + sParticle->m_uchColor[2] = 64; + sParticle->m_uchStartAlpha = Helper_RandomInt( 64, 128 ); + sParticle->m_uchEndAlpha = 0; + + sParticle->m_uchStartSize = 1.0f; + sParticle->m_uchEndSize = 1.0f; + + sParticle->m_vecVelocity = RandomVector( -8.0f, 8.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_SporeTrail::Update( float fTimeDelta ) +{ + if ( m_pParticleMgr == NULL ) + return; + + //Add new particles + if ( m_bEmit ) + { + float tempDelta = fTimeDelta; + + while ( m_teParticleSpawn.NextEvent( tempDelta ) ) + { + AddParticles(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &effectMatrix - +//----------------------------------------------------------------------------- +void C_SporeTrail::StartRender( VMatrix &effectMatrix ) +{ + effectMatrix = effectMatrix * m_mAttachmentMatrix; +} + +void C_SporeTrail::RenderParticles( CParticleRenderIterator *pIterator ) +{ + if ( m_bEmit == false ) + return; + + const SimpleParticle *pParticle = (const SimpleParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + //Render + Vector tPos; + TransformParticle( m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos ); + float sortKey = tPos.z; + + Vector color = Vector( 1.0f, 1.0f, 1.0f ); + + //Render it + RenderParticle_ColorSize( + pIterator->GetParticleDraw(), + tPos, + color, + 1.0f, + 4 ); + + pParticle = (const SimpleParticle*)pIterator->GetNext( sortKey ); + } +} + + +void C_SporeTrail::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + if ( m_bEmit == false ) + return; + + SimpleParticle *pParticle = (SimpleParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + //UpdateVelocity( pParticle, timeDelta ); + pParticle->m_Pos += pParticle->m_vecVelocity * pIterator->GetTimeDelta(); + + //Should this particle die? + pParticle->m_flLifetime += pIterator->GetTimeDelta(); + + if ( pParticle->m_flLifetime >= pParticle->m_flDieTime ) + { + pIterator->RemoveParticle( pParticle ); + } + + pParticle = (SimpleParticle*)pIterator->GetNext(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_SporeTrail::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + C_BaseEntity *pEnt = pAttachedTo->GetBaseEntity(); + + pEnt->GetAttachment( 1, *pAbsOrigin, *pAbsAngles ); + + matrix3x4_t matrix; + + AngleMatrix( *pAbsAngles, *pAbsOrigin, matrix ); + + m_mAttachmentMatrix = matrix; +} + +//================================================== +// FireTrailhou +//================================================== + +// Datatable.. this can have all the smoketrail parameters when we need it to. +IMPLEMENT_CLIENTCLASS_DT(C_FireTrail, DT_FireTrail, CFireTrail) + RecvPropInt(RECVINFO(m_nAttachment)), + RecvPropFloat(RECVINFO(m_flLifetime)), +END_RECV_TABLE() + +// ------------------------------------------------------------------------- // +// ParticleMovieExplosion +// ------------------------------------------------------------------------- // +C_FireTrail::C_FireTrail() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_FireTrail::~C_FireTrail( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_FireTrail::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + BaseClass::Start( pParticleMgr, pArgs ); + + m_pTrailEmitter = CSimpleEmitter::Create( "FireTrail" ); + + if ( !m_pTrailEmitter ) + { + Assert( false ); + return; + } + + m_pTrailEmitter->SetSortOrigin( GetAbsOrigin() ); + + // Setup our materials + m_hMaterial[FTRAIL_SMOKE1] = g_Mat_DustPuff[0]; + m_hMaterial[FTRAIL_SMOKE2] = g_Mat_DustPuff[1]; + + m_hMaterial[FTRAIL_FLAME1] = m_pTrailEmitter->GetPMaterial( "sprites/flamelet1" ); + m_hMaterial[FTRAIL_FLAME2] = m_pTrailEmitter->GetPMaterial( "sprites/flamelet2" ); + m_hMaterial[FTRAIL_FLAME3] = m_pTrailEmitter->GetPMaterial( "sprites/flamelet3" ); + m_hMaterial[FTRAIL_FLAME4] = m_pTrailEmitter->GetPMaterial( "sprites/flamelet4" ); + m_hMaterial[FTRAIL_FLAME5] = m_pTrailEmitter->GetPMaterial( "sprites/flamelet5" ); + + // Setup our smoke emitter + m_pSmokeEmitter = CSmokeParticle::Create( "FireTrail_Smoke" ); + + m_pSmokeEmitter->SetSortOrigin( GetAbsOrigin() ); + m_pSmokeEmitter->SetNearClip( 64.0f, 128.0f ); + + if ( !m_pSmokeEmitter ) + { + Assert( false ); + return; + } + + // Seed our first position as the last known one + m_vecLastPosition = GetAbsOrigin(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_FireTrail::Update( float fTimeDelta ) +{ + if ( !m_pTrailEmitter ) + return; + + if ( ( m_flLifetime != 0 ) && ( m_flLifetime <= gpGlobals->curtime ) ) + return; + + CSmartPtr pSimple = CSimpleEmitter::Create( "FireTrail" ); + pSimple->SetSortOrigin( GetAbsOrigin() ); + + Vector offset; + +#define STARTSIZE 8 +#define ENDSIZE 16 +#define PARTICLE_LIFETIME 0.075f +#define MIN_SPEED 32 +#define MAX_SPEED 64 + + // Add new particles + //if ( ShouldEmit() ) + { + Vector moveDiff = GetAbsOrigin() - m_vecLastPosition; + float moveLength = VectorNormalize( moveDiff ); + + int numPuffs = moveLength / ( STARTSIZE / 2.0f ); + + //FIXME: More rational cap here, perhaps + numPuffs = clamp( numPuffs, 1, 32 ); + + SimpleParticle *pParticle; + Vector offset; + Vector offsetColor; + float step = moveLength / numPuffs; + + //Fill in the gaps + for ( int i = 1; i < numPuffs+1; i++ ) + { + offset = m_vecLastPosition + ( moveDiff * step * i ) + RandomVector( -4.0f, 4.0f ); + + //debugoverlay->AddBoxOverlay( offset, -Vector(2,2,2), Vector(2,2,2), vec3_angle, i*4, i*4, i*4, true, 1.0f ); + + pParticle = (SimpleParticle *) m_pSmokeEmitter->AddParticle( sizeof( SimpleParticle ), m_hMaterial[random->RandomInt( FTRAIL_FLAME1,FTRAIL_FLAME5 )], offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = /*PARTICLE_LIFETIME*/ 0.5f;// + random->RandomFloat(PARTICLE_LIFETIME*0.75f, PARTICLE_LIFETIME*1.25f); + + pParticle->m_vecVelocity.Random( 0.0f, 1.0f ); + pParticle->m_vecVelocity *= random->RandomFloat( MIN_SPEED, MAX_SPEED ); + pParticle->m_vecVelocity[2] += 50;//random->RandomFloat( 32, 64 ); + + pParticle->m_uchColor[0] = 255.0f; + pParticle->m_uchColor[1] = 255.0f; + pParticle->m_uchColor[2] = 255.0f; + + pParticle->m_uchStartSize = STARTSIZE * 2.0f; + pParticle->m_uchEndSize = STARTSIZE * 0.5f; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = 0.0f;//random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -16.0f, 16.0f ); + } + } + + // + // Smoke + // + + offset = RandomVector( -STARTSIZE*0.5f, STARTSIZE*0.5f ) + GetAbsOrigin(); + + pParticle = (SimpleParticle *) m_pSmokeEmitter->AddParticle( sizeof( SimpleParticle ), m_hMaterial[random->RandomInt( FTRAIL_SMOKE1, FTRAIL_SMOKE2 )], offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = ( PARTICLE_LIFETIME * 10.0f ) + random->RandomFloat(PARTICLE_LIFETIME*0.75f, PARTICLE_LIFETIME*1.25f); + + pParticle->m_vecVelocity.Random( 0.0f, 1.0f ); + pParticle->m_vecVelocity *= random->RandomFloat( MIN_SPEED, MAX_SPEED ); + pParticle->m_vecVelocity[2] += random->RandomFloat( 50, 100 ); + + pParticle->m_uchColor[0] = 255.0f * 0.5f; + pParticle->m_uchColor[1] = 245.0f * 0.5f; + pParticle->m_uchColor[2] = 205.0f * 0.5f; + + pParticle->m_uchStartSize = 16 * random->RandomFloat( 0.75f, 1.25f ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2.5f; + + pParticle->m_uchStartAlpha = 64; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -16.0f, 16.0f ); + } + } + + // Save off this position + m_vecLastPosition = GetAbsOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: High drag, non color changing particle +//----------------------------------------------------------------------------- + + +class CDustFollower : public CSimpleEmitter +{ +public: + + CDustFollower( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + //Create + static CDustFollower *Create( const char *pDebugName ) + { + return new CDustFollower( pDebugName ); + } + + //Alpha + virtual float UpdateAlpha( const SimpleParticle *pParticle ) + { + return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) ); + } + + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_vecVelocity = pParticle->m_vecVelocity * ExponentialDecay( 0.3, timeDelta ); + } + + //Roll + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta *= ExponentialDecay( 0.5, timeDelta ); + + return pParticle->m_flRoll; + } + +private: + CDustFollower( const CDustFollower & ); +}; + + +// Datatable.. this can have all the smoketrail parameters when we need it to. +IMPLEMENT_CLIENTCLASS_DT(C_DustTrail, DT_DustTrail, DustTrail) + RecvPropFloat(RECVINFO(m_SpawnRate)), + RecvPropVector(RECVINFO(m_Color)), + RecvPropFloat(RECVINFO(m_ParticleLifetime)), + RecvPropFloat(RECVINFO(m_StopEmitTime)), + RecvPropFloat(RECVINFO(m_MinSpeed)), + RecvPropFloat(RECVINFO(m_MaxSpeed)), + RecvPropFloat(RECVINFO(m_MinDirectedSpeed)), + RecvPropFloat(RECVINFO(m_MaxDirectedSpeed)), + RecvPropFloat(RECVINFO(m_StartSize)), + RecvPropFloat(RECVINFO(m_EndSize)), + RecvPropFloat(RECVINFO(m_SpawnRadius)), + RecvPropInt(RECVINFO(m_bEmit)), + RecvPropFloat(RECVINFO(m_Opacity)), +END_RECV_TABLE() + + +// ------------------------------------------------------------------------- // +// ParticleMovieExplosion +// ------------------------------------------------------------------------- // +C_DustTrail::C_DustTrail() +{ + for (int i = 0; i < DUSTTRAIL_MATERIALS; i++) + { + m_MaterialHandle[i] = NULL; + } + + m_SpawnRate = 10; + m_ParticleSpawn.Init(10); + m_Color.Init(0.5, 0.5, 0.5); + m_ParticleLifetime = 5; + m_StartEmitTime = gpGlobals->curtime; + m_StopEmitTime = 0; // No end time + m_MinSpeed = 2; + m_MaxSpeed = 4; + m_MinDirectedSpeed = m_MaxDirectedSpeed = 0; + m_StartSize = 35; + m_EndSize = 55; + m_SpawnRadius = 2; + m_VelocityOffset.Init(); + m_Opacity = 0.5f; + + m_bEmit = true; + + m_pDustEmitter = NULL; + m_pParticleMgr = NULL; +} + +C_DustTrail::~C_DustTrail() +{ + if ( ToolsEnabled() && clienttools->IsInRecordingMode() && m_pDustEmitter.IsValid() && m_pDustEmitter->GetToolParticleEffectId() != TOOLPARTICLESYSTEMID_INVALID ) + { + KeyValues *msg = new KeyValues( "OldParticleSystem_ActivateEmitter" ); + msg->SetInt( "id", m_pDustEmitter->GetToolParticleEffectId() ); + msg->SetInt( "emitter", 0 ); + msg->SetInt( "active", false ); + msg->SetFloat( "time", gpGlobals->curtime ); + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } + + if ( m_pParticleMgr ) + { + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bEmit - +//----------------------------------------------------------------------------- +void C_DustTrail::SetEmit(bool bEmit) +{ + m_bEmit = bEmit; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rate - +//----------------------------------------------------------------------------- +void C_DustTrail::SetSpawnRate(float rate) +{ + m_SpawnRate = rate; + m_ParticleSpawn.Init(rate); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_DustTrail::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if ( updateType == DATA_UPDATE_CREATED ) + { + Start( ParticleMgr(), NULL ); + } +} + + +// FIXME: These all have to be moved out of this old system and into the new to leverage art assets! +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectDusttrail ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0001" ) +/* +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0002" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0003" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0004" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0005" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0006" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0007" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0008" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0009" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0010" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0011" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0012" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0013" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0014" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0015" ) +CLIENTEFFECT_MATERIAL( "particle/smokesprites_0016" ) +*/ +CLIENTEFFECT_REGISTER_END() + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_DustTrail::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + if(!pParticleMgr->AddEffect( &m_ParticleEffect, this )) + return; + + m_pParticleMgr = pParticleMgr; + m_pDustEmitter = CDustFollower::Create("DustTrail"); + + if ( !m_pDustEmitter ) + { + Assert( false ); + return; + } + + m_pDustEmitter->SetSortOrigin( GetAbsOrigin() ); + m_pDustEmitter->SetNearClip( 64.0f, 128.0f ); + + for (int i = 0; i < DUSTTRAIL_MATERIALS; i++) + { + //char name[256]; + //Q_snprintf( name, sizeof( name ), "particle/smokesprites_%04d", i + 1 ); + m_MaterialHandle[i] = m_pDustEmitter->GetPMaterial( "particle/smokesprites_0001" ); + } + + m_ParticleSpawn.Init( m_SpawnRate ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_DustTrail::Update( float fTimeDelta ) +{ + if ( !m_pDustEmitter ) + return; + + Vector offsetColor; + + // Add new particles + if ( !m_bEmit ) + return; + + if ( ( m_StopEmitTime != 0 ) && ( m_StopEmitTime <= gpGlobals->curtime ) ) + return; + + float tempDelta = fTimeDelta; + + SimpleParticle *pParticle; + Vector offset; + + Vector vecOrigin; + VectorMA( GetAbsOrigin(), -fTimeDelta, GetAbsVelocity(), vecOrigin ); + + Vector vecForward; + GetVectors( &vecForward, NULL, NULL ); + + while( m_ParticleSpawn.NextEvent( tempDelta ) ) + { + float fldt = fTimeDelta - tempDelta; + + offset.Random( -m_SpawnRadius, m_SpawnRadius ); + offset += vecOrigin; + VectorMA( offset, fldt, GetAbsVelocity(), offset ); + + //if ( random->RandomFloat( 0.f, 5.0f ) > GetAbsVelocity().Length()) + // continue; + + pParticle = (SimpleParticle *) m_pDustEmitter->AddParticle( sizeof( SimpleParticle ), m_MaterialHandle[random->RandomInt(0,0)], offset ); // FIXME: the other sprites look bad + + if ( pParticle == NULL ) + continue; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = m_ParticleLifetime; + + pParticle->m_vecVelocity.Random( -1.0f, 1.0f ); + pParticle->m_vecVelocity *= random->RandomFloat( m_MinSpeed, m_MaxSpeed ); + + pParticle->m_vecVelocity = pParticle->m_vecVelocity + GetAbsVelocity(); + + float flDirectedVel = random->RandomFloat( m_MinDirectedSpeed, m_MaxDirectedSpeed ); + VectorMA( pParticle->m_vecVelocity, flDirectedVel, vecForward, pParticle->m_vecVelocity ); + + offsetColor = m_Color; + float flMaxVal = max( m_Color[0], m_Color[1] ); + if ( flMaxVal < m_Color[2] ) + { + flMaxVal = m_Color[2]; + } + offsetColor /= flMaxVal; + + offsetColor *= random->RandomFloat( -0.2f, 0.2f ); + offsetColor += m_Color; + + offsetColor[0] = clamp( offsetColor[0], 0.0f, 1.0f ); + offsetColor[1] = clamp( offsetColor[1], 0.0f, 1.0f ); + offsetColor[2] = clamp( offsetColor[2], 0.0f, 1.0f ); + + pParticle->m_uchColor[0] = offsetColor[0]*255.0f; + pParticle->m_uchColor[1] = offsetColor[1]*255.0f; + pParticle->m_uchColor[2] = offsetColor[2]*255.0f; + + pParticle->m_uchStartSize = m_StartSize; + pParticle->m_uchEndSize = m_EndSize; + + float alpha = random->RandomFloat( m_Opacity*0.75f, m_Opacity*1.25f ); + alpha = clamp( alpha, 0.0f, 1.0f ); + + if ( m_StopEmitTime != 0 && m_StopEmitTime > m_StartEmitTime ) + { + alpha *= sqrt( (m_StopEmitTime - gpGlobals->curtime) /(m_StopEmitTime - m_StartEmitTime) ); + } + + pParticle->m_uchStartAlpha = alpha * 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1.0f, 1.0f ); + } +} + + +void C_DustTrail::RenderParticles( CParticleRenderIterator *pIterator ) +{ +} + + +void C_DustTrail::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ +} + + +//----------------------------------------------------------------------------- +// This is called after sending this entity's recording state +//----------------------------------------------------------------------------- + +void C_DustTrail::CleanupToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + BaseClass::CleanupToolRecordingState( msg ); + + // Generally, this is used to allow the entity to clean up + // allocated state it put into the message, but here we're going + // to use it to send particle system messages because we + // know the grenade has been recorded at this point + if ( !clienttools->IsInRecordingMode() || !m_pDustEmitter.IsValid() ) + return; + + // For now, we can't record Dusttrails that don't have a moveparent + C_BaseEntity *pEnt = GetMoveParent(); + if ( !pEnt ) + return; + + bool bEmitterActive = m_bEmit && ( ( m_StopEmitTime == 0 ) || ( m_StopEmitTime > gpGlobals->curtime ) ); + + // NOTE: Particle system destruction message will be sent by the particle effect itself. + if ( m_pDustEmitter->GetToolParticleEffectId() == TOOLPARTICLESYSTEMID_INVALID ) + { + int nId = m_pDustEmitter->AllocateToolParticleEffectId(); + + KeyValues *msg = new KeyValues( "OldParticleSystem_Create" ); + msg->SetString( "name", "C_DustTrail" ); + msg->SetInt( "id", nId ); + msg->SetFloat( "time", gpGlobals->curtime ); + + KeyValues *pEmitter = msg->FindKey( "DmeSpriteEmitter", true ); + pEmitter->SetString( "material", "particle/smokesprites_0001" ); + pEmitter->SetInt( "count", m_SpawnRate ); // particles per second, when duration is < 0 + pEmitter->SetFloat( "duration", -1 ); // FIXME + pEmitter->SetInt( "active", bEmitterActive ); + + KeyValues *pInitializers = pEmitter->FindKey( "initializers", true ); + + // FIXME: Until we can interpolate ent logs during emission, this can't work + KeyValues *pPosition = pInitializers->FindKey( "DmePositionPointToEntityInitializer", true ); + pPosition->SetPtr( "entindex", (void*)pEnt->entindex() ); + pPosition->SetInt( "attachmentIndex", GetParentAttachment() ); + pPosition->SetFloat( "randomDist", m_SpawnRadius ); + pPosition->SetFloat( "startx", pEnt->GetAbsOrigin().x ); + pPosition->SetFloat( "starty", pEnt->GetAbsOrigin().y ); + pPosition->SetFloat( "startz", pEnt->GetAbsOrigin().z ); + + KeyValues *pVelocity = pInitializers->FindKey( "DmeDecayVelocityInitializer", true ); + pVelocity->SetFloat( "velocityX", pEnt->GetAbsVelocity().x ); + pVelocity->SetFloat( "velocityY", pEnt->GetAbsVelocity().y ); + pVelocity->SetFloat( "velocityZ", pEnt->GetAbsVelocity().z ); + pVelocity->SetFloat( "decayto", 0.5 ); + pVelocity->SetFloat( "decaytime", 0.3 ); + + KeyValues *pLifetime = pInitializers->FindKey( "DmeRandomLifetimeInitializer", true ); + pLifetime->SetFloat( "minLifetime", m_ParticleLifetime ); + pLifetime->SetFloat( "maxLifetime", m_ParticleLifetime ); + + KeyValues *pRoll = pInitializers->FindKey( "DmeRandomRollInitializer", true ); + pRoll->SetFloat( "minRoll", 0.0f ); + pRoll->SetFloat( "maxRoll", 360.0f ); + + KeyValues *pRollSpeed = pInitializers->FindKey( "DmeRandomRollSpeedInitializer", true ); + pRollSpeed->SetFloat( "minRollSpeed", -1.0f ); + pRollSpeed->SetFloat( "maxRollSpeed", 1.0f ); + + KeyValues *pColor = pInitializers->FindKey( "DmeRandomValueColorInitializer", true ); + Color c( + clamp( m_Color.x * 255.0f, 0, 255 ), + clamp( m_Color.y * 255.0f, 0, 255 ), + clamp( m_Color.z * 255.0f, 0, 255 ), 255 ); + pColor->SetColor( "startColor", c ); + pColor->SetFloat( "minStartValueDelta", 0.0f ); + pColor->SetFloat( "maxStartValueDelta", 0.0f ); + pColor->SetColor( "endColor", c ); + + KeyValues *pAlpha = pInitializers->FindKey( "DmeRandomAlphaInitializer", true ); + int nMinAlpha = 255 * m_Opacity * 0.75f; + int nMaxAlpha = 255 * m_Opacity * 1.25f; + pAlpha->SetInt( "minStartAlpha", clamp( nMinAlpha, 0, 255 ) ); + pAlpha->SetInt( "maxStartAlpha", clamp( nMaxAlpha, 0, 255 ) ); + pAlpha->SetInt( "minEndAlpha", clamp( nMinAlpha, 0, 255 ) ); + pAlpha->SetInt( "maxEndAlpha", clamp( nMaxAlpha, 0, 255 ) ); + + KeyValues *pSize = pInitializers->FindKey( "DmeRandomSizeInitializer", true ); + pSize->SetFloat( "minStartSize", m_StartSize ); + pSize->SetFloat( "maxStartSize", m_StartSize ); + pSize->SetFloat( "minEndSize", m_EndSize ); + pSize->SetFloat( "maxEndSize", m_EndSize ); + + KeyValues *pUpdaters = pEmitter->FindKey( "updaters", true ); + pUpdaters->FindKey( "DmePositionVelocityDecayUpdater", true ); + pUpdaters->FindKey( "DmeRollUpdater", true ); + + KeyValues *pRollSpeedUpdater = pUpdaters->FindKey( "DmeRollSpeedAttenuateUpdater", true ); + pRollSpeedUpdater->SetFloat( "attenuation", 1.0f - 8.0f / 30.0f ); + pRollSpeedUpdater->SetFloat( "attenuationTme", 1.0f / 30.0f ); + pRollSpeedUpdater->SetFloat( "minRollSpeed", 0.5f ); + + pUpdaters->FindKey( "DmeAlphaSineRampUpdater", true ); + pUpdaters->FindKey( "DmeColorUpdater", true ); + pUpdaters->FindKey( "DmeSizeUpdater", true ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } + else + { + KeyValues *msg = new KeyValues( "OldParticleSystem_ActivateEmitter" ); + msg->SetInt( "id", m_pDustEmitter->GetToolParticleEffectId() ); + msg->SetInt( "emitter", 0 ); + msg->SetInt( "active", bEmitterActive ); + msg->SetFloat( "time", gpGlobals->curtime ); + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} diff --git a/game/client/c_smoke_trail.h b/game/client/c_smoke_trail.h new file mode 100644 index 00000000..b710f2c2 --- /dev/null +++ b/game/client/c_smoke_trail.h @@ -0,0 +1,390 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +// This defines the client-side SmokeTrail entity. It can also be used without +// an entity, in which case you must pass calls to it and set its position each frame. + +#ifndef PARTICLE_SMOKETRAIL_H +#define PARTICLE_SMOKETRAIL_H + +#include "particlemgr.h" +#include "particle_prototype.h" +#include "particle_util.h" +#include "particles_simple.h" +#include "c_baseentity.h" +#include "baseparticleentity.h" + +#include "fx_trail.h" + +// +// Smoke Trail +// + +class C_SmokeTrail : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_SmokeTrail, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_SmokeTrail(); + virtual ~C_SmokeTrail(); + +public: + + //For attachments + void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ); + + // Enable/disable emission. + void SetEmit(bool bEmit); + + // Change the spawn rate. + void SetSpawnRate(float rate); + + +// C_BaseEntity. +public: + virtual void OnDataChanged(DataUpdateType_t updateType); + + virtual void CleanupToolRecordingState( KeyValues *msg ); + +// IPrototypeAppEffect. +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + +public: + // Effect parameters. These will assume default values but you can change them. + float m_SpawnRate; // How many particles per second. + + Vector m_StartColor; // Fade between these colors. + Vector m_EndColor; + float m_Opacity; + + float m_ParticleLifetime; // How long do the particles live? + float m_StopEmitTime; // When do I stop emitting particles? (-1 = never) + + float m_MinSpeed; // Speed range. + float m_MaxSpeed; + + float m_MinDirectedSpeed; // Directed speed range. + float m_MaxDirectedSpeed; + + float m_StartSize; // Size ramp. + float m_EndSize; + + float m_SpawnRadius; + + Vector m_VelocityOffset; // Emit the particles in a certain direction. + + bool m_bEmit; // Keep emitting particles? + + int m_nAttachment; + +private: + C_SmokeTrail( const C_SmokeTrail & ); + + PMaterialHandle m_MaterialHandle[2]; + TimedEvent m_ParticleSpawn; + + CParticleMgr *m_pParticleMgr; + CSmartPtr m_pSmokeEmitter; +}; + +//================================================== +// C_RocketTrail +//================================================== + +class C_RocketTrail : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_RocketTrail, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_RocketTrail(); + virtual ~C_RocketTrail(); + +public: + + //For attachments + void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ); + + // Enable/disable emission. + void SetEmit(bool bEmit); + + // Change the spawn rate. + void SetSpawnRate(float rate); + + +// C_BaseEntity. +public: + virtual void OnDataChanged(DataUpdateType_t updateType); + +// IPrototypeAppEffect. +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + +public: + // Effect parameters. These will assume default values but you can change them. + float m_SpawnRate; // How many particles per second. + + Vector m_StartColor; // Fade between these colors. + Vector m_EndColor; + float m_Opacity; + + float m_ParticleLifetime; // How long do the particles live? + float m_StopEmitTime; // When do I stop emitting particles? (-1 = never) + + float m_MinSpeed; // Speed range. + float m_MaxSpeed; + + float m_StartSize; // Size ramp. + float m_EndSize; + + float m_SpawnRadius; + + Vector m_VelocityOffset; // Emit the particles in a certain direction. + + bool m_bEmit; // Keep emitting particles? + bool m_bDamaged; // Has been shot down (should be on fire, etc) + + int m_nAttachment; + + Vector m_vecLastPosition; // Last known position of the rocket + float m_flFlareScale; // Size of the flare + +private: + C_RocketTrail( const C_RocketTrail & ); + + PMaterialHandle m_MaterialHandle[2]; + TimedEvent m_ParticleSpawn; + + CParticleMgr *m_pParticleMgr; + CSmartPtr m_pRocketEmitter; +}; + +class SporeSmokeEffect; + + +//================================================== +// SporeEffect +//================================================== + +class SporeEffect : public CSimpleEmitter +{ +public: + SporeEffect( const char *pDebugName ); + static SporeEffect* Create( const char *pDebugName ); + + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ); + virtual Vector UpdateColor( const SimpleParticle *pParticle ); + virtual float UpdateAlpha( const SimpleParticle *pParticle ); + +private: + SporeEffect( const SporeEffect & ); +}; + +//================================================== +// C_SporeExplosion +//================================================== + +class C_SporeExplosion : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_SporeExplosion, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_SporeExplosion( void ); + virtual ~C_SporeExplosion( void ); + +public: + +// C_BaseEntity +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + +// IPrototypeAppEffect +public: + virtual void Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ); + +// IParticleEffect +public: + virtual void Update( float fTimeDelta ); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + +public: + float m_flSpawnRate; + float m_flParticleLifetime; + float m_flStartSize; + float m_flEndSize; + float m_flSpawnRadius; + float m_flPreviousSpawnRate; + + bool m_bEmit; + bool m_bDontRemove; + +private: + C_SporeExplosion( const C_SporeExplosion & ); + + void AddParticles( void ); + + PMaterialHandle m_hMaterial; + TimedEvent m_teParticleSpawn; + + SporeEffect *m_pSporeEffect; + CParticleMgr *m_pParticleMgr; +}; + +// +// Particle trail +// + +class CSmokeParticle; + +class C_FireTrail : public C_ParticleTrail +{ +public: + DECLARE_CLASS( C_FireTrail, C_ParticleTrail ); + DECLARE_CLIENTCLASS(); + + C_FireTrail( void ); + virtual ~C_FireTrail( void ); + + virtual void Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ); + virtual void Update( float fTimeDelta ); + +private: + + enum + { + // Smoke + FTRAIL_SMOKE1, + FTRAIL_SMOKE2, + + // Large flame + FTRAIL_FLAME1, + FTRAIL_FLAME2, + FTRAIL_FLAME3, + FTRAIL_FLAME4, + FTRAIL_FLAME5, + + NUM_FTRAIL_MATERIALS + }; + + CSmartPtr m_pTrailEmitter; + CSmartPtr m_pSmokeEmitter; + + PMaterialHandle m_hMaterial[NUM_FTRAIL_MATERIALS]; + + Vector m_vecLastPosition; + + C_FireTrail( const C_FireTrail & ); +}; + + + + + + + + + + + + + +//================================================== +// C_DustTrail +//================================================== + +class C_DustTrail : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_DustTrail, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_DustTrail(); + virtual ~C_DustTrail(); + +public: + + // Enable/disable emission. + void SetEmit(bool bEmit); + + // Change the spawn rate. + void SetSpawnRate(float rate); + + +// C_BaseEntity. +public: + virtual void OnDataChanged(DataUpdateType_t updateType); + + virtual void CleanupToolRecordingState( KeyValues *msg ); + +// IPrototypeAppEffect. +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + +public: + // Effect parameters. These will assume default values but you can change them. + float m_SpawnRate; // How many particles per second. + + Vector m_Color; + float m_Opacity; + + float m_ParticleLifetime; // How long do the particles live? + float m_StartEmitTime; // When did I start emitting particles? + float m_StopEmitTime; // When do I stop emitting particles? (-1 = never) + + float m_MinSpeed; // Speed range. + float m_MaxSpeed; + + float m_MinDirectedSpeed; // Directed speed range. + float m_MaxDirectedSpeed; + + float m_StartSize; // Size ramp. + float m_EndSize; + + float m_SpawnRadius; + + Vector m_VelocityOffset; // Emit the particles in a certain direction. + + bool m_bEmit; // Keep emitting particles? + +private: + C_DustTrail( const C_DustTrail & ); + +#define DUSTTRAIL_MATERIALS 16 + PMaterialHandle m_MaterialHandle[DUSTTRAIL_MATERIALS]; + TimedEvent m_ParticleSpawn; + + CParticleMgr *m_pParticleMgr; + CSmartPtr m_pDustEmitter; +}; + +#endif diff --git a/game/client/c_smokestack.cpp b/game/client/c_smokestack.cpp new file mode 100644 index 00000000..0a438aff --- /dev/null +++ b/game/client/c_smokestack.cpp @@ -0,0 +1,500 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements a particle system steam jet. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "particle_prototype.h" +#include "baseparticleentity.h" +#include "particles_simple.h" +#include "filesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef HL2_EPISODIC + #define SMOKESTACK_MAX_MATERIALS 8 +#else + #define SMOKESTACK_MAX_MATERIALS 1 +#endif + +//================================================== +// C_SmokeStack +//================================================== + +class C_SmokeStack : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_SmokeStack, C_BaseParticleEntity ); + + C_SmokeStack(); + ~C_SmokeStack(); + + class SmokeStackParticle : public Particle + { + public: + Vector m_Velocity; + Vector m_vAccel; + float m_Lifetime; + float m_flAngle; + float m_flRollDelta; + float m_flSortPos; + }; + +//C_BaseEntity +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink(); + +//IPrototypeAppEffect +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + virtual bool GetPropEditInfo(RecvTable **ppTable, void **ppObj); + + +//IParticleEffect +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + virtual void StartRender( VMatrix &effectMatrix ); + + +private: + + void QueueLightParametersInRenderer(); + + +//Stuff from the datatable +public: + + CParticleSphereRenderer m_Renderer; + + float m_SpreadSpeed; + float m_Speed; + float m_StartSize; + float m_EndSize; + float m_Rate; + float m_JetLength; // Length of the jet. Lifetime is derived from this. + + int m_bEmit; // Emit particles? + float m_flBaseSpread; + + class CLightInfo + { + public: + Vector m_vPos; + Vector m_vColor; + float m_flIntensity; + }; + + // Note: there are two ways the directional light can be specified. The default is to use + // DirLightColor and a default dirlight source (from above or below). + // In this case, m_DirLight.m_vPos and m_DirLight.m_flIntensity are ignored. + // + // The other is to attach a directional env_particlelight to us. + // In this case, m_DirLightSource is ignored and all the m_DirLight parameters are used. + CParticleLightInfo m_AmbientLight; + CParticleLightInfo m_DirLight; + + Vector m_vBaseColor; + + Vector m_vWind; + float m_flTwist; + int m_iMaterialModel; + +private: + C_SmokeStack( const C_SmokeStack & ); + + float m_TwistMat[2][2]; + int m_bTwist; + + float m_flAlphaScale; + float m_InvLifetime; // Calculated from m_JetLength / m_Speed; + + CParticleMgr *m_pParticleMgr; + PMaterialHandle m_MaterialHandle[SMOKESTACK_MAX_MATERIALS]; + TimedEvent m_ParticleSpawn; + int m_iMaxFrames; + bool m_bInView; + float m_flRollSpeed; +}; + + +// ------------------------------------------------------------------------- // +// Tables. +// ------------------------------------------------------------------------- // + +// Expose to the particle app. +EXPOSE_PROTOTYPE_EFFECT(SmokeStack, C_SmokeStack); + + +IMPLEMENT_CLIENTCLASS_DT(C_SmokeStack, DT_SmokeStack, CSmokeStack) + RecvPropFloat(RECVINFO(m_SpreadSpeed), 0), + RecvPropFloat(RECVINFO(m_Speed), 0), + RecvPropFloat(RECVINFO(m_StartSize), 0), + RecvPropFloat(RECVINFO(m_EndSize), 0), + RecvPropFloat(RECVINFO(m_Rate), 0), + RecvPropFloat(RECVINFO(m_JetLength), 0), + RecvPropInt(RECVINFO(m_bEmit), 0), + RecvPropFloat(RECVINFO(m_flBaseSpread)), + RecvPropFloat(RECVINFO(m_flTwist)), + RecvPropFloat(RECVINFO(m_flRollSpeed )), + RecvPropIntWithMinusOneFlag( RECVINFO( m_iMaterialModel ) ), + + RecvPropVector( RECVINFO(m_AmbientLight.m_vPos) ), + RecvPropVector( RECVINFO(m_AmbientLight.m_vColor) ), + RecvPropFloat( RECVINFO(m_AmbientLight.m_flIntensity) ), + + RecvPropVector( RECVINFO(m_DirLight.m_vPos) ), + RecvPropVector( RECVINFO(m_DirLight.m_vColor) ), + RecvPropFloat( RECVINFO(m_DirLight.m_flIntensity) ), + + RecvPropVector(RECVINFO(m_vWind)) +END_RECV_TABLE() + + + +// ------------------------------------------------------------------------- // +// C_SmokeStack implementation. +// ------------------------------------------------------------------------- // +C_SmokeStack::C_SmokeStack() +{ + m_pParticleMgr = NULL; + m_MaterialHandle[0] = INVALID_MATERIAL_HANDLE; + m_iMaterialModel = -1; + + m_SpreadSpeed = 15; + m_Speed = 30; + m_StartSize = 10; + m_EndSize = 15; + m_Rate = 80; + m_JetLength = 180; + m_bEmit = true; + + m_flBaseSpread = 20; + m_bInView = false; + + // Lighting is (base color) + (ambient / dist^2) + bump(directional / dist^2) + // By default, we use bottom-up lighting for the directional. + SetRenderColor( 0, 0, 0, 255 ); + + m_AmbientLight.m_vPos.Init(0,0,-100); + m_AmbientLight.m_vColor.Init( 40, 40, 40 ); + m_AmbientLight.m_flIntensity = 8000; + + m_DirLight.m_vColor.Init( 255, 128, 0 ); + + m_vWind.Init(); + + m_flTwist = 0; +} + + +C_SmokeStack::~C_SmokeStack() +{ + if(m_pParticleMgr) + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called after a data update has occured +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_SmokeStack::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if(updateType == DATA_UPDATE_CREATED) + { + Start(ParticleMgr(), NULL); + } + + // Recalulate lifetime in case length or speed changed. + m_InvLifetime = m_Speed / m_JetLength; +} + + +//----------------------------------------------------------------------------- +// Purpose: Starts the effect +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_SmokeStack::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) +{ + pParticleMgr->AddEffect( &m_ParticleEffect, this ); + + // Figure out the material name. + char str[512] = "unset_material"; + const model_t *pModel = modelinfo->GetModel( m_iMaterialModel ); + if ( pModel ) + { + Q_strncpy( str, modelinfo->GetModelName( pModel ), sizeof( str ) ); + + // Get rid of the extension because the material system doesn't want it. + char *pExt = Q_stristr( str, ".vmt" ); + if ( pExt ) + pExt[0] = 0; + } + + m_MaterialHandle[0] = m_ParticleEffect.FindOrAddMaterial( str ); + +#ifdef HL2_EPISODIC + int iCount = 1; + char szNames[512]; + + int iLength = Q_strlen( str ); + str[iLength-1] = '\0'; + + Q_snprintf( szNames, sizeof( szNames ), "%s%d.vmt", str, iCount ); + + while ( filesystem->FileExists( VarArgs( "materials/%s", szNames ) ) && iCount < SMOKESTACK_MAX_MATERIALS ) + { + char *pExt = Q_stristr( szNames, ".vmt" ); + if ( pExt ) + pExt[0] = 0; + + m_MaterialHandle[iCount] = m_ParticleEffect.FindOrAddMaterial( szNames ); + iCount++; + } + + m_iMaxFrames = iCount-1; +#endif + + m_ParticleSpawn.Init(m_Rate); + + m_InvLifetime = m_Speed / m_JetLength; + + m_pParticleMgr = pParticleMgr; + + // Figure out how we need to draw. + IMaterial *pMaterial = pParticleMgr->PMaterialToIMaterial( m_MaterialHandle[0] ); + if( pMaterial ) + { + m_Renderer.Init( pParticleMgr, pMaterial ); + } + + QueueLightParametersInRenderer(); + + // For the first N seconds, always simulate so it can build up the smokestack. + // Afterwards, we set it to freeze when it's not being rendered. + m_ParticleEffect.SetAlwaysSimulate( true ); + SetNextClientThink( gpGlobals->curtime + 5 ); +} + + +void C_SmokeStack::ClientThink() +{ + m_ParticleEffect.SetAlwaysSimulate( false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : **ppTable - +// **ppObj - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_SmokeStack::GetPropEditInfo( RecvTable **ppTable, void **ppObj ) +{ + *ppTable = &REFERENCE_RECV_TABLE(DT_SmokeStack); + *ppObj = this; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- +void C_SmokeStack::Update(float fTimeDelta) +{ + if( !m_pParticleMgr ) + { + assert(false); + return; + } + + // Don't spawn particles unless we're visible. + if( m_bEmit && (m_ParticleEffect.WasDrawnPrevFrame() || m_ParticleEffect.GetAlwaysSimulate()) ) + { + // Add new particles. + Vector forward, right, up; + AngleVectors(GetAbsAngles(), &forward, &right, &up); + + float tempDelta = fTimeDelta; + while(m_ParticleSpawn.NextEvent(tempDelta)) + { + int iRandomFrame = random->RandomInt( 0, m_iMaxFrames ); + +#ifndef HL2_EPISODIC + iRandomFrame = 0; +#endif + + // Make a new particle. + if(SmokeStackParticle *pParticle = (SmokeStackParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeStackParticle), m_MaterialHandle[iRandomFrame])) + { + float angle = FRand( 0, 2.0f*M_PI_F ); + + pParticle->m_Pos = GetAbsOrigin() + + right * (cos( angle ) * m_flBaseSpread) + + forward * (sin( angle ) * m_flBaseSpread); + + pParticle->m_Velocity = + FRand(-m_SpreadSpeed,m_SpreadSpeed) * right + + FRand(-m_SpreadSpeed,m_SpreadSpeed) * forward + + m_Speed * up; + + pParticle->m_vAccel = m_vWind; + pParticle->m_Lifetime = 0; + pParticle->m_flAngle = 0.0f; + +#ifdef HL2_EPISODIC + pParticle->m_flAngle = RandomFloat( 0, 360 ); +#endif + pParticle->m_flRollDelta = random->RandomFloat( -m_flRollSpeed, m_flRollSpeed ); + pParticle->m_flSortPos = pParticle->m_Pos.z; + } + } + } + + // Setup the twist matrix. + float flTwist = (m_flTwist * (M_PI_F * 2.f) / 360.0f) * Helper_GetFrameTime(); + if( m_bTwist = !!flTwist ) + { + m_TwistMat[0][0] = cos(flTwist); + m_TwistMat[0][1] = sin(flTwist); + m_TwistMat[1][0] = -sin(flTwist); + m_TwistMat[1][1] = cos(flTwist); + } + + QueueLightParametersInRenderer(); +} + + +void C_SmokeStack::StartRender( VMatrix &effectMatrix ) +{ + m_Renderer.StartRender( effectMatrix ); +} + + +void C_SmokeStack::QueueLightParametersInRenderer() +{ + m_Renderer.SetBaseColor( Vector( m_clrRender->r / 255.0f, m_clrRender->g / 255.0f, m_clrRender->b / 255.0f ) ); + m_Renderer.SetAmbientLight( m_AmbientLight ); + m_Renderer.SetDirectionalLight( m_DirLight ); + m_flAlphaScale = (float)m_clrRender->a; +} + + +void C_SmokeStack::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const SmokeStackParticle *pParticle = (const SmokeStackParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Transform. + Vector tPos; + TransformParticle( m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos ); + + // Figure out its alpha. Squaring it after it gets halfway through its lifetime + // makes it get translucent and fade out for a longer time. + //float alpha = cosf( -M_PI_F + tLifetime * M_PI_F * 2.f ) * 0.5f + 0.5f; + float tLifetime = pParticle->m_Lifetime * m_InvLifetime; + float alpha = TableCos( -M_PI_F + tLifetime * M_PI_F * 2.f ) * 0.5f + 0.5f; + if( tLifetime > 0.5f ) + alpha *= alpha; + + m_Renderer.RenderParticle( + pIterator->GetParticleDraw(), + pParticle->m_Pos, + tPos, + alpha * m_flAlphaScale, + FLerp(m_StartSize, m_EndSize, tLifetime), + DEG2RAD( pParticle->m_flAngle ) + ); + + pParticle = (const SmokeStackParticle*)pIterator->GetNext( pParticle->m_flSortPos ); + } +} + + +void C_SmokeStack::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + bool bSortNow = true; // Change this to false if we see sorting issues. + bool bQuickTest = false; + + bool bDrawn = m_ParticleEffect.WasDrawnPrevFrame(); + + if ( bDrawn == true && m_bInView == false ) + { + bSortNow = true; + } + + if ( bDrawn == false && m_bInView == true ) + { + bQuickTest = true; + } + +#ifndef HL2_EPISODIC + bQuickTest = false; + bSortNow = true; +#endif + + if( bQuickTest == false && m_bEmit && (!m_ParticleEffect.WasDrawnPrevFrame() && !m_ParticleEffect.GetAlwaysSimulate()) ) + return; + + SmokeStackParticle *pParticle = (SmokeStackParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Should this particle die? + pParticle->m_Lifetime += pIterator->GetTimeDelta(); + + float tLifetime = pParticle->m_Lifetime * m_InvLifetime; + if( tLifetime >= 1 ) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + // Transform. + Vector tPos; + if( m_bTwist ) + { + Vector vTwist( + pParticle->m_Pos.x - GetAbsOrigin().x, + pParticle->m_Pos.y - GetAbsOrigin().y, + 0); + + pParticle->m_Pos.x = vTwist.x * m_TwistMat[0][0] + vTwist.y * m_TwistMat[0][1] + GetAbsOrigin().x; + pParticle->m_Pos.y = vTwist.x * m_TwistMat[1][0] + vTwist.y * m_TwistMat[1][1] + GetAbsOrigin().y; + } + +#ifndef HL2_EPISODIC + pParticle->m_Pos = pParticle->m_Pos + + pParticle->m_Velocity * pIterator->GetTimeDelta() + + pParticle->m_vAccel * (0.5f * pIterator->GetTimeDelta() * pIterator->GetTimeDelta()); + + pParticle->m_Velocity += pParticle->m_vAccel * pIterator->GetTimeDelta(); +#else + pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * pIterator->GetTimeDelta() + pParticle->m_vAccel * pIterator->GetTimeDelta(); +#endif + + pParticle->m_flAngle += pParticle->m_flRollDelta * pIterator->GetTimeDelta(); + + if ( bSortNow == true ) + { + Vector tPos; + TransformParticle( m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos ); + pParticle->m_flSortPos = tPos.z; + } + } + + pParticle = (SmokeStackParticle*)pIterator->GetNext(); + } + + m_bInView = bDrawn; +} + + diff --git a/game/client/c_soundscape.cpp b/game/client/c_soundscape.cpp new file mode 100644 index 00000000..5abfe22a --- /dev/null +++ b/game/client/c_soundscape.cpp @@ -0,0 +1,1324 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Soundscapes.txt resource file processor +// +// $NoKeywords: $ +//=============================================================================// + + +#include "cbase.h" +#include +#include "engine/ienginesound.h" +#include "filesystem.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "soundchars.h" +#include "view.h" +#include "engine/ivdebugoverlay.h" +#include "tier0/icommandline.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Only allow recursive references to be 8 levels deep. +// This test will flag any circular references and bail. +#define MAX_SOUNDSCAPE_RECURSION 8 + +const float DEFAULT_SOUND_RADIUS = 36.0f; +// Keep an array of all looping sounds so they can be faded in/out +// OPTIMIZE: Get a handle/pointer to the engine's sound channel instead +// of searching each frame! +struct loopingsound_t +{ + Vector position; // position (if !isAmbient) + const char *pWaveName; // name of the wave file + float volumeTarget; // target volume level (fading towards this) + float volumeCurrent; // current volume level + soundlevel_t soundlevel; // sound level (if !isAmbient) + int pitch; // pitch shift + int id; // Used to fade out sounds that don't belong to the most current setting + bool isAmbient; // Ambient sounds have no spatialization - they play from everywhere +}; + +ConVar soundscape_fadetime( "soundscape_fadetime", "3.0", FCVAR_CHEAT, "Time to crossfade sound effects between soundscapes" ); + +#include "interval.h" + +struct randomsound_t +{ + Vector position; + float nextPlayTime; // time to play a sound from the set + interval_t time; + interval_t volume; + interval_t pitch; + interval_t soundlevel; + float masterVolume; + int waveCount; + bool isAmbient; + bool isRandom; + KeyValues *pWaves; + + void Init() + { + memset( this, 0, sizeof(*this) ); + } +}; + +struct subsoundscapeparams_t +{ + int recurseLevel; // test for infinite loops in the script / circular refs + float masterVolume; + int startingPosition; + int positionOverride; // forces all sounds to this position + int ambientPositionOverride; // forces all ambient sounds to this position + bool allowDSP; + bool wroteSoundMixer; + bool wroteDSPVolume; +}; + +class C_SoundscapeSystem : public CBaseGameSystemPerFrame +{ +public: + virtual char const *Name() { return "C_SoundScapeSystem"; } + + C_SoundscapeSystem() + { + m_nRestoreFrame = -1; + } + + ~C_SoundscapeSystem() {} + + void OnStopAllSounds() + { + m_params.ent.Set( NULL ); + m_params.soundscapeIndex = -1; + m_loopingSounds.Purge(); + m_randomSounds.Purge(); + } + + // IClientSystem hooks, not needed + virtual void LevelInitPreEntity() + { + Shutdown(); + Init(); + + TouchSoundFiles(); + } + + virtual void LevelInitPostEntity() + { + if ( !m_pSoundMixerVar ) + { + m_pSoundMixerVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); + } + if ( !m_pDSPVolumeVar ) + { + m_pDSPVolumeVar = (ConVar *)cvar->FindVar( "dsp_volume" ); + } + } + + // The level is shutdown in two parts + virtual void LevelShutdownPreEntity() {} + // Entities are deleted / released here... + virtual void LevelShutdownPostEntity() + { + OnStopAllSounds(); + } + + virtual void OnSave() {} + virtual void OnRestore() + { + m_nRestoreFrame = gpGlobals->framecount; + } + virtual void SafeRemoveIfDesired() {} + + // Called before rendering + virtual void PreRender() { } + + // Called after rendering + virtual void PostRender() { } + + // IClientSystem hooks used + virtual bool Init(); + virtual void Shutdown(); + // Gets called each frame + virtual void Update( float frametime ); + + void PrintDebugInfo() + { + Msg( "\n------- CLIENT SOUNDSCAPES -------\n" ); + for ( int i=0; i < m_soundscapes.Count(); i++ ) + { + Msg( "- %d: %s\n", i, m_soundscapes[i]->GetName() ); + } + if ( m_forcedSoundscapeIndex ) + { + Msg( "- PLAYING DEBUG SOUNDSCAPE: %d [%s]\n", m_forcedSoundscapeIndex, SoundscapeNameByIndex(m_forcedSoundscapeIndex) ); + } + Msg( "- CURRENT SOUNDSCAPE: %d [%s]\n", m_params.soundscapeIndex, SoundscapeNameByIndex(m_params.soundscapeIndex) ); + Msg( "----------------------------------\n\n" ); + } + + + // local functions + void UpdateAudioParams( audioparams_t &audio ); + void GetAudioParams( audioparams_t &out ) const { out = m_params; } + int GetCurrentSoundscape() + { + if ( m_forcedSoundscapeIndex >= 0 ) + return m_forcedSoundscapeIndex; + return m_params.soundscapeIndex; + } + void DevReportSoundscapeName( int index ); + void UpdateLoopingSounds( float frametime ); + int AddLoopingAmbient( const char *pSoundName, float volume, int pitch ); + void UpdateLoopingSound( loopingsound_t &loopSound ); + void StopLoopingSound( loopingsound_t &loopSound ); + int AddLoopingSound( const char *pSoundName, bool isAmbient, float volume, + soundlevel_t soundLevel, int pitch, const Vector &position ); + int AddRandomSound( const randomsound_t &sound ); + void PlayRandomSound( randomsound_t &sound ); + void UpdateRandomSounds( float gameClock ); + Vector GenerateRandomSoundPosition(); + + void ForceSoundscape( const char *pSoundscapeName, float radius ); + + int FindSoundscapeByName( const char *pSoundscapeName ); + const char *SoundscapeNameByIndex( int index ); + KeyValues *SoundscapeByIndex( int index ); + + // main-level soundscape processing, called on new soundscape + void StartNewSoundscape( KeyValues *pSoundscape ); + void StartSubSoundscape( KeyValues *pSoundscape, subsoundscapeparams_t ¶ms ); + + // root level soundscape keys + // add a process for each new command here + // "dsp" + void ProcessDSP( KeyValues *pDSP ); + // "dsp_player" + void ProcessDSPPlayer( KeyValues *pDSPPlayer ); + // "playlooping" + void ProcessPlayLooping( KeyValues *pPlayLooping, const subsoundscapeparams_t ¶ms ); + // "playrandom" + void ProcessPlayRandom( KeyValues *pPlayRandom, const subsoundscapeparams_t ¶ms ); + // "playsoundscape" + void ProcessPlaySoundscape( KeyValues *pPlaySoundscape, subsoundscapeparams_t ¶ms ); + // "soundmixer" + void ProcessSoundMixer( KeyValues *pSoundMixer, subsoundscapeparams_t ¶ms ); + // "dsp_volume" + void ProcessDSPVolume( KeyValues *pKey, subsoundscapeparams_t ¶ms ); + + +private: + + bool IsBeingRestored() const + { + return gpGlobals->framecount == m_nRestoreFrame ? true : false; + } + + void AddSoundScapeFile( const char *filename ); + + void TouchPlayLooping( KeyValues *pAmbient ); + void TouchPlayRandom( KeyValues *pPlayRandom ); + void TouchWaveFiles( KeyValues *pSoundScape ); + void TouchSoundFile( char const *wavefile ); + + void TouchSoundFiles(); + + int m_nRestoreFrame; + + CUtlVector< KeyValues * > m_SoundscapeScripts; // The whole script file in memory + CUtlVector m_soundscapes; // Lookup by index of each root section + audioparams_t m_params; // current player audio params + CUtlVector m_loopingSounds; // list of currently playing sounds + CUtlVector m_randomSounds; // list of random sound commands + float m_nextRandomTime; // next time to play a random sound + int m_loopingSoundId; // marks when the sound was issued + int m_forcedSoundscapeIndex;// >= 0 if this a "forced" soundscape? i.e. debug mode? + float m_forcedSoundscapeRadius;// distance to spatialized sounds + + static ConVar *m_pDSPVolumeVar; + static ConVar *m_pSoundMixerVar; + +}; + + +// singleton system +C_SoundscapeSystem g_SoundscapeSystem; +ConVar *C_SoundscapeSystem::m_pDSPVolumeVar = NULL; +ConVar *C_SoundscapeSystem::m_pSoundMixerVar = NULL; + +IGameSystem *ClientSoundscapeSystem() +{ + return &g_SoundscapeSystem; +} + + +void Soundscape_OnStopAllSounds() +{ + g_SoundscapeSystem.OnStopAllSounds(); +} + + +// player got a network update +void Soundscape_Update( audioparams_t &audio ) +{ + g_SoundscapeSystem.UpdateAudioParams( audio ); +} + +#define SOUNDSCAPE_MANIFEST_FILE "scripts/soundscapes_manifest.txt" + +void C_SoundscapeSystem::AddSoundScapeFile( const char *filename ) +{ + KeyValues *script = new KeyValues( filename ); +#ifndef _XBOX + if ( script->LoadFromFile( filesystem, filename ) ) +#else + if ( filesystem->LoadKeyValues( *script, IFileSystem::TYPE_SOUNDSCAPE, filename, "GAME" ) ) +#endif + { + // parse out all of the top level sections and save their names + KeyValues *pKeys = script; + while ( pKeys ) + { + // save pointers to all sections in the root + // each one is a soundscape + if ( pKeys->GetFirstSubKey() ) + { + m_soundscapes.AddToTail( pKeys ); + } + pKeys = pKeys->GetNextKey(); + } + + // Keep pointer around so we can delete it at exit + m_SoundscapeScripts.AddToTail( script ); + } + else + { + script->deleteThis(); + } +} + +// parse the script file, setup index table +bool C_SoundscapeSystem::Init() +{ + m_loopingSoundId = 0; + + const char *mapname = MapName(); + const char *mapSoundscapeFilename = NULL; + if ( mapname && *mapname ) + { + mapSoundscapeFilename = VarArgs( "scripts/soundscapes_%s.txt", mapname ); + } + + KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE ); + if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDSCAPE, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) ) + { + for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "file" ) ) + { + // Add + AddSoundScapeFile( sub->GetString() ); + if ( mapSoundscapeFilename && FStrEq( sub->GetString(), mapSoundscapeFilename ) ) + { + mapSoundscapeFilename = NULL; // we've already loaded the map's soundscape + } + continue; + } + + Warning( "C_SoundscapeSystem::Init: Manifest '%s' with bogus file type '%s', expecting 'file'\n", + SOUNDSCAPE_MANIFEST_FILE, sub->GetName() ); + } + + if ( mapSoundscapeFilename && filesystem->FileExists( mapSoundscapeFilename ) ) + { + AddSoundScapeFile( mapSoundscapeFilename ); + } + } + else + { + Error( "Unable to load manifest file '%s'\n", SOUNDSCAPE_MANIFEST_FILE ); + } + + manifest->deleteThis(); + + return true; +} + + +int C_SoundscapeSystem::FindSoundscapeByName( const char *pSoundscapeName ) +{ + // UNDONE: Bad perf, linear search! + for ( int i = m_soundscapes.Count()-1; i >= 0; --i ) + { + if ( !Q_stricmp( m_soundscapes[i]->GetName(), pSoundscapeName ) ) + return i; + } + + return -1; +} + +KeyValues *C_SoundscapeSystem::SoundscapeByIndex( int index ) +{ + if ( m_soundscapes.IsValidIndex(index) ) + return m_soundscapes[index]; + return NULL; +} + +const char *C_SoundscapeSystem::SoundscapeNameByIndex( int index ) +{ + if ( index < m_soundscapes.Count() ) + { + return m_soundscapes[index]->GetName(); + } + + return NULL; +} + +void C_SoundscapeSystem::Shutdown() +{ + for ( int i = m_loopingSounds.Count() - 1; i >= 0; --i ) + { + loopingsound_t &sound = m_loopingSounds[i]; + + // sound is done, remove from list. + StopLoopingSound( sound ); + } + + // These are only necessary so we can use shutdown/init calls + // to flush soundscape data + m_loopingSounds.RemoveAll(); + m_randomSounds.RemoveAll(); + m_soundscapes.RemoveAll(); + m_params.ent.Set( NULL ); + m_params.soundscapeIndex = -1; + + while ( m_SoundscapeScripts.Count() > 0 ) + { + KeyValues *kv = m_SoundscapeScripts[ 0 ]; + m_SoundscapeScripts.Remove( 0 ); + kv->deleteThis(); + } +} + +// NOTE: This will not flush the server side so you cannot add or remove +// soundscapes from the list, only change their parameters!!!! +CON_COMMAND_F(cl_soundscape_flush, "Flushes the client side soundscapes", FCVAR_SERVER_CAN_EXECUTE|FCVAR_CHEAT) +{ + // save the current soundscape + audioparams_t tmp; + g_SoundscapeSystem.GetAudioParams( tmp ); + + // kill the system + g_SoundscapeSystem.Shutdown(); + + // restart the system + g_SoundscapeSystem.Init(); + + // reload the soundscape params from the temp copy + Soundscape_Update( tmp ); +} + + +static int SoundscapeCompletion( const char *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) +{ + int current = 0; + + const char *cmdname = "playsoundscape"; + char *substring = NULL; + int substringLen = 0; + if ( Q_strstr( partial, cmdname ) && strlen(partial) > strlen(cmdname) + 1 ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + substringLen = strlen(substring); + } + + int i = 0; + const char *pSoundscapeName = g_SoundscapeSystem.SoundscapeNameByIndex( i ); + while ( pSoundscapeName && current < COMMAND_COMPLETION_MAXITEMS ) + { + if ( !substring || !Q_strncasecmp( pSoundscapeName, substring, substringLen ) ) + { + Q_snprintf( commands[ current ], sizeof( commands[ current ] ), "%s %s", cmdname, pSoundscapeName ); + current++; + } + i++; + pSoundscapeName = g_SoundscapeSystem.SoundscapeNameByIndex( i ); + } + + return current; +} + +CON_COMMAND_F_COMPLETION( playsoundscape, "Forces a soundscape to play", FCVAR_CHEAT, SoundscapeCompletion ) +{ + if ( args.ArgC() < 2 ) + { + g_SoundscapeSystem.DevReportSoundscapeName( g_SoundscapeSystem.GetCurrentSoundscape() ); + return; + } + const char *pSoundscapeName = args[1]; + float radius = args.ArgC() > 2 ? atof( args[2] ) : DEFAULT_SOUND_RADIUS; + g_SoundscapeSystem.ForceSoundscape( pSoundscapeName, radius ); +} + + +CON_COMMAND_F( stopsoundscape, "Stops all soundscape processing and fades current looping sounds", FCVAR_CHEAT ) +{ + g_SoundscapeSystem.StartNewSoundscape( NULL ); +} + +void C_SoundscapeSystem::ForceSoundscape( const char *pSoundscapeName, float radius ) +{ + int index = g_SoundscapeSystem.FindSoundscapeByName( pSoundscapeName ); + if ( index >= 0 ) + { + m_forcedSoundscapeIndex = index; + m_forcedSoundscapeRadius = radius; + g_SoundscapeSystem.StartNewSoundscape( SoundscapeByIndex(index) ); + } + else + { + DevWarning("Can't find soundscape %s\n", pSoundscapeName ); + } +} + +void C_SoundscapeSystem::DevReportSoundscapeName( int index ) +{ + const char *pName = "none"; + if ( index >= 0 && index < m_soundscapes.Count() ) + { + pName = m_soundscapes[index]->GetName(); + } + DevMsg( 1, "Soundscape: %s\n", pName ); +} + + +// This makes all currently playing loops fade toward their target volume +void C_SoundscapeSystem::UpdateLoopingSounds( float frametime ) +{ + float period = soundscape_fadetime.GetFloat(); + float amount = frametime; + if ( period > 0 ) + { + amount *= 1.0 / period; + } + + int fadeCount = m_loopingSounds.Count(); + while ( fadeCount > 0 ) + { + fadeCount--; + loopingsound_t &sound = m_loopingSounds[fadeCount]; + + if ( sound.volumeCurrent != sound.volumeTarget ) + { + sound.volumeCurrent = Approach( sound.volumeTarget, sound.volumeCurrent, amount ); + if ( sound.volumeTarget == 0 && sound.volumeCurrent == 0 ) + { + // sound is done, remove from list. + StopLoopingSound( sound ); + m_loopingSounds.FastRemove( fadeCount ); + } + else + { + // tell the engine about the new volume + UpdateLoopingSound( sound ); + } + } + } +} + +void C_SoundscapeSystem::Update( float frametime ) +{ + if ( m_forcedSoundscapeIndex >= 0 ) + { + // generate fake positional sources + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer ) + { + Vector origin, forward, right; + pPlayer->EyePositionAndVectors( &origin, &forward, &right, NULL ); + + // put the sound origins at the corners of a box around the player + m_params.localSound.Set( 0, origin + m_forcedSoundscapeRadius * (forward-right) ); + m_params.localSound.Set( 1, origin + m_forcedSoundscapeRadius * (forward+right) ); + m_params.localSound.Set( 2, origin + m_forcedSoundscapeRadius * (-forward-right) ); + m_params.localSound.Set( 3, origin + m_forcedSoundscapeRadius * (-forward+right) ); + m_params.localBits = 0x0007; + } + } + // fade out the old sounds over soundscape_fadetime seconds + UpdateLoopingSounds( frametime ); + UpdateRandomSounds( gpGlobals->curtime ); +} + + +void C_SoundscapeSystem::UpdateAudioParams( audioparams_t &audio ) +{ + if ( m_params.soundscapeIndex == audio.soundscapeIndex && m_params.ent.Get() == audio.ent.Get() ) + return; + + m_params = audio; + m_forcedSoundscapeIndex = -1; + if ( audio.ent.Get() && audio.soundscapeIndex >= 0 && audio.soundscapeIndex < m_soundscapes.Count() ) + { + DevReportSoundscapeName( audio.soundscapeIndex ); + StartNewSoundscape( m_soundscapes[audio.soundscapeIndex] ); + } + else + { + // bad index (and the soundscape file actually existed...) + if ( audio.ent.Get() != 0 && + audio.soundscapeIndex != -1 ) + { + DevMsg(1, "Error: Bad soundscape!\n"); + } + } +} + + + +// Called when a soundscape is activated (leading edge of becoming the active soundscape) +void C_SoundscapeSystem::StartNewSoundscape( KeyValues *pSoundscape ) +{ + int i; + + // Reset the system + // fade out the current loops + for ( i = m_loopingSounds.Count()-1; i >= 0; --i ) + { + m_loopingSounds[i].volumeTarget = 0; + if ( !pSoundscape ) + { + // if we're cancelling the soundscape, stop the sound immediately + m_loopingSounds[i].volumeCurrent = 0; + } + } + // update ID + m_loopingSoundId++; + + // clear all random sounds + m_randomSounds.RemoveAll(); + m_nextRandomTime = gpGlobals->curtime; + + if ( pSoundscape ) + { + subsoundscapeparams_t params; + params.allowDSP = true; + params.wroteSoundMixer = false; + params.wroteDSPVolume = false; + + params.masterVolume = 1.0; + params.startingPosition = 0; + params.recurseLevel = 0; + params.positionOverride = -1; + params.ambientPositionOverride = -1; + StartSubSoundscape( pSoundscape, params ); + + if ( !params.wroteDSPVolume ) + { + m_pDSPVolumeVar->Revert(); + } + if ( !params.wroteSoundMixer ) + { + m_pSoundMixerVar->Revert(); + } + } +} + +void C_SoundscapeSystem::StartSubSoundscape( KeyValues *pSoundscape, subsoundscapeparams_t ¶ms ) +{ + // Parse/process all of the commands + KeyValues *pKey = pSoundscape->GetFirstSubKey(); + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "dsp" ) ) + { + if ( params.allowDSP ) + { + ProcessDSP( pKey ); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "dsp_player" ) ) + { + if ( params.allowDSP ) + { + ProcessDSPPlayer( pKey ); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) ) + { + ProcessPlayLooping( pKey, params ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) ) + { + ProcessPlayRandom( pKey, params ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "playsoundscape" ) ) + { + ProcessPlaySoundscape( pKey, params ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "Soundmixer" ) ) + { + if ( params.allowDSP ) + { + ProcessSoundMixer( pKey, params ); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "dsp_volume" ) ) + { + if ( params.allowDSP ) + { + ProcessDSPVolume( pKey, params ); + } + } + // add new commands here + else + { + DevMsg( 1, "Soundscape %s:Unknown command %s\n", pSoundscape->GetName(), pKey->GetName() ); + } + pKey = pKey->GetNextKey(); + } +} + +// add a process for each new command here + +// change DSP effect +void C_SoundscapeSystem::ProcessDSP( KeyValues *pDSP ) +{ + int roomType = pDSP->GetInt(); + CLocalPlayerFilter filter; + enginesound->SetRoomType( filter, roomType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pDSPPlayer - +//----------------------------------------------------------------------------- +void C_SoundscapeSystem::ProcessDSPPlayer( KeyValues *pDSPPlayer ) +{ + int dspType = pDSPPlayer->GetInt(); + CLocalPlayerFilter filter; + enginesound->SetPlayerDSP( filter, dspType, false ); +} + + +void C_SoundscapeSystem::ProcessSoundMixer( KeyValues *pSoundMixer, subsoundscapeparams_t ¶ms ) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer || pPlayer->CanSetSoundMixer() ) + { + m_pSoundMixerVar->SetValue( pSoundMixer->GetString() ); + params.wroteSoundMixer = true; + } +} + +void C_SoundscapeSystem::ProcessDSPVolume( KeyValues *pKey, subsoundscapeparams_t ¶ms ) +{ + m_pDSPVolumeVar->SetValue( pKey->GetFloat() ); + params.wroteDSPVolume = true; +} + +// start a new looping sound +void C_SoundscapeSystem::ProcessPlayLooping( KeyValues *pAmbient, const subsoundscapeparams_t ¶ms ) +{ + float volume = 0; + soundlevel_t soundlevel = ATTN_TO_SNDLVL(ATTN_NORM); + const char *pSoundName = NULL; + int pitch = PITCH_NORM; + int positionIndex = -1; + bool suppress = false; + KeyValues *pKey = pAmbient->GetFirstSubKey(); + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "volume" ) ) + { + volume = params.masterVolume * RandomInterval( ReadInterval( pKey->GetString() ) ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "pitch" ) ) + { + pitch = RandomInterval( ReadInterval( pKey->GetString() ) ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "wave" ) ) + { + pSoundName = pKey->GetString(); + } + else if ( !Q_strcasecmp( pKey->GetName(), "position" ) ) + { + positionIndex = params.startingPosition + pKey->GetInt(); + } + else if ( !Q_strcasecmp( pKey->GetName(), "attenuation" ) ) + { + soundlevel = ATTN_TO_SNDLVL( RandomInterval( ReadInterval( pKey->GetString() ) ) ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "soundlevel" ) ) + { + if ( !Q_strncasecmp( pKey->GetString(), "SNDLVL_", strlen( "SNDLVL_" ) ) ) + { + soundlevel = TextToSoundLevel( pKey->GetString() ); + } + else + { + soundlevel = (soundlevel_t)((int)RandomInterval( ReadInterval( pKey->GetString() ) )); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "suppress_on_restore" ) ) + { + suppress = Q_atoi( pKey->GetString() ) != 0 ? true : false; + } + else + { + DevMsg( 1, "Ambient %s:Unknown command %s\n", pAmbient->GetName(), pKey->GetName() ); + } + pKey = pKey->GetNextKey(); + } + + if ( positionIndex < 0 ) + { + positionIndex = params.ambientPositionOverride; + } + else if ( params.positionOverride >= 0 ) + { + positionIndex = params.positionOverride; + } + + // Sound is mared as "suppress_on_restore" so don't restart it + if ( IsBeingRestored() && suppress ) + { + return; + } + + if ( volume != 0 && pSoundName != NULL ) + { + if ( positionIndex < 0 ) + { + AddLoopingAmbient( pSoundName, volume, pitch ); + } + else + { + if ( positionIndex > 31 || !(m_params.localBits & (1<GetFileTime( VarArgs( "sound/%s", PSkipSoundChars( wavefile ) ), "GAME" ); +} + +// start a new looping sound +void C_SoundscapeSystem::TouchPlayLooping( KeyValues *pAmbient ) +{ + KeyValues *pKey = pAmbient->GetFirstSubKey(); + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "wave" ) ) + { + char const *pSoundName = pKey->GetString(); + + // Touch the file + TouchSoundFile( pSoundName ); + } + + pKey = pKey->GetNextKey(); + } +} + + +Vector C_SoundscapeSystem::GenerateRandomSoundPosition() +{ + float angle = random->RandomFloat( -180, 180 ); + float sinAngle, cosAngle; + SinCos( angle, &sinAngle, &cosAngle ); + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer ) + { + Vector origin, forward, right; + pPlayer->EyePositionAndVectors( &origin, &forward, &right, NULL ); + return origin + DEFAULT_SOUND_RADIUS * (cosAngle * right + sinAngle * forward); + } + else + { + return CurrentViewOrigin() + DEFAULT_SOUND_RADIUS * (cosAngle * CurrentViewRight() + sinAngle * CurrentViewForward()); + } +} + +void C_SoundscapeSystem::TouchSoundFiles() +{ + if ( !CommandLine()->FindParm( "-makereslists" ) ) + return; + + int c = m_soundscapes.Count(); + for ( int i = 0; i < c ; ++i ) + { + TouchWaveFiles( m_soundscapes[ i ] ); + } +} + +void C_SoundscapeSystem::TouchWaveFiles( KeyValues *pSoundScape ) +{ + KeyValues *pKey = pSoundScape->GetFirstSubKey(); + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) ) + { + TouchPlayLooping( pKey ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) ) + { + TouchPlayRandom( pKey ); + } + + pKey = pKey->GetNextKey(); + } + +} + +// puts a recurring random sound event into the queue +void C_SoundscapeSystem::TouchPlayRandom( KeyValues *pPlayRandom ) +{ + KeyValues *pKey = pPlayRandom->GetFirstSubKey(); + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "rndwave" ) ) + { + KeyValues *pWaves = pKey->GetFirstSubKey(); + while ( pWaves ) + { + TouchSoundFile( pWaves->GetString() ); + + pWaves = pWaves->GetNextKey(); + } + } + + pKey = pKey->GetNextKey(); + } +} + +// puts a recurring random sound event into the queue +void C_SoundscapeSystem::ProcessPlayRandom( KeyValues *pPlayRandom, const subsoundscapeparams_t ¶ms ) +{ + randomsound_t sound; + sound.Init(); + sound.masterVolume = params.masterVolume; + int positionIndex = -1; + bool suppress = false; + bool randomPosition = false; + KeyValues *pKey = pPlayRandom->GetFirstSubKey(); + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "volume" ) ) + { + sound.volume = ReadInterval( pKey->GetString() ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "pitch" ) ) + { + sound.pitch = ReadInterval( pKey->GetString() ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "attenuation" ) ) + { + interval_t atten = ReadInterval( pKey->GetString() ); + sound.soundlevel.start = ATTN_TO_SNDLVL( atten.start ); + sound.soundlevel.range = ATTN_TO_SNDLVL( atten.start + atten.range ) - sound.soundlevel.start; + } + else if ( !Q_strcasecmp( pKey->GetName(), "soundlevel" ) ) + { + if ( !Q_strncasecmp( pKey->GetString(), "SNDLVL_", strlen( "SNDLVL_" ) ) ) + { + sound.soundlevel.start = TextToSoundLevel( pKey->GetString() ); + sound.soundlevel.range = 0; + } + else + { + sound.soundlevel = ReadInterval( pKey->GetString() ); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "time" ) ) + { + sound.time = ReadInterval( pKey->GetString() ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "rndwave" ) ) + { + KeyValues *pWaves = pKey->GetFirstSubKey(); + sound.pWaves = pWaves; + sound.waveCount = 0; + while ( pWaves ) + { + sound.waveCount++; + pWaves = pWaves->GetNextKey(); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "position" ) ) + { + if ( !Q_strcasecmp( pKey->GetString(), "random" ) ) + { + randomPosition = true; + } + else + { + positionIndex = params.startingPosition + pKey->GetInt(); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "suppress_on_restore" ) ) + { + suppress = Q_atoi( pKey->GetString() ) != 0 ? true : false; + } + else + { + DevMsg( 1, "Random Sound %s:Unknown command %s\n", pPlayRandom->GetName(), pKey->GetName() ); + } + + pKey = pKey->GetNextKey(); + } + + if ( positionIndex < 0 ) + { + positionIndex = params.ambientPositionOverride; + } + else if ( params.positionOverride >= 0 ) + { + positionIndex = params.positionOverride; + randomPosition = false; // override trumps random position + } + + // Sound is mared as "suppress_on_restore" so don't restart it + if ( IsBeingRestored() && suppress ) + { + return; + } + + if ( sound.waveCount != 0 ) + { + if ( positionIndex < 0 && !randomPosition ) + { + sound.isAmbient = true; + AddRandomSound( sound ); + } + else + { + sound.isAmbient = false; + if ( randomPosition ) + { + sound.isRandom = true; + } + else + { + if ( positionIndex > 31 || !(m_params.localBits & (1< MAX_SOUNDSCAPE_RECURSION ) + { + DevMsg( "Error! Soundscape recursion overrun!\n" ); + return; + } + KeyValues *pKey = pPlaySoundscape->GetFirstSubKey(); + const char *pSoundscapeName = NULL; + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "volume" ) ) + { + subParams.masterVolume = paramsIn.masterVolume * RandomInterval( ReadInterval( pKey->GetString() ) ); + } + else if ( !Q_strcasecmp( pKey->GetName(), "position" ) ) + { + subParams.startingPosition = paramsIn.startingPosition + pKey->GetInt(); + } + else if ( !Q_strcasecmp( pKey->GetName(), "positionoverride" ) ) + { + if ( paramsIn.positionOverride < 0 ) + { + subParams.positionOverride = paramsIn.startingPosition + pKey->GetInt(); + // positionoverride is only ever used to make a whole soundscape come from a point in space + // So go ahead and default ambients there too. + subParams.ambientPositionOverride = paramsIn.startingPosition + pKey->GetInt(); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "ambientpositionoverride" ) ) + { + if ( paramsIn.ambientPositionOverride < 0 ) + { + subParams.ambientPositionOverride = paramsIn.startingPosition + pKey->GetInt(); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "name" ) ) + { + pSoundscapeName = pKey->GetString(); + } + else if ( !Q_strcasecmp(pKey->GetName(), "soundlevel") ) + { + DevMsg(1,"soundlevel not supported on sub-soundscapes\n"); + } + else + { + DevMsg( 1, "Playsoundscape %s:Unknown command %s\n", pSoundscapeName ? pSoundscapeName : pPlaySoundscape->GetName(), pKey->GetName() ); + } + pKey = pKey->GetNextKey(); + } + + if ( pSoundscapeName ) + { + KeyValues *pSoundscapeKeys = SoundscapeByIndex( FindSoundscapeByName( pSoundscapeName ) ); + if ( pSoundscapeKeys ) + { + StartSubSoundscape( pSoundscapeKeys, subParams ); + } + else + { + DevMsg( 1, "Trying to play unknown soundscape %s\n", pSoundscapeName ); + } + } +} + +// special kind of looping sound with no spatialization +int C_SoundscapeSystem::AddLoopingAmbient( const char *pSoundName, float volume, int pitch ) +{ + return AddLoopingSound( pSoundName, true, volume, SNDLVL_NORM, pitch, vec3_origin ); +} + +// add a looping sound to the list +// NOTE: will reuse existing entry (fade from current volume) if possible +// this prevents pops +int C_SoundscapeSystem::AddLoopingSound( const char *pSoundName, bool isAmbient, float volume, soundlevel_t soundlevel, int pitch, const Vector &position ) +{ + loopingsound_t *pSoundSlot = NULL; + int soundSlot = m_loopingSounds.Count() - 1; + bool bForceSoundUpdate = false; + while ( soundSlot >= 0 ) + { + loopingsound_t &sound = m_loopingSounds[soundSlot]; + + // NOTE: Will always restart/crossfade positional sounds + if ( sound.id != m_loopingSoundId && + sound.pitch == pitch && + !Q_strcasecmp( pSoundName, sound.pWaveName ) ) + { + // Ambient sounds can reuse the slots. + if ( isAmbient == true && + sound.isAmbient == true ) + { + // reuse this sound + pSoundSlot = &sound; + break; + } + // Positional sounds can reuse the slots if the positions are the same. + else if ( isAmbient == sound.isAmbient ) + { + if ( VectorsAreEqual( position, sound.position, 0.1f ) ) + { + // reuse this sound + pSoundSlot = &sound; + break; + } + else + { + // If it's trying to fade out one positional sound and fade in another, then it gets screwy + // because it'll be sending alternating commands to the sound engine, referencing the same sound + // (SOUND_FROM_WORLD, CHAN_STATIC, pSoundName). One of the alternating commands will be as + // it fades the sound out, and one will be fading the sound in. + // Because this will occasionally cause the sound to vanish entirely, we stop the old sound immediately. + StopLoopingSound(sound); + pSoundSlot = &sound; + + // make a note to update the sound immediately. Otherwise, if its volume happens to be + // the same as the old sound's volume, it will never update at all. + bForceSoundUpdate = true; + break; + } + } + } + soundSlot--; + } + + if ( soundSlot < 0 ) + { + // can't find the sound in the list, make a new one + soundSlot = m_loopingSounds.AddToTail(); + if ( isAmbient ) + { + // start at 0 and fade in + enginesound->EmitAmbientSound( pSoundName, 0, pitch ); + m_loopingSounds[soundSlot].volumeCurrent = 0.0; + } + else + { + // non-ambients at 0 volume are culled, so start at 0.05 + CLocalPlayerFilter filter; + + EmitSound_t ep; + ep.m_nChannel = CHAN_STATIC; + ep.m_pSoundName = pSoundName; + ep.m_flVolume = 0.05; + ep.m_SoundLevel = soundlevel; + ep.m_nPitch = pitch; + ep.m_pOrigin = &position; + + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); + m_loopingSounds[soundSlot].volumeCurrent = 0.05; + } + } + loopingsound_t &sound = m_loopingSounds[soundSlot]; + // fill out the slot + sound.pWaveName = pSoundName; + sound.volumeTarget = volume; + sound.pitch = pitch; + sound.id = m_loopingSoundId; + sound.isAmbient = isAmbient; + sound.position = position; + sound.soundlevel = soundlevel; + + if (bForceSoundUpdate) + { + UpdateLoopingSound(sound); + } + + return soundSlot; +} + +// stop this loop forever +void C_SoundscapeSystem::StopLoopingSound( loopingsound_t &loopSound ) +{ + if ( loopSound.isAmbient ) + { + enginesound->EmitAmbientSound( loopSound.pWaveName, 0, 0, SND_STOP ); + } + else + { + C_BaseEntity::StopSound( SOUND_FROM_WORLD, CHAN_STATIC, loopSound.pWaveName ); + } +} + +// update with new volume +void C_SoundscapeSystem::UpdateLoopingSound( loopingsound_t &loopSound ) +{ + if ( loopSound.isAmbient ) + { + enginesound->EmitAmbientSound( loopSound.pWaveName, loopSound.volumeCurrent, loopSound.pitch, SND_CHANGE_VOL ); + } + else + { + CLocalPlayerFilter filter; + + EmitSound_t ep; + ep.m_nChannel = CHAN_STATIC; + ep.m_pSoundName = loopSound.pWaveName; + ep.m_flVolume = loopSound.volumeCurrent; + ep.m_SoundLevel = loopSound.soundlevel; + ep.m_nFlags = SND_CHANGE_VOL; + ep.m_nPitch = loopSound.pitch; + ep.m_pOrigin = &loopSound.position; + + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); + } +} + +// add a recurring random sound event +int C_SoundscapeSystem::AddRandomSound( const randomsound_t &sound ) +{ + int index = m_randomSounds.AddToTail( sound ); + m_randomSounds[index].nextPlayTime = gpGlobals->curtime + 0.5 * RandomInterval( sound.time ); + + return index; +} + +// play a random sound randomly from this parameterization table +void C_SoundscapeSystem::PlayRandomSound( randomsound_t &sound ) +{ + Assert( sound.waveCount > 0 ); + + int waveId = random->RandomInt( 0, sound.waveCount-1 ); + KeyValues *pWaves = sound.pWaves; + while ( waveId > 0 && pWaves ) + { + pWaves = pWaves->GetNextKey(); + waveId--; + } + if ( !pWaves ) + return; + + const char *pWaveName = pWaves->GetString(); + + if ( !pWaveName ) + return; + + if ( sound.isAmbient ) + { + enginesound->EmitAmbientSound( pWaveName, sound.masterVolume * RandomInterval( sound.volume ), (int)RandomInterval( sound.pitch ) ); + } + else + { + CLocalPlayerFilter filter; + + EmitSound_t ep; + ep.m_nChannel = CHAN_STATIC; + ep.m_pSoundName = pWaveName; + ep.m_flVolume = sound.masterVolume * RandomInterval( sound.volume ); + ep.m_SoundLevel = (soundlevel_t)(int)RandomInterval( sound.soundlevel ); + ep.m_nPitch = (int)RandomInterval( sound.pitch ); + if ( sound.isRandom ) + { + sound.position = GenerateRandomSoundPosition(); + } + ep.m_pOrigin = &sound.position; + + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); + } +} + +// walk the list of random sound commands and update +void C_SoundscapeSystem::UpdateRandomSounds( float gameTime ) +{ + if ( gameTime < m_nextRandomTime ) + return; + + m_nextRandomTime = gameTime + 3600; // add some big time to check again (an hour) + + for ( int i = m_randomSounds.Count()-1; i >= 0; i-- ) + { + // time to play? + if ( gameTime >= m_randomSounds[i].nextPlayTime ) + { + // UNDONE: add this in to fix range? + // float dt = m_randomSounds[i].nextPlayTime - gameTime; + PlayRandomSound( m_randomSounds[i] ); + + // now schedule the next occurrance + // UNDONE: add support for "play once" sounds? FastRemove() here. + m_randomSounds[i].nextPlayTime = gameTime + RandomInterval( m_randomSounds[i].time ); + } + + // update next time to check the queue + if ( m_randomSounds[i].nextPlayTime < m_nextRandomTime ) + { + m_nextRandomTime = m_randomSounds[i].nextPlayTime; + } + } +} + + + +CON_COMMAND(cl_soundscape_printdebuginfo, "print soundscapes") +{ + g_SoundscapeSystem.PrintDebugInfo(); +} diff --git a/game/client/c_soundscape.h b/game/client/c_soundscape.h new file mode 100644 index 00000000..affbb6e3 --- /dev/null +++ b/game/client/c_soundscape.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_SOUNDSCAPE_H +#define C_SOUNDSCAPE_H +#ifdef _WIN32 +#pragma once +#endif + + +class IGameSystem; +struct audioparams_t; + +extern IGameSystem *ClientSoundscapeSystem(); + +// call when audio params may have changed +extern void Soundscape_Update( audioparams_t &audio ); + +// Called on round restart, otherwise the soundscape system thinks all its +// sounds are still playing when they're not. +void Soundscape_OnStopAllSounds(); + +#endif // C_SOUNDSCAPE_H diff --git a/game/client/c_spotlight_end.cpp b/game/client/c_spotlight_end.cpp new file mode 100644 index 00000000..d2960a44 --- /dev/null +++ b/game/client/c_spotlight_end.cpp @@ -0,0 +1,156 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "dlight.h" +#include "iefx.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//################################################################## +// +// PlasmaBeamNode - generates plasma embers +// +//################################################################## +class C_SpotlightEnd : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_SpotlightEnd, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + DECLARE_INTERPOLATION(); + + C_SpotlightEnd(); + +public: + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + void ClientThink( void ); + + virtual bool ShouldInterpolate(); + + +// Vector m_vSpotlightOrg; +// Vector m_vSpotlightDir; + float m_flLightScale; + float m_Radius; + +private: + dlight_t* m_pDynamicLight; + + //dlight_t* m_pModelLight; +}; + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +C_SpotlightEnd::C_SpotlightEnd(void) : /*m_pModelLight(0), */m_pDynamicLight(0) +{ + m_flLightScale = 100; +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_SpotlightEnd::OnDataChanged(DataUpdateType_t updateType) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink(CLIENT_THINK_ALWAYS); + } +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +bool C_SpotlightEnd::ShouldDraw() +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: YWB: This is a hack, BaseClass::Interpolate skips this entity because model == NULL +// We could do something like model = (model_t *)0x00000001, but that's probably more evil. +// Input : currentTime - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_SpotlightEnd::ShouldInterpolate() +{ + return true; +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void C_SpotlightEnd::ClientThink(void) +{ + // If light scale is zero, don't draw light + if ( m_flLightScale <= 0 ) + return; + + ColorRGBExp32 color; + color.r = m_clrRender->r * m_clrRender->a; + color.g = m_clrRender->g * m_clrRender->a; + color.b = m_clrRender->b * m_clrRender->a; + color.exponent = 0; + if ( color.r == 0 && color.g == 0 && color.b == 0 ) + return; + + // Deal with the environment light + if ( !m_pDynamicLight || (m_pDynamicLight->key != index) ) + { + m_pDynamicLight = effects->CL_AllocDlight( index ); + assert (m_pDynamicLight); + } + + //m_pDynamicLight->flags = DLIGHT_NO_MODEL_ILLUMINATION; + m_pDynamicLight->radius = m_flLightScale*3.0f; + m_pDynamicLight->origin = GetAbsOrigin() + Vector(0,0,5); + m_pDynamicLight->die = gpGlobals->curtime + 0.05f; + m_pDynamicLight->color = color; + + /* + // For bumped lighting + VectorCopy (m_vSpotlightDir, m_pDynamicLight->m_Direction); + + // Deal with the model light + if ( !m_pModelLight || (m_pModelLight->key != -index) ) + { + m_pModelLight = effects->CL_AllocDlight( -index ); + assert (m_pModelLight); + } + + m_pModelLight->radius = m_Radius; + m_pModelLight->flags = DLIGHT_NO_WORLD_ILLUMINATION; + m_pModelLight->color.r = m_clrRender->r * m_clrRender->a; + m_pModelLight->color.g = m_clrRender->g * m_clrRender->a; + m_pModelLight->color.b = m_clrRender->b * m_clrRender->a; + m_pModelLight->color.exponent = 1; + m_pModelLight->origin = m_vSpotlightOrg; + m_pModelLight->m_InnerAngle = 6; + m_pModelLight->m_OuterAngle = 8; + m_pModelLight->die = gpGlobals->curtime + 0.05; + VectorCopy( m_vSpotlightDir, m_pModelLight->m_Direction ); + */ + + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +IMPLEMENT_CLIENTCLASS_DT(C_SpotlightEnd, DT_SpotlightEnd, CSpotlightEnd) + RecvPropFloat (RECVINFO(m_flLightScale)), + RecvPropFloat (RECVINFO(m_Radius)), +// RecvPropVector (RECVINFO(m_vSpotlightOrg)), +// RecvPropVector (RECVINFO(m_vSpotlightDir)), +END_RECV_TABLE() diff --git a/game/client/c_sprite.cpp b/game/client/c_sprite.cpp new file mode 100644 index 00000000..ee64f006 --- /dev/null +++ b/game/client/c_sprite.cpp @@ -0,0 +1,502 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_sprite.h" +#include "model_types.h" +#include "iviewrender.h" +#include "view.h" +#include "enginesprite.h" +#include "engine/ivmodelinfo.h" +#include "util_shared.h" +#include "tier0/vprof.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/IMaterialVar.h" +#include "view_shared.h" +#include "viewrender.h" +#include "tier1/KeyValues.h" +#include "toolframework/itoolframework.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar r_drawsprites( "r_drawsprites", "1", FCVAR_CHEAT ); + +//----------------------------------------------------------------------------- +// Purpose: Generic sprite model renderer +// Input : *baseentity - +// *psprite - +// fscale - +// frame - +// rendermode - +// r - +// g - +// b - +// a - +// forward - +// right - +// up - +//----------------------------------------------------------------------------- +static unsigned int s_nHDRColorScaleCache = 0; +void DrawSpriteModel( IClientEntity *baseentity, CEngineSprite *psprite, const Vector &origin, float fscale, float frame, + int rendermode, int r, int g, int b, int a, const Vector& forward, const Vector& right, const Vector& up, float flHDRColorScale ) +{ + float scale; + IMaterial *material; + + // don't even bother culling, because it's just a single + // polygon without a surface cache + if ( fscale > 0 ) + scale = fscale; + else + scale = 1.0f; + + if ( rendermode == kRenderNormal ) + render->SetBlend( 1.0f ); + + material = psprite->GetMaterial(); + if ( !material ) + { + return; + } + psprite->SetRenderMode( rendermode ); + psprite->SetFrame( frame ); + + CMatRenderContextPtr pRenderContext( materials ); + + if ( ShouldDrawInWireFrameMode() || r_drawsprites.GetInt() == 2 ) + { + IMaterial *pMaterial = materials->FindMaterial( "debug/debugspritewireframe", TEXTURE_GROUP_OTHER ); + pRenderContext->Bind( pMaterial, NULL ); + } + else + { + pRenderContext->Bind( material, (IClientRenderable*)baseentity ); + } + + unsigned char color[4]; + color[0] = r; + color[1] = g; + color[2] = b; + color[3] = a; + + IMaterialVar *pHDRColorScaleVar = material->FindVarFast( "$HDRCOLORSCALE", &s_nHDRColorScaleCache ); + if( pHDRColorScaleVar ) + { + pHDRColorScaleVar->SetVecValue( flHDRColorScale, flHDRColorScale, flHDRColorScale ); + } + + Vector point; + IMesh* pMesh = pRenderContext->GetDynamicMesh(); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + Vector vec_a; + Vector vec_b; + Vector vec_c; + Vector vec_d; + + // isolate common terms + VectorMA( origin, psprite->GetDown() * scale, up, vec_a ); + VectorScale( right, psprite->GetLeft() * scale, vec_b ); + VectorMA( origin, psprite->GetUp() * scale, up, vec_c ); + VectorScale( right, psprite->GetRight() * scale, vec_d ); + + float flMinU, flMinV, flMaxU, flMaxV; + psprite->GetTexCoordRange( &flMinU, &flMinV, &flMaxU, &flMaxV ); + + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, flMinU, flMaxV ); + VectorAdd( vec_a, vec_b, point ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, flMinU, flMinV ); + VectorAdd( vec_c, vec_b, point ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, flMaxU, flMinV ); + VectorAdd( vec_c, vec_d, point ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, flMaxU, flMaxV ); + VectorAdd( vec_a, vec_d, point ); + meshBuilder.Position3fv( point.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Determine glow brightness/scale based on distance to render origin and trace results +// Input : entorigin - +// rendermode - +// renderfx - +// alpha - +// pscale - Pointer to the value for scale, will be changed based on distance and rendermode. +//----------------------------------------------------------------------------- +float StandardGlowBlend( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle, int rendermode, int renderfx, int alpha, float *pscale ) +{ + float dist; + float brightness; + + brightness = PixelVisibility_FractionVisible( params, queryHandle ); + if ( brightness <= 0.0f ) + { + return 0.0f; + } + dist = GlowSightDistance( params.position, false ); + if ( dist <= 0.0f ) + { + return 0.0f; + } + + if ( renderfx == kRenderFxNoDissipation ) + { + return (float)alpha * (1.0f/255.0f) * brightness; + } + + // UNDONE: Tweak these magic numbers (1200 - distance at full brightness) + float fadeOut = (1200.0f*1200.0f) / (dist*dist); + fadeOut = clamp( fadeOut, 0.0f, 1.0f ); + + if (rendermode != kRenderWorldGlow) + { + // Make the glow fixed size in screen space, taking into consideration the scale setting. + if ( *pscale == 0.0f ) + { + *pscale = 1.0f; + } + + *pscale *= dist * (1.0f/200.0f); + } + + return fadeOut * brightness; +} + +static float SpriteAspect( CEngineSprite *pSprite ) +{ + if ( pSprite ) + { + float x = fabsf(pSprite->GetRight() - pSprite->GetLeft()); + float y = fabsf(pSprite->GetDown() - pSprite->GetUp()); + if ( y != 0 && x != 0 ) + { + return x / y; + } + } + + return 1.0f; +} + +float C_SpriteRenderer::GlowBlend( CEngineSprite *psprite, const Vector& entorigin, int rendermode, int renderfx, int alpha, float *pscale ) +{ + pixelvis_queryparams_t params; + float aspect = SpriteAspect(psprite); + params.Init( entorigin, PIXELVIS_DEFAULT_PROXY_SIZE, aspect ); + return StandardGlowBlend( params, &m_queryHandle, rendermode, renderfx, alpha, pscale ); +} + +// since sprites can network down a glow proxy size, handle that here +float CSprite::GlowBlend( CEngineSprite *psprite, const Vector& entorigin, int rendermode, int renderfx, int alpha, float *pscale ) +{ + pixelvis_queryparams_t params; + float aspect = SpriteAspect(psprite); + params.Init( entorigin, m_flGlowProxySize, aspect ); + return StandardGlowBlend( params, &m_queryHandle, rendermode, renderfx, alpha, pscale ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Determine sprite orientation axes +// Input : type - +// forward - +// right - +// up - +//----------------------------------------------------------------------------- +void C_SpriteRenderer::GetSpriteAxes( SPRITETYPE type, + const Vector& origin, + const QAngle& angles, + Vector& forward, + Vector& right, + Vector& up ) +{ + int i; + float dot, angle, sr, cr; + Vector tvec; + + // Automatically roll parallel sprites if requested + if ( angles[2] != 0 && type == SPR_VP_PARALLEL ) + { + type = SPR_VP_PARALLEL_ORIENTED; + } + + switch( type ) + { + case SPR_FACING_UPRIGHT: + { + // generate the sprite's axes, with vup straight up in worldspace, and + // r_spritedesc.vright perpendicular to modelorg. + // This will not work if the view direction is very close to straight up or + // down, because the cross product will be between two nearly parallel + // vectors and starts to approach an undefined state, so we don't draw if + // the two vectors are less than 1 degree apart + tvec[0] = -origin[0]; + tvec[1] = -origin[1]; + tvec[2] = -origin[2]; + VectorNormalize (tvec); + dot = tvec[2]; // same as DotProduct (tvec, r_spritedesc.vup) because + // r_spritedesc.vup is 0, 0, 1 + if ((dot > 0.999848f) || (dot < -0.999848f)) // cos(1 degree) = 0.999848 + return; + up[0] = 0; + up[1] = 0; + up[2] = 1; + right[0] = tvec[1]; + // CrossProduct(r_spritedesc.vup, -modelorg, + right[1] = -tvec[0]; + // r_spritedesc.vright) + right[2] = 0; + VectorNormalize (right); + forward[0] = -right[1]; + forward[1] = right[0]; + forward[2] = 0; + // CrossProduct (r_spritedesc.vright, r_spritedesc.vup, + // r_spritedesc.vpn) + } + break; + + case SPR_VP_PARALLEL: + { + // generate the sprite's axes, completely parallel to the viewplane. There + // are no problem situations, because the sprite is always in the same + // position relative to the viewer + for (i=0 ; i<3 ; i++) + { + up[i] = CurrentViewUp()[i]; + right[i] = CurrentViewRight()[i]; + forward[i] = CurrentViewForward()[i]; + } + } + break; + + case SPR_VP_PARALLEL_UPRIGHT: + { + // generate the sprite's axes, with g_vecVUp straight up in worldspace, and + // r_spritedesc.vright parallel to the viewplane. + // This will not work if the view direction is very close to straight up or + // down, because the cross product will be between two nearly parallel + // vectors and starts to approach an undefined state, so we don't draw if + // the two vectors are less than 1 degree apart + dot = CurrentViewForward()[2]; // same as DotProduct (vpn, r_spritedesc.g_vecVUp) because + // r_spritedesc.vup is 0, 0, 1 + if ((dot > 0.999848f) || (dot < -0.999848f)) // cos(1 degree) = 0.999848 + return; + up[0] = 0; + up[1] = 0; + up[2] = 1; + right[0] = CurrentViewForward()[1]; + // CrossProduct (r_spritedesc.vup, vpn, + right[1] = -CurrentViewForward()[0]; // r_spritedesc.vright) + right[2] = 0; + VectorNormalize (right); + forward[0] = -right[1]; + forward[1] = right[0]; + forward[2] = 0; + // CrossProduct (r_spritedesc.vright, r_spritedesc.vup, + // r_spritedesc.vpn) + } + break; + + case SPR_ORIENTED: + { + // generate the sprite's axes, according to the sprite's world orientation + AngleVectors( angles, &forward, &right, &up ); + } + break; + + case SPR_VP_PARALLEL_ORIENTED: + { + // generate the sprite's axes, parallel to the viewplane, but rotated in + // that plane around the center according to the sprite entity's roll + // angle. So vpn stays the same, but vright and vup rotate + angle = angles[ROLL] * (M_PI*2.0f/360.0f); + SinCos( angle, &sr, &cr ); + + for (i=0 ; i<3 ; i++) + { + forward[i] = CurrentViewForward()[i]; + right[i] = CurrentViewRight()[i] * cr + CurrentViewUp()[i] * sr; + up[i] = CurrentViewRight()[i] * -sr + CurrentViewUp()[i] * cr; + } + } + break; + + default: + Warning( "GetSpriteAxes: Bad sprite type %d\n", type ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int C_SpriteRenderer::DrawSprite( + IClientEntity *entity, + const model_t *model, + const Vector& origin, + const QAngle& angles, + float frame, + IClientEntity *attachedto, + int attachmentindex, + int rendermode, + int renderfx, + int alpha, + int r, + int g, + int b, + float scale, + float flHDRColorScale + ) +{ + VPROF_BUDGET( "C_SpriteRenderer::DrawSprite", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + if ( !r_drawsprites.GetBool() || !model || modelinfo->GetModelType( model ) != mod_sprite ) + { + return 0; + } + + // Get extra data + CEngineSprite *psprite = (CEngineSprite *)modelinfo->GetModelExtraData( model ); + if ( !psprite ) + { + return 0; + } + + Vector effect_origin; + VectorCopy( origin, effect_origin ); + + // Use attachment point + if ( attachedto ) + { + C_BaseEntity *ent = attachedto->GetBaseEntity(); + if ( ent ) + { + // don't draw viewmodel effects in reflections + if ( CurrentViewID() == VIEW_REFLECTION ) + { + int group = ent->GetRenderGroup(); + if ( group == RENDER_GROUP_VIEW_MODEL_TRANSLUCENT || group == RENDER_GROUP_VIEW_MODEL_OPAQUE ) + return 0; + } + QAngle temp; + ent->GetAttachment( attachmentindex, effect_origin, temp ); + } + } + + if ( rendermode != kRenderNormal ) + { + float blend = render->GetBlend(); + + // kRenderGlow and kRenderWorldGlow have a special blending function + if (( rendermode == kRenderGlow ) || ( rendermode == kRenderWorldGlow )) + { + blend *= GlowBlend( psprite, effect_origin, rendermode, renderfx, alpha, &scale ); + + // Fade out the sprite depending on distance from the view origin. + r *= blend; + g *= blend; + b *= blend; + } + + render->SetBlend( blend ); + if ( blend <= 0.0f ) + { + return 0; + } + } + + // Get orthonormal basis + Vector forward, right, up; + GetSpriteAxes( (SPRITETYPE)psprite->GetOrientation(), origin, angles, forward, right, up ); + + // Draw + DrawSpriteModel( + entity, + psprite, + effect_origin, + scale, + frame, + rendermode, + r, + g, + b, + alpha, + forward, right, up, flHDRColorScale ); + + return 1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSprite::GetToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + VPROF_BUDGET( "CSprite::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); + + BaseClass::GetToolRecordingState( msg ); + + // Use attachment point + if ( m_hAttachedToEntity ) + { + C_BaseEntity *ent = m_hAttachedToEntity->GetBaseEntity(); + if ( ent ) + { + BaseEntityRecordingState_t *pState = (BaseEntityRecordingState_t*)msg->GetPtr( "baseentity" ); + + // override position if we're driven by an attachment + QAngle temp; + pState->m_vecRenderOrigin = GetAbsOrigin(); + ent->GetAttachment( m_nAttachment, pState->m_vecRenderOrigin, temp ); + + // override viewmodel if we're driven by an attachment + bool bViewModel = dynamic_cast< C_BaseViewModel* >( ent ) != NULL; + msg->SetInt( "viewmodel", bViewModel ); + } + } + + float renderscale = GetRenderScale(); + if ( m_bWorldSpaceScale ) + { + CEngineSprite *psprite = ( CEngineSprite * )modelinfo->GetModelExtraData( GetModel() ); + float flMinSize = min( psprite->GetWidth(), psprite->GetHeight() ); + renderscale /= flMinSize; + } + + // sprite params + static SpriteRecordingState_t state; + state.m_flRenderScale = renderscale; + state.m_flFrame = m_flFrame; + state.m_flProxyRadius = m_flGlowProxySize; + state.m_nRenderMode = GetRenderMode(); + state.m_nRenderFX = m_nRenderFX; + state.m_Color.SetColor( m_clrRender.GetR(), m_clrRender.GetG(), m_clrRender.GetB(), GetRenderBrightness() ); + + msg->SetPtr( "sprite", &state ); +} diff --git a/game/client/c_sprite.h b/game/client/c_sprite.h new file mode 100644 index 00000000..7942c5d7 --- /dev/null +++ b/game/client/c_sprite.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( C_SPRITE_H ) +#define C_SPRITE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "sprite.h" +#include "c_pixel_visibility.h" + +#endif // C_SPRITE_H diff --git a/game/client/c_sprite_perfmonitor.cpp b/game/client/c_sprite_perfmonitor.cpp new file mode 100644 index 00000000..a496fec1 --- /dev/null +++ b/game/client/c_sprite_perfmonitor.cpp @@ -0,0 +1,68 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#include "cbase.h" + +extern bool g_bMeasureParticlePerformance; +extern bool g_bDisplayParticlePerformance; + +void ResetParticlePerformanceCounters( void ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_ParticlePerformanceMonitor : public C_BaseEntity +{ + DECLARE_CLASS( C_ParticlePerformanceMonitor, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_ParticlePerformanceMonitor(); + ~C_ParticlePerformanceMonitor(); + virtual void OnDataChanged( DataUpdateType_t updateType ); + +private: + bool m_bDisplayPerf; + bool m_bMeasurePerf; +private: + C_ParticlePerformanceMonitor( const C_ParticlePerformanceMonitor & ); +}; + +IMPLEMENT_CLIENTCLASS_DT( C_ParticlePerformanceMonitor, DT_ParticlePerformanceMonitor, CParticlePerformanceMonitor ) + RecvPropInt( RECVINFO(m_bMeasurePerf) ), + RecvPropInt( RECVINFO(m_bDisplayPerf) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_ParticlePerformanceMonitor::C_ParticlePerformanceMonitor( void ) +{ + m_bDisplayPerf = false; + m_bMeasurePerf = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_ParticlePerformanceMonitor::~C_ParticlePerformanceMonitor( void ) +{ + g_bMeasureParticlePerformance = false; + g_bDisplayParticlePerformance = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_ParticlePerformanceMonitor::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged(updateType); + + if ( m_bMeasurePerf && ( ! g_bMeasureParticlePerformance ) ) + ResetParticlePerformanceCounters(); + g_bMeasureParticlePerformance = m_bMeasurePerf; + g_bDisplayParticlePerformance = m_bDisplayPerf; +} + diff --git a/game/client/c_steamjet.cpp b/game/client/c_steamjet.cpp new file mode 100644 index 00000000..0b1a28b0 --- /dev/null +++ b/game/client/c_steamjet.cpp @@ -0,0 +1,525 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements a particle system steam jet. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "particle_prototype.h" +#include "particle_util.h" +#include "baseparticleentity.h" +#include "ClientEffectPrecacheSystem.h" +#include "fx.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//NOTENOTE: Mirrored in dlls\steamjet.h +#define STEAM_NORMAL 0 +#define STEAM_HEATWAVE 1 + +#define STEAMJET_NUMRAMPS 5 +#define SF_EMISSIVE 0x00000001 + + +//================================================== +// C_SteamJet +//================================================== + +class C_SteamJet : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLIENTCLASS(); + DECLARE_CLASS( C_SteamJet, C_BaseParticleEntity ); + + C_SteamJet(); + ~C_SteamJet(); + + class SteamJetParticle : public Particle + { + public: + Vector m_Velocity; + float m_flRoll; + float m_flRollDelta; + float m_Lifetime; + float m_DieTime; + unsigned char m_uchStartSize; + unsigned char m_uchEndSize; + }; + + int IsEmissive( void ) { return ( m_spawnflags & SF_EMISSIVE ); } + +//C_BaseEntity +public: + + virtual void OnDataChanged( DataUpdateType_t updateType ); + + +//IPrototypeAppEffect +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + virtual bool GetPropEditInfo(RecvTable **ppTable, void **ppObj); + + +//IParticleEffect +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + +//Stuff from the datatable +public: + + float m_SpreadSpeed; + float m_Speed; + float m_StartSize; + float m_EndSize; + float m_Rate; + float m_JetLength; // Length of the jet. Lifetime is derived from this. + + int m_bEmit; // Emit particles? + int m_nType; // Type of particles to emit + bool m_bFaceLeft; // For support of legacy env_steamjet entity, which faced left instead of forward. + + int m_spawnflags; + float m_flRollSpeed; + +private: + + void UpdateLightingRamp(); + +private: + + // Stored the last time it updates the lighting ramp, so it can cache the values. + Vector m_vLastRampUpdatePos; + QAngle m_vLastRampUpdateAngles; + + float m_Lifetime; // Calculated from m_JetLength / m_Speed; + + // We sample the world to get these colors and ramp the particles. + Vector m_Ramps[STEAMJET_NUMRAMPS]; + + CParticleMgr *m_pParticleMgr; + PMaterialHandle m_MaterialHandle; + TimedEvent m_ParticleSpawn; + +private: + C_SteamJet( const C_SteamJet & ); +}; + + +// ------------------------------------------------------------------------- // +// Tables. +// ------------------------------------------------------------------------- // + +// Expose to the particle app. +EXPOSE_PROTOTYPE_EFFECT(SteamJet, C_SteamJet); + + +// Datatable.. +IMPLEMENT_CLIENTCLASS_DT(C_SteamJet, DT_SteamJet, CSteamJet) + RecvPropFloat(RECVINFO(m_SpreadSpeed), 0), + RecvPropFloat(RECVINFO(m_Speed), 0), + RecvPropFloat(RECVINFO(m_StartSize), 0), + RecvPropFloat(RECVINFO(m_EndSize), 0), + RecvPropFloat(RECVINFO(m_Rate), 0), + RecvPropFloat(RECVINFO(m_JetLength), 0), + RecvPropInt(RECVINFO(m_bEmit), 0), + RecvPropInt(RECVINFO(m_bFaceLeft), 0), + RecvPropInt(RECVINFO(m_nType), 0), + RecvPropInt( RECVINFO( m_spawnflags ) ), + RecvPropFloat(RECVINFO(m_flRollSpeed), 0 ), +END_RECV_TABLE() + +// ------------------------------------------------------------------------- // +// C_SteamJet implementation. +// ------------------------------------------------------------------------- // +C_SteamJet::C_SteamJet() +{ + m_pParticleMgr = NULL; + m_MaterialHandle = INVALID_MATERIAL_HANDLE; + + m_SpreadSpeed = 15; + m_Speed = 120; + m_StartSize = 10; + m_EndSize = 25; + m_Rate = 26; + m_JetLength = 80; + m_bEmit = true; + m_bFaceLeft = false; + m_ParticleEffect.SetAlwaysSimulate( false ); // Don't simulate outside the PVS or frustum. + + m_vLastRampUpdatePos.Init( 1e24, 1e24, 1e24 ); + m_vLastRampUpdateAngles.Init( 1e24, 1e24, 1e24 ); +} + + +C_SteamJet::~C_SteamJet() +{ + if(m_pParticleMgr) + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called after a data update has occured +// Input : bnewentity - +//----------------------------------------------------------------------------- +void C_SteamJet::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if(updateType == DATA_UPDATE_CREATED) + { + Start(ParticleMgr(), NULL); + } + + // Recalulate lifetime in case length or speed changed. + m_Lifetime = m_JetLength / m_Speed; + m_ParticleEffect.SetParticleCullRadius( max(m_StartSize, m_EndSize) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Starts the effect +// Input : *pParticleMgr - +// *pArgs - +//----------------------------------------------------------------------------- +void C_SteamJet::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) +{ + pParticleMgr->AddEffect( &m_ParticleEffect, this ); + + switch(m_nType) + { + case STEAM_NORMAL: + default: + m_MaterialHandle = g_Mat_DustPuff[0]; + break; + + case STEAM_HEATWAVE: + m_MaterialHandle = m_ParticleEffect.FindOrAddMaterial("sprites/heatwave"); + break; + } + + m_ParticleSpawn.Init(m_Rate); + m_Lifetime = m_JetLength / m_Speed; + m_pParticleMgr = pParticleMgr; + + UpdateLightingRamp(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : **ppTable - +// **ppObj - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_SteamJet::GetPropEditInfo( RecvTable **ppTable, void **ppObj ) +{ + *ppTable = &REFERENCE_RECV_TABLE(DT_SteamJet); + *ppObj = this; + return true; +} + + +// This might be useful someday. +/* +void CalcFastApproximateRenderBoundsAABB( C_BaseEntity *pEnt, float flBloatSize, Vector *pMin, Vector *pMax ) +{ + C_BaseEntity *pParent = pEnt->GetMoveParent(); + if ( pParent ) + { + // Get the parent's abs space world bounds. + CalcFastApproximateRenderBoundsAABB( pParent, 0, pMin, pMax ); + + // Add the maximum of our local render bounds. This is making the assumption that we can be at any + // point and at any angle within the parent's world space bounds. + Vector vAddMins, vAddMaxs; + pEnt->GetRenderBounds( vAddMins, vAddMaxs ); + + flBloatSize += max( vAddMins.Length(), vAddMaxs.Length() ); + } + else + { + // Start out with our own render bounds. Since we don't have a parent, this won't incur any nasty + pEnt->GetRenderBoundsWorldspace( *pMin, *pMax ); + } + + // Bloat the box. + if ( flBloatSize ) + { + *pMin -= Vector( flBloatSize, flBloatSize, flBloatSize ); + *pMax += Vector( flBloatSize, flBloatSize, flBloatSize ); + } +} +*/ + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fTimeDelta - +//----------------------------------------------------------------------------- + +void C_SteamJet::Update(float fTimeDelta) +{ + if(!m_pParticleMgr) + { + assert(false); + return; + } + + if( m_bEmit ) + { + // Add new particles. + int nToEmit = 0; + float tempDelta = fTimeDelta; + while( m_ParticleSpawn.NextEvent(tempDelta) ) + ++nToEmit; + + if ( nToEmit > 0 ) + { + Vector forward, right, up; + AngleVectors(GetAbsAngles(), &forward, &right, &up); + + // Legacy env_steamjet entities faced left instead of forward. + if (m_bFaceLeft) + { + Vector temp = forward; + forward = -right; + right = temp; + } + + // EVIL: Ideally, we could tell the renderer our OBB, and let it build a big box that encloses + // the entity with its parent so it doesn't have to setup its parent's bones here. + Vector vEndPoint = GetAbsOrigin() + forward * m_Speed; + Vector vMin, vMax; + VectorMin( GetAbsOrigin(), vEndPoint, vMin ); + VectorMax( GetAbsOrigin(), vEndPoint, vMax ); + m_ParticleEffect.SetBBox( vMin, vMax ); + + if ( m_ParticleEffect.WasDrawnPrevFrame() ) + { + while ( nToEmit-- ) + { + // Make a new particle. + if( SteamJetParticle *pParticle = (SteamJetParticle*) m_ParticleEffect.AddParticle( sizeof(SteamJetParticle), m_MaterialHandle ) ) + { + pParticle->m_Pos = GetAbsOrigin(); + + pParticle->m_Velocity = + FRand(-m_SpreadSpeed,m_SpreadSpeed) * right + + FRand(-m_SpreadSpeed,m_SpreadSpeed) * up + + m_Speed * forward; + + pParticle->m_Lifetime = 0; + pParticle->m_DieTime = m_Lifetime; + + pParticle->m_uchStartSize = m_StartSize; + pParticle->m_uchEndSize = m_EndSize; + + pParticle->m_flRoll = random->RandomFloat( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -m_flRollSpeed, m_flRollSpeed ); + } + } + } + + UpdateLightingRamp(); + } + } +} + + +// Render a quad on the screen where you pass in color and size. +// Normal is random and "flutters" +inline void RenderParticle_ColorSizePerturbNormal( + ParticleDraw* pDraw, + const Vector &pos, + const Vector &color, + const float alpha, + const float size + ) +{ + // Don't render totally transparent particles. + if( alpha < 0.001f ) + return; + + CMeshBuilder *pBuilder = pDraw->GetMeshBuilder(); + if( !pBuilder ) + return; + + unsigned char ubColor[4]; + ubColor[0] = (unsigned char)RoundFloatToInt( color.x * 254.9f ); + ubColor[1] = (unsigned char)RoundFloatToInt( color.y * 254.9f ); + ubColor[2] = (unsigned char)RoundFloatToInt( color.z * 254.9f ); + ubColor[3] = (unsigned char)RoundFloatToInt( alpha * 254.9f ); + + Vector vNorm; + + vNorm.Random( -1.0f, 1.0f ); + + // Add the 4 corner vertices. + pBuilder->Position3f( pos.x-size, pos.y-size, pos.z ); + pBuilder->Color4ubv( ubColor ); + pBuilder->Normal3fv( vNorm.Base() ); + pBuilder->TexCoord2f( 0, 0, 1.0f ); + pBuilder->AdvanceVertex(); + + pBuilder->Position3f( pos.x-size, pos.y+size, pos.z ); + pBuilder->Color4ubv( ubColor ); + pBuilder->Normal3fv( vNorm.Base() ); + pBuilder->TexCoord2f( 0, 0, 0 ); + pBuilder->AdvanceVertex(); + + pBuilder->Position3f( pos.x+size, pos.y+size, pos.z ); + pBuilder->Color4ubv( ubColor ); + pBuilder->Normal3fv( vNorm.Base() ); + pBuilder->TexCoord2f( 0, 1.0f, 0 ); + pBuilder->AdvanceVertex(); + + pBuilder->Position3f( pos.x+size, pos.y-size, pos.z ); + pBuilder->Color4ubv( ubColor ); + pBuilder->Normal3fv( vNorm.Base() ); + pBuilder->TexCoord2f( 0, 1.0f, 1.0f ); + pBuilder->AdvanceVertex(); +} + + +void C_SteamJet::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const SteamJetParticle *pParticle = (const SteamJetParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Render. + Vector tPos; + TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos); + float sortKey = tPos.z; + + float lifetimeT = pParticle->m_Lifetime / (pParticle->m_DieTime + 0.001); + float fRamp = lifetimeT * (STEAMJET_NUMRAMPS-1); + int iRamp = (int)fRamp; + float fraction = fRamp - iRamp; + + Vector vRampColor = m_Ramps[iRamp] + (m_Ramps[iRamp+1] - m_Ramps[iRamp]) * fraction; + + vRampColor[0] = min( 1.0f, vRampColor[0] ); + vRampColor[1] = min( 1.0f, vRampColor[1] ); + vRampColor[2] = min( 1.0f, vRampColor[2] ); + + float sinLifetime = sin(pParticle->m_Lifetime * 3.14159f / pParticle->m_DieTime); + + if ( m_nType == STEAM_HEATWAVE ) + { + RenderParticle_ColorSizePerturbNormal( + pIterator->GetParticleDraw(), + tPos, + vRampColor, + sinLifetime * (m_clrRender->a/255.0f), + FLerp(m_StartSize, m_EndSize, pParticle->m_Lifetime)); + } + else + { + RenderParticle_ColorSizeAngle( + pIterator->GetParticleDraw(), + tPos, + vRampColor, + sinLifetime * (m_clrRender->a/255.0f), + FLerp(pParticle->m_uchStartSize, pParticle->m_uchEndSize, pParticle->m_Lifetime), + pParticle->m_flRoll ); + } + + pParticle = (const SteamJetParticle*)pIterator->GetNext( sortKey ); + } +} + + +void C_SteamJet::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + //Don't simulate if we're emiting particles... + //This fixes the cases where looking away from a steam jet and then looking back would cause a break on the stream. + if ( m_ParticleEffect.WasDrawnPrevFrame() == false && m_bEmit ) + return; + + SteamJetParticle *pParticle = (SteamJetParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + // Should this particle die? + pParticle->m_Lifetime += pIterator->GetTimeDelta(); + + if( pParticle->m_Lifetime > pParticle->m_DieTime ) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + pParticle->m_flRoll += pParticle->m_flRollDelta * pIterator->GetTimeDelta(); + pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * pIterator->GetTimeDelta(); + } + + pParticle = (SteamJetParticle*)pIterator->GetNext(); + } +} + + +void C_SteamJet::UpdateLightingRamp() +{ + if( VectorsAreEqual( m_vLastRampUpdatePos, GetAbsOrigin(), 0.1 ) && + QAnglesAreEqual( m_vLastRampUpdateAngles, GetAbsAngles(), 0.1 ) ) + { + return; + } + + m_vLastRampUpdatePos = GetAbsOrigin(); + m_vLastRampUpdateAngles = GetAbsAngles(); + + // Sample the world lighting where we think the particles will be. + Vector forward, right, up; + AngleVectors(GetAbsAngles(), &forward, &right, &up); + + // Legacy env_steamjet entities faced left instead of forward. + if (m_bFaceLeft) + { + Vector temp = forward; + forward = -right; + right = temp; + } + + Vector startPos = GetAbsOrigin(); + Vector endPos = GetAbsOrigin() + forward * (m_Speed * m_Lifetime); + + for(int iRamp=0; iRamp < STEAMJET_NUMRAMPS; iRamp++) + { + float t = (float)iRamp / (STEAMJET_NUMRAMPS-1); + Vector vTestPos = startPos + (endPos - startPos) * t; + + Vector *pRamp = &m_Ramps[iRamp]; + *pRamp = WorldGetLightForPoint(vTestPos, false); + + if ( IsEmissive() ) + { + pRamp->x += (m_clrRender->r/255.0f); + pRamp->y += (m_clrRender->g/255.0f); + pRamp->z += (m_clrRender->b/255.0f); + + pRamp->x = clamp( pRamp->x, 0.0f, 1.0f ); + pRamp->y = clamp( pRamp->y, 0.0f, 1.0f ); + pRamp->z = clamp( pRamp->z, 0.0f, 1.0f ); + } + else + { + pRamp->x *= (m_clrRender->r/255.0f); + pRamp->y *= (m_clrRender->g/255.0f); + pRamp->z *= (m_clrRender->b/255.0f); + } + + // Renormalize? + float maxVal = max(pRamp->x, max(pRamp->y, pRamp->z)); + if(maxVal > 1) + { + *pRamp = *pRamp / maxVal; + } + } +} + + diff --git a/game/client/c_stickybolt.cpp b/game/client/c_stickybolt.cpp new file mode 100644 index 00000000..bdf5f90b --- /dev/null +++ b/game/client/c_stickybolt.cpp @@ -0,0 +1,176 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements the Sticky Bolt code. This constraints ragdolls to the world +// after being hit by a crossbow bolt. If something here is acting funny +// let me know - Adrian. +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "fx.h" +#include "decals.h" +#include "iefx.h" +#include "engine/IEngineSound.h" +#include "materialsystem/IMaterialVar.h" +#include "ieffects.h" +#include "engine/IEngineTrace.h" +#include "vphysics/constraints.h" +#include "engine/ivmodelinfo.h" +#include "tempent.h" +#include "c_te_legacytempents.h" +#include "engine/ivdebugoverlay.h" +#include "c_te_effect_dispatch.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IPhysicsSurfaceProps *physprops; +IPhysicsObject *GetWorldPhysObject( void ); + +extern ITempEnts* tempents; + +class CRagdollBoltEnumerator : public IPartitionEnumerator +{ +public: + //Forced constructor + CRagdollBoltEnumerator( Ray_t& shot, Vector vOrigin ) + { + m_rayShot = shot; + m_vWorld = vOrigin; + } + + //Actual work code + IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) + { + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); + if ( pEnt == NULL ) + return ITERATION_CONTINUE; + + C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt ); + + if ( pModel == NULL ) + return ITERATION_CONTINUE; + + trace_t tr; + enginetrace->ClipRayToEntity( m_rayShot, MASK_SHOT, pModel, &tr ); + + IPhysicsObject *pPhysicsObject = NULL; + + //Find the real object we hit. + if( tr.physicsbone >= 0 ) + { + if ( pModel->m_pRagdoll ) + { + CRagdoll *pCRagdoll = dynamic_cast < CRagdoll * > ( pModel->m_pRagdoll ); + + if ( pCRagdoll ) + { + ragdoll_t *pRagdollT = pCRagdoll->GetRagdoll(); + + if ( tr.physicsbone < pRagdollT->listCount ) + { + pPhysicsObject = pRagdollT->list[tr.physicsbone].pObject; + } + } + } + } + + if ( pPhysicsObject == NULL ) + return ITERATION_CONTINUE; + + if ( tr.fraction < 1.0 ) + { + IPhysicsObject *pReference = GetWorldPhysObject(); + + if ( pReference == NULL || pPhysicsObject == NULL ) + return ITERATION_CONTINUE; + + float flMass = pPhysicsObject->GetMass(); + pPhysicsObject->SetMass( flMass * 2 ); + + constraint_ballsocketparams_t ballsocket; + ballsocket.Defaults(); + + pReference->WorldToLocal( &ballsocket.constraintPosition[0], m_vWorld ); + pPhysicsObject->WorldToLocal( &ballsocket.constraintPosition[1], tr.endpos ); + + physenv->CreateBallsocketConstraint( pReference, pPhysicsObject, NULL, ballsocket ); + + //Play a sound + CPASAttenuationFilter filter( pEnt ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = "Weapon_Crossbow.BoltSkewer"; + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NORM; + ep.m_pOrigin = &pEnt->GetAbsOrigin(); + + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); + + return ITERATION_STOP; + } + + return ITERATION_CONTINUE; + } + +private: + Ray_t m_rayShot; + Vector m_vWorld; +}; + +void CreateCrossbowBolt( const Vector &vecOrigin, const Vector &vecDirection ) +{ + model_t *pModel = (model_t *)engine->LoadModel( "models/crossbow_bolt.mdl" ); + + QAngle vAngles; + + VectorAngles( vecDirection, vAngles ); + + if ( gpGlobals->maxClients > 1 ) + { + tempents->SpawnTempModel( pModel, vecOrigin - vecDirection * 8, vAngles, Vector(0, 0, 0 ), 30.0f, FTENT_NONE ); + } + else + { + tempents->SpawnTempModel( pModel, vecOrigin - vecDirection * 8, vAngles, Vector(0, 0, 0 ), 1, FTENT_NEVERDIE ); + } +} + +void StickRagdollNow( const Vector &vecOrigin, const Vector &vecDirection ) +{ + Ray_t shotRay; + trace_t tr; + + UTIL_TraceLine( vecOrigin, vecOrigin + vecDirection * 16, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + if ( tr.surface.flags & SURF_SKY ) + return; + + Vector vecEnd = vecOrigin - vecDirection * 128; + + shotRay.Init( vecOrigin, vecEnd ); + + CRagdollBoltEnumerator ragdollEnum( shotRay, vecOrigin ); + partition->EnumerateElementsAlongRay( PARTITION_CLIENT_RESPONSIVE_EDICTS, shotRay, false, &ragdollEnum ); + + CreateCrossbowBolt( vecOrigin, vecDirection ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void StickyBoltCallback( const CEffectData &data ) +{ + StickRagdollNow( data.m_vOrigin, data.m_vNormal ); +} + +DECLARE_CLIENT_EFFECT( "BoltImpact", StickyBoltCallback ); \ No newline at end of file diff --git a/game/client/c_sun.cpp b/game/client/c_sun.cpp new file mode 100644 index 00000000..d039e041 --- /dev/null +++ b/game/client/c_sun.cpp @@ -0,0 +1,141 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_sun.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static void RecvProxy_HDRColorScale( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_Sun *pSun = ( C_Sun * )pStruct; + + pSun->m_Overlay.m_flHDRColorScale = pData->m_Value.m_Float; + pSun->m_GlowOverlay.m_flHDRColorScale = pData->m_Value.m_Float; +} + +IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_Sun, DT_Sun, CSun ) + + RecvPropInt( RECVINFO(m_clrRender), 0, RecvProxy_IntToColor32 ), + RecvPropInt( RECVINFO(m_clrOverlay), 0, RecvProxy_IntToColor32 ), + RecvPropVector( RECVINFO( m_vDirection ) ), + RecvPropInt( RECVINFO( m_bOn ) ), + RecvPropInt( RECVINFO( m_nSize ) ), + RecvPropInt( RECVINFO( m_nOverlaySize ) ), + RecvPropInt( RECVINFO( m_nMaterial ) ), + RecvPropInt( RECVINFO( m_nOverlayMaterial ) ), + RecvPropFloat("HDRColorScale", 0, SIZEOF_IGNORE, 0, RecvProxy_HDRColorScale), + +END_RECV_TABLE() + +C_Sun::C_Sun() +{ + m_Overlay.m_bDirectional = true; + m_Overlay.m_bInSky = true; + + m_GlowOverlay.m_bDirectional = true; + m_GlowOverlay.m_bInSky = true; +} + + +C_Sun::~C_Sun() +{ +} + + +void C_Sun::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + // We have to do special setup on our colors because we're tinting an additive material. + // If we don't have at least one component at full strength, the luminosity of the material + // will change and that will cause the material to become more translucent This would be incorrect + // for the sun, which should always be completely opaque at its core. Here, we renormalize the + // components to make sure only hue is altered. + + float maxComponent = max ( m_clrRender->r, max ( m_clrRender->g, m_clrRender->b ) ); + + Vector vOverlayColor; + Vector vMainColor; + + // Re-normalize the color ranges + if ( maxComponent <= 0.0f ) + { + // This is an error, set to pure white + vMainColor.Init( 1.0f, 1.0f, 1.0f ); + } + else + { + vMainColor.x = m_clrRender->r / maxComponent; + vMainColor.y = m_clrRender->g / maxComponent; + vMainColor.z = m_clrRender->b / maxComponent; + } + + // If we're non-zero, use the value (otherwise use the value we calculated above) + if ( m_clrOverlay.r != 0 || m_clrOverlay.g != 0 || m_clrOverlay.b != 0 ) + { + // Get our overlay color + vOverlayColor.x = m_clrOverlay.r / 255.0f; + vOverlayColor.y = m_clrOverlay.g / 255.0f; + vOverlayColor.z = m_clrOverlay.b / 255.0f; + } + else + { + vOverlayColor = vMainColor; + } + + // + // Setup the core overlay + // + + m_Overlay.m_vDirection = m_vDirection; + m_Overlay.m_nSprites = 1; + + m_Overlay.m_Sprites[0].m_vColor = vMainColor; + m_Overlay.m_Sprites[0].m_flHorzSize = m_nSize; + m_Overlay.m_Sprites[0].m_flVertSize = m_nSize; + + const model_t* pModel = (m_nMaterial != 0) ? modelinfo->GetModel( m_nMaterial ) : NULL; + const char *pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + m_Overlay.m_Sprites[0].m_pMaterial = materials->FindMaterial( pModelName, TEXTURE_GROUP_OTHER ); + m_Overlay.m_flProxyRadius = 0.05f; // about 1/20th of the screen + + // + // Setup the external glow overlay + // + + m_GlowOverlay.m_vDirection = m_vDirection; + m_GlowOverlay.m_nSprites = 1; + + m_GlowOverlay.m_Sprites[0].m_vColor = vOverlayColor; + m_GlowOverlay.m_Sprites[0].m_flHorzSize = m_nOverlaySize; + m_GlowOverlay.m_Sprites[0].m_flVertSize = m_nOverlaySize; + + pModel = (m_nOverlayMaterial != 0) ? modelinfo->GetModel( m_nOverlayMaterial ) : NULL; + pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + m_GlowOverlay.m_Sprites[0].m_pMaterial = materials->FindMaterial( pModelName, TEXTURE_GROUP_OTHER ); + + // This texture will fade away as the dot between camera and sun changes + m_GlowOverlay.SetModulateByDot(); + m_GlowOverlay.m_flProxyRadius = 0.05f; // about 1/20th of the screen + + + // Either activate or deactivate. + if ( m_bOn ) + { + m_Overlay.Activate(); + m_GlowOverlay.Activate(); + } + else + { + m_Overlay.Deactivate(); + m_GlowOverlay.Deactivate(); + } +} + + + diff --git a/game/client/c_sun.h b/game/client/c_sun.h new file mode 100644 index 00000000..8a3a0907 --- /dev/null +++ b/game/client/c_sun.h @@ -0,0 +1,86 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_SUN_H +#define C_SUN_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" +#include "utllinkedlist.h" +#include "glow_overlay.h" +#include "sun_shared.h" + +// +// Special glow overlay +// + +class C_SunGlowOverlay : public CGlowOverlay +{ + virtual void CalcSpriteColorAndSize( float flDot, CGlowSprite *pSprite, float *flHorzSize, float *flVertSize, Vector *vColor ) + { + if ( m_bModulateByDot ) + { + float alpha = RemapVal( flDot, 1.0f, 0.9f, 0.75f, 0.0f ); + alpha = clamp( alpha, 0.0f, 0.75f ); + + *flHorzSize = pSprite->m_flHorzSize * 6.0f; + *flVertSize = pSprite->m_flVertSize * 6.0f; + *vColor = pSprite->m_vColor * alpha * m_flGlowObstructionScale; + } + else + { + *flHorzSize = pSprite->m_flHorzSize; + *flVertSize = pSprite->m_flVertSize; + *vColor = pSprite->m_vColor * m_flGlowObstructionScale; + } + } + +public: + + void SetModulateByDot( bool state = true ) + { + m_bModulateByDot = state; + } + +protected: + + bool m_bModulateByDot; +}; + +// +// Sun entity +// + +class C_Sun : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_Sun, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_Sun(); + ~C_Sun(); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + +public: + C_SunGlowOverlay m_Overlay; + C_SunGlowOverlay m_GlowOverlay; + + color32 m_clrOverlay; + int m_nSize; + int m_nOverlaySize; + Vector m_vDirection; + bool m_bOn; + + int m_nMaterial; + int m_nOverlayMaterial; +}; + + +#endif // C_SUN_H diff --git a/game/client/c_te.cpp b/game/client/c_te.cpp new file mode 100644 index 00000000..f82eca69 --- /dev/null +++ b/game/client/c_te.cpp @@ -0,0 +1,686 @@ +//=== Copyright © 1996-2005, Valve Corporation, All rights reserved. ========// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "itempents.h" +#include "effect_dispatch_data.h" +#include "tier1/keyvalues.h" +#include "iefx.h" +#include "ieffects.h" +#include "toolframework_client.h" +#include "cdll_client_int.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// External definitions +void TE_ArmorRicochet( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir ); +void TE_BeamEntPoint( IRecipientFilter& filter, float delay, + int nStartEntity, const Vector *start, int nEndEntity, const Vector* end, + int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ); +void TE_BeamEnts( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ); +void TE_BeamFollow( IRecipientFilter& filter, float delay, + int iEntIndex, int modelIndex, int haloIndex, float life, float width, float endWidth, + float fadeLength,float r, float g, float b, float a ); +void TE_BeamPoints( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ); +void TE_BeamLaser( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ); +void TE_BeamRing( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags = 0 ); +void TE_BeamRingPoint( IRecipientFilter& filter, float delay, + const Vector& center, float start_radius, float end_radius, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags = 0 ); +void TE_BeamSpline( IRecipientFilter& filter, float delay, + int points, Vector* rgPoints ); +void TE_BloodStream( IRecipientFilter& filter, float delay, + const Vector* org, const Vector* dir, int r, int g, int b, int a, int amount ); +void TE_BloodStream( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_BloodSprite( IRecipientFilter& filter, float delay, + const Vector* org, const Vector *dir, int r, int g, int b, int a, int size ); +void TE_BloodSprite( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_BreakModel( IRecipientFilter& filter, float delay, + const Vector& pos, const QAngle &angles, const Vector& size, const Vector& vel, + int modelindex, int randomization, int count, float time, int flags ); +void TE_BreakModel( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_BSPDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int entity, int index ); +void TE_ProjectDecal( IRecipientFilter& filter, float delay, + const Vector* pos, const QAngle *angles, float distance, int index ); +void TE_ProjectDecal( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_Bubbles( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float height, int modelindex, int count, float speed ); +void TE_BubbleTrail( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float height, int modelindex, int count, float speed ); +void TE_Decal( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* start, int entity, int hitbox, int index ); +void TE_Decal( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_DynamicLight( IRecipientFilter& filter, float delay, + const Vector* org, int r, int g, int b, int exponent, float radius, float time, float decay, int nLightIndex = LIGHT_INDEX_TE_DYNAMIC ); +void TE_DynamicLight( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_Explosion( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate, int flags, int radius, int magnitude, + const Vector* normal = NULL, unsigned char materialType = 'C', bool bShouldAffectRagdolls = true ); +void TE_Explosion( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_ShatterSurface( IRecipientFilter& filter, float delay, + const Vector* pos, const QAngle* angle, const Vector* vForce, const Vector* vForcePos, + float width, float height, float shardsize, ShatterSurface_t surfacetype, + int front_r, int front_g, int front_b, int back_r, int back_g, int back_b); +void TE_ShatterSurface( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_GlowSprite( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float life, float size, int brightness ); +void TE_GlowSprite( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_FootprintDecal( IRecipientFilter& filter, float delay, const Vector* origin, const Vector* right, + int entity, int index, unsigned char materialType ); +void TE_Fizz( IRecipientFilter& filter, float delay, + const C_BaseEntity *ed, int modelindex, int density, int current ); +void TE_KillPlayerAttachments( IRecipientFilter& filter, float delay, + int player ); +void TE_LargeFunnel( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, int reversed ); +void TE_MetalSparks( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir ); +void TE_EnergySplash( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir, bool bExplosive ); +void TE_PlayerDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int player, int entity ); +void TE_ShowLine( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end ); +void TE_Smoke( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate ); +void TE_Sparks( IRecipientFilter& filter, float delay, + const Vector* pos, int nMagnitude, int nTrailLength, const Vector *pDir ); +void TE_Sprite( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float size, int brightness ); +void TE_Sprite( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_SpriteSpray( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir, int modelindex, int speed, float noise, int count ); +void TE_SpriteSpray( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_WorldDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int index ); +void TE_WorldDecal( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_MuzzleFlash( IRecipientFilter& filter, float delay, + const Vector &start, const QAngle &angles, float scale, int type ); +void TE_Dust( IRecipientFilter& filter, float delay, + const Vector &pos, const Vector &dir, float size, float speed ); +void TE_GaussExplosion( IRecipientFilter& filter, float delayt, + const Vector &pos, const Vector &dir, int type ); +void TE_DispatchEffect( IRecipientFilter& filter, float delay, + const Vector &pos, const char *pName, const CEffectData &data ); +void TE_DispatchEffect( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_PhysicsProp( IRecipientFilter& filter, float delay, + int modelindex, int skin, const Vector& pos, const QAngle &angles, const Vector& vel, bool breakmodel, int effects ); +void TE_PhysicsProp( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_ConcussiveExplosion( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ); +void TE_ClientProjectile( IRecipientFilter& filter, float delay, + const Vector* vecOrigin, const Vector* vecVelocity, int modelindex, int lifetime, CBaseEntity *pOwner ); + +class C_TempEntsSystem : public ITempEntsSystem +{ +private: + //----------------------------------------------------------------------------- + // Purpose: Returning true means don't even call TE func + // Input : filter - + // *suppress_host - + // Output : static bool + //----------------------------------------------------------------------------- + bool SuppressTE( IRecipientFilter& filter ) + { + if ( !CanPredict() ) + return true; + + C_RecipientFilter& _filter = (( C_RecipientFilter & )filter); + + if ( !_filter.GetRecipientCount() ) + { + // Suppress it + return true; + } + + // There's at least one recipient + return false; + } +public: + + virtual void ArmorRicochet( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir ) + { + if ( !SuppressTE( filter ) ) + { + TE_ArmorRicochet( filter, delay, pos, dir ); + } + } + + virtual void BeamEntPoint( IRecipientFilter& filter, float delay, + int nStartEntity, const Vector *pStart, int nEndEntity, const Vector* pEnd, + int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamEntPoint( filter, delay, nStartEntity, pStart, nEndEntity, pEnd, + modelindex, haloindex, startframe, framerate, + life, width, endWidth, fadeLength, amplitude, r, g, b, a, speed ); + } + } + + virtual void BeamEnts( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamEnts( filter, delay, + start, end, modelindex, haloindex, startframe, framerate, + life, width, endWidth, fadeLength, amplitude, + r, g, b, a, speed ); + } + } + virtual void BeamFollow( IRecipientFilter& filter, float delay, + int iEntIndex, int modelIndex, int haloIndex, float life, float width, float endWidth, + float fadeLength, float r, float g, float b, float a ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamFollow( filter, delay, + iEntIndex, modelIndex, haloIndex, life, width, endWidth, fadeLength, + r, g, b, a ); + } + } + virtual void BeamPoints( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamPoints( filter, delay, + start, end, modelindex, haloindex, startframe, framerate, + life, width, endWidth, fadeLength, amplitude, + r, g, b, a, speed ); + } + } + virtual void BeamLaser( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamLaser( filter, delay, + start, end, modelindex, haloindex, startframe, framerate, + life, width, endWidth, fadeLength, amplitude, r, g, b, a, speed ); + } + } + virtual void BeamRing( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags = 0 ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamRing( filter, delay, + start, end, modelindex, haloindex, startframe, framerate, + life, width, spread, amplitude, r, g, b, a, speed, flags ); + } + } + virtual void BeamRingPoint( IRecipientFilter& filter, float delay, + const Vector& center, float start_radius, float end_radius, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags = 0 ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamRingPoint( filter, delay, + center, start_radius, end_radius, modelindex, haloindex, startframe, framerate, + life, width, spread, amplitude, r, g, b, a, speed, flags ); + } + } + virtual void BeamSpline( IRecipientFilter& filter, float delay, + int points, Vector* rgPoints ) + { + if ( !SuppressTE( filter ) ) + { + TE_BeamSpline( filter, delay, points, rgPoints ); + } + } + virtual void BloodStream( IRecipientFilter& filter, float delay, + const Vector* org, const Vector* dir, int r, int g, int b, int a, int amount ) + { + if ( !SuppressTE( filter ) ) + { + TE_BloodStream( filter, delay, org, dir, r, g, b, a, amount ); + } + } + virtual void BloodSprite( IRecipientFilter& filter, float delay, + const Vector* org, const Vector *dir, int r, int g, int b, int a, int size ) + { + if ( !SuppressTE( filter ) ) + { + TE_BloodSprite( filter, delay, org, dir, r, g, b, a, size ); + } + } + virtual void BreakModel( IRecipientFilter& filter, float delay, + const Vector& pos, const QAngle &angles, const Vector& size, const Vector& vel, + int modelindex, int randomization, int count, float time, int flags ) + { + if ( !SuppressTE( filter ) ) + { + TE_BreakModel( filter, delay, pos, angles, size, vel, modelindex, randomization, + count, time, flags ); + } + } + virtual void BSPDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int entity, int index ) + { + if ( !SuppressTE( filter ) ) + { + TE_BSPDecal( filter, delay, pos, entity, index ); + } + } + + virtual void ProjectDecal( IRecipientFilter& filter, float delay, + const Vector* pos, const QAngle *angles, float distance, int index ) + { + if ( !SuppressTE( filter ) ) + { + TE_ProjectDecal( filter, delay, pos, angles, distance, index ); + } + } + + virtual void Bubbles( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float height, int modelindex, int count, float speed ) + { + if ( !SuppressTE( filter ) ) + { + TE_Bubbles( filter, delay, mins, maxs, height, modelindex, count, speed ); + } + } + virtual void BubbleTrail( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float flWaterZ, int modelindex, int count, float speed ) + { + if ( !SuppressTE( filter ) ) + { + TE_BubbleTrail( filter, delay, mins, maxs, flWaterZ, modelindex, count, speed ); + } + } + virtual void Decal( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* start, int entity, int hitbox, int index ) + { + if ( !SuppressTE( filter ) ) + { + TE_Decal( filter, delay, pos, start, entity, hitbox, index ); + } + } + virtual void DynamicLight( IRecipientFilter& filter, float delay, + const Vector* org, int r, int g, int b, int exponent, float radius, float time, float decay ) + { + if ( !SuppressTE( filter ) ) + { + TE_DynamicLight( filter, delay, org, r, g, b, exponent, radius, time, decay ); + } + } + virtual void Explosion( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate, int flags, int radius, int magnitude, const Vector* normal = NULL, unsigned char materialType = 'C' ) + { + if ( !SuppressTE( filter ) ) + { + TE_Explosion( filter, delay, pos, modelindex, scale, framerate, flags, radius, magnitude, + normal, materialType ); + } + } + virtual void ShatterSurface( IRecipientFilter& filter, float delay, + const Vector* pos, const QAngle* angle, const Vector* vForce, const Vector* vForcePos, + float width, float height, float shardsize, ShatterSurface_t surfacetype, + int front_r, int front_g, int front_b, int back_r, int back_g, int back_b) + { + if ( !SuppressTE( filter ) ) + { + TE_ShatterSurface( filter, delay, pos, angle, vForce, vForcePos, + width, height, shardsize, surfacetype, front_r, front_g, front_b, back_r, back_g, back_b); + } + } + virtual void GlowSprite( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float life, float size, int brightness ) + { + if ( !SuppressTE( filter ) ) + { + TE_GlowSprite( filter, delay, pos, modelindex, life, size, brightness ); + } + } + virtual void FootprintDecal( IRecipientFilter& filter, float delay, const Vector* origin, const Vector* right, + int entity, int index, unsigned char materialType ) + { + if ( !SuppressTE( filter ) ) + { + TE_FootprintDecal( filter, delay, origin, right, + entity, index, materialType ); + } + } + virtual void Fizz( IRecipientFilter& filter, float delay, + const C_BaseEntity *ed, int modelindex, int density, int current ) + { + if ( !SuppressTE( filter ) ) + { + TE_Fizz( filter, delay, + ed, modelindex, density, current ); + } + } + virtual void KillPlayerAttachments( IRecipientFilter& filter, float delay, + int player ) + { + if ( !SuppressTE( filter ) ) + { + TE_KillPlayerAttachments( filter, delay, player ); + } + } + virtual void LargeFunnel( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, int reversed ) + { + if ( !SuppressTE( filter ) ) + { + TE_LargeFunnel( filter, delay, pos, modelindex, reversed ); + } + } + virtual void MetalSparks( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir ) + { + if ( !SuppressTE( filter ) ) + { + TE_MetalSparks( filter, delay, pos, dir ); + } + } + virtual void EnergySplash( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir, bool bExplosive ) + { + if ( !SuppressTE( filter ) ) + { + TE_EnergySplash( filter, delay, + pos, dir, bExplosive ); + } + } + virtual void PlayerDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int player, int entity ) + { + if ( !SuppressTE( filter ) ) + { + TE_PlayerDecal( filter, delay, + pos, player, entity ); + } + } + virtual void ShowLine( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end ) + { + if ( !SuppressTE( filter ) ) + { + TE_ShowLine( filter, delay, + start, end ); + } + } + virtual void Smoke( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate ) + { + if ( !SuppressTE( filter ) ) + { + TE_Smoke( filter, delay, + pos, modelindex, scale, framerate ); + } + } + virtual void Sparks( IRecipientFilter& filter, float delay, + const Vector* pos, int nMagnitude, int nTrailLength, const Vector *pDir ) + { + if ( !SuppressTE( filter ) ) + { + TE_Sparks( filter, delay, + pos, nMagnitude, nTrailLength, pDir ); + } + } + virtual void Sprite( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float size, int brightness ) + { + if ( !SuppressTE( filter ) ) + { + TE_Sprite( filter, delay, + pos, modelindex, size, brightness ); + } + } + virtual void SpriteSpray( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir, int modelindex, int speed, float noise, int count ) + { + if ( !SuppressTE( filter ) ) + { + TE_SpriteSpray( filter, delay, + pos, dir, modelindex, speed, noise, count ); + } + } + virtual void WorldDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int index ) + { + if ( !SuppressTE( filter ) ) + { + TE_WorldDecal( filter, delay, + pos, index ); + } + } + virtual void MuzzleFlash( IRecipientFilter& filter, float delay, + const Vector &start, const QAngle &angles, float scale, int type ) + { + if ( !SuppressTE( filter ) ) + { + TE_MuzzleFlash( filter, delay, + start, angles, scale, type ); + } + } + virtual void Dust( IRecipientFilter& filter, float delay, + const Vector &pos, const Vector &dir, float size, float speed ) + { + if ( !SuppressTE( filter ) ) + { + TE_Dust( filter, delay, + pos, dir, size, speed ); + } + } + virtual void GaussExplosion( IRecipientFilter& filter, float delay, + const Vector &pos, const Vector &dir, int type ) + { + if ( !SuppressTE( filter ) ) + { + TE_GaussExplosion( filter, delay, pos, dir, type ); + } + } + virtual void DispatchEffect( IRecipientFilter& filter, float delay, + const Vector &pos, const char *pName, const CEffectData &data ) + { + if ( !SuppressTE( filter ) ) + { + TE_DispatchEffect( filter, delay, pos, pName, data ); + } + } + virtual void PhysicsProp( IRecipientFilter& filter, float delay, int modelindex, int skin, + const Vector& pos, const QAngle &angles, const Vector& vel, int flags, int effects ) + { + if ( !SuppressTE( filter ) ) + { + TE_PhysicsProp( filter, delay, modelindex, skin, pos, angles, vel, flags, effects ); + } + } + virtual void ClientProjectile( IRecipientFilter& filter, float delay, + const Vector* vecOrigin, const Vector* vecVelocity, int modelindex, int lifetime, CBaseEntity *pOwner ) + { + if ( !SuppressTE( filter ) ) + { + TE_ClientProjectile( filter, delay, vecOrigin, vecVelocity, modelindex, lifetime, pOwner ); + } + } + + // For playback from external tools + virtual void TriggerTempEntity( KeyValues *pKeyValues ) + { + g_pEffects->SuppressEffectsSounds( true ); + SuppressParticleEffects( true ); + + // While playing back, suppress recording + bool bIsRecording = clienttools->IsInRecordingMode(); + clienttools->EnableRecordingMode( false ); + + CBroadcastRecipientFilter filter; + + TERecordingType_t type = (TERecordingType_t)pKeyValues->GetInt( "te" ); + switch( type ) + { + case TE_DYNAMIC_LIGHT: + TE_DynamicLight( filter, 0.0f, pKeyValues ); + break; + + case TE_WORLD_DECAL: + TE_WorldDecal( filter, 0.0f, pKeyValues ); + break; + + case TE_DISPATCH_EFFECT: + TE_DispatchEffect( filter, 0.0f, pKeyValues ); + break; + + case TE_MUZZLE_FLASH: + { + Vector vecOrigin; + QAngle angles; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + angles.x = pKeyValues->GetFloat( "anglesx" ); + angles.y = pKeyValues->GetFloat( "anglesy" ); + angles.z = pKeyValues->GetFloat( "anglesz" ); + float flScale = pKeyValues->GetFloat( "scale" ); + int nType = pKeyValues->GetInt( "type" ); + + TE_MuzzleFlash( filter, 0.0f, vecOrigin, angles, flScale, nType ); + } + break; + + case TE_ARMOR_RICOCHET: + { + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecDirection.x = pKeyValues->GetFloat( "directionx" ); + vecDirection.y = pKeyValues->GetFloat( "directiony" ); + vecDirection.z = pKeyValues->GetFloat( "directionz" ); + + TE_ArmorRicochet( filter, 0.0f, &vecOrigin, &vecDirection ); + } + break; + + case TE_METAL_SPARKS: + { + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecDirection.x = pKeyValues->GetFloat( "directionx" ); + vecDirection.y = pKeyValues->GetFloat( "directiony" ); + vecDirection.z = pKeyValues->GetFloat( "directionz" ); + + TE_MetalSparks( filter, 0.0f, &vecOrigin, &vecDirection ); + } + break; + + case TE_SMOKE: + { + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + float flScale = pKeyValues->GetFloat( "scale" ); + int nFrameRate = pKeyValues->GetInt( "framerate" ); + TE_Smoke( filter, 0.0f, &vecOrigin, 0, flScale, nFrameRate ); + } + break; + + case TE_SPARKS: + { + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecDirection.x = pKeyValues->GetFloat( "directionx" ); + vecDirection.y = pKeyValues->GetFloat( "directiony" ); + vecDirection.z = pKeyValues->GetFloat( "directionz" ); + int nMagnitude = pKeyValues->GetInt( "magnitude" ); + int nTrailLength = pKeyValues->GetInt( "traillength" ); + TE_Sparks( filter, 0.0f, &vecOrigin, nMagnitude, nTrailLength, &vecDirection ); + } + break; + + case TE_BLOOD_STREAM: + TE_BloodStream( filter, 0.0f, pKeyValues ); + break; + + case TE_BLOOD_SPRITE: + TE_BloodSprite( filter, 0.0f, pKeyValues ); + break; + + case TE_BREAK_MODEL: + TE_BreakModel( filter, 0.0f, pKeyValues ); + break; + + case TE_GLOW_SPRITE: + TE_GlowSprite( filter, 0.0f, pKeyValues ); + break; + + case TE_PHYSICS_PROP: + TE_PhysicsProp( filter, 0.0f, pKeyValues ); + break; + + case TE_SPRITE_SINGLE: + TE_Sprite( filter, 0.0f, pKeyValues ); + break; + + case TE_SPRITE_SPRAY: + TE_SpriteSpray( filter, 0.0f, pKeyValues ); + break; + + case TE_SHATTER_SURFACE: + TE_ShatterSurface( filter, 0.0f, pKeyValues ); + break; + + case TE_DECAL: + TE_Decal( filter, 0.0f, pKeyValues ); + break; + + case TE_PROJECT_DECAL: + TE_ProjectDecal( filter, 0.0f, pKeyValues ); + break; + + case TE_EXPLOSION: + TE_Explosion( filter, 0.0f, pKeyValues ); + break; + +#ifdef HL2_DLL + case TE_CONCUSSIVE_EXPLOSION: + TE_ConcussiveExplosion( filter, 0.0f, pKeyValues ); + break; +#endif + } + + SuppressParticleEffects( false ); + g_pEffects->SuppressEffectsSounds( false ); + clienttools->EnableRecordingMode( bIsRecording ); + } +}; + +static C_TempEntsSystem g_TESystem; +// Expose to rest of engine +ITempEntsSystem *te = &g_TESystem; \ No newline at end of file diff --git a/game/client/c_te_armorricochet.cpp b/game/client/c_te_armorricochet.cpp new file mode 100644 index 00000000..9bba1df2 --- /dev/null +++ b/game/client/c_te_armorricochet.cpp @@ -0,0 +1,182 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "IEffects.h" +#include "tier1/keyvalues.h" +#include "tier0/vprof.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Armor Ricochet TE +//----------------------------------------------------------------------------- +class C_TEMetalSparks : public C_BaseTempEntity +{ +public: + DECLARE_CLIENTCLASS(); + + C_TEMetalSparks( void ); + virtual ~C_TEMetalSparks( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + Vector m_vecPos; + Vector m_vecDir; + + const struct model_t *m_pModel; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEMetalSparks::C_TEMetalSparks( void ) +{ + m_vecPos.Init(); + m_vecDir.Init(); + m_pModel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEMetalSparks::~C_TEMetalSparks( void ) +{ +} + +void C_TEMetalSparks::Precache( void ) +{ + //m_pModel = engine->LoadModel( "sprites/richo1.vmt" ); +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordMetalSparks( const Vector &start, const Vector &direction ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_METAL_SPARKS ); + msg->SetString( "name", "TE_MetalSparks" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "directionx", direction.x ); + msg->SetFloat( "directiony", direction.y ); + msg->SetFloat( "directionz", direction.z ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEMetalSparks::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEMetalSparks::PostDataUpdate" ); + + g_pEffects->MetalSparks( m_vecPos, m_vecDir ); + RecordMetalSparks( m_vecPos, m_vecDir ); +} + +void TE_MetalSparks( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir ) +{ + g_pEffects->MetalSparks( *pos, *dir ); + RecordMetalSparks( *pos, *dir ); +} + +//----------------------------------------------------------------------------- +// Purpose: Armor Ricochet TE +//----------------------------------------------------------------------------- +class C_TEArmorRicochet : public C_TEMetalSparks +{ + DECLARE_CLASS( C_TEArmorRicochet, C_TEMetalSparks ); +public: + DECLARE_CLIENTCLASS(); + virtual void PostDataUpdate( DataUpdateType_t updateType ); +}; + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordArmorRicochet( const Vector &start, const Vector &direction ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_ARMOR_RICOCHET ); + msg->SetString( "name", "TE_ArmorRicochet" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "directionx", direction.x ); + msg->SetFloat( "directiony", direction.y ); + msg->SetFloat( "directionz", direction.z ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Client side version of API +//----------------------------------------------------------------------------- +void TE_ArmorRicochet( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir ) +{ + g_pEffects->Ricochet( *pos, *dir ); + RecordArmorRicochet( *pos, *dir ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEArmorRicochet::PostDataUpdate( DataUpdateType_t updateType ) +{ + g_pEffects->Ricochet( m_vecPos, m_vecDir ); + RecordArmorRicochet( m_vecPos, m_vecDir ); +} + +// Expose the TE to the engine. +IMPLEMENT_CLIENTCLASS_EVENT( C_TEMetalSparks, DT_TEMetalSparks, CTEMetalSparks ); + +BEGIN_RECV_TABLE_NOBASE(C_TEMetalSparks, DT_TEMetalSparks) + RecvPropVector(RECVINFO(m_vecPos)), + RecvPropVector(RECVINFO(m_vecDir)), +END_RECV_TABLE() + +IMPLEMENT_CLIENTCLASS_EVENT( C_TEArmorRicochet, DT_TEArmorRicochet, CTEArmorRicochet ); +BEGIN_RECV_TABLE(C_TEArmorRicochet, DT_TEArmorRicochet) +END_RECV_TABLE() diff --git a/game/client/c_te_basebeam.cpp b/game/client/c_te_basebeam.cpp new file mode 100644 index 00000000..354701bf --- /dev/null +++ b/game/client/c_te_basebeam.cpp @@ -0,0 +1,82 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Contains common variables for all beam TEs +//----------------------------------------------------------------------------- +C_TEBaseBeam::C_TEBaseBeam( void ) +{ + m_nModelIndex = 0; + m_nHaloIndex = 0; + m_nStartFrame = 0; + m_nFrameRate = 0; + m_fLife = 0.0; + m_fWidth = 0; + m_fEndWidth = 0; + m_nFadeLength = 0; + m_fAmplitude = 0; + r = g = b = a = 0; + m_nSpeed = 0; + m_nFlags = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBaseBeam::~C_TEBaseBeam( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBaseBeam::PreDataUpdate( DataUpdateType_t updateType ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBaseBeam::PostDataUpdate( DataUpdateType_t updateType ) +{ + Assert( 0 ); +} + + +IMPLEMENT_CLIENTCLASS(C_TEBaseBeam, DT_BaseBeam, CTEBaseBeam); + +BEGIN_RECV_TABLE_NOBASE( C_TEBaseBeam, DT_BaseBeam ) + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropInt( RECVINFO(m_nHaloIndex)), + RecvPropInt( RECVINFO(m_nStartFrame)), + RecvPropInt( RECVINFO(m_nFrameRate)), + RecvPropFloat( RECVINFO(m_fLife)), + RecvPropFloat( RECVINFO(m_fWidth)), + RecvPropFloat( RECVINFO(m_fEndWidth)), + RecvPropInt( RECVINFO(m_nFadeLength)), + RecvPropFloat( RECVINFO(m_fAmplitude)), + RecvPropInt( RECVINFO(m_nSpeed)), + RecvPropInt( RECVINFO(r)), + RecvPropInt( RECVINFO(g)), + RecvPropInt( RECVINFO(b)), + RecvPropInt( RECVINFO(a)), + RecvPropInt( RECVINFO(m_nFlags)), +END_RECV_TABLE() + diff --git a/game/client/c_te_basebeam.h b/game/client/c_te_basebeam.h new file mode 100644 index 00000000..5afd7b75 --- /dev/null +++ b/game/client/c_te_basebeam.h @@ -0,0 +1,53 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( C_TE_BASEBEAM_H ) +#define C_TE_BASEBEAM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_basetempentity.h" + +//----------------------------------------------------------------------------- +// Purpose: Base entity for beam te's +//----------------------------------------------------------------------------- +class C_TEBaseBeam : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEBaseBeam, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + +private: + +public: + + C_TEBaseBeam( void ); + virtual ~C_TEBaseBeam( void ); + + virtual void PreDataUpdate( DataUpdateType_t updateType ); + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + int m_nModelIndex; + int m_nHaloIndex; + int m_nStartFrame; + int m_nFrameRate; + float m_fLife; + float m_fWidth; + float m_fEndWidth; + int m_nFadeLength; + float m_fAmplitude; + int r, g, b, a; + int m_nSpeed; + int m_nFlags; +}; + +EXTERN_RECV_TABLE(DT_BaseBeam); + +#endif // C_TE_BASEBEAM_H \ No newline at end of file diff --git a/game/client/c_te_beamentpoint.cpp b/game/client/c_te_beamentpoint.cpp new file mode 100644 index 00000000..f3b3d695 --- /dev/null +++ b/game/client/c_te_beamentpoint.cpp @@ -0,0 +1,82 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_basebeam.h" +#include "iviewrender_beams.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: BeamEntPoint TE +//----------------------------------------------------------------------------- +class C_TEBeamEntPoint : public C_TEBaseBeam +{ +public: + DECLARE_CLASS( C_TEBeamEntPoint, C_TEBaseBeam ); + DECLARE_CLIENTCLASS(); + + C_TEBeamEntPoint( void ); + virtual ~C_TEBeamEntPoint( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + int m_nStartEntity; + int m_nEndEntity; + Vector m_vecStartPoint; + Vector m_vecEndPoint; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamEntPoint::C_TEBeamEntPoint( void ) +{ + m_nStartEntity = 0; + m_vecEndPoint.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamEntPoint::~C_TEBeamEntPoint( void ) +{ +} + +void TE_BeamEntPoint( IRecipientFilter& filter, float delay, + int nStartEntity, const Vector *pStart, int nEndEntity, const Vector* pEnd, + int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ) +{ + beams->CreateBeamEntPoint( nStartEntity, pStart, nEndEntity, pEnd, + modelindex, haloindex, 0.0f, life, width, endWidth, fadeLength, amplitude, + a, 0.1 * (float)speed, startframe, 0.1f * (float)framerate, r, g, b ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamEntPoint::PostDataUpdate( DataUpdateType_t updateType ) +{ + beams->CreateBeamEntPoint( m_nStartEntity, &m_vecStartPoint, m_nEndEntity, &m_vecEndPoint, + m_nModelIndex, m_nHaloIndex, 0.0f, + m_fLife, m_fWidth, m_fEndWidth, m_nFadeLength, m_fAmplitude, a, 0.1 * m_nSpeed, + m_nStartFrame, 0.1 * m_nFrameRate, r, g, b ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBeamEntPoint, DT_TEBeamEntPoint, CTEBeamEntPoint) + RecvPropInt(RECVINFO(m_nStartEntity)), + RecvPropInt(RECVINFO(m_nEndEntity)), + RecvPropVector(RECVINFO(m_vecStartPoint)), + RecvPropVector(RECVINFO(m_vecEndPoint)), +END_RECV_TABLE() + diff --git a/game/client/c_te_beaments.cpp b/game/client/c_te_beaments.cpp new file mode 100644 index 00000000..0cc0fb80 --- /dev/null +++ b/game/client/c_te_beaments.cpp @@ -0,0 +1,79 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_basebeam.h" +#include "iviewrender_beams.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: BeamEnts TE +//----------------------------------------------------------------------------- +class C_TEBeamEnts : public C_TEBaseBeam +{ +public: + DECLARE_CLASS( C_TEBeamEnts, C_TEBaseBeam ); + DECLARE_CLIENTCLASS(); + + C_TEBeamEnts( void ); + virtual ~C_TEBeamEnts( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + int m_nStartEntity; + int m_nEndEntity; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamEnts::C_TEBeamEnts( void ) +{ + m_nStartEntity = 0; + m_nEndEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamEnts::~C_TEBeamEnts( void ) +{ +} + +void TE_BeamEnts( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ) +{ + beams->CreateBeamEnts( start, end, modelindex, haloindex, 0.0f, + life, width, endWidth, fadeLength, amplitude, a, 0.1 * (float)speed, + startframe, 0.1 * (float)framerate, r, g, b ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamEnts::PostDataUpdate( DataUpdateType_t updateType ) +{ + beams->CreateBeamEnts( m_nStartEntity, m_nEndEntity, m_nModelIndex, m_nHaloIndex, 0.0f, + m_fLife, m_fWidth, m_fEndWidth, m_nFadeLength, m_fAmplitude, a, 0.1 * m_nSpeed, + m_nStartFrame, 0.1 * m_nFrameRate, r, g, b ); +} + +// Expose the TE to the engine. +IMPLEMENT_CLIENTCLASS_EVENT( C_TEBeamEnts, DT_TEBeamEnts, CTEBeamEnts ); + +BEGIN_RECV_TABLE(C_TEBeamEnts, DT_TEBeamEnts) + RecvPropInt( RECVINFO(m_nStartEntity)), + RecvPropInt( RECVINFO(m_nEndEntity)), +END_RECV_TABLE() + diff --git a/game/client/c_te_beamfollow.cpp b/game/client/c_te_beamfollow.cpp new file mode 100644 index 00000000..f980e639 --- /dev/null +++ b/game/client/c_te_beamfollow.cpp @@ -0,0 +1,79 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_basebeam.h" +#include "iviewrender_beams.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_TEBeamFollow : public C_TEBaseBeam +{ +public: + DECLARE_CLASS( C_TEBeamFollow, C_TEBaseBeam ); + DECLARE_CLIENTCLASS(); + + C_TEBeamFollow( void ); + virtual ~C_TEBeamFollow( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + + int m_iEntIndex; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamFollow::C_TEBeamFollow( void ) +{ + m_iEntIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamFollow::~C_TEBeamFollow( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamFollow::PostDataUpdate( DataUpdateType_t updateType ) +{ + beams->CreateBeamFollow( m_iEntIndex, m_nModelIndex, m_nHaloIndex, 0, m_fLife, + m_fWidth, m_fEndWidth, m_nFadeLength, r, g, b, a ); +} + +// Expose the TE to the engine. +IMPLEMENT_CLIENTCLASS_EVENT( C_TEBeamFollow, DT_TEBeamFollow, CTEBeamFollow ); + +BEGIN_RECV_TABLE(C_TEBeamFollow, DT_TEBeamFollow) + RecvPropInt( RECVINFO(m_iEntIndex)), +END_RECV_TABLE() + + +void TE_BeamFollow( IRecipientFilter& filter, float delay, + int iEntIndex, int modelIndex, int haloIndex, float life, float width, float endWidth, + float fadeLength,float r, float g, float b, float a ) +{ + beams->CreateBeamFollow( iEntIndex, modelIndex, haloIndex, 0, life, + width, endWidth, fadeLength, r, g, b, a ); +} diff --git a/game/client/c_te_beamlaser.cpp b/game/client/c_te_beamlaser.cpp new file mode 100644 index 00000000..5538553d --- /dev/null +++ b/game/client/c_te_beamlaser.cpp @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Beam that's used for the sniper's laser sight +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tempentity.h" +#include "c_te_basebeam.h" +#include "iviewrender_beams.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Beam used for Laser sights. Fades out when it's perpendicular to the viewpoint. +//----------------------------------------------------------------------------- +class C_TEBeamLaser : public C_TEBaseBeam +{ + DECLARE_CLASS( C_TEBeamLaser, C_TEBaseBeam ); +public: + DECLARE_CLIENTCLASS(); + + C_TEBeamLaser( void ); + virtual ~C_TEBeamLaser( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + int m_nStartEntity; + int m_nEndEntity; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamLaser::C_TEBeamLaser( void ) +{ + m_nStartEntity = 0; + m_nEndEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamLaser::~C_TEBeamLaser( void ) +{ +} + +void TE_BeamLaser( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) +{ + beams->CreateBeamEnts( start, end, modelindex, haloindex, 0.0f, + life, width, endWidth, fadeLength, amplitude, a, 0.1 * speed, + startframe, 0.1 * framerate, r, g, b, TE_BEAMLASER ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamLaser::PostDataUpdate( DataUpdateType_t updateType ) +{ + beams->CreateBeamEnts( m_nStartEntity, m_nEndEntity, m_nModelIndex, m_nHaloIndex, 0.0f, + m_fLife, m_fWidth, m_fEndWidth, m_nFadeLength, m_fAmplitude, a, 0.1 * m_nSpeed, + m_nStartFrame, 0.1 * m_nFrameRate, r, g, b, TE_BEAMLASER ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBeamLaser, DT_TEBeamLaser, CTEBeamLaser) + RecvPropInt(RECVINFO(m_nStartEntity)), + RecvPropInt( RECVINFO(m_nEndEntity)), +END_RECV_TABLE() diff --git a/game/client/c_te_beampoints.cpp b/game/client/c_te_beampoints.cpp new file mode 100644 index 00000000..1d586f4f --- /dev/null +++ b/game/client/c_te_beampoints.cpp @@ -0,0 +1,76 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_basebeam.h" +#include "iviewrender_beams.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: BeamPoints TE +//----------------------------------------------------------------------------- +class C_TEBeamPoints : public C_TEBaseBeam +{ +public: + DECLARE_CLASS( C_TEBeamPoints, C_TEBaseBeam ); + DECLARE_CLIENTCLASS(); + + C_TEBeamPoints( void ); + virtual ~C_TEBeamPoints( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecStartPoint; + Vector m_vecEndPoint; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamPoints::C_TEBeamPoints( void ) +{ + m_vecStartPoint.Init(); + m_vecEndPoint.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamPoints::~C_TEBeamPoints( void ) +{ +} + +void TE_BeamPoints( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, + int r, int g, int b, int a, int speed ) +{ + beams->CreateBeamPoints( (Vector&)*start, (Vector&)*end, modelindex, haloindex, 0.0f, + life, width, endWidth, fadeLength, amplitude, a, 0.1 * speed, + startframe, 0.1 * (float)framerate, r, g, b ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamPoints::PostDataUpdate( DataUpdateType_t updateType ) +{ + beams->CreateBeamPoints( m_vecStartPoint, m_vecEndPoint, m_nModelIndex, m_nHaloIndex, 0.0f, + m_fLife, m_fWidth, m_fEndWidth, m_nFadeLength, m_fAmplitude, a, 0.1 * m_nSpeed, + m_nStartFrame, 0.1 * m_nFrameRate, r, g, b ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBeamPoints, DT_TEBeamPoints, CTEBeamPoints) + RecvPropVector( RECVINFO(m_vecStartPoint)), + RecvPropVector( RECVINFO(m_vecEndPoint)), +END_RECV_TABLE() + diff --git a/game/client/c_te_beamring.cpp b/game/client/c_te_beamring.cpp new file mode 100644 index 00000000..dc93a0fd --- /dev/null +++ b/game/client/c_te_beamring.cpp @@ -0,0 +1,78 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_basebeam.h" +#include "iviewrender_beams.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: BeamRing TE +//----------------------------------------------------------------------------- +class C_TEBeamRing : public C_TEBaseBeam +{ +public: + DECLARE_CLASS( C_TEBeamRing, C_TEBaseBeam ); + DECLARE_CLIENTCLASS(); + + C_TEBeamRing( void ); + virtual ~C_TEBeamRing( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + int m_nStartEntity; + int m_nEndEntity; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamRing::C_TEBeamRing( void ) +{ + m_nStartEntity = 0; + m_nEndEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamRing::~C_TEBeamRing( void ) +{ +} + +void TE_BeamRing( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags ) +{ + beams->CreateBeamRing( start, end, modelindex, haloindex, 0.0f, + life, width, 0.1 * spread, 0.0f, amplitude, a, 0.1 * speed, + startframe, 0.1 * framerate, r, g, b, flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamRing::PostDataUpdate( DataUpdateType_t updateType ) +{ + beams->CreateBeamRing( m_nStartEntity, m_nEndEntity, m_nModelIndex, m_nHaloIndex, 0.0f, + m_fLife, m_fWidth, m_fEndWidth, m_nFadeLength, m_fAmplitude, a, 0.1 * m_nSpeed, + m_nStartFrame, 0.1 * m_nFrameRate, r, g, b, m_nFlags ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBeamRing, DT_TEBeamRing, CTEBeamRing) + RecvPropInt( RECVINFO(m_nStartEntity)), + RecvPropInt( RECVINFO(m_nEndEntity)), +END_RECV_TABLE() diff --git a/game/client/c_te_beamringpoint.cpp b/game/client/c_te_beamringpoint.cpp new file mode 100644 index 00000000..5f9ef2e6 --- /dev/null +++ b/game/client/c_te_beamringpoint.cpp @@ -0,0 +1,81 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_basebeam.h" +#include "iviewrender_beams.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: BeamRingPoint TE +//----------------------------------------------------------------------------- +class C_TEBeamRingPoint : public C_TEBaseBeam +{ +public: + DECLARE_CLASS( C_TEBeamRingPoint, C_TEBaseBeam ); + DECLARE_CLIENTCLASS(); + + C_TEBeamRingPoint( void ); + virtual ~C_TEBeamRingPoint( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecCenter; + float m_flStartRadius; + float m_flEndRadius; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamRingPoint::C_TEBeamRingPoint( void ) +{ + m_vecCenter.Init(); + m_flStartRadius = 0.0f; + m_flEndRadius = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamRingPoint::~C_TEBeamRingPoint( void ) +{ +} + +void TE_BeamRingPoint( IRecipientFilter& filter, float delay, + const Vector& center, float start_radius, float end_radius, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags ) +{ + beams->CreateBeamRingPoint( center, start_radius, end_radius, modelindex, haloindex, 0.0f, + life, width, 0.1 * spread, 0.0f, amplitude, a, 0.1 * speed, + startframe, 0.1 * framerate, r, g, b, flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamRingPoint::PostDataUpdate( DataUpdateType_t updateType ) +{ + beams->CreateBeamRingPoint( m_vecCenter, m_flStartRadius, m_flEndRadius, m_nModelIndex, m_nHaloIndex, 0.0f, + m_fLife, m_fWidth, m_fEndWidth, m_nFadeLength, m_fAmplitude, a, 0.1 * m_nSpeed, + m_nStartFrame, 0.1 * m_nFrameRate, r, g, b, m_nFlags ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBeamRingPoint, DT_TEBeamRingPoint, CTEBeamRingPoint) + RecvPropVector( RECVINFO(m_vecCenter)), + RecvPropFloat( RECVINFO(m_flStartRadius)), + RecvPropFloat( RECVINFO(m_flEndRadius)), +END_RECV_TABLE() diff --git a/game/client/c_te_beamspline.cpp b/game/client/c_te_beamspline.cpp new file mode 100644 index 00000000..98de0ecf --- /dev/null +++ b/game/client/c_te_beamspline.cpp @@ -0,0 +1,86 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MAX_SPLINE_POINTS 16 +//----------------------------------------------------------------------------- +// Purpose: BeamSpline TE +//----------------------------------------------------------------------------- +class C_TEBeamSpline : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEBeamSpline, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEBeamSpline( void ); + virtual ~C_TEBeamSpline( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecPoints[ MAX_SPLINE_POINTS ]; + int m_nPoints; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamSpline::C_TEBeamSpline( void ) +{ + int i; + for ( i = 0; i < MAX_SPLINE_POINTS; i++ ) + { + m_vecPoints[ i ].Init(); + } + m_nPoints = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBeamSpline::~C_TEBeamSpline( void ) +{ +} + +void TE_BeamSpline( IRecipientFilter& filter, float delay, + int points, Vector* rgPoints ) +{ + DevMsg( 1, "Beam spline with %i points invoked\n", points ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBeamSpline::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEBeamSpline::PostDataUpdate" ); + + DevMsg( 1, "Beam spline with %i points received\n", m_nPoints ); +} + +// Expose the TE to the engine. +IMPLEMENT_CLIENTCLASS_EVENT( C_TEBeamSpline, DT_TEBeamSpline, CTEBeamSpline ); + +BEGIN_RECV_TABLE_NOBASE(C_TEBeamSpline, DT_TEBeamSpline) + RecvPropInt( RECVINFO( m_nPoints )), + RecvPropArray( + RecvPropVector( RECVINFO(m_vecPoints[0])), + m_vecPoints) +END_RECV_TABLE() + diff --git a/game/client/c_te_bloodsprite.cpp b/game/client/c_te_bloodsprite.cpp new file mode 100644 index 00000000..07ed8fbb --- /dev/null +++ b/game/client/c_te_bloodsprite.cpp @@ -0,0 +1,171 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "fx.h" +#include "tier1/keyvalues.h" +#include "tier0/vprof.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexBloodDrop; +extern short g_sModelIndexBloodSpray; + +//----------------------------------------------------------------------------- +// Purpose: Blood sprite +//----------------------------------------------------------------------------- +class C_TEBloodSprite : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEBloodSprite, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEBloodSprite( void ); + virtual ~C_TEBloodSprite( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + Vector m_vecDirection; + int r, g, b, a; + int m_nDropModel; + int m_nSprayModel; + int m_nSize; +}; + + +// Expose it to the engine. +IMPLEMENT_CLIENTCLASS_EVENT( C_TEBloodSprite, DT_TEBloodSprite, CTEBloodSprite ); + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +BEGIN_RECV_TABLE_NOBASE(C_TEBloodSprite, DT_TEBloodSprite) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropVector( RECVINFO(m_vecDirection)), + RecvPropInt( RECVINFO(r)), + RecvPropInt( RECVINFO(g)), + RecvPropInt( RECVINFO(b)), + RecvPropInt( RECVINFO(a)), + RecvPropInt( RECVINFO(m_nSprayModel)), + RecvPropInt( RECVINFO(m_nDropModel)), + RecvPropInt( RECVINFO(m_nSize)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBloodSprite::C_TEBloodSprite( void ) +{ + m_vecOrigin.Init(); + m_vecDirection.Init(); + + r = g = b = a = 0; + m_nSize = 0; + m_nSprayModel = 0; + m_nDropModel = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBloodSprite::~C_TEBloodSprite( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordBloodSprite( const Vector &start, const Vector &direction, + int r, int g, int b, int a, int nSprayModelIndex, int nDropModelIndex, int size ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + Color clr( r, g, b, a ); + + const model_t* pSprayModel = (nSprayModelIndex != 0) ? modelinfo->GetModel( nSprayModelIndex ) : NULL; + const model_t* pDropModel = (nDropModelIndex != 0) ? modelinfo->GetModel( nDropModelIndex ) : NULL; + const char *pSprayModelName = pSprayModel ? modelinfo->GetModelName( pSprayModel ) : ""; + const char *pDropModelName = pDropModel ? modelinfo->GetModelName( pDropModel ) : ""; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_BLOOD_SPRITE ); + msg->SetString( "name", "TE_BloodSprite" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "directionx", direction.x ); + msg->SetFloat( "directiony", direction.y ); + msg->SetFloat( "directionz", direction.z ); + msg->SetColor( "color", clr ); + msg->SetString( "spraymodel", pSprayModelName ); + msg->SetString( "dropmodel", pDropModelName ); + msg->SetInt( "size", size ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +void TE_BloodSprite( IRecipientFilter& filter, float delay, + const Vector* org, const Vector *dir, int r, int g, int b, int a, int size ) +{ + Vector offset = *org + ( (*dir) * 4.0f ); + + tempents->BloodSprite( offset, r, g, b, a, g_sModelIndexBloodSpray, g_sModelIndexBloodDrop, size ); + FX_Blood( offset, (Vector &)*dir, r, g, b, a ); + RecordBloodSprite( *org, *dir, r, g, b, a, g_sModelIndexBloodSpray, g_sModelIndexBloodDrop, size ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEBloodSprite::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEBloodSprite::PostDataUpdate" ); + + Vector offset = m_vecOrigin + ( m_vecDirection * 4.0f ); + + tempents->BloodSprite( offset, r, g, b, a, m_nSprayModel, m_nDropModel, m_nSize ); + FX_Blood( offset, m_vecDirection, r, g, b, a ); + RecordBloodSprite( m_vecOrigin, m_vecDirection, r, g, b, a, m_nSprayModel, m_nDropModel, m_nSize ); +} + +void TE_BloodSprite( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecDirection.x = pKeyValues->GetFloat( "directionx" ); + vecDirection.y = pKeyValues->GetFloat( "directiony" ); + vecDirection.z = pKeyValues->GetFloat( "directionz" ); + Color c = pKeyValues->GetColor( "color" ); +// const char *pSprayModelName = pKeyValues->GetString( "spraymodel" ); +// const char *pDropModelName = pKeyValues->GetString( "dropmodel" ); + int nSize = pKeyValues->GetInt( "size" ); + TE_BloodSprite( filter, 0.0f, &vecOrigin, &vecDirection, c.r(), c.g(), c.b(), c.a(), nSize ); +} \ No newline at end of file diff --git a/game/client/c_te_bloodstream.cpp b/game/client/c_te_bloodstream.cpp new file mode 100644 index 00000000..404a55b5 --- /dev/null +++ b/game/client/c_te_bloodstream.cpp @@ -0,0 +1,223 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_te_particlesystem.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Purpose: Blood Stream TE +//----------------------------------------------------------------------------- +class C_TEBloodStream : public C_TEParticleSystem +{ +public: + DECLARE_CLASS( C_TEBloodStream, C_TEParticleSystem ); + DECLARE_CLIENTCLASS(); + + C_TEBloodStream( void ); + virtual ~C_TEBloodStream( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecDirection; + int r, g, b, a; + int m_nAmount; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBloodStream, DT_TEBloodStream, CTEBloodStream) + RecvPropVector( RECVINFO(m_vecDirection)), + RecvPropInt( RECVINFO(r)), + RecvPropInt( RECVINFO(g)), + RecvPropInt( RECVINFO(b)), + RecvPropInt( RECVINFO(a)), + RecvPropInt( RECVINFO(m_nAmount)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBloodStream::C_TEBloodStream( void ) +{ + m_vecOrigin.Init(); + m_vecDirection.Init(); + r = g = b = a = 0; + m_nAmount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBloodStream::~C_TEBloodStream( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordBloodStream( const Vector &start, const Vector &direction, + int r, int g, int b, int a, int amount ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + Color clr( r, g, b, a ); + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_BLOOD_STREAM ); + msg->SetString( "name", "TE_BloodStream" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "directionx", direction.x ); + msg->SetFloat( "directiony", direction.y ); + msg->SetFloat( "directionz", direction.z ); + msg->SetColor( "color", clr ); + msg->SetInt( "amount", amount ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +void TE_BloodStream( IRecipientFilter& filter, float delay, + const Vector* org, const Vector* direction, int r, int g, int b, int a, int amount ) +{ + RecordBloodStream( *org, *direction, r, g, b, a, amount ); + + CSmartPtr pRen = CTEParticleRenderer::Create( "TEBloodStream", *org ); + if( !pRen ) + return; + + // Add our particles. + Vector dirCopy; + float arc = 0.05; + int count, count2; + float num; + int speedCopy = amount; + + Vector dir; + VectorCopy( *direction, dir ); + VectorNormalize( dir ); + + for (count=0 ; count<100 ; count++) + { + StandardParticle_t *p = pRen->AddParticle(); + if(p) + { + p->SetColor(r * random->RandomFloat(0.7, 1.0), g, b); + p->SetAlpha(a); + p->m_Pos = *org; + pRen->SetParticleLifetime(p, 2); + pRen->SetParticleType(p, pt_vox_grav); + + VectorCopy (dir, dirCopy); + + dirCopy[2] -= arc; + arc -= 0.005; + + VectorScale (dirCopy, speedCopy, p->m_Velocity); + + speedCopy -= 0.00001;// so last few will drip + } + } + + // now a few rogue voxels + arc = 0.075; + for (count = 0 ; count < (amount/5); count ++) + { + StandardParticle_t *p = pRen->AddParticle(); + if(p) + { + pRen->SetParticleLifetime(p, 3); + p->SetColor(r * random->RandomFloat(0.7, 1.0), g, b); + p->SetAlpha(a); + p->m_Pos = *org; + pRen->SetParticleType(p, pt_vox_slowgrav); + + VectorCopy (dir, dirCopy); + + dirCopy[2] -= arc; + arc -= 0.005; + + num = random->RandomFloat(0,1); + speedCopy = amount * num; + + num *= 1.7; + + VectorScale (dirCopy, num, dirCopy);// randomize a bit + p->m_Velocity = dirCopy * speedCopy; + + + // add a few extra voxels directly adjacent to this one to give a + // 'chunkier' appearance. + for (count2 = 0; count2 < 2; count2++) + { + StandardParticle_t *p = pRen->AddParticle(); + if(p) + { + pRen->SetParticleLifetime(p, 3); + p->SetColor(random->RandomFloat(0.7, 1.0), g, b); + p->SetAlpha(a); + p->m_Pos.Init( + (*org)[0] + random->RandomFloat(-1,1), + (*org)[1] + random->RandomFloat(-1,1), + (*org)[2] + random->RandomFloat(-1,1)); + + pRen->SetParticleType(p, pt_vox_slowgrav); + + VectorCopy (dir, dirCopy); + + dirCopy[2] -= arc; + + VectorScale (dirCopy, num, dirCopy);// randomize a bit + + p->m_Velocity = dirCopy * speedCopy; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEBloodStream::PostDataUpdate( DataUpdateType_t updateType ) +{ + CBroadcastRecipientFilter filter; + TE_BloodStream( filter, 0.0f, &m_vecOrigin, &m_vecDirection, r, g, b, a, m_nAmount ); +} + +void TE_BloodStream( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecDirection.x = pKeyValues->GetFloat( "directionx" ); + vecDirection.y = pKeyValues->GetFloat( "directiony" ); + vecDirection.z = pKeyValues->GetFloat( "directionz" ); + Color c = pKeyValues->GetColor( "color" ); + int nAmount = pKeyValues->GetInt( "amount" ); + TE_BloodStream( filter, 0.0f, &vecOrigin, &vecDirection, c.r(), c.g(), c.b(), c.a(), nAmount ); +} diff --git a/game/client/c_te_breakmodel.cpp b/game/client/c_te_breakmodel.cpp new file mode 100644 index 00000000..fd985e6c --- /dev/null +++ b/game/client/c_te_breakmodel.cpp @@ -0,0 +1,179 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier1/keyvalues.h" +#include "tier0/vprof.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Breakable Model TE +//----------------------------------------------------------------------------- +class C_TEBreakModel : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEBreakModel, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEBreakModel( void ); + virtual ~C_TEBreakModel( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + QAngle m_angRotation; + Vector m_vecSize; + Vector m_vecVelocity; + int m_nRandomization; + int m_nModelIndex; + int m_nCount; + float m_fTime; + int m_nFlags; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBreakModel, DT_TEBreakModel, CTEBreakModel) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropFloat( RECVINFO( m_angRotation[0] ) ), + RecvPropFloat( RECVINFO( m_angRotation[1] ) ), + RecvPropFloat( RECVINFO( m_angRotation[2] ) ), + RecvPropVector( RECVINFO(m_vecSize)), + RecvPropVector( RECVINFO(m_vecVelocity)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropInt( RECVINFO(m_nRandomization)), + RecvPropInt( RECVINFO(m_nCount)), + RecvPropFloat( RECVINFO(m_fTime)), + RecvPropInt( RECVINFO(m_nFlags)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBreakModel::C_TEBreakModel( void ) +{ + m_vecOrigin.Init(); + m_angRotation.Init(); + m_vecSize.Init(); + m_vecVelocity.Init(); + m_nModelIndex = 0; + m_nRandomization = 0; + m_nCount = 0; + m_fTime = 0.0; + m_nFlags = 0; +} + +C_TEBreakModel::~C_TEBreakModel( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordBreakModel( const Vector &start, const QAngle &angles, const Vector &size, + const Vector &vel, int nModelIndex, int nRandomization, int nCount, float flDuration, int nFlags ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + const model_t* pModel = (nModelIndex != 0) ? modelinfo->GetModel( nModelIndex ) : NULL; + const char *pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_BREAK_MODEL ); + msg->SetString( "name", "TE_BreakModel" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "anglesx", angles.x ); + msg->SetFloat( "anglesy", angles.y ); + msg->SetFloat( "anglesz", angles.z ); + msg->SetFloat( "sizex", size.x ); + msg->SetFloat( "sizey", size.y ); + msg->SetFloat( "sizez", size.z ); + msg->SetFloat( "velx", vel.x ); + msg->SetFloat( "vely", vel.y ); + msg->SetFloat( "velz", vel.z ); + msg->SetString( "model", pModelName ); + msg->SetInt( "randomization", nRandomization ); + msg->SetInt( "count", nCount ); + msg->SetFloat( "duration", flDuration ); + msg->SetInt( "flags", nFlags ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TE_BreakModel( IRecipientFilter& filter, float delay, + const Vector& pos, const QAngle &angles, const Vector& size, const Vector& vel, + int modelindex, int randomization, int count, float time, int flags ) +{ + tempents->BreakModel( pos, angles, size, vel, randomization, time, count, modelindex, flags ); + RecordBreakModel( pos, angles, size, vel, randomization, time, count, modelindex, flags ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEBreakModel::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEBreakModel::PostDataUpdate" ); + + tempents->BreakModel( m_vecOrigin, m_angRotation, m_vecSize, m_vecVelocity, + m_nRandomization, m_fTime, m_nCount, m_nModelIndex, m_nFlags ); + RecordBreakModel( m_vecOrigin, m_angRotation, m_vecSize, m_vecVelocity, + m_nRandomization, m_fTime, m_nCount, m_nModelIndex, m_nFlags ); +} + +void TE_BreakModel( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecSize, vecVel; + QAngle angles; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + angles.x = pKeyValues->GetFloat( "anglesx" ); + angles.y = pKeyValues->GetFloat( "anglesy" ); + angles.z = pKeyValues->GetFloat( "anglesz" ); + vecSize.x = pKeyValues->GetFloat( "sizex" ); + vecSize.y = pKeyValues->GetFloat( "sizey" ); + vecSize.z = pKeyValues->GetFloat( "sizez" ); + vecVel.x = pKeyValues->GetFloat( "velx" ); + vecVel.y = pKeyValues->GetFloat( "vely" ); + vecVel.z = pKeyValues->GetFloat( "velz" ); + Color c = pKeyValues->GetColor( "color" ); + const char *pModelName = pKeyValues->GetString( "model" ); + int nModelIndex = pModelName[0] ? modelinfo->GetModelIndex( pModelName ) : 0; + int nRandomization = pKeyValues->GetInt( "randomization" ); + int nCount = pKeyValues->GetInt( "count" ); + float flDuration = pKeyValues->GetFloat( "duration" ); + int nFlags = pKeyValues->GetInt( "flags" ); + TE_BreakModel( filter, 0.0f, vecOrigin, angles, vecSize, vecVel, + nModelIndex, nRandomization, nCount, flDuration, nFlags ); +} + diff --git a/game/client/c_te_bspdecal.cpp b/game/client/c_te_bspdecal.cpp new file mode 100644 index 00000000..0c0fd8a9 --- /dev/null +++ b/game/client/c_te_bspdecal.cpp @@ -0,0 +1,109 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "iefx.h" +#include "fx.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// UNDONE: Get rid of this? +#define FDECAL_PERMANENT 0x01 + +//----------------------------------------------------------------------------- +// Purpose: BSP Decal TE +//----------------------------------------------------------------------------- +class C_TEBSPDecal : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEBSPDecal, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEBSPDecal( void ); + virtual ~C_TEBSPDecal( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + Vector m_vecOrigin; + int m_nEntity; + int m_nIndex; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBSPDecal::C_TEBSPDecal( void ) +{ + m_vecOrigin.Init(); + m_nEntity = 0; + m_nIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBSPDecal::~C_TEBSPDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEBSPDecal::Precache( void ) +{ +} + +void TE_BSPDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int entity, int index ) +{ + C_BaseEntity *ent; + if ( ( ent = cl_entitylist->GetEnt( entity ) ) == NULL ) + { + DevMsg( 1, "Decal: entity = %i", entity ); + return; + } + + if ( r_decals.GetInt() ) + { + effects->DecalShoot( index, entity, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), *pos, 0, FDECAL_PERMANENT ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBSPDecal::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEBSPDecal::PostDataUpdate" ); + + C_BaseEntity *ent; + if ( ( ent = cl_entitylist->GetEnt( m_nEntity ) ) == NULL ) + { + DevMsg( 1, "Decal: entity = %i", m_nEntity ); + return; + } + + if ( r_decals.GetInt() ) + { + effects->DecalShoot( m_nIndex, m_nEntity, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), m_vecOrigin, 0, FDECAL_PERMANENT ); + } +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBSPDecal, DT_TEBSPDecal, CTEBSPDecal) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropInt( RECVINFO(m_nEntity)), + RecvPropInt( RECVINFO(m_nIndex)), +END_RECV_TABLE() + diff --git a/game/client/c_te_bubbles.cpp b/game/client/c_te_bubbles.cpp new file mode 100644 index 00000000..83643c20 --- /dev/null +++ b/game/client/c_te_bubbles.cpp @@ -0,0 +1,85 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Bubbles TE +//----------------------------------------------------------------------------- +class C_TEBubbles : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEBubbles, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEBubbles( void ); + virtual ~C_TEBubbles( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecMins; + Vector m_vecMaxs; + float m_fHeight; + int m_nModelIndex; + int m_nCount; + float m_fSpeed; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBubbles::C_TEBubbles( void ) +{ + m_vecMins.Init(); + m_vecMaxs.Init(); + m_fHeight = 0.0; + m_nModelIndex = 0; + m_nCount = 0; + m_fSpeed = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBubbles::~C_TEBubbles( void ) +{ +} + +void TE_Bubbles( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float height, int modelindex, int count, float speed ) +{ + tempents->Bubbles( *mins, *maxs, height, modelindex, count, speed ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBubbles::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEBubbles::PostDataUpdate" ); + + tempents->Bubbles( m_vecMins, m_vecMaxs, m_fHeight, m_nModelIndex, m_nCount, m_fSpeed ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBubbles, DT_TEBubbles, CTEBubbles) + RecvPropVector( RECVINFO(m_vecMins)), + RecvPropVector( RECVINFO(m_vecMaxs)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropFloat( RECVINFO(m_fHeight )), + RecvPropInt( RECVINFO(m_nCount)), + RecvPropFloat( RECVINFO(m_fSpeed )), +END_RECV_TABLE() + diff --git a/game/client/c_te_bubbletrail.cpp b/game/client/c_te_bubbletrail.cpp new file mode 100644 index 00000000..9bfbbc26 --- /dev/null +++ b/game/client/c_te_bubbletrail.cpp @@ -0,0 +1,84 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Bubble Trail TE +//----------------------------------------------------------------------------- +class C_TEBubbleTrail : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEBubbleTrail, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEBubbleTrail( void ); + virtual ~C_TEBubbleTrail( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecMins; + Vector m_vecMaxs; + float m_flWaterZ; + int m_nModelIndex; + int m_nCount; + float m_fSpeed; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBubbleTrail::C_TEBubbleTrail( void ) +{ + m_vecMins.Init(); + m_vecMaxs.Init(); + m_flWaterZ = 0.0; + m_nModelIndex = 0; + m_nCount = 0; + m_fSpeed = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEBubbleTrail::~C_TEBubbleTrail( void ) +{ +} + +void TE_BubbleTrail( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float flWaterZ, int modelindex, int count, float speed ) +{ + tempents->BubbleTrail( *mins, *maxs, flWaterZ, modelindex, count, speed ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEBubbleTrail::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEBubbleTrail::PostDataUpdate" ); + + tempents->BubbleTrail( m_vecMins, m_vecMaxs, m_flWaterZ, m_nModelIndex, m_nCount, m_fSpeed ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEBubbleTrail, DT_TEBubbleTrail, CTEBubbleTrail) + RecvPropVector( RECVINFO(m_vecMins)), + RecvPropVector( RECVINFO(m_vecMaxs)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropFloat( RECVINFO(m_flWaterZ )), + RecvPropInt( RECVINFO(m_nCount)), + RecvPropFloat( RECVINFO(m_fSpeed )), +END_RECV_TABLE() diff --git a/game/client/c_te_clientprojectile.cpp b/game/client/c_te_clientprojectile.cpp new file mode 100644 index 00000000..80451289 --- /dev/null +++ b/game/client/c_te_clientprojectile.cpp @@ -0,0 +1,78 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Client Projectile TE +//----------------------------------------------------------------------------- +class C_TEClientProjectile : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEClientProjectile, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEClientProjectile( void ); + virtual ~C_TEClientProjectile( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + Vector m_vecVelocity; + int m_nModelIndex; + int m_nLifeTime; + EHANDLE m_hOwner; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEClientProjectile::C_TEClientProjectile( void ) +{ + m_vecOrigin.Init(); + m_vecVelocity.Init(); + m_nModelIndex = 0; + m_nLifeTime = 0; + m_hOwner = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEClientProjectile::~C_TEClientProjectile( void ) +{ +} + +void TE_ClientProjectile( IRecipientFilter& filter, float delay, + const Vector* vecOrigin, const Vector* vecVelocity, int modelindex, int lifetime, CBaseEntity *pOwner ) +{ + tempents->ClientProjectile( *vecOrigin, *vecVelocity, vec3_origin, modelindex, lifetime, pOwner ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEClientProjectile::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEClientProjectile::PostDataUpdate" ); + + tempents->ClientProjectile( m_vecOrigin, m_vecVelocity, vec3_origin, m_nModelIndex, m_nLifeTime, m_hOwner ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEClientProjectile, DT_TEClientProjectile, CTEClientProjectile) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropVector( RECVINFO(m_vecVelocity)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropInt( RECVINFO(m_nLifeTime)), + RecvPropEHandle( RECVINFO(m_hOwner)), +END_RECV_TABLE() diff --git a/game/client/c_te_decal.cpp b/game/client/c_te_decal.cpp new file mode 100644 index 00000000..cf2a0bac --- /dev/null +++ b/game/client/c_te_decal.cpp @@ -0,0 +1,179 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "iefx.h" +#include "engine/IStaticPropMgr.h" +#include "tier1/keyvalues.h" +#include "tier0/vprof.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Decal TE +//----------------------------------------------------------------------------- +class C_TEDecal : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEDecal, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEDecal( void ); + virtual ~C_TEDecal( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + Vector m_vecOrigin; + Vector m_vecStart; + int m_nEntity; + int m_nHitbox; + int m_nIndex; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEDecal, DT_TEDecal, CTEDecal) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropVector( RECVINFO(m_vecStart)), + RecvPropInt( RECVINFO(m_nEntity)), + RecvPropInt( RECVINFO(m_nHitbox)), + RecvPropInt( RECVINFO(m_nIndex)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEDecal::C_TEDecal( void ) +{ + m_vecOrigin.Init(); + m_vecStart.Init(); + m_nEntity = 0; + m_nIndex = 0; + m_nHitbox = 0; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEDecal::~C_TEDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEDecal::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordDecal( const Vector &pos, const Vector &start, + int entity, int hitbox, int index ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + // FIXME: Can't record on entities yet + if ( entity != 0 ) + return; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_DECAL ); + msg->SetString( "name", "TE_Decal" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", pos.x ); + msg->SetFloat( "originy", pos.y ); + msg->SetFloat( "originz", pos.z ); + msg->SetFloat( "startx", start.x ); + msg->SetFloat( "starty", start.y ); + msg->SetFloat( "startz", start.z ); + msg->SetInt( "hitbox", hitbox ); + msg->SetString( "decalname", effects->Draw_DecalNameFromIndex( index ) ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Tempent +//----------------------------------------------------------------------------- +void TE_Decal( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* start, int entity, int hitbox, int index ) +{ + RecordDecal( *pos, *start, entity, hitbox, index ); + + trace_t tr; + + // Special case for world entity with hitbox: + if ( (entity == 0) && (hitbox != 0) ) + { + Ray_t ray; + ray.Init( *start, *pos ); + staticpropmgr->AddDecalToStaticProp( *start, *pos, hitbox - 1, index, false, tr ); + } + else + { + // Only decal the world + brush models + // Here we deal with decals on entities. + C_BaseEntity* ent; + if ( ( ent = cl_entitylist->GetEnt( entity ) ) == false ) + return; + + ent->AddDecal( *start, *pos, *pos, hitbox, + index, false, tr ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEDecal::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEDecal::PostDataUpdate" ); + + CBroadcastRecipientFilter filter; + TE_Decal( filter, 0.0f, &m_vecOrigin, &m_vecStart, m_nEntity, m_nHitbox, m_nIndex ); +} + + +//----------------------------------------------------------------------------- +// Playback +//----------------------------------------------------------------------------- +void TE_Decal( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecStart; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecStart.x = pKeyValues->GetFloat( "startx" ); + vecStart.y = pKeyValues->GetFloat( "starty" ); + vecStart.z = pKeyValues->GetFloat( "startz" ); + int nHitbox = pKeyValues->GetInt( "hitbox" ); + const char *pDecalName = pKeyValues->GetString( "decalname" ); + + TE_Decal( filter, 0.0f, &vecOrigin, &vecStart, 0, nHitbox, effects->Draw_DecalIndexFromName( (char*)pDecalName ) ); +} diff --git a/game/client/c_te_dynamiclight.cpp b/game/client/c_te_dynamiclight.cpp new file mode 100644 index 00000000..5fd254cd --- /dev/null +++ b/game/client/c_te_dynamiclight.cpp @@ -0,0 +1,152 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "dlight.h" +#include "iefx.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dynamic Light +//----------------------------------------------------------------------------- +class C_TEDynamicLight : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEDynamicLight, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEDynamicLight( void ); + virtual ~C_TEDynamicLight( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + float m_fRadius; + int r; + int g; + int b; + int exponent; + float m_fTime; + float m_fDecay; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEDynamicLight, DT_TEDynamicLight, CTEDynamicLight) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropInt( RECVINFO(r)), + RecvPropInt( RECVINFO(g)), + RecvPropInt( RECVINFO(b)), + RecvPropInt( RECVINFO(exponent)), + RecvPropFloat( RECVINFO(m_fRadius)), + RecvPropFloat( RECVINFO(m_fTime)), + RecvPropFloat( RECVINFO(m_fDecay)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEDynamicLight::C_TEDynamicLight( void ) +{ + m_vecOrigin.Init(); + r = 0; + g = 0; + b = 0; + exponent = 0; + m_fRadius = 0.0; + m_fTime = 0.0; + m_fDecay = 0.0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEDynamicLight::~C_TEDynamicLight( void ) +{ +} + +void TE_DynamicLight( IRecipientFilter& filter, float delay, + const Vector* org, int r, int g, int b, int exponent, float radius, float time, float decay, int nLightIndex ) +{ + dlight_t *dl = effects->CL_AllocDlight( nLightIndex ); + if ( !dl ) + return; + + dl->origin = *org; + dl->radius = radius; + dl->color.r = r; + dl->color.g = g; + dl->color.b = b; + dl->color.exponent = exponent; + dl->die = gpGlobals->curtime + time; + dl->decay = decay; + + if ( ToolsEnabled() && clienttools->IsInRecordingMode() ) + { + Color clr( r, g, b, 255 ); + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_DYNAMIC_LIGHT ); + msg->SetString( "name", "TE_DynamicLight" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "duration", time ); + msg->SetFloat( "originx", org->x ); + msg->SetFloat( "originy", org->y ); + msg->SetFloat( "originz", org->z ); + msg->SetFloat( "radius", radius ); + msg->SetFloat( "decay", decay ); + msg->SetColor( "color", clr ); + msg->SetInt( "exponent", exponent ); + msg->SetInt( "lightindex", nLightIndex ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEDynamicLight::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEDynamicLight::PostDataUpdate" ); + + CBroadcastRecipientFilter filter; + TE_DynamicLight( filter, 0.0f, &m_vecOrigin, r, g, b, exponent, m_fRadius, m_fTime, m_fDecay, LIGHT_INDEX_TE_DYNAMIC ); +} + +void TE_DynamicLight( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + float flDuration = pKeyValues->GetFloat( "duration" ); + Color c = pKeyValues->GetColor( "color" ); + int nExponent = pKeyValues->GetInt( "exponent" ); + float flRadius = pKeyValues->GetFloat( "radius" ); + float flDecay = pKeyValues->GetFloat( "decay" ); + int nLightIndex = pKeyValues->GetInt( "lightindex", LIGHT_INDEX_TE_DYNAMIC ); + + TE_DynamicLight( filter, 0.0f, &vecOrigin, c.r(), c.g(), c.b(), nExponent, + flRadius, flDuration, flDecay, nLightIndex ); +} + diff --git a/game/client/c_te_effect_dispatch.cpp b/game/client/c_te_effect_dispatch.cpp new file mode 100644 index 00000000..338455ed --- /dev/null +++ b/game/client/c_te_effect_dispatch.cpp @@ -0,0 +1,221 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "networkstringtable_clientdll.h" +#include "effect_dispatch_data.h" +#include "c_te_effect_dispatch.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// CClientEffectRegistration registration +//----------------------------------------------------------------------------- + +CClientEffectRegistration *CClientEffectRegistration::s_pHead = NULL; + +CClientEffectRegistration::CClientEffectRegistration( const char *pEffectName, ClientEffectCallback fn ) +{ + m_pEffectName = pEffectName; + m_pFunction = fn; + m_pNext = s_pHead; + s_pHead = this; +} + + +//----------------------------------------------------------------------------- +// Purpose: EffectDispatch TE +//----------------------------------------------------------------------------- +class C_TEEffectDispatch : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEEffectDispatch, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEEffectDispatch( void ); + virtual ~C_TEEffectDispatch( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + CEffectData m_EffectData; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEEffectDispatch::C_TEEffectDispatch( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEEffectDispatch::~C_TEEffectDispatch( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DispatchEffectToCallback( const char *pEffectName, const CEffectData &m_EffectData ) +{ + // Look through all the registered callbacks + for ( CClientEffectRegistration *pReg = CClientEffectRegistration::s_pHead; pReg; pReg = pReg->m_pNext ) + { + // If the name matches, call it + if ( Q_stricmp( pReg->m_pEffectName, pEffectName ) == 0 ) + { + pReg->m_pFunction( m_EffectData ); + return; + } + } + + DevMsg("DispatchEffect: effect '%s' not found on client\n", pEffectName ); + +} + + +//----------------------------------------------------------------------------- +// Record effects +//----------------------------------------------------------------------------- +static void RecordEffect( const char *pEffectName, const CEffectData &data ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() && ( (data.m_fFlags & EFFECTDATA_NO_RECORD) == 0 ) ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + const char *pSurfacePropName = physprops->GetPropName( data.m_nSurfaceProp ); + + char pName[1024]; + Q_snprintf( pName, sizeof(pName), "TE_DispatchEffect %s %s", pEffectName, pSurfacePropName ); + + msg->SetInt( "te", TE_DISPATCH_EFFECT ); + msg->SetString( "name", pName ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", data.m_vOrigin.x ); + msg->SetFloat( "originy", data.m_vOrigin.y ); + msg->SetFloat( "originz", data.m_vOrigin.z ); + msg->SetFloat( "startx", data.m_vStart.x ); + msg->SetFloat( "starty", data.m_vStart.y ); + msg->SetFloat( "startz", data.m_vStart.z ); + msg->SetFloat( "normalx", data.m_vNormal.x ); + msg->SetFloat( "normaly", data.m_vNormal.y ); + msg->SetFloat( "normalz", data.m_vNormal.z ); + msg->SetFloat( "anglesx", data.m_vAngles.x ); + msg->SetFloat( "anglesy", data.m_vAngles.y ); + msg->SetFloat( "anglesz", data.m_vAngles.z ); + msg->SetInt( "flags", data.m_fFlags ); + msg->SetFloat( "scale", data.m_flScale ); + msg->SetFloat( "magnitude", data.m_flMagnitude ); + msg->SetFloat( "radius", data.m_flRadius ); + msg->SetString( "surfaceprop", pSurfacePropName ); + msg->SetInt( "color", data.m_nColor ); + msg->SetInt( "damagetype", data.m_nDamageType ); + msg->SetInt( "hitbox", data.m_nHitBox ); + msg->SetString( "effectname", pEffectName ); + + // FIXME: Need to write the attachment name here + msg->SetInt( "attachmentindex", data.m_nAttachmentIndex ); + + // NOTE: Ptrs are our way of indicating it's an entindex + msg->SetPtr( "entindex", (void*)data.entindex() ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEEffectDispatch::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEEffectDispatch::PostDataUpdate" ); + + // Find the effect name. + const char *pEffectName = g_StringTableEffectDispatch->GetString( m_EffectData.GetEffectNameIndex() ); + if ( pEffectName ) + { + DispatchEffectToCallback( pEffectName, m_EffectData ); + RecordEffect( pEffectName, m_EffectData ); + } +} + + +IMPLEMENT_CLIENTCLASS_EVENT_DT( C_TEEffectDispatch, DT_TEEffectDispatch, CTEEffectDispatch ) + + RecvPropDataTable( RECVINFO_DT( m_EffectData ), 0, &REFERENCE_RECV_TABLE( DT_EffectData ) ) + +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: Clientside version +//----------------------------------------------------------------------------- +void TE_DispatchEffect( IRecipientFilter& filter, float delay, const Vector &pos, const char *pName, const CEffectData &data ) +{ + DispatchEffectToCallback( pName, data ); + RecordEffect( pName, data ); +} + +// Client version of dispatch effect, for predicted weapons +void DispatchEffect( const char *pName, const CEffectData &data ) +{ + CPASFilter filter( data.m_vOrigin ); + te->DispatchEffect( filter, 0.0, data.m_vOrigin, pName, data ); +} + + +//----------------------------------------------------------------------------- +// Playback +//----------------------------------------------------------------------------- +void TE_DispatchEffect( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + CEffectData data; + data.m_nMaterial = 0; + + data.m_vOrigin.x = pKeyValues->GetFloat( "originx" ); + data.m_vOrigin.y = pKeyValues->GetFloat( "originy" ); + data.m_vOrigin.z = pKeyValues->GetFloat( "originz" ); + data.m_vStart.x = pKeyValues->GetFloat( "startx" ); + data.m_vStart.y = pKeyValues->GetFloat( "starty" ); + data.m_vStart.z = pKeyValues->GetFloat( "startz" ); + data.m_vNormal.x = pKeyValues->GetFloat( "normalx" ); + data.m_vNormal.y = pKeyValues->GetFloat( "normaly" ); + data.m_vNormal.z = pKeyValues->GetFloat( "normalz" ); + data.m_vAngles.x = pKeyValues->GetFloat( "anglesx" ); + data.m_vAngles.y = pKeyValues->GetFloat( "anglesy" ); + data.m_vAngles.z = pKeyValues->GetFloat( "anglesz" ); + data.m_fFlags = pKeyValues->GetInt( "flags" ); + data.m_flScale = pKeyValues->GetFloat( "scale" ); + data.m_flMagnitude = pKeyValues->GetFloat( "magnitude" ); + data.m_flRadius = pKeyValues->GetFloat( "radius" ); + const char *pSurfaceProp = pKeyValues->GetString( "surfaceprop" ); + data.m_nSurfaceProp = physprops->GetSurfaceIndex( pSurfaceProp ); + data.m_nDamageType = pKeyValues->GetInt( "damagetype" ); + data.m_nHitBox = pKeyValues->GetInt( "hitbox" ); + data.m_nColor = pKeyValues->GetInt( "color" ); + data.m_nAttachmentIndex = pKeyValues->GetInt( "attachmentindex" ); + + // NOTE: Ptrs are our way of indicating it's an entindex + ClientEntityHandle_t hWorld = ClientEntityList().EntIndexToHandle( 0 ); + data.m_hEntity = (int)pKeyValues->GetPtr( "entindex", (void*)hWorld.ToInt() ); + + const char *pEffectName = pKeyValues->GetString( "effectname" ); + + TE_DispatchEffect( filter, 0.0f, data.m_vOrigin, pEffectName, data ); +} diff --git a/game/client/c_te_effect_dispatch.h b/game/client/c_te_effect_dispatch.h new file mode 100644 index 00000000..23b44c16 --- /dev/null +++ b/game/client/c_te_effect_dispatch.h @@ -0,0 +1,46 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_TE_EFFECT_DISPATCH_H +#define C_TE_EFFECT_DISPATCH_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "effect_dispatch_data.h" + + +typedef void (*ClientEffectCallback)( const CEffectData &data ); + + +class CClientEffectRegistration +{ +public: + CClientEffectRegistration( const char *pEffectName, ClientEffectCallback fn ); + +public: + const char *m_pEffectName; + ClientEffectCallback m_pFunction; + CClientEffectRegistration *m_pNext; + + static CClientEffectRegistration *s_pHead; +}; + + +// +// Use this macro to register a client effect callback. +// If you do DECLARE_CLIENT_EFFECT( "MyEffectName", MyCallback ), then MyCallback will be +// called when the server does DispatchEffect( "MyEffect", data ) +// +#define DECLARE_CLIENT_EFFECT( effectName, callbackFunction ) \ + static CClientEffectRegistration ClientEffectReg_##callbackFunction##( effectName, callbackFunction ); + +void DispatchEffectToCallback( const char *pEffectName, const CEffectData &m_EffectData ); +void DispatchEffect( const char *pName, const CEffectData &data ); + +#endif // C_TE_EFFECT_DISPATCH_H diff --git a/game/client/c_te_energysplash.cpp b/game/client/c_te_energysplash.cpp new file mode 100644 index 00000000..44984190 --- /dev/null +++ b/game/client/c_te_energysplash.cpp @@ -0,0 +1,90 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "IEffects.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Energy Splash TE +//----------------------------------------------------------------------------- +class C_TEEnergySplash : public C_BaseTempEntity +{ +public: + DECLARE_CLIENTCLASS(); + + C_TEEnergySplash( void ); + virtual ~C_TEEnergySplash( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + Vector m_vecPos; + Vector m_vecDir; + bool m_bExplosive; + + const struct model_t *m_pModel; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEEnergySplash::C_TEEnergySplash( void ) +{ + m_vecPos.Init(); + m_vecDir.Init(); + m_bExplosive = false; + m_pModel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEEnergySplash::~C_TEEnergySplash( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEEnergySplash::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEEnergySplash::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEEnergySplash::PostDataUpdate" ); + + g_pEffects->EnergySplash( m_vecPos, m_vecDir, m_bExplosive ); +} + +void TE_EnergySplash( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir, bool bExplosive ) +{ + g_pEffects->EnergySplash( *pos, *dir, bExplosive ); +} + +// Expose the TE to the engine. +IMPLEMENT_CLIENTCLASS_EVENT( C_TEEnergySplash, DT_TEEnergySplash, CTEEnergySplash ); + +BEGIN_RECV_TABLE_NOBASE(C_TEEnergySplash, DT_TEEnergySplash) + RecvPropVector(RECVINFO(m_vecPos)), + RecvPropVector(RECVINFO(m_vecDir)), + RecvPropInt(RECVINFO(m_bExplosive)), +END_RECV_TABLE() + diff --git a/game/client/c_te_explosion.cpp b/game/client/c_te_explosion.cpp new file mode 100644 index 00000000..60bb3b98 --- /dev/null +++ b/game/client/c_te_explosion.cpp @@ -0,0 +1,333 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Client explosions +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "tempentity.h" // FLAGS +#include "c_te_particlesystem.h" +#include "RagdollExplosionEnumerator.h" +#include "glow_overlay.h" +#include "fx_explosion.h" +#include "ClientEffectPrecacheSystem.h" +#include "engine/ivdebugoverlay.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define OLD_EXPLOSION 0 + + +// Enumator class for ragdolls being affected by explosive forces +CRagdollExplosionEnumerator::CRagdollExplosionEnumerator( Vector origin, float radius, float magnitude ) +{ + m_vecOrigin = origin; + m_flMagnitude = magnitude; + m_flRadius = radius; +} + +// Actual work code +IterationRetval_t CRagdollExplosionEnumerator::EnumElement( IHandleEntity *pHandleEntity ) +{ + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); + + if ( pEnt == NULL ) + return ITERATION_CONTINUE; + + C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt ); + + // If the ragdoll was created on this tick, then the forces were already applied on the server + if ( pModel == NULL || WasRagdollCreatedOnCurrentTick( pEnt ) ) + return ITERATION_CONTINUE; + + m_Entities.AddToTail( pEnt ); + + return ITERATION_CONTINUE; +} + +CRagdollExplosionEnumerator::~CRagdollExplosionEnumerator() +{ + for (int i = 0; i < m_Entities.Count(); i++ ) + { + C_BaseEntity *pEnt = m_Entities[i]; + C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt ); + + Vector position = pEnt->CollisionProp()->GetCollisionOrigin(); + + Vector dir = position - m_vecOrigin; + float dist = VectorNormalize( dir ); + float force = m_flMagnitude - ( ( m_flMagnitude / m_flRadius ) * dist ); + + if ( force <= 1.0f ) + continue; + + trace_t tr; + UTIL_TraceLine( m_vecOrigin, position, MASK_SHOT_HULL, NULL, COLLISION_GROUP_NONE, &tr ); + + // debugoverlay->AddLineOverlay( m_vecOrigin, position, 0,255,0, true, 18.0 ); + + if ( tr.fraction < 1.0f && tr.m_pEnt != pModel ) + continue; + + dir *= force; // scale force + + // tricky, adjust tr.start so end-start->= force + tr.startpos = tr.endpos - dir; + // move expolsion center a bit down, so things fly higher + tr.startpos.z -= 32.0f; + + pModel->ImpactTrace( &tr, DMG_BLAST, NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Explosion TE +//----------------------------------------------------------------------------- +class C_TEExplosion : public C_TEParticleSystem +{ +public: + DECLARE_CLASS( C_TEExplosion, C_TEParticleSystem ); + DECLARE_CLIENTCLASS(); + + C_TEExplosion( void ); + virtual ~C_TEExplosion( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + +private: + // Recording + void RecordExplosion( ); + +public: + void AffectRagdolls( void ); + + int m_nModelIndex; + float m_fScale; + int m_nFrameRate; + int m_nFlags; + Vector m_vecNormal; + char m_chMaterialType; + int m_nRadius; + int m_nMagnitude; + + //CParticleCollision m_ParticleCollision; + CParticleMgr *m_pParticleMgr; + PMaterialHandle m_MaterialHandle; + bool m_bShouldAffectRagdolls; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEExplosion, DT_TEExplosion, CTEExplosion) + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropFloat( RECVINFO(m_fScale )), + RecvPropInt( RECVINFO(m_nFrameRate)), + RecvPropInt( RECVINFO(m_nFlags)), + RecvPropVector( RECVINFO(m_vecNormal)), + RecvPropInt( RECVINFO(m_chMaterialType)), + RecvPropInt( RECVINFO(m_nRadius)), + RecvPropInt( RECVINFO(m_nMagnitude)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEExplosion::C_TEExplosion( void ) +{ + m_bShouldAffectRagdolls = true; + m_nModelIndex = 0; + m_fScale = 0; + m_nFrameRate = 0; + m_nFlags = 0; + m_vecNormal.Init(); + m_chMaterialType = 'C'; + m_nRadius = 0; + m_nMagnitude = 0; + + m_pParticleMgr = NULL; + m_MaterialHandle = INVALID_MATERIAL_HANDLE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEExplosion::~C_TEExplosion( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEExplosion::AffectRagdolls( void ) +{ + if ( ( m_nRadius == 0 ) || ( m_nMagnitude == 0 ) || (!m_bShouldAffectRagdolls) ) + return; + + CRagdollExplosionEnumerator ragdollEnum( m_vecOrigin, m_nRadius, m_nMagnitude ); + partition->EnumerateElementsInSphere( PARTITION_CLIENT_RESPONSIVE_EDICTS, m_vecOrigin, m_nRadius, false, &ragdollEnum ); +} + +// +// CExplosionOverlay +// + +bool CExplosionOverlay::Update( void ) +{ + m_flLifetime += gpGlobals->frametime; + + const float flTotalLifetime = 0.1f; + + if ( m_flLifetime < flTotalLifetime ) + { + float flColorScale = 1.0f - ( m_flLifetime / flTotalLifetime ); + + for( int i=0; i < m_nSprites; i++ ) + { + m_Sprites[i].m_vColor = m_vBaseColors[i] * flColorScale; + + m_Sprites[i].m_flHorzSize += 16.0f * gpGlobals->frametime; + m_Sprites[i].m_flVertSize += 16.0f * gpGlobals->frametime; + } + + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +void C_TEExplosion::RecordExplosion( ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + const model_t* pModel = (m_nModelIndex != 0) ? modelinfo->GetModel( m_nModelIndex ) : NULL; + const char *pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_EXPLOSION ); + msg->SetString( "name", "TE_Explosion" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", m_vecOrigin.x ); + msg->SetFloat( "originy", m_vecOrigin.y ); + msg->SetFloat( "originz", m_vecOrigin.z ); + msg->SetFloat( "directionx", m_vecNormal.x ); + msg->SetFloat( "directiony", m_vecNormal.y ); + msg->SetFloat( "directionz", m_vecNormal.z ); + msg->SetString( "model", pModelName ); + msg->SetFloat( "scale", m_fScale ); + msg->SetInt( "framerate", m_nFrameRate ); + msg->SetInt( "flags", m_nFlags ); + msg->SetInt( "materialtype", m_chMaterialType ); + msg->SetInt( "radius", m_nRadius ); + msg->SetInt( "magnitude", m_nMagnitude ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEExplosion::PostDataUpdate( DataUpdateType_t updateType ) +{ + RecordExplosion(); + + AffectRagdolls(); + + // Filter out a water explosion + if ( UTIL_PointContents( m_vecOrigin ) & CONTENTS_WATER ) + { + WaterExplosionEffect().Create( m_vecOrigin, m_nMagnitude, m_fScale, m_nFlags ); + return; + } + + if ( !( m_nFlags & TE_EXPLFLAG_NOFIREBALL ) ) + { + if ( CExplosionOverlay *pOverlay = new CExplosionOverlay ) + { + pOverlay->m_flLifetime = 0; + pOverlay->m_vPos = m_vecOrigin; + pOverlay->m_nSprites = 1; + + pOverlay->m_vBaseColors[0].Init( 1.0f, 0.9f, 0.7f ); + + pOverlay->m_Sprites[0].m_flHorzSize = 0.05f; + pOverlay->m_Sprites[0].m_flVertSize = pOverlay->m_Sprites[0].m_flHorzSize*0.5f; + + pOverlay->Activate(); + } + } + + BaseExplosionEffect().Create( m_vecOrigin, m_nMagnitude, m_fScale, m_nFlags ); +} + +void C_TEExplosion::RenderParticles( CParticleRenderIterator *pIterator ) +{ +} + + +void C_TEExplosion::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + pIterator->RemoveAllParticles(); +} + + +void TE_Explosion( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate, int flags, int radius, int magnitude, + const Vector* normal = NULL, unsigned char materialType = 'C', bool bShouldAffectRagdolls = true ) +{ + // Major hack to access singleton object for doing this event (simulate receiving network message) + __g_C_TEExplosion.m_nModelIndex = modelindex; + __g_C_TEExplosion.m_fScale = scale; + __g_C_TEExplosion.m_nFrameRate = framerate; + __g_C_TEExplosion.m_nFlags = flags; + __g_C_TEExplosion.m_vecOrigin = *pos; + __g_C_TEExplosion.m_vecNormal = *normal; + __g_C_TEExplosion.m_chMaterialType = materialType; + __g_C_TEExplosion.m_nRadius = radius; + __g_C_TEExplosion.m_nMagnitude = magnitude; + __g_C_TEExplosion.m_bShouldAffectRagdolls = bShouldAffectRagdolls; + + __g_C_TEExplosion.PostDataUpdate( DATA_UPDATE_CREATED ); + +} + +void TE_Explosion( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecNormal; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecNormal.x = pKeyValues->GetFloat( "directionx" ); + vecNormal.y = pKeyValues->GetFloat( "directiony" ); + vecNormal.z = pKeyValues->GetFloat( "directionz" ); + const char *pModelName = pKeyValues->GetString( "model" ); + int nModelIndex = pModelName[0] ? modelinfo->GetModelIndex( pModelName ) : 0; + float flScale = pKeyValues->GetFloat( "scale" ); + int nFrameRate = pKeyValues->GetInt( "framerate" ); + int nFlags = pKeyValues->GetInt( "flags" ); + int nMaterialType = pKeyValues->GetInt( "materialtype" ); + int nRadius = pKeyValues->GetInt( "radius" ); + int nMagnitude = pKeyValues->GetInt( "magnitude" ); + TE_Explosion( filter, 0.0f, &vecOrigin, nModelIndex, flScale, nFrameRate, + nFlags, nRadius, nMagnitude, &vecNormal, (unsigned char)nMaterialType, false ); +} diff --git a/game/client/c_te_fizz.cpp b/game/client/c_te_fizz.cpp new file mode 100644 index 00000000..f0036578 --- /dev/null +++ b/game/client/c_te_fizz.cpp @@ -0,0 +1,92 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Fizz TE +//----------------------------------------------------------------------------- +class C_TEFizz : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEFizz, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEFizz( void ); + virtual ~C_TEFizz( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + int m_nEntity; + int m_nModelIndex; + int m_nDensity; + int m_nCurrent; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEFizz::C_TEFizz( void ) +{ + m_nEntity = 0; + m_nModelIndex = 0; + m_nDensity = 0; + m_nCurrent = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEFizz::~C_TEFizz( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEFizz::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEFizz::PostDataUpdate" ); + + C_BaseEntity *pEnt = cl_entitylist->GetEnt( m_nEntity ); + if (pEnt != NULL) + { + tempents->FizzEffect(pEnt, m_nModelIndex, m_nDensity, m_nCurrent ); + } +} + +void TE_Fizz( IRecipientFilter& filter, float delay, + const C_BaseEntity *ed, int modelindex, int density, int current ) +{ + C_BaseEntity *pEnt = (C_BaseEntity *)ed; + if (pEnt != NULL) + { + tempents->FizzEffect(pEnt, modelindex, density, current ); + } +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEFizz, DT_TEFizz, CTEFizz) + RecvPropInt( RECVINFO(m_nEntity)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropInt( RECVINFO(m_nDensity)), + RecvPropInt( RECVINFO(m_nCurrent)), +END_RECV_TABLE() + + diff --git a/game/client/c_te_footprint.cpp b/game/client/c_te_footprint.cpp new file mode 100644 index 00000000..53758674 --- /dev/null +++ b/game/client/c_te_footprint.cpp @@ -0,0 +1,110 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "iefx.h" +#include "fx.h" +#include "tier0/vprof.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Footprint Decal TE +//----------------------------------------------------------------------------- + +class C_TEFootprintDecal : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEFootprintDecal, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEFootprintDecal( void ); + virtual ~C_TEFootprintDecal( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + Vector m_vecOrigin; + Vector m_vecDirection; + Vector m_vecStart; + int m_nEntity; + int m_nIndex; + char m_chMaterialType; +}; + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEFootprintDecal, DT_TEFootprintDecal, CTEFootprintDecal) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropVector( RECVINFO(m_vecDirection)), + RecvPropInt( RECVINFO(m_nEntity)), + RecvPropInt( RECVINFO(m_nIndex)), + RecvPropInt( RECVINFO(m_chMaterialType)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- + +C_TEFootprintDecal::C_TEFootprintDecal( void ) +{ + m_vecOrigin.Init(); + m_vecStart.Init(); + m_nEntity = 0; + m_nIndex = 0; + m_chMaterialType = 'C'; +} + +C_TEFootprintDecal::~C_TEFootprintDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void C_TEFootprintDecal::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Do stuff when data changes +//----------------------------------------------------------------------------- + +void C_TEFootprintDecal::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEFootprintDecal::PostDataUpdate" ); + + // FIXME: Make this choose the decal based on material type + if ( r_decals.GetInt() ) + { + C_BaseEntity *ent = cl_entitylist->GetEnt( m_nEntity ); + if ( ent ) + { + effects->DecalShoot( m_nIndex, + m_nEntity, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), m_vecOrigin, &m_vecDirection, 0 ); + } + } +} + +void TE_FootprintDecal( IRecipientFilter& filter, float delay, const Vector *origin, const Vector* right, + int entity, int index, unsigned char materialType ) +{ + if ( r_decals.GetInt() ) + { + C_BaseEntity *ent = cl_entitylist->GetEnt( entity ); + if ( ent ) + { + effects->DecalShoot( index, entity, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), *origin, right, 0 ); + } + } +} \ No newline at end of file diff --git a/game/client/c_te_glassshatter.cpp b/game/client/c_te_glassshatter.cpp new file mode 100644 index 00000000..b44619ab --- /dev/null +++ b/game/client/c_te_glassshatter.cpp @@ -0,0 +1,320 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "particle_simple3D.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "fx.h" +#include "tier0/vprof.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define PI 3.14159265359 +#define GLASS_SHARD_MIN_LIFE 2 +#define GLASS_SHARD_MAX_LIFE 5 +#define GLASS_SHARD_NOISE 0.3 +#define GLASS_SHARD_GRAVITY 500 +#define GLASS_SHARD_DAMPING 0.3 + +#include "ClientEffectPrecacheSystem.h" + +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectGlassShatter ) +CLIENTEFFECT_MATERIAL( "effects/fleck_glass1" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_glass2" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_tile1" ) +CLIENTEFFECT_MATERIAL( "effects/fleck_tile2" ) +CLIENTEFFECT_REGISTER_END() + +//################################################### +// > C_TEShatterSurface +//################################################### +class C_TEShatterSurface : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEShatterSurface, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEShatterSurface( void ); + ~C_TEShatterSurface( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +private: + // Recording + void RecordShatterSurface( ); + +public: + Vector m_vecOrigin; + QAngle m_vecAngles; + Vector m_vecForce; + Vector m_vecForcePos; + float m_flWidth; + float m_flHeight; + float m_flShardSize; + PMaterialHandle m_pMaterialHandle; + int m_nSurfaceType; + byte m_uchFrontColor[3]; + byte m_uchBackColor[3]; +}; + + +//------------------------------------------------------------------------------ +// Networking +//------------------------------------------------------------------------------ +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEShatterSurface, DT_TEShatterSurface, CTEShatterSurface) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropVector( RECVINFO(m_vecAngles)), + RecvPropVector( RECVINFO(m_vecForce)), + RecvPropVector( RECVINFO(m_vecForcePos)), + RecvPropFloat( RECVINFO(m_flWidth)), + RecvPropFloat( RECVINFO(m_flHeight)), + RecvPropFloat( RECVINFO(m_flShardSize)), + RecvPropInt( RECVINFO(m_nSurfaceType)), + RecvPropInt( RECVINFO(m_uchFrontColor[0])), + RecvPropInt( RECVINFO(m_uchFrontColor[1])), + RecvPropInt( RECVINFO(m_uchFrontColor[2])), + RecvPropInt( RECVINFO(m_uchBackColor[0])), + RecvPropInt( RECVINFO(m_uchBackColor[1])), + RecvPropInt( RECVINFO(m_uchBackColor[2])), +END_RECV_TABLE() + + +//------------------------------------------------------------------------------ +// Constructor, destructor +//------------------------------------------------------------------------------ +C_TEShatterSurface::C_TEShatterSurface( void ) +{ + m_vecOrigin.Init(); + m_vecAngles.Init(); + m_vecForce.Init(); + m_vecForcePos.Init(); + m_flWidth = 16.0; + m_flHeight = 16.0; + m_flShardSize = 3; + m_nSurfaceType = SHATTERSURFACE_GLASS; + m_uchFrontColor[0] = 255; + m_uchFrontColor[1] = 255; + m_uchFrontColor[2] = 255; + m_uchBackColor[0] = 255; + m_uchBackColor[1] = 255; + m_uchBackColor[2] = 255; +} + +C_TEShatterSurface::~C_TEShatterSurface() +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +void C_TEShatterSurface::RecordShatterSurface( ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + Color front( m_uchFrontColor[0], m_uchFrontColor[1], m_uchFrontColor[2], 255 ); + Color back( m_uchBackColor[0], m_uchBackColor[1], m_uchBackColor[2], 255 ); + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_SHATTER_SURFACE ); + msg->SetString( "name", "TE_ShatterSurface" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", m_vecOrigin.x ); + msg->SetFloat( "originy", m_vecOrigin.y ); + msg->SetFloat( "originz", m_vecOrigin.z ); + msg->SetFloat( "anglesx", m_vecAngles.x ); + msg->SetFloat( "anglesy", m_vecAngles.y ); + msg->SetFloat( "anglesz", m_vecAngles.z ); + msg->SetFloat( "forcex", m_vecForce.x ); + msg->SetFloat( "forcey", m_vecForce.y ); + msg->SetFloat( "forcez", m_vecForce.z ); + msg->SetFloat( "forceposx", m_vecForcePos.x ); + msg->SetFloat( "forceposy", m_vecForcePos.y ); + msg->SetFloat( "forceposz", m_vecForcePos.z ); + msg->SetColor( "frontcolor", front ); + msg->SetColor( "backcolor", back ); + msg->SetFloat( "width", m_flWidth ); + msg->SetFloat( "height", m_flHeight ); + msg->SetFloat( "size", m_flShardSize ); + msg->SetInt( "surfacetype", m_nSurfaceType ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEShatterSurface::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEShatterSurface::PostDataUpdate" ); + + RecordShatterSurface(); + + CSmartPtr pGlassEmitter = CSimple3DEmitter::Create( "C_TEShatterSurface 1" ); + pGlassEmitter->SetSortOrigin( m_vecOrigin ); + + Vector vecColor; + engine->ComputeLighting( m_vecOrigin, NULL, true, vecColor ); + + // HACK: Blend a little toward white to match the materials... + VectorLerp( vecColor, Vector( 1, 1, 1 ), 0.3, vecColor ); + + PMaterialHandle *hMaterial; + if (m_nSurfaceType == SHATTERSURFACE_GLASS) + { + hMaterial = g_Mat_Fleck_Glass; + } + else + { + hMaterial = g_Mat_Fleck_Tile; + } + + // --------------------------------------------------- + // Figure out number of particles required to fill space + // --------------------------------------------------- + int nNumWide = m_flWidth / m_flShardSize; + int nNumHigh = m_flHeight / m_flShardSize; + + Vector vWidthStep,vHeightStep; + AngleVectors(m_vecAngles,NULL,&vWidthStep,&vHeightStep); + vWidthStep *= m_flShardSize; + vHeightStep *= m_flShardSize; + + // --------------------- + // Create glass shards + // ---------------------- + Vector vCurPos = m_vecOrigin; + vCurPos.x += 0.5*m_flShardSize; + vCurPos.z += 0.5*m_flShardSize; + + float flMinSpeed = 9999999999; + float flMaxSpeed = 0; + + Particle3D *pParticle = NULL; + + for (int width=0;widthAddParticle( sizeof(Particle3D), hMaterial[random->RandomInt(0,1)], vCurPos ); + + Vector vForceVel = Vector(0,0,0); + if (random->RandomInt(0, 3) != 0) + { + float flForceDistSqr = (vCurPos - m_vecForcePos).LengthSqr(); + vForceVel = m_vecForce; + if (flForceDistSqr > 0 ) + { + vForceVel *= ( 40.0f / flForceDistSqr ); + } + } + + if (pParticle) + { + pParticle->m_flLifeRemaining = random->RandomFloat(GLASS_SHARD_MIN_LIFE,GLASS_SHARD_MAX_LIFE); + pParticle->m_vecVelocity = vForceVel; + pParticle->m_vecVelocity += RandomVector(-25,25); + pParticle->m_uchSize = m_flShardSize + random->RandomFloat(-0.5*m_flShardSize,0.5*m_flShardSize); + pParticle->m_vAngles = m_vecAngles; + pParticle->m_flAngSpeed = random->RandomFloat(-400,400); + + pParticle->m_uchFrontColor[0] = (byte)(m_uchFrontColor[0] * vecColor.x ); + pParticle->m_uchFrontColor[1] = (byte)(m_uchFrontColor[1] * vecColor.y ); + pParticle->m_uchFrontColor[2] = (byte)(m_uchFrontColor[2] * vecColor.z ); + pParticle->m_uchBackColor[0] = (byte)(m_uchBackColor[0] * vecColor.x ); + pParticle->m_uchBackColor[1] = (byte)(m_uchBackColor[1] * vecColor.y ); + pParticle->m_uchBackColor[2] = (byte)(m_uchBackColor[2] * vecColor.z ); + } + + // Keep track of min and max speed for collision detection + float flForceSpeed = vForceVel.Length(); + if (flForceSpeed > flMaxSpeed) + { + flMaxSpeed = flForceSpeed; + } + if (flForceSpeed < flMinSpeed) + { + flMinSpeed = flForceSpeed; + } + + vCurPos += vHeightStep; + } + vCurPos -= nNumHigh*vHeightStep; + vCurPos += vWidthStep; + } + + // -------------------------------------------------- + // Set collision parameters + // -------------------------------------------------- + Vector vMoveDir = m_vecForce; + VectorNormalize(vMoveDir); + + pGlassEmitter->m_ParticleCollision.Setup( m_vecOrigin, &vMoveDir, GLASS_SHARD_NOISE, + flMinSpeed, flMaxSpeed, GLASS_SHARD_GRAVITY, GLASS_SHARD_DAMPING ); +} + +void TE_ShatterSurface( IRecipientFilter& filter, float delay, + const Vector* pos, const QAngle* angle, const Vector* vForce, const Vector* vForcePos, + float width, float height, float shardsize, ShatterSurface_t surfacetype, + int front_r, int front_g, int front_b, int back_r, int back_g, int back_b) +{ + // Major hack to simulate receiving network message + __g_C_TEShatterSurface.m_vecOrigin = *pos; + __g_C_TEShatterSurface.m_vecAngles = *angle; + __g_C_TEShatterSurface.m_vecForce = *vForce; + __g_C_TEShatterSurface.m_vecForcePos = *vForcePos; + __g_C_TEShatterSurface.m_flWidth = width; + __g_C_TEShatterSurface.m_flHeight = height; + __g_C_TEShatterSurface.m_flShardSize = shardsize; + __g_C_TEShatterSurface.m_nSurfaceType = surfacetype; + __g_C_TEShatterSurface.m_uchFrontColor[0] = front_r; + __g_C_TEShatterSurface.m_uchFrontColor[1] = front_g; + __g_C_TEShatterSurface.m_uchFrontColor[2] = front_b; + __g_C_TEShatterSurface.m_uchBackColor[0] = back_r; + __g_C_TEShatterSurface.m_uchBackColor[1] = back_g; + __g_C_TEShatterSurface.m_uchBackColor[2] = back_b; + + __g_C_TEShatterSurface.PostDataUpdate( DATA_UPDATE_CREATED ); +} + +void TE_ShatterSurface( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecForce, vecForcePos; + QAngle angles; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + angles.x = pKeyValues->GetFloat( "anglesx" ); + angles.y = pKeyValues->GetFloat( "anglesy" ); + angles.z = pKeyValues->GetFloat( "anglesz" ); + vecForce.x = pKeyValues->GetFloat( "forcex" ); + vecForce.y = pKeyValues->GetFloat( "forcey" ); + vecForce.z = pKeyValues->GetFloat( "forcez" ); + vecForcePos.x = pKeyValues->GetFloat( "forceposx" ); + vecForcePos.y = pKeyValues->GetFloat( "forceposy" ); + vecForcePos.z = pKeyValues->GetFloat( "forceposz" ); + Color front = pKeyValues->GetColor( "frontcolor" ); + Color back = pKeyValues->GetColor( "backcolor" ); + float flWidth = pKeyValues->GetFloat( "width" ); + float flHeight = pKeyValues->GetFloat( "height" ); + float flSize = pKeyValues->GetFloat( "size" ); + ShatterSurface_t nSurfaceType = (ShatterSurface_t)pKeyValues->GetInt( "surfacetype" ); + TE_ShatterSurface( filter, 0.0f, &vecOrigin, &angles, &vecForce, &vecForcePos, + flWidth, flHeight, flSize, nSurfaceType, front.r(), front.g(), front.b(), + back.r(), back.g(), back.b() ); +} diff --git a/game/client/c_te_glowsprite.cpp b/game/client/c_te_glowsprite.cpp new file mode 100644 index 00000000..a294e303 --- /dev/null +++ b/game/client/c_te_glowsprite.cpp @@ -0,0 +1,149 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tempent.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Glow Sprite TE +//----------------------------------------------------------------------------- +class C_TEGlowSprite : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEGlowSprite, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEGlowSprite( void ); + virtual ~C_TEGlowSprite( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + int m_nModelIndex; + float m_fScale; + float m_fLife; + int m_nBrightness; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEGlowSprite, DT_TEGlowSprite, CTEGlowSprite) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropFloat( RECVINFO(m_fScale )), + RecvPropFloat( RECVINFO(m_fLife )), + RecvPropInt( RECVINFO(m_nBrightness)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEGlowSprite::C_TEGlowSprite( void ) +{ + m_vecOrigin.Init(); + m_nModelIndex = 0; + m_fScale = 0; + m_fLife = 0; + m_nBrightness = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEGlowSprite::~C_TEGlowSprite( void ) +{ +} + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordGlowSprite( const Vector &start, int nModelIndex, + float flDuration, float flSize, int nBrightness ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + const model_t* pModel = (nModelIndex != 0) ? modelinfo->GetModel( nModelIndex ) : NULL; + const char *pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_GLOW_SPRITE ); + msg->SetString( "name", "TE_GlowSprite" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetString( "model", pModelName ); + msg->SetFloat( "duration", flDuration ); + msg->SetFloat( "size", flSize ); + msg->SetInt( "brightness", nBrightness ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEGlowSprite::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEGlowSprite::PostDataUpdate" ); + + float a = ( 1.0 / 255.0 ) * m_nBrightness; + C_LocalTempEntity *ent = tempents->TempSprite( m_vecOrigin, vec3_origin, m_fScale, m_nModelIndex, kRenderTransAdd, 0, a, m_fLife, FTENT_SPRANIMATE | FTENT_SPRANIMATELOOP ); + if ( ent ) + { + ent->bounceFactor = 0.2; + } + RecordGlowSprite( m_vecOrigin, m_nModelIndex, m_fLife, m_fScale, m_nBrightness ); +} + +void TE_GlowSprite( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float life, float size, int brightness ) +{ + float a = ( 1.0 / 255.0 ) * brightness; + C_LocalTempEntity *ent = tempents->TempSprite( *pos, vec3_origin, size, modelindex, kRenderTransAdd, 0, a, life, FTENT_SPRANIMATE | FTENT_SPRANIMATELOOP ); + if ( ent ) + { + ent->bounceFactor = 0.2; + } + RecordGlowSprite( *pos, modelindex, life, size, brightness ); +} + +void TE_GlowSprite( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + const char *pModelName = pKeyValues->GetString( "model" ); + int nModelIndex = pModelName[0] ? modelinfo->GetModelIndex( pModelName ) : 0; + float flDuration = pKeyValues->GetFloat( "duration" ); + float flSize = pKeyValues->GetFloat( "size" ); + int nBrightness = pKeyValues->GetFloat( "brightness" ); + + TE_GlowSprite( filter, delay, &vecOrigin, nModelIndex, flDuration, flSize, nBrightness ); +} + diff --git a/game/client/c_te_impact.cpp b/game/client/c_te_impact.cpp new file mode 100644 index 00000000..d76a0705 --- /dev/null +++ b/game/client/c_te_impact.cpp @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IPhysicsSurfaceProps *physprops; + +class C_TEImpact : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEImpact, C_BaseTempEntity ); + + DECLARE_CLIENTCLASS(); + + C_TEImpact( void ); + virtual ~C_TEImpact( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + virtual void Precache( void ); + + virtual void PlayImpactSound( trace_t &tr ); + virtual void PerformCustomEffects( trace_t &tr, Vector &shotDir ); +public: + Vector m_vecOrigin; + Vector m_vecNormal; + int m_iType; + byte m_ucFlags; +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : +//----------------------------------------------------------------------------- +C_TEImpact::C_TEImpact( void ) +{ + m_vecOrigin.Init(); + m_vecNormal.Init(); + + m_iType = -1; + m_ucFlags = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : +//----------------------------------------------------------------------------- +C_TEImpact::~C_TEImpact( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEImpact::Precache( void ) +{ + //TODO: Precache all materials/sounds used by impacts here +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : unused - +//----------------------------------------------------------------------------- +void C_TEImpact::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEImpact::PostDataUpdate" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEImpact::PlayImpactSound( trace_t &tr ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Perform custom effects based on the Decal index +//----------------------------------------------------------------------------- +void C_TEImpact::PerformCustomEffects( trace_t &tr, Vector &shotDir ) +{ +} + +//Receive data table +IMPLEMENT_CLIENTCLASS_EVENT_DT( C_TEImpact, DT_TEImpact, CTEImpact) + RecvPropVector( RECVINFO( m_vecOrigin ) ), + RecvPropVector( RECVINFO( m_vecNormal ) ), + RecvPropInt( RECVINFO( m_iType ) ), + RecvPropInt( RECVINFO( m_ucFlags ) ), +END_RECV_TABLE() diff --git a/game/client/c_te_killplayerattachments.cpp b/game/client/c_te_killplayerattachments.cpp new file mode 100644 index 00000000..61bfb93a --- /dev/null +++ b/game/client/c_te_killplayerattachments.cpp @@ -0,0 +1,69 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Kills Player Attachments +//----------------------------------------------------------------------------- +class C_TEKillPlayerAttachments : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEKillPlayerAttachments, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEKillPlayerAttachments( void ); + virtual ~C_TEKillPlayerAttachments( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + int m_nPlayer; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEKillPlayerAttachments::C_TEKillPlayerAttachments( void ) +{ + m_nPlayer = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEKillPlayerAttachments::~C_TEKillPlayerAttachments( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEKillPlayerAttachments::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEKillPlayerAttachments::PostDataUpdate" ); + + tempents->KillAttachedTents( m_nPlayer ); +} + +void TE_KillPlayerAttachments( IRecipientFilter& filter, float delay, + int player ) +{ + tempents->KillAttachedTents( player ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEKillPlayerAttachments, DT_TEKillPlayerAttachments, CTEKillPlayerAttachments) + RecvPropInt( RECVINFO(m_nPlayer)), +END_RECV_TABLE() diff --git a/game/client/c_te_largefunnel.cpp b/game/client/c_te_largefunnel.cpp new file mode 100644 index 00000000..6d2e4c93 --- /dev/null +++ b/game/client/c_te_largefunnel.cpp @@ -0,0 +1,163 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_particlesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Large Funnel TE +//----------------------------------------------------------------------------- +class C_TELargeFunnel : public C_TEParticleSystem +{ +public: + DECLARE_CLASS( C_TELargeFunnel, C_TEParticleSystem ); + DECLARE_CLIENTCLASS(); + + C_TELargeFunnel( void ); + virtual ~C_TELargeFunnel( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + +public: + void CreateFunnel( void ); + + int m_nModelIndex; + int m_nReversed; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TELargeFunnel::C_TELargeFunnel( void ) +{ + m_nModelIndex = 0; + m_nReversed = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TELargeFunnel::~C_TELargeFunnel( void ) +{ +} + + +void C_TELargeFunnel::CreateFunnel( void ) +{ + CSmartPtr pSimple = CSimpleEmitter::Create( "TELargeFunnel" ); + pSimple->SetSortOrigin( m_vecOrigin ); + + int i, j; + SimpleParticle *pParticle; + + Vector vecDir; + Vector vecDest; + + float ratio = 0.25; + float invratio = 1 / ratio; + + PMaterialHandle hMaterial = pSimple->GetPMaterial( "sprites/flare6" ); + + for ( i = -256 ; i <= 256 ; i += 24 ) //24 from 32.. little more dense + { + for ( j = -256 ; j <= 256 ; j += 24 ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, m_vecOrigin ); + if( pParticle ) + { + if ( m_nReversed ) + { + pParticle->m_Pos = m_vecOrigin; + + vecDir[0] = i; + vecDir[1] = j; + vecDir[2] = random->RandomFloat(100, 800); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + } + else + { + pParticle->m_Pos[0] = m_vecOrigin[0] + i; + pParticle->m_Pos[1] = m_vecOrigin[1] + j; + pParticle->m_Pos[2] = m_vecOrigin[2] + random->RandomFloat(100, 800); + + // send particle heading to org at a random speed + vecDir = m_vecOrigin - pParticle->m_Pos; + + pParticle->m_uchStartAlpha = 0; + pParticle->m_uchEndAlpha = 255; + } + + vecDir *= ratio; + + pParticle->m_vecVelocity = vecDir; + + pParticle->m_flLifetime = 0; + pParticle->m_flDieTime = invratio; + + if( random->RandomInt( 0, 10 ) < 5 ) + { + // small green particle + pParticle->m_uchColor[0] = 0; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 0; + + pParticle->m_uchStartSize = 4.0; + } + else + { + // large white particle + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartSize = 15.0; + } + + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = i; // pseudorandom + pParticle->m_flRollDelta = 0; + pParticle->m_iFlags = 0; + } + + } + } + + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TELargeFunnel::PostDataUpdate( DataUpdateType_t updateType ) +{ + CreateFunnel(); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TELargeFunnel, DT_TELargeFunnel, CTELargeFunnel) + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropInt( RECVINFO(m_nReversed)), +END_RECV_TABLE() + +void TE_LargeFunnel( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, int reversed ) +{ + // Major hack to simulate receiving network message + __g_C_TELargeFunnel.m_vecOrigin = *pos; + __g_C_TELargeFunnel.m_nModelIndex = modelindex; + __g_C_TELargeFunnel.m_nReversed = reversed; + + __g_C_TELargeFunnel.PostDataUpdate( DATA_UPDATE_CREATED ); +} + + diff --git a/game/client/c_te_legacytempents.cpp b/game/client/c_te_legacytempents.cpp new file mode 100644 index 00000000..6ec9423f --- /dev/null +++ b/game/client/c_te_legacytempents.cpp @@ -0,0 +1,3395 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "model_types.h" +#include "view_shared.h" +#include "iviewrender.h" +#include "tempentity.h" +#include "dlight.h" +#include "tempent.h" +#include "c_te_legacytempents.h" +#include "clientsideeffects.h" +#include "cl_animevent.h" +#include "iefx.h" +#include "engine/IEngineSound.h" +#include "env_wind_shared.h" +#include "ClientEffectPrecacheSystem.h" +#include "fx_sparks.h" +#include "fx.h" +#include "movevars_shared.h" +#include "engine/ivmodelinfo.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "view.h" +#include "tier0/vprof.h" +#include "particles_localspace.h" +#include "physpropclientside.h" +#include "tier0/ICommandLine.h" +#include "datacache/imdlcache.h" +#include "engine/IVDebugOverlay.h" +#include "effect_dispatch_data.h" +#include "c_te_effect_dispatch.h" + +// NOTE: Always include this last! +#include "tier0/memdbgon.h" + +extern ConVar muzzleflash_light; + +#define TENT_WIND_ACCEL 50 + +//Precache the effects +#ifndef TF_CLIENT_DLL +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectMuzzleFlash ) + CLIENTEFFECT_MATERIAL( "effects/combinemuzzle1" ) + CLIENTEFFECT_MATERIAL( "effects/combinemuzzle2" ) + CLIENTEFFECT_MATERIAL( "effects/combinemuzzle1_noz" ) + CLIENTEFFECT_MATERIAL( "effects/combinemuzzle2_noz" ) + + CLIENTEFFECT_MATERIAL( "effects/muzzleflash1" ) + CLIENTEFFECT_MATERIAL( "effects/muzzleflash2" ) + CLIENTEFFECT_MATERIAL( "effects/muzzleflash3" ) + CLIENTEFFECT_MATERIAL( "effects/muzzleflash4" ) + CLIENTEFFECT_MATERIAL( "effects/muzzleflash1_noz" ) + CLIENTEFFECT_MATERIAL( "effects/muzzleflash2_noz" ) + CLIENTEFFECT_MATERIAL( "effects/muzzleflash3_noz" ) + CLIENTEFFECT_MATERIAL( "effects/muzzleflash4_noz" ) + + CLIENTEFFECT_MATERIAL( "effects/strider_muzzle" ) +CLIENTEFFECT_REGISTER_END() +#endif + +//Whether or not to eject brass from weapons +ConVar cl_ejectbrass( "cl_ejectbrass", "1" ); + +ConVar func_break_max_pieces( "func_break_max_pieces", "15", FCVAR_ARCHIVE | FCVAR_REPLICATED ); + +ConVar cl_fasttempentcollision( "cl_fasttempentcollision", "5" ); + +#if !defined( HL1_CLIENT_DLL ) // HL1 implements a derivative of CTempEnts +// Temp entity interface +static CTempEnts g_TempEnts; +// Expose to rest of the client .dll +ITempEnts *tempents = ( ITempEnts * )&g_TempEnts; +#endif + + + + +C_LocalTempEntity::C_LocalTempEntity() +{ +#ifdef _DEBUG + tentOffset.Init(); + m_vecTempEntVelocity.Init(); + m_vecTempEntAngVelocity.Init(); + m_vecNormal.Init(); +#endif + m_vecTempEntAcceleration.Init(); + m_pfnDrawHelper = 0; + m_pszImpactEffect = NULL; +} + + +#if defined( CSTRIKE_DLL ) || defined (SDK_DLL ) + +#define TE_RIFLE_SHELL 1024 +#define TE_PISTOL_SHELL 2048 +#define TE_SHOTGUN_SHELL 4096 + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Prepare a temp entity for creation +// Input : time - +// *model - +//----------------------------------------------------------------------------- +void C_LocalTempEntity::Prepare( model_t *pmodel, float time ) +{ + Interp_SetupMappings( GetVarMapping() ); + + index = -1; + Clear(); + + // Use these to set per-frame and termination conditions / actions + flags = FTENT_NONE; + die = time + 0.75; + SetModelPointer( pmodel ); + SetRenderMode( kRenderNormal ); + m_nRenderFX = kRenderFxNone; + m_nBody = 0; + m_nSkin = 0; + fadeSpeed = 0.5; + hitSound = 0; + clientIndex = -1; + bounceFactor = 1; + m_nFlickerFrame = 0; + m_bParticleCollision = false; +} + +//----------------------------------------------------------------------------- +// Sets the velocity +//----------------------------------------------------------------------------- +void C_LocalTempEntity::SetVelocity( const Vector &vecVelocity ) +{ + m_vecTempEntVelocity = vecVelocity; +} + +//----------------------------------------------------------------------------- +// Sets the velocity +//----------------------------------------------------------------------------- +void C_LocalTempEntity::SetAcceleration( const Vector &vecVelocity ) +{ + m_vecTempEntAcceleration = vecVelocity; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int C_LocalTempEntity::DrawStudioModel( int flags ) +{ + VPROF_BUDGET( "C_LocalTempEntity::DrawStudioModel", VPROF_BUDGETGROUP_MODEL_RENDERING ); + int drawn = 0; + + if ( !GetModel() || modelinfo->GetModelType( GetModel() ) != mod_studio ) + return drawn; + + // Make sure m_pstudiohdr is valid for drawing + MDLCACHE_CRITICAL_SECTION(); + if ( !GetModelPtr() ) + return drawn; + + if ( m_pfnDrawHelper ) + { + drawn = ( *m_pfnDrawHelper )( this, flags ); + } + else + { + drawn = modelrender->DrawModel( + flags, + this, + MODEL_INSTANCE_INVALID, + index, + GetModel(), + GetAbsOrigin(), + GetAbsAngles(), + m_nSkin, + m_nBody, + m_nHitboxSet ); + } + return drawn; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flags - +//----------------------------------------------------------------------------- +int C_LocalTempEntity::DrawModel( int flags ) +{ + int drawn = 0; + + if ( !GetModel() ) + { + return drawn; + } + + if ( this->flags & FTENT_BEOCCLUDED ) + { + // Check normal + Vector vecDelta = (GetAbsOrigin() - MainViewOrigin()); + VectorNormalize( vecDelta ); + float flDot = DotProduct( m_vecNormal, vecDelta ); + if ( flDot > 0 ) + { + float flAlpha = RemapVal( min(flDot,0.3), 0, 0.3, 0, 1 ); + flAlpha = max( 1.0, tempent_renderamt - (tempent_renderamt * flAlpha) ); + SetRenderColorA( flAlpha ); + } + } + + switch ( modelinfo->GetModelType( GetModel() ) ) + { + case mod_sprite: + drawn = DrawSprite( + this, + GetModel(), + GetAbsOrigin(), + GetAbsAngles(), + m_flFrame, // sprite frame to render + m_nBody > 0 ? cl_entitylist->GetBaseEntity( m_nBody ) : NULL, // attach to + m_nSkin, // attachment point + GetRenderMode(), // rendermode + m_nRenderFX, // renderfx + m_clrRender->a, // alpha + m_clrRender->r, + m_clrRender->g, + m_clrRender->b, + m_flSpriteScale // sprite scale + ); + break; + case mod_studio: + drawn = DrawStudioModel( flags ); + break; + default: + break; + } + + return drawn; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_LocalTempEntity::IsActive( void ) +{ + bool active = true; + + float life = die - gpGlobals->curtime; + + if ( life < 0 ) + { + if ( flags & FTENT_FADEOUT ) + { + int alpha; + if (GetRenderMode() == kRenderNormal) + { + SetRenderMode( kRenderTransTexture ); + } + + alpha = tempent_renderamt * ( 1 + life * fadeSpeed ); + + if ( alpha <= 0 ) + { + active = false; + alpha = 0; + } + + SetRenderColorA( alpha ); + } + else + { + active = false; + } + } + + // Never die tempents only die when their die is cleared + if ( flags & FTENT_NEVERDIE ) + { + active = (die != 0); + } + + return active; +} + +bool C_LocalTempEntity::Frame( float frametime, int framenumber ) +{ + float fastFreq = gpGlobals->curtime * 5.5; + float gravity = -frametime * sv_gravity.GetFloat(); + float gravitySlow = gravity * 0.5; + float traceFraction = 1; + + Assert( !GetMoveParent() ); + + m_vecPrevLocalOrigin = GetLocalOrigin(); + + m_vecTempEntVelocity = m_vecTempEntVelocity + ( m_vecTempEntAcceleration * frametime ); + + if ( flags & FTENT_PLYRATTACHMENT ) + { + if ( IClientEntity *pClient = cl_entitylist->GetClientEntity( clientIndex ) ) + { + SetLocalOrigin( pClient->GetAbsOrigin() + tentOffset ); + } + } + else if ( flags & FTENT_SINEWAVE ) + { + x += m_vecTempEntVelocity[0] * frametime; + y += m_vecTempEntVelocity[1] * frametime; + + SetLocalOrigin( Vector( + x + sin( m_vecTempEntVelocity[2] + gpGlobals->curtime /* * anim.prevframe */ ) * (10*m_flSpriteScale), + y + sin( m_vecTempEntVelocity[2] + fastFreq + 0.7 ) * (8*m_flSpriteScale), + GetLocalOriginDim( Z_INDEX ) + m_vecTempEntVelocity[2] * frametime ) ); + } + else if ( flags & FTENT_SPIRAL ) + { + float s, c; + SinCos( m_vecTempEntVelocity[2] + fastFreq, &s, &c ); + + SetLocalOrigin( GetLocalOrigin() + Vector( + m_vecTempEntVelocity[0] * frametime + 8 * sin( gpGlobals->curtime * 20 ), + m_vecTempEntVelocity[1] * frametime + 4 * sin( gpGlobals->curtime * 30 ), + m_vecTempEntVelocity[2] * frametime ) ); + } + else + { + SetLocalOrigin( GetLocalOrigin() + m_vecTempEntVelocity * frametime ); + } + + if ( flags & FTENT_SPRANIMATE ) + { + m_flFrame += frametime * m_flFrameRate; + if ( m_flFrame >= m_flFrameMax ) + { + m_flFrame = m_flFrame - (int)(m_flFrame); + + if ( !(flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + die = 0.0f; + return false; + } + } + } + else if ( flags & FTENT_SPRCYCLE ) + { + m_flFrame += frametime * 10; + if ( m_flFrame >= m_flFrameMax ) + { + m_flFrame = m_flFrame - (int)(m_flFrame); + } + } + + if ( flags & FTENT_SMOKEGROWANDFADE ) + { + m_flSpriteScale += frametime * 0.5f; + //m_clrRender.a -= frametime * 1500; + } + + if ( flags & FTENT_ROTATE ) + { + SetLocalAngles( GetLocalAngles() + m_vecTempEntAngVelocity * frametime ); + } + else if ( flags & FTENT_ALIGNTOMOTION ) + { + if ( m_vecTempEntVelocity.Length() > 0.0f ) + { + QAngle angles; + VectorAngles( m_vecTempEntVelocity, angles ); + SetAbsAngles( angles ); + } + } + + if ( flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + Vector traceNormal; + traceNormal.Init(); + bool bShouldCollide = true; + + trace_t trace; + + if ( flags & FTENT_COLLIDEALL ) + { + Vector vPrevOrigin = m_vecPrevLocalOrigin; + + if ( cl_fasttempentcollision.GetInt() > 0 && flags & FTENT_USEFASTCOLLISIONS ) + { + if ( m_iLastCollisionFrame + cl_fasttempentcollision.GetInt() > gpGlobals->framecount ) + { + bShouldCollide = false; + } + else + { + if ( m_vLastCollisionOrigin != vec3_origin ) + { + vPrevOrigin = m_vLastCollisionOrigin; + } + + m_iLastCollisionFrame = gpGlobals->framecount; + bShouldCollide = true; + } + } + + if ( bShouldCollide == true ) + { + // If the FTENT_COLLISIONGROUP flag is set, use the entity's collision group + int collisionGroup = COLLISION_GROUP_NONE; + if ( flags & FTENT_COLLISIONGROUP ) + { + collisionGroup = GetCollisionGroup(); + } + + UTIL_TraceLine( vPrevOrigin, GetLocalOrigin(), MASK_SOLID, GetOwnerEntity(), collisionGroup, &trace ); + + // Make sure it didn't bump into itself... (?!?) + if ( + (trace.fraction != 1) && + ( (trace.DidHitWorld()) || + (trace.m_pEnt != ClientEntityList().GetEnt(clientIndex)) ) + ) + { + traceFraction = trace.fraction; + VectorCopy( trace.plane.normal, traceNormal ); + } + + m_vLastCollisionOrigin = trace.endpos; + } + } + else if ( flags & FTENT_COLLIDEWORLD ) + { + CTraceFilterWorldOnly traceFilter; + UTIL_TraceLine( m_vecPrevLocalOrigin, GetLocalOrigin(), MASK_SOLID, &traceFilter, &trace ); + if ( trace.fraction != 1 ) + { + traceFraction = trace.fraction; + VectorCopy( trace.plane.normal, traceNormal ); + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + SetLocalOrigin( trace.endpos ); + + // Damp velocity + damp = bounceFactor; + if ( flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( m_vecTempEntVelocity[2] <= 0 && m_vecTempEntVelocity[2] >= gravity*3 ) + { + damp = 0; // Stop + flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + SetLocalAnglesDim( X_INDEX, 0 ); + SetLocalAnglesDim( Z_INDEX, 0 ); + } + } + } + + if ( flags & (FTENT_CHANGERENDERONCOLLIDE) ) + { + m_RenderGroup = RENDER_GROUP_OTHER; + flags &= ~FTENT_CHANGERENDERONCOLLIDE; + } + + if (hitSound) + { + tempents->PlaySound(this, damp); + } + + if ( m_pszImpactEffect ) + { + CEffectData data; + //data.m_vOrigin = newOrigin; + data.m_vOrigin = trace.endpos; + data.m_vStart = trace.startpos; + data.m_nSurfaceProp = trace.surface.surfaceProps; + data.m_nHitBox = trace.hitbox; + + data.m_nDamageType = TEAM_UNASSIGNED; + + IClientNetworkable *pClient = cl_entitylist->GetClientEntity( clientIndex ); + + if ( pClient ) + { + C_BasePlayer *pPlayer = dynamic_cast(pClient); + if( pPlayer ) + { + data.m_nDamageType = pPlayer->GetTeamNumber(); + } + } + + if ( trace.m_pEnt ) + { + data.m_hEntity = ClientEntityList().EntIndexToHandle( trace.m_pEnt->entindex() ); + } + DispatchEffect( m_pszImpactEffect, data ); + } + + // Check for a collision and stop the particle system. + if ( flags & FTENT_CLIENTSIDEPARTICLES ) + { + // Stop the emission of particles on collision - removed from the ClientEntityList on removal from the tempent pool. + ParticleProp()->StopEmission(); + m_bParticleCollision = true; + } + + if (flags & FTENT_COLLIDEKILL) + { + // die on impact + flags &= ~FTENT_FADEOUT; + die = gpGlobals->curtime; + } + else if ( flags & FTENT_ATTACHTOTARGET) + { + // If we've hit the world, just stop moving + if ( trace.DidHitWorld() && !( trace.surface.flags & SURF_SKY ) ) + { + m_vecTempEntVelocity = vec3_origin; + m_vecTempEntAcceleration = vec3_origin; + + // Remove movement flags so we don't keep tracing + flags &= ~(FTENT_COLLIDEALL | FTENT_COLLIDEWORLD); + } + else + { + // Couldn't attach to this entity. Die. + flags &= ~FTENT_FADEOUT; + die = gpGlobals->curtime; + } + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = ((Vector)m_vecTempEntVelocity).Dot(traceNormal); + VectorMA( m_vecTempEntVelocity, -proj*2, traceNormal, m_vecTempEntVelocity ); + // Reflect rotation (fake) + SetLocalAnglesDim( Y_INDEX, -GetLocalAnglesDim( Y_INDEX ) ); + } + + if ( damp != 1 ) + { + VectorScale( m_vecTempEntVelocity, damp, m_vecTempEntVelocity ); + SetLocalAngles( GetLocalAngles() * 0.9 ); + } + } + } + } + + + if ( (flags & FTENT_FLICKER) && framenumber == m_nFlickerFrame ) + { + dlight_t *dl = effects->CL_AllocDlight (LIGHT_INDEX_TE_DYNAMIC); + VectorCopy (GetLocalOrigin(), dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = gpGlobals->curtime + 0.01; + } + + if ( flags & FTENT_SMOKETRAIL ) + { + Assert( !"FIXME: Rework smoketrail to be client side\n" ); + } + + // add gravity if we didn't collide in this frame + if ( traceFraction == 1 ) + { + if ( flags & FTENT_GRAVITY ) + m_vecTempEntVelocity[2] += gravity; + else if ( flags & FTENT_SLOWGRAVITY ) + m_vecTempEntVelocity[2] += gravitySlow; + } + + if ( flags & FTENT_WINDBLOWN ) + { + Vector vecWind; + GetWindspeedAtTime( gpGlobals->curtime, vecWind ); + + for ( int i = 0 ; i < 2 ; i++ ) + { + if ( m_vecTempEntVelocity[i] < vecWind[i] ) + { + m_vecTempEntVelocity[i] += ( frametime * TENT_WIND_ACCEL ); + + // clamp + if ( m_vecTempEntVelocity[i] > vecWind[i] ) + m_vecTempEntVelocity[i] = vecWind[i]; + } + else if (m_vecTempEntVelocity[i] > vecWind[i] ) + { + m_vecTempEntVelocity[i] -= ( frametime * TENT_WIND_ACCEL ); + + // clamp. + if ( m_vecTempEntVelocity[i] < vecWind[i] ) + m_vecTempEntVelocity[i] = vecWind[i]; + } + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Attach a particle effect to a temp entity. +//----------------------------------------------------------------------------- +void C_LocalTempEntity::AddParticleEffect( const char *pszParticleEffect ) +{ + // Do we have a valid particle effect. + if ( !pszParticleEffect || ( pszParticleEffect[0] == '\0' ) ) + return; + + // Check to see that we don't already have a particle effect. + if ( ( flags & FTENT_CLIENTSIDEPARTICLES ) != 0 ) + return; + + // Add the entity to the ClientEntityList and create the particle system. + ClientEntityList().AddNonNetworkableEntity( this ); + ParticleProp()->Create( pszParticleEffect, PATTACH_ABSORIGIN_FOLLOW ); + + // Set the particle flag on the temp entity and save the name of the particle effect. + flags |= FTENT_CLIENTSIDEPARTICLES; + SetParticleEffect( pszParticleEffect ); +} + +//----------------------------------------------------------------------------- +// Purpose: This helper keeps track of batches of "breakmodels" so that they can all share the lighting origin +// of the first of the group (because the server sends down 15 chunks at a time, and rebuilding 15 light cache +// entries for a map with a lot of worldlights is really slow). +//----------------------------------------------------------------------------- +class CBreakableHelper +{ +public: + void Insert( C_LocalTempEntity *entity, bool isSlave ); + void Remove( C_LocalTempEntity *entity ); + + void Clear(); + + const Vector *GetLightingOrigin( C_LocalTempEntity *entity ); + +private: + + // A context is the first master until the next one, which starts a new context + struct BreakableList_t + { + unsigned int context; + C_LocalTempEntity *entity; + }; + + CUtlLinkedList< BreakableList_t, unsigned short > m_Breakables; + unsigned int m_nCurrentContext; +}; + +//----------------------------------------------------------------------------- +// Purpose: Adds brekable to list, starts new context if needed +// Input : *entity - +// isSlave - +//----------------------------------------------------------------------------- +void CBreakableHelper::Insert( C_LocalTempEntity *entity, bool isSlave ) +{ + // A master signifies the start of a new run of broken objects + if ( !isSlave ) + { + ++m_nCurrentContext; + } + + BreakableList_t entry; + entry.context = m_nCurrentContext; + entry.entity = entity; + + m_Breakables.AddToTail( entry ); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes all instances of entity in the list +// Input : *entity - +//----------------------------------------------------------------------------- +void CBreakableHelper::Remove( C_LocalTempEntity *entity ) +{ + for ( unsigned short i = m_Breakables.Head(); i != m_Breakables.InvalidIndex() ; ) + { + unsigned short n = m_Breakables.Next( i ); + + if ( m_Breakables[ i ].entity == entity ) + { + m_Breakables.Remove( i ); + } + + i = n; + } +} + +//----------------------------------------------------------------------------- +// Purpose: For a given breakable, find the "first" or head object and use it's current +// origin as the lighting origin for the entire group of objects +// Input : *entity - +// Output : const Vector +//----------------------------------------------------------------------------- +const Vector *CBreakableHelper::GetLightingOrigin( C_LocalTempEntity *entity ) +{ + unsigned int nCurContext = 0; + C_LocalTempEntity *head = NULL; + FOR_EACH_LL( m_Breakables, i ) + { + BreakableList_t& e = m_Breakables[ i ]; + + if ( e.context != nCurContext ) + { + nCurContext = e.context; + head = e.entity; + } + + if ( e.entity == entity ) + { + Assert( head ); + return head ? &head->GetAbsOrigin() : NULL; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Wipe breakable helper list +// Input : - +//----------------------------------------------------------------------------- +void CBreakableHelper::Clear() +{ + m_Breakables.RemoveAll(); + m_nCurrentContext = 0; +} + +static CBreakableHelper g_BreakableHelper; + +//----------------------------------------------------------------------------- +// Purpose: See if it's in the breakable helper list and, if so, remove +// Input : - +//----------------------------------------------------------------------------- +void C_LocalTempEntity::OnRemoveTempEntity() +{ + g_BreakableHelper.Remove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTempEnts::CTempEnts( void ) : + m_TempEntsPool( ( MAX_TEMP_ENTITIES / 20 ), CMemoryPool::GROW_SLOW ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTempEnts::~CTempEnts( void ) +{ + m_TempEntsPool.Clear(); + m_TempEnts.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a fizz effect +// Input : *pent - +// modelIndex - +// density - +//----------------------------------------------------------------------------- +void CTempEnts::FizzEffect( C_BaseEntity *pent, int modelIndex, int density, int current ) +{ + C_LocalTempEntity *pTemp; + const model_t *model; + int i, width, depth, count, frameCount; + float maxHeight, speed, xspeed, yspeed; + Vector origin; + Vector mins, maxs; + + if ( !pent->GetModel() || !modelIndex ) + return; + + model = modelinfo->GetModel( modelIndex ); + if ( !model ) + return; + + count = density + 1; + density = count * 3 + 6; + + modelinfo->GetModelBounds( pent->GetModel(), mins, maxs ); + + maxHeight = maxs[2] - mins[2]; + width = maxs[0] - mins[0]; + depth = maxs[1] - mins[1]; + speed = current; + + SinCos( pent->GetLocalAngles()[1]*M_PI/180, &yspeed, &xspeed ); + xspeed *= speed; + yspeed *= speed; + frameCount = modelinfo->GetModelFrameCount( model ); + + for (i=0 ; iRandomInt(0,width-1); + origin[1] = mins[1] + random->RandomInt(0,depth-1); + origin[2] = mins[2]; + pTemp = TempEntAlloc( origin, (model_t *)model ); + if (!pTemp) + return; + + pTemp->flags |= FTENT_SINEWAVE; + + pTemp->x = origin[0]; + pTemp->y = origin[1]; + + float zspeed = random->RandomInt(80,140); + pTemp->SetVelocity( Vector(xspeed, yspeed, zspeed) ); + pTemp->die = gpGlobals->curtime + (maxHeight / zspeed) - 0.1; + pTemp->m_flFrame = random->RandomInt(0,frameCount-1); + // Set sprite scale + pTemp->m_flSpriteScale = 1.0 / random->RandomFloat(2,5); + pTemp->SetRenderMode( kRenderTransAlpha ); + pTemp->SetRenderColorA( 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create bubbles +// Input : *mins - +// *maxs - +// height - +// modelIndex - +// count - +// speed - +//----------------------------------------------------------------------------- +void CTempEnts::Bubbles( const Vector &mins, const Vector &maxs, float height, int modelIndex, int count, float speed ) +{ + C_LocalTempEntity *pTemp; + const model_t *model; + int i, frameCount; + float sine, cosine; + Vector origin; + + if ( !modelIndex ) + return; + + model = modelinfo->GetModel( modelIndex ); + if ( !model ) + return; + + frameCount = modelinfo->GetModelFrameCount( model ); + + for (i=0 ; iRandomInt( mins[0], maxs[0] ); + origin[1] = random->RandomInt( mins[1], maxs[1] ); + origin[2] = random->RandomInt( mins[2], maxs[2] ); + pTemp = TempEntAlloc( origin, ( model_t * )model ); + if (!pTemp) + return; + + pTemp->flags |= FTENT_SINEWAVE; + + pTemp->x = origin[0]; + pTemp->y = origin[1]; + SinCos( random->RandomInt( -M_PI, M_PI ), &sine, &cosine ); + + float zspeed = random->RandomInt(80,140); + pTemp->SetVelocity( Vector(speed * cosine, speed * sine, zspeed) ); + pTemp->die = gpGlobals->curtime + ((height - (origin[2] - mins[2])) / zspeed) - 0.1; + pTemp->m_flFrame = random->RandomInt( 0, frameCount-1 ); + + // Set sprite scale + pTemp->m_flSpriteScale = 1.0 / random->RandomFloat(4,16); + pTemp->SetRenderMode( kRenderTransAlpha ); + + pTemp->SetRenderColor( 255, 255, 255, 192 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create bubble trail +// Input : *start - +// *end - +// height - +// modelIndex - +// count - +// speed - +//----------------------------------------------------------------------------- +void CTempEnts::BubbleTrail( const Vector &start, const Vector &end, float flWaterZ, int modelIndex, int count, float speed ) +{ + C_LocalTempEntity *pTemp; + const model_t *model; + int i, frameCount; + float dist, angle; + Vector origin; + + if ( !modelIndex ) + return; + + model = modelinfo->GetModel( modelIndex ); + if ( !model ) + return; + + frameCount = modelinfo->GetModelFrameCount( model ); + + for (i=0 ; iRandomFloat( 0, 1.0 ); + VectorLerp( start, end, dist, origin ); + pTemp = TempEntAlloc( origin, ( model_t * )model ); + if (!pTemp) + return; + + pTemp->flags |= FTENT_SINEWAVE; + + pTemp->x = origin[0]; + pTemp->y = origin[1]; + angle = random->RandomInt( -M_PI, M_PI ); + + float zspeed = random->RandomInt(80,140); + pTemp->SetVelocity( Vector(speed * cos(angle), speed * sin(angle), zspeed) ); + pTemp->die = gpGlobals->curtime + ((flWaterZ - origin[2]) / zspeed) - 0.1; + pTemp->m_flFrame = random->RandomInt(0,frameCount-1); + // Set sprite scale + pTemp->m_flSpriteScale = 1.0 / random->RandomFloat(4,8); + pTemp->SetRenderMode( kRenderTransAlpha ); + + pTemp->SetRenderColor( 255, 255, 255, 192 ); + } +} + +#define SHARD_VOLUME 12.0 // on shard ever n^3 units + +//----------------------------------------------------------------------------- +// Purpose: Only used by BreakModel temp ents for now. Allows them to share a single +// lighting origin amongst a group of objects. If the master object goes away, the next object +// in the group becomes the new lighting origin, etc. +// Input : *entity - +// flags - +// Output : int +//----------------------------------------------------------------------------- +int BreakModelDrawHelper( C_LocalTempEntity *entity, int flags ) +{ + ModelRenderInfo_t sInfo; + sInfo.flags = flags; + sInfo.pRenderable = entity; + sInfo.instance = MODEL_INSTANCE_INVALID; + sInfo.entity_index = entity->index; + sInfo.pModel = entity->GetModel(); + sInfo.origin = entity->GetRenderOrigin(); + sInfo.angles = entity->GetRenderAngles(); + sInfo.skin = entity->m_nSkin; + sInfo.body = entity->m_nBody; + sInfo.hitboxset = entity->m_nHitboxSet; + + // This is the main change, look up a lighting origin from the helper singleton + const Vector *pLightingOrigin = g_BreakableHelper.GetLightingOrigin( entity ); + if ( pLightingOrigin ) + { + sInfo.pLightingOrigin = pLightingOrigin; + } + + int drawn = modelrender->DrawModelEx( sInfo ); + return drawn; +} + +//----------------------------------------------------------------------------- +// Purpose: Create model shattering shards +// Input : *pos - +// *size - +// *dir - +// random - +// life - +// count - +// modelIndex - +// flags - +//----------------------------------------------------------------------------- +void CTempEnts::BreakModel( const Vector &pos, const QAngle &angles, const Vector &size, const Vector &dir, + float randRange, float life, int count, int modelIndex, char flags) +{ + int i, frameCount; + C_LocalTempEntity *pTemp; + const model_t *pModel; + + if (!modelIndex) + return; + + pModel = modelinfo->GetModel( modelIndex ); + if ( !pModel ) + return; + + // See g_BreakableHelper above for notes... + bool isSlave = ( flags & BREAK_SLAVE ) ? true : false; + + frameCount = modelinfo->GetModelFrameCount( pModel ); + + if (count == 0) + { + // assume surface (not volume) + count = (size[0] * size[1] + size[1] * size[2] + size[2] * size[0])/(3 * SHARD_VOLUME * SHARD_VOLUME); + } + + if ( count > func_break_max_pieces.GetInt() ) + { + count = func_break_max_pieces.GetInt(); + } + + matrix3x4_t transform; + AngleMatrix( angles, pos, transform ); + for ( i = 0; i < count; i++ ) + { + Vector vecLocalSpot, vecSpot; + + // fill up the box with stuff + vecLocalSpot[0] = random->RandomFloat(-0.5,0.5) * size[0]; + vecLocalSpot[1] = random->RandomFloat(-0.5,0.5) * size[1]; + vecLocalSpot[2] = random->RandomFloat(-0.5,0.5) * size[2]; + VectorTransform( vecLocalSpot, transform, vecSpot ); + + pTemp = TempEntAlloc(vecSpot, ( model_t * )pModel); + + if (!pTemp) + return; + + // keep track of break_type, so we know how to play sound on collision + pTemp->hitSound = flags; + + if ( modelinfo->GetModelType( pModel ) == mod_sprite ) + { + pTemp->m_flFrame = random->RandomInt(0,frameCount-1); + } + else if ( modelinfo->GetModelType( pModel ) == mod_studio ) + { + pTemp->m_nBody = random->RandomInt(0,frameCount-1); + } + + pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_SLOWGRAVITY; + + if ( random->RandomInt(0,255) < 200 ) + { + pTemp->flags |= FTENT_ROTATE; + pTemp->m_vecTempEntAngVelocity[0] = random->RandomFloat(-256,255); + pTemp->m_vecTempEntAngVelocity[1] = random->RandomFloat(-256,255); + pTemp->m_vecTempEntAngVelocity[2] = random->RandomFloat(-256,255); + } + + if ( (random->RandomInt(0,255) < 100 ) && (flags & BREAK_SMOKE) ) + { + pTemp->flags |= FTENT_SMOKETRAIL; + } + + if ((flags & BREAK_GLASS) || (flags & BREAK_TRANS)) + { + pTemp->SetRenderMode( kRenderTransTexture ); + pTemp->SetRenderColorA( 128 ); + pTemp->tempent_renderamt = 128; + pTemp->bounceFactor = 0.3f; + } + else + { + pTemp->SetRenderMode( kRenderNormal ); + pTemp->tempent_renderamt = 255; // Set this for fadeout + } + + pTemp->SetVelocity( Vector( dir[0] + random->RandomFloat(-randRange,randRange), + dir[1] + random->RandomFloat(-randRange,randRange), + dir[2] + random->RandomFloat( 0,randRange) ) ); + + pTemp->die = gpGlobals->curtime + life + random->RandomFloat(0,1); // Add an extra 0-1 secs of life + + // We use a special rendering function because these objects will want to share their lighting + // origin with the first/master object. Prevents a huge spike in Light Cache building in maps + // with many worldlights. + pTemp->SetDrawHelper( BreakModelDrawHelper ); + g_BreakableHelper.Insert( pTemp, isSlave ); + } +} + +void CTempEnts::PhysicsProp( int modelindex, int skin, const Vector& pos, const QAngle &angles, const Vector& vel, int flags, int effects ) +{ + C_PhysPropClientside *pEntity = C_PhysPropClientside::CreateNew(); + + if ( !pEntity ) + return; + + const model_t *model = modelinfo->GetModel( modelindex ); + + if ( !model ) + { + DevMsg("CTempEnts::PhysicsProp: model index %i not found\n", modelinfo ); + return; + } + + pEntity->SetModelName( modelinfo->GetModelName(model) ); + pEntity->m_nSkin = skin; + pEntity->SetAbsOrigin( pos ); + pEntity->SetAbsAngles( angles ); + pEntity->SetPhysicsMode( PHYSICS_MULTIPLAYER_CLIENTSIDE ); + pEntity->SetEffects( effects ); + + if ( !pEntity->Initialize() ) + { + pEntity->Release(); + return; + } + + IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); + + if( pPhysicsObject ) + { + pPhysicsObject->AddVelocity( &vel, NULL ); + } + else + { + // failed to create a physics object + pEntity->Release(); + return; + } + + if ( flags & 1 ) + { + pEntity->SetHealth( 0 ); + pEntity->Break(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create a clientside projectile +// Input : vecOrigin - +// vecVelocity - +// modelindex - +// lifetime - +// *pOwner - +//----------------------------------------------------------------------------- +C_LocalTempEntity *CTempEnts::ClientProjectile( const Vector& vecOrigin, const Vector& vecVelocity, const Vector& vecAcceleration, int modelIndex, int lifetime, CBaseEntity *pOwner, const char *pszImpactEffect, const char *pszParticleEffect ) +{ + C_LocalTempEntity *pTemp; + const model_t *model; + + if ( !modelIndex ) + return NULL; + + model = modelinfo->GetModel( modelIndex ); + if ( !model ) + { + Warning("ClientProjectile: No model %d!\n", modelIndex); + return NULL; + } + + pTemp = TempEntAlloc( vecOrigin, ( model_t * )model ); + if (!pTemp) + return NULL; + + pTemp->SetVelocity( vecVelocity ); + pTemp->SetAcceleration( vecAcceleration ); + QAngle angles; + VectorAngles( vecVelocity, angles ); + pTemp->SetAbsAngles( angles ); + pTemp->SetAbsOrigin( vecOrigin ); + pTemp->die = gpGlobals->curtime + lifetime; + pTemp->flags = FTENT_COLLIDEALL | FTENT_ATTACHTOTARGET | FTENT_ALIGNTOMOTION; + pTemp->clientIndex = ( pOwner != NULL ) ? pOwner->entindex() : 0; + pTemp->SetOwnerEntity( pOwner ); + pTemp->SetImpactEffect( pszImpactEffect ); + if ( pszParticleEffect ) + { + // Add the entity to the ClientEntityList and create the particle system. + ClientEntityList().AddNonNetworkableEntity( pTemp ); + pTemp->ParticleProp()->Create( pszParticleEffect, PATTACH_ABSORIGIN_FOLLOW ); + + // Set the particle flag on the temp entity and save the name of the particle effect. + pTemp->flags |= FTENT_CLIENTSIDEPARTICLES; + pTemp->SetParticleEffect( pszParticleEffect ); + } + return pTemp; +} + +//----------------------------------------------------------------------------- +// Purpose: Create sprite TE +// Input : *pos - +// *dir - +// scale - +// modelIndex - +// rendermode - +// renderfx - +// a - +// life - +// flags - +// Output : C_LocalTempEntity +//----------------------------------------------------------------------------- +C_LocalTempEntity *CTempEnts::TempSprite( const Vector &pos, const Vector &dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags, const Vector &normal ) +{ + C_LocalTempEntity *pTemp; + const model_t *model; + int frameCount; + + if ( !modelIndex ) + return NULL; + + model = modelinfo->GetModel( modelIndex ); + if ( !model ) + { + Warning("No model %d!\n", modelIndex); + return NULL; + } + + frameCount = modelinfo->GetModelFrameCount( model ); + + pTemp = TempEntAlloc( pos, ( model_t * )model ); + if (!pTemp) + return NULL; + + pTemp->m_flFrameMax = frameCount - 1; + pTemp->m_flFrameRate = 10; + pTemp->SetRenderMode( (RenderMode_t)rendermode ); + pTemp->m_nRenderFX = renderfx; + pTemp->m_flSpriteScale = scale; + pTemp->tempent_renderamt = a * 255; + pTemp->m_vecNormal = normal; + pTemp->SetRenderColor( 255, 255, 255, a * 255 ); + + pTemp->flags |= flags; + + pTemp->SetVelocity( dir ); + pTemp->SetLocalOrigin( pos ); + if ( life ) + pTemp->die = gpGlobals->curtime + life; + else + pTemp->die = gpGlobals->curtime + (frameCount * 0.1) + 1; + + pTemp->m_flFrame = 0; + return pTemp; +} + +//----------------------------------------------------------------------------- +// Purpose: Spray sprite +// Input : *pos - +// *dir - +// modelIndex - +// count - +// speed - +// iRand - +//----------------------------------------------------------------------------- +void CTempEnts::Sprite_Spray( const Vector &pos, const Vector &dir, int modelIndex, int count, int speed, int iRand ) +{ + C_LocalTempEntity *pTemp; + const model_t *pModel; + float noise; + float znoise; + int frameCount; + int i; + + noise = (float)iRand / 100; + + // more vertical displacement + znoise = noise * 1.5; + + if ( znoise > 1 ) + { + znoise = 1; + } + + pModel = modelinfo->GetModel( modelIndex ); + + if ( !pModel ) + { + Warning("No model %d!\n", modelIndex); + return; + } + + frameCount = modelinfo->GetModelFrameCount( pModel ) - 1; + + for ( i = 0; i < count; i++ ) + { + pTemp = TempEntAlloc( pos, ( model_t * )pModel ); + if (!pTemp) + return; + + pTemp->SetRenderMode( kRenderTransAlpha ); + pTemp->SetRenderColor( 255, 255, 255, 255 ); + pTemp->tempent_renderamt = 255; + pTemp->m_nRenderFX = kRenderFxNoDissipation; + //pTemp->scale = random->RandomFloat( 0.1, 0.25 ); + pTemp->m_flSpriteScale = 0.5; + pTemp->flags |= FTENT_FADEOUT | FTENT_SLOWGRAVITY; + pTemp->fadeSpeed = 2.0; + + // make the spittle fly the direction indicated, but mix in some noise. + Vector velocity; + velocity.x = dir[ 0 ] + random->RandomFloat ( -noise, noise ); + velocity.y = dir[ 1 ] + random->RandomFloat ( -noise, noise ); + velocity.z = dir[ 2 ] + random->RandomFloat ( 0, znoise ); + velocity *= random->RandomFloat( (speed * 0.8), (speed * 1.2) ); + pTemp->SetVelocity( velocity ); + + pTemp->SetLocalOrigin( pos ); + + pTemp->die = gpGlobals->curtime + 0.35; + + pTemp->m_flFrame = random->RandomInt( 0, frameCount ); + } +} + +void CTempEnts::Sprite_Trail( const Vector &vecStart, const Vector &vecEnd, int modelIndex, int nCount, float flLife, float flSize, float flAmplitude, int nRenderamt, float flSpeed ) +{ + C_LocalTempEntity *pTemp; + const model_t *pModel; + int flFrameCount; + + pModel = modelinfo->GetModel( modelIndex ); + + if ( !pModel ) + { + Warning("No model %d!\n", modelIndex); + return; + } + + flFrameCount = modelinfo->GetModelFrameCount( pModel ); + + Vector vecDelta; + VectorSubtract( vecEnd, vecStart, vecDelta ); + + Vector vecDir; + VectorCopy( vecDelta, vecDir ); + VectorNormalize( vecDir ); + + flAmplitude /= 256.0; + + for ( int i = 0 ; i < nCount; i++ ) + { + Vector vecPos; + + // Be careful of divide by 0 when using 'count' here... + if ( i == 0 ) + { + VectorMA( vecStart, 0, vecDelta, vecPos ); + } + else + { + VectorMA( vecStart, i / (nCount - 1.0), vecDelta, vecPos ); + } + + pTemp = TempEntAlloc( vecPos, ( model_t * )pModel ); + if (!pTemp) + return; + + pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SPRCYCLE | FTENT_FADEOUT | FTENT_SLOWGRAVITY; + + Vector vecVel = vecDir * flSpeed; + vecVel.x += random->RandomInt( -127,128 ) * flAmplitude; + vecVel.y += random->RandomInt( -127,128 ) * flAmplitude; + vecVel.z += random->RandomInt( -127,128 ) * flAmplitude; + pTemp->SetVelocity( vecVel ); + pTemp->SetLocalOrigin( vecPos ); + + pTemp->m_flSpriteScale = flSize; + pTemp->SetRenderMode( kRenderGlow ); + pTemp->m_nRenderFX = kRenderFxNoDissipation; + pTemp->tempent_renderamt = nRenderamt; + pTemp->SetRenderColor( 255, 255, 255 ); + + pTemp->m_flFrame = random->RandomInt( 0, flFrameCount - 1 ); + pTemp->m_flFrameMax = flFrameCount - 1; + pTemp->die = gpGlobals->curtime + flLife + random->RandomFloat( 0, 4 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Attaches entity to player +// Input : client - +// modelIndex - +// zoffset - +// life - +//----------------------------------------------------------------------------- +void CTempEnts::AttachTentToPlayer( int client, int modelIndex, float zoffset, float life ) +{ + C_LocalTempEntity *pTemp; + const model_t *pModel; + Vector position; + int frameCount; + + if ( client <= 0 || client > gpGlobals->maxClients ) + { + Warning("Bad client in AttachTentToPlayer()!\n"); + return; + } + + IClientEntity *clientClass = cl_entitylist->GetClientEntity( client ); + if ( !clientClass ) + { + Warning("Couldn't get IClientEntity for %i\n", client ); + return; + } + + pModel = modelinfo->GetModel( modelIndex ); + + if ( !pModel ) + { + Warning("No model %d!\n", modelIndex); + return; + } + + VectorCopy( clientClass->GetAbsOrigin(), position ); + position[ 2 ] += zoffset; + + pTemp = TempEntAllocHigh( position, ( model_t * )pModel ); + if (!pTemp) + { + Warning("No temp ent.\n"); + return; + } + + pTemp->SetRenderMode( kRenderNormal ); + pTemp->SetRenderColorA( 255 ); + pTemp->tempent_renderamt = 255; + pTemp->m_nRenderFX = kRenderFxNoDissipation; + + pTemp->clientIndex = client; + pTemp->tentOffset[ 0 ] = 0; + pTemp->tentOffset[ 1 ] = 0; + pTemp->tentOffset[ 2 ] = zoffset; + pTemp->die = gpGlobals->curtime + life; + pTemp->flags |= FTENT_PLYRATTACHMENT | FTENT_PERSIST; + + // is the model a sprite? + if ( modelinfo->GetModelType( pTemp->GetModel() ) == mod_sprite ) + { + frameCount = modelinfo->GetModelFrameCount( pModel ); + pTemp->m_flFrameMax = frameCount - 1; + pTemp->flags |= FTENT_SPRANIMATE | FTENT_SPRANIMATELOOP; + pTemp->m_flFrameRate = 10; + } + else + { + // no animation support for attached clientside studio models. + pTemp->m_flFrameMax = 0; + } + + pTemp->m_flFrame = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Detach entity from player +//----------------------------------------------------------------------------- +void CTempEnts::KillAttachedTents( int client ) +{ + if ( client <= 0 || client > gpGlobals->maxClients ) + { + Warning("Bad client in KillAttachedTents()!\n"); + return; + } + + FOR_EACH_LL( m_TempEnts, i ) + { + C_LocalTempEntity *pTemp = m_TempEnts[ i ]; + + if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + // this TENT is player attached. + // if it is attached to this client, set it to die instantly. + if ( pTemp->clientIndex == client ) + { + pTemp->die = gpGlobals->curtime;// good enough, it will die on next tent update. + } + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create ricochet sprite +// Input : *pos - +// *pmodel - +// duration - +// scale - +//----------------------------------------------------------------------------- +void CTempEnts::RicochetSprite( const Vector &pos, model_t *pmodel, float duration, float scale ) +{ + C_LocalTempEntity *pTemp; + + pTemp = TempEntAlloc( pos, ( model_t * )pmodel ); + if (!pTemp) + return; + + pTemp->SetRenderMode( kRenderGlow ); + pTemp->m_nRenderFX = kRenderFxNoDissipation; + pTemp->SetRenderColorA( 200 ); + pTemp->tempent_renderamt = 200; + pTemp->m_flSpriteScale = scale; + pTemp->flags = FTENT_FADEOUT; + + pTemp->SetVelocity( vec3_origin ); + + pTemp->SetLocalOrigin( pos ); + + pTemp->fadeSpeed = 8; + pTemp->die = gpGlobals->curtime; + + pTemp->m_flFrame = 0; + pTemp->SetLocalAnglesDim( Z_INDEX, 45 * random->RandomInt( 0, 7 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create blood sprite +// Input : *org - +// colorindex - +// modelIndex - +// modelIndex2 - +// size - +//----------------------------------------------------------------------------- +void CTempEnts::BloodSprite( const Vector &org, int r, int g, int b, int a, int modelIndex, int modelIndex2, float size ) +{ + const model_t *model; + + //Validate the model first + if ( modelIndex && (model = modelinfo->GetModel( modelIndex ) ) != NULL ) + { + C_LocalTempEntity *pTemp; + int frameCount = modelinfo->GetModelFrameCount( model ); + color32 impactcolor = { r, g, b, a }; + + //Large, single blood sprite is a high-priority tent + if ( ( pTemp = TempEntAllocHigh( org, ( model_t * )model ) ) != NULL ) + { + pTemp->SetRenderMode( kRenderTransTexture ); + pTemp->m_nRenderFX = kRenderFxClampMinScale; + pTemp->m_flSpriteScale = random->RandomFloat( size / 25, size / 35); + pTemp->flags = FTENT_SPRANIMATE; + + pTemp->m_clrRender = impactcolor; + pTemp->tempent_renderamt= pTemp->m_clrRender->a; + + pTemp->SetVelocity( vec3_origin ); + + pTemp->m_flFrameRate = frameCount * 4; // Finish in 0.250 seconds + pTemp->die = gpGlobals->curtime + (frameCount / pTemp->m_flFrameRate); // Play the whole thing Once + + pTemp->m_flFrame = 0; + pTemp->m_flFrameMax = frameCount - 1; + pTemp->bounceFactor = 0; + pTemp->SetLocalAnglesDim( Z_INDEX, random->RandomInt( 0, 360 ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create default sprite TE +// Input : *pos - +// spriteIndex - +// framerate - +// Output : C_LocalTempEntity +//----------------------------------------------------------------------------- +C_LocalTempEntity *CTempEnts::DefaultSprite( const Vector &pos, int spriteIndex, float framerate ) +{ + C_LocalTempEntity *pTemp; + int frameCount; + const model_t *pSprite; + + // don't spawn while paused + if ( gpGlobals->frametime == 0.0 ) + return NULL; + + pSprite = modelinfo->GetModel( spriteIndex ); + if ( !spriteIndex || !pSprite || modelinfo->GetModelType( pSprite ) != mod_sprite ) + { + DevWarning( 1,"No Sprite %d!\n", spriteIndex); + return NULL; + } + + frameCount = modelinfo->GetModelFrameCount( pSprite ); + + pTemp = TempEntAlloc( pos, ( model_t * )pSprite ); + if (!pTemp) + return NULL; + + pTemp->m_flFrameMax = frameCount - 1; + pTemp->m_flSpriteScale = 1.0; + pTemp->flags |= FTENT_SPRANIMATE; + if ( framerate == 0 ) + framerate = 10; + + pTemp->m_flFrameRate = framerate; + pTemp->die = gpGlobals->curtime + (float)frameCount / framerate; + pTemp->m_flFrame = 0; + pTemp->SetLocalOrigin( pos ); + + return pTemp; +} + +//----------------------------------------------------------------------------- +// Purpose: Create sprite smoke +// Input : *pTemp - +// scale - +//----------------------------------------------------------------------------- +void CTempEnts::Sprite_Smoke( C_LocalTempEntity *pTemp, float scale ) +{ + if ( !pTemp ) + return; + + pTemp->SetRenderMode( kRenderTransAlpha ); + pTemp->m_nRenderFX = kRenderFxNone; + pTemp->SetVelocity( Vector( 0, 0, 30 ) ); + int iColor = random->RandomInt(20,35); + pTemp->SetRenderColor( iColor, + iColor, + iColor, + 255 ); + pTemp->SetLocalOriginDim( Z_INDEX, pTemp->GetLocalOriginDim( Z_INDEX ) + 20 ); + pTemp->m_flSpriteScale = scale; + pTemp->flags = FTENT_WINDBLOWN; + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pos1 - +// angles - +// type - +//----------------------------------------------------------------------------- +void CTempEnts::EjectBrass( const Vector &pos1, const QAngle &angles, const QAngle &gunAngles, int type ) +{ + if ( cl_ejectbrass.GetBool() == false ) + return; + + const model_t *pModel = m_pShells[type]; + + if ( pModel == NULL ) + return; + + C_LocalTempEntity *pTemp = TempEntAlloc( pos1, ( model_t * ) pModel ); + + if ( pTemp == NULL ) + return; + + //Keep track of shell type + if ( type == 2 ) + { + pTemp->hitSound = BOUNCE_SHOTSHELL; + } + else + { + pTemp->hitSound = BOUNCE_SHELL; + } + + pTemp->m_nBody = 0; + + pTemp->flags |= ( FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_GRAVITY | FTENT_ROTATE ); + + pTemp->m_vecTempEntAngVelocity[0] = random->RandomFloat(-1024,1024); + pTemp->m_vecTempEntAngVelocity[1] = random->RandomFloat(-1024,1024); + pTemp->m_vecTempEntAngVelocity[2] = random->RandomFloat(-1024,1024); + + //Face forward + pTemp->SetAbsAngles( gunAngles ); + + pTemp->SetRenderMode( kRenderNormal ); + pTemp->tempent_renderamt = 255; // Set this for fadeout + + Vector dir; + + AngleVectors( angles, &dir ); + + dir *= random->RandomFloat( 150.0f, 200.0f ); + + pTemp->SetVelocity( Vector(dir[0] + random->RandomFloat(-64,64), + dir[1] + random->RandomFloat(-64,64), + dir[2] + random->RandomFloat( 0,64) ) ); + + pTemp->die = gpGlobals->curtime + 1.0f + random->RandomFloat( 0.0f, 1.0f ); // Add an extra 0-1 secs of life +} + +//----------------------------------------------------------------------------- +// Purpose: Create some simple physically simulated models +//----------------------------------------------------------------------------- +C_LocalTempEntity * CTempEnts::SpawnTempModel( model_t *pModel, const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, float flLifeTime, int iFlags ) +{ + Assert( pModel ); + + // Alloc a new tempent + C_LocalTempEntity *pTemp = TempEntAlloc( vecOrigin, ( model_t * ) pModel ); + if ( !pTemp ) + return NULL; + + pTemp->SetAbsAngles( vecAngles ); + pTemp->m_nBody = 0; + pTemp->flags |= iFlags; + pTemp->m_vecTempEntAngVelocity[0] = random->RandomFloat(-255,255); + pTemp->m_vecTempEntAngVelocity[1] = random->RandomFloat(-255,255); + pTemp->m_vecTempEntAngVelocity[2] = random->RandomFloat(-255,255); + pTemp->SetRenderMode( kRenderNormal ); + pTemp->tempent_renderamt = 255; + pTemp->SetVelocity( vecVelocity ); + pTemp->die = gpGlobals->curtime + flLifeTime; + + return pTemp; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// entityIndex - +// attachmentIndex - +// firstPerson - +//----------------------------------------------------------------------------- +void CTempEnts::MuzzleFlash( int type, ClientEntityHandle_t hEntity, int attachmentIndex, bool firstPerson ) +{ + switch( type ) + { + case MUZZLEFLASH_COMBINE: + if ( firstPerson ) + { + MuzzleFlash_Combine_Player( hEntity, attachmentIndex ); + } + else + { + MuzzleFlash_Combine_NPC( hEntity, attachmentIndex ); + } + break; + + case MUZZLEFLASH_SMG1: + if ( firstPerson ) + { + MuzzleFlash_SMG1_Player( hEntity, attachmentIndex ); + } + else + { + MuzzleFlash_SMG1_NPC( hEntity, attachmentIndex ); + } + break; + + case MUZZLEFLASH_PISTOL: + if ( firstPerson ) + { + MuzzleFlash_Pistol_Player( hEntity, attachmentIndex ); + } + else + { + MuzzleFlash_Pistol_NPC( hEntity, attachmentIndex ); + } + break; + case MUZZLEFLASH_SHOTGUN: + if ( firstPerson ) + { + MuzzleFlash_Shotgun_Player( hEntity, attachmentIndex ); + } + else + { + MuzzleFlash_Shotgun_NPC( hEntity, attachmentIndex ); + } + break; + case MUZZLEFLASH_357: + if ( firstPerson ) + { + MuzzleFlash_357_Player( hEntity, attachmentIndex ); + } + break; + case MUZZLEFLASH_RPG: + if ( firstPerson ) + { + // MuzzleFlash_RPG_Player( hEntity, attachmentIndex ); + } + else + { + MuzzleFlash_RPG_NPC( hEntity, attachmentIndex ); + } + break; + break; + default: + { + //NOTENOTE: This means you specified an invalid muzzleflash type, check your spelling? + Assert( 0 ); + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play muzzle flash +// Input : *pos1 - +// type - +//----------------------------------------------------------------------------- +void CTempEnts::MuzzleFlash( const Vector& pos1, const QAngle& angles, int type, ClientEntityHandle_t hEntity, bool firstPerson ) +{ +#ifdef CSTRIKE_DLL + + return; + +#else + + //NOTENOTE: This function is becoming obsolete as the muzzles are moved over to being local to attachments + + switch ( type ) + { + // + // Shotgun + // + case MUZZLEFLASH_SHOTGUN: + if ( firstPerson ) + { + MuzzleFlash_Shotgun_Player( hEntity, 1 ); + } + else + { + MuzzleFlash_Shotgun_NPC( hEntity, 1 ); + } + break; + + // UNDONE: These need their own effects/sprites. For now use the pistol + // SMG1 + case MUZZLEFLASH_SMG1: + if ( firstPerson ) + { + MuzzleFlash_SMG1_Player( hEntity, 1 ); + } + else + { + MuzzleFlash_SMG1_NPC( hEntity, 1 ); + } + break; + + // SMG2 + case MUZZLEFLASH_SMG2: + case MUZZLEFLASH_PISTOL: + if ( firstPerson ) + { + MuzzleFlash_Pistol_Player( hEntity, 1 ); + } + else + { + MuzzleFlash_Pistol_NPC( hEntity, 1 ); + } + break; + + case MUZZLEFLASH_COMBINE: + if ( firstPerson ) + { + //FIXME: These should go away + MuzzleFlash_Combine_Player( hEntity, 1 ); + } + else + { + //FIXME: These should go away + MuzzleFlash_Combine_NPC( hEntity, 1 ); + } + break; + + default: + // There's no supported muzzle flash for the type specified! + Assert(0); + break; + } + +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Create explosion sprite +// Input : *pTemp - +// scale - +// flags - +//----------------------------------------------------------------------------- +void CTempEnts::Sprite_Explode( C_LocalTempEntity *pTemp, float scale, int flags ) +{ + if ( !pTemp ) + return; + + if ( flags & TE_EXPLFLAG_NOADDITIVE ) + { + // solid sprite + pTemp->SetRenderMode( kRenderNormal ); + pTemp->SetRenderColorA( 255 ); + } + else if( flags & TE_EXPLFLAG_DRAWALPHA ) + { + // alpha sprite + pTemp->SetRenderMode( kRenderTransAlpha ); + pTemp->SetRenderColorA( 180 ); + } + else + { + // additive sprite + pTemp->SetRenderMode( kRenderTransAdd ); + pTemp->SetRenderColorA( 180 ); + } + + if ( flags & TE_EXPLFLAG_ROTATE ) + { + pTemp->SetLocalAnglesDim( Z_INDEX, random->RandomInt( 0, 360 ) ); + } + + pTemp->m_nRenderFX = kRenderFxNone; + pTemp->SetVelocity( Vector( 0, 0, 8 ) ); + pTemp->SetRenderColor( 255, 255, 255 ); + pTemp->SetLocalOriginDim( Z_INDEX, pTemp->GetLocalOriginDim( Z_INDEX ) + 10 ); + pTemp->m_flSpriteScale = scale; +} + +enum +{ + SHELL_NONE = 0, + SHELL_SMALL, + SHELL_BIG, + SHELL_SHOTGUN, +}; + +//----------------------------------------------------------------------------- +// Purpose: Clear existing temp entities +//----------------------------------------------------------------------------- +void CTempEnts::Clear( void ) +{ + FOR_EACH_LL( m_TempEnts, i ) + { + C_LocalTempEntity *p = m_TempEnts[ i ]; + + m_TempEntsPool.Free( p ); + } + + m_TempEnts.RemoveAll(); + g_BreakableHelper.Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: Allocate temp entity ( normal/low priority ) +// Input : *org - +// *model - +// Output : C_LocalTempEntity +//----------------------------------------------------------------------------- +C_LocalTempEntity *CTempEnts::TempEntAlloc( const Vector& org, model_t *model ) +{ + C_LocalTempEntity *pTemp; + + if ( !model ) + { + DevWarning( 1, "Can't create temporary entity with NULL model!\n" ); + return NULL; + } + + pTemp = TempEntAlloc(); + + if ( !pTemp ) + { + DevWarning( 1, "Overflow %d temporary ents!\n", MAX_TEMP_ENTITIES ); + return NULL; + } + + m_TempEnts.AddToTail( pTemp ); + + pTemp->Prepare( model, gpGlobals->curtime ); + + pTemp->priority = TENTPRIORITY_LOW; + pTemp->SetAbsOrigin( org ); + + pTemp->m_RenderGroup = RENDER_GROUP_OTHER; + pTemp->AddToLeafSystem( pTemp->m_RenderGroup ); + + if ( CommandLine()->CheckParm( "-tools" ) != NULL ) + { +#ifdef _DEBUG + static bool first = true; + if ( first ) + { + Msg( "Currently not recording tempents, since recording them as entites causes them to be deleted as entities, even though they were allocated through the tempent pool. (crash)\n" ); + first = false; + } +#endif +// ClientEntityList().AddNonNetworkableEntity( pTemp ); + } + + return pTemp; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : C_LocalTempEntity +//----------------------------------------------------------------------------- +C_LocalTempEntity *CTempEnts::TempEntAlloc() +{ + if ( m_TempEnts.Count() >= MAX_TEMP_ENTITIES ) + return NULL; + + C_LocalTempEntity *pTemp = m_TempEntsPool.AllocZero(); + return pTemp; +} + +void CTempEnts::TempEntFree( int index ) +{ + C_LocalTempEntity *pTemp = m_TempEnts[ index ]; + if ( pTemp ) + { + // Remove from the active list. + m_TempEnts.Remove( index ); + + // Cleanup its data. + pTemp->RemoveFromLeafSystem(); + + // Remove the tempent from the ClientEntityList before removing it from the pool. + if ( ( pTemp->flags & FTENT_CLIENTSIDEPARTICLES ) ) + { + // Stop the particle emission if this hasn't happened already - collision or system timing out on its own. + if ( !pTemp->m_bParticleCollision ) + { + pTemp->ParticleProp()->StopEmission(); + } + ClientEntityList().RemoveEntity( pTemp->GetRefEHandle() ); + } + + pTemp->OnRemoveTempEntity(); + + m_TempEntsPool.Free( pTemp ); + } +} + + +// Free the first low priority tempent it finds. +bool CTempEnts::FreeLowPriorityTempEnt() +{ + int next = 0; + for( int i = m_TempEnts.Head(); i != m_TempEnts.InvalidIndex(); i = next ) + { + next = m_TempEnts.Next( i ); + + C_LocalTempEntity *pActive = m_TempEnts[ i ]; + + if ( pActive->priority == TENTPRIORITY_LOW ) + { + TempEntFree( i ); + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Allocate a temp entity, if there are no slots, kick out a low priority +// one if possible +// Input : *org - +// *model - +// Output : C_LocalTempEntity +//----------------------------------------------------------------------------- +C_LocalTempEntity *CTempEnts::TempEntAllocHigh( const Vector& org, model_t *model ) +{ + C_LocalTempEntity *pTemp; + + if ( !model ) + { + DevWarning( 1, "temporary ent model invalid\n" ); + return NULL; + } + + pTemp = TempEntAlloc(); + if ( !pTemp ) + { + // no temporary ents free, so find the first active low-priority temp ent + // and overwrite it. + FreeLowPriorityTempEnt(); + + pTemp = TempEntAlloc(); + } + + + if ( !pTemp ) + { + // didn't find anything? The tent list is either full of high-priority tents + // or all tents in the list are still due to live for > 10 seconds. + DevWarning( 1,"Couldn't alloc a high priority TENT (max %i)!\n", MAX_TEMP_ENTITIES ); + return NULL; + } + + m_TempEnts.AddToTail( pTemp ); + + pTemp->Prepare( model, gpGlobals->curtime ); + + pTemp->priority = TENTPRIORITY_HIGH; + pTemp->SetLocalOrigin( org ); + + pTemp->m_RenderGroup = RENDER_GROUP_OTHER; + pTemp->AddToLeafSystem( pTemp->m_RenderGroup ); + + if ( CommandLine()->CheckParm( "-tools" ) != NULL ) + { + ClientEntityList().AddNonNetworkableEntity( pTemp ); + } + + return pTemp; +} + +//----------------------------------------------------------------------------- +// Purpose: Play sound when temp ent collides with something +// Input : *pTemp - +// damp - +//----------------------------------------------------------------------------- +void CTempEnts::PlaySound ( C_LocalTempEntity *pTemp, float damp ) +{ + const char *soundname = NULL; + float fvol; + bool isshellcasing = false; + int zvel; + + switch ( pTemp->hitSound ) + { + default: + return; // null sound + + case BOUNCE_GLASS: + { + soundname = "Bounce.Glass"; + } + break; + + case BOUNCE_METAL: + { + soundname = "Bounce.Metal"; + } + break; + + case BOUNCE_FLESH: + { + soundname = "Bounce.Flesh"; + } + break; + + case BOUNCE_WOOD: + { + soundname = "Bounce.Wood"; + } + break; + + case BOUNCE_SHRAP: + { + soundname = "Bounce.Shrapnel"; + } + break; + + case BOUNCE_SHOTSHELL: + { + soundname = "Bounce.ShotgunShell"; + isshellcasing = true; // shell casings have different playback parameters + } + break; + + case BOUNCE_SHELL: + { + soundname = "Bounce.Shell"; + isshellcasing = true; // shell casings have different playback parameters + } + break; + + case BOUNCE_CONCRETE: + { + soundname = "Bounce.Concrete"; + } + break; + +#ifdef CSTRIKE_DLL + + case TE_PISTOL_SHELL: + { + soundname = "Bounce.PistolShell"; + } + break; + + case TE_RIFLE_SHELL: + { + soundname = "Bounce.RifleShell"; + } + break; + + case TE_SHOTGUN_SHELL: + { + soundname = "Bounce.ShotgunShell"; + } + break; +#endif + } + + zvel = abs( pTemp->GetVelocity()[2] ); + + // only play one out of every n + + if ( isshellcasing ) + { + // play first bounce, then 1 out of 3 + if ( zvel < 200 && random->RandomInt(0,3) ) + return; + } + else + { + if ( random->RandomInt(0,5) ) + return; + } + + CSoundParameters params; + if ( !C_BaseEntity::GetParametersForSound( soundname, params, NULL ) ) + return; + + fvol = params.volume; + + if ( damp > 0.0 ) + { + int pitch; + + if ( isshellcasing ) + { + fvol *= min (1.0, ((float)zvel) / 350.0); + } + else + { + fvol *= min (1.0, ((float)zvel) / 450.0); + } + + if ( !random->RandomInt(0,3) && !isshellcasing ) + { + pitch = random->RandomInt( params.pitchlow, params.pitchhigh ); + } + else + { + pitch = params.pitch; + } + + CLocalPlayerFilter filter; + + EmitSound_t ep; + ep.m_nChannel = params.channel; + ep.m_pSoundName = params.soundname; + ep.m_flVolume = fvol; + ep.m_SoundLevel = params.soundlevel; + ep.m_nPitch = pitch; + ep.m_pOrigin = &pTemp->GetAbsOrigin(); + + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add temp entity to visible entities list of it's in PVS +// Input : *pEntity - +// Output : int +//----------------------------------------------------------------------------- +int CTempEnts::AddVisibleTempEntity( C_LocalTempEntity *pEntity ) +{ + int i; + Vector mins, maxs; + Vector model_mins, model_maxs; + + if ( !pEntity->GetModel() ) + return 0; + + modelinfo->GetModelBounds( pEntity->GetModel(), model_mins, model_maxs ); + + for (i=0 ; i<3 ; i++) + { + mins[i] = pEntity->GetAbsOrigin()[i] + model_mins[i]; + maxs[i] = pEntity->GetAbsOrigin()[i] + model_maxs[i]; + } + + // FIXME: Vis isn't setup by the time we get here, so this call fails if + // you try to add a tempent before the first frame is drawn, and it's + // one frame behind the rest of the time. Fix this. + // does the box intersect a visible leaf? + //if ( engine->IsBoxInViewCluster( mins, maxs ) ) + { + // Temporary entities have no corresponding element in cl_entitylist + pEntity->index = -1; + + // Add to list + if( pEntity->m_RenderGroup == RENDER_GROUP_OTHER ) + { + pEntity->AddToLeafSystem(); + } + else + { + pEntity->AddToLeafSystem( pEntity->m_RenderGroup ); + } + + return 1; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Runs Temp Ent simulation routines +//----------------------------------------------------------------------------- +void CTempEnts::Update(void) +{ + VPROF_("CTempEnts::Update", 1, VPROF_BUDGETGROUP_CLIENT_SIM, false, BUDGETFLAG_CLIENT); + static int gTempEntFrame = 0; + float frametime; + + // Don't simulate while loading + if ( ( m_TempEnts.Count() == 0 ) || !engine->IsInGame() ) + { + return; + } + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + frametime = gpGlobals->frametime; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if ( frametime == 0 ) + { + FOR_EACH_LL( m_TempEnts, i ) + { + C_LocalTempEntity *current = m_TempEnts[ i ]; + + AddVisibleTempEntity( current ); + } + } + else + { + int next = 0; + for( int i = m_TempEnts.Head(); i != m_TempEnts.InvalidIndex(); i = next ) + { + next = m_TempEnts.Next( i ); + + C_LocalTempEntity *current = m_TempEnts[ i ]; + + // Kill it + if ( !current->IsActive() || !current->Frame( frametime, gTempEntFrame ) ) + { + TempEntFree( i ); + } + else + { + // Cull to PVS (not frustum cull, just PVS) + if ( !AddVisibleTempEntity( current ) ) + { + if ( !( current->flags & FTENT_PERSIST ) ) + { + // If we can't draw it this frame, just dump it. + current->die = gpGlobals->curtime; + // Don't fade out, just die + current->flags &= ~FTENT_FADEOUT; + + TempEntFree( i ); + } + } + } + } + } +} + +// Recache tempents which might have been flushed +void CTempEnts::LevelInit() +{ +#ifndef TF_CLIENT_DLL + m_pSpriteMuzzleFlash[0] = (model_t *)engine->LoadModel( "sprites/ar2_muzzle1.vmt" ); + m_pSpriteMuzzleFlash[1] = (model_t *)engine->LoadModel( "sprites/muzzleflash4.vmt" ); + m_pSpriteMuzzleFlash[2] = (model_t *)engine->LoadModel( "sprites/muzzleflash4.vmt" ); + + m_pSpriteAR2Flash[0] = (model_t *)engine->LoadModel( "sprites/ar2_muzzle1b.vmt" ); + m_pSpriteAR2Flash[1] = (model_t *)engine->LoadModel( "sprites/ar2_muzzle2b.vmt" ); + m_pSpriteAR2Flash[2] = (model_t *)engine->LoadModel( "sprites/ar2_muzzle3b.vmt" ); + m_pSpriteAR2Flash[3] = (model_t *)engine->LoadModel( "sprites/ar2_muzzle4b.vmt" ); + + m_pSpriteCombineFlash[0] = (model_t *)engine->LoadModel( "effects/combinemuzzle1.vmt" ); + m_pSpriteCombineFlash[1] = (model_t *)engine->LoadModel( "effects/combinemuzzle2.vmt" ); + + m_pShells[0] = (model_t *) engine->LoadModel( "models/weapons/shell.mdl" ); + m_pShells[1] = (model_t *) engine->LoadModel( "models/weapons/rifleshell.mdl" ); + m_pShells[2] = (model_t *) engine->LoadModel( "models/weapons/shotgun_shell.mdl" ); +#endif + +#if defined( HL1_CLIENT_DLL ) + m_pHL1Shell = (model_t *)engine->LoadModel( "models/shell.mdl" ); + m_pHL1ShotgunShell = (model_t *)engine->LoadModel( "models/shotgunshell.mdl" ); +#endif + +#if defined( CSTRIKE_DLL ) || defined ( SDK_DLL ) + m_pCS_9MMShell = (model_t *)engine->LoadModel( "models/Shells/shell_9mm.mdl" ); + m_pCS_57Shell = (model_t *)engine->LoadModel( "models/Shells/shell_57.mdl" ); + m_pCS_12GaugeShell = (model_t *)engine->LoadModel( "models/Shells/shell_12gauge.mdl" ); + m_pCS_556Shell = (model_t *)engine->LoadModel( "models/Shells/shell_556.mdl" ); + m_pCS_762NATOShell = (model_t *)engine->LoadModel( "models/Shells/shell_762nato.mdl" ); + m_pCS_338MAGShell = (model_t *)engine->LoadModel( "models/Shells/shell_338mag.mdl" ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Initialize TE system +//----------------------------------------------------------------------------- +void CTempEnts::Init (void) +{ + m_pSpriteMuzzleFlash[0] = NULL; + m_pSpriteMuzzleFlash[1] = NULL; + m_pSpriteMuzzleFlash[2] = NULL; + + m_pSpriteAR2Flash[0] = NULL; + m_pSpriteAR2Flash[1] = NULL; + m_pSpriteAR2Flash[2] = NULL; + m_pSpriteAR2Flash[3] = NULL; + + m_pSpriteCombineFlash[0] = NULL; + m_pSpriteCombineFlash[1] = NULL; + + m_pShells[0] = NULL; + m_pShells[1] = NULL; + m_pShells[2] = NULL; + +#if defined( HL1_CLIENT_DLL ) + m_pHL1Shell = NULL; + m_pHL1ShotgunShell = NULL; +#endif + +#if defined( CSTRIKE_DLL ) || defined ( SDK_DLL ) + m_pCS_9MMShell = NULL; + m_pCS_57Shell = NULL; + m_pCS_12GaugeShell = NULL; + m_pCS_556Shell = NULL; + m_pCS_762NATOShell = NULL; + m_pCS_338MAGShell = NULL; +#endif + + // Clear out lists to start + Clear(); +} + + +void CTempEnts::LevelShutdown() +{ + // Free all active tempents. + Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTempEnts::Shutdown() +{ + LevelShutdown(); +} + +//----------------------------------------------------------------------------- +// Purpose: Cache off all material references +// Input : *pEmitter - Emitter used for material lookup +//----------------------------------------------------------------------------- +inline void CTempEnts::CacheMuzzleFlashes( void ) +{ + int i; + for ( i = 0; i < 4; i++ ) + { + if ( m_Material_MuzzleFlash_Player[i] == NULL ) + { + m_Material_MuzzleFlash_Player[i] = ParticleMgr()->GetPMaterial( VarArgs( "effects/muzzleflash%d_noz", i+1 ) ); + } + } + + for ( i = 0; i < 4; i++ ) + { + if ( m_Material_MuzzleFlash_NPC[i] == NULL ) + { + m_Material_MuzzleFlash_NPC[i] = ParticleMgr()->GetPMaterial( VarArgs( "effects/muzzleflash%d", i+1 ) ); + } + } + + for ( i = 0; i < 2; i++ ) + { + if ( m_Material_Combine_MuzzleFlash_Player[i] == NULL ) + { + m_Material_Combine_MuzzleFlash_Player[i] = ParticleMgr()->GetPMaterial( VarArgs( "effects/combinemuzzle%d_noz", i+1 ) ); + } + } + + for ( i = 0; i < 2; i++ ) + { + if ( m_Material_Combine_MuzzleFlash_NPC[i] == NULL ) + { + m_Material_Combine_MuzzleFlash_NPC[i] = ParticleMgr()->GetPMaterial( VarArgs( "effects/combinemuzzle%d", i+1 ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : entityIndex - +// attachmentIndex - +//----------------------------------------------------------------------------- +void CTempEnts::MuzzleFlash_Combine_Player( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + VPROF_BUDGET( "MuzzleFlash_Combine_Player", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CLocalSpaceEmitter::Create( "MuzzleFlash", hEntity, attachmentIndex, FLE_VIEWMODEL ); + + CacheMuzzleFlashes(); + + SimpleParticle *pParticle; + Vector forward(1,0,0), offset; //NOTENOTE: All coords are in local space + + float flScale = random->RandomFloat( 2.0f, 2.25f ); + + pSimple->SetDrawBeforeViewModel( true ); + + // Flash + for ( int i = 1; i < 6; i++ ) + { + offset = (forward * (i*8.0f*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_Combine_MuzzleFlash_Player[random->RandomInt(0,1)], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.025f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 200+random->RandomInt(0,55); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 255; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 6.0f, 8.0f ) * (12-(i))/12) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + // Tack on the smoke + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_Combine_MuzzleFlash_Player[random->RandomInt(0,1)], vec3_origin ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.025f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = random->RandomInt( 64, 128 ); + pParticle->m_uchEndAlpha = 32; + + pParticle->m_uchStartSize = random->RandomFloat( 10.0f, 16.0f ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &angles - +// entityIndex - +//----------------------------------------------------------------------------- +void CTempEnts::MuzzleFlash_Combine_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + VPROF_BUDGET( "MuzzleFlash_Combine_NPC", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CLocalSpaceEmitter::Create( "MuzzleFlash_Combine_NPC", hEntity, attachmentIndex ); + + SimpleParticle *pParticle; + Vector forward(1,0,0), offset; //NOTENOTE: All coords are in local space + + float flScale = random->RandomFloat( 1.0f, 1.5f ); + + float burstSpeed = random->RandomFloat( 50.0f, 150.0f ); + +#define FRONT_LENGTH 6 + + // Front flash + for ( int i = 1; i < FRONT_LENGTH; i++ ) + { + offset = (forward * (i*2.0f*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_Combine_Muzzleflash[random->RandomInt(0,1)], offset ); + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.1f; + + pParticle->m_vecVelocity = forward * burstSpeed; + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = 255.0f; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 6.0f, 8.0f ) * (FRONT_LENGTH*1.25f-(i))/(FRONT_LENGTH)) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + Vector right(0,1,0), up(0,0,1); + Vector dir = right - up; + +#define SIDE_LENGTH 6 + + burstSpeed = random->RandomFloat( 50.0f, 150.0f ); + + // Diagonal flash + for ( int i = 1; i < SIDE_LENGTH; i++ ) + { + offset = (dir * (i*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_Combine_Muzzleflash[random->RandomInt(0,1)], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.2f; + + pParticle->m_vecVelocity = dir * burstSpeed * 0.25f; + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + dir = right + up; + burstSpeed = random->RandomFloat( 50.0f, 150.0f ); + + // Diagonal flash + for ( int i = 1; i < SIDE_LENGTH; i++ ) + { + offset = (-dir * (i*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_Combine_Muzzleflash[random->RandomInt(0,1)], offset ); + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.2f; + + pParticle->m_vecVelocity = dir * -burstSpeed * 0.25f; + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + dir = up; + burstSpeed = random->RandomFloat( 50.0f, 150.0f ); + + // Top flash + for ( int i = 1; i < SIDE_LENGTH; i++ ) + { + offset = (dir * (i*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_Combine_Muzzleflash[random->RandomInt(0,1)], offset ); + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.2f; + + pParticle->m_vecVelocity = dir * burstSpeed * 0.25f; + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_Combine_Muzzleflash[2], vec3_origin ); + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.3f, 0.4f ); + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = flScale * random->RandomFloat( 12.0f, 16.0f ); + pParticle->m_uchEndSize = 0.0f; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + + matrix3x4_t matAttachment; + Vector origin; + + // Grab the origin out of the transform for the attachment + if ( FX_GetAttachmentTransform( hEntity, attachmentIndex, matAttachment ) ) + { + origin.x = matAttachment[0][3]; + origin.y = matAttachment[1][3]; + origin.z = matAttachment[2][3]; + } + else + { + //NOTENOTE: If you're here, you've specified an entity or an attachment that is invalid + Assert(0); + return; + } + + if ( muzzleflash_light.GetBool() ) + { + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( hEntity ); + if ( pEnt ) + { + dlight_t *el = effects->CL_AllocElight( LIGHT_INDEX_MUZZLEFLASH + pEnt->entindex() ); + + el->origin = origin; + + el->color.r = 64; + el->color.g = 128; + el->color.b = 255; + el->color.exponent = 5; + + el->radius = random->RandomInt( 32, 128 ); + el->decay = el->radius / 0.05f; + el->die = gpGlobals->curtime + 0.05f; + } + } +} + +//================================================== +// Purpose: +// Input: +//================================================== + +void CTempEnts::MuzzleFlash_AR2_NPC( const Vector &origin, const QAngle &angles, ClientEntityHandle_t hEntity ) +{ + //Draw the cloud of fire + FX_MuzzleEffect( origin, angles, 1.0f, hEntity ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTempEnts::MuzzleFlash_SMG1_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + //Draw the cloud of fire + FX_MuzzleEffectAttached( 1.0f, hEntity, attachmentIndex, NULL, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTempEnts::MuzzleFlash_SMG1_Player( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + VPROF_BUDGET( "MuzzleFlash_SMG1_Player", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CLocalSpaceEmitter::Create( "MuzzleFlash_SMG1_Player", hEntity, attachmentIndex, FLE_VIEWMODEL ); + + CacheMuzzleFlashes(); + + SimpleParticle *pParticle; + Vector forward(1,0,0), offset; //NOTENOTE: All coords are in local space + + float flScale = random->RandomFloat( 1.25f, 1.5f ); + + pSimple->SetDrawBeforeViewModel( true ); + + // Flash + for ( int i = 1; i < 6; i++ ) + { + offset = (forward * (i*8.0f*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_MuzzleFlash_Player[random->RandomInt(0,3)], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.025f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 200+random->RandomInt(0,55); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 255; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 6.0f, 8.0f ) * (8-(i))/6) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } +} + +//================================================== +// Purpose: +// Input: +//================================================== + +void CTempEnts::MuzzleFlash_Shotgun_Player( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + VPROF_BUDGET( "MuzzleFlash_Shotgun_Player", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CSimpleEmitter::Create( "MuzzleFlash_Shotgun_Player" ); + + pSimple->SetDrawBeforeViewModel( true ); + + CacheMuzzleFlashes(); + + Vector origin; + QAngle angles; + + // Get our attachment's transformation matrix + FX_GetAttachmentTransform( hEntity, attachmentIndex, &origin, &angles ); + + pSimple->GetBinding().SetBBox( origin - Vector( 4, 4, 4 ), origin + Vector( 4, 4, 4 ) ); + + Vector forward; + AngleVectors( angles, &forward, NULL, NULL ); + + SimpleParticle *pParticle; + Vector offset; + + float flScale = random->RandomFloat( 1.25f, 1.5f ); + + // Flash + for ( int i = 1; i < 6; i++ ) + { + offset = origin + (forward * (i*8.0f*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_MuzzleFlash_Player[random->RandomInt(0,3)], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.0001f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 200+random->RandomInt(0,55); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 255; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 6.0f, 8.0f ) * (8-(i))/6) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } +} + +//================================================== +// Purpose: +// Input: +//================================================== + +void CTempEnts::MuzzleFlash_Shotgun_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + //Draw the cloud of fire + FX_MuzzleEffectAttached( 0.75f, hEntity, attachmentIndex ); + + QAngle angles; + + Vector forward; + int i; + + // Setup the origin. + Vector origin; + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( hEntity ); + if ( !pRenderable ) + return; + + pRenderable->GetAttachment( attachmentIndex, origin, angles ); + AngleVectors( angles, &forward ); + + //Embers less often + if ( random->RandomInt( 0, 2 ) == 0 ) + { + //Embers + CSmartPtr pEmbers = CEmberEffect::Create( "muzzle_embers" ); + pEmbers->SetSortOrigin( origin ); + + SimpleParticle *pParticle; + + int numEmbers = random->RandomInt( 0, 4 ); + + for ( int i = 0; i < numEmbers; i++ ) + { + pParticle = (SimpleParticle *) pEmbers->AddParticle( sizeof( SimpleParticle ), g_Mat_SMG_Muzzleflash[0], origin ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.2f, 0.4f ); + + pParticle->m_vecVelocity.Random( -0.05f, 0.05f ); + pParticle->m_vecVelocity += forward; + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= random->RandomFloat( 64.0f, 256.0f ); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 128; + pParticle->m_uchColor[2] = 64; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = 1; + pParticle->m_uchEndSize = 0; + + pParticle->m_flRoll = 0; + pParticle->m_flRollDelta = 0; + } + } + + // + // Trails + // + + CSmartPtr pTrails = CTrailParticles::Create( "MuzzleFlash_Shotgun_NPC" ); + pTrails->SetSortOrigin( origin ); + + TrailParticle *pTrailParticle; + + pTrails->SetFlag( bitsPARTICLE_TRAIL_FADE ); + pTrails->m_ParticleCollision.SetGravity( 0.0f ); + + int numEmbers = random->RandomInt( 4, 8 ); + + for ( i = 0; i < numEmbers; i++ ) + { + pTrailParticle = (TrailParticle *) pTrails->AddParticle( sizeof( TrailParticle ), g_Mat_SMG_Muzzleflash[0], origin ); + + if ( pTrailParticle == NULL ) + return; + + pTrailParticle->m_flLifetime = 0.0f; + pTrailParticle->m_flDieTime = random->RandomFloat( 0.1f, 0.2f ); + + float spread = 0.05f; + + pTrailParticle->m_vecVelocity.Random( -spread, spread ); + pTrailParticle->m_vecVelocity += forward; + + VectorNormalize( pTrailParticle->m_vecVelocity ); + VectorNormalize( forward ); + + float dot = forward.Dot( pTrailParticle->m_vecVelocity ); + + dot = (1.0f-fabs(dot)) / spread; + pTrailParticle->m_vecVelocity *= (random->RandomFloat( 256.0f, 1024.0f ) * (1.0f-dot)); + + Color32Init( pTrailParticle->m_color, 255, 242, 191, 255 ); + + pTrailParticle->m_flLength = 0.05f; + pTrailParticle->m_flWidth = random->RandomFloat( 0.25f, 0.5f ); + } +} + +//================================================== +// Purpose: +//================================================== +void CTempEnts::MuzzleFlash_357_Player( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + VPROF_BUDGET( "MuzzleFlash_357_Player", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CSimpleEmitter::Create( "MuzzleFlash_357_Player" ); + + pSimple->SetDrawBeforeViewModel( true ); + + CacheMuzzleFlashes(); + + Vector origin; + QAngle angles; + + // Get our attachment's transformation matrix + FX_GetAttachmentTransform( hEntity, attachmentIndex, &origin, &angles ); + + pSimple->GetBinding().SetBBox( origin - Vector( 4, 4, 4 ), origin + Vector( 4, 4, 4 ) ); + + Vector forward; + AngleVectors( angles, &forward, NULL, NULL ); + + SimpleParticle *pParticle; + Vector offset; + + // Smoke + offset = origin + forward * 8.0f; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[0], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + pParticle->m_vecVelocity.Init(); + pParticle->m_vecVelocity = forward * random->RandomFloat( 8.0f, 64.0f ); + pParticle->m_vecVelocity[2] += random->RandomFloat( 4.0f, 16.0f ); + + int color = random->RandomInt( 200, 255 ); + pParticle->m_uchColor[0] = color; + pParticle->m_uchColor[1] = color; + pParticle->m_uchColor[2] = color; + + pParticle->m_uchStartAlpha = random->RandomInt( 64, 128 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = random->RandomInt( 2, 4 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 8.0f; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -0.5f, 0.5f ); + + float flScale = random->RandomFloat( 1.25f, 1.5f ); + + // Flash + for ( int i = 1; i < 6; i++ ) + { + offset = origin + (forward * (i*8.0f*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_MuzzleFlash_Player[random->RandomInt(0,3)], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.01f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 200+random->RandomInt(0,55); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 255; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 6.0f, 8.0f ) * (8-(i))/6) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } +} + +//================================================== +// Purpose: +// Input: +//================================================== + +void CTempEnts::MuzzleFlash_Pistol_Player( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + VPROF_BUDGET( "MuzzleFlash_Pistol_Player", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CSimpleEmitter::Create( "MuzzleFlash_Pistol_Player" ); + pSimple->SetDrawBeforeViewModel( true ); + + CacheMuzzleFlashes(); + + Vector origin; + QAngle angles; + + // Get our attachment's transformation matrix + FX_GetAttachmentTransform( hEntity, attachmentIndex, &origin, &angles ); + + pSimple->GetBinding().SetBBox( origin - Vector( 4, 4, 4 ), origin + Vector( 4, 4, 4 ) ); + + Vector forward; + AngleVectors( angles, &forward, NULL, NULL ); + + SimpleParticle *pParticle; + Vector offset; + + // Smoke + offset = origin + forward * 8.0f; + + if ( random->RandomInt( 0, 3 ) != 0 ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[0], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + pParticle->m_vecVelocity.Init(); + pParticle->m_vecVelocity = forward * random->RandomFloat( 48.0f, 64.0f ); + pParticle->m_vecVelocity[2] += random->RandomFloat( 4.0f, 16.0f ); + + int color = random->RandomInt( 200, 255 ); + pParticle->m_uchColor[0] = color; + pParticle->m_uchColor[1] = color; + pParticle->m_uchColor[2] = color; + + pParticle->m_uchStartAlpha = random->RandomInt( 64, 128 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = random->RandomInt( 2, 4 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4.0f; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -0.1f, 0.1f ); + } + + float flScale = random->RandomFloat( 1.0f, 1.25f ); + + // Flash + for ( int i = 1; i < 6; i++ ) + { + offset = origin + (forward * (i*4.0f*flScale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_MuzzleFlash_Player[random->RandomInt(0,3)], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.01f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 200+random->RandomInt(0,55); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 255; + + pParticle->m_uchStartSize = ( (random->RandomFloat( 6.0f, 8.0f ) * (8-(i))/6) * flScale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } +} + +//================================================== +// Purpose: +// Input: +//================================================== + +void CTempEnts::MuzzleFlash_Pistol_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + FX_MuzzleEffectAttached( 0.5f, hEntity, attachmentIndex, NULL, true ); +} + + + + +//================================================== +// Purpose: +// Input: +//================================================== + +void CTempEnts::MuzzleFlash_RPG_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ) +{ + //Draw the cloud of fire + FX_MuzzleEffectAttached( 1.5f, hEntity, attachmentIndex ); + +} + + + +void CTempEnts::RocketFlare( const Vector& pos ) +{ + C_LocalTempEntity *pTemp; + const model_t *model; + int nframeCount; + + model = (model_t *)engine->LoadModel( "sprites/animglow01.vmt" ); + if ( !model ) + { + return; + } + + nframeCount = modelinfo->GetModelFrameCount( model ); + + pTemp = TempEntAlloc( pos, (model_t *)model ); + if ( !pTemp ) + return; + + pTemp->m_flFrameMax = nframeCount - 1; + pTemp->SetRenderMode( kRenderGlow ); + pTemp->m_nRenderFX = kRenderFxNoDissipation; + pTemp->tempent_renderamt = 255; + pTemp->m_flFrameRate = 1.0; + pTemp->m_flFrame = random->RandomInt( 0, nframeCount - 1); + pTemp->m_flSpriteScale = 1.0; + pTemp->SetAbsOrigin( pos ); + pTemp->die = gpGlobals->curtime + 0.01; +} + + +void CTempEnts::HL1EjectBrass( const Vector &vecPosition, const QAngle &angAngles, const Vector &vecVelocity, int nType ) +{ + const model_t *pModel = NULL; + +#if defined( HL1_CLIENT_DLL ) + switch ( nType ) + { + case 0: + default: + pModel = m_pHL1Shell; + break; + case 1: + pModel = m_pHL1ShotgunShell; + break; + } +#endif + if ( pModel == NULL ) + return; + + C_LocalTempEntity *pTemp = TempEntAlloc( vecPosition, ( model_t * ) pModel ); + + if ( pTemp == NULL ) + return; + + switch ( nType ) + { + case 0: + default: + pTemp->hitSound = BOUNCE_SHELL; + break; + case 1: + pTemp->hitSound = BOUNCE_SHOTSHELL; + break; + } + + pTemp->m_nBody = 0; + pTemp->flags |= ( FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_GRAVITY | FTENT_ROTATE ); + + pTemp->m_vecTempEntAngVelocity[0] = random->RandomFloat( -512,511 ); + pTemp->m_vecTempEntAngVelocity[1] = random->RandomFloat( -256,255 ); + pTemp->m_vecTempEntAngVelocity[2] = random->RandomFloat( -256,255 ); + + //Face forward + pTemp->SetAbsAngles( angAngles ); + + pTemp->SetRenderMode( kRenderNormal ); + pTemp->tempent_renderamt = 255; // Set this for fadeout + + pTemp->SetVelocity( vecVelocity ); + + pTemp->die = gpGlobals->curtime + 2.5; +} + +#define SHELLTYPE_PISTOL 0 +#define SHELLTYPE_RIFLE 1 +#define SHELLTYPE_SHOTGUN 2 + + +void CTempEnts::CSEjectBrass( const Vector &vecPosition, const QAngle &angVelocity, int nVelocity, int shellType, CBasePlayer *pShooter ) +{ + const model_t *pModel = NULL; + int hitsound = TE_BOUNCE_SHELL; + +#if defined ( CSTRIKE_DLL ) || defined ( SDK_DLL ) + + switch( shellType ) + { + default: + case CS_SHELL_9MM: + hitsound = TE_PISTOL_SHELL; + pModel = m_pCS_9MMShell; + break; + case CS_SHELL_57: + hitsound = TE_PISTOL_SHELL; + pModel = m_pCS_57Shell; + break; + case CS_SHELL_12GAUGE: + hitsound = TE_SHOTGUN_SHELL; + pModel = m_pCS_12GaugeShell; + break; + case CS_SHELL_556: + hitsound = TE_RIFLE_SHELL; + pModel = m_pCS_556Shell; + break; + case CS_SHELL_762NATO: + hitsound = TE_RIFLE_SHELL; + pModel = m_pCS_762NATOShell; + break; + case CS_SHELL_338MAG: + hitsound = TE_RIFLE_SHELL; + pModel = m_pCS_338MAGShell; + break; + } +#endif + + if ( pModel == NULL ) + return; + + Vector forward, right, up; + Vector velocity; + Vector origin; + QAngle angle; + + // Add some randomness to the velocity + + AngleVectors( angVelocity, &forward, &right, &up ); + + velocity = forward * nVelocity * random->RandomFloat( 1.2, 2.8 ) + + up * random->RandomFloat( -10, 10 ) + + right * random->RandomFloat( -20, 20 ); + + if( pShooter ) + velocity += pShooter->GetAbsVelocity(); + + C_LocalTempEntity *pTemp = TempEntAlloc( vecPosition, ( model_t * )pModel ); + if ( !pTemp ) + return; + + if( pShooter ) + pTemp->SetAbsAngles( pShooter->EyeAngles() ); + else + pTemp->SetAbsAngles( vec3_angle ); + + pTemp->SetVelocity( velocity ); + + pTemp->hitSound = hitsound; + + pTemp->SetGravity( 0.4 ); + + pTemp->m_nBody = 0; + pTemp->flags = FTENT_FADEOUT | FTENT_GRAVITY | FTENT_COLLIDEALL | FTENT_HITSOUND | FTENT_ROTATE | FTENT_CHANGERENDERONCOLLIDE; + + pTemp->m_vecTempEntAngVelocity[0] = random->RandomFloat(-256,256); + pTemp->m_vecTempEntAngVelocity[1] = random->RandomFloat(-256,256); + pTemp->m_vecTempEntAngVelocity[2] = 0; + pTemp->SetRenderMode( kRenderNormal ); + pTemp->tempent_renderamt = 255; + + pTemp->die = gpGlobals->curtime + 10; + + bool bViewModelBrass = false; + + if ( pShooter && pShooter->GetObserverMode() == OBS_MODE_IN_EYE ) + { + // we are spectating the shooter in first person view + pShooter = ToBasePlayer( pShooter->GetObserverTarget() ); + bViewModelBrass = true; + } + + if ( pShooter ) + { + pTemp->clientIndex = pShooter->entindex(); + bViewModelBrass |= pShooter->IsLocalPlayer(); + } + else + { + pTemp->clientIndex = 0; + } + + if ( bViewModelBrass ) + { + // for viewmodel brass put it in the viewmodel renderer group + pTemp->m_RenderGroup = RENDER_GROUP_VIEW_MODEL_OPAQUE; + } + + +} + diff --git a/game/client/c_te_legacytempents.h b/game/client/c_te_legacytempents.h new file mode 100644 index 00000000..39301e75 --- /dev/null +++ b/game/client/c_te_legacytempents.h @@ -0,0 +1,216 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( C_TE_LEGACYTEMPENTS_H ) +#define C_TE_LEGACYTEMPENTS_H +#ifdef _WIN32 +#pragma once +#endif + +class C_BaseEntity; +class C_LocalTempEntity; +struct model_t; + +#include "mempool.h" +#include "UtlLinkedList.h" + +#if defined( CSTRIKE_DLL ) || defined( SDK_DLL ) +enum +{ + CS_SHELL_9MM = 0, + CS_SHELL_57, + CS_SHELL_12GAUGE, + CS_SHELL_556, + CS_SHELL_762NATO, + CS_SHELL_338MAG +}; +#endif + +//----------------------------------------------------------------------------- +// Purpose: Interface for lecacy temp entities +//----------------------------------------------------------------------------- +abstract_class ITempEnts +{ +public: + virtual ~ITempEnts() {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual void LevelInit() = 0; + virtual void LevelShutdown() = 0; + + virtual void Update( void ) = 0; + virtual void Clear( void ) = 0; + + virtual void BloodSprite( const Vector &org, int r, int g, int b, int a, int modelIndex, int modelIndex2, float size ) = 0; + virtual void RicochetSprite( const Vector &pos, model_t *pmodel, float duration, float scale ) = 0; + virtual void MuzzleFlash( int type, ClientEntityHandle_t hEntity, int attachmentIndex, bool firstPerson ) = 0; + virtual void MuzzleFlash( const Vector &pos1, const QAngle &angles, int type, ClientEntityHandle_t hEntity, bool firstPerson ) = 0; + virtual void EjectBrass( const Vector& pos1, const QAngle& angles, const QAngle& gunAngles, int type ) = 0; + virtual C_LocalTempEntity *SpawnTempModel( model_t *pModel, const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, float flLifeTime, int iFlags ) = 0; + virtual void BreakModel( const Vector &pos, const QAngle &angles, const Vector &size, const Vector &dir, float random, float life, int count, int modelIndex, char flags) = 0; + virtual void Bubbles( const Vector &mins, const Vector &maxs, float height, int modelIndex, int count, float speed ) = 0; + virtual void BubbleTrail( const Vector &start, const Vector &end, float flWaterZ, int modelIndex, int count, float speed ) = 0; + virtual void Sprite_Explode( C_LocalTempEntity *pTemp, float scale, int flags ) = 0; + virtual void FizzEffect( C_BaseEntity *pent, int modelIndex, int density, int current ) = 0; + virtual C_LocalTempEntity *DefaultSprite( const Vector &pos, int spriteIndex, float framerate ) = 0; + virtual void Sprite_Smoke( C_LocalTempEntity *pTemp, float scale ) = 0; + virtual C_LocalTempEntity *TempSprite( const Vector &pos, const Vector &dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags, const Vector &normal = vec3_origin ) = 0; + virtual void AttachTentToPlayer( int client, int modelIndex, float zoffset, float life ) = 0; + virtual void KillAttachedTents( int client ) = 0; + virtual void Sprite_Spray( const Vector &pos, const Vector &dir, int modelIndex, int count, int speed, int iRand ) = 0; + virtual void Sprite_Trail( const Vector &vecStart, const Vector &vecEnd, int modelIndex, int nCount, float flLife, float flSize, float flAmplitude, int nRenderamt, float flSpeed ) = 0; + virtual void RocketFlare( const Vector& pos ) = 0; + virtual void HL1EjectBrass( const Vector &vecPosition, const QAngle &angAngles, const Vector &vecVelocity, int nType ) = 0; + virtual void CSEjectBrass( const Vector &vecPosition, const QAngle &angVelocity, int nType, int nShellType, CBasePlayer *pShooter ) = 0; + + virtual void PlaySound ( C_LocalTempEntity *pTemp, float damp ) = 0; + virtual void PhysicsProp( int modelindex, int skin, const Vector& pos, const QAngle &angles, const Vector& vel, int flags, int effects = 0 ) = 0; + virtual C_LocalTempEntity *ClientProjectile( const Vector& vecOrigin, const Vector& vecVelocity, const Vector& vecAccel, int modelindex, int lifetime, CBaseEntity *pOwner, const char *pszImpactEffect = NULL, const char *pszParticleEffect = NULL ) = 0; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Default implementation of the temp entity interface +//----------------------------------------------------------------------------- +class CTempEnts : public ITempEnts +{ +// Construction +public: + CTempEnts( void ); + virtual ~CTempEnts( void ); +// Exposed interface +public: + virtual void Init( void ); + virtual void Shutdown( void ); + + virtual void LevelInit(); + + virtual void LevelShutdown(); + + virtual void Update( void ); + virtual void Clear( void ); + + // Legacy temp entities still supported + virtual void BloodSprite( const Vector &org, int r, int g, int b, int a, int modelIndex, int modelIndex2, float size ); + virtual void RicochetSprite( const Vector &pos, model_t *pmodel, float duration, float scale ); + + virtual void MuzzleFlash( int type, ClientEntityHandle_t hEntity, int attachmentIndex, bool firstPerson ); + virtual void MuzzleFlash( const Vector &pos1, const QAngle &angles, int type, ClientEntityHandle_t hEntity, bool firstPerson = false ); + + virtual void BreakModel(const Vector &pos, const QAngle &angles, const Vector &size, const Vector &dir, float random, float life, int count, int modelIndex, char flags); + virtual void Bubbles( const Vector &mins, const Vector &maxs, float height, int modelIndex, int count, float speed ); + virtual void BubbleTrail( const Vector &start, const Vector &end, float height, int modelIndex, int count, float speed ); + virtual void Sprite_Explode( C_LocalTempEntity *pTemp, float scale, int flags ); + virtual void FizzEffect( C_BaseEntity *pent, int modelIndex, int density, int current ); + virtual C_LocalTempEntity *DefaultSprite( const Vector &pos, int spriteIndex, float framerate ); + virtual void Sprite_Smoke( C_LocalTempEntity *pTemp, float scale ); + virtual C_LocalTempEntity *TempSprite( const Vector &pos, const Vector &dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags, const Vector &normal = vec3_origin ); + virtual void AttachTentToPlayer( int client, int modelIndex, float zoffset, float life ); + virtual void KillAttachedTents( int client ); + virtual void Sprite_Spray( const Vector &pos, const Vector &dir, int modelIndex, int count, int speed, int iRand ); + void Sprite_Trail( const Vector &vecStart, const Vector &vecEnd, int modelIndex, int nCount, float flLife, float flSize, float flAmplitude, int nRenderamt, float flSpeed ); + + virtual void PlaySound ( C_LocalTempEntity *pTemp, float damp ); + virtual void EjectBrass( const Vector &pos1, const QAngle &angles, const QAngle &gunAngles, int type ); + virtual C_LocalTempEntity *SpawnTempModel( model_t *pModel, const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, float flLifeTime, int iFlags ); + void RocketFlare( const Vector& pos ); + void HL1EjectBrass( const Vector &vecPosition, const QAngle &angAngles, const Vector &vecVelocity, int nType ); + void CSEjectBrass( const Vector &vecPosition, const QAngle &angAngles, int nType, int nShellType, CBasePlayer *pShooter ); + void PhysicsProp( int modelindex, int skin, const Vector& pos, const QAngle &angles, const Vector& vel, int flags, int effects = 0 ); + C_LocalTempEntity *ClientProjectile( const Vector& vecOrigin, const Vector& vecVelocity, const Vector& vecAcceleration, int modelindex, int lifetime, CBaseEntity *pOwner, const char *pszImpactEffect = NULL, const char *pszParticleEffect = NULL ); + +// Data +private: + enum + { + MAX_TEMP_ENTITIES = 500, + MAX_TEMP_ENTITY_SPRITES = 200, + MAX_TEMP_ENTITY_STUDIOMODEL = 50, + }; + + // Global temp entity pool + CClassMemoryPool< C_LocalTempEntity > m_TempEntsPool; + CUtlLinkedList< C_LocalTempEntity *, unsigned short > m_TempEnts; + + // Muzzle flash sprites + struct model_t *m_pSpriteMuzzleFlash[10]; + struct model_t *m_pSpriteAR2Flash[4]; + struct model_t *m_pShells[3]; + struct model_t *m_pSpriteCombineFlash[2]; + +#if defined( HL1_CLIENT_DLL ) + struct model_t *m_pHL1Shell; + struct model_t *m_pHL1ShotgunShell; +#endif + +#if defined( CSTRIKE_DLL ) || defined ( SDK_DLL ) + struct model_t *m_pCS_9MMShell; + struct model_t *m_pCS_57Shell; + struct model_t *m_pCS_12GaugeShell; + struct model_t *m_pCS_556Shell; + struct model_t *m_pCS_762NATOShell; + struct model_t *m_pCS_338MAGShell; +#endif + +// Internal methods also available to children +protected: + C_LocalTempEntity *TempEntAlloc( const Vector& org, model_t *model ); + C_LocalTempEntity *TempEntAllocHigh( const Vector& org, model_t *model ); + +// Material handle caches +private: + + inline void CacheMuzzleFlashes( void ); + PMaterialHandle m_Material_MuzzleFlash_Player[4]; + PMaterialHandle m_Material_MuzzleFlash_NPC[4]; + PMaterialHandle m_Material_Combine_MuzzleFlash_Player[2]; + PMaterialHandle m_Material_Combine_MuzzleFlash_NPC[2]; + +// Internal methods +private: + CTempEnts( const CTempEnts & ); + + void TempEntFree( int index ); + C_LocalTempEntity *TempEntAlloc(); + bool FreeLowPriorityTempEnt(); + + int AddVisibleTempEntity( C_LocalTempEntity *pEntity ); + + // AR2 + void MuzzleFlash_AR2_Player( const Vector &origin, const QAngle &angles, ClientEntityHandle_t hEntity ); + void MuzzleFlash_AR2_NPC( const Vector &origin, const QAngle &angles, ClientEntityHandle_t hEntity ); + + // SMG1 + void MuzzleFlash_SMG1_Player( ClientEntityHandle_t hEntity, int attachmentIndex ); + void MuzzleFlash_SMG1_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ); + + // Shotgun + void MuzzleFlash_Shotgun_Player( ClientEntityHandle_t hEntity, int attachmentIndex ); + void MuzzleFlash_Shotgun_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ); + + // Pistol + void MuzzleFlash_Pistol_Player( ClientEntityHandle_t hEntity, int attachmentIndex ); + void MuzzleFlash_Pistol_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ); + + // Combine + void MuzzleFlash_Combine_Player( ClientEntityHandle_t hEntity, int attachmentIndex ); + void MuzzleFlash_Combine_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ); + + // 357 + void MuzzleFlash_357_Player( ClientEntityHandle_t hEntity, int attachmentIndex ); + + // RPG + void MuzzleFlash_RPG_NPC( ClientEntityHandle_t hEntity, int attachmentIndex ); +}; + + +extern ITempEnts *tempents; + + +#endif // C_TE_LEGACYTEMPENTS_H diff --git a/game/client/c_te_muzzleflash.cpp b/game/client/c_te_muzzleflash.cpp new file mode 100644 index 00000000..f8ddf49d --- /dev/null +++ b/game/client/c_te_muzzleflash.cpp @@ -0,0 +1,112 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Muzzle flash temp ent +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "IEffects.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: User Tracer TE +//----------------------------------------------------------------------------- +class C_TEMuzzleFlash : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEMuzzleFlash, C_BaseTempEntity ); + + DECLARE_CLIENTCLASS(); + + C_TEMuzzleFlash( void ); + virtual ~C_TEMuzzleFlash( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + QAngle m_vecAngles; + float m_flScale; + int m_nType; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEMuzzleFlash::C_TEMuzzleFlash( void ) +{ + m_vecOrigin.Init(); + m_vecAngles.Init(); + m_flScale = 1.0f; + m_nType = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEMuzzleFlash::~C_TEMuzzleFlash( void ) +{ +} + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordMuzzleFlash( const Vector &start, const QAngle &angles, float scale, int type ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_MUZZLE_FLASH ); + msg->SetString( "name", "TE_MuzzleFlash" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "anglesx", angles.x ); + msg->SetFloat( "anglesy", angles.y ); + msg->SetFloat( "anglesz", angles.z ); + msg->SetFloat( "scale", scale ); + msg->SetInt( "type", type ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEMuzzleFlash::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEMuzzleFlash::PostDataUpdate" ); + + //FIXME: Index is incorrect + g_pEffects->MuzzleFlash( m_vecOrigin, m_vecAngles, m_flScale, m_nType ); + RecordMuzzleFlash( m_vecOrigin, m_vecAngles, m_flScale, m_nType ); +} + +void TE_MuzzleFlash( IRecipientFilter& filter, float delay, + const Vector &start, const QAngle &angles, float scale, int type ) +{ + g_pEffects->MuzzleFlash( start, angles, scale, 0 ); + RecordMuzzleFlash( start, angles, scale, 0 ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEMuzzleFlash, DT_TEMuzzleFlash, CTEMuzzleFlash) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropVector( RECVINFO(m_vecAngles)), + RecvPropFloat( RECVINFO(m_flScale)), + RecvPropInt( RECVINFO(m_nType)), +END_RECV_TABLE() diff --git a/game/client/c_te_particlesystem.cpp b/game/client/c_te_particlesystem.cpp new file mode 100644 index 00000000..553fb789 --- /dev/null +++ b/game/client/c_te_particlesystem.cpp @@ -0,0 +1,282 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "c_te_particlesystem.h" +#include "movevars_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +int ramp1[][3] = +{ + { 255, 243, 227 }, + { 223, 171, 39 }, + { 191, 119, 47 }, + { 127, 59, 43 }, + { 99, 47, 31 }, + { 75, 35, 19 }, + { 47, 23, 11 }, + { 175, 103, 35 }, +}; + +int ramp2[][3] = +{ + { 255, 243, 227 }, + { 239, 203, 31 }, + { 223, 171, 39 }, + { 207, 143, 43 }, + { 191, 119, 47 }, + { 175, 99, 47 }, + { 143, 67, 51 }, + { 115, 55, 35 }, +}; + +int ramp3[][3] = +{ + { 223, 171, 39 }, + { 191, 119, 47 }, + { 91, 91, 91 }, + { 75, 75, 75 }, + { 63, 63, 63 }, + { 47, 47, 47 }, +}; + +#define SPARK_COLORCOUNT 9 + +int gSparkRamp[ SPARK_COLORCOUNT ][3] = +{ + { 255, 255, 255 }, + { 255, 247, 199 }, + { 255, 243, 147 }, + { 255, 243, 27 }, + { 239, 203, 31 }, + { 223, 171, 39 }, + { 207, 143, 43 }, + { 127, 59, 43 }, + { 35, 19, 7 } +}; + + + +// ------------------------------------------------------------------------ // +// C_TEParticleSystem. +// ------------------------------------------------------------------------ // + +IMPLEMENT_CLIENTCLASS_DT(C_TEParticleSystem, DT_TEParticleSystem, CTEParticleSystem) + RecvPropFloat( RECVINFO(m_vecOrigin[0]) ), + RecvPropFloat( RECVINFO(m_vecOrigin[1]) ), + RecvPropFloat( RECVINFO(m_vecOrigin[2]) ), +END_RECV_TABLE() + + +C_TEParticleSystem::C_TEParticleSystem() +{ + m_vecOrigin.Init(); +} + + + +// ------------------------------------------------------------------------ // +// CTEParticleRenderer implementation. +// ------------------------------------------------------------------------ // + +CTEParticleRenderer::CTEParticleRenderer( const char *pDebugName ) : + CParticleEffect( pDebugName ) +{ + m_ParticleSize = 1.5f; + m_MaterialHandle = INVALID_MATERIAL_HANDLE; +} + + +CTEParticleRenderer::~CTEParticleRenderer() +{ +} + + +CSmartPtr CTEParticleRenderer::Create( const char *pDebugName, const Vector &vOrigin ) +{ + CTEParticleRenderer *pRet = new CTEParticleRenderer( pDebugName ); + if( pRet ) + { + pRet->SetDynamicallyAllocated( true ); + pRet->SetSortOrigin( vOrigin ); + } + + return pRet; +} + + +StandardParticle_t* CTEParticleRenderer::AddParticle() +{ + if(m_MaterialHandle == INVALID_MATERIAL_HANDLE) + { + m_MaterialHandle = m_ParticleEffect.FindOrAddMaterial("particle/particledefault"); + } + + StandardParticle_t *pParticle = + (StandardParticle_t*)BaseClass::AddParticle( sizeof(StandardParticle_t), m_MaterialHandle, m_vSortOrigin ); + + if(pParticle) + pParticle->m_EffectDataWord = 0; // (ramp) + + return pParticle; +} + + +void CTEParticleRenderer::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const StandardParticle_t *pParticle = (const StandardParticle_t*)pIterator->GetFirst(); + while ( pParticle ) + { + // Render. + Vector tPos; + TransformParticle(ParticleMgr()->GetModelView(), pParticle->m_Pos, tPos); + float sortKey = tPos.z; + + Vector vColor(pParticle->m_Color[0]/255.9f, pParticle->m_Color[1]/255.9f, pParticle->m_Color[2]/255.9f); + RenderParticle_ColorSize( + pIterator->GetParticleDraw(), + tPos, + vColor, + pParticle->m_Color[3]/255.9f, + m_ParticleSize); + + pParticle = (const StandardParticle_t*)pIterator->GetNext( sortKey ); + } +} + +void CTEParticleRenderer::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + StandardParticle_t *pParticle = (StandardParticle_t*)pIterator->GetFirst(); + while ( pParticle ) + { + // Remove the particle? + SetParticleLifetime(pParticle, GetParticleLifetime(pParticle) - pIterator->GetTimeDelta()); + if(GetParticleLifetime(pParticle) < 0) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + float ft = pIterator->GetTimeDelta(); + float time3 = 15.0 * ft; + float time2 = 10.0 * ft; + float time1 = 5.0 * ft; + float dvel = 4* ft ; + + float grav = ft * sv_gravity.GetFloat() * 0.05f; + + int (*colorIndex)[3]; + int iRamp; + + switch(GetParticleType(pParticle)) + { + case pt_static: + break; + + case pt_fire: + pParticle->m_EffectDataWord += (unsigned short)(time1 * (1 << SIMSHIFT)); + iRamp = pParticle->m_EffectDataWord >> SIMSHIFT; + if(iRamp >= 6) + { + pParticle->m_Lifetime = -1; + } + else + { + colorIndex = &ramp3[ iRamp ]; + pParticle->SetColor((float)(*colorIndex)[0] / 255.0f, (float)(*colorIndex)[1] / 255.0f, (float)(*colorIndex)[2] / 255.0f); + } + pParticle->m_Velocity[2] += grav; + break; + + case pt_explode: + pParticle->m_EffectDataWord += (unsigned short)(time2 * (1 << SIMSHIFT)); + iRamp = pParticle->m_EffectDataWord >> SIMSHIFT; + if(iRamp >= 8) + { + pParticle->m_Lifetime = -1; + } + else + { + colorIndex = &ramp1[ iRamp ]; + pParticle->SetColor((float)(*colorIndex)[0] / 255.0f, (float)(*colorIndex)[1] / 255.0f, (float)(*colorIndex)[2] / 255.0f); + } + pParticle->m_Velocity = pParticle->m_Velocity + pParticle->m_Velocity * dvel; + pParticle->m_Velocity[2] -= grav; + break; + + case pt_explode2: + pParticle->m_EffectDataWord += (unsigned short)(time3 * (1 << SIMSHIFT)); + iRamp = pParticle->m_EffectDataWord >> SIMSHIFT; + if(iRamp >= 8) + { + pParticle->m_Lifetime = -1; + } + else + { + colorIndex = &ramp2[ iRamp ]; + pParticle->SetColor((float)(*colorIndex)[0] / 255.0f, (float)(*colorIndex)[1] / 255.0f, (float)(*colorIndex)[2] / 255.0f); + } + pParticle->m_Velocity = pParticle->m_Velocity - pParticle->m_Velocity * ft; + pParticle->m_Velocity[2] -= grav; + break; + + case pt_grav: + pParticle->m_Velocity[2] -= grav * 20; + break; + case pt_slowgrav: + pParticle->m_Velocity[2] = grav; + break; + + case pt_vox_grav: + pParticle->m_Velocity[2] -= grav * 8; + break; + + case pt_vox_slowgrav: + pParticle->m_Velocity[2] -= grav * 4; + break; + + + case pt_blob: + case pt_blob2: + pParticle->m_EffectDataWord += (unsigned short)(time2 * (1 << SIMSHIFT)); + iRamp = pParticle->m_EffectDataWord >> SIMSHIFT; + if(iRamp >= SPARK_COLORCOUNT) + { + pParticle->m_EffectDataWord = 0; + iRamp = 0; + } + + colorIndex = &gSparkRamp[ iRamp ]; + pParticle->SetColor((float)(*colorIndex)[0] / 255.0f, (float)(*colorIndex)[1] / 255.0f, (float)(*colorIndex)[2] / 255.0f); + + pParticle->m_Velocity[0] -= pParticle->m_Velocity[0]*0.5*ft; + pParticle->m_Velocity[1] -= pParticle->m_Velocity[1]*0.5*ft; + pParticle->m_Velocity[2] -= grav * 5; + + if ( random->RandomInt(0,3) ) + { + SetParticleType(pParticle, pt_blob); + pParticle->SetAlpha(0); + } + else + { + SetParticleType(pParticle, pt_blob2); + pParticle->SetAlpha(255.9f); + } + break; + } + // Update position. + pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * ft; + } + + pParticle = (StandardParticle_t*)pIterator->GetNext(); + } +} + + diff --git a/game/client/c_te_particlesystem.h b/game/client/c_te_particlesystem.h new file mode 100644 index 00000000..7fdafcda --- /dev/null +++ b/game/client/c_te_particlesystem.h @@ -0,0 +1,133 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// This file defines the C_TEParticleSystem class, which handles most of the +// interfacing with the particle manager, simulation, and rendering. + +// NOTE: most tempents are singletons. + +#ifndef C_TE_PARTICLESYSTEM_H +#define C_TE_PARTICLESYSTEM_H + + +#include "particlemgr.h" +#include "c_basetempentity.h" +#include "particles_simple.h" + + +#define SIMSHIFT 10 + + +typedef enum { + pt_static, + pt_grav, + pt_slowgrav, + pt_fire, + pt_explode, + pt_explode2, + pt_blob, + pt_blob2, + pt_vox_slowgrav, + pt_vox_grav, + pt_snow, + pt_rain, + pt_clientcustom // Must have callback function specified +} ptype_t; + + + +class C_TEParticleSystem : public C_BaseTempEntity +{ +public: + + DECLARE_CLASS( C_TEParticleSystem, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEParticleSystem(); + + +public: + + // particle effect sort origin + Vector m_vecOrigin; +}; + + +// This is the class that legacy tempents use to emit particles. +// They use it in this pattern: +// CTEParticleRenderer *pRen = CTEParticleRenderer::Create(); +// pRen->AddParticle.... +// pRen->Release(); + +class CTEParticleRenderer : public CParticleEffect +{ +public: + DECLARE_CLASS( CTEParticleRenderer, CParticleEffect ); + virtual ~CTEParticleRenderer(); + + // Create a CTEParticleRenderer. Pass in your sort origin (m_vecOrigin). + static CSmartPtr Create( const char *pDebugName, const Vector &vOrigin ); + + StandardParticle_t* AddParticle(); + + CParticleMgr* GetParticleMgr(); + + void SetParticleType( StandardParticle_t *pParticle, ptype_t type ); + ptype_t GetParticleType( StandardParticle_t *pParticle ); + + // Get/set lifetime. Note: lifetime here is a counter. You set it to a value and it + // counts down and disappears after that long. + void SetParticleLifetime( StandardParticle_t *pParticle, float lifetime ); + float GetParticleLifetime( StandardParticle_t *pParticle ); + + +// IParticleEffect overrides. +public: + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + +private: + CTEParticleRenderer( const char *pDebugName ); + CTEParticleRenderer( const CTEParticleRenderer & ); // not defined, not accessible + + int m_nActiveParticles; + float m_ParticleSize; + PMaterialHandle m_MaterialHandle; +}; + + + +// ------------------------------------------------------------------------ // +// Inlines. +// ------------------------------------------------------------------------ // +inline void CTEParticleRenderer::SetParticleType(StandardParticle_t *pParticle, ptype_t type) +{ + pParticle->m_EffectData = (unsigned char)type; +} + +inline ptype_t CTEParticleRenderer::GetParticleType(StandardParticle_t *pParticle) +{ + return (ptype_t)pParticle->m_EffectData; +} + +// Get/set lifetime. Note that +inline void CTEParticleRenderer::SetParticleLifetime(StandardParticle_t *pParticle, float lifetime) +{ + pParticle->m_Lifetime = lifetime; +} + +inline float CTEParticleRenderer::GetParticleLifetime(StandardParticle_t *pParticle) +{ + return pParticle->m_Lifetime; +} + + +#endif + + + diff --git a/game/client/c_te_physicsprop.cpp b/game/client/c_te_physicsprop.cpp new file mode 100644 index 00000000..6bdcf534 --- /dev/null +++ b/game/client/c_te_physicsprop.cpp @@ -0,0 +1,165 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Purpose: Breakable Model TE +//----------------------------------------------------------------------------- +class C_TEPhysicsProp : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEPhysicsProp, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEPhysicsProp( void ); + virtual ~C_TEPhysicsProp( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + QAngle m_angRotation; + Vector m_vecVelocity; + int m_nModelIndex; + int m_nSkin; + int m_nFlags; + int m_nEffects; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEPhysicsProp, DT_TEPhysicsProp, CTEPhysicsProp) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropFloat( RECVINFO( m_angRotation[0] ) ), + RecvPropFloat( RECVINFO( m_angRotation[1] ) ), + RecvPropFloat( RECVINFO( m_angRotation[2] ) ), + RecvPropVector( RECVINFO(m_vecVelocity)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropInt( RECVINFO(m_nFlags)), + RecvPropInt( RECVINFO(m_nSkin)), + RecvPropInt( RECVINFO(m_nEffects)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEPhysicsProp::C_TEPhysicsProp( void ) +{ + m_vecOrigin.Init(); + m_angRotation.Init(); + m_vecVelocity.Init(); + m_nModelIndex = 0; + m_nSkin = 0; + m_nFlags = 0; + m_nEffects = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEPhysicsProp::~C_TEPhysicsProp( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordPhysicsProp( const Vector& start, const QAngle &angles, + const Vector& vel, int nModelIndex, bool bBreakModel, int nSkin, int nEffects ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + const model_t* pModel = (nModelIndex != 0) ? modelinfo->GetModel( nModelIndex ) : NULL; + const char *pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_PHYSICS_PROP ); + msg->SetString( "name", "TE_PhysicsProp" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "anglesx", angles.x ); + msg->SetFloat( "anglesy", angles.y ); + msg->SetFloat( "anglesz", angles.z ); + msg->SetFloat( "velx", vel.x ); + msg->SetFloat( "vely", vel.y ); + msg->SetFloat( "velz", vel.z ); + msg->SetString( "model", pModelName ); + msg->SetInt( "breakmodel", bBreakModel ); + msg->SetInt( "skin", nSkin ); + msg->SetInt( "effects", nEffects ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TE_PhysicsProp( IRecipientFilter& filter, float delay, + int modelindex, int skin, const Vector& pos, const QAngle &angles, const Vector& vel, bool breakmodel, int effects ) +{ + tempents->PhysicsProp( modelindex, skin, pos, angles, vel, breakmodel, effects ); + RecordPhysicsProp( pos, angles, vel, modelindex, breakmodel, skin, effects ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEPhysicsProp::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEPhysicsProp::PostDataUpdate" ); + + tempents->PhysicsProp( m_nModelIndex, m_nSkin, m_vecOrigin, m_angRotation, m_vecVelocity, m_nFlags, m_nEffects ); + RecordPhysicsProp( m_vecOrigin, m_angRotation, m_vecVelocity, m_nModelIndex, m_nFlags, m_nSkin, m_nEffects ); +} + +void TE_PhysicsProp( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecVel; + QAngle angles; + int nSkin; + nSkin = pKeyValues->GetInt( "skin", 0 ); + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + angles.x = pKeyValues->GetFloat( "anglesx" ); + angles.y = pKeyValues->GetFloat( "anglesy" ); + angles.z = pKeyValues->GetFloat( "anglesz" ); + vecVel.x = pKeyValues->GetFloat( "velx" ); + vecVel.y = pKeyValues->GetFloat( "vely" ); + vecVel.z = pKeyValues->GetFloat( "velz" ); + const char *pModelName = pKeyValues->GetString( "model" ); + int nModelIndex = pModelName[0] ? modelinfo->GetModelIndex( pModelName ) : 0; + bool bBreakModel = pKeyValues->GetInt( "breakmodel" ) != 0; + int nEffects = pKeyValues->GetInt( "effects" ); + + TE_PhysicsProp( filter, delay, nModelIndex, nSkin, vecOrigin, angles, vecVel, bBreakModel, nEffects ); +} + diff --git a/game/client/c_te_playerdecal.cpp b/game/client/c_te_playerdecal.cpp new file mode 100644 index 00000000..745d542b --- /dev/null +++ b/game/client/c_te_playerdecal.cpp @@ -0,0 +1,263 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "iefx.h" +#include "fx.h" +#include "decals.h" +#include "materialsystem/IMaterialSystem.h" +#include "filesystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/itexture.h" +#include "materialsystem/imaterialvar.h" +#include "ClientEffectPrecacheSystem.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar cl_playerspraydisable( "cl_playerspraydisable", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Disable player sprays." ); + +#ifndef _XBOX +CLIENTEFFECT_REGISTER_BEGIN( PrecachePlayerDecal ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo01" ) +#if !defined(HL2_DLL) || defined(HL2MP) +CLIENTEFFECT_MATERIAL( "decals/playerlogo02" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo03" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo04" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo05" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo06" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo07" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo08" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo09" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo10" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo11" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo12" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo13" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo14" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo15" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo16" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo17" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo18" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo19" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo20" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo21" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo22" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo23" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo24" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo25" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo26" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo27" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo28" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo29" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo30" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo31" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo32" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo33" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo34" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo35" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo36" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo37" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo38" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo39" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo40" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo41" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo42" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo43" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo44" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo45" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo46" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo47" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo48" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo49" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo40" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo41" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo42" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo43" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo44" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo45" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo46" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo47" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo48" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo49" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo50" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo51" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo52" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo53" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo54" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo55" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo56" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo57" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo58" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo59" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo60" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo61" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo62" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo63" ) +CLIENTEFFECT_MATERIAL( "decals/playerlogo64" ) +#endif +CLIENTEFFECT_REGISTER_END() +#endif + +//----------------------------------------------------------------------------- +// Purpose: Player Decal TE +//----------------------------------------------------------------------------- +class C_TEPlayerDecal : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEPlayerDecal, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEPlayerDecal( void ); + virtual ~C_TEPlayerDecal( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + int m_nPlayer; + Vector m_vecOrigin; + int m_nEntity; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEPlayerDecal::C_TEPlayerDecal( void ) +{ + m_nPlayer = 0; + m_vecOrigin.Init(); + m_nEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEPlayerDecal::~C_TEPlayerDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEPlayerDecal::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : filter - +// delay - +// pos - +// player - +// entity - +//----------------------------------------------------------------------------- +void TE_PlayerDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int player, int entity ) +{ + if ( cl_playerspraydisable.GetBool() ) + return; + + // No valid target? + C_BaseEntity *ent = cl_entitylist->GetEnt( entity ); + if ( !ent ) + return; + + // Find player logo for shooter + player_info_t info; + engine->GetPlayerInfo( player, &info ); + + // Doesn't have a logo + if ( !info.customFiles[0] ) + return; + + IMaterial *logo = materials->FindMaterial( VarArgs("decals/playerlogo%2.2d", player), TEXTURE_GROUP_DECAL ); + if ( IsErrorMaterial( logo ) ) + return; + + char logohex[ 16 ]; + Q_binarytohex( (byte *)&info.customFiles[0], sizeof( info.customFiles[0] ), logohex, sizeof( logohex ) ); + + // See if logo has been downloaded. + char texname[ 512 ]; + Q_snprintf( texname, sizeof( texname ), "temp/%s", logohex ); + char fulltexname[ 512 ]; + Q_snprintf( fulltexname, sizeof( fulltexname ), "materials/temp/%s.vtf", logohex ); + + if ( !filesystem->FileExists( fulltexname ) ) + { + char custname[ 512 ]; + Q_snprintf( custname, sizeof( custname ), "downloads/%s.dat", logohex ); + // it may have been downloaded but not copied under materials folder + if ( !filesystem->FileExists( custname ) ) + return; // not downloaded yet + + // copy from download folder to materials/temp folder + // this is done since material system can access only materials/*.vtf files + + if ( !engine->CopyFile( custname, fulltexname) ) + return; + } + + ITexture *texture = materials->FindTexture( texname, TEXTURE_GROUP_DECAL ); + if ( IsErrorTexture( texture ) ) + { + return; // not found + } + + // Update the texture used by the material if need be. + bool bFound = false; + IMaterialVar *pMatVar = logo->FindVar( "$basetexture", &bFound ); + if ( bFound && pMatVar ) + { + if ( pMatVar->GetTextureValue() != texture ) + { + pMatVar->SetTextureValue( texture ); + logo->RefreshPreservingMaterialVars(); + } + } + + color32 rgbaColor = { 255, 255, 255, 255 }; + effects->PlayerDecalShoot( + logo, + (void *)player, + entity, + ent->GetModel(), + ent->GetAbsOrigin(), + ent->GetAbsAngles(), + *pos, + 0, + 0, + rgbaColor ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEPlayerDecal::PostDataUpdate( DataUpdateType_t updateType ) +{ +#ifndef _XBOX + VPROF( "C_TEPlayerDecal::PostDataUpdate" ); + + // Decals disabled? + if ( !r_decals.GetBool() ) + return; + + CLocalPlayerFilter filter; + TE_PlayerDecal( filter, 0.0f, &m_vecOrigin, m_nPlayer, m_nEntity ); +#endif +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEPlayerDecal, DT_TEPlayerDecal, CTEPlayerDecal) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropInt( RECVINFO(m_nEntity)), + RecvPropInt( RECVINFO(m_nPlayer)), +END_RECV_TABLE() diff --git a/game/client/c_te_projecteddecal.cpp b/game/client/c_te_projecteddecal.cpp new file mode 100644 index 00000000..55e752ba --- /dev/null +++ b/game/client/c_te_projecteddecal.cpp @@ -0,0 +1,182 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "iefx.h" +#include "engine/IStaticPropMgr.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// UNDONE: Get rid of this? +#define FDECAL_PERMANENT 0x01 + +//----------------------------------------------------------------------------- +// Purpose: Projected Decal TE +//----------------------------------------------------------------------------- +class C_TEProjectedDecal : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEProjectedDecal, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEProjectedDecal( void ); + virtual ~C_TEProjectedDecal( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + Vector m_vecOrigin; + QAngle m_angRotation; + float m_flDistance; + int m_nIndex; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEProjectedDecal, DT_TEProjectedDecal, CTEProjectedDecal) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropQAngles( RECVINFO( m_angRotation )), + RecvPropFloat( RECVINFO(m_flDistance)), + RecvPropInt( RECVINFO(m_nIndex)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEProjectedDecal::C_TEProjectedDecal( void ) +{ + m_vecOrigin.Init(); + m_angRotation.Init(); + m_flDistance = 0.0f; + m_nIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEProjectedDecal::~C_TEProjectedDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEProjectedDecal::Precache( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordProjectDecal( const Vector &pos, const QAngle &angles, + float flDistance, int index ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_PROJECT_DECAL ); + msg->SetString( "name", "TE_ProjectDecal" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", pos.x ); + msg->SetFloat( "originy", pos.y ); + msg->SetFloat( "originz", pos.z ); + msg->SetFloat( "anglesx", angles.x ); + msg->SetFloat( "anglesy", angles.y ); + msg->SetFloat( "anglesz", angles.z ); + msg->SetFloat( "distance", flDistance ); + msg->SetString( "decalname", effects->Draw_DecalNameFromIndex( index ) ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + +void TE_ProjectDecal( IRecipientFilter& filter, float delay, + const Vector* pos, const QAngle *angles, float distance, int index ) +{ + RecordProjectDecal( *pos, *angles, distance, index ); + + trace_t tr; + + Vector fwd; + AngleVectors( *angles, &fwd ); + + Vector endpos; + VectorMA( *pos, distance, fwd, endpos ); + + CTraceFilterHitAll traceFilter; + UTIL_TraceLine( *pos, endpos, MASK_ALL, &traceFilter, &tr ); + + if ( tr.fraction == 1.0f ) + { + return; + } + + C_BaseEntity* ent = tr.m_pEnt; + Assert( ent ); + + int hitbox = tr.hitbox; + + if ( tr.hitbox != 0 ) + { + staticpropmgr->AddDecalToStaticProp( *pos, endpos, hitbox - 1, index, false, tr ); + } + else + { + // Only decal the world + brush models + ent->AddDecal( *pos, endpos, endpos, hitbox, + index, false, tr ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEProjectedDecal::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEProjectedDecal::PostDataUpdate" ); + + CBroadcastRecipientFilter filter; + TE_ProjectDecal( filter, 0.0f, &m_vecOrigin, &m_angRotation, m_flDistance, m_nIndex ); +} + + +//----------------------------------------------------------------------------- +// Playback +//----------------------------------------------------------------------------- +void TE_ProjectDecal( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin; + QAngle angles; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + angles.x = pKeyValues->GetFloat( "anglesx" ); + angles.y = pKeyValues->GetFloat( "anglesy" ); + angles.z = pKeyValues->GetFloat( "anglesz" ); + float flDistance = pKeyValues->GetFloat( "distance" ); + const char *pDecalName = pKeyValues->GetString( "decalname" ); + + TE_ProjectDecal( filter, 0.0f, &vecOrigin, &angles, flDistance, effects->Draw_DecalIndexFromName( (char*)pDecalName ) ); +} + diff --git a/game/client/c_te_showline.cpp b/game/client/c_te_showline.cpp new file mode 100644 index 00000000..1cb93b90 --- /dev/null +++ b/game/client/c_te_showline.cpp @@ -0,0 +1,110 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_te_particlesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Show Line TE +//----------------------------------------------------------------------------- +class C_TEShowLine : public C_TEParticleSystem +{ +public: + DECLARE_CLASS( C_TEShowLine, C_TEParticleSystem ); + DECLARE_CLIENTCLASS(); + + C_TEShowLine( void ); + virtual ~C_TEShowLine( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecEnd; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEShowLine::C_TEShowLine( void ) +{ + m_vecEnd.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TEShowLine::~C_TEShowLine( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void C_TEShowLine::PostDataUpdate( DataUpdateType_t updateType ) +{ + Vector vec; + float len; + StandardParticle_t *p; + int dec; + static int tracercount; + + VectorSubtract (m_vecEnd, m_vecOrigin, vec); + len = VectorNormalize (vec); + + dec = 3; + + VectorScale(vec, dec, vec); + + CSmartPtr pRen = CTEParticleRenderer::Create( "TEShowLine", m_vecOrigin ); + if( !pRen ) + return; + + while (len > 0) + { + len -= dec; + + p = pRen->AddParticle(); + if ( p ) + { + p->m_Velocity.Init(); + + pRen->SetParticleLifetime(p, 30); + + p->SetColor(0, 1, 1); + p->SetAlpha(1); + pRen->SetParticleType(p, pt_static); + + p->m_Pos = m_vecOrigin; + + m_vecOrigin += vec; + } + } +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEShowLine, DT_TEShowLine, CTEShowLine) + RecvPropVector( RECVINFO(m_vecEnd)), +END_RECV_TABLE() + +void TE_ShowLine( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end ) +{ + // Major hack to simulate receiving network message + __g_C_TEShowLine.m_vecOrigin = *start; + __g_C_TEShowLine.m_vecEnd = *end; + + __g_C_TEShowLine.PostDataUpdate( DATA_UPDATE_CREATED ); +} \ No newline at end of file diff --git a/game/client/c_te_smoke.cpp b/game/client/c_te_smoke.cpp new file mode 100644 index 00000000..cbb0dcc3 --- /dev/null +++ b/game/client/c_te_smoke.cpp @@ -0,0 +1,111 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "IEffects.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Smoke TE +//----------------------------------------------------------------------------- +class C_TESmoke : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TESmoke, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TESmoke( void ); + virtual ~C_TESmoke( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + int m_nModelIndex; + float m_fScale; + int m_nFrameRate; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESmoke::C_TESmoke( void ) +{ + m_vecOrigin.Init(); + m_nModelIndex = 0; + m_fScale = 0; + m_nFrameRate = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESmoke::~C_TESmoke( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordSmoke( const Vector &start, float flScale, int nFrameRate ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_SMOKE ); + msg->SetString( "name", "TE_Smoke" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "scale", flScale ); + msg->SetInt( "framerate", nFrameRate ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TESmoke::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TESmoke::PostDataUpdate" ); + + // The number passed down is 10 times smaller... + g_pEffects->Smoke( m_vecOrigin, m_nModelIndex, m_fScale * 10.0f, m_nFrameRate ); + RecordSmoke( m_vecOrigin, m_fScale * 10.0f, m_nFrameRate ); +} + +void TE_Smoke( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate ) +{ + // The number passed down is 10 times smaller... + g_pEffects->Smoke( *pos, modelindex, scale * 10.0f, framerate ); + RecordSmoke( *pos, scale * 10.0f, framerate ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TESmoke, DT_TESmoke, CTESmoke) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropFloat( RECVINFO(m_fScale )), + RecvPropInt( RECVINFO(m_nFrameRate)), +END_RECV_TABLE() diff --git a/game/client/c_te_sparks.cpp b/game/client/c_te_sparks.cpp new file mode 100644 index 00000000..8236f111 --- /dev/null +++ b/game/client/c_te_sparks.cpp @@ -0,0 +1,105 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_te_particlesystem.h" +#include "IEffects.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Sparks TE +//----------------------------------------------------------------------------- +class C_TESparks : public C_TEParticleSystem +{ +public: + DECLARE_CLASS( C_TESparks, C_TEParticleSystem ); + DECLARE_CLIENTCLASS(); + + C_TESparks( void ); + virtual ~C_TESparks( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + virtual void Precache( void ); + + int m_nMagnitude; + int m_nTrailLength; + Vector m_vecDir; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESparks::C_TESparks( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESparks::~C_TESparks( void ) +{ +} + +void C_TESparks::Precache( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordSparks( const Vector &start, int nMagnitude, int nTrailLength, const Vector &direction ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_SPARKS ); + msg->SetString( "name", "TE_Sparks" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "directionx", direction.x ); + msg->SetFloat( "directiony", direction.y ); + msg->SetFloat( "directionz", direction.z ); + msg->SetInt( "magnitude", nMagnitude ); + msg->SetInt( "traillength", nTrailLength ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TESparks::PostDataUpdate( DataUpdateType_t updateType ) +{ + g_pEffects->Sparks( m_vecOrigin, m_nMagnitude, m_nTrailLength, &m_vecDir ); + RecordSparks( m_vecOrigin, m_nMagnitude, m_nTrailLength, m_vecDir ); +} + +void TE_Sparks( IRecipientFilter& filter, float delay, + const Vector* pos, int nMagnitude, int nTrailLength, const Vector *pDir ) +{ + g_pEffects->Sparks( *pos, nMagnitude, nTrailLength, pDir ); + RecordSparks( *pos, nMagnitude, nTrailLength, *pDir ); +} + +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TESparks, DT_TESparks, CTESparks) + RecvPropInt( RECVINFO( m_nMagnitude ) ), + RecvPropInt( RECVINFO( m_nTrailLength ) ), + RecvPropVector( RECVINFO( m_vecDir ) ), +END_RECV_TABLE() diff --git a/game/client/c_te_sprite.cpp b/game/client/c_te_sprite.cpp new file mode 100644 index 00000000..566be477 --- /dev/null +++ b/game/client/c_te_sprite.cpp @@ -0,0 +1,136 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tempent.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Sprite TE +//----------------------------------------------------------------------------- +class C_TESprite : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TESprite, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TESprite( void ); + virtual ~C_TESprite( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + int m_nModelIndex; + float m_fScale; + int m_nBrightness; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TESprite, DT_TESprite, CTESprite) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropFloat( RECVINFO(m_fScale )), + RecvPropInt( RECVINFO(m_nBrightness)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESprite::C_TESprite( void ) +{ + m_vecOrigin.Init(); + m_nModelIndex = 0; + m_fScale = 0; + m_nBrightness = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESprite::~C_TESprite( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordSprite( const Vector& start, int nModelIndex, + float flScale, int nBrightness ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + const model_t* pModel = (nModelIndex != 0) ? modelinfo->GetModel( nModelIndex ) : NULL; + const char *pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_SPRITE_SINGLE ); + msg->SetString( "name", "TE_Sprite" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetString( "model", pModelName ); + msg->SetFloat( "scale", flScale ); + msg->SetInt( "brightness", nBrightness ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TESprite::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TESprite::PostDataUpdate" ); + + float a = ( 1.0 / 255.0 ) * m_nBrightness; + tempents->TempSprite( m_vecOrigin, vec3_origin, m_fScale, m_nModelIndex, kRenderTransAdd, 0, a, 0, FTENT_SPRANIMATE ); + RecordSprite( m_vecOrigin, m_nModelIndex, m_fScale, m_nBrightness ); +} + +void TE_Sprite( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float size, int brightness ) +{ + float a = ( 1.0 / 255.0 ) * brightness; + tempents->TempSprite( *pos, vec3_origin, size, modelindex, kRenderTransAdd, 0, a, 0, FTENT_SPRANIMATE ); + RecordSprite( *pos, modelindex, size, brightness ); +} + +void TE_Sprite( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + const char *pModelName = pKeyValues->GetString( "model" ); + int nModelIndex = pModelName[0] ? modelinfo->GetModelIndex( pModelName ) : 0; + float flScale = pKeyValues->GetFloat( "scale" ); + int nBrightness = pKeyValues->GetInt( "brightness" ); + + TE_Sprite( filter, delay, &vecOrigin, nModelIndex, flScale, nBrightness ); +} diff --git a/game/client/c_te_spritespray.cpp b/game/client/c_te_spritespray.cpp new file mode 100644 index 00000000..b2689171 --- /dev/null +++ b/game/client/c_te_spritespray.cpp @@ -0,0 +1,146 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// $NoKeywords: $ +// +//===========================================================================// +#include "cbase.h" +#include "c_basetempentity.h" +#include "c_te_legacytempents.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "tier0/vprof.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Sprite Spray TE +//----------------------------------------------------------------------------- +class C_TESpriteSpray : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TESpriteSpray, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TESpriteSpray( void ); + virtual ~C_TESpriteSpray( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + +public: + Vector m_vecOrigin; + Vector m_vecDirection; + int m_nModelIndex; + int m_nSpeed; + float m_fNoise; + int m_nCount; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TESpriteSpray, DT_TESpriteSpray, CTESpriteSpray) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropVector( RECVINFO(m_vecDirection)), + RecvPropInt( RECVINFO(m_nModelIndex)), + RecvPropFloat( RECVINFO(m_fNoise )), + RecvPropInt( RECVINFO(m_nCount)), + RecvPropInt( RECVINFO(m_nSpeed)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESpriteSpray::C_TESpriteSpray( void ) +{ + m_vecOrigin.Init(); + m_vecDirection.Init(); + m_nModelIndex = 0; + m_fNoise = 0; + m_nSpeed = 0; + m_nCount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TESpriteSpray::~C_TESpriteSpray( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Recording +//----------------------------------------------------------------------------- +static inline void RecordSpriteSpray( const Vector& start, const Vector &direction, + int nModelIndex, int nSpeed, float flNoise, int nCount ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + const model_t* pModel = (nModelIndex != 0) ? modelinfo->GetModel( nModelIndex ) : NULL; + const char *pModelName = pModel ? modelinfo->GetModelName( pModel ) : ""; + + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_SPRITE_SPRAY ); + msg->SetString( "name", "TE_SpriteSpray" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", start.x ); + msg->SetFloat( "originy", start.y ); + msg->SetFloat( "originz", start.z ); + msg->SetFloat( "directionx", direction.x ); + msg->SetFloat( "directiony", direction.y ); + msg->SetFloat( "directionz", direction.z ); + msg->SetString( "model", pModelName ); + msg->SetInt( "speed", nSpeed ); + msg->SetFloat( "noise", flNoise ); + msg->SetInt( "count", nCount ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TESpriteSpray::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TESpriteSpray::PostDataUpdate" ); + + tempents->Sprite_Spray( m_vecOrigin, m_vecDirection, m_nModelIndex, m_nCount, m_nSpeed * 0.2, m_fNoise * 100.0 ); + RecordSpriteSpray( m_vecOrigin, m_vecDirection, m_nModelIndex, m_nSpeed, m_fNoise, m_nCount ); +} + +void TE_SpriteSpray( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* dir, int modelindex, int speed, float noise, int count ) +{ + tempents->Sprite_Spray( *pos, *dir, modelindex, count, speed * 0.2, noise * 100.0 ); + RecordSpriteSpray( *pos, *dir, modelindex, speed, noise, count ); +} + +void TE_SpriteSpray( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin, vecDirection; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + vecDirection.x = pKeyValues->GetFloat( "directionx" ); + vecDirection.y = pKeyValues->GetFloat( "directiony" ); + vecDirection.z = pKeyValues->GetFloat( "directionz" ); + const char *pModelName = pKeyValues->GetString( "model" ); + int nModelIndex = pModelName[0] ? modelinfo->GetModelIndex( pModelName ) : 0; + int nSpeed = pKeyValues->GetInt( "speed" ); + float flNoise = pKeyValues->GetFloat( "noise" ); + int nCount = pKeyValues->GetInt( "count" ); + + TE_SpriteSpray( filter, delay, &vecOrigin, &vecDirection, nModelIndex, nSpeed, flNoise, nCount ); +} + diff --git a/game/client/c_te_worlddecal.cpp b/game/client/c_te_worlddecal.cpp new file mode 100644 index 00000000..bfbf7a16 --- /dev/null +++ b/game/client/c_te_worlddecal.cpp @@ -0,0 +1,142 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" +#include "c_basetempentity.h" +#include "iefx.h" +#include "tier1/keyvalues.h" +#include "toolframework_client.h" +#include "fx.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: World Decal TE +//----------------------------------------------------------------------------- +class C_TEWorldDecal : public C_BaseTempEntity +{ +public: + DECLARE_CLASS( C_TEWorldDecal, C_BaseTempEntity ); + DECLARE_CLIENTCLASS(); + + C_TEWorldDecal( void ); + virtual ~C_TEWorldDecal( void ); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + virtual void Precache( void ); + +public: + Vector m_vecOrigin; + int m_nIndex; +}; + + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +IMPLEMENT_CLIENTCLASS_EVENT_DT(C_TEWorldDecal, DT_TEWorldDecal, CTEWorldDecal) + RecvPropVector( RECVINFO(m_vecOrigin)), + RecvPropInt( RECVINFO(m_nIndex)), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +C_TEWorldDecal::C_TEWorldDecal( void ) +{ + m_vecOrigin.Init(); + m_nIndex = 0; +} + +C_TEWorldDecal::~C_TEWorldDecal( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEWorldDecal::Precache( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Shared code +//----------------------------------------------------------------------------- +static inline void RecordWorldDecal( const Vector *pos, int index ) +{ + if ( !ToolsEnabled() ) + return; + + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "TempEntity" ); + + msg->SetInt( "te", TE_WORLD_DECAL ); + msg->SetString( "name", "TE_WorldDecal" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetFloat( "originx", pos->x ); + msg->SetFloat( "originy", pos->y ); + msg->SetFloat( "originz", pos->z ); + msg->SetString( "decalname", effects->Draw_DecalNameFromIndex( index ) ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TEWorldDecal::PostDataUpdate( DataUpdateType_t updateType ) +{ + VPROF( "C_TEWorldDecal::PostDataUpdate" ); + + if ( r_decals.GetInt() ) + { + C_BaseEntity *ent = cl_entitylist->GetEnt( 0 ); + if ( ent ) + { + effects->DecalShoot( m_nIndex, 0, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), m_vecOrigin, 0, 0 ); + } + } + RecordWorldDecal( &m_vecOrigin, m_nIndex ); +} + + +//----------------------------------------------------------------------------- +// Client-side effects +//----------------------------------------------------------------------------- +void TE_WorldDecal( IRecipientFilter& filter, float delay, const Vector* pos, int index ) +{ + if ( r_decals.GetInt() ) + { + C_BaseEntity *ent = cl_entitylist->GetEnt( 0 ); + if ( ent ) + { + effects->DecalShoot( index, 0, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), *pos, 0, 0 ); + } + } + RecordWorldDecal( pos, index ); +} + + +void TE_WorldDecal( IRecipientFilter& filter, float delay, KeyValues *pKeyValues ) +{ + Vector vecOrigin; + vecOrigin.x = pKeyValues->GetFloat( "originx" ); + vecOrigin.y = pKeyValues->GetFloat( "originy" ); + vecOrigin.z = pKeyValues->GetFloat( "originz" ); + const char *pDecalName = pKeyValues->GetString( "decalname" ); + + TE_WorldDecal( filter, 0.0f, &vecOrigin, effects->Draw_DecalIndexFromName( (char*)pDecalName ) ); +} diff --git a/game/client/c_team.cpp b/game/client/c_team.cpp new file mode 100644 index 00000000..aa73b56c --- /dev/null +++ b/game/client/c_team.cpp @@ -0,0 +1,256 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client side CTeam class +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_team.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: RecvProxy that converts the Team's player UtlVector to entindexes +//----------------------------------------------------------------------------- +void RecvProxy_PlayerList( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_Team *pTeam = (C_Team*)pOut; + pTeam->m_aPlayers[pData->m_iElement] = pData->m_Value.m_Int; +} + + +void RecvProxyArrayLength_PlayerArray( void *pStruct, int objectID, int currentArrayLength ) +{ + C_Team *pTeam = (C_Team*)pStruct; + + if ( pTeam->m_aPlayers.Size() != currentArrayLength ) + pTeam->m_aPlayers.SetSize( currentArrayLength ); +} + + +IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_Team, DT_Team, CTeam) + RecvPropInt( RECVINFO(m_iTeamNum)), + RecvPropInt( RECVINFO(m_iScore)), + RecvPropInt( RECVINFO(m_iRoundsWon) ), + RecvPropString( RECVINFO(m_szTeamname)), + + RecvPropArray2( + RecvProxyArrayLength_PlayerArray, + RecvPropInt( "player_array_element", 0, SIZEOF_IGNORE, 0, RecvProxy_PlayerList ), + MAX_PLAYERS, + 0, + "player_array" + ) +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_Team ) + DEFINE_PRED_ARRAY( m_szTeamname, FIELD_CHARACTER, MAX_TEAM_NAME_LENGTH, FTYPEDESC_PRIVATE ), + DEFINE_PRED_FIELD( m_iScore, FIELD_INTEGER, FTYPEDESC_PRIVATE ), + DEFINE_PRED_FIELD( m_iRoundsWon, FIELD_INTEGER, FTYPEDESC_PRIVATE ), + DEFINE_PRED_FIELD( m_iDeaths, FIELD_INTEGER, FTYPEDESC_PRIVATE ), + DEFINE_PRED_FIELD( m_iPing, FIELD_INTEGER, FTYPEDESC_PRIVATE ), + DEFINE_PRED_FIELD( m_iPacketloss, FIELD_INTEGER, FTYPEDESC_PRIVATE ), + DEFINE_PRED_FIELD( m_iTeamNum, FIELD_INTEGER, FTYPEDESC_PRIVATE ), +END_PREDICTION_DATA(); + +// Global list of client side team entities +CUtlVector< C_Team * > g_Teams; + +//================================================================================================= +// C_Team functionality + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_Team::C_Team() +{ + m_iScore = 0; + m_iRoundsWon = 0; + memset( m_szTeamname, 0, sizeof(m_szTeamname) ); + + m_iDeaths = 0; + m_iPing = 0; + m_iPacketloss = 0; + + // Add myself to the global list of team entities + g_Teams.AddToTail( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_Team::~C_Team() +{ + g_Teams.FindAndRemove( this ); +} + + +void C_Team::RemoveAllPlayers() +{ + m_aPlayers.RemoveAll(); +} + +void C_Team::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); +} + + +//----------------------------------------------------------------------------- +// Gets the ith player on the team (may return NULL) +//----------------------------------------------------------------------------- +C_BasePlayer* C_Team::GetPlayer( int idx ) +{ + return (C_BasePlayer*)cl_entitylist->GetEnt(m_aPlayers[idx]); +} + + +int C_Team::GetTeamNumber() const +{ + return m_iTeamNum; +} + + +//================================================================================================= +// TEAM HANDLING +//================================================================================================= +// Purpose: +//----------------------------------------------------------------------------- +char *C_Team::Get_Name( void ) +{ + return m_szTeamname; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_Team::Get_Score( void ) +{ + return m_iScore; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_Team::Get_Deaths( void ) +{ + return m_iDeaths; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_Team::Get_Ping( void ) +{ + return m_iPing; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of players in this team +//----------------------------------------------------------------------------- +int C_Team::Get_Number_Players( void ) +{ + return m_aPlayers.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the specified player is on this team +//----------------------------------------------------------------------------- +bool C_Team::ContainsPlayer( int iPlayerIndex ) +{ + for (int i = 0; i < m_aPlayers.Size(); i++ ) + { + if ( m_aPlayers[i] == iPlayerIndex ) + return true; + } + + return false; +} + + +void C_Team::ClientThink() +{ +} + + +//================================================================================================= +// GLOBAL CLIENT TEAM HANDLING +//================================================================================================= +// Purpose: Get the C_Team for the local player +//----------------------------------------------------------------------------- +C_Team *GetLocalTeam( void ) +{ + C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); + + if ( !player ) + return NULL; + + return GetPlayersTeam( player->index ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the C_Team for the specified team number +//----------------------------------------------------------------------------- +C_Team *GetGlobalTeam( int iTeamNumber ) +{ + for (int i = 0; i < g_Teams.Count(); i++ ) + { + if ( g_Teams[i]->GetTeamNumber() == iTeamNumber ) + return g_Teams[i]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the number of teams you can access via GetGlobalTeam() (hence the +1) +//----------------------------------------------------------------------------- +int GetNumTeams() +{ + return g_Teams.Count() + 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the team of the specified player +//----------------------------------------------------------------------------- +C_Team *GetPlayersTeam( int iPlayerIndex ) +{ + for (int i = 0; i < g_Teams.Count(); i++ ) + { + if ( g_Teams[i]->ContainsPlayer( iPlayerIndex ) ) + return g_Teams[i]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the team of the specified player +//----------------------------------------------------------------------------- +C_Team *GetPlayersTeam( C_BasePlayer *pPlayer ) +{ + return GetPlayersTeam( pPlayer->entindex() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the two specified players are on the same team +//----------------------------------------------------------------------------- +bool ArePlayersOnSameTeam( int iPlayerIndex1, int iPlayerIndex2 ) +{ + for (int i = 0; i < g_Teams.Count(); i++ ) + { + if ( g_Teams[i]->ContainsPlayer( iPlayerIndex1 ) && g_Teams[i]->ContainsPlayer( iPlayerIndex2 ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of team managers +//----------------------------------------------------------------------------- +int GetNumberOfTeams( void ) +{ + return g_Teams.Size(); +} \ No newline at end of file diff --git a/game/client/c_team.h b/game/client/c_team.h new file mode 100644 index 00000000..55988783 --- /dev/null +++ b/game/client/c_team.h @@ -0,0 +1,87 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client side CTeam class +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_TEAM_H +#define C_TEAM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "shareddefs.h" +#include "utlvector.h" +#include "client_thinklist.h" + + +class C_BasePlayer; + +class C_Team : public C_BaseEntity +{ + DECLARE_CLASS( C_Team, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_Team(); + virtual ~C_Team(); + + virtual void PreDataUpdate( DataUpdateType_t updateType ); + + // Data Handling + virtual char *Get_Name( void ); + virtual int Get_Score( void ); + virtual int Get_Deaths( void ); + virtual int Get_Ping( void ); + + // Player Handling + virtual int Get_Number_Players( void ); + virtual bool ContainsPlayer( int iPlayerIndex ); + C_BasePlayer* GetPlayer( int idx ); + + // for shared code, use the same function name + virtual int GetNumPlayers( void ) { return Get_Number_Players(); } + + int GetTeamNumber() const; + + int GetRoundsWon(void) { return m_iRoundsWon; } + + void RemoveAllPlayers(); + + +// IClientThinkable overrides. +public: + + virtual void ClientThink(); + + +public: + + // Data received from the server + CUtlVector< int > m_aPlayers; + char m_szTeamname[ MAX_TEAM_NAME_LENGTH ]; + int m_iScore; + int m_iRoundsWon; + + // Data for the scoreboard + int m_iDeaths; + int m_iPing; + int m_iPacketloss; + int m_iTeamNum; +}; + + +// Global list of client side team entities +extern CUtlVector< C_Team * > g_Teams; + +// Global team handling functions +C_Team *GetLocalTeam( void ); +C_Team *GetGlobalTeam( int iTeamNumber ); +C_Team *GetPlayersTeam( int iPlayerIndex ); +C_Team *GetPlayersTeam( C_BasePlayer *pPlayer ); +bool ArePlayersOnSameTeam( int iPlayerIndex1, int iPlayerIndex2 ); +extern int GetNumberOfTeams( void ); + +#endif // C_TEAM_H diff --git a/game/client/c_team_objectiveresource.cpp b/game/client/c_team_objectiveresource.cpp new file mode 100644 index 00000000..a22bc0c5 --- /dev/null +++ b/game/client/c_team_objectiveresource.cpp @@ -0,0 +1,401 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: An entity that networks the state of the game's objectives. +// +//============================================================================= + +#include "cbase.h" +#include "c_team_objectiveresource.h" +#include "igameevents.h" +#include "teamplayroundbased_gamerules.h" +#include "c_baseplayer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define RESOURCE_THINK_TIME 0.1 + +extern ConVar mp_capstyle; +extern ConVar mp_capdeteriorate_time; + +//----------------------------------------------------------------------------- +// Purpose: Owner recv proxy +//----------------------------------------------------------------------------- +void RecvProxy_Owner( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + // hacks? Not sure how else to get the index of the integer that is + // being transmitted. + int index = pData->m_pRecvProp->GetOffset() / sizeof(int); + + ObjectiveResource()->SetOwningTeam( index, pData->m_Value.m_Int ); +} + +//----------------------------------------------------------------------------- +// Purpose: capper recv proxy +//----------------------------------------------------------------------------- +void RecvProxy_CappingTeam( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + int index = pData->m_pRecvProp->GetOffset() / sizeof(int); + + ObjectiveResource()->SetCappingTeam( index, pData->m_Value.m_Int ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RecvProxy_CapLayout( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + ObjectiveResource()->SetCapLayout( pData->m_Value.m_pString ); +} + +IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_BaseTeamObjectiveResource, DT_BaseTeamObjectiveResource, CBaseTeamObjectiveResource) + RecvPropInt( RECVINFO(m_iTimerToShowInHUD) ), + RecvPropInt( RECVINFO(m_iStopWatchTimer) ), + + RecvPropInt( RECVINFO(m_iNumControlPoints) ), + RecvPropBool( RECVINFO(m_bPlayingMiniRounds) ), + RecvPropBool( RECVINFO(m_bControlPointsReset) ), + RecvPropInt( RECVINFO(m_iUpdateCapHudParity) ), + + RecvPropArray( RecvPropVector(RECVINFO(m_vCPPositions[0])), m_vCPPositions), + RecvPropArray3( RECVINFO_ARRAY(m_bCPIsVisible), RecvPropInt( RECVINFO(m_bCPIsVisible[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_flLazyCapPerc), RecvPropFloat( RECVINFO(m_flLazyCapPerc[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iTeamIcons), RecvPropInt( RECVINFO(m_iTeamIcons[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iTeamOverlays), RecvPropInt( RECVINFO(m_iTeamOverlays[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iTeamReqCappers), RecvPropInt( RECVINFO(m_iTeamReqCappers[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_flTeamCapTime), RecvPropTime( RECVINFO(m_flTeamCapTime[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iPreviousPoints), RecvPropInt( RECVINFO(m_iPreviousPoints[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_bTeamCanCap), RecvPropBool( RECVINFO(m_bTeamCanCap[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iTeamBaseIcons), RecvPropInt( RECVINFO(m_iTeamBaseIcons[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iBaseControlPoints), RecvPropInt( RECVINFO(m_iBaseControlPoints[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_bInMiniRound), RecvPropBool( RECVINFO(m_bInMiniRound[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iWarnOnCap), RecvPropInt( RECVINFO(m_iWarnOnCap[0]) ) ), + RecvPropArray( RecvPropString( RECVINFO( m_iszWarnSound[0]) ), m_iszWarnSound ), + RecvPropArray3( RECVINFO_ARRAY(m_flPathDistance), RecvPropFloat( RECVINFO(m_flPathDistance[0]) ) ), + + // state variables + RecvPropArray3( RECVINFO_ARRAY(m_iNumTeamMembers), RecvPropInt( RECVINFO(m_iNumTeamMembers[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iCappingTeam), RecvPropInt( RECVINFO(m_iCappingTeam[0]), 0, RecvProxy_CappingTeam ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iTeamInZone), RecvPropInt( RECVINFO(m_iTeamInZone[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_bBlocked), RecvPropInt( RECVINFO(m_bBlocked[0]) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_iOwner), RecvPropInt( RECVINFO(m_iOwner[0]), 0, RecvProxy_Owner ) ), + RecvPropString( RECVINFO(m_pszCapLayoutInHUD), 0, RecvProxy_CapLayout ), +END_RECV_TABLE() + +C_BaseTeamObjectiveResource *g_pObjectiveResource = NULL; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BaseTeamObjectiveResource::C_BaseTeamObjectiveResource() +{ + m_iNumControlPoints = 0; + m_iPrevNumControlPoints = 0; + m_pszCapLayoutInHUD[0] = 0; + m_iUpdateCapHudParity = 0; + m_bControlPointsReset = false; + + for ( int i=0; i < MAX_CONTROL_POINTS; i++ ) + { + m_flCapTimeLeft[i] = 0; + m_flCapLastThinkTime[i] = 0; + m_flLastCapWarningTime[i] = 0; + m_bWarnedOnFinalCap[i] = false; // have we warned + m_iWarnOnCap[i] = CP_WARN_NORMAL; // should we warn + m_iszWarnSound[i][0] = 0; // what sound should be played + m_flLazyCapPerc[i] = 0.0; + + for ( int team = 0; team < MAX_CONTROL_POINT_TEAMS; team++ ) + { + int iTeamIndex = TEAM_ARRAY( i, team ); + + m_iTeamIcons[ iTeamIndex ] = 0; + m_iTeamOverlays[ iTeamIndex ] = 0; + m_iTeamReqCappers[ iTeamIndex ] = 0; + m_flTeamCapTime[ iTeamIndex ] = 0.0f; + m_iNumTeamMembers[ iTeamIndex ] = 0; + for ( int ipoint = 0; ipoint < MAX_PREVIOUS_POINTS; ipoint++ ) + { + int iIntIndex = ipoint + (i * MAX_PREVIOUS_POINTS) + (team * MAX_CONTROL_POINTS * MAX_PREVIOUS_POINTS); + m_iPreviousPoints[ iIntIndex ] = -1; + } + } + } + + for ( int team = 0; team < MAX_CONTROL_POINT_TEAMS; team++ ) + { + m_iTeamBaseIcons[team] = 0; + } + + g_pObjectiveResource = this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BaseTeamObjectiveResource::~C_BaseTeamObjectiveResource() +{ + g_pObjectiveResource = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseTeamObjectiveResource::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + m_iPrevNumControlPoints = m_iNumControlPoints; + m_iOldUpdateCapHudParity = m_iUpdateCapHudParity; + m_bOldControlPointsReset = m_bControlPointsReset; + + memcpy( m_flOldLazyCapPerc, m_flLazyCapPerc, sizeof(float)*m_iNumControlPoints ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseTeamObjectiveResource::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if ( m_bOldControlPointsReset != m_bControlPointsReset || m_iNumControlPoints != m_iPrevNumControlPoints ) + { + // Tell everyone we know how many control points we have + IGameEvent *event = gameeventmanager->CreateEvent( "controlpoint_initialized" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + } + + if ( m_iUpdateCapHudParity != m_iOldUpdateCapHudParity ) + { + UpdateControlPoint( "controlpoint_updateimages" ); + } + + for ( int i = 0; i < m_iNumControlPoints; i++ ) + { + if ( m_flOldLazyCapPerc[i] != m_flLazyCapPerc[i] ) + { + m_flCapTimeLeft[i] = m_flLazyCapPerc[i] * m_flTeamCapTime[ TEAM_ARRAY(i,m_iCappingTeam[i]) ]; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseTeamObjectiveResource::UpdateControlPoint( const char *pszEvent, int index ) +{ + IGameEvent *event = gameeventmanager->CreateEvent( pszEvent ); + if ( event ) + { + event->SetInt( "index", index ); + gameeventmanager->FireEventClientSide( event ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float C_BaseTeamObjectiveResource::GetCPCapPercentage( int index ) +{ + Assert( 0 <= index && index <= m_iNumControlPoints ); + + float flCapLength = m_flTeamCapTime[ TEAM_ARRAY(index,m_iCappingTeam[index]) ]; + + if( flCapLength <= 0 ) + return 0.0f; + + float flElapsedTime = flCapLength - m_flCapTimeLeft[index]; + + if( flElapsedTime > flCapLength ) + return 1.0f; + + return ( flElapsedTime / flCapLength ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_BaseTeamObjectiveResource::GetNumControlPointsOwned( void ) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer ) + return 0; + + int iTeam = pPlayer->GetTeamNumber(); + int nOwned = 0; + for ( int i = 0; i < GetNumControlPoints(); ++i ) + { + if ( GetOwningTeam( i ) == iTeam ) + { + ++nOwned; + } + } + return nOwned; +} + +//----------------------------------------------------------------------------- +// Purpose: +// team - +//----------------------------------------------------------------------------- +void C_BaseTeamObjectiveResource::SetOwningTeam( int index, int team ) +{ + if ( team == m_iCappingTeam[index] ) + { + // successful cap, reset things + m_iCappingTeam[index] = TEAM_UNASSIGNED; + m_flCapTimeLeft[index] = 0.0f; + m_flCapLastThinkTime[index] = 0; + } + + m_iOwner[index] = team; + + UpdateControlPoint( "controlpoint_updateowner", index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseTeamObjectiveResource::SetCappingTeam( int index, int team ) +{ + if ( team != GetOwningTeam( index ) && ( team > LAST_SHARED_TEAM ) ) + { + m_flCapTimeLeft[index] = m_flTeamCapTime[ TEAM_ARRAY(index,team) ]; + } + else + { + m_flCapTimeLeft[index] = 0.0; + } + + m_iCappingTeam[index] = team; + m_bWarnedOnFinalCap[index] = false; + + m_flCapLastThinkTime[index] = gpGlobals->curtime; + SetNextClientThink( gpGlobals->curtime + RESOURCE_THINK_TIME ); + UpdateControlPoint( "controlpoint_updatecapping", index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseTeamObjectiveResource::SetCapLayout( const char *pszLayout ) +{ + Q_strncpy( m_pszCapLayoutInHUD, pszLayout, MAX_CAPLAYOUT_LENGTH ); + + UpdateControlPoint( "controlpoint_updatelayout" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseTeamObjectiveResource::CapIsBlocked( int index ) +{ + Assert( 0 <= index && index <= m_iNumControlPoints ); + + if ( m_flCapTimeLeft[index] ) + { + // Blocked caps have capping teams & cap times, but no players on the point + if ( GetNumPlayersInArea( index, m_iCappingTeam[index] ) == 0 ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseTeamObjectiveResource::ClientThink() +{ + BaseClass::ClientThink(); + + for ( int i = 0; i < MAX_CONTROL_POINTS; i++ ) + { + if ( m_flCapTimeLeft[i] ) + { + if ( !IsCPBlocked(i) ) + { + bool bDeteriorateNormally = true; + + // Make sure there is only 1 team on the cap + int iPlayersCapping = GetNumPlayersInArea( i, GetTeamInZone(i) ); + if ( iPlayersCapping > 0 ) + { + float flReduction = gpGlobals->curtime - m_flCapLastThinkTime[i]; + if ( mp_capstyle.GetInt() == 1 ) + { + // Diminishing returns for successive players. + for ( int iPlayer = 1; iPlayer < iPlayersCapping; iPlayer++ ) + { + flReduction += ((gpGlobals->curtime - m_flCapLastThinkTime[i]) / (float)(iPlayer+1)); + } + } + + if ( GetTeamInZone(i) == m_iCappingTeam[i] ) + { + bDeteriorateNormally = false; + m_flCapTimeLeft[i] -= flReduction; + + if ( !m_bWarnedOnFinalCap[i] ) + { + // If this the local player's team, warn him + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer ) + { + if ( m_iCappingTeam[i] != TEAM_UNASSIGNED && + pPlayer->GetTeamNumber() != m_iCappingTeam[i] && + GetCapWarningLevel( i ) == CP_WARN_FINALCAP ) + { + // Prevent spam + if ( gpGlobals->curtime > ( m_flLastCapWarningTime[i] + 5 ) ) + { + pPlayer->EmitSound( GetWarnSound( i ) ); + + m_bWarnedOnFinalCap[i] = true; + m_flLastCapWarningTime[i] = gpGlobals->curtime; + } + } + } + } + } + else if ( GetOwningTeam(i) == TEAM_UNASSIGNED && GetTeamInZone(i) != TEAM_UNASSIGNED ) + { + bDeteriorateNormally = false; + m_flCapTimeLeft[i] += flReduction; + } + } + + if ( bDeteriorateNormally ) + { + // Caps deteriorate over time + // If we're not cappable at all right now, wipe all progress + if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->TeamMayCapturePoint(m_iCappingTeam[i],i) ) + { + float flCapLength = m_flTeamCapTime[ TEAM_ARRAY(i,m_iCappingTeam[i]) ]; + float flDecrease = (flCapLength / mp_capdeteriorate_time.GetFloat()) * (gpGlobals->curtime - m_flCapLastThinkTime[i]); + if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->InOvertime() ) + { + flDecrease *= 6; + } + m_flCapTimeLeft[i] += flDecrease; + } + else + { + m_flCapTimeLeft[i] = 0.0; + } + + m_bWarnedOnFinalCap[i] = false; + } + } + + UpdateControlPoint( "controlpoint_updatelayout", i ); + m_flCapLastThinkTime[i] = gpGlobals->curtime; + } + } + + + SetNextClientThink( gpGlobals->curtime + RESOURCE_THINK_TIME ); +} diff --git a/game/client/c_team_objectiveresource.h b/game/client/c_team_objectiveresource.h new file mode 100644 index 00000000..950c25b9 --- /dev/null +++ b/game/client/c_team_objectiveresource.h @@ -0,0 +1,265 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef C_TEAM_OBJECTIVERESOURCE_H +#define C_TEAM_OBJECTIVERESOURCE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "shareddefs.h" +#include "const.h" +#include "c_baseentity.h" +#include + +#define TEAM_ARRAY( index, team ) (index + (team * MAX_CONTROL_POINTS)) + +//----------------------------------------------------------------------------- +// Purpose: An entity that networks the state of the game's objectives. +// May contain data for objectives that aren't used by your mod, but +// the extra data will never be networked as long as it's zeroed out. +//----------------------------------------------------------------------------- +class C_BaseTeamObjectiveResource : public C_BaseEntity +{ + DECLARE_CLASS( C_BaseTeamObjectiveResource, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_BaseTeamObjectiveResource(); + virtual ~C_BaseTeamObjectiveResource(); + +public: + virtual void ClientThink(); + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + + void UpdateControlPoint( const char *pszEvent, int index = -1 ); + float GetCPCapPercentage( int index ); + int GetNumControlPoints( void ) { return m_iNumControlPoints; } + int GetNumControlPointsOwned( void ); + void SetOwningTeam( int index, int team ); + virtual void SetCappingTeam( int index, int team ); + void SetCapLayout( const char *pszLayout ); + + // Is the point visible in the objective display + bool IsCPVisible( int index ) + { + Assert( index < m_iNumControlPoints ); + return m_bCPIsVisible[index]; + } + + bool IsCPBlocked( int index ) + { + Assert( index < m_iNumControlPoints ); + return m_bBlocked[index]; + } + + // Get the world location of this control point + Vector& GetCPPosition( int index ) + { + Assert( index < m_iNumControlPoints ); + return m_vCPPositions[index]; + } + + int GetOwningTeam( int index ) + { + if ( index >= m_iNumControlPoints ) + return TEAM_UNASSIGNED; + + return m_iOwner[index]; + } + + int GetCappingTeam( int index ) + { + if ( index >= m_iNumControlPoints ) + return TEAM_UNASSIGNED; + + return m_iCappingTeam[index]; + } + + int GetTeamInZone( int index ) + { + if ( index >= m_iNumControlPoints ) + return TEAM_UNASSIGNED; + + return m_iTeamInZone[index]; + } + + // Icons + int GetCPCurrentOwnerIcon( int index, int iOwner ) + { + Assert( index < m_iNumControlPoints ); + + return GetIconForTeam( index, iOwner ); + } + + int GetCPCappingIcon( int index ) + { + Assert( index < m_iNumControlPoints ); + + int iCapper = GetCappingTeam(index); + + Assert( iCapper != TEAM_UNASSIGNED ); + + return GetIconForTeam( index, iCapper );; + } + + // Icon for the specified team + int GetIconForTeam( int index, int team ) + { + Assert( index < m_iNumControlPoints ); + return m_iTeamIcons[ TEAM_ARRAY(index,team) ]; + } + + // Overlay for the specified team + int GetOverlayForTeam( int index, int team ) + { + Assert( index < m_iNumControlPoints ); + return m_iTeamOverlays[ TEAM_ARRAY(index,team) ]; + } + + // Number of players in the area + int GetNumPlayersInArea( int index, int team ) + { + Assert( index < m_iNumControlPoints ); + return m_iNumTeamMembers[ TEAM_ARRAY(index,team) ]; + } + + // get the required cappers for the passed team + int GetRequiredCappers( int index, int team ) + { + Assert( index < m_iNumControlPoints ); + return m_iTeamReqCappers[ TEAM_ARRAY(index,team) ]; + } + + // Base Icon for the specified team + int GetBaseIconForTeam( int team ) + { + Assert( team < MAX_TEAMS ); + return m_iTeamBaseIcons[ team ]; + } + + int GetBaseControlPointForTeam( int iTeam ) + { + Assert( iTeam < MAX_TEAMS ); + return m_iBaseControlPoints[iTeam]; + } + + int GetPreviousPointForPoint( int index, int team, int iPrevIndex ) + { + Assert( index < m_iNumControlPoints ); + Assert( iPrevIndex >= 0 && iPrevIndex < MAX_PREVIOUS_POINTS ); + int iIntIndex = iPrevIndex + (index * MAX_PREVIOUS_POINTS) + (team * MAX_CONTROL_POINTS * MAX_PREVIOUS_POINTS); + return m_iPreviousPoints[ iIntIndex ]; + } + + bool TeamCanCapPoint( int index, int team ) + { + Assert( index < m_iNumControlPoints ); + return m_bTeamCanCap[ TEAM_ARRAY( index, team ) ]; + } + + const char *GetCapLayoutInHUD( void ) { return m_pszCapLayoutInHUD; } + + bool PlayingMiniRounds( void ){ return m_bPlayingMiniRounds; } + bool IsInMiniRound( int index ) { return m_bInMiniRound[index]; } + + int GetCapWarningLevel( int index ) + { + Assert( index < m_iNumControlPoints ); + return m_iWarnOnCap[index]; + } + + const char *GetWarnSound( int index ) + { + Assert( index < m_iNumControlPoints ); + return m_iszWarnSound[index]; + } + + virtual const char *GetGameSpecificCPCappingSwipe( int index, int iCappingTeam ) + { + // You need to implement this in your game's objective resource. + Assert(0); + return NULL; + } + virtual const char *GetGameSpecificCPBarFG( int index, int iOwningTeam ) + { + // You need to implement this in your game's objective resource. + Assert(0); + return NULL; + } + virtual const char *GetGameSpecificCPBarBG( int index, int iCappingTeam ) + { + // You need to implement this in your game's objective resource. + Assert(0); + return NULL; + } + + bool CapIsBlocked( int index ); + + int GetTimerToShowInHUD( void ) { return m_iTimerToShowInHUD; } + int GetStopWatchTimer( void ) { return m_iStopWatchTimer; } + + float GetPathDistance( int index ) + { + Assert( index < m_iNumControlPoints ); + return m_flPathDistance[index]; + } + +protected: + int m_iTimerToShowInHUD; + int m_iStopWatchTimer; + + int m_iNumControlPoints; + int m_iPrevNumControlPoints; + bool m_bPlayingMiniRounds; + bool m_bControlPointsReset; + bool m_bOldControlPointsReset; + int m_iUpdateCapHudParity; + int m_iOldUpdateCapHudParity; + + // data variables + Vector m_vCPPositions[MAX_CONTROL_POINTS]; + bool m_bCPIsVisible[MAX_CONTROL_POINTS]; + float m_flLazyCapPerc[MAX_CONTROL_POINTS]; + float m_flOldLazyCapPerc[MAX_CONTROL_POINTS]; + int m_iTeamIcons[MAX_CONTROL_POINTS * MAX_CONTROL_POINT_TEAMS]; + int m_iTeamOverlays[MAX_CONTROL_POINTS * MAX_CONTROL_POINT_TEAMS]; + int m_iTeamReqCappers[MAX_CONTROL_POINTS * MAX_CONTROL_POINT_TEAMS]; + float m_flTeamCapTime[MAX_CONTROL_POINTS * MAX_CONTROL_POINT_TEAMS]; + int m_iPreviousPoints[ MAX_CONTROL_POINTS * MAX_CONTROL_POINT_TEAMS * MAX_PREVIOUS_POINTS ]; + bool m_bTeamCanCap[ MAX_CONTROL_POINTS * MAX_CONTROL_POINT_TEAMS ]; + int m_iTeamBaseIcons[MAX_TEAMS]; + int m_iBaseControlPoints[MAX_TEAMS]; + bool m_bInMiniRound[MAX_CONTROL_POINTS]; + int m_iWarnOnCap[MAX_CONTROL_POINTS]; + char m_iszWarnSound[MAX_CONTROL_POINTS][255]; + float m_flPathDistance[MAX_CONTROL_POINTS]; + + // state variables + int m_iNumTeamMembers[MAX_CONTROL_POINTS * MAX_CONTROL_POINT_TEAMS]; + int m_iCappingTeam[MAX_CONTROL_POINTS]; + int m_iTeamInZone[MAX_CONTROL_POINTS]; + bool m_bBlocked[MAX_CONTROL_POINTS]; + int m_iOwner[MAX_CONTROL_POINTS]; + + // client calculated state + float m_flCapTimeLeft[MAX_CONTROL_POINTS]; + float m_flCapLastThinkTime[MAX_CONTROL_POINTS]; + + bool m_bWarnedOnFinalCap[MAX_CONTROL_POINTS]; + float m_flLastCapWarningTime[MAX_CONTROL_POINTS]; + char m_pszCapLayoutInHUD[MAX_CAPLAYOUT_LENGTH]; +}; + +extern C_BaseTeamObjectiveResource *g_pObjectiveResource; + +inline C_BaseTeamObjectiveResource *ObjectiveResource() +{ + return g_pObjectiveResource; +} + +#endif // C_TEAM_OBJECTIVERESOURCE_H diff --git a/game/client/c_team_train_watcher.cpp b/game/client/c_team_train_watcher.cpp new file mode 100644 index 00000000..61786a73 --- /dev/null +++ b/game/client/c_team_train_watcher.cpp @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: Entity that propagates train data for escort gametype +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_team_train_watcher.h" +#include "igameevents.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_TeamTrainWatcher, DT_TeamTrainWatcher, CTeamTrainWatcher) + + RecvPropFloat( RECVINFO( m_flTotalProgress ) ), + RecvPropInt( RECVINFO( m_iTrainSpeedLevel ) ), + RecvPropFloat( RECVINFO( m_flRecedeTime ) ), + RecvPropInt( RECVINFO( m_nNumCappers ) ), + +END_RECV_TABLE() + +C_TeamTrainWatcher *g_pTrainWatcher = NULL; + +C_TeamTrainWatcher::C_TeamTrainWatcher() +{ + g_pTrainWatcher = this; + + // force updates when we get our baseline + m_iTrainSpeedLevel = -2; + m_flTotalProgress = -1; + m_flRecedeTime = -1; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_TeamTrainWatcher::~C_TeamTrainWatcher() +{ + if ( g_pTrainWatcher == this ) + { + g_pTrainWatcher = NULL; + } +} + +void C_TeamTrainWatcher::OnPreDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnPreDataChanged( updateType ); + + m_iOldTrainSpeedLevel = m_iTrainSpeedLevel; + m_flOldProgress = m_flTotalProgress; + m_flOldRecedeTime = m_flRecedeTime; + m_nOldNumCappers = m_nNumCappers; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TeamTrainWatcher::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( m_iOldTrainSpeedLevel != m_iTrainSpeedLevel || m_nOldNumCappers != m_nNumCappers ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "escort_speed" ); + if ( event ) + { + event->SetInt( "speed", m_iTrainSpeedLevel ); + event->SetInt( "players", m_nNumCappers ); + gameeventmanager->FireEventClientSide( event ); + } + } + + if ( m_flOldProgress != m_flTotalProgress ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "escort_progress" ); + if ( event ) + { + event->SetFloat( "progress", m_flTotalProgress ); + + if ( m_flOldProgress <= -1 ) + { + event->SetBool( "reset", true ); + } + + gameeventmanager->FireEventClientSide( event ); + } + } + + if ( m_flOldRecedeTime != m_flRecedeTime ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "escort_recede" ); + if ( event ) + { + event->SetFloat( "recedetime", m_flRecedeTime ); + gameeventmanager->FireEventClientSide( event ); + } + } +} \ No newline at end of file diff --git a/game/client/c_team_train_watcher.h b/game/client/c_team_train_watcher.h new file mode 100644 index 00000000..cd666b2c --- /dev/null +++ b/game/client/c_team_train_watcher.h @@ -0,0 +1,59 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef C_TEAM_TRAIN_WATCHER_H +#define C_TEAM_TRAIN_WATCHER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" + +class C_TeamTrainWatcher : public C_BaseEntity +{ + DECLARE_CLASS( C_TeamTrainWatcher, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_TeamTrainWatcher(); + ~C_TeamTrainWatcher(); + + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + + float GetTotalProgress( void ) { return m_flTotalProgress; } + int GetSpeedLevel( void ) { return m_iTrainSpeedLevel; } + +private: + + // === Networked Data === + + // percent distance to cp1, distance to cp2, etc + // adds up to 1.0 + //CNetworkArray( float, m_flDistances, MAX_CONTROL_POINTS ); + + // current total progress, percentage + CNetworkVar( float, m_flTotalProgress ); + float m_flOldProgress; + + CNetworkVar( int, m_iTrainSpeedLevel ); + int m_iOldTrainSpeedLevel; + + CNetworkVar( float, m_flRecedeTime ); + float m_flOldRecedeTime; + + CNetworkVar( int, m_nNumCappers ); + int m_nOldNumCappers; +}; + +extern C_TeamTrainWatcher *g_pTrainWatcher; + +inline C_TeamTrainWatcher *TrainWatcher() +{ + return g_pTrainWatcher; +} + +#endif //C_TEAM_TRAIN_WATCHER_H \ No newline at end of file diff --git a/game/client/c_tesla.cpp b/game/client/c_tesla.cpp new file mode 100644 index 00000000..b8241328 --- /dev/null +++ b/game/client/c_tesla.cpp @@ -0,0 +1,65 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "c_tesla.h" +#include "fx.h" +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT( C_Tesla, DT_Tesla, CTesla ) + RecvPropString( RECVINFO( m_SoundName ) ), + RecvPropString( RECVINFO( m_iszSpriteName ) ) +END_RECV_TABLE() + + +C_Tesla::C_Tesla() +{ +} + +void C_Tesla::ReceiveMessage( int classID, bf_read &msg ) +{ + CTeslaInfo teslaInfo; + + msg.ReadBitVec3Coord( teslaInfo.m_vPos ); + teslaInfo.m_vAngles.Init(); + + teslaInfo.m_nEntIndex = msg.ReadShort(); + teslaInfo.m_flRadius = msg.ReadFloat(); + + teslaInfo.m_vColor.x = ((unsigned char)msg.ReadChar()) / 255.0f; + teslaInfo.m_vColor.y = ((unsigned char)msg.ReadChar()) / 255.0f; + teslaInfo.m_vColor.z = ((unsigned char)msg.ReadChar()) / 255.0f; + + float flAlpha = 0; + flAlpha = ((unsigned char)msg.ReadChar()) / 255.0f; + + teslaInfo.m_nBeams = msg.ReadChar(); + + teslaInfo.m_flBeamWidth = msg.ReadFloat(); + teslaInfo.m_flTimeVisible = msg.ReadFloat(); + teslaInfo.m_pszSpriteName = m_iszSpriteName; + + EmitSound( m_SoundName ); + + m_QueuedCommands.AddToTail( teslaInfo ); + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + + +void C_Tesla::ClientThink() +{ + FOR_EACH_LL( m_QueuedCommands, i ) + { + FX_Tesla( m_QueuedCommands[i] ); + } + m_QueuedCommands.Purge(); + SetNextClientThink( CLIENT_THINK_NEVER ); +} + + diff --git a/game/client/c_tesla.h b/game/client/c_tesla.h new file mode 100644 index 00000000..cd6f19ab --- /dev/null +++ b/game/client/c_tesla.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_TESLA_H +#define C_TESLA_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "c_baseentity.h" +#include "fx.h" +#include "utllinkedlist.h" + + +class C_Tesla : public C_BaseEntity +{ +public: + + DECLARE_CLASS( C_Tesla, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_Tesla(); + + virtual void ReceiveMessage( int classID, bf_read &msg ); + virtual void ClientThink(); + + +public: + + CUtlLinkedList m_QueuedCommands; + char m_SoundName[64]; + char m_iszSpriteName[256]; +}; + + +#endif // C_TESLA_H diff --git a/game/client/c_test_proxytoggle.cpp b/game/client/c_test_proxytoggle.cpp new file mode 100644 index 00000000..023a9b48 --- /dev/null +++ b/game/client/c_test_proxytoggle.cpp @@ -0,0 +1,80 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_baseentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_Test_ProxyToggle_Networkable; +static C_Test_ProxyToggle_Networkable *g_pTestObj = 0; + + +// ---------------------------------------------------------------------------------------- // +// C_Test_ProxyToggle_Networkable +// ---------------------------------------------------------------------------------------- // + +class C_Test_ProxyToggle_Networkable : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_Test_ProxyToggle_Networkable, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_Test_ProxyToggle_Networkable() + { + g_pTestObj = this; + } + + ~C_Test_ProxyToggle_Networkable() + { + g_pTestObj = 0; + } + + int m_WithProxy; +}; + + +// ---------------------------------------------------------------------------------------- // +// Datatables. +// ---------------------------------------------------------------------------------------- // + +BEGIN_RECV_TABLE_NOBASE( C_Test_ProxyToggle_Networkable, DT_ProxyToggle_ProxiedData ) + RecvPropInt( RECVINFO( m_WithProxy ) ) +END_RECV_TABLE() + +IMPLEMENT_CLIENTCLASS_DT( C_Test_ProxyToggle_Networkable, DT_ProxyToggle, CTest_ProxyToggle_Networkable ) + RecvPropDataTable( "blah", 0, 0, &REFERENCE_RECV_TABLE( DT_ProxyToggle_ProxiedData ) ) +END_RECV_TABLE() + + + +// ---------------------------------------------------------------------------------------- // +// Console commands. +// ---------------------------------------------------------------------------------------- // + +// The engine uses this to get the current value. +CON_COMMAND_F( Test_ProxyToggle_EnsureValue, "Test_ProxyToggle_EnsureValue", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) + { + Error( "Test_ProxyToggle_EnsureValue: requires value parameter." ); + } + else if ( !g_pTestObj ) + { + Error( "Test_ProxyToggle_EnsureValue: object doesn't exist on the client." ); + } + + int wantedValue = atoi( args[ 1 ] ); + if ( g_pTestObj->m_WithProxy != wantedValue ) + { + Error( "Test_ProxyToggle_EnsureValue: value (%d) doesn't match wanted value (%d).", g_pTestObj->m_WithProxy, wantedValue ); + } +} + + + + diff --git a/game/client/c_testtraceline.cpp b/game/client/c_testtraceline.cpp new file mode 100644 index 00000000..cdbef9a0 --- /dev/null +++ b/game/client/c_testtraceline.cpp @@ -0,0 +1,181 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "materialsystem/IMesh.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// -------------------------------------------------------------------------------- // +// An entity used to test traceline +// -------------------------------------------------------------------------------- // +class C_TestTraceline : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_TestTraceline, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_TestTraceline(); + virtual ~C_TestTraceline(); + +// IClientEntity overrides. +public: + virtual int DrawModel( int flags ); + virtual bool ShouldDraw() { return true; } + +private: + void DrawCube( Vector& center, unsigned char* pColor ); + IMaterial* m_pWireframe; +}; + +// Expose it to the engine. +IMPLEMENT_CLIENTCLASS(C_TestTraceline, DT_TestTraceline, CTestTraceline); + +BEGIN_RECV_TABLE_NOBASE(C_TestTraceline, DT_TestTraceline) + RecvPropInt(RECVINFO(m_clrRender)), + RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), + RecvPropFloat( RECVINFO_NAME( m_angNetworkAngles[0], m_angRotation[0] ) ), + RecvPropFloat( RECVINFO_NAME( m_angNetworkAngles[1], m_angRotation[1] ) ), + RecvPropFloat( RECVINFO_NAME( m_angNetworkAngles[2], m_angRotation[2] ) ), + RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ), +END_RECV_TABLE() + + +// -------------------------------------------------------------------------------- // +// Functions. +// -------------------------------------------------------------------------------- // + +C_TestTraceline::C_TestTraceline() +{ + m_pWireframe = materials->FindMaterial("shadertest/wireframevertexcolor", TEXTURE_GROUP_OTHER); +} + +C_TestTraceline::~C_TestTraceline() +{ +} + + +enum +{ + CUBE_SIZE = 5 +}; + +void C_TestTraceline::DrawCube( Vector& center, unsigned char* pColor ) +{ + Vector facePoints[8]; + Vector bmins, bmaxs; + + bmins[0] = center[0] - CUBE_SIZE; + bmins[1] = center[1] - CUBE_SIZE; + bmins[2] = center[2] - CUBE_SIZE; + + bmaxs[0] = center[0] + CUBE_SIZE; + bmaxs[1] = center[1] + CUBE_SIZE; + bmaxs[2] = center[2] + CUBE_SIZE; + + facePoints[0][0] = bmins[0]; + facePoints[0][1] = bmins[1]; + facePoints[0][2] = bmins[2]; + + facePoints[1][0] = bmins[0]; + facePoints[1][1] = bmins[1]; + facePoints[1][2] = bmaxs[2]; + + facePoints[2][0] = bmins[0]; + facePoints[2][1] = bmaxs[1]; + facePoints[2][2] = bmins[2]; + + facePoints[3][0] = bmins[0]; + facePoints[3][1] = bmaxs[1]; + facePoints[3][2] = bmaxs[2]; + + facePoints[4][0] = bmaxs[0]; + facePoints[4][1] = bmins[1]; + facePoints[4][2] = bmins[2]; + + facePoints[5][0] = bmaxs[0]; + facePoints[5][1] = bmins[1]; + facePoints[5][2] = bmaxs[2]; + + facePoints[6][0] = bmaxs[0]; + facePoints[6][1] = bmaxs[1]; + facePoints[6][2] = bmins[2]; + + facePoints[7][0] = bmaxs[0]; + facePoints[7][1] = bmaxs[1]; + facePoints[7][2] = bmaxs[2]; + + int nFaces[6][4] = + { + { 0, 2, 3, 1 }, + { 0, 1, 5, 4 }, + { 4, 5, 7, 6 }, + { 2, 6, 7, 3 }, + { 1, 3, 7, 5 }, + { 0, 4, 6, 2 } + }; + + for (int nFace = 0; nFace < 6; nFace++) + { + int nP1, nP2, nP3, nP4; + + nP1 = nFaces[nFace][0]; + nP2 = nFaces[nFace][1]; + nP3 = nFaces[nFace][2]; + nP4 = nFaces[nFace][3]; + + // Draw the face. + CMeshBuilder meshBuilder; + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh(); + meshBuilder.DrawQuad( pMesh, facePoints[nP1].Base(), facePoints[nP2].Base(), + facePoints[nP3].Base(), facePoints[nP4].Base(), pColor, true ); + } +} + +int C_TestTraceline::DrawModel( int flags ) +{ + trace_t tr; + Vector forward, right, up, endpos, hitpos; + AngleVectors (GetAbsAngles(), &forward, &right, &up); + endpos = GetAbsOrigin() + forward * MAX_TRACE_LENGTH; + + UTIL_TraceLine( GetAbsOrigin(), endpos, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + CMatRenderContextPtr pRenderContext( materials ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_pWireframe ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 1 ); + + meshBuilder.Position3fv( GetAbsOrigin().Base() ); + meshBuilder.Color3ub( 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( tr.endpos.Base() ); + meshBuilder.Color3ub( 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + + // Didn't hit anything + if ( tr.fraction != 1.0 ) + { + unsigned char color[] = { 0, 255, 0 }; + DrawCube( tr.endpos, color ); + } + + if ( (!tr.allsolid) && (tr.fractionleftsolid != 0.0) ) + { + unsigned char color[] = { 255, 0, 0 }; + DrawCube( tr.startpos, color ); + } + + return 1; +} diff --git a/game/client/c_tracer.cpp b/game/client/c_tracer.cpp new file mode 100644 index 00000000..4a6d2251 --- /dev/null +++ b/game/client/c_tracer.cpp @@ -0,0 +1,152 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "particledraw.h" +#include "materialsystem/IMesh.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Sees if the tracer is behind the camera or should be culled +//----------------------------------------------------------------------------- + +static bool ClipTracer( const Vector &start, const Vector &delta, Vector &clippedStart, Vector &clippedDelta ) +{ + // dist1 = start dot forward - origin dot forward + // dist2 = (start + delta ) dot forward - origin dot forward + // in camera space this is -start[2] since origin = 0 and vecForward = (0, 0, -1) + float dist1 = -start[2]; + float dist2 = dist1 - delta[2]; + + // Clipped, skip this tracer + if ( dist1 <= 0 && dist2 <= 0 ) + return true; + + clippedStart = start; + clippedDelta = delta; + + // Needs to be clipped + if ( dist1 <= 0 || dist2 <= 0 ) + { + float fraction = dist2 - dist1; + + // Too close to clipping plane + if ( fraction < 1e-3 && fraction > -1e-3 ) + return true; + + fraction = -dist1 / fraction; + + if ( dist1 <= 0 ) + { + VectorMA( start, fraction, delta, clippedStart ); + } + else + { + VectorMultiply( delta, fraction, clippedDelta ); + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Computes the four verts to draw the tracer with +//----------------------------------------------------------------------------- +bool Tracer_ComputeVerts( const Vector &start, const Vector &delta, float width, Vector *pVerts ) +{ + Vector clippedStart, clippedDelta; + + // Clip the tracer + if ( ClipTracer( start, delta, clippedStart, clippedDelta ) ) + return false; + + // Figure out direction in camera space of the normal + Vector normal; + CrossProduct( clippedDelta, clippedStart, normal ); + + // don't draw if they are parallel + float sqLength = DotProduct( normal, normal ); + if (sqLength < 1e-3) + return false; + + // Resize the normal to be appropriate based on the width + VectorScale( normal, 0.5f * width / sqrt(sqLength), normal ); + + VectorSubtract( clippedStart, normal, pVerts[0] ); + VectorAdd( clippedStart, normal, pVerts[1] ); + + VectorAdd( pVerts[0], clippedDelta, pVerts[2] ); + VectorAdd( pVerts[1], clippedDelta, pVerts[3] ); + + return true; +} + + +void Tracer_Draw( CMeshBuilder *pMeshBuilder, Vector& start, Vector& delta, float width, float* color, float startV, float endV ) +{ + // Clip the tracer + Vector verts[4]; + if (!Tracer_ComputeVerts( start, delta, width, verts )) + return; + + // NOTE: Gotta get the winding right so it's not backface culled + // (we need to turn of backface culling for these bad boys) + pMeshBuilder->Position3f( verts[0].x, verts[0].y, verts[0].z ); + pMeshBuilder->TexCoord2f( 0, 0.0f, startV ); + if (color) + { + pMeshBuilder->Color4fv( color ); + } + else + { + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + } + pMeshBuilder->AdvanceVertex(); + + pMeshBuilder->Position3f( verts[1].x, verts[1].y, verts[1].z ); + pMeshBuilder->TexCoord2f( 0, 1.0f, startV ); + if (color) + { + pMeshBuilder->Color4fv( color ); + } + else + { + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + } + pMeshBuilder->AdvanceVertex(); + + pMeshBuilder->Position3f( verts[3].x, verts[3].y, verts[3].z ); + pMeshBuilder->TexCoord2f( 0, 1.0f, endV ); + if (color) + pMeshBuilder->Color4fv( color ); + else + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->AdvanceVertex(); + + pMeshBuilder->Position3f( verts[2].x, verts[2].y, verts[2].z ); + pMeshBuilder->TexCoord2f( 0, 0.0f, endV ); + if (color) + pMeshBuilder->Color4fv( color ); + else + pMeshBuilder->Color4ub( 255, 255, 255, 255 ); + pMeshBuilder->AdvanceVertex(); +} + + +//----------------------------------------------------------------------------- +// draw a tracer. +//----------------------------------------------------------------------------- +void Tracer_Draw( ParticleDraw* pDraw, Vector& start, Vector& delta, float width, float* color, float startV, float endV ) +{ + if( !pDraw->GetMeshBuilder() ) + return; + + Tracer_Draw( pDraw->GetMeshBuilder(), start, delta, width, color, startV, endV ); +} diff --git a/game/client/c_tracer.h b/game/client/c_tracer.h new file mode 100644 index 00000000..2e571f39 --- /dev/null +++ b/game/client/c_tracer.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef C_TRACER_H +#define C_TRACER_H + +class Vector; +class ParticleDraw; +class CMeshBuilder; + +//----------------------------------------------------------------------------- +// Tracer_Draw(): draws a tracer, assuming the modelview matrix is identity +// This function accepts all arguments in CAMERA (pre-projected) space +// +// arguments +// [in] Vector& : The origin of the tracer (CAMERA space) +// [in] Vector& : The direction and length of the tracer (CAMERA space) +// [in] float : The tracer width (CAMERA space) +// [in] float* : r, g, b, a (0 - 1) +//----------------------------------------------------------------------------- +void Tracer_Draw( ParticleDraw* pDraw, Vector& start, Vector& delta, + float width, float* color, float startV = 0.0, float endV = 1.0 ); + +void Tracer_Draw( CMeshBuilder *pMeshBuilder, Vector& start, Vector& delta, float width, float* color, float startV = 0.0, float endV = 1.0 ); + + +//----------------------------------------------------------------------------- +// Computes the four verts to draw the tracer with, in the following order: +// start vertex left side, start vertex right side +// end vertex left side, end vertex right side +// returne false if the tracer is offscreen +//----------------------------------------------------------------------------- +bool Tracer_ComputeVerts( const Vector &start, const Vector &delta, float width, Vector *pVerts ); + +#endif // C_TRACER_H \ No newline at end of file diff --git a/game/client/c_user_message_register.cpp b/game/client/c_user_message_register.cpp new file mode 100644 index 00000000..c8b5f173 --- /dev/null +++ b/game/client/c_user_message_register.cpp @@ -0,0 +1,36 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "c_user_message_register.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CUserMessageRegister *CUserMessageRegister::s_pHead = NULL; + + +CUserMessageRegister::CUserMessageRegister( const char *pMessageName, pfnUserMsgHook pHookFn ) +{ + m_pMessageName = pMessageName; + m_pHookFn = pHookFn; + + // Link it in. + m_pNext = s_pHead; + s_pHead = this; +} + + +void CUserMessageRegister::RegisterAll() +{ + for ( CUserMessageRegister *pCur=s_pHead; pCur; pCur=pCur->m_pNext ) + { + usermessages->HookMessage( pCur->m_pMessageName, pCur->m_pHookFn ); + } +} + + + diff --git a/game/client/c_user_message_register.h b/game/client/c_user_message_register.h new file mode 100644 index 00000000..757061bb --- /dev/null +++ b/game/client/c_user_message_register.h @@ -0,0 +1,42 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef C_USER_MESSAGE_REGISTER_H +#define C_USER_MESSAGE_REGISTER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "usermessages.h" + +// This provides an alternative to HOOK_MESSAGE, where you can declare it globally +// instead of finding a place to run it. +// It registers a function called __MsgFunc_ +#define USER_MESSAGE_REGISTER( msgName ) \ + static CUserMessageRegister userMessageRegister_##msgName( #msgName, __MsgFunc_##msgName ); + + +class CUserMessageRegister +{ +public: + CUserMessageRegister( const char *pMessageName, pfnUserMsgHook pHookFn ); + + // This is called at startup to register all the user messages. + static void RegisterAll(); + + +private: + const char *m_pMessageName; + pfnUserMsgHook m_pHookFn; + + // Linked list of all the CUserMessageRegisters. + static CUserMessageRegister *s_pHead; + CUserMessageRegister *m_pNext; +}; + + +#endif // C_USER_MESSAGE_REGISTER_H diff --git a/game/client/c_vehicle_choreo_generic.cpp b/game/client/c_vehicle_choreo_generic.cpp new file mode 100644 index 00000000..6b680335 --- /dev/null +++ b/game/client/c_vehicle_choreo_generic.cpp @@ -0,0 +1,244 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "hud.h" +#include "c_props.h" +#include "IClientVehicle.h" +#include +#include +#include "vehicle_choreo_generic_shared.h" +#include "vehicle_viewblend_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern float RemapAngleRange( float startInterval, float endInterval, float value ); + + +#define ROLL_CURVE_ZERO 5 // roll less than this is clamped to zero +#define ROLL_CURVE_LINEAR 45 // roll greater than this is copied out + +#define PITCH_CURVE_ZERO 10 // pitch less than this is clamped to zero +#define PITCH_CURVE_LINEAR 45 // pitch greater than this is copied out + // spline in between + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_PropVehicleChoreoGeneric : public C_DynamicProp, public IClientVehicle +{ + DECLARE_CLASS( C_PropVehicleChoreoGeneric, C_DynamicProp ); + +public: + + DECLARE_CLIENTCLASS(); + DECLARE_DATADESC(); + + C_PropVehicleChoreoGeneric(); + + void PreDataUpdate( DataUpdateType_t updateType ); + void PostDataUpdate( DataUpdateType_t updateType ); + +public: + + // IClientVehicle overrides. + virtual void GetVehicleViewPosition( int nRole, Vector *pOrigin, QAngle *pAngles, float *pFOV = NULL ); + virtual void GetVehicleFOV( float &flFOV ) + { + flFOV = m_flFOV; + } + virtual void DrawHudElements(); + virtual bool IsPassengerUsingStandardWeapons( int nRole = VEHICLE_ROLE_DRIVER ) { return false; } + virtual void UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ); + virtual C_BaseCombatCharacter *GetPassenger( int nRole ); + virtual int GetPassengerRole( C_BaseCombatCharacter *pPassenger ); + virtual void GetVehicleClipPlanes( float &flZNear, float &flZFar ) const; + virtual int GetPrimaryAmmoType() const { return -1; } + virtual int GetPrimaryAmmoCount() const { return -1; } + virtual int GetPrimaryAmmoClip() const { return -1; } + virtual bool PrimaryAmmoUsesClips() const { return false; } + virtual int GetJoystickResponseCurve() const { return 0; } + +public: + + // C_BaseEntity overrides. + virtual IClientVehicle* GetClientVehicle() { return this; } + virtual C_BaseEntity *GetVehicleEnt() { return this; } + virtual void SetupMove( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) {} + virtual void ProcessMovement( C_BasePlayer *pPlayer, CMoveData *pMoveData ) {} + virtual void FinishMove( C_BasePlayer *player, CUserCmd *ucmd, CMoveData *move ) {} + virtual bool IsPredicted() const { return false; } + virtual void ItemPostFrame( C_BasePlayer *pPlayer ) {} + virtual bool IsSelfAnimating() { return false; }; + +private: + + CHandle m_hPlayer; + CHandle m_hPrevPlayer; + + bool m_bEnterAnimOn; + bool m_bExitAnimOn; + Vector m_vecEyeExitEndpoint; + float m_flFOV; // The current FOV (changes during entry/exit anims). + + ViewSmoothingData_t m_ViewSmoothingData; + + vehicleview_t m_vehicleView; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_PropVehicleChoreoGeneric, DT_PropVehicleChoreoGeneric, CPropVehicleChoreoGeneric) + RecvPropEHandle( RECVINFO(m_hPlayer) ), + RecvPropBool( RECVINFO( m_bEnterAnimOn ) ), + RecvPropBool( RECVINFO( m_bExitAnimOn ) ), + RecvPropVector( RECVINFO( m_vecEyeExitEndpoint ) ), + RecvPropBool( RECVINFO( m_vehicleView.bClampEyeAngles ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flPitchCurveZero ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flPitchCurveLinear ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flRollCurveZero ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flRollCurveLinear ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flFOV ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flYawMin ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flYawMax ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flPitchMin ) ), + RecvPropFloat( RECVINFO( m_vehicleView.flPitchMax ) ), +END_RECV_TABLE() + + +BEGIN_DATADESC( C_PropVehicleChoreoGeneric ) + DEFINE_EMBEDDED( m_ViewSmoothingData ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_PropVehicleChoreoGeneric::C_PropVehicleChoreoGeneric( void ) +{ + memset( &m_ViewSmoothingData, 0, sizeof( m_ViewSmoothingData ) ); + + m_ViewSmoothingData.pVehicle = this; + m_ViewSmoothingData.flPitchCurveZero = PITCH_CURVE_ZERO; + m_ViewSmoothingData.flPitchCurveLinear = PITCH_CURVE_LINEAR; + m_ViewSmoothingData.flRollCurveZero = ROLL_CURVE_ZERO; + m_ViewSmoothingData.flRollCurveLinear = ROLL_CURVE_LINEAR; + m_flFOV = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_PropVehicleChoreoGeneric::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); + + m_hPrevPlayer = m_hPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropVehicleChoreoGeneric::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + if ( !m_hPlayer && m_hPrevPlayer ) + { + // They have just exited the vehicle. + // Sometimes we never reach the end of our exit anim, such as if the + // animation doesn't have fadeout 0 specified in the QC, so we fail to + // catch it in VehicleViewSmoothing. Catch it here instead. + m_ViewSmoothingData.bWasRunningAnim = false; + + //There's no need to "smooth" the view when leaving the vehicle so just set this here so the stair code doesn't get confused. + m_hPrevPlayer->SetOldPlayerZ( m_hPrevPlayer->GetLocalOrigin().z ); + } + + m_ViewSmoothingData.bClampEyeAngles = m_vehicleView.bClampEyeAngles; + m_ViewSmoothingData.flFOV = m_vehicleView.flFOV; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_BaseCombatCharacter *C_PropVehicleChoreoGeneric::GetPassenger( int nRole ) +{ + if ( nRole == VEHICLE_ROLE_DRIVER ) + return m_hPlayer.Get(); + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Returns the role of the passenger +//----------------------------------------------------------------------------- +int C_PropVehicleChoreoGeneric::GetPassengerRole( C_BaseCombatCharacter *pPassenger ) +{ + if ( m_hPlayer.Get() == pPassenger ) + return VEHICLE_ROLE_DRIVER; + + return VEHICLE_ROLE_NONE; +} + + +//----------------------------------------------------------------------------- +// Purpose: Modify the player view/camera while in a vehicle +//----------------------------------------------------------------------------- +void C_PropVehicleChoreoGeneric::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles, float *pFOV /*=NULL*/ ) +{ + SharedVehicleViewSmoothing( m_hPlayer, + pAbsOrigin, pAbsAngles, + m_bEnterAnimOn, m_bExitAnimOn, + m_vecEyeExitEndpoint, + &m_ViewSmoothingData, + pFOV ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pLocalPlayer - +// pCmd - +//----------------------------------------------------------------------------- +void C_PropVehicleChoreoGeneric::UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ) +{ + int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); + Vector vehicleEyeOrigin; + QAngle vehicleEyeAngles; + GetAttachmentLocal( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); + + // Limit the yaw. + float flAngleDiff = AngleDiff( pCmd->viewangles.y, vehicleEyeAngles.y ); + flAngleDiff = clamp( flAngleDiff, m_vehicleView.flYawMin, m_vehicleView.flYawMax ); + pCmd->viewangles.y = vehicleEyeAngles.y + flAngleDiff; + + // Limit the pitch -- don't let them look down into the empty pod! + flAngleDiff = AngleDiff( pCmd->viewangles.x, vehicleEyeAngles.x ); + flAngleDiff = clamp( flAngleDiff, m_vehicleView.flPitchMin, m_vehicleView.flPitchMax ); + pCmd->viewangles.x = vehicleEyeAngles.x + flAngleDiff; +} + + +//----------------------------------------------------------------------------- +// Futzes with the clip planes +//----------------------------------------------------------------------------- +void C_PropVehicleChoreoGeneric::GetVehicleClipPlanes( float &flZNear, float &flZFar ) const +{ + // Pod doesn't need to adjust the clip planes. + //flZNear = 6; +} + + +//----------------------------------------------------------------------------- +// Renders hud elements +//----------------------------------------------------------------------------- +void C_PropVehicleChoreoGeneric::DrawHudElements( ) +{ +} + + diff --git a/game/client/c_vehicle_jeep.cpp b/game/client/c_vehicle_jeep.cpp new file mode 100644 index 00000000..52d809ec --- /dev/null +++ b/game/client/c_vehicle_jeep.cpp @@ -0,0 +1,324 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "c_vehicle_jeep.h" +#include "movevars_shared.h" +#include "view.h" +#include "flashlighteffect.h" +#include "c_baseplayer.h" +#include "c_te_effect_dispatch.h" +#include "fx.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar default_fov; + +ConVar r_JeepViewBlendTo( "r_JeepViewBlendTo", "1", FCVAR_CHEAT ); +ConVar r_JeepViewBlendToScale( "r_JeepViewBlendToScale", "0.03", FCVAR_CHEAT ); +ConVar r_JeepViewBlendToTime( "r_JeepViewBlendToTime", "1.5", FCVAR_CHEAT ); + +#define JEEP_DELTA_LENGTH_MAX 12.0f // 1 foot +#define JEEP_FRAMETIME_MIN 1e-6 +#define JEEP_HEADLIGHT_DISTANCE 1000 + +IMPLEMENT_CLIENTCLASS_DT( C_PropJeep, DT_PropJeep, CPropJeep ) + RecvPropBool( RECVINFO( m_bHeadlightIsOn ) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +C_PropJeep::C_PropJeep() +{ + m_vecEyeSpeed.Init(); + m_flViewAngleDeltaTime = 0.0f; + m_pHeadlight = NULL; + + ConVarRef r_JeepFOV( "r_JeepFOV" ); + m_ViewSmoothingData.flFOV = r_JeepFOV.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: Deconstructor +//----------------------------------------------------------------------------- +C_PropJeep::~C_PropJeep() +{ + if ( m_pHeadlight ) + { + delete m_pHeadlight; + } +} + +void C_PropJeep::Simulate( void ) +{ + // The dim light is the flashlight. + if ( m_bHeadlightIsOn ) + { + if ( m_pHeadlight == NULL ) + { + // Turned on the headlight; create it. + m_pHeadlight = new CHeadlightEffect; + + if ( m_pHeadlight == NULL ) + return; + + m_pHeadlight->TurnOn(); + } + + QAngle vAngle; + Vector vVector; + Vector vecForward, vecRight, vecUp; + + int iAttachment = LookupAttachment( "headlight" ); + + if ( iAttachment != -1 ) + { + GetAttachment( iAttachment, vVector, vAngle ); + AngleVectors( vAngle, &vecForward, &vecRight, &vecUp ); + + m_pHeadlight->UpdateLight( vVector, vecForward, vecRight, vecUp, JEEP_HEADLIGHT_DISTANCE ); + } + } + else if ( m_pHeadlight ) + { + // Turned off the flashlight; delete it. + delete m_pHeadlight; + m_pHeadlight = NULL; + } + + BaseClass::Simulate(); +} + +//----------------------------------------------------------------------------- +// Purpose: Blend view angles. +//----------------------------------------------------------------------------- +void C_PropJeep::UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ) +{ + if ( r_JeepViewBlendTo.GetInt() ) + { + // Check to see if the mouse has been touched in a bit or that we are not throttling. + if ( ( pCmd->mousedx != 0 || pCmd->mousedy != 0 ) || ( fabsf( m_flThrottle ) < 0.01f ) ) + { + m_flViewAngleDeltaTime = 0.0f; + } + else + { + m_flViewAngleDeltaTime += gpGlobals->frametime; + } + + if ( m_flViewAngleDeltaTime > r_JeepViewBlendToTime.GetFloat() ) + { + // Blend the view angles. + int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); + Vector vehicleEyeOrigin; + QAngle vehicleEyeAngles; + GetAttachmentLocal( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); + + QAngle outAngles; + InterpolateAngles( pCmd->viewangles, vehicleEyeAngles, outAngles, r_JeepViewBlendToScale.GetFloat() ); + pCmd->viewangles = outAngles; + } + } + + BaseClass::UpdateViewAngles( pLocalPlayer, pCmd ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropJeep::DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ) +{ + // Get the frametime. (Check to see if enough time has passed to warrent dampening). + float flFrameTime = gpGlobals->frametime; + + if ( flFrameTime < JEEP_FRAMETIME_MIN ) + { + vecVehicleEyePos = m_vecLastEyePos; + DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, 0.0f ); + return; + } + + // Keep static the sideways motion. + // Dampen forward/backward motion. + DampenForwardMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime ); + + // Blend up/down motion. + DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime ); +} + + +//----------------------------------------------------------------------------- +// Use the controller as follows: +// speed += ( pCoefficientsOut[0] * ( targetPos - currentPos ) + pCoefficientsOut[1] * ( targetSpeed - currentSpeed ) ) * flDeltaTime; +//----------------------------------------------------------------------------- +void C_PropJeep::ComputePDControllerCoefficients( float *pCoefficientsOut, + float flFrequency, float flDampening, + float flDeltaTime ) +{ + float flKs = 9.0f * flFrequency * flFrequency; + float flKd = 4.5f * flFrequency * flDampening; + + float flScale = 1.0f / ( 1.0f + flKd * flDeltaTime + flKs * flDeltaTime * flDeltaTime ); + + pCoefficientsOut[0] = flKs * flScale; + pCoefficientsOut[1] = ( flKd + flKs * flDeltaTime ) * flScale; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropJeep::DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ) +{ + // vecVehicleEyePos = real eye position this frame + + // m_vecLastEyePos = eye position last frame + // m_vecEyeSpeed = eye speed last frame + // vecPredEyePos = predicted eye position this frame (assuming no acceleration - it will get that from the pd controller). + // vecPredEyeSpeed = predicted eye speed + Vector vecPredEyePos = m_vecLastEyePos + m_vecEyeSpeed * flFrameTime; + Vector vecPredEyeSpeed = m_vecEyeSpeed; + + // m_vecLastEyeTarget = real eye position last frame (used for speed calculation). + // Calculate the approximate speed based on the current vehicle eye position and the eye position last frame. + Vector vecVehicleEyeSpeed = ( vecVehicleEyePos - m_vecLastEyeTarget ) / flFrameTime; + m_vecLastEyeTarget = vecVehicleEyePos; + if (vecVehicleEyeSpeed.Length() == 0.0) + return; + + // Calculate the delta between the predicted eye position and speed and the current eye position and speed. + Vector vecDeltaSpeed = vecVehicleEyeSpeed - vecPredEyeSpeed; + Vector vecDeltaPos = vecVehicleEyePos - vecPredEyePos; + + // Forward vector. + Vector vecForward; + AngleVectors( vecVehicleEyeAngles, &vecForward ); + + float flDeltaLength = vecDeltaPos.Length(); + if ( flDeltaLength > JEEP_DELTA_LENGTH_MAX ) + { + // Clamp. + float flDelta = flDeltaLength - JEEP_DELTA_LENGTH_MAX; + if ( flDelta > 40.0f ) + { + // This part is a bit of a hack to get rid of large deltas (at level load, etc.). + m_vecLastEyePos = vecVehicleEyePos; + m_vecEyeSpeed = vecVehicleEyeSpeed; + } + else + { + // Position clamp. + float flRatio = JEEP_DELTA_LENGTH_MAX / flDeltaLength; + vecDeltaPos *= flRatio; + Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) ); + vecVehicleEyePos -= vecForwardOffset; + m_vecLastEyePos = vecVehicleEyePos; + + // Speed clamp. + vecDeltaSpeed *= flRatio; + float flCoefficients[2]; + ComputePDControllerCoefficients( flCoefficients, r_JeepViewDampenFreq.GetFloat(), r_JeepViewDampenDamp.GetFloat(), flFrameTime ); + m_vecEyeSpeed += ( ( flCoefficients[0] * vecDeltaPos + flCoefficients[1] * vecDeltaSpeed ) * flFrameTime ); + } + } + else + { + // Generate an updated (dampening) speed for use in next frames position prediction. + float flCoefficients[2]; + ComputePDControllerCoefficients( flCoefficients, r_JeepViewDampenFreq.GetFloat(), r_JeepViewDampenDamp.GetFloat(), flFrameTime ); + m_vecEyeSpeed += ( ( flCoefficients[0] * vecDeltaPos + flCoefficients[1] * vecDeltaSpeed ) * flFrameTime ); + + // Save off data for next frame. + m_vecLastEyePos = vecPredEyePos; + + // Move eye forward/backward. + Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) ); + vecVehicleEyePos -= vecForwardOffset; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropJeep::DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ) +{ + // Get up vector. + Vector vecUp; + AngleVectors( vecVehicleEyeAngles, NULL, NULL, &vecUp ); + vecUp.z = clamp( vecUp.z, 0.0f, vecUp.z ); + vecVehicleEyePos.z += r_JeepViewZHeight.GetFloat() * vecUp.z; + + // NOTE: Should probably use some damped equation here. +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropJeep::OnEnteredVehicle( C_BasePlayer *pPlayer ) +{ + int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); + Vector vehicleEyeOrigin; + QAngle vehicleEyeAngles; + GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); + + m_vecLastEyeTarget = vehicleEyeOrigin; + m_vecLastEyePos = vehicleEyeOrigin; + m_vecEyeSpeed = vec3_origin; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void WheelDustCallback( const CEffectData &data ) +{ + CSmartPtr pSimple = CSimpleEmitter::Create( "dust" ); + pSimple->SetSortOrigin( data.m_vOrigin ); + pSimple->SetNearClip( 32, 64 ); + + SimpleParticle *pParticle; + + Vector offset; + + //FIXME: Better sampling area + offset = data.m_vOrigin + ( data.m_vNormal * data.m_flScale ); + + //Find area ambient light color and use it to tint smoke + Vector worldLight = WorldGetLightForPoint( offset, true ); + + //Throw puffs + offset.Random( -(data.m_flScale*16.0f), data.m_flScale*16.0f ); + offset.z = 0.0f; + offset += data.m_vOrigin + ( data.m_vNormal * data.m_flScale ); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[0], offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + pParticle->m_vecVelocity = RandomVector( -1.0f, 1.0f ); + VectorNormalize( pParticle->m_vecVelocity ); + pParticle->m_vecVelocity[2] += random->RandomFloat( 16.0f, 32.0f ) * (data.m_flScale*2.0f); + + int color = random->RandomInt( 100, 150 ); + + pParticle->m_uchColor[0] = 16 + ( worldLight[0] * (float) color ); + pParticle->m_uchColor[1] = 8 + ( worldLight[1] * (float) color ); + pParticle->m_uchColor[2] = ( worldLight[2] * (float) color ); + + pParticle->m_uchStartAlpha = random->RandomInt( 64.0f*data.m_flScale, 128.0f*data.m_flScale ); + pParticle->m_uchEndAlpha = 0; + pParticle->m_uchStartSize = random->RandomInt( 16, 24 ) * data.m_flScale; + pParticle->m_uchEndSize = random->RandomInt( 32, 48 ) * data.m_flScale; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + } +} + +DECLARE_CLIENT_EFFECT( "WheelDust", WheelDustCallback ); \ No newline at end of file diff --git a/game/client/c_vehicle_jeep.h b/game/client/c_vehicle_jeep.h new file mode 100644 index 00000000..403cb9cd --- /dev/null +++ b/game/client/c_vehicle_jeep.h @@ -0,0 +1,66 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#ifndef C_VEHICLE_JEEP_H +#define C_VEHICLE_JEEP_H +#pragma once + +#include "cbase.h" +#include "c_prop_vehicle.h" +//#include "movevars_shared.h" +//#include "view.h" +#include "flashlighteffect.h" +//#include "c_baseplayer.h" +//#include "c_te_effect_dispatch.h" + +// memdbgon must be the last include file in a .cpp file!!! +//#include "tier0/memdbgon.h" + +//============================================================================= +// +// Client-side Jeep Class +// +class C_PropJeep : public C_PropVehicleDriveable +{ + + DECLARE_CLASS( C_PropJeep, C_PropVehicleDriveable ); + +public: + + DECLARE_CLIENTCLASS(); + DECLARE_INTERPOLATION(); + + C_PropJeep(); + ~C_PropJeep(); + +public: + + void UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ); + void DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ); + + void OnEnteredVehicle( C_BasePlayer *pPlayer ); + void Simulate( void ); + +private: + + void DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); + void DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); + void ComputePDControllerCoefficients( float *pCoefficientsOut, float flFrequency, float flDampening, float flDeltaTime ); + +private: + + Vector m_vecLastEyePos; + Vector m_vecLastEyeTarget; + Vector m_vecEyeSpeed; + Vector m_vecTargetSpeed; + + float m_flViewAngleDeltaTime; + + float m_flJeepFOV; + CHeadlightEffect *m_pHeadlight; + bool m_bHeadlightIsOn; +}; + +#endif C_VEHICLE_JEEP_H \ No newline at end of file diff --git a/game/client/c_vguiscreen.cpp b/game/client/c_vguiscreen.cpp new file mode 100644 index 00000000..ef93026d --- /dev/null +++ b/game/client/c_vguiscreen.cpp @@ -0,0 +1,878 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "networkstringtable_clientdll.h" +#include +#include "PanelMetaClassMgr.h" +#include +#include "mathlib/VMatrix.h" +#include "VGUIMatSurface/IMatSystemSurface.h" +#include "view.h" +#include "CollisionUtils.h" +#include +#include +#include +#include "ienginevgui.h" +#include "in_buttons.h" +#include +#include "materialsystem/IMesh.h" +#include "ClientEffectPrecacheSystem.h" +#include "C_VGuiScreen.h" +#include "IClientMode.h" +#include "vgui_bitmapbutton.h" +#include "vgui_bitmappanel.h" +#include "filesystem.h" +#include "iinput.h" + +#include +extern vgui::IInputInternal *g_InputInternal; + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define VGUI_SCREEN_MODE_RADIUS 80 + +//Precache the materials +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectVGuiScreen ) +CLIENTEFFECT_MATERIAL( "engine/writez" ) +CLIENTEFFECT_REGISTER_END() + + +// ----------------------------------------------------------------------------- // +// This is a cache of preloaded keyvalues. +// ----------------------------------------------------------------------------- // + +CUtlDict g_KeyValuesCache; + +KeyValues* CacheKeyValuesForFile( const char *pFilename ) +{ + MEM_ALLOC_CREDIT(); + int i = g_KeyValuesCache.Find( pFilename ); + if ( i == g_KeyValuesCache.InvalidIndex() ) + { + KeyValues *rDat = new KeyValues( pFilename ); + rDat->LoadFromFile( filesystem, pFilename, NULL ); + g_KeyValuesCache.Insert( pFilename, rDat ); + return rDat; + } + else + { + return g_KeyValuesCache[i]; + } +} + +void ClearKeyValuesCache() +{ + MEM_ALLOC_CREDIT(); + for ( int i=g_KeyValuesCache.First(); i != g_KeyValuesCache.InvalidIndex(); i=g_KeyValuesCache.Next( i ) ) + { + g_KeyValuesCache[i]->deleteThis(); + } + g_KeyValuesCache.Purge(); +} + + +IMPLEMENT_CLIENTCLASS_DT(C_VGuiScreen, DT_VGuiScreen, CVGuiScreen) + RecvPropFloat( RECVINFO(m_flWidth) ), + RecvPropFloat( RECVINFO(m_flHeight) ), + RecvPropInt( RECVINFO(m_fScreenFlags) ), + RecvPropInt( RECVINFO(m_nPanelName) ), + RecvPropInt( RECVINFO(m_nAttachmentIndex) ), + RecvPropInt( RECVINFO(m_nOverlayMaterial) ), + RecvPropEHandle( RECVINFO(m_hPlayerOwner) ), +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +C_VGuiScreen::C_VGuiScreen() +{ + m_nOldPanelName = m_nPanelName = -1; + m_nOldOverlayMaterial = m_nOverlayMaterial = -1; + m_nOldPx = m_nOldPy = -1; + m_nButtonState = 0; + m_bLoseThinkNextFrame = false; + m_bAcceptsInput = true; + + m_WriteZMaterial.Init( "engine/writez", TEXTURE_GROUP_VGUI ); + m_OverlayMaterial.Init( m_WriteZMaterial ); +} + +C_VGuiScreen::~C_VGuiScreen() +{ + DestroyVguiScreen(); +} + +//----------------------------------------------------------------------------- +// Network updates +//----------------------------------------------------------------------------- +void C_VGuiScreen::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); + m_nOldPanelName = m_nPanelName; + m_nOldOverlayMaterial = m_nOverlayMaterial; +} + +void C_VGuiScreen::OnDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnDataChanged( type ); + + if ((type == DATA_UPDATE_CREATED) || (m_nPanelName != m_nOldPanelName)) + { + CreateVguiScreen( PanelName() ); + m_nButtonState = 0; + } + + // Set up the overlay material + if (m_nOldOverlayMaterial != m_nOverlayMaterial) + { + m_OverlayMaterial.Shutdown(); + + const char *pMaterialName = GetMaterialNameFromIndex(m_nOverlayMaterial); + if (pMaterialName) + { + m_OverlayMaterial.Init( pMaterialName, TEXTURE_GROUP_VGUI ); + } + else + { + m_OverlayMaterial.Init( m_WriteZMaterial ); + } + } +} + +void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); + +//----------------------------------------------------------------------------- +// Returns the attachment render origin + origin +//----------------------------------------------------------------------------- +void C_VGuiScreen::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles ) +{ + C_BaseEntity *pEnt = pAttachedTo->GetBaseEntity(); + if (pEnt && (m_nAttachmentIndex > 0)) + { + { + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, true ); + pEnt->GetAttachment( m_nAttachmentIndex, *pOrigin, *pAngles ); + } + + if ( IsAttachedToViewModel() ) + { + FormatViewModelAttachment( *pOrigin, true ); + } + } + else + { + BaseClass::GetAimEntOrigin( pAttachedTo, pOrigin, pAngles ); + } +} + +//----------------------------------------------------------------------------- +// Create, destroy vgui panels... +//----------------------------------------------------------------------------- +void C_VGuiScreen::CreateVguiScreen( const char *pTypeName ) +{ + // Clear out any old screens. + DestroyVguiScreen(); + + AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); + + // Create the new screen... + VGuiScreenInitData_t initData( this ); + m_PanelWrapper.Activate( pTypeName, NULL, 0, &initData ); + + // Retrieve the panel dimensions + vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); + if (pPanel) + { + int x, y; + pPanel->GetBounds( x, y, m_nPixelWidth, m_nPixelHeight ); + } + else + { + m_nPixelWidth = m_nPixelHeight = 0; + } +} + +void C_VGuiScreen::DestroyVguiScreen( ) +{ + m_PanelWrapper.Deactivate(); +} + + +//----------------------------------------------------------------------------- +// Is the screen active? +//----------------------------------------------------------------------------- +bool C_VGuiScreen::IsActive() const +{ + return (m_fScreenFlags & VGUI_SCREEN_ACTIVE) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_VGuiScreen::IsAttachedToViewModel() const +{ + return (m_fScreenFlags & VGUI_SCREEN_ATTACHED_TO_VIEWMODEL) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_VGuiScreen::AcceptsInput() const +{ + return m_bAcceptsInput; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : acceptsinput - +//----------------------------------------------------------------------------- +void C_VGuiScreen::SetAcceptsInput( bool acceptsinput ) +{ + m_bAcceptsInput = acceptsinput; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : RenderGroup_t +//----------------------------------------------------------------------------- +RenderGroup_t C_VGuiScreen::GetRenderGroup() +{ + if ( IsAttachedToViewModel() ) + return RENDER_GROUP_VIEW_MODEL_TRANSLUCENT; + + return BaseClass::GetRenderGroup(); +} + +//----------------------------------------------------------------------------- +// Are we only visible to teammates? +//----------------------------------------------------------------------------- +bool C_VGuiScreen::IsVisibleOnlyToTeammates() const +{ + return (m_fScreenFlags & VGUI_SCREEN_VISIBLE_TO_TEAMMATES) != 0; +} + +//----------------------------------------------------------------------------- +// Are we visible to someone on this team? +//----------------------------------------------------------------------------- +bool C_VGuiScreen::IsVisibleToTeam( int nTeam ) +{ + // FIXME: Should this maybe go into a derived class of some sort? + // Don't bother with screens on the wrong team + if (IsVisibleOnlyToTeammates() && (nTeam > 0)) + { + // Hmmm... sort of a hack... + C_BaseEntity *pOwner = GetOwnerEntity(); + if ( pOwner && (nTeam != pOwner->GetTeamNumber()) ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Activate, deactivate the view screen +//----------------------------------------------------------------------------- +void C_VGuiScreen::GainFocus( ) +{ + SetNextClientThink( CLIENT_THINK_ALWAYS ); + m_bLoseThinkNextFrame = false; + m_nOldButtonState = 0; +} + +void C_VGuiScreen::LoseFocus() +{ + m_bLoseThinkNextFrame = true; + m_nOldButtonState = 0; +} + +void C_VGuiScreen::SetButtonState( int nButtonState ) +{ + m_nButtonState = nButtonState; +} + + + +//----------------------------------------------------------------------------- +// Returns the panel name +//----------------------------------------------------------------------------- +const char *C_VGuiScreen::PanelName() const +{ + return g_StringTableVguiScreen->GetString( m_nPanelName ); +} + +//-------------------------------------------------------------------------- +// Purpose: +// Given a field of view and mouse/screen positions as well as the current +// render origin and angles, returns a unit vector through the mouse position +// that can be used to trace into the world under the mouse click pixel. +// Input : +// mousex - +// mousey - +// fov - +// vecRenderOrigin - +// vecRenderAngles - +// Output : +// vecPickingRay +//-------------------------------------------------------------------------- +void ScreenToWorld( int mousex, int mousey, float fov, + const Vector& vecRenderOrigin, + const QAngle& vecRenderAngles, + Vector& vecPickingRay ) +{ + float dx, dy; + float c_x, c_y; + float dist; + Vector vpn, vup, vright; + + float scaled_fov = ScaleFOVByWidthRatio( fov, engine->GetScreenAspectRatio() * 0.75f ); + + c_x = ScreenWidth() / 2; + c_y = ScreenHeight() / 2; + + dx = (float)mousex - c_x; + // Invert Y + dy = c_y - (float)mousey; + + // Convert view plane distance + dist = c_x / tan( M_PI * scaled_fov / 360.0 ); + + // Decompose view angles + AngleVectors( vecRenderAngles, &vpn, &vright, &vup ); + + // Offset forward by view plane distance, and then by pixel offsets + vecPickingRay = vpn * dist + vright * ( dx ) + vup * ( dy ); + + // Convert to unit vector + VectorNormalize( vecPickingRay ); +} + +//----------------------------------------------------------------------------- +// Purpose: Deal with input +//----------------------------------------------------------------------------- +void C_VGuiScreen::ClientThink( void ) +{ + int nButtonsChanged = m_nOldButtonState ^ m_nButtonState; + + m_nOldButtonState = m_nButtonState; + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_nButtonPressed = nButtonsChanged & m_nButtonState; // The changed ones still down are "pressed" + m_nButtonReleased = nButtonsChanged & (~m_nButtonState); // The ones not down are "released" + + BaseClass::ClientThink(); + + // FIXME: We should really be taking bob, shake, and roll into account + // but if we did, then all the inputs would be generated multiple times + // if the world was rendered multiple times (for things like water, etc.) + + vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); + if (!pPanel) + return; + + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if (!pLocalPlayer) + return; + + // Generate a ray along the view direction + Vector vecEyePosition = pLocalPlayer->EyePosition(); + + QAngle viewAngles = pLocalPlayer->EyeAngles( ); + + // Compute cursor position... + Ray_t lookDir; + Vector endPos; + + float u, v; + + // Viewmodel attached screens that take input need to have a moving cursor + // Do a pick under the cursor as our selection + Vector viewDir; + AngleVectors( viewAngles, &viewDir ); + VectorMA( vecEyePosition, 1000.0f, viewDir, endPos ); + lookDir.Init( vecEyePosition, endPos ); + + if (!IntersectWithRay( lookDir, &u, &v, NULL )) + return; + + if ( ((u < 0) || (v < 0) || (u > 1) || (v > 1)) && !m_bLoseThinkNextFrame) + return; + + // This will cause our panel to grab all input! + g_pClientMode->ActivateInGameVGuiContext( pPanel ); + + // Convert (u,v) into (px,py) + int px = (int)(u * m_nPixelWidth + 0.5f); + int py = (int)(v * m_nPixelHeight + 0.5f); + + // Generate mouse input commands + if ((px != m_nOldPx) || (py != m_nOldPy)) + { + g_InputInternal->InternalCursorMoved( px, py ); + m_nOldPx = px; + m_nOldPy = py; + } + + if (m_nButtonPressed & IN_ATTACK) + { + g_InputInternal->SetMouseCodeState( MOUSE_LEFT, vgui::BUTTON_PRESSED ); + g_InputInternal->InternalMousePressed(MOUSE_LEFT); + } + if (m_nButtonPressed & IN_ATTACK2) + { + g_InputInternal->SetMouseCodeState( MOUSE_RIGHT, vgui::BUTTON_PRESSED ); + g_InputInternal->InternalMousePressed( MOUSE_RIGHT ); + } + if ( (m_nButtonReleased & IN_ATTACK) || m_bLoseThinkNextFrame) // for a button release on loosing focus + { + g_InputInternal->SetMouseCodeState( MOUSE_LEFT, vgui::BUTTON_RELEASED ); + g_InputInternal->InternalMouseReleased( MOUSE_LEFT ); + } + if (m_nButtonReleased & IN_ATTACK2) + { + g_InputInternal->SetMouseCodeState( MOUSE_RIGHT, vgui::BUTTON_RELEASED ); + g_InputInternal->InternalMouseReleased( MOUSE_RIGHT ); + } + + if ( m_bLoseThinkNextFrame == true ) + { + m_bLoseThinkNextFrame = false; + SetNextClientThink( CLIENT_THINK_NEVER ); + } + + g_pClientMode->DeactivateInGameVGuiContext( ); +} + + +//----------------------------------------------------------------------------- +// Computes control points of the quad describing the screen +//----------------------------------------------------------------------------- +void C_VGuiScreen::ComputeEdges( Vector *pUpperLeft, Vector *pUpperRight, Vector *pLowerLeft ) +{ + Vector vecOrigin = GetAbsOrigin(); + Vector xaxis, yaxis; + AngleVectors( GetAbsAngles(), &xaxis, &yaxis, NULL ); + + // NOTE: Have to multiply by -1 here because yaxis goes out the -y axis in AngleVectors actually... + yaxis *= -1.0f; + + VectorCopy( vecOrigin, *pLowerLeft ); + VectorMA( vecOrigin, m_flHeight, yaxis, *pUpperLeft ); + VectorMA( *pUpperLeft, m_flWidth, xaxis, *pUpperRight ); +} + + +//----------------------------------------------------------------------------- +// Return intersection point of ray with screen in barycentric coords +//----------------------------------------------------------------------------- +bool C_VGuiScreen::IntersectWithRay( const Ray_t &ray, float *u, float *v, float *t ) +{ + // Perform a raycast to see where in barycentric coordinates the ray hits + // the viewscreen; if it doesn't hit it, you're not in the mode + Vector origin, upt, vpt; + ComputeEdges( &origin, &upt, &vpt ); + return ComputeIntersectionBarycentricCoordinates( ray, origin, upt, vpt, *u, *v, t ); +} + + +//----------------------------------------------------------------------------- +// Is the vgui screen backfacing? +//----------------------------------------------------------------------------- +bool C_VGuiScreen::IsBackfacing( const Vector &viewOrigin ) +{ + // Compute a ray from camera to center of the screen.. + Vector cameraToScreen; + VectorSubtract( GetAbsOrigin(), viewOrigin, cameraToScreen ); + + // Figure out the face normal + Vector zaxis; + GetVectors( NULL, NULL, &zaxis ); + + // The actual backface cull + return (DotProduct( zaxis, cameraToScreen ) > 0.0f); +} + + +//----------------------------------------------------------------------------- +// Computes the panel center to world transform +//----------------------------------------------------------------------------- +void C_VGuiScreen::ComputePanelToWorld() +{ + // The origin is at the upper-left corner of the screen + Vector vecOrigin, vecUR, vecLL; + ComputeEdges( &vecOrigin, &vecUR, &vecLL ); + m_PanelToWorld.SetupMatrixOrgAngles( vecOrigin, GetAbsAngles() ); +} + + +//----------------------------------------------------------------------------- +// a pass to set the z buffer... +//----------------------------------------------------------------------------- +void C_VGuiScreen::DrawScreenOverlay() +{ + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadMatrix( m_PanelToWorld ); + + unsigned char pColor[4] = {255, 255, 255, 255}; + + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_OverlayMaterial ); + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + meshBuilder.Position3f( 0.0f, 0.0f, 0 ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ubv( pColor ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( m_flWidth, 0.0f, 0 ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ubv( pColor ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( m_flWidth, -m_flHeight, 0 ); + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.Color4ubv( pColor ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, -m_flHeight, 0 ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.Color4ubv( pColor ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + + pRenderContext->PopMatrix(); +} + + +//----------------------------------------------------------------------------- +// Draws the panel using a 3D transform... +//----------------------------------------------------------------------------- +int C_VGuiScreen::DrawModel( int flags ) +{ + vgui::Panel *pPanel = m_PanelWrapper.GetPanel(); + if (!pPanel || !IsActive()) + return 0; + + // Don't bother drawing stuff not visible to me... + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if (!pLocalPlayer || !IsVisibleToTeam(pLocalPlayer->GetTeamNumber()) ) + return 0; + + if ( !IsVisibleToPlayer( pLocalPlayer ) ) + { + return 0; + } + + // Backface cull the entire panel here... + if (IsBackfacing(CurrentViewOrigin())) + return 0; + + // Recompute the panel-to-world center + // FIXME: Can this be cached off? + ComputePanelToWorld(); + + g_pMatSystemSurface->DrawPanelIn3DSpace( pPanel->GetVPanel(), m_PanelToWorld, + m_nPixelWidth, m_nPixelHeight, m_flWidth, m_flHeight ); + + // Finally, a pass to set the z buffer... + DrawScreenOverlay(); + + return 1; +} + +bool C_VGuiScreen::ShouldDraw( void ) +{ + return !IsEffectActive(EF_NODRAW); +} + + +//----------------------------------------------------------------------------- +// Purpose: Hook for vgui screens to determine visibility +//----------------------------------------------------------------------------- +bool C_VGuiScreen::IsVisibleToPlayer( C_BasePlayer *pViewingPlayer ) +{ + return true; +} + +bool C_VGuiScreen::IsTransparent( void ) +{ + return (m_fScreenFlags & VGUI_SCREEN_TRANSPARENT) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Sometimes we only want a specific player to be able to input to a panel +//----------------------------------------------------------------------------- +C_BasePlayer *C_VGuiScreen::GetPlayerOwner( void ) +{ + return ( C_BasePlayer * )( m_hPlayerOwner.Get() ); +} + +bool C_VGuiScreen::IsInputOnlyToOwner( void ) +{ + return (m_fScreenFlags & VGUI_SCREEN_ONLY_USABLE_BY_OWNER) != 0; +} + +//----------------------------------------------------------------------------- +// +// Enumator class for finding vgui screens close to the local player +// +//----------------------------------------------------------------------------- +class CVGuiScreenEnumerator : public IPartitionEnumerator +{ + DECLARE_CLASS_GAMEROOT( CVGuiScreenEnumerator, IPartitionEnumerator ); +public: + virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); + + int GetScreenCount(); + C_VGuiScreen *GetVGuiScreen( int index ); + +private: + CUtlVector< CHandle< C_VGuiScreen > > m_VguiScreens; +}; + +IterationRetval_t CVGuiScreenEnumerator::EnumElement( IHandleEntity *pHandleEntity ) +{ + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); + if ( pEnt == NULL ) + return ITERATION_CONTINUE; + + // FIXME.. pretty expensive... + C_VGuiScreen *pScreen = dynamic_cast(pEnt); + if ( pScreen ) + { + int i = m_VguiScreens.AddToTail( ); + m_VguiScreens[i].Set( pScreen ); + } + + return ITERATION_CONTINUE; +} + +int CVGuiScreenEnumerator::GetScreenCount() +{ + return m_VguiScreens.Count(); +} + +C_VGuiScreen *CVGuiScreenEnumerator::GetVGuiScreen( int index ) +{ + return m_VguiScreens[index].Get(); +} + + +//----------------------------------------------------------------------------- +// +// Look for vgui screens, returns true if it found one ... +// +//----------------------------------------------------------------------------- +C_BaseEntity *FindNearbyVguiScreen( const Vector &viewPosition, const QAngle &viewAngle, int nTeam ) +{ + if ( IsX360() ) + { + // X360TBD: Turn this on if feature actually used + return NULL; + } + + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + + Assert( pLocalPlayer ); + + if ( !pLocalPlayer ) + return NULL; + + // Get the view direction... + Vector lookDir; + AngleVectors( viewAngle, &lookDir ); + + // Create a ray used for raytracing + Vector lookEnd; + VectorMA( viewPosition, 2.0f * VGUI_SCREEN_MODE_RADIUS, lookDir, lookEnd ); + + Ray_t lookRay; + lookRay.Init( viewPosition, lookEnd ); + + // Look for vgui screens that are close to the player + CVGuiScreenEnumerator localScreens; + partition->EnumerateElementsInSphere( PARTITION_CLIENT_NON_STATIC_EDICTS, viewPosition, VGUI_SCREEN_MODE_RADIUS, false, &localScreens ); + + Vector vecOut, vecViewDelta; + + float flBestDist = 2.0f; + C_VGuiScreen *pBestScreen = NULL; + for (int i = localScreens.GetScreenCount(); --i >= 0; ) + { + C_VGuiScreen *pScreen = localScreens.GetVGuiScreen(i); + + if ( pScreen->IsAttachedToViewModel() ) + continue; + + // Don't bother with screens I'm behind... + // Hax - don't cancel backfacing with viewmodel attached screens. + // we can get prediction bugs that make us backfacing for one frame and + // it resets the mouse position if we lose focus. + if ( pScreen->IsBackfacing(viewPosition) ) + continue; + + // Don't bother with screens that are turned off + if (!pScreen->IsActive()) + continue; + + // FIXME: Should this maybe go into a derived class of some sort? + // Don't bother with screens on the wrong team + if (!pScreen->IsVisibleToTeam(nTeam)) + continue; + + if ( !pScreen->AcceptsInput() ) + continue; + + if ( pScreen->IsInputOnlyToOwner() && pScreen->GetPlayerOwner() != pLocalPlayer ) + continue; + + // Test perpendicular distance from the screen... + pScreen->GetVectors( NULL, NULL, &vecOut ); + VectorSubtract( viewPosition, pScreen->GetAbsOrigin(), vecViewDelta ); + float flPerpDist = DotProduct(vecViewDelta, vecOut); + if ( (flPerpDist < 0) || (flPerpDist > VGUI_SCREEN_MODE_RADIUS) ) + continue; + + // Perform a raycast to see where in barycentric coordinates the ray hits + // the viewscreen; if it doesn't hit it, you're not in the mode + float u, v, t; + if (!pScreen->IntersectWithRay( lookRay, &u, &v, &t )) + continue; + + // Barycentric test + if ((u < 0) || (v < 0) || (u > 1) || (v > 1)) + continue; + + if ( t < flBestDist ) + { + flBestDist = t; + pBestScreen = pScreen; + } + } + + return pBestScreen; +} + +void ActivateVguiScreen( C_BaseEntity *pVguiScreenEnt ) +{ + if (pVguiScreenEnt) + { + Assert( dynamic_cast(pVguiScreenEnt) ); + C_VGuiScreen *pVguiScreen = static_cast(pVguiScreenEnt); + pVguiScreen->GainFocus( ); + } +} + +void SetVGuiScreenButtonState( C_BaseEntity *pVguiScreenEnt, int nButtonState ) +{ + if (pVguiScreenEnt) + { + Assert( dynamic_cast(pVguiScreenEnt) ); + C_VGuiScreen *pVguiScreen = static_cast(pVguiScreenEnt); + pVguiScreen->SetButtonState( nButtonState ); + } +} + +void DeactivateVguiScreen( C_BaseEntity *pVguiScreenEnt ) +{ + if (pVguiScreenEnt) + { + Assert( dynamic_cast(pVguiScreenEnt) ); + C_VGuiScreen *pVguiScreen = static_cast(pVguiScreenEnt); + pVguiScreen->LoseFocus( ); + } +} + +CVGuiScreenPanel::CVGuiScreenPanel( vgui::Panel *parent, const char *panelName ) + : BaseClass( parent, panelName ) +{ + m_hEntity = NULL; +} + +CVGuiScreenPanel::CVGuiScreenPanel( vgui::Panel *parent, const char *panelName, vgui::HScheme hScheme ) + : BaseClass( parent, panelName, hScheme ) +{ + m_hEntity = NULL; +} + + +bool CVGuiScreenPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) +{ + const char *pResFile = pKeyValues->GetString( "resfile" ); + if (pResFile[0] != 0) + { + KeyValues *pCachedKeyValues = CacheKeyValuesForFile( pResFile ); + LoadControlSettings( pResFile, NULL, pCachedKeyValues ); + } + + // Dimensions in pixels + int nWidth, nHeight; + nWidth = pKeyValues->GetInt( "pixelswide", 240 ); + nHeight = pKeyValues->GetInt( "pixelshigh", 160 ); + if ((nWidth <= 0) || (nHeight <= 0)) + return false; + + // If init data isn't specified, then we're just precaching. + if ( pInitData ) + { + m_hEntity.Set( pInitData->m_pEntity ); + + C_VGuiScreen *screen = dynamic_cast< C_VGuiScreen * >( pInitData->m_pEntity ); + if ( screen ) + { + bool acceptsInput = pKeyValues->GetInt( "acceptsinput", 1 ) ? true : false; + screen->SetAcceptsInput( acceptsInput ); + } + } + + SetBounds( 0, 0, nWidth, nHeight ); + + return true; +} + +vgui::Panel *CVGuiScreenPanel::CreateControlByName(const char *controlName) +{ + // Check the panel metaclass manager to make these controls... + if (!Q_strncmp(controlName, "MaterialImage", 20)) + { + return new CBitmapPanel(NULL, "BitmapPanel"); + } + + if (!Q_strncmp(controlName, "MaterialButton", 20)) + { + return new CBitmapButton(NULL, "BitmapButton", ""); + } + + // Didn't find it? Just use the default stuff + return BaseClass::CreateControlByName( controlName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the user presses a button +//----------------------------------------------------------------------------- +void CVGuiScreenPanel::OnCommand( const char *command) +{ + if ( Q_stricmp( command, "vguicancel" ) ) + { + engine->ClientCmd( const_cast( command ) ); + } + + BaseClass::OnCommand(command); +} + +DECLARE_VGUI_SCREEN_FACTORY( CVGuiScreenPanel, "vgui_screen_panel" ); \ No newline at end of file diff --git a/game/client/c_vguiscreen.h b/game/client/c_vguiscreen.h new file mode 100644 index 00000000..8ffb82d1 --- /dev/null +++ b/game/client/c_vguiscreen.h @@ -0,0 +1,187 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_VGUISCREEN_H +#define C_VGUISCREEN_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include +#include "C_BaseEntity.h" +#include "PanelMetaClassMgr.h" + +class KeyValues; + + +//----------------------------------------------------------------------------- +// Helper macro to make overlay factories one line of code. Use like this: +// DECLARE_VGUI_SCREEN_FACTORY( CVguiScreenPanel, "image" ); +//----------------------------------------------------------------------------- +struct VGuiScreenInitData_t +{ + C_BaseEntity *m_pEntity; + + VGuiScreenInitData_t() : m_pEntity(NULL) {} + VGuiScreenInitData_t( C_BaseEntity *pEntity ) : m_pEntity(pEntity) {} +}; + +#define DECLARE_VGUI_SCREEN_FACTORY( _PanelClass, _nameString ) \ + DECLARE_PANEL_FACTORY( _PanelClass, VGuiScreenInitData_t, _nameString ) + + +//----------------------------------------------------------------------------- +// Base class for vgui screen panels +//----------------------------------------------------------------------------- +class CVGuiScreenPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_GAMEROOT( CVGuiScreenPanel, vgui::EditablePanel ); + +public: + CVGuiScreenPanel( vgui::Panel *parent, const char *panelName ); + CVGuiScreenPanel( vgui::Panel *parent, const char *panelName, vgui::HScheme hScheme ); + virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ); + vgui::Panel *CreateControlByName(const char *controlName); + virtual void CVGuiScreenPanel::OnCommand( const char *command ); + +protected: + C_BaseEntity *GetEntity() const { return m_hEntity.Get(); } + +private: + EHANDLE m_hEntity; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_VGuiScreen : public C_BaseEntity +{ + DECLARE_CLASS( C_VGuiScreen, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_VGuiScreen(); + ~C_VGuiScreen(); + + virtual void PreDataUpdate( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t type ); + virtual int DrawModel( int flags ); + virtual bool ShouldDraw( void ); + virtual void ClientThink( ); + virtual void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pOrigin, QAngle *pAngles ); + virtual bool IsVisibleToPlayer( C_BasePlayer *pViewingPlayer ); + virtual bool IsTransparent( void ); + + const char *PanelName() const; + + // The view screen has the cursor pointing at it + void GainFocus( ); + void LoseFocus(); + + // Button state... + void SetButtonState( int nButtonState ); + + // Is the screen backfaced given a view position? + bool IsBackfacing( const Vector &viewOrigin ); + + // Return intersection point of ray with screen in barycentric coords + bool IntersectWithRay( const Ray_t &ray, float *u, float *v, float *t ); + + // Is the screen turned on? + bool IsActive() const; + + // Are we only visible to teammates? + bool IsVisibleOnlyToTeammates() const; + + // Are we visible to someone on this team? + bool IsVisibleToTeam( int nTeam ); + + bool IsAttachedToViewModel() const; + + virtual RenderGroup_t GetRenderGroup(); + + bool AcceptsInput() const; + void SetAcceptsInput( bool acceptsinput ); + + C_BasePlayer *GetPlayerOwner( void ); + bool IsInputOnlyToOwner( void ); + +private: + // Vgui screen management + void CreateVguiScreen( const char *pTypeName ); + void DestroyVguiScreen( ); + + // Computes the panel to world transform + void ComputePanelToWorld(); + + // Computes control points of the quad describing the screen + void ComputeEdges( Vector *pUpperLeft, Vector *pUpperRight, Vector *pLowerLeft ); + + // Writes the z buffer + void DrawScreenOverlay(); + +private: + int m_nPixelWidth; + int m_nPixelHeight; + float m_flWidth; + float m_flHeight; + int m_nPanelName; // The name of the panel + int m_nButtonState; + int m_nButtonPressed; + int m_nButtonReleased; + int m_nOldPx; + int m_nOldPy; + int m_nOldButtonState; + int m_nAttachmentIndex; + int m_nOverlayMaterial; + int m_fScreenFlags; + + int m_nOldPanelName; + int m_nOldOverlayMaterial; + + bool m_bLoseThinkNextFrame; + + bool m_bAcceptsInput; + + CMaterialReference m_WriteZMaterial; + CMaterialReference m_OverlayMaterial; + + VMatrix m_PanelToWorld; + + CPanelWrapper m_PanelWrapper; + + CHandle m_hPlayerOwner; +}; + + +//----------------------------------------------------------------------------- +// Returns an entity that is the nearby vgui screen; NULL if there isn't one +//----------------------------------------------------------------------------- +C_BaseEntity *FindNearbyVguiScreen( const Vector &viewPosition, const QAngle &viewAngle, int nTeam = -1 ); + + +//----------------------------------------------------------------------------- +// Activates/Deactivates vgui screen +//----------------------------------------------------------------------------- +void ActivateVguiScreen( C_BaseEntity *pVguiScreen ); +void DeactivateVguiScreen( C_BaseEntity *pVguiScreen ); + + +//----------------------------------------------------------------------------- +// Updates vgui screen button state +//----------------------------------------------------------------------------- +void SetVGuiScreenButtonState( C_BaseEntity *pVguiScreen, int nButtonState ); + + +// Called at shutdown. +void ClearKeyValuesCache(); + + +#endif // C_VGUISCREEN_H + diff --git a/game/client/c_weapon__stubs.h b/game/client/c_weapon__stubs.h new file mode 100644 index 00000000..68b611e8 --- /dev/null +++ b/game/client/c_weapon__stubs.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef C_WEAPON__STUBS_H +#define C_WEAPON__STUBS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "client_class.h" + +// This is an ugly hack to link client classes to weapons for now +// these will be removed once we predict all weapons, especially TF2 weapons +#define STUB_WEAPON_CLASS_IMPLEMENT( entityName, className ) \ + BEGIN_PREDICTION_DATA( className ) \ + END_PREDICTION_DATA() \ + LINK_ENTITY_TO_CLASS( entityName, className ); + + +#define STUB_WEAPON_CLASS( entityName, className, baseClassName ) \ + class C_##className## : public baseClassName \ + { \ + DECLARE_CLASS( C_##className##, baseClassName ); \ + public: \ + DECLARE_PREDICTABLE(); \ + DECLARE_CLIENTCLASS(); \ + C_##className() {}; \ + private: \ + C_##className( const C_##className & ); \ + }; \ + STUB_WEAPON_CLASS_IMPLEMENT( entityName, C_##className ); \ + IMPLEMENT_CLIENTCLASS_DT( C_##className, DT_##className, C##className ) \ + END_RECV_TABLE() + +#endif // C_WEAPON__STUBS_H diff --git a/game/client/c_world.cpp b/game/client/c_world.cpp new file mode 100644 index 00000000..7c51958a --- /dev/null +++ b/game/client/c_world.cpp @@ -0,0 +1,189 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "c_world.h" +#include "ivmodemanager.h" +#include "activitylist.h" +#include "decals.h" +#include "engine/ivmodelinfo.h" +#include "ivieweffects.h" +#include "shake.h" +#include "eventlist.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef CWorld +#undef CWorld +#endif + +C_GameRules *g_pGameRules = NULL; +static C_World *g_pClientWorld; + + +void ClientWorldFactoryInit() +{ + g_pClientWorld = new C_World; +} + +void ClientWorldFactoryShutdown() +{ + delete g_pClientWorld; + g_pClientWorld = NULL; +} + +static IClientNetworkable* ClientWorldFactory( int entnum, int serialNum ) +{ + Assert( g_pClientWorld != NULL ); + + g_pClientWorld->Init( entnum, serialNum ); + return g_pClientWorld; +} + + +IMPLEMENT_CLIENTCLASS_FACTORY( C_World, DT_World, CWorld, ClientWorldFactory ); + +BEGIN_RECV_TABLE( C_World, DT_World ) + RecvPropFloat(RECVINFO(m_flWaveHeight)), + RecvPropVector(RECVINFO(m_WorldMins)), + RecvPropVector(RECVINFO(m_WorldMaxs)), + RecvPropInt(RECVINFO(m_bStartDark)), + RecvPropFloat(RECVINFO(m_flMaxOccludeeArea)), + RecvPropFloat(RECVINFO(m_flMinOccluderArea)), + RecvPropFloat(RECVINFO(m_flMaxPropScreenSpaceWidth)), + RecvPropFloat(RECVINFO(m_flMinPropScreenSpaceWidth)), + RecvPropString(RECVINFO(m_iszDetailSpriteMaterial)), + RecvPropInt(RECVINFO(m_bColdWorld)), +END_RECV_TABLE() + + +C_World::C_World( void ) +{ +} + +C_World::~C_World( void ) +{ +} + +bool C_World::Init( int entnum, int iSerialNum ) +{ + m_flWaveHeight = 0.0f; + ActivityList_Init(); + EventList_Init(); + + return BaseClass::Init( entnum, iSerialNum ); +} + +void C_World::Release() +{ + ActivityList_Free(); + Term(); +} + +void C_World::PreDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PreDataUpdate( updateType ); +} + +void C_World::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + // Always force reset to normal mode upon receipt of world in new map + if ( updateType == DATA_UPDATE_CREATED ) + { + modemanager->SwitchMode( false, true ); + + if ( m_bStartDark ) + { + ScreenFade_t sf; + memset( &sf, 0, sizeof( sf ) ); + sf.a = 255; + sf.r = 0; + sf.g = 0; + sf.b = 0; + sf.duration = (float)(1<Fade( sf ); + } + + OcclusionParams_t params; + params.m_flMaxOccludeeArea = m_flMaxOccludeeArea; + params.m_flMinOccluderArea = m_flMinOccluderArea; + engine->SetOcclusionParameters( params ); + + modelinfo->SetLevelScreenFadeRange( m_flMinPropScreenSpaceWidth, m_flMaxPropScreenSpaceWidth ); + } +} + +void C_World::RegisterSharedActivities( void ) +{ + ActivityList_RegisterSharedActivities(); + EventList_RegisterSharedEvents(); +} + +// ----------------------------------------- +// Sprite Index info +// ----------------------------------------- +short g_sModelIndexLaser; // holds the index for the laser beam +const char *g_pModelNameLaser = "sprites/laserbeam.vmt"; +short g_sModelIndexLaserDot; // holds the index for the laser beam dot +short g_sModelIndexFireball; // holds the index for the fireball +short g_sModelIndexSmoke; // holds the index for the smoke cloud +short g_sModelIndexWExplosion; // holds the index for the underwater explosion +short g_sModelIndexBubbles; // holds the index for the bubbles model +short g_sModelIndexBloodDrop; // holds the sprite index for the initial blood +short g_sModelIndexBloodSpray; // holds the sprite index for splattered blood + +//----------------------------------------------------------------------------- +// Purpose: Precache global weapon sounds +//----------------------------------------------------------------------------- +void W_Precache(void) +{ + PrecacheFileWeaponInfoDatabase( filesystem, g_pGameRules->GetEncryptionKey() ); + + g_sModelIndexFireball = modelinfo->GetModelIndex ("sprites/zerogxplode.vmt");// fireball + g_sModelIndexWExplosion = modelinfo->GetModelIndex ("sprites/WXplo1.vmt");// underwater fireball + g_sModelIndexSmoke = modelinfo->GetModelIndex ("sprites/steam1.vmt");// smoke + g_sModelIndexBubbles = modelinfo->GetModelIndex ("sprites/bubble.vmt");//bubbles + g_sModelIndexBloodSpray = modelinfo->GetModelIndex ("sprites/bloodspray.vmt"); // initial blood + g_sModelIndexBloodDrop = modelinfo->GetModelIndex ("sprites/blood.vmt"); // splattered blood + g_sModelIndexLaser = modelinfo->GetModelIndex( (char *)g_pModelNameLaser ); + g_sModelIndexLaserDot = modelinfo->GetModelIndex("sprites/laserdot.vmt"); +} + +void C_World::Precache( void ) +{ + // UNDONE: Make most of these things server systems or precache_registers + // ================================================= + // Activities + // ================================================= + ActivityList_Free(); + EventList_Free(); + + RegisterSharedActivities(); + + // Get weapon precaches + W_Precache(); + + // Call all registered precachers. + CPrecacheRegister::Precache(); +} + +void C_World::Spawn( void ) +{ + Precache(); +} + + + +C_World *GetClientWorldEntity() +{ + Assert( g_pClientWorld != NULL ); + return g_pClientWorld; +} + diff --git a/game/client/c_world.h b/game/client/c_world.h new file mode 100644 index 00000000..82f0edbc --- /dev/null +++ b/game/client/c_world.h @@ -0,0 +1,79 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( C_WORLD_H ) +#define C_WORLD_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_baseentity.h" + +#if defined( CLIENT_DLL ) +#define CWorld C_World +#endif + +class C_World : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_World, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_World( void ); + ~C_World( void ); + + // Override the factory create/delete functions since the world is a singleton. + virtual bool Init( int entnum, int iSerialNum ); + virtual void Release(); + + virtual void Precache(); + virtual void Spawn(); + + // Don't worry about adding the world to the collision list; it's already there + virtual CollideType_t GetCollideType( void ) { return ENTITY_SHOULD_NOT_COLLIDE; } + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void PreDataUpdate( DataUpdateType_t updateType ); + + float GetWaveHeight() const; + const char *GetDetailSpriteMaterial() const; + +public: + enum + { + MAX_DETAIL_SPRITE_MATERIAL_NAME_LENGTH = 256, + }; + + float m_flWaveHeight; + Vector m_WorldMins; + Vector m_WorldMaxs; + bool m_bStartDark; + float m_flMaxOccludeeArea; + float m_flMinOccluderArea; + float m_flMinPropScreenSpaceWidth; + float m_flMaxPropScreenSpaceWidth; + bool m_bColdWorld; + +private: + void RegisterSharedActivities( void ); + char m_iszDetailSpriteMaterial[MAX_DETAIL_SPRITE_MATERIAL_NAME_LENGTH]; +}; + +inline float C_World::GetWaveHeight() const +{ + return m_flWaveHeight; +} + +inline const char *C_World::GetDetailSpriteMaterial() const +{ + return m_iszDetailSpriteMaterial; +} + +void ClientWorldFactoryInit(); +void ClientWorldFactoryShutdown(); +C_World* GetClientWorldEntity(); + +#endif // C_WORLD_H \ No newline at end of file diff --git a/game/client/camomaterialproxy.cpp b/game/client/camomaterialproxy.cpp new file mode 100644 index 00000000..41078b08 --- /dev/null +++ b/game/client/camomaterialproxy.cpp @@ -0,0 +1,582 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "cbase.h" +// identifier was truncated to '255' characters in the debug information +#pragma warning(disable: 4786) + +#include "ProxyEntity.h" +#include "materialsystem/IMaterialVar.h" +#include "materialsystem/ITexture.h" +#include "bitmap/TGALoader.h" +#include "view.h" +#include "datacache/idatacache.h" +#include "materialsystem/IMaterial.h" +#include "vtf/vtf.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CCamoMaterialProxy; + +class CCamoTextureRegen : public ITextureRegenerator +{ +public: + CCamoTextureRegen( CCamoMaterialProxy *pProxy ) : m_pProxy(pProxy) {} + virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ); + virtual void Release() {} + +private: + CCamoMaterialProxy *m_pProxy; +}; + +class CCamoMaterialProxy : public CEntityMaterialProxy +{ +public: + CCamoMaterialProxy(); + virtual ~CCamoMaterialProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind(C_BaseEntity *pC_BaseEntity ); + virtual IMaterial *GetMaterial(); + + // Procedurally generates the camo texture... + void GenerateCamoTexture( ITexture* pTexture, IVTFTexture *pVTFTexture ); + +protected: +#if 0 + virtual void SetInstanceDataSize( int size ); + virtual void *FindInstanceData( C_BaseEntity *pEntity ); + virtual void *AllocateInstanceData( C_BaseEntity *pEntity ); +#endif + +private: + void LoadCamoPattern( void ); + void GenerateRandomPointsInNormalizedCube( void ); + void GetColors( Vector &lighting, Vector &base, int index, + const Vector &boxMin, const Vector &boxExtents, + const Vector &forward, const Vector &right, const Vector &up, + const Vector& entityPosition ); + // this needs to go in a base class + +private: +#if 0 + // stuff that needs to be in a base class. + struct InstanceData_t + { + C_BaseEntity *pEntity; + void *data; + struct InstanceData_s *next; + }; + + struct CamoInstanceData_t + { + int dummy; + }; +#endif + + unsigned char *m_pCamoPatternImage; + +#if 0 + int m_InstanceDataSize; + InstanceData_t *m_InstanceDataListHead; +#endif + + IMaterial *m_pMaterial; + IMaterialVar *m_pCamoTextureVar; + IMaterialVar *m_pCamoPatternTextureVar; + Vector *m_pointsInNormalizedBox; // [m_CamoPatternNumColors] + + int m_CamoPatternNumColors; + int m_CamoPatternWidth; + int m_CamoPatternHeight; +#if 0 + cache_user_t m_camoImageDataCache; +#endif + unsigned char m_CamoPalette[256][3]; + // these represent that part of the entitiy's bounding box that we + // want to cast rays through to get colors for the camo + Vector m_SubBoundingBoxMin; // normalized + Vector m_SubBoundingBoxMax; // normalized + + CCamoTextureRegen m_TextureRegen; + C_BaseEntity *m_pEnt; +}; + + +void CCamoTextureRegen::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) +{ + m_pProxy->GenerateCamoTexture( pTexture, pVTFTexture ); +} + + +#pragma warning (disable:4355) + +CCamoMaterialProxy::CCamoMaterialProxy() : m_TextureRegen(this) +{ +#if 0 + m_InstanceDataSize = 0; +#endif +#if 0 + memset( &m_camoImageDataCache, 0,sizeof( m_camoImageDataCache ) ); +#endif + m_pointsInNormalizedBox = NULL; +#if 0 + m_InstanceDataListHead = NULL; +#endif + m_pCamoPatternImage = NULL; + m_pMaterial = NULL; + m_pCamoTextureVar = NULL; + m_pCamoPatternTextureVar = NULL; + m_pointsInNormalizedBox = NULL; + m_pEnt = NULL; +} + +#pragma warning (default:4355) + +CCamoMaterialProxy::~CCamoMaterialProxy() +{ +#if 0 + InstanceData_t *curr = m_InstanceDataListHead; + while( curr ) + { + InstanceData_t *next; + next = curr->next; + delete curr; + curr = next; + } + m_InstanceDataListHead = NULL; +#endif + + // Disconnect the texture regenerator... + if (m_pCamoTextureVar) + { + ITexture *pCamoTexture = m_pCamoTextureVar->GetTextureValue(); + if (pCamoTexture) + pCamoTexture->SetTextureRegenerator( NULL ); + } + + delete m_pCamoPatternImage; + delete m_pointsInNormalizedBox; +} + + +#if 0 +void CCamoMaterialProxy::SetInstanceDataSize( int size ) +{ + m_InstanceDataSize = size; +} +#endif + +#if 0 +void *CCamoMaterialProxy::FindInstanceData( C_BaseEntity *pEntity ) +{ + InstanceData_t *curr = m_InstanceDataListHead; + while( curr ) + { + if( pEntity == curr->pEntity ) + { + return curr->data; + } + curr = curr->next; + } + return NULL; +} +#endif + +#if 0 +void *CCamoMaterialProxy::AllocateInstanceData( C_BaseEntity *pEntity ) +{ + InstanceData_t *newData = new InstanceData_t; + newData->pEntity = pEntity; + newData->next = m_InstanceDataListHead; + m_InstanceDataListHead = newData; + newData->data = new unsigned char[m_InstanceDataSize]; + return newData->data; +} +#endif + +bool CCamoMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + return false; // hack! Need to make sure that the TGA loader has a valid filesystem before trying + // to load the camo pattern. + +#if 0 + // set how big our instance data is. + SetInstanceDataSize( sizeof( CamoInstanceData_t ) ); +#endif + // remember what material we belong to. + m_pMaterial = pMaterial; + // get pointers to material vars. + bool found; + m_pCamoTextureVar = m_pMaterial->FindVar( "$baseTexture", &found ); + if( !found ) + { + m_pCamoTextureVar = NULL; + return false; + } + ITexture *pCamoTexture = m_pCamoTextureVar->GetTextureValue(); + if (pCamoTexture) + pCamoTexture->SetTextureRegenerator( &m_TextureRegen ); + + // Need to get the palettized texture to create the procedural texture from + // somewhere. + m_pCamoPatternTextureVar = m_pMaterial->FindVar( "$camoPatternTexture", &found ); + if( !found ) + { + m_pCamoTextureVar = NULL; + return false; + } + + IMaterialVar *subBoundingBoxMinVar, *subBoundingBoxMaxVar; + + subBoundingBoxMinVar = m_pMaterial->FindVar( "$camoBoundingBoxMin", &found, false ); + if( !found ) + { + m_SubBoundingBoxMin = Vector( 0.0f, 0.0f, 0.0f ); + } + else + { + subBoundingBoxMinVar->GetVecValue( m_SubBoundingBoxMin.Base(), 3 ); + } + + subBoundingBoxMaxVar = m_pMaterial->FindVar( "$camoBoundingBoxMax", &found, false ); + if( !found ) + { + m_SubBoundingBoxMax = Vector( 1.0f, 1.0f, 1.0f ); + } + else + { + subBoundingBoxMaxVar->GetVecValue( m_SubBoundingBoxMax.Base(), 3 ); + } + + LoadCamoPattern(); + GenerateRandomPointsInNormalizedCube(); + + return true; +} + +void CCamoMaterialProxy::GetColors( Vector &diffuseColor, Vector &baseColor, int index, + const Vector &boxMin, const Vector &boxExtents, + const Vector &forward, const Vector &right, const Vector &up, + const Vector& entityPosition ) +{ + Vector position, transformedPosition; + + // hack +// m_pointsInNormalizedBox[index] = Vector( 0.5f, 0.5f, 1.0f ); + + position[0] = m_pointsInNormalizedBox[index][0] * boxExtents[0] + boxMin[0]; + position[1] = m_pointsInNormalizedBox[index][1] * boxExtents[1] + boxMin[1]; + position[2] = m_pointsInNormalizedBox[index][2] * boxExtents[2] + boxMin[2]; + transformedPosition[0] = right[0] * position[0] + forward[0] * position[1] + up[0] * position[2]; + transformedPosition[1] = right[1] * position[0] + forward[1] * position[1] + up[1] * position[2]; + transformedPosition[2] = right[2] * position[0] + forward[2] * position[1] + up[2] * position[2]; + transformedPosition = transformedPosition + entityPosition; + Vector direction = transformedPosition - CurrentViewOrigin(); + VectorNormalize( direction ); + direction = direction * ( COORD_EXTENT * 1.74f ); + Vector endPoint = position + direction; + + // baseColor is already in gamma space +// engine->TraceLineMaterialAndLighting( g_vecInstantaneousRenderOrigin, endPoint, diffuseColor, baseColor ); + engine->TraceLineMaterialAndLighting( transformedPosition, endPoint, diffuseColor, baseColor ); + + // hack - optimize! - convert from linear to gamma space - this should be hidden + diffuseColor[0] = pow( diffuseColor[0], 1.0f / 2.2f ); + diffuseColor[1] = pow( diffuseColor[1], 1.0f / 2.2f ); + diffuseColor[2] = pow( diffuseColor[2], 1.0f / 2.2f ); + +#if 0 + Msg( "%f %f %f\n", + diffuseColor[0], + diffuseColor[1], + diffuseColor[2] ); +#endif + +#if 0 + float max; + max = diffuseColor[0]; + if( diffuseColor[1] > max ) + { + max = diffuseColor[1]; + } + if( diffuseColor[2] > max ) + { + max = diffuseColor[2]; + } + if( max > 1.0f ) + { + max = 1.0f / max; + diffuseColor = diffuseColor * max; + } +#else + if( diffuseColor[0] > 1.0f ) + { + diffuseColor[0] = 1.0f; + } + if( diffuseColor[1] > 1.0f ) + { + diffuseColor[1] = 1.0f; + } + if( diffuseColor[2] > 1.0f ) + { + diffuseColor[2] = 1.0f; + } +#endif + // hack + //baseColor = Vector( 1.0f, 1.0f, 1.0f ); + //diffuseColor = Vector( 1.0f, 1.0f, 1.0f ); +} + + +//----------------------------------------------------------------------------- +// Procedurally generates the camo texture... +//----------------------------------------------------------------------------- +void CCamoMaterialProxy::GenerateCamoTexture( ITexture* pTexture, IVTFTexture *pVTFTexture ) +{ + if (!m_pEnt) + return; + +#if 0 + CamoInstanceData_t *pInstanceData; + pInstanceData = ( CamoInstanceData_t * )FindInstanceData( pEnt ); + if( !pInstanceData ) + { + pInstanceData = ( CamoInstanceData_t * )AllocateInstanceData( pEnt ); + if( !pInstanceData ) + { + return; + } + // init the instance data + } +#endif + + Vector entityPosition; + entityPosition = m_pEnt->GetAbsOrigin(); + + QAngle entityAngles; + entityAngles = m_pEnt->GetAbsAngles(); + + // Get the bounding box for the entity + Vector mins, maxs; + mins = m_pEnt->WorldAlignMins(); + maxs = m_pEnt->WorldAlignMaxs(); + + Vector traceDirection; + Vector traceEnd; + trace_t traceResult; + + Vector forward, right, up; + AngleVectors( entityAngles, &forward, &right, &up ); + + Vector position, transformedPosition; + Vector maxsMinusMins = maxs - mins; + + Vector diffuseColor[256]; + Vector baseColor; + + unsigned char camoPalette[256][3]; + // Calculate the camo palette + //Msg( "start of loop\n" ); + int i; + for( i = 0; i < m_CamoPatternNumColors; i++ ) + { + GetColors( diffuseColor[i], baseColor, i, + mins, maxsMinusMins, forward, right, up, entityPosition ); +#if 1 + camoPalette[i][0] = diffuseColor[i][0] * baseColor[0] * 255.0f; + camoPalette[i][1] = diffuseColor[i][1] * baseColor[1] * 255.0f; + camoPalette[i][2] = diffuseColor[i][2] * baseColor[2] * 255.0f; +#endif +#if 0 + camoPalette[i][0] = baseColor[0] * 255.0f; + camoPalette[i][1] = baseColor[1] * 255.0f; + camoPalette[i][2] = baseColor[2] * 255.0f; +#endif +#if 0 + camoPalette[i][0] = diffuseColor[i][0] * 255.0f; + camoPalette[i][1] = diffuseColor[i][1] * 255.0f; + camoPalette[i][2] = diffuseColor[i][2] * 255.0f; +#endif + } + + int width = pVTFTexture->Width(); + int height = pVTFTexture->Height(); + if( width != m_CamoPatternWidth || height != m_CamoPatternHeight ) + { + return; + } + + unsigned char *imageData = pVTFTexture->ImageData( 0, 0, 0 ); + enum ImageFormat imageFormat = pVTFTexture->Format(); + if( imageFormat != IMAGE_FORMAT_RGB888 ) + { + return; + } + // optimize +#if 1 + int x, y; + for( y = 0; y < height; y++ ) + { + for( x = 0; x < width; x++ ) + { + int offset = 3 * ( x + y * width ); + assert( offset < width * height * 3 ); + int paletteID = m_pCamoPatternImage[x + y * width]; + assert( paletteID < 256 ); +#if 1 + imageData[offset + 0] = camoPalette[paletteID][0]; + imageData[offset + 1] = camoPalette[paletteID][1]; + imageData[offset + 2] = camoPalette[paletteID][2]; +#else + imageData[offset] = 255; + imageData[offset + 1] = 0; + imageData[offset + 2] = 0; +#endif + } + } +#endif +} + + +//----------------------------------------------------------------------------- +// Called when the texture is bound... +//----------------------------------------------------------------------------- +void CCamoMaterialProxy::OnBind( C_BaseEntity *pEntity ) +{ + if( !m_pCamoTextureVar ) + { + return; + } + + m_pEnt = pEntity; + ITexture *pCamoTexture = m_pCamoTextureVar->GetTextureValue(); + pCamoTexture->Download(); + + // Mark it so it doesn't get regenerated on task switch + m_pEnt = NULL; +} + +void CCamoMaterialProxy::LoadCamoPattern( void ) +{ +#if 0 + // hack - need to figure out a name to attach that isn't too long. + m_pCamoPatternImage = + ( unsigned char * )datacache->FindByName( &m_camoImageDataCache, "camopattern" ); + + if( m_pCamoPatternImage ) + { + // is already in the cache. + return m_pCamoPatternImage; + } +#endif + + enum ImageFormat indexImageFormat; + int indexImageSize; +#ifndef _XBOX + float dummyGamma; + if( !TGALoader::GetInfo( m_pCamoPatternTextureVar->GetStringValue(), + &m_CamoPatternWidth, &m_CamoPatternHeight, &indexImageFormat, &dummyGamma ) ) + { + //Warning( "Can't get tga info for hl2/materials/models/combine_elite/camo7paletted.tga for camo material\n" ); + m_pCamoTextureVar = NULL; + return; + } +#else + // xboxissue - no tga support, why implemented this way + Assert( 0 ); + m_pCamoTextureVar = NULL; + return; +#endif + + if( indexImageFormat != IMAGE_FORMAT_I8 ) + { + // Warning( "Camo material texture hl2/materials/models/combine_elite/camo7paletted.tga must be 8-bit greyscale\n" ); + m_pCamoTextureVar = NULL; + return; + } + + indexImageSize = ImageLoader::GetMemRequired( m_CamoPatternWidth, m_CamoPatternHeight, 1, indexImageFormat, false ); +#if 0 + m_pCamoPatternImage = ( unsigned char * ) + datacache->Alloc( &m_camoImageDataCache, indexImageSize, "camopattern" ); +#endif + m_pCamoPatternImage = ( unsigned char * )new unsigned char[indexImageSize]; + if( !m_pCamoPatternImage ) + { + m_pCamoTextureVar = NULL; + return; + } + +#ifndef _XBOX + if( !TGALoader::Load( m_pCamoPatternImage, m_pCamoPatternTextureVar->GetStringValue(), + m_CamoPatternWidth, m_CamoPatternHeight, IMAGE_FORMAT_I8, dummyGamma, false ) ) + { + // Warning( "camo texture hl2/materials/models/combine_elite/camo7paletted.tga must be grey-scale" ); + m_pCamoTextureVar = NULL; + return; + } +#else + // xboxissue - no tga support, why is the camo done this way? + Assert( 0 ); +#endif + + bool colorUsed[256]; + int colorRemap[256]; + // count the number of colors used in the image. + int i; + for( i = 0; i < 256; i++ ) + { + colorUsed[i] = false; + } + for( i = 0; i < indexImageSize; i++ ) + { + colorUsed[m_pCamoPatternImage[i]] = true; + } + m_CamoPatternNumColors = 0; + for( i = 0; i < 256; i++ ) + { + if( colorUsed[i] ) + { + colorRemap[i] = m_CamoPatternNumColors; + m_CamoPatternNumColors++; + } + } + // remap the color to the beginning of the palette. + for( i = 0; i < indexImageSize; i++ ) + { + m_pCamoPatternImage[i] = colorRemap[m_pCamoPatternImage[i]]; + // hack +// m_pCamoPatternImage[i] = 0; + } +} + +void CCamoMaterialProxy::GenerateRandomPointsInNormalizedCube( void ) +{ + m_pointsInNormalizedBox = new Vector[m_CamoPatternNumColors]; + if( !m_pointsInNormalizedBox ) + { + m_pCamoTextureVar = NULL; + return; + } + + int i; + for( i = 0; i < m_CamoPatternNumColors; i++ ) + { + m_pointsInNormalizedBox[i][0] = random->RandomFloat( m_SubBoundingBoxMin[0], m_SubBoundingBoxMax[0] ); + m_pointsInNormalizedBox[i][1] = random->RandomFloat( m_SubBoundingBoxMin[1], m_SubBoundingBoxMax[1] ); + m_pointsInNormalizedBox[i][2] = random->RandomFloat( m_SubBoundingBoxMin[2], m_SubBoundingBoxMax[2] ); + } +} + +IMaterial *CCamoMaterialProxy::GetMaterial() +{ + return m_pMaterial; +} + +EXPOSE_INTERFACE( CCamoMaterialProxy, IMaterialProxy, "Camo" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/cbase.h b/game/client/cbase.h new file mode 100644 index 00000000..c192ff41 --- /dev/null +++ b/game/client/cbase.h @@ -0,0 +1,58 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CBASE_H +#define CBASE_H +#ifdef _WIN32 +#pragma once +#endif + +struct studiohdr_t; + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include "string_t.h" + +// These two have to be included very early +#include +#include + +#include "cdll_util.h" +#include + +#include +#include + + +// This is a precompiled header. Include a bunch of common stuff. +// This is kind of ugly in that it adds a bunch of dependency where it isn't needed. +// But on balance, the compile time is much lower (even incrementally) once the precompiled +// headers contain these headers. +#include "precache_register.h" +#include "c_basecombatweapon.h" +#include "c_basecombatcharacter.h" +#include "gamerules.h" +#include "c_baseplayer.h" +#include "itempents.h" +#include "vphysics_interface.h" +#include "physics.h" +#include "c_recipientfilter.h" +#include "cdll_client_int.h" +#include "worldsize.h" +#include "engine/ivmodelinfo.h" + +#endif // CBASE_H diff --git a/game/client/cdll_bounded_cvars.cpp b/game/client/cdll_bounded_cvars.cpp new file mode 100644 index 00000000..a3f09dec --- /dev/null +++ b/game/client/cdll_bounded_cvars.cpp @@ -0,0 +1,139 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "cdll_bounded_cvars.h" +#include "convar_serverbounded.h" + + +bool g_bForceCLPredictOff = false; + +// ------------------------------------------------------------------------------------------ // +// cl_predict. +// ------------------------------------------------------------------------------------------ // + +class CBoundedCvar_Predict : public ConVar_ServerBounded +{ +public: + CBoundedCvar_Predict() : + ConVar_ServerBounded( "cl_predict", + "1.0", +#if defined(DOD_DLL) || defined(CSTRIKE_DLL) + FCVAR_USERINFO | FCVAR_CHEAT, +#else + FCVAR_USERINFO, +#endif + "Perform client side prediction." ) + { + } + + virtual float GetFloat() const + { + // Used temporarily for CS kill cam. + if ( g_bForceCLPredictOff ) + return 0; + + static const ConVar *pClientPredict = dynamic_cast< const ConVar* >( g_pCVar->FindCommandBase( "sv_client_predict" ) ); + if ( pClientPredict && pClientPredict->GetInt() != -1 ) + { + // Ok, the server wants to control this value. + return pClientPredict->GetFloat(); + } + else + { + return GetBaseFloatValue(); + } + } +}; + +static CBoundedCvar_Predict cl_predict_var; +ConVar_ServerBounded *cl_predict = &cl_predict_var; + + + +// ------------------------------------------------------------------------------------------ // +// cl_interp_ratio. +// ------------------------------------------------------------------------------------------ // + +class CBoundedCvar_InterpRatio : public ConVar_ServerBounded +{ +public: + CBoundedCvar_InterpRatio() : + ConVar_ServerBounded( "cl_interp_ratio", + "2.0", + FCVAR_USERINFO, + "Sets the interpolation amount (final amount is cl_interp_ratio / cl_updaterate)." ) + { + } + + virtual float GetFloat() const + { + static const ConVar *pMin = dynamic_cast< const ConVar* >( g_pCVar->FindCommandBase( "sv_client_min_interp_ratio" ) ); + static const ConVar *pMax = dynamic_cast< const ConVar* >( g_pCVar->FindCommandBase( "sv_client_max_interp_ratio" ) ); + if ( pMin && pMax && pMin->GetFloat() != -1 ) + { + return clamp( GetBaseFloatValue(), pMin->GetFloat(), pMax->GetFloat() ); + } + else + { + return GetBaseFloatValue(); + } + } +}; + +static CBoundedCvar_InterpRatio cl_interp_ratio_var; +ConVar_ServerBounded *cl_interp_ratio = &cl_interp_ratio_var; + + +// ------------------------------------------------------------------------------------------ // +// cl_interp +// ------------------------------------------------------------------------------------------ // + +class CBoundedCvar_Interp : public ConVar_ServerBounded +{ +public: + CBoundedCvar_Interp() : + ConVar_ServerBounded( "cl_interp", + "0.1", + FCVAR_USERINFO, + "Sets the interpolation amount (bounded on low side by server interp ratio settings).", true, 0.0f, true, 0.5f ) + { + } + + virtual float GetFloat() const + { + static const ConVar_ServerBounded *pUpdateRate = dynamic_cast< const ConVar_ServerBounded* >( g_pCVar->FindCommandBase( "cl_updaterate" ) ); + static const ConVar *pMin = dynamic_cast< const ConVar* >( g_pCVar->FindCommandBase( "sv_client_min_interp_ratio" ) ); + if ( pUpdateRate && pMin && pMin->GetFloat() != -1 ) + { + return max( GetBaseFloatValue(), pMin->GetFloat() / pUpdateRate->GetFloat() ); + } + else + { + return GetBaseFloatValue(); + } + } +}; + +static CBoundedCvar_Interp cl_interp_var; +ConVar_ServerBounded *cl_interp = &cl_interp_var; + +float GetClientInterpAmount() +{ + static const ConVar_ServerBounded *pUpdateRate = dynamic_cast< const ConVar_ServerBounded* >( g_pCVar->FindCommandBase( "cl_updaterate" ) ); + if ( pUpdateRate ) + { + // #define FIXME_INTERP_RATIO + return max( cl_interp->GetFloat(), cl_interp_ratio->GetFloat() / pUpdateRate->GetFloat() ); + } + else + { + AssertMsgOnce( false, "GetInterpolationAmount: can't get cl_updaterate cvar." ); + return 0.1; + } +} + diff --git a/game/client/cdll_bounded_cvars.h b/game/client/cdll_bounded_cvars.h new file mode 100644 index 00000000..15951e12 --- /dev/null +++ b/game/client/cdll_bounded_cvars.h @@ -0,0 +1,32 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Provides access to cvars that are bounded in the client DLL. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CDLL_BOUNDED_CVARS_H +#define CDLL_BOUNDED_CVARS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "convar_serverbounded.h" + + +extern ConVar_ServerBounded *cl_predict; +extern ConVar_ServerBounded *cl_interp; + +// Returns cl_interp_ratio / cl_updaterate. +float GetClientInterpAmount(); + + +#if !defined( NO_ENTITY_PREDICTION ) +extern bool g_bForceCLPredictOff; // If this is set to true, then prediction is forced off. Used temporarily for kill cam. +#endif + + + +#endif // CDLL_BOUNDED_CVARS_H + diff --git a/game/client/cdll_client_int.cpp b/game/client/cdll_client_int.cpp new file mode 100644 index 00000000..66999881 --- /dev/null +++ b/game/client/cdll_client_int.cpp @@ -0,0 +1,2003 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include +#include "vgui_int.h" +#include "clientmode.h" +#include "iinput.h" +#include "iviewrender.h" +#include "ivieweffects.h" +#include "ivmodemanager.h" +#include "prediction.h" +#include "clientsideeffects.h" +#include "particlemgr.h" +#include "steam/steam_api.h" +#include "initializer.h" +#include "smoke_fog_overlay.h" +#include "view.h" +#include "ienginevgui.h" +#include "iefx.h" +#include "enginesprite.h" +#include "networkstringtable_clientdll.h" +#include "voice_status.h" +#include "FileSystem.h" +#include "c_te_legacytempents.h" +#include "c_rope.h" +#include "engine/IShadowMgr.h" +#include "engine/IStaticPropMgr.h" +#include "hud_basechat.h" +#include "hud_crosshair.h" +#include "view_shared.h" +#include "env_wind_shared.h" +#include "detailobjectsystem.h" +#include "clienteffectprecachesystem.h" +#include "soundEnvelope.h" +#include "c_basetempentity.h" +#include "materialsystem/imaterialsystemstub.h" +#include "vguimatsurface/IMatSystemSurface.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "c_soundscape.h" +#include "engine/IVDebugOverlay.h" +#include "vguicenterprint.h" +#include "iviewrender_beams.h" +#include "tier0/vprof.h" +#include "engine/IEngineTrace.h" +#include "engine/ivmodelinfo.h" +#include "physics.h" +#include "usermessages.h" +#include "gamestringpool.h" +#include "c_user_message_register.h" +#include "igameuifuncs.h" +#include "saverestoretypes.h" +#include "saverestore.h" +#include "physics_saverestore.h" +#include "igameevents.h" +#include "datacache/idatacache.h" +#include "datacache/imdlcache.h" +#include "kbutton.h" +#include "tier0/icommandline.h" +#include "gamerules_register.h" +#include "vgui_controls/AnimationController.h" +#include "bitmap/tgawriter.h" +#include "c_world.h" +#include "perfvisualbenchmark.h" +#include "soundemittersystem/isoundemittersystembase.h" +#include "hud_closecaption.h" +#include "colorcorrectionmgr.h" +#include "physpropclientside.h" +#include "panelmetaclassmgr.h" +#include "c_vguiscreen.h" +#include "imessagechars.h" +#include "game/client/IGameClientExports.h" +#include "client_factorylist.h" +#include "ragdoll_shared.h" +#include "rendertexture.h" +#include "view_scene.h" +#include "iclientmode.h" +#include "con_nprint.h" +#include "inputsystem/iinputsystem.h" +#include "appframework/IAppSystemGroup.h" +#include "scenefilecache/ISceneFileCache.h" +#include "tier3/tier3.h" +#include "avi/iavi.h" +#include "ihudlcd.h" +#include "toolframework_client.h" +#include "hltvcamera.h" +#include "ixboxsystem.h" +#include "ipresence.h" +#include "engine/imatchmaking.h" +#include "cdll_bounded_cvars.h" +#include "matsys_controls/matsyscontrols.h" +#include "GameStats.h" + +#ifdef PORTAL +#include "PortalRender.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IClientMode *GetClientModeNormal(); + +// IF YOU ADD AN INTERFACE, EXTERN IT IN THE HEADER FILE. +IVEngineClient *engine = NULL; +IVModelRender *modelrender = NULL; +IVEfx *effects = NULL; +IVRenderView *render = NULL; +IVDebugOverlay *debugoverlay = NULL; +IMaterialSystemStub *materials_stub = NULL; +IDataCache *datacache = NULL; +IVModelInfoClient *modelinfo = NULL; +IEngineVGui *enginevgui = NULL; +INetworkStringTableContainer *networkstringtable = NULL; +ISpatialPartition* partition = NULL; +IFileSystem *filesystem = NULL; +IShadowMgr *shadowmgr = NULL; +IStaticPropMgrClient *staticpropmgr = NULL; +IEngineSound *enginesound = NULL; +IUniformRandomStream *random = NULL; +static CGaussianRandomStream s_GaussianRandomStream; +CGaussianRandomStream *randomgaussian = &s_GaussianRandomStream; +ISharedGameRules *sharedgamerules = NULL; +IEngineTrace *enginetrace = NULL; +IGameUIFuncs *gameuifuncs = NULL; +IGameEventManager2 *gameeventmanager = NULL; +ISoundEmitterSystemBase *soundemitterbase = NULL; +IInputSystem *inputsystem = NULL; +ISceneFileCache *scenefilecache = NULL; +IXboxSystem *xboxsystem = NULL; // Xbox 360 only +IMatchmaking *matchmaking = NULL; +IAvi *avi = NULL; +IBik *bik = NULL; +IUploadGameStats *gamestatsuploader = NULL; + + +IGameSystem *SoundEmitterSystem(); +IGameSystem *ToolFrameworkClientSystem(); + +static CSteamAPIContext g_SteamAPIContext; +CSteamAPIContext *steamapicontext = &g_SteamAPIContext; + +// Engine player info, no game related infos here +BEGIN_BYTESWAP_DATADESC( player_info_s ) + DEFINE_ARRAY( name, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ), + DEFINE_FIELD( userID, FIELD_INTEGER ), + DEFINE_ARRAY( guid, FIELD_CHARACTER, SIGNED_GUID_LEN + 1 ), + DEFINE_FIELD( friendsID, FIELD_INTEGER ), + DEFINE_ARRAY( friendsName, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ), + DEFINE_FIELD( fakeplayer, FIELD_BOOLEAN ), + DEFINE_FIELD( ishltv, FIELD_BOOLEAN ), + DEFINE_ARRAY( customFiles, FIELD_INTEGER, MAX_CUSTOM_FILES ), + DEFINE_FIELD( filesDownloaded, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +static bool g_bRequestCacheUsedMaterials = false; +void RequestCacheUsedMaterials() +{ + g_bRequestCacheUsedMaterials = true; +} + +void ProcessCacheUsedMaterials() +{ + if ( !g_bRequestCacheUsedMaterials ) + return; + + g_bRequestCacheUsedMaterials = false; + if ( materials ) + { + materials->CacheUsedMaterials(); + } +} + +// String tables +INetworkStringTable *g_pStringTableParticleEffectNames = NULL; +INetworkStringTable *g_StringTableEffectDispatch = NULL; +INetworkStringTable *g_StringTableVguiScreen = NULL; +INetworkStringTable *g_pStringTableMaterials = NULL; +INetworkStringTable *g_pStringTableInfoPanel = NULL; +INetworkStringTable *g_pStringTableClientSideChoreoScenes = NULL; + +static CGlobalVarsBase dummyvars( true ); +// So stuff that might reference gpGlobals during DLL initialization won't have a NULL pointer. +// Once the engine calls Init on this DLL, this pointer gets assigned to the shared data in the engine +CGlobalVarsBase *gpGlobals = &dummyvars; +class CHudChat; +class CViewRender; +extern CViewRender g_DefaultViewRender; + +extern void StopAllRumbleEffects( void ); + +static C_BaseEntityClassList *s_pClassLists = NULL; +C_BaseEntityClassList::C_BaseEntityClassList() +{ + m_pNextClassList = s_pClassLists; + s_pClassLists = this; +} +C_BaseEntityClassList::~C_BaseEntityClassList() +{ +} + +// Any entities that want an OnDataChanged during simulation register for it here. +class CDataChangedEvent +{ +public: + CDataChangedEvent() {} + CDataChangedEvent( IClientNetworkable *ent, DataUpdateType_t updateType, int *pStoredEvent ) + { + m_pEntity = ent; + m_UpdateType = updateType; + m_pStoredEvent = pStoredEvent; + } + + IClientNetworkable *m_pEntity; + DataUpdateType_t m_UpdateType; + int *m_pStoredEvent; +}; + +ISaveRestoreBlockHandler *GetEntitySaveRestoreBlockHandler(); +ISaveRestoreBlockHandler *GetViewEffectsRestoreBlockHandler(); + +CUtlLinkedList g_DataChangedEvents; +ClientFrameStage_t g_CurFrameStage = FRAME_UNDEFINED; + + +class IMoveHelper; + +void DispatchHudText( const char *pszName ); + +static ConVar s_CV_ShowParticleCounts("showparticlecounts", "0", 0, "Display number of particles drawn per frame"); +static ConVar s_cl_team("cl_team", "default", FCVAR_USERINFO|FCVAR_ARCHIVE, "Default team when joining a game"); +static ConVar s_cl_class("cl_class", "default", FCVAR_USERINFO|FCVAR_ARCHIVE, "Default class when joining a game"); + +// Physics system +bool g_bLevelInitialized; +bool g_bTextMode = false; + +static ConVar *g_pcv_ThreadMode = NULL; + +//----------------------------------------------------------------------------- +// Purpose: interface for gameui to modify voice bans +//----------------------------------------------------------------------------- +class CGameClientExports : public IGameClientExports +{ +public: + // ingame voice manipulation + bool IsPlayerGameVoiceMuted(int playerIndex) + { + return GetClientVoiceMgr()->IsPlayerBlocked(playerIndex); + } + + void MutePlayerGameVoice(int playerIndex) + { + GetClientVoiceMgr()->SetPlayerBlockedState(playerIndex, true); + } + + void UnmutePlayerGameVoice(int playerIndex) + { + GetClientVoiceMgr()->SetPlayerBlockedState(playerIndex, false); + } + + void OnGameUIActivated( void ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "gameui_activated" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + } + + void OnGameUIHidden( void ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "gameui_hidden" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + } +}; + +EXPOSE_SINGLE_INTERFACE( CGameClientExports, IGameClientExports, GAMECLIENTEXPORTS_INTERFACE_VERSION ); + +class CClientDLLSharedAppSystems : public IClientDLLSharedAppSystems +{ +public: + CClientDLLSharedAppSystems() + { + AddAppSystem( "soundemittersystem.dll", SOUNDEMITTERSYSTEM_INTERFACE_VERSION ); + AddAppSystem( "scenefilecache.dll", SCENE_FILE_CACHE_INTERFACE_VERSION ); + } + + virtual int Count() + { + return m_Systems.Count(); + } + virtual char const *GetDllName( int idx ) + { + return m_Systems[ idx ].m_pModuleName; + } + virtual char const *GetInterfaceName( int idx ) + { + return m_Systems[ idx ].m_pInterfaceName; + } +private: + void AddAppSystem( char const *moduleName, char const *interfaceName ) + { + AppSystemInfo_t sys; + sys.m_pModuleName = moduleName; + sys.m_pInterfaceName = interfaceName; + m_Systems.AddToTail( sys ); + } + + CUtlVector< AppSystemInfo_t > m_Systems; +}; + +EXPOSE_SINGLE_INTERFACE( CClientDLLSharedAppSystems, IClientDLLSharedAppSystems, CLIENT_DLL_SHARED_APPSYSTEMS ); + + +//----------------------------------------------------------------------------- +// Helper interface for voice. +//----------------------------------------------------------------------------- +class CHLVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 128; + } + + virtual void UpdateCursorState() + { + } + + virtual bool CanShowSpeakerLabels() + { + return true; + } +}; +static CHLVoiceStatusHelper g_VoiceStatusHelper; + +//----------------------------------------------------------------------------- +// Code to display which entities are having their bones setup each frame. +//----------------------------------------------------------------------------- + +ConVar cl_ShowBoneSetupEnts( "cl_ShowBoneSetupEnts", "0", 0, "Show which entities are having their bones setup each frame." ); + +class CBoneSetupEnt +{ +public: + char m_ModelName[128]; + int m_Index; + int m_Count; +}; + +bool BoneSetupCompare( const CBoneSetupEnt &a, const CBoneSetupEnt &b ) +{ + return a.m_Index < b.m_Index; +} + +CUtlRBTree g_BoneSetupEnts( BoneSetupCompare ); + + +void TrackBoneSetupEnt( C_BaseAnimating *pEnt ) +{ +#ifdef _DEBUG + if ( IsRetail() ) + return; + + if ( !cl_ShowBoneSetupEnts.GetInt() ) + return; + + CBoneSetupEnt ent; + ent.m_Index = pEnt->entindex(); + unsigned short i = g_BoneSetupEnts.Find( ent ); + if ( i == g_BoneSetupEnts.InvalidIndex() ) + { + Q_strncpy( ent.m_ModelName, modelinfo->GetModelName( pEnt->GetModel() ), sizeof( ent.m_ModelName ) ); + ent.m_Count = 1; + g_BoneSetupEnts.Insert( ent ); + } + else + { + g_BoneSetupEnts[i].m_Count++; + } +#endif +} + +void DisplayBoneSetupEnts() +{ +#ifdef _DEBUG + if ( IsRetail() ) + return; + + if ( !cl_ShowBoneSetupEnts.GetInt() ) + return; + + unsigned short i; + int nElements = 0; + for ( i=g_BoneSetupEnts.FirstInorder(); i != g_BoneSetupEnts.LastInorder(); i=g_BoneSetupEnts.NextInorder( i ) ) + ++nElements; + + engine->Con_NPrintf( 0, "%d bone setup ents (name/count/entindex) ------------", nElements ); + + con_nprint_s printInfo; + printInfo.time_to_live = -1; + printInfo.fixed_width_font = true; + printInfo.color[0] = printInfo.color[1] = printInfo.color[2] = 1; + + printInfo.index = 2; + for ( i=g_BoneSetupEnts.FirstInorder(); i != g_BoneSetupEnts.LastInorder(); i=g_BoneSetupEnts.NextInorder( i ) ) + { + CBoneSetupEnt *pEnt = &g_BoneSetupEnts[i]; + + if ( pEnt->m_Count >= 3 ) + { + printInfo.color[0] = 1; + printInfo.color[1] = printInfo.color[2] = 0; + } + else if ( pEnt->m_Count == 2 ) + { + printInfo.color[0] = (float)200 / 255; + printInfo.color[1] = (float)220 / 255; + printInfo.color[2] = 0; + } + else + { + printInfo.color[0] = printInfo.color[0] = printInfo.color[0] = 1; + } + engine->Con_NXPrintf( &printInfo, "%25s / %3d / %3d", pEnt->m_ModelName, pEnt->m_Count, pEnt->m_Index ); + printInfo.index++; + } + + g_BoneSetupEnts.RemoveAll(); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: engine to client .dll interface +//----------------------------------------------------------------------------- +class CHLClient : public IBaseClientDLL +{ +public: + CHLClient(); + + virtual int Init( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physicsFactory, CGlobalVarsBase *pGlobals ); + + virtual void PostInit(); + virtual void Shutdown( void ); + + virtual void LevelInitPreEntity( const char *pMapName ); + virtual void LevelInitPostEntity(); + virtual void LevelShutdown( void ); + + virtual ClientClass *GetAllClasses( void ); + + virtual int HudVidInit( void ); + virtual void HudProcessInput( bool bActive ); + virtual void HudUpdate( bool bActive ); + virtual void HudReset( void ); + virtual void HudText( const char * message ); + + // Mouse Input Interfaces + virtual void IN_ActivateMouse( void ); + virtual void IN_DeactivateMouse( void ); + virtual void IN_Accumulate( void ); + virtual void IN_ClearStates( void ); + virtual bool IN_IsKeyDown( const char *name, bool& isdown ); + // Raw signal + virtual int IN_KeyEvent( int eventcode, ButtonCode_t keynum, const char *pszCurrentBinding ); + virtual void IN_SetSampleTime( float frametime ); + // Create movement command + virtual void CreateMove ( int sequence_number, float input_sample_frametime, bool active ); + virtual void ExtraMouseSample( float frametime, bool active ); + virtual bool WriteUsercmdDeltaToBuffer( bf_write *buf, int from, int to, bool isnewcommand ); + virtual void EncodeUserCmdToBuffer( bf_write& buf, int slot ); + virtual void DecodeUserCmdFromBuffer( bf_read& buf, int slot ); + + + virtual void View_Render( vrect_t *rect ); + virtual void RenderView( const CViewSetup &view, int nClearFlags, int whatToDraw ); + virtual void View_Fade( ScreenFade_t *pSF ); + + virtual void SetCrosshairAngle( const QAngle& angle ); + + virtual void InitSprite( CEngineSprite *pSprite, const char *loadname ); + virtual void ShutdownSprite( CEngineSprite *pSprite ); + + virtual int GetSpriteSize( void ) const; + + virtual void VoiceStatus( int entindex, qboolean bTalking ); + + virtual void InstallStringTableCallback( const char *tableName ); + + virtual void FrameStageNotify( ClientFrameStage_t curStage ); + + virtual bool DispatchUserMessage( int msg_type, bf_read &msg_data ); + + // Save/restore system hooks + virtual CSaveRestoreData *SaveInit( int size ); + virtual void SaveWriteFields( CSaveRestoreData *, const char *, void *, datamap_t *, typedescription_t *, int ); + virtual void SaveReadFields( CSaveRestoreData *, const char *, void *, datamap_t *, typedescription_t *, int ); + virtual void PreSave( CSaveRestoreData * ); + virtual void Save( CSaveRestoreData * ); + virtual void WriteSaveHeaders( CSaveRestoreData * ); + virtual void ReadRestoreHeaders( CSaveRestoreData * ); + virtual void Restore( CSaveRestoreData *, bool ); + virtual void DispatchOnRestore(); + virtual void WriteSaveGameScreenshot( const char *pFilename ); + + // Given a list of "S(wavname) S(wavname2)" tokens, look up the localized text and emit + // the appropriate close caption if running with closecaption = 1 + virtual void EmitSentenceCloseCaption( char const *tokenstream ); + virtual void EmitCloseCaption( char const *captionname, float duration ); + + virtual CStandardRecvProxies* GetStandardRecvProxies(); + + virtual bool CanRecordDemo( char *errorMsg, int length ) const; + + // save game screenshot writing + virtual void WriteSaveGameScreenshotOfSize( const char *pFilename, int width, int height ); + + // Gets the location of the player viewpoint + virtual bool GetPlayerView( CViewSetup &playerView ); + + // Matchmaking + virtual void SetupGameProperties( CUtlVector< XUSER_CONTEXT > &contexts, CUtlVector< XUSER_PROPERTY > &properties ); + virtual uint GetPresenceID( const char *pIDName ); + virtual const char *GetPropertyIdString( const uint id ); + virtual void GetPropertyDisplayString( uint id, uint value, char *pOutput, int nBytes ); + virtual void StartStatsReporting( HANDLE handle, bool bArbitrated ); + + virtual void InvalidateMdlCache(); +public: + void PrecacheMaterial( const char *pMaterialName ); + +private: + void UncacheAllMaterials( ); + void ResetStringTablePointers(); + + CUtlVector< IMaterial * > m_CachedMaterials; +}; + + +CHLClient gHLClient; +IBaseClientDLL *clientdll = &gHLClient; + +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CHLClient, IBaseClientDLL, CLIENT_DLL_INTERFACE_VERSION, gHLClient ); + + +//----------------------------------------------------------------------------- +// Precaches a material +//----------------------------------------------------------------------------- +void PrecacheMaterial( const char *pMaterialName ) +{ + gHLClient.PrecacheMaterial( pMaterialName ); +} + +//----------------------------------------------------------------------------- +// Converts a previously precached material into an index +//----------------------------------------------------------------------------- +int GetMaterialIndex( const char *pMaterialName ) +{ + if (pMaterialName) + { + int nIndex = g_pStringTableMaterials->FindStringIndex( pMaterialName ); + Assert( nIndex >= 0 ); + if (nIndex >= 0) + return nIndex; + } + + // This is the invalid string index + return 0; +} + +//----------------------------------------------------------------------------- +// Converts precached material indices into strings +//----------------------------------------------------------------------------- +const char *GetMaterialNameFromIndex( int nIndex ) +{ + if (nIndex != (g_pStringTableMaterials->GetMaxStrings() - 1)) + { + return g_pStringTableMaterials->GetString( nIndex ); + } + else + { + return NULL; + } +} + + +//----------------------------------------------------------------------------- +// Precaches a particle system +//----------------------------------------------------------------------------- +void PrecacheParticleSystem( const char *pParticleSystemName ) +{ + g_pStringTableParticleEffectNames->AddString( false, pParticleSystemName ); + g_pParticleSystemMgr->PrecacheParticleSystem( pParticleSystemName ); +} + + +//----------------------------------------------------------------------------- +// Converts a previously precached particle system into an index +//----------------------------------------------------------------------------- +int GetParticleSystemIndex( const char *pParticleSystemName ) +{ + if ( pParticleSystemName ) + { + int nIndex = g_pStringTableParticleEffectNames->FindStringIndex( pParticleSystemName ); + if ( nIndex != INVALID_STRING_INDEX ) + return nIndex; + DevWarning("Client: Missing precache for particle system \"%s\"!\n", pParticleSystemName ); + } + + // This is the invalid string index + return 0; +} + +//----------------------------------------------------------------------------- +// Converts precached particle system indices into strings +//----------------------------------------------------------------------------- +const char *GetParticleSystemNameFromIndex( int nIndex ) +{ + if ( nIndex < g_pStringTableParticleEffectNames->GetMaxStrings() ) + return g_pStringTableParticleEffectNames->GetString( nIndex ); + return "error"; +} + +//----------------------------------------------------------------------------- +// Returns true if host_thread_mode is set to non-zero (and engine is running in threaded mode) +//----------------------------------------------------------------------------- +bool IsEngineThreaded() +{ + if ( g_pcv_ThreadMode ) + { + return g_pcv_ThreadMode->GetBool(); + } + return false; +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- + +CHLClient::CHLClient() +{ + // Kinda bogus, but the logic in the engine is too convoluted to put it there + g_bLevelInitialized = false; +} + + +extern IGameSystem *ViewportClientSystem(); + +//----------------------------------------------------------------------------- +// Purpose: Called when the DLL is first loaded. +// Input : engineFactory - +// Output : int +//----------------------------------------------------------------------------- +int CHLClient::Init( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physicsFactory, CGlobalVarsBase *pGlobals ) +{ + InitCRTMemDebug(); + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + + // Hook up global variables + gpGlobals = pGlobals; + + ConnectTier1Libraries( &appSystemFactory, 1 ); + ConnectTier2Libraries( &appSystemFactory, 1 ); + ConnectTier3Libraries( &appSystemFactory, 1 ); + + g_SteamAPIContext.Init(); + + // We aren't happy unless we get all of our interfaces. + // please don't collapse this into one monolithic boolean expression (impossible to debug) + if ( (engine = (IVEngineClient *)appSystemFactory( VENGINE_CLIENT_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (modelrender = (IVModelRender *)appSystemFactory( VENGINE_HUDMODEL_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (effects = (IVEfx *)appSystemFactory( VENGINE_EFFECTS_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (enginetrace = (IEngineTrace *)appSystemFactory( INTERFACEVERSION_ENGINETRACE_CLIENT, NULL )) == NULL ) + return false; + if ( (render = (IVRenderView *)appSystemFactory( VENGINE_RENDERVIEW_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (debugoverlay = (IVDebugOverlay *)appSystemFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (datacache = (IDataCache*)appSystemFactory(DATACACHE_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( !mdlcache ) + return false; + if ( (modelinfo = (IVModelInfoClient *)appSystemFactory(VMODELINFO_CLIENT_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (enginevgui = (IEngineVGui *)appSystemFactory(VENGINE_VGUI_VERSION, NULL )) == NULL ) + return false; + if ( (networkstringtable = (INetworkStringTableContainer *)appSystemFactory(INTERFACENAME_NETWORKSTRINGTABLECLIENT,NULL)) == NULL ) + return false; + if ( (partition = (ISpatialPartition *)appSystemFactory(INTERFACEVERSION_SPATIALPARTITION, NULL)) == NULL ) + return false; + if ( (shadowmgr = (IShadowMgr *)appSystemFactory(ENGINE_SHADOWMGR_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (staticpropmgr = (IStaticPropMgrClient *)appSystemFactory(INTERFACEVERSION_STATICPROPMGR_CLIENT, NULL)) == NULL ) + return false; + if ( (enginesound = (IEngineSound *)appSystemFactory(IENGINESOUND_CLIENT_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (filesystem = (IFileSystem *)appSystemFactory(FILESYSTEM_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (random = (IUniformRandomStream *)appSystemFactory(VENGINE_CLIENT_RANDOM_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (gameuifuncs = (IGameUIFuncs * )appSystemFactory( VENGINE_GAMEUIFUNCS_VERSION, NULL )) == NULL ) + return false; + if ( (gameeventmanager = (IGameEventManager2 *)appSystemFactory(INTERFACEVERSION_GAMEEVENTSMANAGER2,NULL)) == NULL ) + return false; + if ( (soundemitterbase = (ISoundEmitterSystemBase *)appSystemFactory(SOUNDEMITTERSYSTEM_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (inputsystem = (IInputSystem *)appSystemFactory(INPUTSYSTEM_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( IsPC() && (avi = (IAvi *)appSystemFactory(AVI_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (bik = (IBik *)appSystemFactory(BIK_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (scenefilecache = (ISceneFileCache *)appSystemFactory( SCENE_FILE_CACHE_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( IsX360() && (xboxsystem = (IXboxSystem *)appSystemFactory( XBOXSYSTEM_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( IsX360() && (matchmaking = (IMatchmaking *)appSystemFactory( VENGINE_MATCHMAKING_VERSION, NULL )) == NULL ) + return false; +#ifndef _XBOX + if ( ( gamestatsuploader = (IUploadGameStats *)appSystemFactory( INTERFACEVERSION_UPLOADGAMESTATS, NULL )) == NULL ) + return false; +#endif + if (!g_pMatSystemSurface) + return false; + + factorylist_t factories; + factories.appSystemFactory = appSystemFactory; + factories.physicsFactory = physicsFactory; + FactoryList_Store( factories ); + + // Yes, both the client and game .dlls will try to Connect, the soundemittersystem.dll will handle this gracefully + if ( !soundemitterbase->Connect( appSystemFactory ) ) + { + return false; + } + + if ( CommandLine()->FindParm( "-textmode" ) ) + g_bTextMode = true; + + if ( CommandLine()->FindParm( "-makedevshots" ) ) + g_MakingDevShots = true; + + // Not fatal if the material system stub isn't around. + materials_stub = (IMaterialSystemStub*)appSystemFactory( MATERIAL_SYSTEM_STUB_INTERFACE_VERSION, NULL ); + + if( !g_pMaterialSystemHardwareConfig ) + return false; + + // Hook up the gaussian random number generator + s_GaussianRandomStream.AttachToStream( random ); + + // Initialize the console variables. + ConVar_Register( FCVAR_CLIENTDLL ); + + g_pcv_ThreadMode = g_pCVar->FindVar( "host_thread_mode" ); + + if (!Initializer::InitializeAllObjects()) + return false; + + if (!ParticleMgr()->Init(MAX_TOTAL_PARTICLES, materials)) + return false; + + + if (!VGui_Startup( appSystemFactory )) + return false; + + vgui::VGui_InitMatSysInterfacesList( "ClientDLL", &appSystemFactory, 1 ); + + // Add the client systems. + + // Client Leaf System has to be initialized first, since DetailObjectSystem uses it + IGameSystem::Add( GameStringSystem() ); + IGameSystem::Add( SoundEmitterSystem() ); + IGameSystem::Add( ToolFrameworkClientSystem() ); + IGameSystem::Add( ClientLeafSystem() ); + IGameSystem::Add( DetailObjectSystem() ); + IGameSystem::Add( ViewportClientSystem() ); + IGameSystem::Add( ClientEffectPrecacheSystem() ); + IGameSystem::Add( g_pClientShadowMgr ); + IGameSystem::Add( g_pColorCorrectionMgr ); // NOTE: This must happen prior to ClientThinkList (color correction is updated there) + IGameSystem::Add( ClientThinkList() ); + IGameSystem::Add( ClientSoundscapeSystem() ); + IGameSystem::Add( PerfVisualBenchmark() ); + +#if defined( CLIENT_DLL ) && defined( COPY_CHECK_STRESSTEST ) + IGameSystem::Add( GetPredictionCopyTester() ); +#endif + + modemanager->Init( ); + + g_pClientMode->InitViewport(); + + gHUD.Init(); + + g_pClientMode->Init(); + + if ( !IGameSystem::InitAllSystems() ) + return false; + + g_pClientMode->Enable(); + + if ( !view ) + { + view = ( IViewRender * )&g_DefaultViewRender; + } + + view->Init(); + vieweffects->Init(); + + C_BaseTempEntity::PrecacheTempEnts(); + + input->Init_All(); + + VGui_CreateGlobalPanels(); + + InitSmokeFogOverlay(); + + // Register user messages.. + CUserMessageRegister::RegisterAll(); + + ClientVoiceMgr_Init(); + + // Embed voice status icons inside chat element + { + vgui::VPANEL parent = enginevgui->GetPanel( PANEL_CLIENTDLL ); + GetClientVoiceMgr()->Init( &g_VoiceStatusHelper, parent ); + } + + if ( !PhysicsDLLInit( physicsFactory ) ) + return false; + + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEntitySaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetPhysSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetViewEffectsRestoreBlockHandler() ); + + ClientWorldFactoryInit(); + + C_BaseAnimating::InitBoneSetupThreadPool(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Called after client & server DLL are loaded and all systems initialized +//----------------------------------------------------------------------------- +void CHLClient::PostInit() +{ + IGameSystem::PostInitAllSystems(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the client .dll is being dismissed +//----------------------------------------------------------------------------- +void CHLClient::Shutdown( void ) +{ + C_BaseAnimating::ShutdownBoneSetupThreadPool(); + ClientWorldFactoryShutdown(); + + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetViewEffectsRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetPhysSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEntitySaveRestoreBlockHandler() ); + + ClientVoiceMgr_Shutdown(); + + Initializer::FreeAllObjects(); + + g_pClientMode->Disable(); + g_pClientMode->Shutdown(); + + input->Shutdown_All(); + C_BaseTempEntity::ClearDynamicTempEnts(); + TermSmokeFogOverlay(); + view->Shutdown(); + g_pParticleSystemMgr->UncacheAllParticleSystems(); + UncacheAllMaterials(); + + IGameSystem::ShutdownAllSystems(); + + gHUD.Shutdown(); + VGui_Shutdown(); + + ClearKeyValuesCache(); + + DisconnectTier3Libraries( ); + DisconnectTier2Libraries( ); + ConVar_Unregister(); + DisconnectTier1Libraries( ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Called when the game initializes +// and whenever the vid_mode is changed +// so the HUD can reinitialize itself. +// Output : int +//----------------------------------------------------------------------------- +int CHLClient::HudVidInit( void ) +{ + gHUD.VidInit(); + + GetClientVoiceMgr()->VidInit(); + + return 1; +} + +//----------------------------------------------------------------------------- +// Method used to allow the client to filter input messages before the +// move record is transmitted to the server +//----------------------------------------------------------------------------- +void CHLClient::HudProcessInput( bool bActive ) +{ + g_pClientMode->ProcessInput( bActive ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when shared data gets changed, allows dll to modify data +// Input : bActive - +//----------------------------------------------------------------------------- +void CHLClient::HudUpdate( bool bActive ) +{ + float frametime = gpGlobals->frametime; + + GetClientVoiceMgr()->Frame( frametime ); + + gHUD.UpdateHud( bActive ); + + { + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); + IGameSystem::UpdateAllSystems( frametime ); + } + + // run vgui animations + vgui::GetAnimationController()->UpdateAnimations( engine->Time() ); + + hudlcd->SetGlobalStat( "(time_int)", VarArgs( "%d", (int)gpGlobals->curtime ) ); + hudlcd->SetGlobalStat( "(time_float)", VarArgs( "%.2f", gpGlobals->curtime ) ); + + // I don't think this is necessary any longer, but I will leave it until + // I can check into this further. + C_BaseTempEntity::CheckDynamicTempEnts(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called to restore to "non"HUD state. +//----------------------------------------------------------------------------- +void CHLClient::HudReset( void ) +{ + gHUD.VidInit(); + PhysicsReset(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called to add hud text message +//----------------------------------------------------------------------------- +void CHLClient::HudText( const char * message ) +{ + DispatchHudText( message ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : ClientClass +//----------------------------------------------------------------------------- +ClientClass *CHLClient::GetAllClasses( void ) +{ + return g_pClientClassHead; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLClient::IN_ActivateMouse( void ) +{ + input->ActivateMouse(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLClient::IN_DeactivateMouse( void ) +{ + input->DeactivateMouse(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLClient::IN_Accumulate ( void ) +{ + input->AccumulateMouse(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLClient::IN_ClearStates ( void ) +{ + input->ClearStates(); +} + +//----------------------------------------------------------------------------- +// Purpose: Engine can query for particular keys +// Input : *name - +//----------------------------------------------------------------------------- +bool CHLClient::IN_IsKeyDown( const char *name, bool& isdown ) +{ + kbutton_t *key = input->FindKey( name ); + if ( !key ) + { + return false; + } + + isdown = ( key->state & 1 ) ? true : false; + + // Found the key by name + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Engine can issue a key event +// Input : eventcode - +// keynum - +// *pszCurrentBinding - +// Output : int +//----------------------------------------------------------------------------- +int CHLClient::IN_KeyEvent( int eventcode, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + return input->KeyEvent( eventcode, keynum, pszCurrentBinding ); +} + +void CHLClient::ExtraMouseSample( float frametime, bool active ) +{ + Assert( C_BaseEntity::IsAbsRecomputationsEnabled() ); + Assert( C_BaseEntity::IsAbsQueriesValid() ); + + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); + + MDLCACHE_CRITICAL_SECTION(); + input->ExtraMouseSample( frametime, active ); +} + +void CHLClient::IN_SetSampleTime( float frametime ) +{ + input->Joystick_SetSampleTime( frametime ); + input->IN_SetSampleTime( frametime ); +} +//----------------------------------------------------------------------------- +// Purpose: Fills in usercmd_s structure based on current view angles and key/controller inputs +// Input : frametime - timestamp for last frame +// *cmd - the command to fill in +// active - whether the user is fully connected to a server +//----------------------------------------------------------------------------- +void CHLClient::CreateMove ( int sequence_number, float input_sample_frametime, bool active ) +{ + + Assert( C_BaseEntity::IsAbsRecomputationsEnabled() ); + Assert( C_BaseEntity::IsAbsQueriesValid() ); + + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); + + MDLCACHE_CRITICAL_SECTION(); + input->CreateMove( sequence_number, input_sample_frametime, active ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *buf - +// from - +// to - +//----------------------------------------------------------------------------- +bool CHLClient::WriteUsercmdDeltaToBuffer( bf_write *buf, int from, int to, bool isnewcommand ) +{ + return input->WriteUsercmdDeltaToBuffer( buf, from, to, isnewcommand ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +// buffersize - +// slot - +//----------------------------------------------------------------------------- +void CHLClient::EncodeUserCmdToBuffer( bf_write& buf, int slot ) +{ + input->EncodeUserCmdToBuffer( buf, slot ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +// buffersize - +// slot - +//----------------------------------------------------------------------------- +void CHLClient::DecodeUserCmdFromBuffer( bf_read& buf, int slot ) +{ + input->DecodeUserCmdFromBuffer( buf, slot ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLClient::View_Render( vrect_t *rect ) +{ + VPROF( "View_Render" ); + + // UNDONE: This gets hit at startup sometimes, investigate - will cause NaNs in calcs inside Render() + if ( rect->width == 0 || rect->height == 0 ) + return; + + view->Render( rect ); + UpdatePerfStats(); +} + + +//----------------------------------------------------------------------------- +// Gets the location of the player viewpoint +//----------------------------------------------------------------------------- +bool CHLClient::GetPlayerView( CViewSetup &playerView ) +{ + playerView = *view->GetPlayerViewSetup(); + return true; +} + +//----------------------------------------------------------------------------- +// Matchmaking +//----------------------------------------------------------------------------- +void CHLClient::SetupGameProperties( CUtlVector< XUSER_CONTEXT > &contexts, CUtlVector< XUSER_PROPERTY > &properties ) +{ + presence->SetupGameProperties( contexts, properties ); +} + +uint CHLClient::GetPresenceID( const char *pIDName ) +{ + return presence->GetPresenceID( pIDName ); +} + +const char *CHLClient::GetPropertyIdString( const uint id ) +{ + return presence->GetPropertyIdString( id ); +} + +void CHLClient::GetPropertyDisplayString( uint id, uint value, char *pOutput, int nBytes ) +{ + presence->GetPropertyDisplayString( id, value, pOutput, nBytes ); +} + +void CHLClient::StartStatsReporting( HANDLE handle, bool bArbitrated ) +{ + presence->StartStatsReporting( handle, bArbitrated ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CHLClient::InvalidateMdlCache() +{ + C_BaseAnimating *pAnimating; + for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) ) + { + pAnimating = dynamic_cast(pEntity); + if ( pAnimating ) + { + pAnimating->InvalidateMdlCache(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSF - +//----------------------------------------------------------------------------- +void CHLClient::View_Fade( ScreenFade_t *pSF ) +{ + if ( pSF != NULL ) + vieweffects->Fade( *pSF ); +} + +//----------------------------------------------------------------------------- +// Purpose: Per level init +//----------------------------------------------------------------------------- +void CHLClient::LevelInitPreEntity( char const* pMapName ) +{ + // HACK: Bogus, but the logic is too complicated in the engine + if (g_bLevelInitialized) + return; + g_bLevelInitialized = true; + + input->LevelInit(); + + vieweffects->LevelInit(); + + // Tell mode manager that map is changing + modemanager->LevelInit( pMapName ); + ParticleMgr()->LevelInit(); + + hudlcd->SetGlobalStat( "(mapname)", pMapName ); + + C_BaseTempEntity::ClearDynamicTempEnts(); + clienteffects->Flush(); + view->LevelInit(); + tempents->LevelInit(); + ResetToneMapping(1.0); + + IGameSystem::LevelInitPreEntityAllSystems(pMapName); + + ResetWindspeed(); + +#if !defined( NO_ENTITY_PREDICTION ) + // don't do prediction if single player! + // don't set direct because of FCVAR_USERINFO + if ( gpGlobals->maxClients > 1 ) + { + if ( !cl_predict->GetInt() ) + { + engine->ClientCmd( "cl_predict 1" ); + } + } + else + { + if ( cl_predict->GetInt() ) + { + engine->ClientCmd( "cl_predict 0" ); + } + } +#endif + + // Check low violence settings for this map + g_RagdollLVManager.SetLowViolence( pMapName ); + + gHUD.LevelInit(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Per level init +//----------------------------------------------------------------------------- +void CHLClient::LevelInitPostEntity( ) +{ + IGameSystem::LevelInitPostEntityAllSystems(); + C_PhysPropClientside::RecreateAll(); + internalCenterPrint->Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset our global string table pointers +//----------------------------------------------------------------------------- +void CHLClient::ResetStringTablePointers() +{ + g_pStringTableParticleEffectNames = NULL; + g_StringTableEffectDispatch = NULL; + g_StringTableVguiScreen = NULL; + g_pStringTableMaterials = NULL; + g_pStringTableInfoPanel = NULL; + g_pStringTableClientSideChoreoScenes = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Per level de-init +//----------------------------------------------------------------------------- +void CHLClient::LevelShutdown( void ) +{ + // HACK: Bogus, but the logic is too complicated in the engine + if (!g_bLevelInitialized) + return; + + g_bLevelInitialized = false; + + // Disable abs recomputations when everything is shutting down + CBaseEntity::EnableAbsRecomputations( false ); + + // Level shutdown sequence. + // First do the pre-entity shutdown of all systems + IGameSystem::LevelShutdownPreEntityAllSystems(); + + C_PhysPropClientside::DestroyAll(); + + modemanager->LevelShutdown(); + + // Remove temporary entities before removing entities from the client entity list so that the te_* may + // clean up before hand. + tempents->LevelShutdown(); + + // Now release/delete the entities + cl_entitylist->Release(); + + C_BaseEntityClassList *pClassList = s_pClassLists; + while ( pClassList ) + { + pClassList->LevelShutdown(); + pClassList = pClassList->m_pNextClassList; + } + + // Now do the post-entity shutdown of all systems + IGameSystem::LevelShutdownPostEntityAllSystems(); + + view->LevelShutdown(); + beams->ClearBeams(); + ParticleMgr()->RemoveAllEffects(); + + StopAllRumbleEffects(); + + gHUD.LevelShutdown(); + + internalCenterPrint->Clear(); + + messagechars->Clear(); + + g_pParticleSystemMgr->UncacheAllParticleSystems(); + UncacheAllMaterials(); + +#ifdef _XBOX + ReleaseRenderTargets(); +#endif + + // string tables are cleared on disconnect from a server, so reset our global pointers to NULL + ResetStringTablePointers(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Engine received crosshair offset ( autoaim ) +// Input : angle - +//----------------------------------------------------------------------------- +void CHLClient::SetCrosshairAngle( const QAngle& angle ) +{ + CHudCrosshair *crosshair = GET_HUDELEMENT( CHudCrosshair ); + if ( crosshair ) + { + crosshair->SetCrosshairAngle( angle ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Helper to initialize sprite from .spr semaphor +// Input : *pSprite - +// *loadname - +//----------------------------------------------------------------------------- +void CHLClient::InitSprite( CEngineSprite *pSprite, const char *loadname ) +{ + if ( pSprite ) + { + pSprite->Init( loadname ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSprite - +//----------------------------------------------------------------------------- +void CHLClient::ShutdownSprite( CEngineSprite *pSprite ) +{ + if ( pSprite ) + { + pSprite->Shutdown(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tells engine how much space to allocate for sprite objects +// Output : int +//----------------------------------------------------------------------------- +int CHLClient::GetSpriteSize( void ) const +{ + return sizeof( CEngineSprite ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : entindex - +// bTalking - +//----------------------------------------------------------------------------- +void CHLClient::VoiceStatus( int entindex, qboolean bTalking ) +{ + GetClientVoiceMgr()->UpdateSpeakerStatus( entindex, !!bTalking ); +} + + +//----------------------------------------------------------------------------- +// Called when the string table for materials changes +//----------------------------------------------------------------------------- +void OnMaterialStringTableChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, void const *newData ) +{ + // Make sure this puppy is precached + gHLClient.PrecacheMaterial( newString ); + RequestCacheUsedMaterials(); +} + + +//----------------------------------------------------------------------------- +// Called when the string table for particle systems changes +//----------------------------------------------------------------------------- +void OnParticleSystemStringTableChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, void const *newData ) +{ + // Make sure this puppy is precached + g_pParticleSystemMgr->PrecacheParticleSystem( newString ); + RequestCacheUsedMaterials(); +} + + +//----------------------------------------------------------------------------- +// Called when the string table for VGUI changes +//----------------------------------------------------------------------------- +void OnVguiScreenTableChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, void const *newData ) +{ + // Make sure this puppy is precached + vgui::Panel *pPanel = PanelMetaClassMgr()->CreatePanelMetaClass( newString, 100, NULL, NULL ); + if ( pPanel ) + PanelMetaClassMgr()->DestroyPanelMetaClass( pPanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Preload the string on the client (if single player it should already be in the cache from the server!!!) +// Input : *object - +// *stringTable - +// stringNumber - +// *newString - +// *newData - +//----------------------------------------------------------------------------- +void OnSceneStringTableChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, void const *newData ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Hook up any callbacks here, the table definition has been parsed but +// no data has been added yet +//----------------------------------------------------------------------------- +void CHLClient::InstallStringTableCallback( const char *tableName ) +{ + // Here, cache off string table IDs + if (!Q_strcasecmp(tableName, "VguiScreen")) + { + // Look up the id + g_StringTableVguiScreen = networkstringtable->FindTable( tableName ); + + // When the material list changes, we need to know immediately + g_StringTableVguiScreen->SetStringChangedCallback( NULL, OnVguiScreenTableChanged ); + } + else if (!Q_strcasecmp(tableName, "Materials")) + { + // Look up the id + g_pStringTableMaterials = networkstringtable->FindTable( tableName ); + + // When the material list changes, we need to know immediately + g_pStringTableMaterials->SetStringChangedCallback( NULL, OnMaterialStringTableChanged ); + } + else if ( !Q_strcasecmp( tableName, "EffectDispatch" ) ) + { + g_StringTableEffectDispatch = networkstringtable->FindTable( tableName ); + } + else if ( !Q_strcasecmp( tableName, "InfoPanel" ) ) + { + g_pStringTableInfoPanel = networkstringtable->FindTable( tableName ); + } + else if ( !Q_strcasecmp( tableName, "Scenes" ) ) + { + g_pStringTableClientSideChoreoScenes = networkstringtable->FindTable( tableName ); + g_pStringTableClientSideChoreoScenes->SetStringChangedCallback( NULL, OnSceneStringTableChanged ); + } + else if ( !Q_strcasecmp( tableName, "ParticleEffectNames" ) ) + { + g_pStringTableParticleEffectNames = networkstringtable->FindTable( tableName ); + networkstringtable->SetAllowClientSideAddString( g_pStringTableParticleEffectNames, true ); + // When the particle system list changes, we need to know immediately + g_pStringTableParticleEffectNames->SetStringChangedCallback( NULL, OnParticleSystemStringTableChanged ); + } + + + InstallStringTableCallback_GameRules(); +} + + +//----------------------------------------------------------------------------- +// Material precache +//----------------------------------------------------------------------------- +void CHLClient::PrecacheMaterial( const char *pMaterialName ) +{ + Assert( pMaterialName ); + + int nLen = Q_strlen( pMaterialName ); + char *pTempBuf = (char*)stackalloc( nLen + 1 ); + memcpy( pTempBuf, pMaterialName, nLen + 1 ); + char *pFound = Q_strstr( pTempBuf, ".vmt\0" ); + if ( pFound ) + { + *pFound = 0; + } + + IMaterial *pMaterial = materials->FindMaterial( pTempBuf, TEXTURE_GROUP_PRECACHED ); + if ( !IsErrorMaterial( pMaterial ) ) + { + pMaterial->IncrementReferenceCount(); + m_CachedMaterials.AddToTail( pMaterial ); + } +} + +void CHLClient::UncacheAllMaterials( ) +{ + for (int i = m_CachedMaterials.Count(); --i >= 0; ) + { + m_CachedMaterials[i]->DecrementReferenceCount(); + } + m_CachedMaterials.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszName - +// iSize - +// *pbuf - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHLClient::DispatchUserMessage( int msg_type, bf_read &msg_data ) +{ + return usermessages->DispatchUserMessage( msg_type, msg_data ); +} + + +void SimulateEntities() +{ + VPROF_BUDGET("Client SimulateEntities", VPROF_BUDGETGROUP_CLIENT_SIM); + + // Service timer events (think functions). + ClientThinkList()->PerformThinkFunctions(); + + // TODO: make an ISimulateable interface so C_BaseNetworkables can simulate? + { + VPROF_("C_BaseEntity::Simulate", 1, VPROF_BUDGETGROUP_CLIENT_SIM, false, BUDGETFLAG_CLIENT); + C_BaseEntityIterator iterator; + C_BaseEntity *pEnt; + while ( (pEnt = iterator.Next()) != NULL ) + { + pEnt->Simulate(); + } + } +} + + +bool AddDataChangeEvent( IClientNetworkable *ent, DataUpdateType_t updateType, int *pStoredEvent ) +{ + Assert( ent ); + // Make sure we don't already have an event queued for this guy. + if ( *pStoredEvent >= 0 ) + { + Assert( g_DataChangedEvents[*pStoredEvent].m_pEntity == ent ); + + // DATA_UPDATE_CREATED always overrides DATA_UPDATE_CHANGED. + if ( updateType == DATA_UPDATE_CREATED ) + g_DataChangedEvents[*pStoredEvent].m_UpdateType = updateType; + + return false; + } + else + { + *pStoredEvent = g_DataChangedEvents.AddToTail( CDataChangedEvent( ent, updateType, pStoredEvent ) ); + return true; + } +} + + +void ClearDataChangedEvent( int iStoredEvent ) +{ + if ( iStoredEvent != -1 ) + g_DataChangedEvents.Remove( iStoredEvent ); +} + + +void ProcessOnDataChangedEvents() +{ + VPROF_("ProcessOnDataChangedEvents", 1, VPROF_BUDGETGROUP_CLIENT_SIM, false, BUDGETFLAG_CLIENT); + FOR_EACH_LL( g_DataChangedEvents, i ) + { + CDataChangedEvent *pEvent = &g_DataChangedEvents[i]; + + // Reset their stored event identifier. + *pEvent->m_pStoredEvent = -1; + + // Send the event. + IClientNetworkable *pNetworkable = pEvent->m_pEntity; + pNetworkable->OnDataChanged( pEvent->m_UpdateType ); + } + + g_DataChangedEvents.Purge(); +} + + +void UpdateClientRenderableInPVSStatus() +{ + // Vis for this view should already be setup at this point. + + // For each client-only entity, notify it if it's newly coming into the PVS. + CUtlLinkedList &theList = ClientEntityList().GetPVSNotifiers(); + FOR_EACH_LL( theList, i ) + { + CClientEntityList::CPVSNotifyInfo *pInfo = &theList[i]; + + if ( pInfo->m_InPVSStatus & INPVS_YES ) + { + // Ok, this entity already thinks it's in the PVS. No need to notify it. + // We need to set the INPVS_YES_THISFRAME flag if it's in this frame at all, so we + // don't tell the entity it's not in the PVS anymore at the end of the frame. + if ( !( pInfo->m_InPVSStatus & INPVS_THISFRAME ) ) + { + if ( g_pClientLeafSystem->IsRenderableInPVS( pInfo->m_pRenderable ) ) + { + pInfo->m_InPVSStatus |= INPVS_THISFRAME; + } + } + } + else + { + // This entity doesn't think it's in the PVS yet. If it is now in the PVS, let it know. + if ( g_pClientLeafSystem->IsRenderableInPVS( pInfo->m_pRenderable ) ) + { + pInfo->m_InPVSStatus |= ( INPVS_YES | INPVS_THISFRAME | INPVS_NEEDSNOTIFY ); + } + } + } +} + +void UpdatePVSNotifiers() +{ + MDLCACHE_CRITICAL_SECTION(); + + // At this point, all the entities that were rendered in the previous frame have INPVS_THISFRAME set + // so we can tell the entities that aren't in the PVS anymore so. + CUtlLinkedList &theList = ClientEntityList().GetPVSNotifiers(); + FOR_EACH_LL( theList, i ) + { + CClientEntityList::CPVSNotifyInfo *pInfo = &theList[i]; + + // If this entity thinks it's in the PVS, but it wasn't in the PVS this frame, tell it so. + if ( pInfo->m_InPVSStatus & INPVS_YES ) + { + if ( pInfo->m_InPVSStatus & INPVS_THISFRAME ) + { + if ( pInfo->m_InPVSStatus & INPVS_NEEDSNOTIFY ) + { + pInfo->m_pNotify->OnPVSStatusChanged( true ); + } + // Clear it for the next time around. + pInfo->m_InPVSStatus &= ~( INPVS_THISFRAME | INPVS_NEEDSNOTIFY ); + } + else + { + pInfo->m_InPVSStatus &= ~INPVS_YES; + pInfo->m_pNotify->OnPVSStatusChanged( false ); + } + } + } +} + + +void OnRenderStart() +{ + VPROF( "OnRenderStart" ); + MDLCACHE_CRITICAL_SECTION(); + MDLCACHE_COARSE_LOCK(); + +#ifdef PORTAL + g_pPortalRender->UpdatePortalPixelVisibility(); //updating this one or two lines before querying again just isn't cutting it. Update as soon as it's cheap to do so. +#endif + + partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, true ); + C_BaseEntity::SetAbsQueriesValid( false ); + + Rope_ResetCounters(); + + // Interpolate server entities and move aiments. + { + PREDICTION_TRACKVALUECHANGESCOPE( "interpolation" ); + C_BaseEntity::InterpolateServerEntities(); + } + + { + // vprof node for this bloc of math + VPROF( "OnRenderStart: dirty bone caches"); + // Invalidate any bone information. + C_BaseAnimating::InvalidateBoneCaches(); + + C_BaseEntity::SetAbsQueriesValid( true ); + C_BaseEntity::EnableAbsRecomputations( true ); + + // Enable access to all model bones except view models. + // This is necessary for aim-ent computation to occur properly + C_BaseAnimating::PushAllowBoneAccess( true, false, "OnRenderStart->CViewRender::SetUpView" ); // pops in CViewRender::SetUpView + + // FIXME: This needs to be done before the player moves; it forces + // aiments the player may be attached to to forcibly update their position + C_BaseEntity::MarkAimEntsDirty(); + } + + // Make sure the camera simulation happens before OnRenderStart, where it's used. + // NOTE: the only thing that happens in CAM_Think is thirdperson related code. + input->CAM_Think(); + + // This will place the player + the view models + all parent + // entities at the correct abs position so that their attachment points + // are at the correct location + view->OnRenderStart(); + + RopeManager()->OnRenderStart(); + + // This will place all entities in the correct position in world space and in the KD-tree + C_BaseAnimating::UpdateClientSideAnimations(); + + partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, false ); + + // Process OnDataChanged events. + ProcessOnDataChangedEvents(); + + // Reset the overlay alpha. Entities can change the state of this in their think functions. + g_SmokeFogOverlayAlpha = 0; + + // This must occur prior to SimulatEntities, + // which is where the client thinks for c_colorcorrection + c_colorcorrectionvolumes + // update the color correction weights. + // FIXME: The place where IGameSystem::Update is called should be in here + // so we don't have to explicitly call ResetColorCorrectionWeights + SimulateEntities, etc. + g_pColorCorrectionMgr->ResetColorCorrectionWeights(); + + // Simulate all the entities. + SimulateEntities(); + PhysicsSimulate(); + + C_BaseAnimating::ThreadedBoneSetup(); + + { + VPROF_("Client TempEnts", 0, VPROF_BUDGETGROUP_CLIENT_SIM, false, BUDGETFLAG_CLIENT); + // This creates things like temp entities. + engine->FireEvents(); + + // Update temp entities + tempents->Update(); + + // Update temp ent beams... + beams->UpdateTempEntBeams(); + + // Lock the frame from beam additions + SetBeamCreationAllowed( false ); + } + + // Update particle effects (eventually, the effects should use Simulate() instead of having + // their own update system). + { + VPROF_BUDGET( "ParticleMgr()->Simulate", VPROF_BUDGETGROUP_PARTICLE_SIMULATION ); + ParticleMgr()->Simulate( gpGlobals->frametime ); + } + + // Now that the view model's position is setup and aiments are marked dirty, update + // their positions so they're in the leaf system correctly. + C_BaseEntity::CalcAimEntPositions(); + + // For entities marked for recording, post bone messages to IToolSystems + if ( ToolsEnabled() ) + { + C_BaseEntity::ToolRecordEntities(); + } + + // Finally, link all the entities into the leaf system right before rendering. + C_BaseEntity::AddVisibleEntities(); +} + + +void OnRenderEnd() +{ + // Disallow access to bones (access is enabled in CViewRender::SetUpView). + C_BaseAnimating::PopBoneAccess( "CViewRender::SetUpView->OnRenderEnd" ); + + UpdatePVSNotifiers(); + + DisplayBoneSetupEnts(); +} + + + +void CHLClient::FrameStageNotify( ClientFrameStage_t curStage ) +{ + g_CurFrameStage = curStage; + + switch( curStage ) + { + default: + break; + + case FRAME_RENDER_START: + { + VPROF( "CHLClient::FrameStageNotify FRAME_RENDER_START" ); + + // Last thing before rendering, run simulation. + OnRenderStart(); + } + break; + + case FRAME_RENDER_END: + { + VPROF( "CHLClient::FrameStageNotify FRAME_RENDER_END" ); + OnRenderEnd(); + + PREDICTION_SPEWVALUECHANGES(); + } + break; + + case FRAME_NET_UPDATE_START: + { + VPROF( "CHLClient::FrameStageNotify FRAME_NET_UPDATE_START" ); + // disabled all recomputations while we update entities + C_BaseEntity::EnableAbsRecomputations( false ); + C_BaseEntity::SetAbsQueriesValid( false ); + Interpolation_SetLastPacketTimeStamp( engine->GetLastTimeStamp() ); + partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, true ); + + PREDICTION_STARTTRACKVALUE( "netupdate" ); + } + break; + case FRAME_NET_UPDATE_END: + { + ProcessCacheUsedMaterials(); + + // reenable abs recomputation since now all entities have been updated + C_BaseEntity::EnableAbsRecomputations( true ); + C_BaseEntity::SetAbsQueriesValid( true ); + partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, false ); + + PREDICTION_ENDTRACKVALUE(); + } + break; + case FRAME_NET_UPDATE_POSTDATAUPDATE_START: + { + VPROF( "CHLClient::FrameStageNotify FRAME_NET_UPDATE_POSTDATAUPDATE_START" ); + PREDICTION_STARTTRACKVALUE( "postdataupdate" ); + } + break; + case FRAME_NET_UPDATE_POSTDATAUPDATE_END: + { + VPROF( "CHLClient::FrameStageNotify FRAME_NET_UPDATE_POSTDATAUPDATE_END" ); + PREDICTION_ENDTRACKVALUE(); + // Let prediction copy off pristine data + prediction->PostEntityPacketReceived(); + HLTVCamera()->PostEntityPacketReceived(); + } + break; + case FRAME_START: + { + // Mark the frame as open for client fx additions + SetFXCreationAllowed( true ); + SetBeamCreationAllowed( true ); + C_BaseEntity::CheckCLInterpChanged(); + } + break; + } +} + +CSaveRestoreData *SaveInit( int size ); + +// Save/restore system hooks +CSaveRestoreData *CHLClient::SaveInit( int size ) +{ + return ::SaveInit(size); +} + +void CHLClient::SaveWriteFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pMap, pFields, fieldCount ); +} + +void CHLClient::SaveReadFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pMap, pFields, fieldCount ); +} + +void CHLClient::PreSave( CSaveRestoreData *s ) +{ + g_pGameSaveRestoreBlockSet->PreSave( s ); +} + +void CHLClient::Save( CSaveRestoreData *s ) +{ + CSave saveHelper( s ); + g_pGameSaveRestoreBlockSet->Save( &saveHelper ); +} + +void CHLClient::WriteSaveHeaders( CSaveRestoreData *s ) +{ + CSave saveHelper( s ); + g_pGameSaveRestoreBlockSet->WriteSaveHeaders( &saveHelper ); + g_pGameSaveRestoreBlockSet->PostSave(); +} + +void CHLClient::ReadRestoreHeaders( CSaveRestoreData *s ) +{ + CRestore restoreHelper( s ); + g_pGameSaveRestoreBlockSet->PreRestore(); + g_pGameSaveRestoreBlockSet->ReadRestoreHeaders( &restoreHelper ); +} + +void CHLClient::Restore( CSaveRestoreData *s, bool b ) +{ + CRestore restore(s); + g_pGameSaveRestoreBlockSet->Restore( &restore, b ); + g_pGameSaveRestoreBlockSet->PostRestore(); +} + +static CUtlVector g_RestoredEntities; + +void AddRestoredEntity( C_BaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + g_RestoredEntities.AddToTail( EHANDLE(pEntity) ); +} + +void CHLClient::DispatchOnRestore() +{ + for ( int i = 0; i < g_RestoredEntities.Count(); i++ ) + { + if ( g_RestoredEntities[i] != NULL ) + { + MDLCACHE_CRITICAL_SECTION(); + g_RestoredEntities[i]->OnRestore(); + } + } + g_RestoredEntities.RemoveAll(); +} + +void CHLClient::WriteSaveGameScreenshot( const char *pFilename ) +{ + view->WriteSaveGameScreenshot( pFilename ); +} + +// Given a list of "S(wavname) S(wavname2)" tokens, look up the localized text and emit +// the appropriate close caption if running with closecaption = 1 +void CHLClient::EmitSentenceCloseCaption( char const *tokenstream ) +{ + extern ConVar closecaption; + + if ( !closecaption.GetBool() ) + return; + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->ProcessSentenceCaptionStream( tokenstream ); + } +} + + +void CHLClient::EmitCloseCaption( char const *captionname, float duration ) +{ + extern ConVar closecaption; + + if ( !closecaption.GetBool() ) + return; + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->ProcessCaption( captionname, duration ); + } +} + +CStandardRecvProxies* CHLClient::GetStandardRecvProxies() +{ + return &g_StandardRecvProxies; +} + +bool CHLClient::CanRecordDemo( char *errorMsg, int length ) const +{ + if ( GetClientModeNormal() ) + { + return GetClientModeNormal()->CanRecordDemo( errorMsg, length ); + } + + return true; +} + +// NEW INTERFACES +// save game screenshot writing +void CHLClient::WriteSaveGameScreenshotOfSize( const char *pFilename, int width, int height ) +{ + view->WriteSaveGameScreenshotOfSize( pFilename, width, height ); +} + +// See RenderViewInfo_t +void CHLClient::RenderView( const CViewSetup &setup, int nClearFlags, int whatToDraw ) +{ + VPROF("RenderView"); + view->RenderView( setup, nClearFlags, whatToDraw ); +} diff --git a/game/client/cdll_client_int.h b/game/client/cdll_client_int.h new file mode 100644 index 00000000..10b5f0f3 --- /dev/null +++ b/game/client/cdll_client_int.h @@ -0,0 +1,138 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef CDLL_CLIENT_INT_H +#define CDLL_CLIENT_INT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "iclientnetworkable.h" +#include "utllinkedlist.h" +#include "cdll_int.h" +#include "eiface.h" + + +class IVModelRender; +class IVEngineClient; +class IVModelRender; +class IVEfx; +class IVRenderView; +class IVDebugOverlay; +class IMaterialSystem; +class IMaterialSystemStub; +class IDataCache; +class IMDLCache; +class IVModelInfoClient; +class IEngineVGui; +class ISpatialPartition; +class IBaseClientDLL; +class ISpatialPartition; +class IFileSystem; +class IStaticPropMgrClient; +class IShadowMgr; +class IUniformRandomStream; +class CGaussianRandomStream; +class IEngineSound; +class IMatSystemSurface; +class IMaterialSystemHardwareConfig; +class ISharedGameRules; +class IEngineTrace; +class IGameUIFuncs; +class IGameEventManager2; +class IPhysicsGameTrace; +class CGlobalVarsBase; +class IClientTools; +class C_BaseAnimating; +class IColorCorrectionSystem; +class IInputSystem; +class ISceneFileCache; +class IXboxSystem; // Xbox 360 only +class IMatchmaking; +class IAvi; +class IBik; +class CSteamAPIContext; + +extern IVModelRender *modelrender; +extern IVEngineClient *engine; +extern IVModelRender *modelrender; +extern IVEfx *effects; +extern IVRenderView *render; +extern IVDebugOverlay *debugoverlay; +extern IMaterialSystem *materials; +extern IMaterialSystemStub *materials_stub; +extern IMaterialSystemHardwareConfig *g_pMaterialSystemHardwareConfig; +extern IDataCache *datacache; +extern IMDLCache *mdlcache; +extern IVModelInfoClient *modelinfo; +extern IEngineVGui *enginevgui; +extern ISpatialPartition* partition; +extern IBaseClientDLL *clientdll; +extern IFileSystem *filesystem; +extern IStaticPropMgrClient *staticpropmgr; +extern IShadowMgr *shadowmgr; +extern IUniformRandomStream *random; +extern CGaussianRandomStream *randomgaussian; +extern IEngineSound *enginesound; +extern IMatSystemSurface *g_pMatSystemSurface; +extern IEngineTrace *enginetrace; +extern IGameUIFuncs *gameuifuncs; +extern IGameEventManager2 *gameeventmanager; +extern IPhysicsGameTrace *physgametrace; +extern CGlobalVarsBase *gpGlobals; +extern IClientTools *clienttools; +extern IInputSystem *inputsystem; +extern ISceneFileCache *scenefilecache; +extern IXboxSystem *xboxsystem; // Xbox 360 only +extern IMatchmaking *matchmaking; +extern IAvi *avi; +extern IBik *bik; +extern IUploadGameStats *gamestatsuploader; +extern CSteamAPIContext *steamapicontext; + + +// Set to true between LevelInit and LevelShutdown. +extern bool g_bLevelInitialized; +extern bool g_bTextMode; + + +// Returns true if a new OnDataChanged event is registered for this frame. +bool AddDataChangeEvent( IClientNetworkable *ent, DataUpdateType_t updateType, int *pStoredEvent ); + +void ClearDataChangedEvent( int iStoredEvent ); + +//----------------------------------------------------------------------------- +// Precaches a material +//----------------------------------------------------------------------------- +void PrecacheMaterial( const char *pMaterialName ); + +//----------------------------------------------------------------------------- +// Converts a previously precached material into an index +//----------------------------------------------------------------------------- +int GetMaterialIndex( const char *pMaterialName ); + +//----------------------------------------------------------------------------- +// Converts precached material indices into strings +//----------------------------------------------------------------------------- +const char *GetMaterialNameFromIndex( int nIndex ); + +//----------------------------------------------------------------------------- +// Precache-related methods for particle systems +//----------------------------------------------------------------------------- +void PrecacheParticleSystem( const char *pParticleSystemName ); +int GetParticleSystemIndex( const char *pParticleSystemName ); +const char *GetParticleSystemNameFromIndex( int nIndex ); + + +//----------------------------------------------------------------------------- +// Called during bone setup to test perf +//----------------------------------------------------------------------------- +void TrackBoneSetupEnt( C_BaseAnimating *pEnt ); + +bool IsEngineThreaded(); + +#endif // CDLL_CLIENT_INT_H diff --git a/game/client/cdll_util.cpp b/game/client/cdll_util.cpp new file mode 100644 index 00000000..b2e7255e --- /dev/null +++ b/game/client/cdll_util.cpp @@ -0,0 +1,1195 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include +#include "hud.h" +#include "itextmessage.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/ITexture.h" +#include "materialsystem/IMaterialSystem.h" +#include "imovehelper.h" +#include "checksum_crc.h" +#include "decals.h" +#include "iefx.h" +#include "view_scene.h" +#include "filesystem.h" +#include "model_types.h" +#include "engine/IEngineTrace.h" +#include "engine/ivmodelinfo.h" +#include "c_te_effect_dispatch.h" +#include +#include +#include +#include "view.h" +#include "ixboxsystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// ConVars +//----------------------------------------------------------------------------- +#ifdef _DEBUG + +ConVar r_FadeProps( "r_FadeProps", "1" ); + +#endif +bool g_MakingDevShots = false; +extern ConVar cl_leveloverview; + +//----------------------------------------------------------------------------- +// Purpose: Performs a var args printf into a static return buffer +// Input : *format - +// ... - +// Output : char +//----------------------------------------------------------------------------- +char *VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + Q_vsnprintf (string, sizeof( string ), format,argptr); + va_end (argptr); + + return string; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the entity index corresponds to a player slot +// Input : index - +// Output : bool +//----------------------------------------------------------------------------- +bool IsPlayerIndex( int index ) +{ + return ( index >= 1 && index <= gpGlobals->maxClients ) ? true : false; +} + +int GetLocalPlayerIndex( void ) +{ + C_BasePlayer * player = C_BasePlayer::GetLocalPlayer(); + + if ( player ) + return player->entindex(); + else + return 0; // game not started yet +} + +bool IsLocalPlayerSpectator( void ) +{ + C_BasePlayer * player = C_BasePlayer::GetLocalPlayer(); + + if ( player ) + return player->IsObserver(); + else + return false; // game not started yet +} + +int GetSpectatorMode( void ) +{ + C_BasePlayer * player = C_BasePlayer::GetLocalPlayer(); + + if ( player ) + return player->GetObserverMode(); + else + return OBS_MODE_NONE; // game not started yet +} + +int GetSpectatorTarget( void ) +{ + C_BasePlayer * player = C_BasePlayer::GetLocalPlayer(); + + if ( player ) + { + CBaseEntity * target = player->GetObserverTarget(); + + if ( target ) + return target->entindex(); + else + return 0; + } + else + { + return 0; // game not started yet + } +} + +int GetLocalPlayerTeam( void ) +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( pPlayer ) + return pPlayer->GetTeamNumber(); + else + return TEAM_UNASSIGNED; +} + +//----------------------------------------------------------------------------- +// Purpose: Convert angles to -180 t 180 range +// Input : angles - +//----------------------------------------------------------------------------- +void NormalizeAngles( QAngle& angles ) +{ + int i; + + // Normalize angles to -180 to 180 range + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Interpolate Euler angles using quaternions to avoid singularities +// Input : start - +// end - +// output - +// frac - +//----------------------------------------------------------------------------- +void InterpolateAngles( const QAngle& start, const QAngle& end, QAngle& output, float frac ) +{ + Quaternion src, dest; + + // Convert to quaternions + AngleQuaternion( start, src ); + AngleQuaternion( end, dest ); + + Quaternion result; + + // Slerp + QuaternionSlerp( src, dest, frac, result ); + + // Convert to euler + QuaternionAngles( result, output ); +} + +//----------------------------------------------------------------------------- +// Purpose: Simple linear interpolation +// Input : frac - +// src - +// dest - +// output - +//----------------------------------------------------------------------------- +void InterpolateVector( float frac, const Vector& src, const Vector& dest, Vector& output ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + output[ i ] = src[ i ] + frac * ( dest[ i ] - src[ i ] ); + } +} + +client_textmessage_t *TextMessageGet( const char *pName ) +{ + return engine->TextMessageGet( pName ); +} + +//----------------------------------------------------------------------------- +// Purpose: ScreenHeight returns the height of the screen, in pixels +// Output : int +//----------------------------------------------------------------------------- +int ScreenHeight( void ) +{ + int w, h; + GetHudSize( w, h ); + return h; +} + +//----------------------------------------------------------------------------- +// Purpose: ScreenWidth returns the width of the screen, in pixels +// Output : int +//----------------------------------------------------------------------------- +int ScreenWidth( void ) +{ + int w, h; + GetHudSize( w, h ); + return w; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the difference between two angles +// Input : destAngle - +// srcAngle - +// Output : float +//----------------------------------------------------------------------------- +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + while ( delta >= 180 ) + delta -= 360; + } + else + { + while ( delta <= -180 ) + delta += 360; + } + return delta; +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if ( !(UTIL_PointContents(midUp) & MASK_WATER) ) + return minz; + + midUp.z = maxz; + if ( UTIL_PointContents(midUp) & MASK_WATER ) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if ( UTIL_PointContents(midUp) & MASK_WATER ) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + +void UTIL_Bubbles( const Vector& mins, const Vector& maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + CPASFilter filter( mid ); + + int bubbles = modelinfo->GetModelIndex( "sprites/bubble.vmt" ); + + te->Bubbles( filter, 0.0, + &mins, &maxs, flHeight, bubbles, count, 8.0 ); +} + +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake ) +{ + // Nothing for now +} + +char TEXTURETYPE_Find( trace_t *ptr ) +{ + surfacedata_t *psurfaceData = physprops->GetSurfaceData( ptr->surface.surfaceProps ); + + return psurfaceData->game.material; +} + +//----------------------------------------------------------------------------- +// Purpose: Make a tracer effect +//----------------------------------------------------------------------------- +void UTIL_Tracer( const Vector &vecStart, const Vector &vecEnd, int iEntIndex, int iAttachment, float flVelocity, bool bWhiz, char *pCustomTracerName ) +{ + CEffectData data; + data.m_vStart = vecStart; + data.m_vOrigin = vecEnd; + data.m_hEntity = ClientEntityList().EntIndexToHandle( iEntIndex ); + data.m_flScale = flVelocity; + + // Flags + if ( bWhiz ) + { + data.m_fFlags |= TRACER_FLAG_WHIZ; + } + if ( iAttachment != TRACER_DONT_USE_ATTACHMENT ) + { + data.m_fFlags |= TRACER_FLAG_USEATTACHMENT; + // Stomp the start, since it's not going to be used anyway + data.m_vStart[0] = iAttachment; + } + + // Fire it off + if ( pCustomTracerName ) + { + DispatchEffect( pCustomTracerName, data ); + } + else + { + DispatchEffect( "Tracer", data ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : Creates both an decal and any associated impact effects (such +// as flecks) for the given iDamageType and the trace's end position +// Input : +// Output : +//------------------------------------------------------------------------------ +void UTIL_ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ) +{ + C_BaseEntity *pEntity = pTrace->m_pEnt; + + // Is the entity valid, is the surface sky? + if ( !pEntity || (pTrace->surface.flags & SURF_SKY) ) + return; + + if (pTrace->fraction == 1.0) + return; + + // don't decal nodraw surfaces + if ( pTrace->surface.flags & SURF_NODRAW ) + return; + + pEntity->ImpactTrace( pTrace, iDamageType, pCustomImpactName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int UTIL_PrecacheDecal( const char *name, bool preload ) +{ + return effects->Draw_DecalIndexFromName( (char*)name ); +} + +extern short g_sModelIndexSmoke; + +void UTIL_Smoke( const Vector &origin, const float scale, const float framerate ) +{ + CPVSFilter filter( origin ); + te->Smoke( filter, 0.0f, &origin, g_sModelIndexSmoke, scale, framerate ); +} + +void UTIL_SetOrigin( C_BaseEntity *entity, const Vector &vecOrigin ) +{ + entity->SetLocalOrigin( vecOrigin ); +} + +//#define PRECACHE_OTHER_ONCE +// UNDONE: Do we need this to avoid doing too much of this? Measure startup times and see +#if PRECACHE_OTHER_ONCE + +#include "utlsymbol.h" +class CPrecacheOtherList : public CAutoServerSystem +{ +public: + virtual void LevelInitPreEntity(); + virtual void LevelShutdownPostEntity(); + + bool AddOrMarkPrecached( const char *pClassname ); + +private: + CUtlSymbolTable m_list; +}; + +void CPrecacheOtherList::LevelInitPreEntity() +{ + m_list.RemoveAll(); +} + +void CPrecacheOtherList::LevelShutdownPostEntity() +{ + m_list.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: mark or add +// Input : *pEntity - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPrecacheOtherList::AddOrMarkPrecached( const char *pClassname ) +{ + CUtlSymbol sym = m_list.Find( pClassname ); + if ( sym.IsValid() ) + return false; + + m_list.AddString( pClassname ); + return true; +} + +CPrecacheOtherList g_PrecacheOtherList; +#endif + +void UTIL_PrecacheOther( const char *szClassname ) +{ +#if PRECACHE_OTHER_ONCE + // already done this one?, if not, mark as done + if ( !g_PrecacheOtherList.AddOrMarkPrecached( szClassname ) ) + return; +#endif + + // Client should only do this once entities are coming down from server!!! + // Assert( engine->IsConnected() ); + + C_BaseEntity *pEntity = CreateEntityByName( szClassname ); + if ( !pEntity ) + { + Warning( "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + if (pEntity) + { + pEntity->Precache( ); + } + + // Bye bye + pEntity->Release(); +} + +static csurface_t g_NullSurface = { "**empty**", 0 }; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void UTIL_SetTrace(trace_t& trace, const Ray_t& ray, C_BaseEntity *ent, float fraction, int hitgroup, unsigned int contents, const Vector& normal, float intercept ) +{ + trace.startsolid = (fraction == 0.0f); + trace.fraction = fraction; + VectorCopy( ray.m_Start, trace.startpos ); + VectorMA( ray.m_Start, fraction, ray.m_Delta, trace.endpos ); + VectorCopy( normal, trace.plane.normal ); + trace.plane.dist = intercept; + trace.m_pEnt = C_BaseEntity::Instance( ent ); + trace.hitgroup = hitgroup; + trace.surface = g_NullSurface; + trace.contents = contents; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the x & y positions of a world position in screenspace +// Returns true if it's onscreen +//----------------------------------------------------------------------------- +bool GetVectorInScreenSpace( Vector pos, int& iX, int& iY, Vector *vecOffset ) +{ + Vector screen; + + // Apply the offset, if one was specified + if ( vecOffset != NULL ) + pos += *vecOffset; + + // Transform to screen space + int iFacing = ScreenTransform( pos, screen ); + iX = 0.5 * screen[0] * ScreenWidth(); + iY = -0.5 * screen[1] * ScreenHeight(); + iX += 0.5 * ScreenWidth(); + iY += 0.5 * ScreenHeight(); + + // Make sure the player's facing it + if ( iFacing ) + { + // We're actually facing away from the Target. Stomp the screen position. + iX = -640; + iY = -640; + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the x & y positions of an entity in screenspace +// Returns true if it's onscreen +//----------------------------------------------------------------------------- +bool GetTargetInScreenSpace( C_BaseEntity *pTargetEntity, int& iX, int& iY, Vector *vecOffset ) +{ + return GetVectorInScreenSpace( pTargetEntity->WorldSpaceCenter(), iX, iY, vecOffset ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// msg_dest - +// *msg_name - +// *param1 - +// *param2 - +// *param3 - +// *param4 - +//----------------------------------------------------------------------------- +void ClientPrint( C_BasePlayer *player, int msg_dest, const char *msg_name, const char *param1 /*= NULL*/, const char *param2 /*= NULL*/, const char *param3 /*= NULL*/, const char *param4 /*= NULL*/ ) +{ +} + +//----------------------------------------------------------------------------- +// class CFlaggedEntitiesEnum +//----------------------------------------------------------------------------- +// enumerate entities that match a set of edict flags into a static array +class CFlaggedEntitiesEnum : public IPartitionEnumerator +{ +public: + CFlaggedEntitiesEnum( C_BaseEntity **pList, int listMax, int flagMask ); + // This gets called by the enumeration methods with each element + // that passes the test. + virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); + + int GetCount() { return m_count; } + bool AddToList( C_BaseEntity *pEntity ); + +private: + C_BaseEntity **m_pList; + int m_listMax; + int m_flagMask; + int m_count; +}; + +CFlaggedEntitiesEnum::CFlaggedEntitiesEnum( C_BaseEntity **pList, int listMax, int flagMask ) +{ + m_pList = pList; + m_listMax = listMax; + m_flagMask = flagMask; + m_count = 0; +} + +bool CFlaggedEntitiesEnum::AddToList( C_BaseEntity *pEntity ) +{ + if ( m_count >= m_listMax ) + return false; + m_pList[m_count] = pEntity; + m_count++; + return true; +} + +IterationRetval_t CFlaggedEntitiesEnum::EnumElement( IHandleEntity *pHandleEntity ) +{ + IClientEntity *pClientEntity = cl_entitylist->GetClientEntityFromHandle( pHandleEntity->GetRefEHandle() ); + C_BaseEntity *pEntity = pClientEntity ? pClientEntity->GetBaseEntity() : NULL; + if ( pEntity ) + { + if ( m_flagMask && !(pEntity->GetFlags() & m_flagMask) ) // Does it meet the criteria? + return ITERATION_CONTINUE; + + if ( !AddToList( pEntity ) ) + return ITERATION_STOP; + } + + return ITERATION_CONTINUE; +} + +//----------------------------------------------------------------------------- +// Purpose: Pass in an array of pointers and an array size, it fills the array and returns the number inserted +// Input : **pList - +// listMax - +// &mins - +// &maxs - +// flagMask - +// Output : int +//----------------------------------------------------------------------------- +int UTIL_EntitiesInBox( C_BaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask, int partitionMask ) +{ + CFlaggedEntitiesEnum boxEnum( pList, listMax, flagMask ); + partition->EnumerateElementsInBox( partitionMask, mins, maxs, false, &boxEnum ); + + return boxEnum.GetCount(); + +} + +//----------------------------------------------------------------------------- +// Purpose: Pass in an array of pointers and an array size, it fills the array and returns the number inserted +// Input : **pList - +// listMax - +// ¢er - +// radius - +// flagMask - +// Output : int +//----------------------------------------------------------------------------- +int UTIL_EntitiesInSphere( C_BaseEntity **pList, int listMax, const Vector ¢er, float radius, int flagMask, int partitionMask ) +{ + CFlaggedEntitiesEnum sphereEnum( pList, listMax, flagMask ); + partition->EnumerateElementsInSphere( partitionMask, center, radius, false, &sphereEnum ); + + return sphereEnum.GetCount(); + +} + +CEntitySphereQuery::CEntitySphereQuery( const Vector ¢er, float radius, int flagMask, int partitionMask ) +{ + m_listIndex = 0; + m_listCount = UTIL_EntitiesInSphere( m_pList, ARRAYSIZE(m_pList), center, radius, flagMask, partitionMask ); +} + +CBaseEntity *CEntitySphereQuery::GetCurrentEntity() +{ + if ( m_listIndex < m_listCount ) + return m_pList[m_listIndex]; + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Slightly modified strtok. Does not modify the input string. Does +// not skip over more than one separator at a time. This allows parsing +// strings where tokens between separators may or may not be present: +// +// Door01,,,0 would be parsed as "Door01" "" "" "0" +// Door01,Open,,0 would be parsed as "Door01" "Open" "" "0" +// +// Input : token - Returns with a token, or zero length if the token was missing. +// str - String to parse. +// sep - Character to use as separator. UNDONE: allow multiple separator chars +// Output : Returns a pointer to the next token to be parsed. +//----------------------------------------------------------------------------- +const char *nexttoken(char *token, const char *str, char sep) +{ + if ((str == NULL) || (*str == '\0')) + { + *token = '\0'; + return(NULL); + } + + // + // Copy everything up to the first separator into the return buffer. + // Do not include separators in the return buffer. + // + while ((*str != sep) && (*str != '\0')) + { + *token++ = *str++; + } + *token = '\0'; + + // + // Advance the pointer unless we hit the end of the input string. + // + if (*str == '\0') + { + return(str); + } + + return(++str); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : font - +// *str - +// Output : int +//----------------------------------------------------------------------------- +int UTIL_ComputeStringWidth( vgui::HFont& font, const char *str ) +{ + int pixels = 0; + char *p = (char *)str; + while ( *p ) + { + pixels += vgui::surface()->GetCharacterWidth( font, *p++ ); + } + return pixels; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : font - +// *str - +// Output : int +//----------------------------------------------------------------------------- +int UTIL_ComputeStringWidth( vgui::HFont& font, const wchar_t *str ) +{ + int pixels = 0; + wchar_t *p = (wchar_t *)str; + while ( *p ) + { + pixels += vgui::surface()->GetCharacterWidth( font, *p++ ); + } + return pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: Scans player names +//Passes the player name to be checked in a KeyValues pointer +//with the keyname "name" +// - replaces '&' with '&&' so they will draw in the scoreboard +// - replaces '#' at the start of the name with '*' +//----------------------------------------------------------------------------- + +void UTIL_MakeSafeName( const char *oldName, char *newName, int newNameBufSize ) +{ + int newpos = 0; + + for( const char *p=oldName; *p != 0 && newpos < newNameBufSize-1; p++ ) + { + //check for a '#' char at the beginning + if( p == oldName && *p == '#' ) + { + newName[newpos] = '*'; + newpos++; + } + else if( *p == '%' ) + { + // remove % chars + newName[newpos] = '*'; + newpos++; + } + else if( *p == '&' ) + { + //insert another & after this one + if ( newpos+2 < newNameBufSize ) + { + newName[newpos] = '&'; + newName[newpos+1] = '&'; + newpos+=2; + } + } + else + { + newName[newpos] = *p; + newpos++; + } + } + newName[newpos] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Scans player names and replaces characters that vgui won't +// display properly +// Input : *oldName - player name to be fixed up +// Output : *char - static buffer with the safe name +//----------------------------------------------------------------------------- + +const char * UTIL_SafeName( const char *oldName ) +{ + static char safeName[ MAX_PLAYER_NAME_LENGTH * 2 + 1 ]; + UTIL_MakeSafeName( oldName, safeName, sizeof( safeName ) ); + + return safeName; +} + + +//----------------------------------------------------------------------------- +// Purpose: Looks up key bindings for commands and replaces them in string. +// %% will get replaced with its bound control, e.g. %attack2% +// Input buffer sizes are in bytes rather than unicode character count +// for consistency with other APIs. If inbufsizebytes is 0 a NULL-terminated +// input buffer is assumed, or you can pass the size of the input buffer if +// not NULL-terminated. +//----------------------------------------------------------------------------- +void UTIL_ReplaceKeyBindings( const wchar_t *inbuf, int inbufsizebytes, wchar_t *outbuf, int outbufsizebytes ) +{ + if ( !inbuf || !inbuf[0] ) + return; + + // copy to a new buf if there are vars + outbuf[0]=0; + int pos = 0; + const wchar_t *inbufend = NULL; + if ( inbufsizebytes > 0 ) + { + inbufend = inbuf + ( inbufsizebytes / 2 ); + } + + while( inbuf != inbufend && *inbuf != 0 ) + { + // check for variables + if ( *inbuf == '%' ) + { + ++inbuf; + + const wchar_t *end = wcschr( inbuf, '%' ); + if ( end && ( end != inbuf ) ) // make sure we handle %% in the string, which should be treated in the output as % + { + wchar_t token[64]; + wcsncpy( token, inbuf, end - inbuf ); + token[end - inbuf] = 0; + + inbuf += end - inbuf; + + // lookup key names + char binding[64]; + g_pVGuiLocalize->ConvertUnicodeToANSI( token, binding, sizeof(binding) ); + + const char *key = engine->Key_LookupBinding( *binding == '+' ? binding + 1 : binding ); + if ( !key ) + { + key = IsX360() ? "" : "< not bound >"; + } + + //!! change some key names into better names + char friendlyName[64]; + bool bAddBrackets = false; + if ( IsX360() ) + { + if ( !key || !key[0] ) + { + Q_snprintf( friendlyName, sizeof(friendlyName), "#GameUI_None" ); + bAddBrackets = true; + } + else + { + Q_snprintf( friendlyName, sizeof(friendlyName), "#GameUI_KeyNames_%s", key ); + } + } + else + { + Q_snprintf( friendlyName, sizeof(friendlyName), "%s", key ); + } + Q_strupr( friendlyName ); + + wchar_t *locName = g_pVGuiLocalize->Find( friendlyName ); + if ( !locName || wcslen(locName) <= 0) + { + g_pVGuiLocalize->ConvertANSIToUnicode( friendlyName, token, sizeof(token) ); + + outbuf[pos] = '\0'; + wcscat( outbuf, token ); + pos += wcslen(token); + } + else + { + outbuf[pos] = '\0'; + if ( bAddBrackets ) + { + wcscat( outbuf, L"[" ); + pos += 1; + } + wcscat( outbuf, locName ); + pos += wcslen(locName); + if ( bAddBrackets ) + { + wcscat( outbuf, L"]" ); + pos += 1; + } + } + } + else + { + outbuf[pos] = *inbuf; + ++pos; + } + } + else + { + outbuf[pos] = *inbuf; + ++pos; + } + + ++inbuf; + } + + outbuf[pos] = '\0'; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// *pLength - +// Output : byte +//----------------------------------------------------------------------------- +byte *UTIL_LoadFileForMe( const char *filename, int *pLength ) +{ + byte *buffer; + + FileHandle_t file; + file = filesystem->Open( filename, "rb", "GAME" ); + if ( FILESYSTEM_INVALID_HANDLE == file ) + { + if ( pLength ) *pLength = 0; + return NULL; + } + + int size = filesystem->Size( file ); + buffer = new byte[ size + 1 ]; + if ( !buffer ) + { + Warning( "UTIL_LoadFileForMe: Couldn't allocate buffer of size %i for file %s\n", size + 1, filename ); + filesystem->Close( file ); + return NULL; + } + filesystem->Read( buffer, size, file ); + filesystem->Close( file ); + + // Ensure null terminator + buffer[ size ] =0; + + if ( pLength ) + { + *pLength = size; + } + + return buffer; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *buffer - +//----------------------------------------------------------------------------- +void UTIL_FreeFile( byte *buffer ) +{ + delete[] buffer; +} + + +//----------------------------------------------------------------------------- +// Compute distance fade +//----------------------------------------------------------------------------- +static unsigned char ComputeDistanceFade( C_BaseEntity *pEntity, float flMinDist, float flMaxDist ) +{ + if ((flMinDist <= 0) && (flMaxDist <= 0)) + return 255; + + if( flMinDist > flMaxDist ) + { + swap( flMinDist, flMaxDist ); + } + + // If a negative value is provided for the min fade distance, then base it off the max. + if( flMinDist < 0 ) + { + flMinDist = flMaxDist - 400; + if( flMinDist < 0 ) + { + flMinDist = 0; + } + } + + flMinDist *= flMinDist; + flMaxDist *= flMaxDist; + + float flCurrentDistanceSq = CurrentViewOrigin().DistToSqr( pEntity->WorldSpaceCenter() ); + C_BasePlayer *pLocal = C_BasePlayer::GetLocalPlayer(); + if ( pLocal ) + { + float flDistFactor = pLocal->GetFOVDistanceAdjustFactor(); + flCurrentDistanceSq *= flDistFactor * flDistFactor; + } + + // If I'm inside the minimum range than don't resort to alpha trickery + if ( flCurrentDistanceSq <= flMinDist ) + return 255; + + if ( flCurrentDistanceSq >= flMaxDist ) + return 0; + + // NOTE: Because of the if-checks above, flMinDist != flMinDist here + float flFalloffFactor = 255.0f / (flMaxDist - flMinDist); + int nAlpha = flFalloffFactor * (flMaxDist - flCurrentDistanceSq); + return clamp( nAlpha, 0, 255 ); +} + + +//----------------------------------------------------------------------------- +// Compute fade amount +//----------------------------------------------------------------------------- +unsigned char UTIL_ComputeEntityFade( C_BaseEntity *pEntity, float flMinDist, float flMaxDist, float flFadeScale ) +{ + unsigned char nAlpha = 255; + + // If we're taking devshots, don't fade props at all + if ( g_MakingDevShots || cl_leveloverview.GetFloat() > 0 ) + return 255; + +#ifdef _DEBUG + if ( r_FadeProps.GetBool() ) +#endif + { + nAlpha = ComputeDistanceFade( pEntity, flMinDist, flMaxDist ); + + // NOTE: This computation for the center + radius is invalid! + // The center of the sphere is at the center of the OBB, which is not necessarily + // at the render origin. But it should be close enough. + Vector vecMins, vecMaxs; + pEntity->GetRenderBounds( vecMins, vecMaxs ); + float flRadius = vecMins.DistTo( vecMaxs ) * 0.5f; + + Vector vecAbsCenter; + if ( modelinfo->GetModelType( pEntity->GetModel() ) == mod_brush ) + { + Vector vecRenderMins, vecRenderMaxs; + pEntity->GetRenderBoundsWorldspace( vecRenderMins, vecRenderMaxs ); + VectorAdd( vecRenderMins, vecRenderMaxs, vecAbsCenter ); + vecAbsCenter *= 0.5f; + } + else + { + vecAbsCenter = pEntity->GetRenderOrigin(); + } + + unsigned char nGlobalAlpha = IsXbox() ? 255 : modelinfo->ComputeLevelScreenFade( vecAbsCenter, flRadius, flFadeScale ); + unsigned char nDistAlpha; + + if ( !engine->IsLevelMainMenuBackground() ) + { + nDistAlpha = modelinfo->ComputeViewScreenFade( vecAbsCenter, flRadius, flFadeScale ); + } + else + { + nDistAlpha = 255; + } + + if ( nDistAlpha < nGlobalAlpha ) + { + nGlobalAlpha = nDistAlpha; + } + + if ( nGlobalAlpha < nAlpha ) + { + nAlpha = nGlobalAlpha; + } + } + + return nAlpha; +} + + +//----------------------------------------------------------------------------- +// Purpose: Given a vector, clamps the scalar axes to MAX_COORD_FLOAT ranges from worldsize.h +// Input : *pVecPos - +//----------------------------------------------------------------------------- +void UTIL_BoundToWorldSize( Vector *pVecPos ) +{ + Assert( pVecPos ); + for ( int i = 0; i < 3; ++i ) + { + (*pVecPos)[ i ] = clamp( (*pVecPos)[ i ], MIN_COORD_FLOAT, MAX_COORD_FLOAT ); + } +} + +#ifdef _X360 +#define MAP_KEY_FILE_DIR "cfg" +#else +#define MAP_KEY_FILE_DIR "media" +#endif + +//----------------------------------------------------------------------------- +// Purpose: Returns the filename to count map loads in +//----------------------------------------------------------------------------- +bool UTIL_GetMapLoadCountFileName( const char *pszFilePrependName, char *pszBuffer, int iBuflen ) +{ + if ( IsX360() ) + { +#ifdef _X360 + if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED ) + return false; +#endif + } + + if ( IsX360() ) + { + Q_snprintf( pszBuffer, iBuflen, "%s:/%s", MAP_KEY_FILE_DIR, pszFilePrependName ); + } + else + { + Q_snprintf( pszBuffer, iBuflen, "%s/%s", MAP_KEY_FILE_DIR, pszFilePrependName ); + } + + return true; +} + +#ifdef TF_CLIENT_DLL +#define MAP_KEY_FILE "viewed.res" +#else +#define MAP_KEY_FILE "mapkeys.res" +#endif + +void UTIL_IncrementMapKey( const char *pszCustomKey ) +{ + if ( !pszCustomKey ) + return; + + char szFilename[ _MAX_PATH ]; + if ( !UTIL_GetMapLoadCountFileName( MAP_KEY_FILE, szFilename, _MAX_PATH ) ) + return; + + int iCount = 1; + + KeyValues *kvMapLoadFile = new KeyValues( MAP_KEY_FILE ); + if ( kvMapLoadFile ) + { + kvMapLoadFile->LoadFromFile( g_pFullFileSystem, szFilename, "MOD" ); + + char mapname[MAX_MAP_NAME]; + Q_FileBase( engine->GetLevelName(), mapname, sizeof( mapname) ); + Q_strlower( mapname ); + + // Increment existing, or add a new one + KeyValues *pMapKey = kvMapLoadFile->FindKey( mapname ); + if ( pMapKey ) + { + iCount = pMapKey->GetInt( pszCustomKey, 0 ) + 1; + pMapKey->SetInt( pszCustomKey, iCount ); + } + else + { + KeyValues *pNewKey = new KeyValues( mapname ); + if ( pNewKey ) + { + pNewKey->SetString( pszCustomKey, "1" ); + kvMapLoadFile->AddSubKey( pNewKey ); + } + } + + // Write it out + + // force create this directory incase it doesn't exist + filesystem->CreateDirHierarchy( MAP_KEY_FILE_DIR, "MOD"); + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kvMapLoadFile->RecursiveSaveToFile( buf, 0 ); + g_pFullFileSystem->WriteFile( szFilename, "MOD", buf ); + + kvMapLoadFile->deleteThis(); + } + + if ( IsX360() ) + { +#ifdef _X360 + xboxsystem->FinishContainerWrites(); +#endif + } +} + +int UTIL_GetMapKeyCount( const char *pszCustomKey ) +{ + if ( !pszCustomKey ) + return 0; + + char szFilename[ _MAX_PATH ]; + if ( !UTIL_GetMapLoadCountFileName( MAP_KEY_FILE, szFilename, _MAX_PATH ) ) + return 0; + + int iCount = 0; + + KeyValues *kvMapLoadFile = new KeyValues( MAP_KEY_FILE ); + if ( kvMapLoadFile ) + { + // create an empty file if none exists + if ( !g_pFullFileSystem->FileExists( szFilename, "MOD" ) ) + { + // force create this directory incase it doesn't exist + filesystem->CreateDirHierarchy( MAP_KEY_FILE_DIR, "MOD"); + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + g_pFullFileSystem->WriteFile( szFilename, "MOD", buf ); + } + + kvMapLoadFile->LoadFromFile( g_pFullFileSystem, szFilename, "MOD" ); + + char mapname[MAX_MAP_NAME]; + Q_FileBase( engine->GetLevelName(), mapname, sizeof( mapname) ); + Q_strlower( mapname ); + + KeyValues *pMapKey = kvMapLoadFile->FindKey( mapname ); + if ( pMapKey ) + { + iCount = pMapKey->GetInt( pszCustomKey ); + } + + kvMapLoadFile->deleteThis(); + } + + return iCount; +} \ No newline at end of file diff --git a/game/client/cdll_util.h b/game/client/cdll_util.h new file mode 100644 index 00000000..67a5cf10 --- /dev/null +++ b/game/client/cdll_util.h @@ -0,0 +1,172 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( UTIL_H ) +#define UTIL_H + +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "mathlib/vector.h" +#include + +#include "ispatialpartition.h" +#include "materialsystem/materialsystemutil.h" + +class Vector; +class QAngle; +class IMaterial; +class ITexture; +class IClientEntity; +class CHudTexture; +class CGameTrace; +class C_BaseEntity; + +struct Ray_t; +struct client_textmessage_t; +typedef CGameTrace trace_t; + +namespace vgui +{ + typedef unsigned long HFont; +}; + + +enum ImageFormat; +enum ShakeCommand_t; + +extern bool g_MakingDevShots; + +// ScreenHeight returns the height of the screen, in pixels +int ScreenHeight( void ); +// ScreenWidth returns the width of the screen, in pixels +int ScreenWidth( void ); + +#define XRES(x) ( x * ( ( float )ScreenWidth() / 640.0 ) ) +#define YRES(y) ( y * ( ( float )ScreenHeight() / 480.0 ) ) + +int UTIL_ComputeStringWidth( vgui::HFont& font, const char *str ); +int UTIL_ComputeStringWidth( vgui::HFont& font, const wchar_t *str ); +float UTIL_AngleDiff( float destAngle, float srcAngle ); +void UTIL_Bubbles( const Vector& mins, const Vector& maxs, int count ); +void UTIL_Smoke( const Vector &origin, const float scale, const float framerate ); +void UTIL_ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName = NULL ); +int UTIL_PrecacheDecal( const char *name, bool preload = false ); +void UTIL_EmitAmbientSound( C_BaseEntity *entity, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch ); +void UTIL_SetOrigin( C_BaseEntity *entity, const Vector &vecOrigin ); +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake=false ); +byte *UTIL_LoadFileForMe( const char *filename, int *pLength ); +void UTIL_FreeFile( byte *buffer ); +void UTIL_MakeSafeName( const char *oldName, char *newName, int newNameBufSize ); ///< Cleans up player names for putting in vgui controls (cleaned names can be up to original*2+1 in length) +const char *UTIL_SafeName( const char *oldName ); ///< Wraps UTIL_MakeSafeName, and returns a static buffer +void UTIL_ReplaceKeyBindings( const wchar_t *inbuf, int inbufsizebytes, wchar_t *outbuf, int outbufsizebytes ); + +// Fade out an entity based on distance fades +unsigned char UTIL_ComputeEntityFade( C_BaseEntity *pEntity, float flMinDist, float flMaxDist, float flFadeScale ); + +client_textmessage_t *TextMessageGet( const char *pName ); + +char *VarArgs( char *format, ... ); + + +// Get the entity the local player is spectating (can be a player or a ragdoll entity). +int GetSpectatorTarget(); +int GetSpectatorMode( void ); +bool IsPlayerIndex( int index ); +int GetLocalPlayerIndex( void ); +int GetLocalPlayerTeam( void ); +bool IsLocalPlayerSpectator( void ); +void NormalizeAngles( QAngle& angles ); +void InterpolateAngles( const QAngle& start, const QAngle& end, QAngle& output, float frac ); +void InterpolateVector( float frac, const Vector& src, const Vector& dest, Vector& output ); + +const char *nexttoken(char *token, const char *str, char sep); + +//----------------------------------------------------------------------------- +// Base light indices to avoid index collision +//----------------------------------------------------------------------------- + +enum +{ + LIGHT_INDEX_TE_DYNAMIC = 0x10000000, + LIGHT_INDEX_PLAYER_BRIGHT = 0x20000000, + LIGHT_INDEX_MUZZLEFLASH = 0x40000000, +}; + +void UTIL_PrecacheOther( const char *szClassname ); + +void UTIL_SetTrace(trace_t& tr, const Ray_t& ray, C_BaseEntity *edict, float fraction, int hitgroup, unsigned int contents, const Vector& normal, float intercept ); + +bool GetVectorInScreenSpace( Vector pos, int& iX, int& iY, Vector *vecOffset = NULL ); +bool GetTargetInScreenSpace( C_BaseEntity *pTargetEntity, int& iX, int& iY, Vector *vecOffset = NULL ); + +// prints messages through the HUD (stub in client .dll right now ) +class C_BasePlayer; +void ClientPrint( C_BasePlayer *player, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +int UTIL_EntitiesInBox( C_BaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask, int partitionMask = PARTITION_CLIENT_NON_STATIC_EDICTS ); +int UTIL_EntitiesInSphere( C_BaseEntity **pList, int listMax, const Vector ¢er, float radius, int flagMask, int partitionMask = PARTITION_CLIENT_NON_STATIC_EDICTS ); + +// make this a fixed size so it just sits on the stack +#define MAX_SPHERE_QUERY 256 +class CEntitySphereQuery +{ +public: + // currently this builds the list in the constructor + // UNDONE: make an iterative query of ISpatialPartition so we could + // make queries like this optimal + CEntitySphereQuery( const Vector ¢er, float radius, int flagMask=0, int partitionMask = PARTITION_CLIENT_NON_STATIC_EDICTS ); + C_BaseEntity *GetCurrentEntity(); + inline void NextEntity() { m_listIndex++; } + +private: + int m_listIndex; + int m_listCount; + C_BaseEntity *m_pList[MAX_SPHERE_QUERY]; +}; + +// creates an entity by name, and ensure it's correctness +// does not spawn the entity +// use the CREATE_ENTITY() macro which wraps this, instead of using it directly +template< class T > +T *_CreateEntity( T *newClass, const char *className ) +{ + T *newEnt = dynamic_cast( CreateEntityByName(className) ); + if ( !newEnt ) + { + Warning( "classname %s used to create wrong class type\n" ); + Assert(0); + } + + return newEnt; +} + +#define CREATE_ENTITY( newClass, className ) _CreateEntity( (newClass*)NULL, className ) +#define CREATE_UNSAVED_ENTITY( newClass, className ) _CreateEntityTemplate( (newClass*)NULL, className ) + +// Misc useful +inline bool FStrEq(const char *sz1, const char *sz2) +{ + return(stricmp(sz1, sz2) == 0); +} + +// Given a vector, clamps the scalar axes to MAX_COORD_FLOAT ranges from worldsize.h +void UTIL_BoundToWorldSize( Vector *pVecPos ); + +// Increments the passed key for the current map, eg "viewed" if TF holds the number of times the player has +// viewed the intro movie for this map +void UTIL_IncrementMapKey( const char *pszCustomKey ); + +// Gets the value of the passed key for the current map, eg "viewed" for number of times the player has viewed +// the intro movie for this map +int UTIL_GetMapKeyCount( const char *pszCustomKey ); + +#endif // !UTIL_H diff --git a/game/client/cl_animevent.h b/game/client/cl_animevent.h new file mode 100644 index 00000000..330df6a9 --- /dev/null +++ b/game/client/cl_animevent.h @@ -0,0 +1,52 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Hold definitions for all client animation events +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#if !defined( CL_ANIMEVENT_H ) +#define CL_ANIMEVENT_H +#ifdef _WIN32 +#pragma once +#endif + +//Animation event codes +#define CL_EVENT_MUZZLEFLASH0 5001 // Muzzleflash on attachment 0 +#define CL_EVENT_MUZZLEFLASH1 5011 // Muzzleflash on attachment 1 +#define CL_EVENT_MUZZLEFLASH2 5021 // Muzzleflash on attachment 2 +#define CL_EVENT_MUZZLEFLASH3 5031 // Muzzleflash on attachment 3 +#define CL_EVENT_SPARK0 5002 // Spark on attachment 0 +#define CL_EVENT_NPC_MUZZLEFLASH0 5003 // Muzzleflash on attachment 0 for third person views +#define CL_EVENT_NPC_MUZZLEFLASH1 5013 // Muzzleflash on attachment 1 for third person views +#define CL_EVENT_NPC_MUZZLEFLASH2 5023 // Muzzleflash on attachment 2 for third person views +#define CL_EVENT_NPC_MUZZLEFLASH3 5033 // Muzzleflash on attachment 3 for third person views +#define CL_EVENT_SOUND 5004 // Emit a sound // NOTE THIS MUST MATCH THE DEFINE AT CBaseEntity::PrecacheModel on the server!!!!! +#define CL_EVENT_EJECTBRASS1 6001 // Eject a brass shell from attachment 1 + +#define CL_EVENT_DISPATCHEFFECT0 9001 // Hook into a DispatchEffect on attachment 0 +#define CL_EVENT_DISPATCHEFFECT1 9011 // Hook into a DispatchEffect on attachment 1 +#define CL_EVENT_DISPATCHEFFECT2 9021 // Hook into a DispatchEffect on attachment 2 +#define CL_EVENT_DISPATCHEFFECT3 9031 // Hook into a DispatchEffect on attachment 3 +#define CL_EVENT_DISPATCHEFFECT4 9041 // Hook into a DispatchEffect on attachment 4 +#define CL_EVENT_DISPATCHEFFECT5 9051 // Hook into a DispatchEffect on attachment 5 +#define CL_EVENT_DISPATCHEFFECT6 9061 // Hook into a DispatchEffect on attachment 6 +#define CL_EVENT_DISPATCHEFFECT7 9071 // Hook into a DispatchEffect on attachment 7 +#define CL_EVENT_DISPATCHEFFECT8 9081 // Hook into a DispatchEffect on attachment 8 +#define CL_EVENT_DISPATCHEFFECT9 9091 // Hook into a DispatchEffect on attachment 9 + +// These two events are used by c_env_spritegroup. +// FIXME: Should this be local to c_env_spritegroup? +#define CL_EVENT_SPRITEGROUP_CREATE 6002 +#define CL_EVENT_SPRITEGROUP_DESTROY 6003 +#define CL_EVENT_FOOTSTEP_LEFT 6004 +#define CL_EVENT_FOOTSTEP_RIGHT 6005 +#define CL_EVENT_MFOOTSTEP_LEFT 6006 // Footstep sounds based on material underfoot. +#define CL_EVENT_MFOOTSTEP_RIGHT 6007 +#define CL_EVENT_MFOOTSTEP_LEFT_LOUD 6008 // Loud material impact sounds from feet attachments +#define CL_EVENT_MFOOTSTEP_RIGHT_LOUD 6009 + + +#endif // CL_ANIMEVENT_H \ No newline at end of file diff --git a/game/client/cl_mat_stub.cpp b/game/client/cl_mat_stub.cpp new file mode 100644 index 00000000..f0878f40 --- /dev/null +++ b/game/client/cl_mat_stub.cpp @@ -0,0 +1,74 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" +#include "bitmap/imageformat.h" +#include "cl_mat_stub.h" +#include "materialsystem/imaterialsystemstub.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Hook the engine's mat_stub cvar. +ConVar mat_stub( "mat_stub", "0", FCVAR_CHEAT ); +extern ConVar gl_clear; + + +IMaterialSystemStub* GetStubMaterialSystem() +{ + return materials_stub; +} + +// ---------------------------------------------------------------------------------------- // +// CMatStubHandler implementation. +// ---------------------------------------------------------------------------------------- // + +CMatStubHandler::CMatStubHandler() +{ + if ( mat_stub.GetInt() ) + { + m_pOldMaterialSystem = materials; + + // Replace all material system pointers with the stub. + GetStubMaterialSystem()->SetRealMaterialSystem( materials ); + materials->SetInStubMode( true ); + materials = GetStubMaterialSystem(); + engine->Mat_Stub( materials ); + } + else + { + m_pOldMaterialSystem = 0; + } +} + + +CMatStubHandler::~CMatStubHandler() +{ + End(); +} + + +void CMatStubHandler::End() +{ + // Put back the original material system pointer. + if ( m_pOldMaterialSystem ) + { + materials = m_pOldMaterialSystem; + materials->SetInStubMode( false ); + engine->Mat_Stub( materials ); + m_pOldMaterialSystem = 0; +// if( gl_clear.GetBool() ) + { + materials->ClearBuffers( true, true ); + } + } +} + + +bool IsMatStubEnabled() +{ + return mat_stub.GetBool(); +} diff --git a/game/client/cl_mat_stub.h b/game/client/cl_mat_stub.h new file mode 100644 index 00000000..87564785 --- /dev/null +++ b/game/client/cl_mat_stub.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MAT_STUB_H +#define MAT_STUB_H +#ifdef _WIN32 +#pragma once +#endif + + +class IMaterialSystem; + + +// To stub out the material system in a block of code (if mat_stub is 1), +// make an instance of this class. You can unstub it by calling End() or +// it will automatically unstub in its destructor. +class CMatStubHandler +{ +public: + CMatStubHandler(); + ~CMatStubHandler(); + + void End(); + +public: + + IMaterialSystem *m_pOldMaterialSystem; +}; + + +// Returns true if mat_stub is 1. +bool IsMatStubEnabled(); + + +#endif // MAT_STUB_H diff --git a/game/client/classmap.cpp b/game/client/classmap.cpp new file mode 100644 index 00000000..837760df --- /dev/null +++ b/game/client/classmap.cpp @@ -0,0 +1,137 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "iclassmap.h" +#include "utldict.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class classentry_t +{ +public: + classentry_t() + { + mapname[ 0 ] = 0; + factory = 0; + size = -1; + } + + char const *GetMapName() const + { + return mapname; + } + + void SetMapName( char const *newname ) + { + Q_strncpy( mapname, newname, sizeof( mapname ) ); + } + + DISPATCHFUNCTION factory; + int size; +private: + char mapname[ 40 ]; +}; + +class CClassMap : public IClassMap +{ +public: + virtual void Add( const char *mapname, const char *classname, int size, DISPATCHFUNCTION factory /*= 0*/ ); + virtual const char *Lookup( const char *classname ); + virtual C_BaseEntity *CreateEntity( const char *mapname ); + virtual int GetClassSize( const char *classname ); + +private: + CUtlDict< classentry_t, unsigned short > m_ClassDict; +}; + +IClassMap& GetClassMap( void ) +{ + static CClassMap g_Classmap; + return g_Classmap; +} + +void CClassMap::Add( const char *mapname, const char *classname, int size, DISPATCHFUNCTION factory = 0 ) +{ + const char *map = Lookup( classname ); + if ( map && !Q_strcasecmp( mapname, map ) ) + return; + + if ( map ) + { + int index = m_ClassDict.Find( classname ); + Assert( index != m_ClassDict.InvalidIndex() ); + m_ClassDict.RemoveAt( index ); + } + + classentry_t element; + element.SetMapName( mapname ); + element.factory = factory; + element.size = size; + m_ClassDict.Insert( classname, element ); +} + +const char *CClassMap::Lookup( const char *classname ) +{ + unsigned short index; + static classentry_t lookup; + + index = m_ClassDict.Find( classname ); + if ( index == m_ClassDict.InvalidIndex() ) + return NULL; + + lookup = m_ClassDict.Element( index ); + return lookup.GetMapName(); +} + +C_BaseEntity *CClassMap::CreateEntity( const char *mapname ) +{ + int c = m_ClassDict.Count(); + int i; + + for ( i = 0; i < c; i++ ) + { + classentry_t *lookup = &m_ClassDict[ i ]; + if ( !lookup ) + continue; + + if ( Q_stricmp( lookup->GetMapName(), mapname ) ) + continue; + + if ( !lookup->factory ) + { +#if defined( _DEBUG ) + Msg( "No factory for %s/%s\n", lookup->GetMapName(), m_ClassDict.GetElementName( i ) ); +#endif + continue; + } + + return ( *lookup->factory )(); + } + + return NULL; +} + +int CClassMap::GetClassSize( const char *classname ) +{ + int c = m_ClassDict.Count(); + int i; + + for ( i = 0; i < c; i++ ) + { + classentry_t *lookup = &m_ClassDict[ i ]; + if ( !lookup ) + continue; + + if ( Q_strcmp( lookup->GetMapName(), classname ) ) + continue; + + return lookup->size; + } + + return -1; +} diff --git a/game/client/client_episodic-2005.vcproj b/game/client/client_episodic-2005.vcproj new file mode 100644 index 00000000..a005b739 --- /dev/null +++ b/game/client/client_episodic-2005.vcproj @@ -0,0 +1,5115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/game/client/client_factorylist.cpp b/game/client/client_factorylist.cpp new file mode 100644 index 00000000..f7d52430 --- /dev/null +++ b/game/client/client_factorylist.cpp @@ -0,0 +1,25 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "client_factorylist.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static factorylist_t s_factories; + +// Store off the factories +void FactoryList_Store( const factorylist_t &sourceData ) +{ + s_factories = sourceData; +} + +// retrieve the stored factories +void FactoryList_Retrieve( factorylist_t &destData ) +{ + destData = s_factories; +} diff --git a/game/client/client_factorylist.h b/game/client/client_factorylist.h new file mode 100644 index 00000000..f3651a1a --- /dev/null +++ b/game/client/client_factorylist.h @@ -0,0 +1,27 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef CLIENT_FACTORYLIST_H +#define CLIENT_FACTORYLIST_H +#ifdef _WIN32 +#pragma once +#endif + +#include "interface.h" + +struct factorylist_t +{ + CreateInterfaceFn appSystemFactory; + CreateInterfaceFn physicsFactory; +}; + +// Store off the factories +void FactoryList_Store( const factorylist_t &sourceData ); + +// retrieve the stored factories +void FactoryList_Retrieve( factorylist_t &destData ); + +#endif // CLIENT_FACTORYLIST_H diff --git a/game/client/client_hl2-2005.vcproj b/game/client/client_hl2-2005.vcproj new file mode 100644 index 00000000..565ce537 --- /dev/null +++ b/game/client/client_hl2-2005.vcproj @@ -0,0 +1,5051 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/game/client/client_hl2mp-2005.vcproj b/game/client/client_hl2mp-2005.vcproj new file mode 100644 index 00000000..69202e2e --- /dev/null +++ b/game/client/client_hl2mp-2005.vcproj @@ -0,0 +1,5227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/game/client/client_scratch-2005.vcproj b/game/client/client_scratch-2005.vcproj new file mode 100644 index 00000000..03fcada7 --- /dev/null +++ b/game/client/client_scratch-2005.vcproj @@ -0,0 +1,4859 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/game/client/client_thinklist.cpp b/game/client/client_thinklist.cpp new file mode 100644 index 00000000..5f8394fd --- /dev/null +++ b/game/client/client_thinklist.cpp @@ -0,0 +1,402 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" + +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CClientThinkList g_ClientThinkList; + + +CClientThinkList::CClientThinkList() +{ +} + + +CClientThinkList::~CClientThinkList() +{ +} + + +//----------------------------------------------------------------------------- +// Methods of IGameSystem +//----------------------------------------------------------------------------- +bool CClientThinkList::Init() +{ + m_nIterEnum = 0; + m_bInThinkLoop = false; + return true; +} + + +void CClientThinkList::Shutdown() +{ +} + + +void CClientThinkList::LevelInitPreEntity() +{ + m_nIterEnum = 0; +} + +void CClientThinkList::LevelShutdownPreEntity() +{ +} + + +void CClientThinkList::LevelShutdownPostEntity() +{ +} + + +void CClientThinkList::PreRender() +{ +} + + +void CClientThinkList::Update( float frametime ) +{ +} + + +//----------------------------------------------------------------------------- +// Sets the client think +//----------------------------------------------------------------------------- +void CClientThinkList::SetNextClientThink( ClientThinkHandle_t hThink, float flNextTime ) +{ + if ( hThink == INVALID_THINK_HANDLE ) + return; + + if ( m_bInThinkLoop ) + { + // Queue up all changes + int i = m_aChangeList.AddToTail(); + m_aChangeList[i].m_hEnt = INVALID_CLIENTENTITY_HANDLE; + m_aChangeList[i].m_hThink = hThink; + m_aChangeList[i].m_flNextTime = flNextTime; + return; + } + + if ( flNextTime == CLIENT_THINK_NEVER ) + { + RemoveThinkable( hThink ); + } + else + { + GetThinkEntry( hThink )->m_flNextClientThink = flNextTime; + } +} + +void CClientThinkList::SetNextClientThink( ClientEntityHandle_t hEnt, float flNextTime ) +{ + if ( flNextTime == CLIENT_THINK_NEVER ) + { + RemoveThinkable( hEnt ); + return; + } + + IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( hEnt ); + if ( !pThink ) + return; + + ClientThinkHandle_t hThink = pThink->GetThinkHandle(); + + if ( m_bInThinkLoop ) + { + // Queue up all changes + int i = m_aChangeList.AddToTail(); + m_aChangeList[i].m_hEnt = hEnt; + m_aChangeList[i].m_hThink = hThink; + m_aChangeList[i].m_flNextTime = flNextTime; + return; + } + + // Add it to the list if it's not already in there. + if ( hThink == INVALID_THINK_HANDLE ) + { + hThink = (ClientThinkHandle_t)m_ThinkEntries.AddToTail(); + pThink->SetThinkHandle( hThink ); + + ThinkEntry_t *pEntry = GetThinkEntry( hThink ); + pEntry->m_hEnt = hEnt; + pEntry->m_nIterEnum = -1; + pEntry->m_flLastClientThink = 0.0f; + } + + Assert( GetThinkEntry( hThink )->m_hEnt == hEnt ); + GetThinkEntry( hThink )->m_flNextClientThink = flNextTime; +} + + +//----------------------------------------------------------------------------- +// Removes the thinkable from the list +//----------------------------------------------------------------------------- +void CClientThinkList::RemoveThinkable( ClientThinkHandle_t hThink ) +{ + if ( hThink == INVALID_THINK_HANDLE ) + return; + + if ( m_bInThinkLoop ) + { + // Queue up all changes + int i = m_aChangeList.AddToTail(); + m_aChangeList[i].m_hEnt = INVALID_CLIENTENTITY_HANDLE; + m_aChangeList[i].m_hThink = hThink; + m_aChangeList[i].m_flNextTime = CLIENT_THINK_NEVER; + return; + } + + ThinkEntry_t *pEntry = GetThinkEntry( hThink ); + IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( pEntry->m_hEnt ); + if ( pThink ) + { + pThink->SetThinkHandle( INVALID_THINK_HANDLE ); + } + m_ThinkEntries.Remove( (unsigned long)hThink ); +} + + +//----------------------------------------------------------------------------- +// Removes the thinkable from the list +//----------------------------------------------------------------------------- +void CClientThinkList::RemoveThinkable( ClientEntityHandle_t hEnt ) +{ + IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( hEnt ); + if ( pThink ) + { + ClientThinkHandle_t hThink = pThink->GetThinkHandle(); + if ( hThink != INVALID_THINK_HANDLE ) + { + Assert( GetThinkEntry( hThink )->m_hEnt == hEnt ); + RemoveThinkable( hThink ); + } + } +} + + +//----------------------------------------------------------------------------- +// Performs the think function +//----------------------------------------------------------------------------- +void CClientThinkList::PerformThinkFunction( ThinkEntry_t *pEntry, float flCurtime ) +{ + IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( pEntry->m_hEnt ); + if ( !pThink ) + { + RemoveThinkable( pEntry->m_hEnt ); + return; + } + + if ( pEntry->m_flNextClientThink == CLIENT_THINK_ALWAYS ) + { + // NOTE: The Think function here could call SetNextClientThink + // which would cause it to be removed + readded into the list + pThink->ClientThink(); + } + else if ( pEntry->m_flNextClientThink == FLT_MAX ) + { + // This is an entity that doesn't need to think again; remove it + RemoveThinkable( pEntry->m_hEnt ); + } + else + { + Assert( pEntry->m_flNextClientThink <= flCurtime ); + + // Indicate we're not going to think again + pEntry->m_flNextClientThink = FLT_MAX; + + // NOTE: The Think function here could call SetNextClientThink + // which would cause it to be readded into the list + pThink->ClientThink(); + } + + // Set this after the Think calls in case they look at LastClientThink + pEntry->m_flLastClientThink = flCurtime; +} + + +//----------------------------------------------------------------------------- +// Add entity to frame think list +//----------------------------------------------------------------------------- +void CClientThinkList::AddEntityToFrameThinkList( ThinkEntry_t *pEntry, bool bAlwaysChain, int &nCount, ThinkEntry_t **ppFrameThinkList ) +{ + // We may already have processed this owing to hierarchy rules + if ( pEntry->m_nIterEnum == m_nIterEnum ) + return; + + // If we're not thinking this frame, we don't have to worry about thinking after our parents + bool bThinkThisInterval = ( pEntry->m_flNextClientThink == CLIENT_THINK_ALWAYS ) || + ( pEntry->m_flNextClientThink <= gpGlobals->curtime ); + + // This logic makes it so that if a child thinks, + // *all* hierarchical parents + grandparents will think first, even if some + // of the parents don't need to think this frame + if ( !bThinkThisInterval && !bAlwaysChain ) + return; + + // Respect hierarchy + C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( pEntry->m_hEnt ); + if ( pEntity ) + { + C_BaseEntity *pParent = pEntity->GetMoveParent(); + if ( pParent && (pParent->GetThinkHandle() != INVALID_THINK_HANDLE) ) + { + ThinkEntry_t *pParentEntry = GetThinkEntry( pParent->GetThinkHandle() ); + AddEntityToFrameThinkList( pParentEntry, true, nCount, ppFrameThinkList ); + } + } + + if ( !bThinkThisInterval ) + return; + + // Add the entry into the list + pEntry->m_nIterEnum = m_nIterEnum; + ppFrameThinkList[nCount++] = pEntry; +} + + +//----------------------------------------------------------------------------- +// Think for all entities that need it +//----------------------------------------------------------------------------- +void CClientThinkList::PerformThinkFunctions() +{ + VPROF_("Client Thinks", 1, VPROF_BUDGETGROUP_CLIENT_SIM, false, BUDGETFLAG_CLIENT); + + int nMaxList = m_ThinkEntries.Count(); + if ( nMaxList == 0 ) + return; + + ++m_nIterEnum; + + // Build a list of entities to think this frame, in order of hierarchy. + // Do this because the list may be modified during the thinking and also to + // prevent bad situations where an entity can think more than once in a frame. + ThinkEntry_t **ppThinkEntryList = (ThinkEntry_t**)stackalloc( nMaxList * sizeof(ThinkEntry_t*) ); + int nThinkCount = 0; + for ( unsigned short iCur=m_ThinkEntries.Head(); iCur != m_ThinkEntries.InvalidIndex(); iCur = m_ThinkEntries.Next( iCur ) ) + { + AddEntityToFrameThinkList( &m_ThinkEntries[iCur], false, nThinkCount, ppThinkEntryList ); + Assert( nThinkCount <= nMaxList ); + } + + // While we're in the loop, no changes to the think list are allowed + m_bInThinkLoop = true; + + // Perform thinks on all entities that need it + int i; + for ( i = 0; i < nThinkCount; ++i ) + { + PerformThinkFunction( ppThinkEntryList[i], gpGlobals->curtime ); + } + + m_bInThinkLoop = false; + + // Apply changes to the think list + int nCount = m_aChangeList.Count(); + for ( i = 0; i < nCount; ++i ) + { + ClientThinkHandle_t hThink = m_aChangeList[i].m_hThink; + if ( hThink != INVALID_THINK_HANDLE ) + { + // This can happen if the same think handle was removed twice + if ( !m_ThinkEntries.IsInList( (unsigned long)hThink ) ) + continue; + + // NOTE: This is necessary for the case where the client entity handle + // is slammed to NULL during a think interval; the hThink will get stuck + // in the list and can never leave. + SetNextClientThink( hThink, m_aChangeList[i].m_flNextTime ); + } + else + { + SetNextClientThink( m_aChangeList[i].m_hEnt, m_aChangeList[i].m_flNextTime ); + } + } + m_aChangeList.RemoveAll(); + + // Clear out the client-side entity deletion list. + CleanUpDeleteList(); +} + + +//----------------------------------------------------------------------------- +// Queued-up entity deletion +//----------------------------------------------------------------------------- +void CClientThinkList::AddToDeleteList( ClientEntityHandle_t hEnt ) +{ + // Sanity check! + Assert( hEnt != ClientEntityList().InvalidHandle() ); + if ( hEnt == ClientEntityList().InvalidHandle() ) + return; + + // Check to see if entity is networkable -- don't let it release! + C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( hEnt ); + if ( pEntity ) + { + // Check to see if the entity is already being removed! + if ( pEntity->IsMarkedForDeletion() ) + return; + + // Don't add networkable entities to delete list -- the server should + // take care of this. The delete list is for client-side only entities. + if ( !pEntity->GetClientNetworkable() ) + { + m_aDeleteList.AddToTail( hEnt ); + pEntity->SetRemovalFlag( true ); + } + } +} + +void CClientThinkList::RemoveFromDeleteList( ClientEntityHandle_t hEnt ) +{ + // Sanity check! + Assert( hEnt != ClientEntityList().InvalidHandle() ); + if ( hEnt == ClientEntityList().InvalidHandle() ) + return; + + int nSize = m_aDeleteList.Count(); + for ( int iHandle = 0; iHandle < nSize; ++iHandle ) + { + if ( m_aDeleteList[iHandle] == hEnt ) + { + m_aDeleteList[iHandle] = ClientEntityList().InvalidHandle(); + + C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( hEnt ); + if ( pEntity ) + { + pEntity->SetRemovalFlag( false ); + } + } + } +} + +void CClientThinkList::CleanUpDeleteList() +{ + int nThinkCount = m_aDeleteList.Count(); + for ( int iThink = 0; iThink < nThinkCount; ++iThink ) + { + ClientEntityHandle_t handle = m_aDeleteList[iThink]; + if ( handle != ClientEntityList().InvalidHandle() ) + { + C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( handle ); + if ( pEntity ) + { + pEntity->SetRemovalFlag( false ); + } + + IClientThinkable *pThink = ClientEntityList().GetClientThinkableFromHandle( handle ); + if ( pThink ) + { + pThink->Release(); + } + } + } + + m_aDeleteList.RemoveAll(); +} + diff --git a/game/client/client_thinklist.h b/game/client/client_thinklist.h new file mode 100644 index 00000000..e77899eb --- /dev/null +++ b/game/client/client_thinklist.h @@ -0,0 +1,135 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CLIENT_THINKLIST_H +#define CLIENT_THINKLIST_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "IGameSystem.h" +#include "utllinkedlist.h" +#include "cliententitylist.h" +#include "iclientthinkable.h" +#include "utlrbtree.h" + + +#define CLIENT_THINK_ALWAYS -1293 +#define CLIENT_THINK_NEVER -1 + + +#define INVALID_THINK_HANDLE ClientThinkList()->GetInvalidThinkHandle() + + +class CClientThinkList : public IGameSystemPerFrame +{ +public: + + CClientThinkList(); + virtual ~CClientThinkList(); + + virtual char const *Name() { return "CClientThinkList"; } + virtual bool IsPerFrame() { return true; } + + // Set the next time at which you want to think. You can also use + // one of the CLIENT_THINK_ defines. + void SetNextClientThink( ClientEntityHandle_t hEnt, float nextTime ); + + // Remove an entity from the think list. + void RemoveThinkable( ClientEntityHandle_t hEnt ); + + // Use to initialize your think handles in IClientThinkables. + ClientThinkHandle_t GetInvalidThinkHandle(); + + // This is called after network updating and before rendering. + void PerformThinkFunctions(); + + // Call this to destroy a thinkable object - deletes the object post think. + void AddToDeleteList( ClientEntityHandle_t hEnt ); + void RemoveFromDeleteList( ClientEntityHandle_t hEnt ); + +// IClientSystem implementation. +public: + + virtual bool Init(); + virtual void PostInit() {}; + virtual void Shutdown(); + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity() {} + virtual void LevelShutdownPreEntity(); + virtual void LevelShutdownPostEntity(); + virtual void PreRender(); + virtual void PostRender() { } + virtual void Update( float frametime ); + virtual void OnSave() {} + virtual void OnRestore() {} + virtual void SafeRemoveIfDesired() {} + +private: + struct ThinkEntry_t + { + ClientEntityHandle_t m_hEnt; + float m_flNextClientThink; + float m_flLastClientThink; + int m_nIterEnum; + }; + + struct ThinkListChanges_t + { + ClientEntityHandle_t m_hEnt; + ClientThinkHandle_t m_hThink; + float m_flNextTime; + }; + +// Internal stuff. +private: + void SetNextClientThink( ClientThinkHandle_t hThink, float nextTime ); + void RemoveThinkable( ClientThinkHandle_t hThink ); + void PerformThinkFunction( ThinkEntry_t *pEntry, float curtime ); + ThinkEntry_t* GetThinkEntry( ClientThinkHandle_t hThink ); + void CleanUpDeleteList(); + + // Add entity to frame think list + void AddEntityToFrameThinkList( ThinkEntry_t *pEntry, bool bAlwaysChain, int &nCount, ThinkEntry_t **ppFrameThinkList ); + +private: + CUtlLinkedList m_ThinkEntries; + + CUtlVector m_aDeleteList; + CUtlVector m_aChangeList; + + // Makes sure the entries are thinked once per frame in the face of hierarchy + int m_nIterEnum; + bool m_bInThinkLoop; +}; + + +// -------------------------------------------------------------------------------- // +// Inlines. +// -------------------------------------------------------------------------------- // + +inline ClientThinkHandle_t CClientThinkList::GetInvalidThinkHandle() +{ + return (ClientThinkHandle_t)m_ThinkEntries.InvalidIndex(); +} + + +inline CClientThinkList::ThinkEntry_t* CClientThinkList::GetThinkEntry( ClientThinkHandle_t hThink ) +{ + return &m_ThinkEntries[ (unsigned long)hThink ]; +} + + +inline CClientThinkList* ClientThinkList() +{ + extern CClientThinkList g_ClientThinkList; + return &g_ClientThinkList; +} + + +#endif // CLIENT_THINKLIST_H diff --git a/game/client/clienteffectprecachesystem.cpp b/game/client/clienteffectprecachesystem.cpp new file mode 100644 index 00000000..8ce93fdd --- /dev/null +++ b/game/client/clienteffectprecachesystem.cpp @@ -0,0 +1,78 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Deals with precaching requests from client effects +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "fx.h" +#include "ClientEffectPrecacheSystem.h" +#include "particles/particles.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//Global singelton accessor +CClientEffectPrecacheSystem *ClientEffectPrecacheSystem( void ) +{ + static CClientEffectPrecacheSystem s_ClientEffectPrecacheSystem; + return &s_ClientEffectPrecacheSystem; +} + +//----------------------------------------------------------------------------- +// Purpose: Precache all the registered effects +//----------------------------------------------------------------------------- +void CClientEffectPrecacheSystem::LevelInitPreEntity( void ) +{ + //Precache all known effects + for ( int i = 0; i < m_Effects.Size(); i++ ) + { + m_Effects[i]->Cache(); + } + + //FIXME: Double check this + //Finally, force the cache of these materials + materials->CacheUsedMaterials(); + + // Now, cache off our material handles + FX_CacheMaterialHandles(); +} + +//----------------------------------------------------------------------------- +// Purpose: Nothing to do here +//----------------------------------------------------------------------------- +void CClientEffectPrecacheSystem::LevelShutdownPreEntity( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Dereference all the registered effects +//----------------------------------------------------------------------------- +void CClientEffectPrecacheSystem::LevelShutdownPostEntity( void ) +{ + // mark all known effects as free + for ( int i = 0; i < m_Effects.Size(); i++ ) + { + m_Effects[i]->Cache( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Purges the effect list +//----------------------------------------------------------------------------- +void CClientEffectPrecacheSystem::Shutdown( void ) +{ + //Release all effects + m_Effects.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds the effect to the list to be precached +// Input : *effect - system to precache +//----------------------------------------------------------------------------- +void CClientEffectPrecacheSystem::Register( IClientEffect *effect ) +{ + //Hold onto this effect for precaching later + m_Effects.AddToTail( effect ); +} diff --git a/game/client/clienteffectprecachesystem.h b/game/client/clienteffectprecachesystem.h new file mode 100644 index 00000000..79c6e3fa --- /dev/null +++ b/game/client/clienteffectprecachesystem.h @@ -0,0 +1,150 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Deals with singleton +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#if !defined( CLIENTEFFECTPRECACHESYSTEM_H ) +#define CLIENTEFFECTPRECACHESYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "IGameSystem.h" +#include "CommonMacros.h" +#include "utlvector.h" +#include "materialsystem/IMaterialSystem.h" +#include "materialsystem/IMaterial.h" + +//----------------------------------------------------------------------------- +// Interface to automated system for precaching materials +//----------------------------------------------------------------------------- +class IClientEffect +{ +public: + virtual void Cache( bool precache = true ) = 0; +}; + +//----------------------------------------------------------------------------- +// Responsible for managing precaching of particles +//----------------------------------------------------------------------------- + +class CClientEffectPrecacheSystem : public IGameSystem +{ +public: + virtual char const *Name() { return "CCLientEffectPrecacheSystem"; } + + virtual bool IsPerFrame() { return false; } + + // constructor, destructor + CClientEffectPrecacheSystem() {} + virtual ~CClientEffectPrecacheSystem() {} + + // Init, shutdown + virtual bool Init() { return true; } + virtual void PostInit() {} + virtual void Shutdown(); + + // Level init, shutdown + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity() {} + virtual void LevelShutdownPreEntity(); + virtual void LevelShutdownPostEntity(); + + virtual void OnSave() {} + virtual void OnRestore() {} + virtual void SafeRemoveIfDesired() {} + + void Register( IClientEffect *effect ); + +protected: + + CUtlVector< IClientEffect * > m_Effects; +}; + +//Singleton accessor +extern CClientEffectPrecacheSystem *ClientEffectPrecacheSystem(); + +//----------------------------------------------------------------------------- +// Deals with automated registering and precaching of materials for effects +//----------------------------------------------------------------------------- + +class CClientEffect : public IClientEffect +{ +public: + + CClientEffect( void ) + { + //Register with the main effect system + ClientEffectPrecacheSystem()->Register( this ); + } + +//----------------------------------------------------------------------------- +// Purpose: Precache a material by artificially incrementing its reference counter +// Input : *materialName - name of the material +// : increment - whether to increment or decrement the reference counter +//----------------------------------------------------------------------------- + + inline void ReferenceMaterial( const char *materialName, bool increment = true ) + { + IMaterial *material = materials->FindMaterial( materialName, TEXTURE_GROUP_CLIENT_EFFECTS ); + if ( !IsErrorMaterial( material ) ) + { + if ( increment ) + { + material->IncrementReferenceCount(); + } + else + { + material->DecrementReferenceCount(); + } + } + } +}; + +//Automatic precache macros + +//Beginning +#define CLIENTEFFECT_REGISTER_BEGIN( className ) \ +namespace className { \ +class ClientEffectRegister : public CClientEffect \ +{ \ +private: \ + static const char *m_pszMaterials[]; \ +public: \ + void Cache( bool precache = true ); \ +}; \ +const char *ClientEffectRegister::m_pszMaterials[] = { + +//Material definitions +#define CLIENTEFFECT_MATERIAL( materialName ) materialName, + +//End +#define CLIENTEFFECT_REGISTER_END( ) }; \ +void ClientEffectRegister::Cache( bool precache ) \ +{ \ + for ( int i = 0; i < ARRAYSIZE( m_pszMaterials ); i++ ) \ + { \ + ReferenceMaterial( m_pszMaterials[i], precache ); \ + } \ +} \ +ClientEffectRegister register_ClientEffectRegister; \ +} + +#define CLIENTEFFECT_REGISTER_END_CONDITIONAL(condition ) }; \ +void ClientEffectRegister::Cache( bool precache ) \ +{ \ + if ( condition) \ + { \ + for ( int i = 0; i < ARRAYSIZE( m_pszMaterials ); i++ ) \ + { \ + ReferenceMaterial( m_pszMaterials[i], precache ); \ + } \ + } \ +} \ +ClientEffectRegister register_ClientEffectRegister; \ +} + +#endif //CLIENTEFFECTPRECACHESYSTEM_H diff --git a/game/client/cliententitylist.cpp b/game/client/cliententitylist.cpp new file mode 100644 index 00000000..e666dbea --- /dev/null +++ b/game/client/cliententitylist.cpp @@ -0,0 +1,505 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + +//----------------------------------------------------------------------------- +// Purpose: a global list of all the entities in the game. All iteration through +// entities is done through this object. +//----------------------------------------------------------------------------- +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- + +// Create interface +static CClientEntityList s_EntityList; +CBaseEntityList *g_pEntityList = &s_EntityList; + +// Expose list to engine +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CClientEntityList, IClientEntityList, VCLIENTENTITYLIST_INTERFACE_VERSION, s_EntityList ); + +// Store local pointer to interface for rest of client .dll only +// (CClientEntityList instead of IClientEntityList ) +CClientEntityList *cl_entitylist = &s_EntityList; + + +bool PVSNotifierMap_LessFunc( IClientUnknown* const &a, IClientUnknown* const &b ) +{ + return a < b; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CClientEntityList::CClientEntityList( void ) : + m_PVSNotifierMap( 0, 0, PVSNotifierMap_LessFunc ) +{ + m_iMaxUsedServerIndex = -1; + m_iMaxServerEnts = 0; + Release(); + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CClientEntityList::~CClientEntityList( void ) +{ + Release(); +} + +//----------------------------------------------------------------------------- +// Purpose: Clears all entity lists and releases entities +//----------------------------------------------------------------------------- +void CClientEntityList::Release( void ) +{ + // Free all the entities. + ClientEntityHandle_t iter = FirstHandle(); + while( iter != InvalidHandle() ) + { + // Try to call release on anything we can. + IClientNetworkable *pNet = GetClientNetworkableFromHandle( iter ); + if ( pNet ) + { + pNet->Release(); + } + else + { + // Try to call release on anything we can. + IClientThinkable *pThinkable = GetClientThinkableFromHandle( iter ); + if ( pThinkable ) + { + pThinkable->Release(); + } + } + RemoveEntity( iter ); + + iter = FirstHandle(); + } + + m_iNumServerEnts = 0; + m_iMaxServerEnts = 0; + m_iNumClientNonNetworkable = 0; + m_iMaxUsedServerIndex = -1; +} + +IClientNetworkable* CClientEntityList::GetClientNetworkable( int entnum ) +{ + Assert( entnum >= 0 ); + Assert( entnum < MAX_EDICTS ); + return m_EntityCacheInfo[entnum].m_pNetworkable; +} + + +IClientEntity* CClientEntityList::GetClientEntity( int entnum ) +{ + IClientUnknown *pEnt = GetListedEntity( entnum ); + return pEnt ? pEnt->GetIClientEntity() : 0; +} + + +int CClientEntityList::NumberOfEntities( bool bIncludeNonNetworkable ) +{ + if ( bIncludeNonNetworkable == true ) + return m_iNumServerEnts + m_iNumClientNonNetworkable; + + return m_iNumServerEnts; +} + + +void CClientEntityList::SetMaxEntities( int maxents ) +{ + m_iMaxServerEnts = maxents; +} + + +int CClientEntityList::GetMaxEntities( void ) +{ + return m_iMaxServerEnts; +} + + +//----------------------------------------------------------------------------- +// Convenience methods to convert between entindex + ClientEntityHandle_t +//----------------------------------------------------------------------------- +int CClientEntityList::HandleToEntIndex( ClientEntityHandle_t handle ) +{ + if ( handle == INVALID_EHANDLE_INDEX ) + return -1; + C_BaseEntity *pEnt = GetBaseEntityFromHandle( handle ); + return pEnt ? pEnt->entindex() : -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: Because m_iNumServerEnts != last index +// Output : int +//----------------------------------------------------------------------------- +int CClientEntityList::GetHighestEntityIndex( void ) +{ + return m_iMaxUsedServerIndex; +} + +void CClientEntityList::RecomputeHighestEntityUsed( void ) +{ + m_iMaxUsedServerIndex = -1; + + // Walk backward looking for first valid index + int i; + for ( i = MAX_EDICTS - 1; i >= 0; i-- ) + { + if ( GetListedEntity( i ) != NULL ) + { + m_iMaxUsedServerIndex = i; + break; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a raw C_BaseEntity to the entity list. +// Input : index - +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +//----------------------------------------------------------------------------- + +C_BaseEntity* CClientEntityList::GetBaseEntity( int entnum ) +{ + IClientUnknown *pEnt = GetListedEntity( entnum ); + return pEnt ? pEnt->GetBaseEntity() : 0; +} + + +ICollideable* CClientEntityList::GetCollideable( int entnum ) +{ + IClientUnknown *pEnt = GetListedEntity( entnum ); + return pEnt ? pEnt->GetCollideable() : 0; +} + + +IClientNetworkable* CClientEntityList::GetClientNetworkableFromHandle( ClientEntityHandle_t hEnt ) +{ + IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); + return pEnt ? pEnt->GetClientNetworkable() : 0; +} + + +IClientEntity* CClientEntityList::GetClientEntityFromHandle( ClientEntityHandle_t hEnt ) +{ + IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); + return pEnt ? pEnt->GetIClientEntity() : 0; +} + + +IClientRenderable* CClientEntityList::GetClientRenderableFromHandle( ClientEntityHandle_t hEnt ) +{ + IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); + return pEnt ? pEnt->GetClientRenderable() : 0; +} + + +C_BaseEntity* CClientEntityList::GetBaseEntityFromHandle( ClientEntityHandle_t hEnt ) +{ + IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); + return pEnt ? pEnt->GetBaseEntity() : 0; +} + + +ICollideable* CClientEntityList::GetCollideableFromHandle( ClientEntityHandle_t hEnt ) +{ + IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); + return pEnt ? pEnt->GetCollideable() : 0; +} + + +IClientThinkable* CClientEntityList::GetClientThinkableFromHandle( ClientEntityHandle_t hEnt ) +{ + IClientUnknown *pEnt = GetClientUnknownFromHandle( hEnt ); + return pEnt ? pEnt->GetClientThinkable() : 0; +} + + +void CClientEntityList::AddPVSNotifier( IClientUnknown *pUnknown ) +{ + IClientRenderable *pRen = pUnknown->GetClientRenderable(); + if ( pRen ) + { + IPVSNotify *pNotify = pRen->GetPVSNotifyInterface(); + if ( pNotify ) + { + unsigned short index = m_PVSNotifyInfos.AddToTail(); + CPVSNotifyInfo *pInfo = &m_PVSNotifyInfos[index]; + pInfo->m_pNotify = pNotify; + pInfo->m_pRenderable = pRen; + pInfo->m_InPVSStatus = 0; + pInfo->m_PVSNotifiersLink = index; + + m_PVSNotifierMap.Insert( pUnknown, index ); + } + } +} + + +void CClientEntityList::RemovePVSNotifier( IClientUnknown *pUnknown ) +{ + IClientRenderable *pRenderable = pUnknown->GetClientRenderable(); + if ( pRenderable ) + { + IPVSNotify *pNotify = pRenderable->GetPVSNotifyInterface(); + if ( pNotify ) + { + unsigned short index = m_PVSNotifierMap.Find( pUnknown ); + if ( !m_PVSNotifierMap.IsValidIndex( index ) ) + { + Warning( "PVS notifier not in m_PVSNotifierMap\n" ); + Assert( false ); + return; + } + + unsigned short indexIntoPVSNotifyInfos = m_PVSNotifierMap[index]; + + Assert( m_PVSNotifyInfos[indexIntoPVSNotifyInfos].m_pNotify == pNotify ); + Assert( m_PVSNotifyInfos[indexIntoPVSNotifyInfos].m_pRenderable == pRenderable ); + + m_PVSNotifyInfos.Remove( indexIntoPVSNotifyInfos ); + m_PVSNotifierMap.RemoveAt( index ); + return; + } + } + + // If it didn't report itself as a notifier, let's hope it's not in the notifier list now + // (which would mean that it reported itself as a notifier earlier, but not now). +#ifdef _DEBUG + unsigned short index = m_PVSNotifierMap.Find( pUnknown ); + Assert( !m_PVSNotifierMap.IsValidIndex( index ) ); +#endif +} + +void CClientEntityList::AddListenerEntity( IClientEntityListener *pListener ) +{ + if ( m_entityListeners.Find( pListener ) >= 0 ) + { + AssertMsg( 0, "Can't add listeners multiple times\n" ); + return; + } + m_entityListeners.AddToTail( pListener ); +} + +void CClientEntityList::RemoveListenerEntity( IClientEntityListener *pListener ) +{ + m_entityListeners.FindAndRemove( pListener ); +} + +void CClientEntityList::OnAddEntity( IHandleEntity *pEnt, CBaseHandle handle ) +{ + int entnum = handle.GetEntryIndex(); + EntityCacheInfo_t *pCache = &m_EntityCacheInfo[entnum]; + + if ( entnum >= 0 && entnum < MAX_EDICTS ) + { + // Update our counters. + m_iNumServerEnts++; + if ( entnum > m_iMaxUsedServerIndex ) + { + m_iMaxUsedServerIndex = entnum; + } + + + // Cache its networkable pointer. + Assert( dynamic_cast< IClientUnknown* >( pEnt ) ); + Assert( ((IClientUnknown*)pEnt)->GetClientNetworkable() ); // Server entities should all be networkable. + pCache->m_pNetworkable = ((IClientUnknown*)pEnt)->GetClientNetworkable(); + } + + IClientUnknown *pUnknown = (IClientUnknown*)pEnt; + + // If this thing wants PVS notifications, hook it up. + AddPVSNotifier( pUnknown ); + + // Store it in a special list for fast iteration if it's a C_BaseEntity. + C_BaseEntity *pBaseEntity = pUnknown->GetBaseEntity(); + if ( pBaseEntity ) + { + pCache->m_BaseEntitiesIndex = m_BaseEntities.AddToTail( pBaseEntity ); + + if ( pBaseEntity->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) + { + m_iNumClientNonNetworkable++; + } + + //DevMsg(2,"Created %s\n", pBaseEnt->GetClassname() ); + for ( int i = m_entityListeners.Count()-1; i >= 0; i-- ) + { + m_entityListeners[i]->OnEntityCreated( pBaseEntity ); + } + } + else + { + pCache->m_BaseEntitiesIndex = m_BaseEntities.InvalidIndex(); + } + + +} + + +void CClientEntityList::OnRemoveEntity( IHandleEntity *pEnt, CBaseHandle handle ) +{ + int entnum = handle.GetEntryIndex(); + EntityCacheInfo_t *pCache = &m_EntityCacheInfo[entnum]; + + if ( entnum >= 0 && entnum < MAX_EDICTS ) + { + // This is a networkable ent. Clear out our cache info for it. + pCache->m_pNetworkable = NULL; + m_iNumServerEnts--; + + if ( entnum >= m_iMaxUsedServerIndex ) + { + RecomputeHighestEntityUsed(); + } + } + + + IClientUnknown *pUnknown = (IClientUnknown*)pEnt; + + // If this is a PVS notifier, remove it. + RemovePVSNotifier( pUnknown ); + + C_BaseEntity *pBaseEntity = pUnknown->GetBaseEntity(); + + if ( pBaseEntity ) + { + if ( pBaseEntity->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) + { + m_iNumClientNonNetworkable--; + } + + //DevMsg(2,"Deleted %s\n", pBaseEnt->GetClassname() ); + for ( int i = m_entityListeners.Count()-1; i >= 0; i-- ) + { + m_entityListeners[i]->OnEntityDeleted( pBaseEntity ); + } + } + + if ( pCache->m_BaseEntitiesIndex != m_BaseEntities.InvalidIndex() ) + m_BaseEntities.Remove( pCache->m_BaseEntitiesIndex ); + + pCache->m_BaseEntitiesIndex = m_BaseEntities.InvalidIndex(); +} + + +// Use this to iterate over all the C_BaseEntities. +C_BaseEntity* CClientEntityList::FirstBaseEntity() const +{ + const CEntInfo *pList = FirstEntInfo(); + while ( pList ) + { + if ( pList->m_pEntity ) + { + IClientUnknown *pUnk = static_cast( pList->m_pEntity ); + C_BaseEntity *pRet = pUnk->GetBaseEntity(); + if ( pRet ) + return pRet; + } + pList = pList->m_pNext; + } + + return NULL; + +} + +C_BaseEntity* CClientEntityList::NextBaseEntity( C_BaseEntity *pEnt ) const +{ + if ( pEnt == NULL ) + return FirstBaseEntity(); + + // Run through the list until we get a C_BaseEntity. + const CEntInfo *pList = GetEntInfoPtr( pEnt->GetRefEHandle() ); + if ( pList ) + { + pList = NextEntInfo(pList); + } + + while ( pList ) + { + if ( pList->m_pEntity ) + { + IClientUnknown *pUnk = static_cast( pList->m_pEntity ); + C_BaseEntity *pRet = pUnk->GetBaseEntity(); + if ( pRet ) + return pRet; + } + pList = pList->m_pNext; + } + + return NULL; +} + + + +// -------------------------------------------------------------------------------------------------- // +// C_AllBaseEntityIterator +// -------------------------------------------------------------------------------------------------- // +C_AllBaseEntityIterator::C_AllBaseEntityIterator() +{ + Restart(); +} + + +void C_AllBaseEntityIterator::Restart() +{ + m_CurBaseEntity = ClientEntityList().m_BaseEntities.Head(); +} + + +C_BaseEntity* C_AllBaseEntityIterator::Next() +{ + if ( m_CurBaseEntity == ClientEntityList().m_BaseEntities.InvalidIndex() ) + return NULL; + + C_BaseEntity *pRet = ClientEntityList().m_BaseEntities[m_CurBaseEntity]; + m_CurBaseEntity = ClientEntityList().m_BaseEntities.Next( m_CurBaseEntity ); + return pRet; +} + + +// -------------------------------------------------------------------------------------------------- // +// C_BaseEntityIterator +// -------------------------------------------------------------------------------------------------- // +C_BaseEntityIterator::C_BaseEntityIterator() +{ + Restart(); +} + +void C_BaseEntityIterator::Restart() +{ + m_CurBaseEntity = ClientEntityList().m_BaseEntities.Head(); +} + +C_BaseEntity* C_BaseEntityIterator::Next() +{ + // Skip dormant entities + while ( m_CurBaseEntity != ClientEntityList().m_BaseEntities.InvalidIndex() ) + { + C_BaseEntity *pRet = ClientEntityList().m_BaseEntities[m_CurBaseEntity]; + m_CurBaseEntity = ClientEntityList().m_BaseEntities.Next( m_CurBaseEntity ); + + if (!pRet->IsDormant()) + return pRet; + } + + return NULL; +} \ No newline at end of file diff --git a/game/client/cliententitylist.h b/game/client/cliententitylist.h new file mode 100644 index 00000000..b8a403d1 --- /dev/null +++ b/game/client/cliententitylist.h @@ -0,0 +1,308 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#if !defined( CLIENTENTITYLIST_H ) +#define CLIENTENTITYLIST_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/dbg.h" +#include "icliententitylist.h" +#include "iclientunknown.h" +#include "UtlLinkedList.h" +#include "UtlVector.h" +#include "icliententityinternal.h" +#include "ispatialpartition.h" +#include "cdll_util.h" +#include "entitylist_base.h" +#include "utlmap.h" + +class C_Beam; +class C_BaseViewModel; +class C_BaseEntity; + + +#define INPVS_YES 0x0001 // The entity thinks it's in the PVS. +#define INPVS_THISFRAME 0x0002 // Accumulated as different views are rendered during the frame and used to notify the entity if + // it is not in the PVS anymore (at the end of the frame). +#define INPVS_NEEDSNOTIFY 0x0004 // The entity thinks it's in the PVS. + +class IClientEntityListener; + +abstract_class C_BaseEntityClassList +{ +public: + C_BaseEntityClassList(); + ~C_BaseEntityClassList(); + virtual void LevelShutdown() = 0; + + C_BaseEntityClassList *m_pNextClassList; +}; + +template< class T > +class C_EntityClassList : public C_BaseEntityClassList +{ +public: + virtual void LevelShutdown() { m_pClassList = NULL; } + + void Insert( T *pEntity ) + { + pEntity->m_pNext = m_pClassList; + m_pClassList = pEntity; + } + + void Remove( T *pEntity ) + { + T **pPrev = &m_pClassList; + T *pCur = *pPrev; + while ( pCur ) + { + if ( pCur == pEntity ) + { + *pPrev = pCur->m_pNext; + return; + } + pPrev = &pCur->m_pNext; + pCur = *pPrev; + } + } + + static T *m_pClassList; +}; + + +// Maximum size of entity list +#define INVALID_CLIENTENTITY_HANDLE CBaseHandle( INVALID_EHANDLE_INDEX ) + + +// +// This is the IClientEntityList implemenation. It serves two functions: +// +// 1. It converts server entity indices into IClientNetworkables for the engine. +// +// 2. It provides a place to store IClientUnknowns and gives out ClientEntityHandle_t's +// so they can be indexed and retreived. For example, this is how static props are referenced +// by the spatial partition manager - it doesn't know what is being inserted, so it's +// given ClientEntityHandle_t's, and the handlers for spatial partition callbacks can +// use the client entity list to look them up and check for supported interfaces. +// +class CClientEntityList : public CBaseEntityList, public IClientEntityList +{ +friend class C_BaseEntityIterator; +friend class C_AllBaseEntityIterator; + +public: + // Constructor, destructor + CClientEntityList( void ); + virtual ~CClientEntityList( void ); + + void Release(); // clears everything and releases entities + + +// Implement IClientEntityList +public: + + virtual IClientNetworkable* GetClientNetworkable( int entnum ); + virtual IClientEntity* GetClientEntity( int entnum ); + + virtual int NumberOfEntities( bool bIncludeNonNetworkable = false ); + + virtual IClientUnknown* GetClientUnknownFromHandle( ClientEntityHandle_t hEnt ); + virtual IClientNetworkable* GetClientNetworkableFromHandle( ClientEntityHandle_t hEnt ); + virtual IClientEntity* GetClientEntityFromHandle( ClientEntityHandle_t hEnt ); + + virtual int GetHighestEntityIndex( void ); + + virtual void SetMaxEntities( int maxents ); + virtual int GetMaxEntities( ); + + +// CBaseEntityList overrides. +protected: + + virtual void OnAddEntity( IHandleEntity *pEnt, CBaseHandle handle ); + virtual void OnRemoveEntity( IHandleEntity *pEnt, CBaseHandle handle ); + + +// Internal to client DLL. +public: + + // All methods of accessing specialized IClientUnknown's go through here. + IClientUnknown* GetListedEntity( int entnum ); + + // Simple wrappers for convenience.. + C_BaseEntity* GetBaseEntity( int entnum ); + ICollideable* GetCollideable( int entnum ); + + IClientRenderable* GetClientRenderableFromHandle( ClientEntityHandle_t hEnt ); + C_BaseEntity* GetBaseEntityFromHandle( ClientEntityHandle_t hEnt ); + ICollideable* GetCollideableFromHandle( ClientEntityHandle_t hEnt ); + IClientThinkable* GetClientThinkableFromHandle( ClientEntityHandle_t hEnt ); + + // Convenience methods to convert between entindex + ClientEntityHandle_t + ClientEntityHandle_t EntIndexToHandle( int entnum ); + int HandleToEntIndex( ClientEntityHandle_t handle ); + + // Is a handle valid? + bool IsHandleValid( ClientEntityHandle_t handle ) const; + + // For backwards compatibility... + C_BaseEntity* GetEnt( int entnum ) { return GetBaseEntity( entnum ); } + + void RecomputeHighestEntityUsed( void ); + + + // Use this to iterate over all the C_BaseEntities. + C_BaseEntity* FirstBaseEntity() const; + C_BaseEntity* NextBaseEntity( C_BaseEntity *pEnt ) const; + + class CPVSNotifyInfo + { + public: + IPVSNotify *m_pNotify; + IClientRenderable *m_pRenderable; + unsigned char m_InPVSStatus; // Combination of the INPVS_ flags. + unsigned short m_PVSNotifiersLink; // Into m_PVSNotifyInfos. + }; + + // Get the list of all PVS notifiers. + CUtlLinkedList& GetPVSNotifiers(); + + CUtlVector m_entityListeners; + + // add a class that gets notified of entity events + void AddListenerEntity( IClientEntityListener *pListener ); + void RemoveListenerEntity( IClientEntityListener *pListener ); + + void NotifyCreateEntity( C_BaseEntity *pEnt ); + void NotifyRemoveEntity( C_BaseEntity *pEnt ); + +private: + + // Cached info for networked entities. + struct EntityCacheInfo_t + { + // Cached off because GetClientNetworkable is called a *lot* + IClientNetworkable *m_pNetworkable; + unsigned short m_BaseEntitiesIndex; // Index into m_BaseEntities (or m_BaseEntities.InvalidIndex() if none). + }; + + // Current count + int m_iNumServerEnts; + // Max allowed + int m_iMaxServerEnts; + + int m_iNumClientNonNetworkable; + + // Current last used slot + int m_iMaxUsedServerIndex; + + // This holds fast lookups for special edicts. + EntityCacheInfo_t m_EntityCacheInfo[NUM_ENT_ENTRIES]; + + // For fast iteration. + CUtlLinkedList m_BaseEntities; + + +private: + + void AddPVSNotifier( IClientUnknown *pUnknown ); + void RemovePVSNotifier( IClientUnknown *pUnknown ); + + // These entities want to know when they enter and leave the PVS (server entities + // already can get the equivalent notification with NotifyShouldTransmit, but client + // entities have to get it this way). + CUtlLinkedList m_PVSNotifyInfos; + CUtlMap m_PVSNotifierMap; // Maps IClientUnknowns to indices into m_PVSNotifyInfos. +}; + + +// Use this to iterate over *all* (even dormant) the C_BaseEntities in the client entity list. +class C_AllBaseEntityIterator +{ +public: + C_AllBaseEntityIterator(); + + void Restart(); + C_BaseEntity* Next(); // keep calling this until it returns null. + +private: + unsigned short m_CurBaseEntity; +}; + +class C_BaseEntityIterator +{ +public: + C_BaseEntityIterator(); + + void Restart(); + C_BaseEntity* Next(); // keep calling this until it returns null. + +private: + unsigned short m_CurBaseEntity; +}; + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- +inline bool CClientEntityList::IsHandleValid( ClientEntityHandle_t handle ) const +{ + return handle.Get() != 0; +} + +inline IClientUnknown* CClientEntityList::GetListedEntity( int entnum ) +{ + return (IClientUnknown*)LookupEntityByNetworkIndex( entnum ); +} + +inline IClientUnknown* CClientEntityList::GetClientUnknownFromHandle( ClientEntityHandle_t hEnt ) +{ + return (IClientUnknown*)LookupEntity( hEnt ); +} + +inline CUtlLinkedList& CClientEntityList::GetPVSNotifiers() +{ + return m_PVSNotifyInfos; +} + + +//----------------------------------------------------------------------------- +// Convenience methods to convert between entindex + ClientEntityHandle_t +//----------------------------------------------------------------------------- +inline ClientEntityHandle_t CClientEntityList::EntIndexToHandle( int entnum ) +{ + if ( entnum < -1 ) + return INVALID_EHANDLE_INDEX; + IClientUnknown *pUnk = GetListedEntity( entnum ); + return pUnk ? pUnk->GetRefEHandle() : INVALID_EHANDLE_INDEX; +} + + +//----------------------------------------------------------------------------- +// Returns the client entity list +//----------------------------------------------------------------------------- +extern CClientEntityList *cl_entitylist; + +inline CClientEntityList& ClientEntityList() +{ + return *cl_entitylist; +} + +// Implement this class and register with entlist to receive entity create/delete notification +class IClientEntityListener +{ +public: + virtual void OnEntityCreated( C_BaseEntity *pEntity ) {}; + //virtual void OnEntitySpawned( C_BaseEntity *pEntity ) {}; + virtual void OnEntityDeleted( C_BaseEntity *pEntity ) {}; +}; + + +#endif // CLIENTENTITYLIST_H + diff --git a/game/client/clientleafsystem.cpp b/game/client/clientleafsystem.cpp new file mode 100644 index 00000000..1d81f74b --- /dev/null +++ b/game/client/clientleafsystem.cpp @@ -0,0 +1,1767 @@ +//===== Copyright © 1996-2007, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +//===========================================================================// + +#include "cbase.h" +#include "ClientLeafSystem.h" +#include "UtlBidirectionalSet.h" +#include "model_types.h" +#include "IVRenderView.h" +#include "tier0/vprof.h" +#include "BSPTreeData.h" +#include "DetailObjectSystem.h" +#include "engine/IStaticPropMgr.h" +#include "engine/IVDebugOverlay.h" +#include "vstdlib/jobthread.h" +#include "tier1/utllinkedlist.h" +#include "datacache/imdlcache.h" +#include "view.h" +#include "viewrender.h" +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class VMatrix; // forward decl + +static ConVar cl_drawleaf("cl_drawleaf", "-1", FCVAR_CHEAT ); +static ConVar r_PortalTestEnts( "r_PortalTestEnts", "1", FCVAR_CHEAT, "Clip entities against portal frustums." ); +static ConVar r_portalsopenall( "r_portalsopenall", "0", FCVAR_CHEAT, "Open all portals" ); +static ConVar cl_threaded_client_leaf_system("cl_threaded_client_leaf_system", "0" ); + + +DEFINE_FIXEDSIZE_ALLOCATOR( CClientRenderablesList, 1, CMemoryPool::GROW_SLOW ); + +//----------------------------------------------------------------------------- +// Threading helpers +//----------------------------------------------------------------------------- + +static void FrameLock() +{ + mdlcache->BeginLock(); +} + +static void FrameUnlock() +{ + mdlcache->EndLock(); +} + +static void CallComputeFXBlend( IClientRenderable *&pRenderable ) +{ + pRenderable->ComputeFxBlend(); +} + +//----------------------------------------------------------------------------- +// The client leaf system +//----------------------------------------------------------------------------- +class CClientLeafSystem : public IClientLeafSystem, public ISpatialLeafEnumerator +{ +public: + virtual char const *Name() { return "CClientLeafSystem"; } + + // constructor, destructor + CClientLeafSystem(); + virtual ~CClientLeafSystem(); + + // Methods of IClientSystem + bool Init() { return true; } + void PostInit() {} + void Shutdown() {} + + virtual bool IsPerFrame() { return true; } + + void PreRender(); + void PostRender() { } + void Update( float frametime ) { } + + void LevelInitPreEntity(); + void LevelInitPostEntity() {} + void LevelShutdownPreEntity(); + void LevelShutdownPostEntity(); + + virtual void OnSave() {} + virtual void OnRestore() {} + virtual void SafeRemoveIfDesired() {} + +// Methods of IClientLeafSystem +public: + + virtual void AddRenderable( IClientRenderable* pRenderable, RenderGroup_t group ); + virtual bool IsRenderableInPVS( IClientRenderable *pRenderable ); + virtual void CreateRenderableHandle( IClientRenderable* pRenderable, bool bIsStaticProp ); + virtual void RemoveRenderable( ClientRenderHandle_t handle ); + + + virtual void SetSubSystemDataInLeaf( int leaf, int nSubSystemIdx, CClientLeafSubSystemData *pData ); + virtual CClientLeafSubSystemData *GetSubSystemDataInLeaf( int leaf, int nSubSystemIdx ); + + // FIXME: There's an incestuous relationship between DetailObjectSystem + // and the ClientLeafSystem. Maybe they should be the same system? + virtual void GetDetailObjectsInLeaf( int leaf, int& firstDetailObject, int& detailObjectCount ); + virtual void SetDetailObjectsInLeaf( int leaf, int firstDetailObject, int detailObjectCount ); + virtual void DrawDetailObjectsInLeaf( int leaf, int frameNumber, int& nFirstDetailObject, int& nDetailObjectCount ); + virtual bool ShouldDrawDetailObjectsInLeaf( int leaf, int frameNumber ); + virtual void RenderableChanged( ClientRenderHandle_t handle ); + virtual void SetRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ); + virtual void ComputeTranslucentRenderLeaf( int count, const LeafIndex_t *pLeafList, const LeafFogVolume_t *pLeafFogVolumeList, int frameNumber, int viewID ); + virtual void CollateViewModelRenderables( CUtlVector< IClientRenderable * >& opaque, CUtlVector< IClientRenderable * >& translucent ); + virtual void BuildRenderablesList( const SetupRenderInfo_t &info ); + void CollateRenderablesInLeaf( int leaf, int worldListLeafIndex, const SetupRenderInfo_t &info ); + virtual void DrawStaticProps( bool enable ); + virtual void DrawSmallEntities( bool enable ); + virtual void EnableAlternateSorting( ClientRenderHandle_t handle, bool bEnable ); + + // Adds a renderable to a set of leaves + virtual void AddRenderableToLeaves( ClientRenderHandle_t handle, int nLeafCount, unsigned short *pLeaves ); + + // The following methods are related to shadows... + virtual ClientLeafShadowHandle_t AddShadow( ClientShadowHandle_t userId, unsigned short flags ); + virtual void RemoveShadow( ClientLeafShadowHandle_t h ); + + virtual void ProjectShadow( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ); + virtual void ProjectFlashlight( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ); + + // Find all shadow casters in a set of leaves + virtual void EnumerateShadowsInLeaves( int leafCount, LeafIndex_t* pLeaves, IClientLeafShadowEnum* pEnum ); + + // methods of ISpatialLeafEnumerator +public: + + bool EnumerateLeaf( int leaf, int context ); + + // Adds a shadow to a leaf + void AddShadowToLeaf( int leaf, ClientLeafShadowHandle_t handle ); + + // Fill in a list of the leaves this renderable is in. + // Returns -1 if the handle is invalid. + int GetRenderableLeaves( ClientRenderHandle_t handle, int leaves[128] ); + + // Get leaves this renderable is in + virtual bool GetRenderableLeaf ( ClientRenderHandle_t handle, int* pOutLeaf, const int* pInIterator = 0, int* pOutIterator = 0 ); + + // Singleton instance... + static CClientLeafSystem s_ClientLeafSystem; + +private: + // Creates a new renderable + void NewRenderable( IClientRenderable* pRenderable, RenderGroup_t type, int flags = 0 ); + + // Adds a renderable to the list of renderables + void AddRenderableToLeaf( int leaf, ClientRenderHandle_t handle ); + + void SortEntities( const Vector &vecRenderOrigin, const Vector &vecRenderForward, CClientRenderablesList::CEntry *pEntities, int nEntities ); + + // Returns -1 if the renderable spans more than one area. If it's totally in one area, then this returns the leaf. + short GetRenderableArea( ClientRenderHandle_t handle ); + + // remove renderables from leaves + void InsertIntoTree( ClientRenderHandle_t &handle ); + void RemoveFromTree( ClientRenderHandle_t handle ); + + // Returns if it's a view model render group + inline bool IsViewModelRenderGroup( RenderGroup_t group ) const; + + // Adds, removes renderables from view model list + void AddToViewModelList( ClientRenderHandle_t handle ); + void RemoveFromViewModelList( ClientRenderHandle_t handle ); + + // Insert translucent renderables into list of translucent objects + void InsertTranslucentRenderable( IClientRenderable* pRenderable, + int& count, IClientRenderable** pList, float* pDist ); + + // Used to change renderables from translucent to opaque + // Only really used by the static prop fading... + void ChangeRenderableRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ); + + // Adds a shadow to a leaf/removes shadow from renderable + void AddShadowToRenderable( ClientRenderHandle_t renderHandle, ClientLeafShadowHandle_t shadowHandle ); + void RemoveShadowFromRenderables( ClientLeafShadowHandle_t handle ); + + // Adds a shadow to a leaf/removes shadow from renderable + bool ShouldRenderableReceiveShadow( ClientRenderHandle_t renderHandle, int nShadowFlags ); + + // Adds a shadow to a leaf/removes shadow from leaf + void RemoveShadowFromLeaves( ClientLeafShadowHandle_t handle ); + + // Methods associated with the various bi-directional sets + static unsigned short& FirstRenderableInLeaf( int leaf ) + { + return s_ClientLeafSystem.m_Leaf[leaf].m_FirstElement; + } + + static unsigned short& FirstLeafInRenderable( unsigned short renderable ) + { + return s_ClientLeafSystem.m_Renderables[renderable].m_LeafList; + } + + static unsigned short& FirstShadowInLeaf( int leaf ) + { + return s_ClientLeafSystem.m_Leaf[leaf].m_FirstShadow; + } + + static unsigned short& FirstLeafInShadow( ClientLeafShadowHandle_t shadow ) + { + return s_ClientLeafSystem.m_Shadows[shadow].m_FirstLeaf; + } + + static unsigned short& FirstShadowOnRenderable( unsigned short renderable ) + { + return s_ClientLeafSystem.m_Renderables[renderable].m_FirstShadow; + } + + static unsigned short& FirstRenderableInShadow( ClientLeafShadowHandle_t shadow ) + { + return s_ClientLeafSystem.m_Shadows[shadow].m_FirstRenderable; + } + + void FrameLock() + { + mdlcache->BeginLock(); + } + + void FrameUnlock() + { + mdlcache->EndLock(); + } + +private: + enum + { + RENDER_FLAGS_TWOPASS = 0x01, + RENDER_FLAGS_STATIC_PROP = 0x02, + RENDER_FLAGS_BRUSH_MODEL = 0x04, + RENDER_FLAGS_STUDIO_MODEL = 0x08, + RENDER_FLAGS_HASCHANGED = 0x10, + RENDER_FLAGS_ALTERNATE_SORTING = 0x20, + }; + + // All the information associated with a particular handle + struct RenderableInfo_t + { + IClientRenderable* m_pRenderable; + int m_RenderFrame; // which frame did I render it in? + int m_RenderFrame2; + int m_EnumCount; // Have I been added to a particular shadow yet? + int m_TranslucencyCalculated; + unsigned short m_LeafList; // What leafs is it in? + unsigned short m_RenderLeaf; // What leaf do I render in? + unsigned char m_Flags; // rendering flags + unsigned char m_RenderGroup; // RenderGroup_t type + unsigned short m_FirstShadow; // The first shadow caster that cast on it + short m_Area; // -1 if the renderable spans multiple areas. + signed char m_TranslucencyCalculatedView; + }; + + // The leaf contains an index into a list of renderables + struct ClientLeaf_t + { + unsigned short m_FirstElement; + unsigned short m_FirstShadow; + + unsigned short m_FirstDetailProp; + unsigned short m_DetailPropCount; + int m_DetailPropRenderFrame; + CClientLeafSubSystemData *m_pSubSystemData[N_CLSUBSYSTEMS]; + + }; + + // Shadow information + struct ShadowInfo_t + { + unsigned short m_FirstLeaf; + unsigned short m_FirstRenderable; + int m_EnumCount; + ClientShadowHandle_t m_Shadow; + unsigned short m_Flags; + }; + + struct EnumResult_t + { + int leaf; + EnumResult_t *pNext; + }; + + struct EnumResultList_t + { + EnumResult_t *pHead; + ClientRenderHandle_t handle; + }; + + // Stores data associated with each leaf. + CUtlVector< ClientLeaf_t > m_Leaf; + + // Stores all unique non-detail renderables + CUtlLinkedList< RenderableInfo_t, ClientRenderHandle_t, false, unsigned int > m_Renderables; + + // Information associated with shadows registered with the client leaf system + CUtlLinkedList< ShadowInfo_t, ClientLeafShadowHandle_t, false, unsigned int > m_Shadows; + + // Maintains the list of all renderables in a particular leaf + CBidirectionalSet< int, ClientRenderHandle_t, unsigned short, unsigned int > m_RenderablesInLeaf; + + // Maintains a list of all shadows in a particular leaf + CBidirectionalSet< int, ClientLeafShadowHandle_t, unsigned short, unsigned int > m_ShadowsInLeaf; + + // Maintains a list of all shadows cast on a particular renderable + CBidirectionalSet< ClientRenderHandle_t, ClientLeafShadowHandle_t, unsigned short, unsigned int > m_ShadowsOnRenderable; + + // Dirty list of renderables + CUtlVector< ClientRenderHandle_t > m_DirtyRenderables; + + // List of renderables in view model render groups + CUtlVector< ClientRenderHandle_t > m_ViewModels; + + // Should I draw static props? + bool m_DrawStaticProps; + bool m_DrawSmallObjects; + + // A little enumerator to help us when adding shadows to renderables + int m_ShadowEnum; + + CTSList m_DeferredInserts; +}; + + +//----------------------------------------------------------------------------- +// Expose IClientLeafSystem to the client dll. +//----------------------------------------------------------------------------- +CClientLeafSystem CClientLeafSystem::s_ClientLeafSystem; +IClientLeafSystem *g_pClientLeafSystem = &CClientLeafSystem::s_ClientLeafSystem; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CClientLeafSystem, IClientLeafSystem, CLIENTLEAFSYSTEM_INTERFACE_VERSION, CClientLeafSystem::s_ClientLeafSystem ); + +void CalcRenderableWorldSpaceAABB_Fast( IClientRenderable *pRenderable, Vector &absMin, Vector &absMax ); + +//----------------------------------------------------------------------------- +// Helper functions. +//----------------------------------------------------------------------------- +void DefaultRenderBoundsWorldspace( IClientRenderable *pRenderable, Vector &absMins, Vector &absMaxs ) +{ + // Tracker 37433: This fixes a bug where if the stunstick is being wielded by a combine soldier, the fact that the stick was + // attached to the soldier's hand would move it such that it would get frustum culled near the edge of the screen. + C_BaseEntity *pEnt = pRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEnt && pEnt->IsFollowingEntity() ) + { + C_BaseEntity *pParent = pEnt->GetFollowedEntity(); + if ( pParent ) + { + // Get the parent's abs space world bounds. + CalcRenderableWorldSpaceAABB_Fast( pParent, absMins, absMaxs ); + + // Add the maximum of our local render bounds. This is making the assumption that we can be at any + // point and at any angle within the parent's world space bounds. + Vector vAddMins, vAddMaxs; + pEnt->GetRenderBounds( vAddMins, vAddMaxs ); + // if our origin is actually farther away than that, expand again + float radius = pEnt->GetLocalOrigin().Length(); + + float flBloatSize = max( vAddMins.Length(), vAddMaxs.Length() ); + flBloatSize = max(flBloatSize, radius); + absMins -= Vector( flBloatSize, flBloatSize, flBloatSize ); + absMaxs += Vector( flBloatSize, flBloatSize, flBloatSize ); + return; + } + } + + Vector mins, maxs; + pRenderable->GetRenderBounds( mins, maxs ); + + // FIXME: Should I just use a sphere here? + // Another option is to pass the OBB down the tree; makes for a better fit + // Generate a world-aligned AABB + const QAngle& angles = pRenderable->GetRenderAngles(); + const Vector& origin = pRenderable->GetRenderOrigin(); + if (angles == vec3_angle) + { + VectorAdd( mins, origin, absMins ); + VectorAdd( maxs, origin, absMaxs ); + } + else + { + matrix3x4_t boxToWorld; + AngleMatrix( angles, origin, boxToWorld ); + TransformAABB( boxToWorld, mins, maxs, absMins, absMaxs ); + } + Assert( absMins.IsValid() && absMaxs.IsValid() ); +} + +// Figure out a world space bounding box that encloses the entity's local render bounds in world space. +inline void CalcRenderableWorldSpaceAABB( + IClientRenderable *pRenderable, + Vector &absMins, + Vector &absMaxs ) +{ + pRenderable->GetRenderBoundsWorldspace( absMins, absMaxs ); +} + + +// This gets an AABB for the renderable, but it doesn't cause a parent's bones to be setup. +// This is used for placement in the leaves, but the more expensive version is used for culling. +void CalcRenderableWorldSpaceAABB_Fast( IClientRenderable *pRenderable, Vector &absMin, Vector &absMax ) +{ + C_BaseEntity *pEnt = pRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEnt && pEnt->IsFollowingEntity() ) + { + C_BaseEntity *pParent = pEnt->GetMoveParent(); + Assert( pParent ); + + // Get the parent's abs space world bounds. + CalcRenderableWorldSpaceAABB_Fast( pParent, absMin, absMax ); + + // Add the maximum of our local render bounds. This is making the assumption that we can be at any + // point and at any angle within the parent's world space bounds. + Vector vAddMins, vAddMaxs; + pEnt->GetRenderBounds( vAddMins, vAddMaxs ); + // if our origin is actually farther away than that, expand again + float radius = pEnt->GetLocalOrigin().Length(); + + float flBloatSize = max( vAddMins.Length(), vAddMaxs.Length() ); + flBloatSize = max(flBloatSize, radius); + absMin -= Vector( flBloatSize, flBloatSize, flBloatSize ); + absMax += Vector( flBloatSize, flBloatSize, flBloatSize ); + } + else + { + // Start out with our own render bounds. Since we don't have a parent, this won't incur any nasty + CalcRenderableWorldSpaceAABB( pRenderable, absMin, absMax ); + } +} + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +CClientLeafSystem::CClientLeafSystem() : m_DrawStaticProps(true), m_DrawSmallObjects(true) +{ + // Set up the bi-directional lists... + m_RenderablesInLeaf.Init( FirstRenderableInLeaf, FirstLeafInRenderable ); + m_ShadowsInLeaf.Init( FirstShadowInLeaf, FirstLeafInShadow ); + m_ShadowsOnRenderable.Init( FirstShadowOnRenderable, FirstRenderableInShadow ); +} + +CClientLeafSystem::~CClientLeafSystem() +{ +} + +//----------------------------------------------------------------------------- +// Activate, deactivate static props +//----------------------------------------------------------------------------- +void CClientLeafSystem::DrawStaticProps( bool enable ) +{ + m_DrawStaticProps = enable; +} + +void CClientLeafSystem::DrawSmallEntities( bool enable ) +{ + m_DrawSmallObjects = enable; +} + + +//----------------------------------------------------------------------------- +// Level init, shutdown +//----------------------------------------------------------------------------- +void CClientLeafSystem::LevelInitPreEntity() +{ + MEM_ALLOC_CREDIT(); + + m_Renderables.EnsureCapacity( 1024 ); + m_RenderablesInLeaf.EnsureCapacity( 1024 ); + m_ShadowsInLeaf.EnsureCapacity( 256 ); + m_ShadowsOnRenderable.EnsureCapacity( 256 ); + m_DirtyRenderables.EnsureCapacity( 256 ); + + // Add all the leaves we'll need + int leafCount = engine->LevelLeafCount(); + m_Leaf.EnsureCapacity( leafCount ); + + ClientLeaf_t newLeaf; + newLeaf.m_FirstElement = m_RenderablesInLeaf.InvalidIndex(); + newLeaf.m_FirstShadow = m_ShadowsInLeaf.InvalidIndex(); + memset( newLeaf.m_pSubSystemData, 0, sizeof( newLeaf.m_pSubSystemData ) ); + newLeaf.m_FirstDetailProp = 0; + newLeaf.m_DetailPropCount = 0; + newLeaf.m_DetailPropRenderFrame = -1; + while ( --leafCount >= 0 ) + { + m_Leaf.AddToTail( newLeaf ); + } +} + +void CClientLeafSystem::LevelShutdownPreEntity() +{ +} + +void CClientLeafSystem::LevelShutdownPostEntity() +{ + m_ViewModels.Purge(); + m_Renderables.Purge(); + m_RenderablesInLeaf.Purge(); + m_Shadows.Purge(); + + // delete subsystem data + for( int i = 0; i < m_Leaf.Count() ; i++ ) + { + for( int j = 0 ; j < ARRAYSIZE( m_Leaf[i].m_pSubSystemData ) ; j++ ) + { + if ( m_Leaf[i].m_pSubSystemData[j] ) + { + delete m_Leaf[i].m_pSubSystemData[j]; + m_Leaf[i].m_pSubSystemData[j] = NULL; + } + } + } + m_Leaf.Purge(); + m_ShadowsInLeaf.Purge(); + m_ShadowsOnRenderable.Purge(); + m_DirtyRenderables.Purge(); +} + + +//----------------------------------------------------------------------------- +// This is what happens before rendering a particular view +//----------------------------------------------------------------------------- +void CClientLeafSystem::PreRender() +{ + VPROF_BUDGET( "CClientLeafSystem::PreRender", "PreRender" ); + + int i; + int nIterations = 0; + + while ( m_DirtyRenderables.Count() ) + { + if ( ++nIterations > 10 ) + { + Warning( "Too many dirty renderables!\n" ); + break; + } + + int nDirty = m_DirtyRenderables.Count(); + for ( i = nDirty; --i >= 0; ) + { + ClientRenderHandle_t handle = m_DirtyRenderables[i]; + Assert( m_Renderables[ handle ].m_Flags & RENDER_FLAGS_HASCHANGED ); + + // Update position in leaf system + RemoveFromTree( handle ); + } + + bool bThreaded = ( nDirty > 5 && cl_threaded_client_leaf_system.GetBool() && g_pThreadPool->NumThreads() ); + + if ( !bThreaded ) + { + for ( i = nDirty; --i >= 0; ) + { + InsertIntoTree( m_DirtyRenderables[i] ); + } + } + else + { + // InsertIntoTree can result in new renderables being added, so copy: + ClientRenderHandle_t *pDirtyRenderables = (ClientRenderHandle_t *)alloca( sizeof(ClientRenderHandle_t) * nDirty ); + memcpy( pDirtyRenderables, m_DirtyRenderables.Base(), sizeof(ClientRenderHandle_t) * nDirty ); + ParallelProcess( pDirtyRenderables, nDirty, this, &CClientLeafSystem::InsertIntoTree, &CClientLeafSystem::FrameLock, &CClientLeafSystem::FrameUnlock ); + } + + if ( m_DeferredInserts.Count() ) + { + EnumResultList_t enumResultList; + while ( m_DeferredInserts.PopItem( &enumResultList ) ) + { + m_ShadowEnum++; + while ( enumResultList.pHead ) + { + EnumResult_t *p = enumResultList.pHead; + enumResultList.pHead = p->pNext; + AddRenderableToLeaf( p->leaf, enumResultList.handle ); + delete p; + } + } + } + + for ( i = nDirty; --i >= 0; ) + { + // Cache off the area it's sitting in. + ClientRenderHandle_t handle = m_DirtyRenderables[i]; + RenderableInfo_t& renderable = m_Renderables[ handle ]; + + renderable.m_Flags &= ~RENDER_FLAGS_HASCHANGED; + m_Renderables[handle].m_Area = GetRenderableArea( handle ); + } + + m_DirtyRenderables.RemoveMultiple( 0, nDirty ); + } +} + + +//----------------------------------------------------------------------------- +// Creates a new renderable +//----------------------------------------------------------------------------- +void CClientLeafSystem::NewRenderable( IClientRenderable* pRenderable, RenderGroup_t type, int flags ) +{ + Assert( pRenderable ); + Assert( pRenderable->RenderHandle() == INVALID_CLIENT_RENDER_HANDLE ); + + ClientRenderHandle_t handle = m_Renderables.AddToTail(); + RenderableInfo_t &info = m_Renderables[handle]; + + // We need to know if it's a brush model for shadows + int modelType = modelinfo->GetModelType( pRenderable->GetModel() ); + if (modelType == mod_brush) + { + flags |= RENDER_FLAGS_BRUSH_MODEL; + } + else if ( modelType == mod_studio ) + { + flags |= RENDER_FLAGS_STUDIO_MODEL; + } + + info.m_pRenderable = pRenderable; + info.m_RenderFrame = -1; + info.m_RenderFrame2 = -1; + info.m_TranslucencyCalculated = -1; + info.m_TranslucencyCalculatedView = VIEW_ILLEGAL; + info.m_FirstShadow = m_ShadowsOnRenderable.InvalidIndex(); + info.m_LeafList = m_RenderablesInLeaf.InvalidIndex(); + info.m_Flags = flags; + info.m_RenderGroup = (unsigned char)type; + info.m_EnumCount = 0; + info.m_RenderLeaf = 0xFFFF; + if ( IsViewModelRenderGroup( (RenderGroup_t)info.m_RenderGroup ) ) + { + AddToViewModelList( handle ); + } + + pRenderable->RenderHandle() = handle; +} + +void CClientLeafSystem::CreateRenderableHandle( IClientRenderable* pRenderable, bool bIsStaticProp ) +{ + // FIXME: The argument is unnecessary if we could get this next line to work + // the reason why we can't is because currently there are IClientRenderables + // which don't correctly implement GetRefEHandle. + + //bool bIsStaticProp = staticpropmgr->IsStaticProp( pRenderable->GetIClientUnknown() ); + + // Add the prop to all the leaves it lies in + RenderGroup_t group = pRenderable->IsTransparent() ? RENDER_GROUP_TRANSLUCENT_ENTITY : RENDER_GROUP_OPAQUE_ENTITY; + + bool bTwoPass = false; + if ( group == RENDER_GROUP_TRANSLUCENT_ENTITY ) + { + bTwoPass = pRenderable->IsTwoPass( ); + } + + int flags = 0; + if ( bIsStaticProp ) + { + flags = RENDER_FLAGS_STATIC_PROP; + if ( group == RENDER_GROUP_OPAQUE_ENTITY ) + { + group = RENDER_GROUP_OPAQUE_STATIC; + } + } + + if (bTwoPass) + { + flags |= RENDER_FLAGS_TWOPASS; + } + + NewRenderable( pRenderable, group, flags ); +} + + +//----------------------------------------------------------------------------- +// Used to change renderables from translucent to opaque +//----------------------------------------------------------------------------- +void CClientLeafSystem::ChangeRenderableRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ) +{ + RenderableInfo_t &info = m_Renderables[handle]; + info.m_RenderGroup = (unsigned char)group; +} + + +//----------------------------------------------------------------------------- +// Use alternate translucent sorting algorithm (draw translucent objects in the furthest leaf they lie in) +//----------------------------------------------------------------------------- +void CClientLeafSystem::EnableAlternateSorting( ClientRenderHandle_t handle, bool bEnable ) +{ + RenderableInfo_t &info = m_Renderables[handle]; + if ( bEnable ) + { + info.m_Flags |= RENDER_FLAGS_ALTERNATE_SORTING; + } + else + { + info.m_Flags &= ~RENDER_FLAGS_ALTERNATE_SORTING; + } +} + + +//----------------------------------------------------------------------------- +// Add/remove renderable +//----------------------------------------------------------------------------- +void CClientLeafSystem::AddRenderable( IClientRenderable* pRenderable, RenderGroup_t group ) +{ + // force a relink we we try to draw it for the first time + int flags = RENDER_FLAGS_HASCHANGED; + + if ( group == RENDER_GROUP_TWOPASS ) + { + group = RENDER_GROUP_TRANSLUCENT_ENTITY; + flags |= RENDER_FLAGS_TWOPASS; + } + + NewRenderable( pRenderable, group, flags ); + ClientRenderHandle_t handle = pRenderable->RenderHandle(); + m_DirtyRenderables.AddToTail( handle ); +} + +void CClientLeafSystem::RemoveRenderable( ClientRenderHandle_t handle ) +{ + // This can happen upon level shutdown + if (!m_Renderables.IsValidIndex(handle)) + return; + + // Reset the render handle in the entity. + IClientRenderable *pRenderable = m_Renderables[handle].m_pRenderable; + Assert( handle == pRenderable->RenderHandle() ); + pRenderable->RenderHandle() = INVALID_CLIENT_RENDER_HANDLE; + + // Reemove the renderable from the dirty list + if ( m_Renderables[handle].m_Flags & RENDER_FLAGS_HASCHANGED ) + { + // NOTE: This isn't particularly fast (linear search), + // but I'm assuming it's an unusual case where we remove + // renderables that are changing or that m_DirtyRenderables usually + // only has a couple entries + int i = m_DirtyRenderables.Find( handle ); + Assert( i != m_DirtyRenderables.InvalidIndex() ); + m_DirtyRenderables.FastRemove( i ); + } + + if ( IsViewModelRenderGroup( (RenderGroup_t)m_Renderables[handle].m_RenderGroup ) ) + { + RemoveFromViewModelList( handle ); + } + + RemoveFromTree( handle ); + m_Renderables.Remove( handle ); +} + + +int CClientLeafSystem::GetRenderableLeaves( ClientRenderHandle_t handle, int leaves[128] ) +{ + if ( !m_Renderables.IsValidIndex( handle ) ) + return -1; + + RenderableInfo_t *pRenderable = &m_Renderables[handle]; + if ( pRenderable->m_LeafList == m_RenderablesInLeaf.InvalidIndex() ) + return -1; + + int nLeaves = 0; + for ( int i=m_RenderablesInLeaf.FirstBucket( handle ); i != m_RenderablesInLeaf.InvalidIndex(); i = m_RenderablesInLeaf.NextBucket( i ) ) + { + leaves[nLeaves++] = m_RenderablesInLeaf.Bucket( i ); + if ( nLeaves >= 128 ) + break; + } + return nLeaves; +} + + +//----------------------------------------------------------------------------- +// Retrieve leaf handles to leaves a renderable is in +// the pOutLeaf parameter is filled with the leaf the renderable is in. +// If pInIterator is not specified, pOutLeaf is the first leaf in the list. +// if pInIterator is specified, that iterator is used to return the next leaf +// in the list in pOutLeaf. +// the pOutIterator parameter is filled with the iterater which index to the pOutLeaf returned. +// +// Returns false on failure cases where pOutLeaf will be invalid. CHECK THE RETURN! +//----------------------------------------------------------------------------- +bool CClientLeafSystem::GetRenderableLeaf(ClientRenderHandle_t handle, int* pOutLeaf, const int* pInIterator /* = 0 */, int* pOutIterator /* = 0 */) +{ + // bail on invalid handle + if ( !m_Renderables.IsValidIndex( handle ) ) + return false; + + // bail on no output value pointer + if ( !pOutLeaf ) + return false; + + // an iterator was specified + if ( pInIterator ) + { + int iter = *pInIterator; + + // test for invalid iterator + if ( iter == m_RenderablesInLeaf.InvalidIndex() ) + return false; + + int iterNext = m_RenderablesInLeaf.NextBucket( iter ); + + // test for end of list + if ( iterNext == m_RenderablesInLeaf.InvalidIndex() ) + return false; + + // Give the caller the iterator used + if ( pOutIterator ) + { + *pOutIterator = iterNext; + } + + // set output value to the next leaf + *pOutLeaf = m_RenderablesInLeaf.Bucket( iterNext ); + + } + else // no iter param, give them the first bucket in the renderable's list + { + int iter = m_RenderablesInLeaf.FirstBucket( handle ); + + if ( iter == m_RenderablesInLeaf.InvalidIndex() ) + return false; + + // Set output value to this leaf + *pOutLeaf = m_RenderablesInLeaf.Bucket( iter ); + + // give this iterator to caller + if ( pOutIterator ) + { + *pOutIterator = iter; + } + + } + + return true; +} + +bool CClientLeafSystem::IsRenderableInPVS( IClientRenderable *pRenderable ) +{ + ClientRenderHandle_t handle = pRenderable->RenderHandle(); + int leaves[128]; + int nLeaves = GetRenderableLeaves( handle, leaves ); + if ( nLeaves == -1 ) + return false; + + // Ask the engine if this guy is visible. + return render->AreAnyLeavesVisible( leaves, nLeaves ); +} + +short CClientLeafSystem::GetRenderableArea( ClientRenderHandle_t handle ) +{ + int leaves[128]; + int nLeaves = GetRenderableLeaves( handle, leaves ); + if ( nLeaves == -1 ) + return 0; + + // Now ask the + return engine->GetLeavesArea( leaves, nLeaves ); +} + + +void CClientLeafSystem::SetSubSystemDataInLeaf( int leaf, int nSubSystemIdx, CClientLeafSubSystemData *pData ) +{ + assert( nSubSystemIdx < N_CLSUBSYSTEMS ); + if ( m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx] ) + delete m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx]; + m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx] = pData; +} + +CClientLeafSubSystemData *CClientLeafSystem::GetSubSystemDataInLeaf( int leaf, int nSubSystemIdx ) +{ + assert( nSubSystemIdx < N_CLSUBSYSTEMS ); + return m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx]; +} + +//----------------------------------------------------------------------------- +// Indicates which leaves detail objects are in +//----------------------------------------------------------------------------- +void CClientLeafSystem::SetDetailObjectsInLeaf( int leaf, int firstDetailObject, + int detailObjectCount ) +{ + m_Leaf[leaf].m_FirstDetailProp = firstDetailObject; + m_Leaf[leaf].m_DetailPropCount = detailObjectCount; +} + +//----------------------------------------------------------------------------- +// Returns the detail objects in a leaf +//----------------------------------------------------------------------------- +void CClientLeafSystem::GetDetailObjectsInLeaf( int leaf, int& firstDetailObject, + int& detailObjectCount ) +{ + firstDetailObject = m_Leaf[leaf].m_FirstDetailProp; + detailObjectCount = m_Leaf[leaf].m_DetailPropCount; +} + + +//----------------------------------------------------------------------------- +// Create/destroy shadows... +//----------------------------------------------------------------------------- +ClientLeafShadowHandle_t CClientLeafSystem::AddShadow( ClientShadowHandle_t userId, unsigned short flags ) +{ + ClientLeafShadowHandle_t idx = m_Shadows.AddToTail(); + m_Shadows[idx].m_Shadow = userId; + m_Shadows[idx].m_FirstLeaf = m_ShadowsInLeaf.InvalidIndex(); + m_Shadows[idx].m_FirstRenderable = m_ShadowsOnRenderable.InvalidIndex(); + m_Shadows[idx].m_EnumCount = 0; + m_Shadows[idx].m_Flags = flags; + return idx; +} + +void CClientLeafSystem::RemoveShadow( ClientLeafShadowHandle_t handle ) +{ + // Remove the shadow from all leaves + renderables... + RemoveShadowFromLeaves( handle ); + RemoveShadowFromRenderables( handle ); + + // Blow away the handle + m_Shadows.Remove( handle ); +} + + +//----------------------------------------------------------------------------- +// Adds a shadow to a leaf/removes shadow from renderable +//----------------------------------------------------------------------------- +inline bool CClientLeafSystem::ShouldRenderableReceiveShadow( ClientRenderHandle_t renderHandle, int nShadowFlags ) +{ + RenderableInfo_t &renderable = m_Renderables[renderHandle]; + if( !( renderable.m_Flags & ( RENDER_FLAGS_BRUSH_MODEL | RENDER_FLAGS_STATIC_PROP | RENDER_FLAGS_STUDIO_MODEL ) ) ) + return false; + + return renderable.m_pRenderable->ShouldReceiveProjectedTextures( nShadowFlags ); +} + + +//----------------------------------------------------------------------------- +// Adds a shadow to a leaf/removes shadow from renderable +//----------------------------------------------------------------------------- +void CClientLeafSystem::AddShadowToRenderable( ClientRenderHandle_t renderHandle, + ClientLeafShadowHandle_t shadowHandle ) +{ + // Check if this renderable receives the type of projected texture that shadowHandle refers to. + int nShadowFlags = m_Shadows[shadowHandle].m_Flags; + if ( !ShouldRenderableReceiveShadow( renderHandle, nShadowFlags ) ) + return; + + m_ShadowsOnRenderable.AddElementToBucket( renderHandle, shadowHandle ); + + // Also, do some stuff specific to the particular types of renderables + + // If the renderable is a brush model, then add this shadow to it + if (m_Renderables[renderHandle].m_Flags & RENDER_FLAGS_BRUSH_MODEL) + { + IClientRenderable* pRenderable = m_Renderables[renderHandle].m_pRenderable; + g_pClientShadowMgr->AddShadowToReceiver( m_Shadows[shadowHandle].m_Shadow, + pRenderable, SHADOW_RECEIVER_BRUSH_MODEL ); + } + else if( m_Renderables[renderHandle].m_Flags & RENDER_FLAGS_STATIC_PROP ) + { + IClientRenderable* pRenderable = m_Renderables[renderHandle].m_pRenderable; + g_pClientShadowMgr->AddShadowToReceiver( m_Shadows[shadowHandle].m_Shadow, + pRenderable, SHADOW_RECEIVER_STATIC_PROP ); + } + else if( m_Renderables[renderHandle].m_Flags & RENDER_FLAGS_STUDIO_MODEL ) + { + IClientRenderable* pRenderable = m_Renderables[renderHandle].m_pRenderable; + g_pClientShadowMgr->AddShadowToReceiver( m_Shadows[shadowHandle].m_Shadow, + pRenderable, SHADOW_RECEIVER_STUDIO_MODEL ); + } +} + +void CClientLeafSystem::RemoveShadowFromRenderables( ClientLeafShadowHandle_t handle ) +{ + m_ShadowsOnRenderable.RemoveElement( handle ); +} + + +//----------------------------------------------------------------------------- +// Adds a shadow to a leaf/removes shadow from leaf +//----------------------------------------------------------------------------- +void CClientLeafSystem::AddShadowToLeaf( int leaf, ClientLeafShadowHandle_t shadow ) +{ + m_ShadowsInLeaf.AddElementToBucket( leaf, shadow ); + + // Add the shadow exactly once to all renderables in the leaf + unsigned short i = m_RenderablesInLeaf.FirstElement( leaf ); + while ( i != m_RenderablesInLeaf.InvalidIndex() ) + { + ClientRenderHandle_t renderable = m_RenderablesInLeaf.Element(i); + RenderableInfo_t& info = m_Renderables[renderable]; + + // Add each shadow exactly once to each renderable + if (info.m_EnumCount != m_ShadowEnum) + { + AddShadowToRenderable( renderable, shadow ); + info.m_EnumCount = m_ShadowEnum; + } + + i = m_RenderablesInLeaf.NextElement(i); + } +} + +void CClientLeafSystem::RemoveShadowFromLeaves( ClientLeafShadowHandle_t handle ) +{ + m_ShadowsInLeaf.RemoveElement( handle ); +} + + +//----------------------------------------------------------------------------- +// Adds a shadow to all leaves listed +//----------------------------------------------------------------------------- +void CClientLeafSystem::ProjectShadow( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ) +{ + // Remove the shadow from any leaves it current exists in + RemoveShadowFromLeaves( handle ); + RemoveShadowFromRenderables( handle ); + + Assert( ( m_Shadows[handle].m_Flags & SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) == SHADOW_FLAGS_SHADOW ); + + // This will help us to avoid adding the shadow multiple times to a renderable + ++m_ShadowEnum; + + for ( int i = 0; i < nLeafCount; ++i ) + { + AddShadowToLeaf( pLeafList[i], handle ); + } +} + +void CClientLeafSystem::ProjectFlashlight( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ) +{ + VPROF_BUDGET( "CClientLeafSystem::ProjectFlashlight", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + // Remove the shadow from any leaves it current exists in + RemoveShadowFromLeaves( handle ); + RemoveShadowFromRenderables( handle ); + + Assert( ( m_Shadows[handle].m_Flags & SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) == SHADOW_FLAGS_FLASHLIGHT ); + + // This will help us to avoid adding the shadow multiple times to a renderable + ++m_ShadowEnum; + + for ( int i = 0; i < nLeafCount; ++i ) + { + AddShadowToLeaf( pLeafList[i], handle ); + } +} + + +//----------------------------------------------------------------------------- +// Find all shadow casters in a set of leaves +//----------------------------------------------------------------------------- +void CClientLeafSystem::EnumerateShadowsInLeaves( int leafCount, LeafIndex_t* pLeaves, IClientLeafShadowEnum* pEnum ) +{ + if (leafCount == 0) + return; + + // This will help us to avoid enumerating the shadow multiple times + ++m_ShadowEnum; + + for (int i = 0; i < leafCount; ++i) + { + int leaf = pLeaves[i]; + + unsigned short j = m_ShadowsInLeaf.FirstElement( leaf ); + while ( j != m_ShadowsInLeaf.InvalidIndex() ) + { + ClientLeafShadowHandle_t shadow = m_ShadowsInLeaf.Element(j); + ShadowInfo_t& info = m_Shadows[shadow]; + + if (info.m_EnumCount != m_ShadowEnum) + { + pEnum->EnumShadow(info.m_Shadow); + info.m_EnumCount = m_ShadowEnum; + } + + j = m_ShadowsInLeaf.NextElement(j); + } + } +} + + +//----------------------------------------------------------------------------- +// Adds a renderable to a leaf +//----------------------------------------------------------------------------- +void CClientLeafSystem::AddRenderableToLeaf( int leaf, ClientRenderHandle_t renderable ) +{ +#ifdef VALIDATE_CLIENT_LEAF_SYSTEM + m_RenderablesInLeaf.ValidateAddElementToBucket( leaf, renderable ); +#endif + m_RenderablesInLeaf.AddElementToBucket( leaf, renderable ); + + if ( !ShouldRenderableReceiveShadow( renderable, SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) ) + return; + + // Add all shadows in the leaf to the renderable... + unsigned short i = m_ShadowsInLeaf.FirstElement( leaf ); + while (i != m_ShadowsInLeaf.InvalidIndex() ) + { + ClientLeafShadowHandle_t shadow = m_ShadowsInLeaf.Element(i); + ShadowInfo_t& info = m_Shadows[shadow]; + + // Add each shadow exactly once to each renderable + if (info.m_EnumCount != m_ShadowEnum) + { + AddShadowToRenderable( renderable, shadow ); + info.m_EnumCount = m_ShadowEnum; + } + + i = m_ShadowsInLeaf.NextElement(i); + } +} + + +//----------------------------------------------------------------------------- +// Adds a renderable to a set of leaves +//----------------------------------------------------------------------------- +void CClientLeafSystem::AddRenderableToLeaves( ClientRenderHandle_t handle, int nLeafCount, unsigned short *pLeaves ) +{ + for (int j = 0; j < nLeafCount; ++j) + { + AddRenderableToLeaf( pLeaves[j], handle ); + } + m_Renderables[handle].m_Area = GetRenderableArea( handle ); +} + + +//----------------------------------------------------------------------------- +// Inserts an element into the tree +//----------------------------------------------------------------------------- +bool CClientLeafSystem::EnumerateLeaf( int leaf, int context ) +{ + EnumResultList_t *pList = (EnumResultList_t *)context; + if ( ThreadInMainThread() ) + { + AddRenderableToLeaf( leaf, pList->handle ); + } + else + { + EnumResult_t *p = new EnumResult_t; + p->leaf = leaf; + p->pNext = pList->pHead; + pList->pHead = p; + } + return true; +} + +void CClientLeafSystem::InsertIntoTree( ClientRenderHandle_t &handle ) +{ + if ( ThreadInMainThread() ) + { + // When we insert into the tree, increase the shadow enumerator + // to make sure each shadow is added exactly once to each renderable + m_ShadowEnum++; + } + + EnumResultList_t list = { NULL, handle }; + + // NOTE: The render bounds here are relative to the renderable's coordinate system + IClientRenderable* pRenderable = m_Renderables[handle].m_pRenderable; + Vector absMins, absMaxs; + + CalcRenderableWorldSpaceAABB_Fast( pRenderable, absMins, absMaxs ); + Assert( absMins.IsValid() && absMaxs.IsValid() ); + + ISpatialQuery* pQuery = engine->GetBSPTreeQuery(); + pQuery->EnumerateLeavesInBox( absMins, absMaxs, this, (int)&list ); + + if ( list.pHead ) + { + m_DeferredInserts.PushItem( list ); + } +} + +//----------------------------------------------------------------------------- +// Removes an element from the tree +//----------------------------------------------------------------------------- +void CClientLeafSystem::RemoveFromTree( ClientRenderHandle_t handle ) +{ + m_RenderablesInLeaf.RemoveElement( handle ); + + // Remove all shadows cast onto the object + m_ShadowsOnRenderable.RemoveBucket( handle ); + + // If the renderable is a brush model, then remove all shadows from it + if (m_Renderables[handle].m_Flags & RENDER_FLAGS_BRUSH_MODEL) + { + g_pClientShadowMgr->RemoveAllShadowsFromReceiver( + m_Renderables[handle].m_pRenderable, SHADOW_RECEIVER_BRUSH_MODEL ); + } + else if( m_Renderables[handle].m_Flags & RENDER_FLAGS_STUDIO_MODEL ) + { + g_pClientShadowMgr->RemoveAllShadowsFromReceiver( + m_Renderables[handle].m_pRenderable, SHADOW_RECEIVER_STUDIO_MODEL ); + } +} + + +//----------------------------------------------------------------------------- +// Call this when the renderable moves +//----------------------------------------------------------------------------- +void CClientLeafSystem::RenderableChanged( ClientRenderHandle_t handle ) +{ + Assert ( handle != INVALID_CLIENT_RENDER_HANDLE ); + Assert( m_Renderables.IsValidIndex( handle ) ); + if ( !m_Renderables.IsValidIndex( handle ) ) + return; + + if ( (m_Renderables[handle].m_Flags & RENDER_FLAGS_HASCHANGED ) == 0 ) + { + m_Renderables[handle].m_Flags |= RENDER_FLAGS_HASCHANGED; + m_DirtyRenderables.AddToTail( handle ); + } +#if _DEBUG + else + { + // It had better be in the list + Assert( m_DirtyRenderables.Find( handle ) != m_DirtyRenderables.InvalidIndex() ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Returns if it's a view model render group +//----------------------------------------------------------------------------- +inline bool CClientLeafSystem::IsViewModelRenderGroup( RenderGroup_t group ) const +{ + return (group == RENDER_GROUP_VIEW_MODEL_TRANSLUCENT) || (group == RENDER_GROUP_VIEW_MODEL_OPAQUE); +} + + +//----------------------------------------------------------------------------- +// Adds, removes renderables from view model list +//----------------------------------------------------------------------------- +void CClientLeafSystem::AddToViewModelList( ClientRenderHandle_t handle ) +{ + MEM_ALLOC_CREDIT(); + Assert( m_ViewModels.Find( handle ) == m_ViewModels.InvalidIndex() ); + m_ViewModels.AddToTail( handle ); +} + +void CClientLeafSystem::RemoveFromViewModelList( ClientRenderHandle_t handle ) +{ + int i = m_ViewModels.Find( handle ); + Assert( i != m_ViewModels.InvalidIndex() ); + m_ViewModels.FastRemove( i ); +} + + +//----------------------------------------------------------------------------- +// Call this to change the render group +//----------------------------------------------------------------------------- +void CClientLeafSystem::SetRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ) +{ + RenderableInfo_t *pInfo = &m_Renderables[handle]; + + bool twoPass = false; + if ( group == RENDER_GROUP_TWOPASS ) + { + twoPass = true; + group = RENDER_GROUP_TRANSLUCENT_ENTITY; + } + + if ( twoPass ) + { + pInfo->m_Flags |= RENDER_FLAGS_TWOPASS; + } + else + { + pInfo->m_Flags &= ~RENDER_FLAGS_TWOPASS; + } + + bool bOldViewModelRenderGroup = IsViewModelRenderGroup( (RenderGroup_t)pInfo->m_RenderGroup ); + bool bNewViewModelRenderGroup = IsViewModelRenderGroup( group ); + if ( bOldViewModelRenderGroup != bNewViewModelRenderGroup ) + { + if ( bOldViewModelRenderGroup ) + { + RemoveFromViewModelList( handle ); + } + else + { + AddToViewModelList( handle ); + } + } + + pInfo->m_RenderGroup = group; + +} + + +//----------------------------------------------------------------------------- +// Detail system marks +//----------------------------------------------------------------------------- +void CClientLeafSystem::DrawDetailObjectsInLeaf( int leaf, int nFrameNumber, int& nFirstDetailObject, int& nDetailObjectCount ) +{ + ClientLeaf_t &leafInfo = m_Leaf[leaf]; + leafInfo.m_DetailPropRenderFrame = nFrameNumber; + nFirstDetailObject = leafInfo.m_FirstDetailProp; + nDetailObjectCount = leafInfo.m_DetailPropCount; +} + + +//----------------------------------------------------------------------------- +// Are we close enough to this leaf to draw detail props *and* are there any props in the leaf? +//----------------------------------------------------------------------------- +bool CClientLeafSystem::ShouldDrawDetailObjectsInLeaf( int leaf, int frameNumber ) +{ + ClientLeaf_t &leafInfo = m_Leaf[leaf]; + return ( (leafInfo.m_DetailPropRenderFrame == frameNumber ) && + ( ( leafInfo.m_DetailPropCount != 0 ) || ( leafInfo.m_pSubSystemData[CLSUBSYSTEM_DETAILOBJECTS] ) ) ); +} + + +//----------------------------------------------------------------------------- +// Compute which leaf the translucent renderables should render in +//----------------------------------------------------------------------------- +void CClientLeafSystem::ComputeTranslucentRenderLeaf( int count, const LeafIndex_t *pLeafList, const LeafFogVolume_t *pLeafFogVolumeList, int frameNumber, int viewID ) +{ + ASSERT_NO_REENTRY(); + VPROF_BUDGET( "CClientLeafSystem::ComputeTranslucentRenderLeaf", "ComputeTranslucentRenderLeaf" ); + + #define LeafToMarker( leaf ) reinterpret_cast(( (leaf) << 1 ) | 1) + #define IsLeafMarker( p ) (bool)((reinterpret_cast(p)) & 1) + #define MarkerToLeaf( p ) (int)((reinterpret_cast(p)) >> 1) + + // For better sorting, we're gonna choose the leaf that is closest to the camera. + // The leaf list passed in here is sorted front to back + bool bThreaded = ( cl_threaded_client_leaf_system.GetBool() && g_pThreadPool->NumThreads() ); + int globalFrameCount = gpGlobals->framecount; + int i; + + static CUtlVector orderedList; // @MULTICORE (toml 8/30/2006): will need to make non-static if thread this function + static CUtlVector renderablesToUpdate; + int leaf = 0; + for ( i = 0; i < count; ++i ) + { + leaf = pLeafList[i]; + orderedList.AddToTail( LeafToMarker( leaf ) ); + + // iterate over all elements in this leaf + unsigned short idx = m_RenderablesInLeaf.FirstElement(leaf); + while (idx != m_RenderablesInLeaf.InvalidIndex()) + { + RenderableInfo_t& info = m_Renderables[m_RenderablesInLeaf.Element(idx)]; + if ( info.m_TranslucencyCalculated != globalFrameCount || info.m_TranslucencyCalculatedView != viewID ) + { + // Compute translucency + if ( bThreaded ) + { + renderablesToUpdate.AddToTail( info.m_pRenderable ); + } + else + { + info.m_pRenderable->ComputeFxBlend(); + } + info.m_TranslucencyCalculated = globalFrameCount; + info.m_TranslucencyCalculatedView = viewID; + } + orderedList.AddToTail( &info ); + idx = m_RenderablesInLeaf.NextElement(idx); + } + } + + if ( bThreaded ) + { + ParallelProcess( renderablesToUpdate.Base(), renderablesToUpdate.Count(), &CallComputeFXBlend, &::FrameLock, &::FrameUnlock ); + renderablesToUpdate.RemoveAll(); + } + + for ( i = 0; i != orderedList.Count(); i++ ) + { + RenderableInfo_t *pInfo = orderedList[i]; + if ( !IsLeafMarker( pInfo ) ) + { + if( pInfo->m_RenderFrame != frameNumber ) + { + if( pInfo->m_RenderGroup == RENDER_GROUP_TRANSLUCENT_ENTITY ) + { + pInfo->m_RenderLeaf = leaf; + } + pInfo->m_RenderFrame = frameNumber; + } + else if ( pInfo->m_Flags & RENDER_FLAGS_ALTERNATE_SORTING ) + { + if( pInfo->m_RenderGroup == RENDER_GROUP_TRANSLUCENT_ENTITY ) + { + pInfo->m_RenderLeaf = leaf; + } + } + + } + else + { + leaf = MarkerToLeaf( pInfo ); + } + } + + orderedList.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Adds a renderable to the list of renderables to render this frame +//----------------------------------------------------------------------------- +inline void AddRenderableToRenderList( CClientRenderablesList &renderList, IClientRenderable *pRenderable, + int iLeaf, RenderGroup_t group, ClientRenderHandle_t renderHandle, bool bTwoPass = false ) +{ +#ifdef _DEBUG + if (cl_drawleaf.GetInt() >= 0) + { + if (iLeaf != cl_drawleaf.GetInt()) + return; + } +#endif + + Assert( group >= 0 && group < RENDER_GROUP_COUNT ); + + int &curCount = renderList.m_RenderGroupCounts[group]; + if ( curCount < CClientRenderablesList::MAX_GROUP_ENTITIES ) + { + Assert( (iLeaf >= 0) && (iLeaf <= 65535) ); + + CClientRenderablesList::CEntry *pEntry = &renderList.m_RenderGroups[group][curCount]; + pEntry->m_pRenderable = pRenderable; + pEntry->m_iWorldListInfoLeaf = iLeaf; + pEntry->m_TwoPass = bTwoPass; + pEntry->m_RenderHandle = renderHandle; + curCount++; + } + else + { + engine->Con_NPrintf( 10, "Warning: overflowed CClientRenderablesList group %d", group ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : renderList - +// renderGroup - +//----------------------------------------------------------------------------- +void CClientLeafSystem::CollateViewModelRenderables( CUtlVector< IClientRenderable * >& opaque, CUtlVector< IClientRenderable * >& translucent ) +{ + for ( int i = m_ViewModels.Count()-1; i >= 0; --i ) + { + ClientRenderHandle_t handle = m_ViewModels[i]; + RenderableInfo_t& renderable = m_Renderables[handle]; + + // NOTE: In some cases, this removes the entity from the view model list + renderable.m_pRenderable->ComputeFxBlend(); + + // That's why we need to test RENDER_GROUP_OPAQUE_ENTITY - it may have changed in ComputeFXBlend() + if ( renderable.m_RenderGroup == RENDER_GROUP_VIEW_MODEL_OPAQUE || renderable.m_RenderGroup == RENDER_GROUP_OPAQUE_ENTITY ) + { + opaque.AddToTail( renderable.m_pRenderable ); + } + else + { + translucent.AddToTail( renderable.m_pRenderable ); + } + } +} + +static RenderGroup_t DetectBucketedRenderGroup( RenderGroup_t group, float fDimension ) +{ + float const arrThresholds[ 3 ] = { + 200.f, // tree size + 80.f, // player size + 30.f, // crate size + }; + Assert( ARRAYSIZE( arrThresholds ) + 1 >= RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS ); + Assert( group >= RENDER_GROUP_OPAQUE_STATIC && group <= RENDER_GROUP_OPAQUE_ENTITY ); + + int bucketedGroupIndex; + if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS <= 2 || + fDimension >= arrThresholds[1] ) + { + if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS <= 1 || + fDimension >= arrThresholds[0] ) + bucketedGroupIndex = 0; + else + bucketedGroupIndex = 1; + } + else + { + if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS <= 3 || + fDimension >= arrThresholds[2] ) + bucketedGroupIndex = 2; + else + bucketedGroupIndex = 3; + } + + // Determine the new bucketed group + RenderGroup_t bucketedGroup = RenderGroup_t( group - ( ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS - 1 ) - bucketedGroupIndex ) * 2 ); + Assert( bucketedGroup >= RENDER_GROUP_OPAQUE_STATIC_HUGE && bucketedGroup <= RENDER_GROUP_OPAQUE_ENTITY ); + + return bucketedGroup; +} + +void CClientLeafSystem::CollateRenderablesInLeaf( int leaf, int worldListLeafIndex, const SetupRenderInfo_t &info ) +{ + bool portalTestEnts = r_PortalTestEnts.GetBool() && !r_portalsopenall.GetBool(); + + // Place a fake entity for static/opaque ents in this leaf + AddRenderableToRenderList( *info.m_pRenderList, NULL, worldListLeafIndex, RENDER_GROUP_OPAQUE_STATIC, NULL ); + AddRenderableToRenderList( *info.m_pRenderList, NULL, worldListLeafIndex, RENDER_GROUP_OPAQUE_ENTITY, NULL ); + + // Collate everything. + unsigned short idx = m_RenderablesInLeaf.FirstElement(leaf); + for ( ;idx != m_RenderablesInLeaf.InvalidIndex(); idx = m_RenderablesInLeaf.NextElement(idx) ) + { + ClientRenderHandle_t handle = m_RenderablesInLeaf.Element(idx); + RenderableInfo_t& renderable = m_Renderables[handle]; + + // Early out on static props if we don't want to render them + if ((!m_DrawStaticProps) && (renderable.m_Flags & RENDER_FLAGS_STATIC_PROP)) + continue; + + // Early out if we're told to not draw small objects (top view only, + /* that's why we don't check the z component). + if (!m_DrawSmallObjects) + { + CCachedRenderInfo& cachedInfo = m_CachedRenderInfos[renderable.m_CachedRenderInfo]; + float sizeX = cachedInfo.m_Maxs.x - cachedInfo.m_Mins.x; + float sizeY = cachedInfo.m_Maxs.y - cachedInfo.m_Mins.y; + if ((sizeX < 50.f) && (sizeY < 50.f)) + continue; + }*/ + + Assert( m_DrawSmallObjects ); // MOTODO + + // Don't hit the same ent in multiple leaves twice. + if ( renderable.m_RenderGroup != RENDER_GROUP_TRANSLUCENT_ENTITY ) + { + if ( renderable.m_RenderFrame2 == info.m_nRenderFrame ) + continue; + + renderable.m_RenderFrame2 = info.m_nRenderFrame; + } + else // translucent + { + // Shadow depth skips ComputeTranslucentRenderLeaf! + + // Translucent entities already have had ComputeTranslucentRenderLeaf called on them + // so m_RenderLeaf should be set to the nearest leaf, so that's what we want here. + if ( renderable.m_RenderLeaf != leaf ) + continue; + } + + unsigned char nAlpha = 255; + if ( info.m_bDrawTranslucentObjects ) + { + // Prevent culling if the renderable is invisible + // NOTE: OPAQUE objects can have alpha == 0. + // They are made to be opaque because they don't have to be sorted. + nAlpha = renderable.m_pRenderable->GetFxBlend(); + if ( nAlpha == 0 ) + continue; + } + + Vector absMins, absMaxs; + CalcRenderableWorldSpaceAABB( renderable.m_pRenderable, absMins, absMaxs ); + // If the renderable is inside an area, cull it using the frustum for that area. + if ( portalTestEnts && renderable.m_Area != -1 ) + { + VPROF( "r_PortalTestEnts" ); + if ( !engine->DoesBoxTouchAreaFrustum( absMins, absMaxs, renderable.m_Area ) ) + continue; + } + else + { + // cull with main frustum + if ( engine->CullBox( absMins, absMaxs ) ) + continue; + } + + // UNDONE: Investigate speed tradeoffs of occlusion culling brush models too? + if ( renderable.m_Flags & RENDER_FLAGS_STUDIO_MODEL ) + { + // test to see if this renderable is occluded by the engine's occlusion system + if ( engine->IsOccluded( absMins, absMaxs ) ) + continue; + } + +#ifdef INVASION_CLIENT_DLL + if (info.m_flRenderDistSq != 0.0f) + { + Vector mins, maxs; + renderable.m_pRenderable->GetRenderBounds( mins, maxs ); + + if ((maxs.z - mins.z) < 100) + { + Vector vCenter; + VectorLerp( mins, maxs, 0.5f, vCenter ); + vCenter += renderable.m_pRenderable->GetRenderOrigin(); + + float flDistSq = info.m_vecRenderOrigin.DistToSqr( vCenter ); + if (info.m_flRenderDistSq <= flDistSq) + continue; + } + } +#endif + + if( renderable.m_RenderGroup != RENDER_GROUP_TRANSLUCENT_ENTITY ) + { + RenderGroup_t group = (RenderGroup_t)renderable.m_RenderGroup; + + // Determine object group offset + if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS > 1 && + group >= RENDER_GROUP_OPAQUE_STATIC && + group <= RENDER_GROUP_OPAQUE_ENTITY ) + { + Vector dims; + VectorSubtract( absMaxs, absMins, dims ); + + float const fDimension = max( max( fabs(dims.x), fabs(dims.y) ), fabs(dims.z) ); + group = DetectBucketedRenderGroup( group, fDimension ); + + Assert( group >= RENDER_GROUP_OPAQUE_STATIC_HUGE && group <= RENDER_GROUP_OPAQUE_ENTITY ); + } + + AddRenderableToRenderList( *info.m_pRenderList, renderable.m_pRenderable, + worldListLeafIndex, group, handle); + } + else + { + bool bTwoPass = ((renderable.m_Flags & RENDER_FLAGS_TWOPASS) != 0) && ( nAlpha == 255 ); // Two pass? + + // Add to appropriate list if drawing translucent objects (shadow depth mapping will skip this) + if ( info.m_bDrawTranslucentObjects ) + { + AddRenderableToRenderList( *info.m_pRenderList, renderable.m_pRenderable, + worldListLeafIndex, (RenderGroup_t)renderable.m_RenderGroup, handle, bTwoPass ); + } + + if ( bTwoPass ) // Also add to opaque list if it's a two-pass model... + { + AddRenderableToRenderList( *info.m_pRenderList, renderable.m_pRenderable, + worldListLeafIndex, RENDER_GROUP_OPAQUE_ENTITY, handle, bTwoPass ); + } + } + } + + // Do detail objects. + // These don't have render handles! + if ( info.m_bDrawDetailObjects && ShouldDrawDetailObjectsInLeaf( leaf, info.m_nDetailBuildFrame ) ) + { + idx = m_Leaf[leaf].m_FirstDetailProp; + int count = m_Leaf[leaf].m_DetailPropCount; + while( --count >= 0 ) + { + IClientRenderable* pRenderable = DetailObjectSystem()->GetDetailModel(idx); + + // FIXME: This if check here is necessary because the detail object system also maintains lists of sprites... + if (pRenderable) + { + if( pRenderable->IsTransparent() ) + { + if ( info.m_bDrawTranslucentObjects ) // Don't draw translucent objects into shadow depth maps + { + // Lots of the detail entities are invisible so avoid sorting them and all that. + if( pRenderable->GetFxBlend() > 0 ) + { + AddRenderableToRenderList( *info.m_pRenderList, pRenderable, + worldListLeafIndex, RENDER_GROUP_TRANSLUCENT_ENTITY, DETAIL_PROP_RENDER_HANDLE ); + } + } + } + else + { + AddRenderableToRenderList( *info.m_pRenderList, pRenderable, + worldListLeafIndex, RENDER_GROUP_OPAQUE_ENTITY, DETAIL_PROP_RENDER_HANDLE ); + } + } + ++idx; + } + } +} + + +//----------------------------------------------------------------------------- +// Sort entities in a back-to-front ordering +//----------------------------------------------------------------------------- +void CClientLeafSystem::SortEntities( const Vector &vecRenderOrigin, const Vector &vecRenderForward, CClientRenderablesList::CEntry *pEntities, int nEntities ) +{ + // Don't sort if we only have 1 entity + if ( nEntities <= 1 ) + return; + + float dists[CClientRenderablesList::MAX_GROUP_ENTITIES]; + + // First get a distance for each entity. + int i; + for( i=0; i < nEntities; i++ ) + { + IClientRenderable *pRenderable = pEntities[i].m_pRenderable; + + // Compute the center of the object (needed for translucent brush models) + Vector boxcenter; + Vector mins,maxs; + pRenderable->GetRenderBounds( mins, maxs ); + VectorAdd( mins, maxs, boxcenter ); + VectorMA( pRenderable->GetRenderOrigin(), 0.5f, boxcenter, boxcenter ); + + // Compute distance... + Vector delta; + VectorSubtract( boxcenter, vecRenderOrigin, delta ); + dists[i] = DotProduct( delta, vecRenderForward ); + } + + // H-sort. + int stepSize = 4; + while( stepSize ) + { + int end = nEntities - stepSize; + for( i=0; i < end; i += stepSize ) + { + if( dists[i] > dists[i+stepSize] ) + { + swap( pEntities[i], pEntities[i+stepSize] ); + swap( dists[i], dists[i+stepSize] ); + + if( i == 0 ) + { + i = -stepSize; + } + else + { + i -= stepSize << 1; + } + } + } + + stepSize >>= 1; + } +} + + +void CClientLeafSystem::BuildRenderablesList( const SetupRenderInfo_t &info ) +{ + VPROF_BUDGET( "BuildRenderablesList", "BuildRenderablesList" ); + int leafCount = info.m_pWorldListInfo->m_LeafCount; + const Vector &vecRenderOrigin = info.m_vecRenderOrigin; + const Vector &vecRenderForward = info.m_vecRenderForward; + CClientRenderablesList::CEntry *pTranslucentEntries = info.m_pRenderList->m_RenderGroups[RENDER_GROUP_TRANSLUCENT_ENTITY]; + int &nTranslucentEntries = info.m_pRenderList->m_RenderGroupCounts[RENDER_GROUP_TRANSLUCENT_ENTITY]; + + for( int i = 0; i < leafCount; i++ ) + { + int nTranslucent = nTranslucentEntries; + + // Add renderables from this leaf... + CollateRenderablesInLeaf( info.m_pWorldListInfo->m_pLeafList[i], i, info ); + + int nNewTranslucent = nTranslucentEntries - nTranslucent; + if( (nNewTranslucent != 0 ) && info.m_bDrawTranslucentObjects ) + { + // Sort the new translucent entities. + SortEntities( vecRenderOrigin, vecRenderForward, &pTranslucentEntries[nTranslucent], nNewTranslucent ); + } + } +} diff --git a/game/client/clientleafsystem.h b/game/client/clientleafsystem.h new file mode 100644 index 00000000..f3a51c3c --- /dev/null +++ b/game/client/clientleafsystem.h @@ -0,0 +1,217 @@ +//===== Copyright © 1996-2007, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +// +//===========================================================================// + +#if !defined( CLIENTLEAFSYSTEM_H ) +#define CLIENTLEAFSYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "IGameSystem.h" +#include "engine/IClientLeafSystem.h" +#include "cdll_int.h" +#include "IVRenderView.h" +#include "tier1/mempool.h" +#include "tier1/refcount.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +struct WorldListInfo_t; +class IClientRenderable; +class Vector; +class CGameTrace; +typedef CGameTrace trace_t; +struct Ray_t; +class Vector2D; +class CStaticProp; + + +//----------------------------------------------------------------------------- +// Handle to an renderables in the client leaf system +//----------------------------------------------------------------------------- +enum +{ + DETAIL_PROP_RENDER_HANDLE = (ClientRenderHandle_t)0xfffe +}; + + +class CClientRenderablesList : public CRefCounted<> +{ + DECLARE_FIXEDSIZE_ALLOCATOR( CClientRenderablesList ); + +public: + enum + { + MAX_GROUP_ENTITIES = 4096 + }; + + struct CEntry + { + IClientRenderable *m_pRenderable; + unsigned short m_iWorldListInfoLeaf; // NOTE: this indexes WorldListInfo_t's leaf list. + unsigned short m_TwoPass; + ClientRenderHandle_t m_RenderHandle; + }; + + // The leaves for the entries are in the order of the leaves you call CollateRenderablesInLeaf in. + CEntry m_RenderGroups[RENDER_GROUP_COUNT][MAX_GROUP_ENTITIES]; + int m_RenderGroupCounts[RENDER_GROUP_COUNT]; +}; + + +//----------------------------------------------------------------------------- +// Used by CollateRenderablesInLeaf +//----------------------------------------------------------------------------- +struct SetupRenderInfo_t +{ + WorldListInfo_t *m_pWorldListInfo; + CClientRenderablesList *m_pRenderList; + Vector m_vecRenderOrigin; + Vector m_vecRenderForward; + int m_nRenderFrame; + int m_nDetailBuildFrame; // The "render frame" for detail objects + float m_flRenderDistSq; + bool m_bDrawDetailObjects : 1; + bool m_bDrawTranslucentObjects : 1; + + SetupRenderInfo_t::SetupRenderInfo_t() + { + m_bDrawDetailObjects = true; + m_bDrawTranslucentObjects = true; + } +}; + + +//----------------------------------------------------------------------------- +// A handle associated with shadows managed by the client leaf system +//----------------------------------------------------------------------------- +typedef unsigned short ClientLeafShadowHandle_t; +enum +{ + CLIENT_LEAF_SHADOW_INVALID_HANDLE = (ClientLeafShadowHandle_t)~0 +}; + + +//----------------------------------------------------------------------------- +// The client leaf system +//----------------------------------------------------------------------------- +abstract_class IClientLeafShadowEnum +{ +public: + // The user ID is the id passed into CreateShadow + virtual void EnumShadow( ClientShadowHandle_t userId ) = 0; +}; + + +// subclassed by things which wish to add per-leaf data managed by the client leafsystem +class CClientLeafSubSystemData +{ +public: + virtual ~CClientLeafSubSystemData( void ) + { + } +}; + + +// defines for subsystem ids. each subsystem id uses up one pointer in each leaf +#define CLSUBSYSTEM_DETAILOBJECTS 0 +#define N_CLSUBSYSTEMS 1 + + + +//----------------------------------------------------------------------------- +// The client leaf system +//----------------------------------------------------------------------------- +abstract_class IClientLeafSystem : public IClientLeafSystemEngine, public IGameSystemPerFrame +{ +public: + // Adds and removes renderables from the leaf lists + virtual void AddRenderable( IClientRenderable* pRenderable, RenderGroup_t group ) = 0; + + // This tells if the renderable is in the current PVS. It assumes you've updated the renderable + // with RenderableChanged() calls + virtual bool IsRenderableInPVS( IClientRenderable *pRenderable ) = 0; + + virtual void SetSubSystemDataInLeaf( int leaf, int nSubSystemIdx, CClientLeafSubSystemData *pData ) =0; + virtual CClientLeafSubSystemData *GetSubSystemDataInLeaf( int leaf, int nSubSystemIdx ) =0; + + + virtual void SetDetailObjectsInLeaf( int leaf, int firstDetailObject, int detailObjectCount ) = 0; + virtual void GetDetailObjectsInLeaf( int leaf, int& firstDetailObject, int& detailObjectCount ) = 0; + + // Indicates which leaves detail objects should be rendered from, returns the detais objects in the leaf + virtual void DrawDetailObjectsInLeaf( int leaf, int frameNumber, int& firstDetailObject, int& detailObjectCount ) = 0; + + // Should we draw detail objects (sprites or models) in this leaf (because it's close enough to the view) + // *and* are there any objects in the leaf? + virtual bool ShouldDrawDetailObjectsInLeaf( int leaf, int frameNumber ) = 0; + + // Call this when a renderable origin/angles/bbox parameters has changed + virtual void RenderableChanged( ClientRenderHandle_t handle ) = 0; + + // Set a render group + virtual void SetRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ) = 0; + + // Computes which leaf translucent objects should be rendered in + virtual void ComputeTranslucentRenderLeaf( int count, const LeafIndex_t *pLeafList, const LeafFogVolume_t *pLeafFogVolumeList, int frameNumber, int viewID ) = 0; + + // Put renderables into their appropriate lists. + virtual void BuildRenderablesList( const SetupRenderInfo_t &info ) = 0; + + // Put renderables in the leaf into their appropriate lists. + virtual void CollateViewModelRenderables( CUtlVector< IClientRenderable * >& opaqueList, CUtlVector< IClientRenderable * >& translucentList ) = 0; + + // Call this to deactivate static prop rendering.. + virtual void DrawStaticProps( bool enable ) = 0; + + // Call this to deactivate small object rendering + virtual void DrawSmallEntities( bool enable ) = 0; + + // The following methods are related to shadows... + virtual ClientLeafShadowHandle_t AddShadow( ClientShadowHandle_t userId, unsigned short flags ) = 0; + virtual void RemoveShadow( ClientLeafShadowHandle_t h ) = 0; + + // Project a shadow + virtual void ProjectShadow( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ) = 0; + + // Project a projected texture spotlight + virtual void ProjectFlashlight( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ) = 0; + + // Find all shadow casters in a set of leaves + virtual void EnumerateShadowsInLeaves( int leafCount, LeafIndex_t* pLeaves, IClientLeafShadowEnum* pEnum ) = 0; + + // Fill in a list of the leaves this renderable is in. + // Returns -1 if the handle is invalid. + virtual int GetRenderableLeaves( ClientRenderHandle_t handle, int leaves[128] ) = 0; + + // Get leaves this renderable is in + virtual bool GetRenderableLeaf ( ClientRenderHandle_t handle, int* pOutLeaf, const int* pInIterator = 0, int* pOutIterator = 0 ) = 0; + + // Use alternate translucent sorting algorithm (draw translucent objects in the furthest leaf they lie in) + virtual void EnableAlternateSorting( ClientRenderHandle_t handle, bool bEnable ) = 0; +}; + + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- +extern IClientLeafSystem *g_pClientLeafSystem; +inline IClientLeafSystem* ClientLeafSystem() +{ + return g_pClientLeafSystem; +} + + +#endif // CLIENTLEAFSYSTEM_H + + diff --git a/game/client/clientmode.h b/game/client/clientmode.h new file mode 100644 index 00000000..a82b740d --- /dev/null +++ b/game/client/clientmode.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef CLIENTMODE_H +#define CLIENTMODE_H + +#include "iclientmode.h" + +typedef struct +{ + char *name; + bool draw; +} ModeElements; + +#endif diff --git a/game/client/clientmode_normal.cpp b/game/client/clientmode_normal.cpp new file mode 100644 index 00000000..5bcdef52 --- /dev/null +++ b/game/client/clientmode_normal.cpp @@ -0,0 +1,8 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// delete \ No newline at end of file diff --git a/game/client/clientmode_normal.h b/game/client/clientmode_normal.h new file mode 100644 index 00000000..5bcdef52 --- /dev/null +++ b/game/client/clientmode_normal.h @@ -0,0 +1,8 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// delete \ No newline at end of file diff --git a/game/client/clientmode_shared.cpp b/game/client/clientmode_shared.cpp new file mode 100644 index 00000000..c24a388f --- /dev/null +++ b/game/client/clientmode_shared.cpp @@ -0,0 +1,955 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Normal HUD mode +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + + +#include "cbase.h" +#include "clientmode_shared.h" +#include "iinput.h" +#include "view_shared.h" +#include "iviewrender.h" +#include "hud_basechat.h" +#include "weapon_selection.h" +#include +#include +#include +#include "engine/ienginesound.h" +#include +#include +#include "vgui_int.h" +#include "hud_macros.h" +#include "hltvcamera.h" +#include "particlemgr.h" +#include "c_vguiscreen.h" +#include "c_team.h" +#include "c_rumble.h" +#include "fmtstr.h" +#include "achievementmgr.h" +#include "c_playerresource.h" +#include +#if defined( _X360 ) +#include "xbox/xbox_console.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CHudWeaponSelection; +class CHudChat; + +static vgui::HContext s_hVGuiContext = DEFAULT_VGUI_CONTEXT; + +ConVar cl_drawhud( "cl_drawhud", "1", FCVAR_CHEAT, "Enable the rendering of the hud" ); +ConVar hud_takesshots( "hud_takesshots", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Auto-save a scoreboard screenshot at the end of a map." ); + +extern ConVar v_viewmodel_fov; + +extern bool IsInCommentaryMode( void ); + +CON_COMMAND( hud_reloadscheme, "Reloads hud layout and animation scripts." ) +{ + ClientModeShared *mode = ( ClientModeShared * )GetClientModeNormal(); + if ( !mode ) + return; + + mode->ReloadScheme(); +} + +#ifdef _DEBUG +CON_COMMAND_F( crash, "Crash the client. Optional parameter -- type of crash:\n 0: read from NULL\n 1: write to NULL\n 2: DmCrashDump() (xbox360 only)", FCVAR_CHEAT ) +{ + int crashtype = 0; + int dummy; + if ( args.ArgC() > 1 ) + { + crashtype = Q_atoi( args[1] ); + } + switch (crashtype) + { + case 0: + dummy = *((int *) NULL); + Msg("Crashed! %d\n", dummy); // keeps dummy from optimizing out + break; + case 1: + *((int *)NULL) = 42; + break; +#if defined( _X360 ) + case 2: + XBX_CrashDump(false); + break; +#endif + default: + Msg("Unknown variety of crash. You have now failed to crash. I hope you're happy.\n"); + break; + } +} +#endif // _DEBUG + +static void __MsgFunc_Rumble( bf_read &msg ) +{ + unsigned char waveformIndex; + unsigned char rumbleData; + unsigned char rumbleFlags; + + waveformIndex = msg.ReadByte(); + rumbleData = msg.ReadByte(); + rumbleFlags = msg.ReadByte(); + + RumbleEffect( waveformIndex, rumbleData, rumbleFlags ); +} + +static void __MsgFunc_VGUIMenu( bf_read &msg ) +{ + char panelname[2048]; + + msg.ReadString( panelname, sizeof(panelname) ); + + bool bShow = msg.ReadByte()!=0; + + IViewPortPanel *viewport = gViewPortInterface->FindPanelByName( panelname ); + + if ( !viewport ) + { + // DevMsg("VGUIMenu: couldn't find panel '%s'.\n", panelname ); + return; + } + + int count = msg.ReadByte(); + + if ( count > 0 ) + { + KeyValues *keys = new KeyValues("data"); + + for ( int i=0; iSetString( name, data ); + } + + viewport->SetData( keys ); + + keys->deleteThis(); + } + + // is the server telling us to show the scoreboard (at the end of a map)? + if ( Q_stricmp( panelname, "scores" ) == 0 ) + { + if ( hud_takesshots.GetBool() == true ) + { + gHUD.SetScreenShotTime( gpGlobals->curtime + 1.0 ); // take a screenshot in 1 second + } + } + + gViewPortInterface->ShowPanel( viewport, bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ClientModeShared::ClientModeShared() +{ + m_pViewport = NULL; + m_pChatElement = NULL; + m_pWeaponSelection = NULL; + m_nRootSize[ 0 ] = m_nRootSize[ 1 ] = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ClientModeShared::~ClientModeShared() +{ + delete m_pViewport; +} + +void ClientModeShared::ReloadScheme( void ) +{ + m_pViewport->ReloadScheme( "resource/ClientScheme.res" ); + ClearKeyValuesCache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::Init() +{ + m_pChatElement = ( CBaseHudChat * )GET_HUDELEMENT( CHudChat ); + Assert( m_pChatElement ); + + m_pWeaponSelection = ( CBaseHudWeaponSelection * )GET_HUDELEMENT( CHudWeaponSelection ); + Assert( m_pWeaponSelection ); + + // Derived ClientMode class must make sure m_Viewport is instantiated + Assert( m_pViewport ); + m_pViewport->LoadControlSettings( "scripts/HudLayout.res" ); + + ListenForGameEvent( "player_connect" ); + ListenForGameEvent( "player_disconnect" ); + ListenForGameEvent( "player_team" ); + ListenForGameEvent( "server_cvar" ); + ListenForGameEvent( "player_changename" ); + ListenForGameEvent( "teamplay_broadcast_audio" ); + ListenForGameEvent( "achievement_earned" ); + +#ifndef _XBOX + HLTVCamera()->Init(); +#endif + m_CursorNone = vgui::dc_none; + + HOOK_MESSAGE( VGUIMenu ); + HOOK_MESSAGE( Rumble ); +} + + +void ClientModeShared::InitViewport() +{ +} + + +void ClientModeShared::VGui_Shutdown() +{ + delete m_pViewport; + m_pViewport = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::Shutdown() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : frametime - +// *cmd - +//----------------------------------------------------------------------------- +bool ClientModeShared::CreateMove( float flInputSampleTime, CUserCmd *cmd ) +{ + // Let the player override the view. + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if(!pPlayer) + return true; + + // Let the player at it + return pPlayer->CreateMove( flInputSampleTime, cmd ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSetup - +//----------------------------------------------------------------------------- +void ClientModeShared::OverrideView( CViewSetup *pSetup ) +{ + QAngle camAngles; + + // Let the player override the view. + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if(!pPlayer) + return; + + pPlayer->OverrideView( pSetup ); + + if( ::input->CAM_IsThirdPerson() ) + { + Vector cam_ofs; + + ::input->CAM_GetCameraOffset( cam_ofs ); + + camAngles[ PITCH ] = cam_ofs[ PITCH ]; + camAngles[ YAW ] = cam_ofs[ YAW ]; + camAngles[ ROLL ] = 0; + + Vector camForward, camRight, camUp; + AngleVectors( camAngles, &camForward, &camRight, &camUp ); + + VectorMA( pSetup->origin, -cam_ofs[ ROLL ], camForward, pSetup->origin ); + + // Override angles from third person camera + VectorCopy( camAngles, pSetup->angles ); + } + else if (::input->CAM_IsOrthographic()) + { + pSetup->m_bOrtho = true; + float w, h; + ::input->CAM_OrthographicSize( w, h ); + w *= 0.5f; + h *= 0.5f; + pSetup->m_OrthoLeft = -w; + pSetup->m_OrthoTop = -h; + pSetup->m_OrthoRight = w; + pSetup->m_OrthoBottom = h; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ClientModeShared::ShouldDrawEntity(C_BaseEntity *pEnt) +{ + return true; +} + +bool ClientModeShared::ShouldDrawParticles( ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Allow weapons to override mouse input (for binoculars) +//----------------------------------------------------------------------------- +void ClientModeShared::OverrideMouseInput( float *x, float *y ) +{ + C_BaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( pWeapon ) + { + pWeapon->OverrideMouseInput( x, y ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ClientModeShared::ShouldDrawViewModel() +{ + return true; +} + +bool ClientModeShared::ShouldDrawDetailObjects( ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ClientModeShared::ShouldDrawCrosshair( void ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Don't draw the current view entity if we are not in 3rd person +//----------------------------------------------------------------------------- +bool ClientModeShared::ShouldDrawLocalPlayer( C_BasePlayer *pPlayer ) +{ + if ( ( pPlayer->index == render->GetViewEntity() ) && !C_BasePlayer::ShouldDrawLocalPlayer() ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: The mode can choose to not draw fog +//----------------------------------------------------------------------------- +bool ClientModeShared::ShouldDrawFog( void ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::AdjustEngineViewport( int& x, int& y, int& width, int& height ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::PreRender( CViewSetup *pSetup ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::PostRender() +{ + // Let the particle manager simulate things that haven't been simulated. + ParticleMgr()->PostRender(); +} + +void ClientModeShared::PostRenderVGui() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::Update() +{ + if ( m_pViewport->IsVisible() != cl_drawhud.GetBool() ) + { + m_pViewport->SetVisible( cl_drawhud.GetBool() ); + } + + UpdateRumbleEffects(); +} + +//----------------------------------------------------------------------------- +// This processes all input before SV Move messages are sent +//----------------------------------------------------------------------------- + +void ClientModeShared::ProcessInput(bool bActive) +{ + gHUD.ProcessInput( bActive ); +} + +//----------------------------------------------------------------------------- +// Purpose: We've received a keypress from the engine. Return 1 if the engine is allowed to handle it. +//----------------------------------------------------------------------------- +int ClientModeShared::KeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + if ( engine->Con_IsVisible() ) + return 1; + + // Should we start typing a message? + if ( pszCurrentBinding && + ( Q_strcmp( pszCurrentBinding, "messagemode" ) == 0 || + Q_strcmp( pszCurrentBinding, "say" ) == 0 ) ) + { + if ( down ) + { + StartMessageMode( MM_SAY ); + } + return 0; + } + else if ( pszCurrentBinding && + ( Q_strcmp( pszCurrentBinding, "messagemode2" ) == 0 || + Q_strcmp( pszCurrentBinding, "say_team" ) == 0 ) ) + { + if ( down ) + { + StartMessageMode( MM_SAY_TEAM ); + } + return 0; + } + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + // if ingame spectator mode, let spectator input intercept key event here + if( pPlayer && + ( pPlayer->GetObserverMode() > OBS_MODE_DEATHCAM ) && + !HandleSpectatorKeyInput( down, keynum, pszCurrentBinding ) ) + { + return 0; + } + + // Let game-specific hud elements get a crack at the key input + if ( !HudElementKeyInput( down, keynum, pszCurrentBinding ) ) + { + return 0; + } + + C_BaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( pWeapon ) + { + return pWeapon->KeyInput( down, keynum, pszCurrentBinding ); + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: See if spectator input occurred. Return 0 if the key is swallowed. +//----------------------------------------------------------------------------- +int ClientModeShared::HandleSpectatorKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + // we are in spectator mode, open spectator menu + if ( down && pszCurrentBinding && Q_strcmp( pszCurrentBinding, "+duck" ) == 0 ) + { + m_pViewport->ShowPanel( PANEL_SPECMENU, true ); + return 0; // we handled it, don't handle twice or send to server + } + else if ( down && pszCurrentBinding && Q_strcmp( pszCurrentBinding, "+attack" ) == 0 ) + { + engine->ClientCmd( "spec_next" ); + return 0; + } + else if ( down && pszCurrentBinding && Q_strcmp( pszCurrentBinding, "+attack2" ) == 0 ) + { + engine->ClientCmd( "spec_prev" ); + return 0; + } + else if ( down && pszCurrentBinding && Q_strcmp( pszCurrentBinding, "+jump" ) == 0 ) + { + engine->ClientCmd( "spec_mode" ); + return 0; + } + else if ( down && pszCurrentBinding && Q_strcmp( pszCurrentBinding, "+strafe" ) == 0 ) + { + HLTVCamera()->SetAutoDirector( true ); + return 0; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: See if hud elements want key input. Return 0 if the key is swallowed +//----------------------------------------------------------------------------- +int ClientModeShared::HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + if ( m_pWeaponSelection ) + { + if ( !m_pWeaponSelection->KeyInput( down, keynum, pszCurrentBinding ) ) + { + return 0; + } + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : vgui::Panel +//----------------------------------------------------------------------------- +vgui::Panel *ClientModeShared::GetMessagePanel() +{ + if ( m_pChatElement && m_pChatElement->GetInputPanel() && m_pChatElement->GetInputPanel()->IsVisible() ) + return m_pChatElement->GetInputPanel(); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: The player has started to type a message +//----------------------------------------------------------------------------- +void ClientModeShared::StartMessageMode( int iMessageModeType ) +{ + // Can only show chat UI in multiplayer!!! + if ( gpGlobals->maxClients == 1 ) + { + return; + } + if ( m_pChatElement ) + { + m_pChatElement->StartMessageMode( iMessageModeType ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *newmap - +//----------------------------------------------------------------------------- +void ClientModeShared::LevelInit( const char *newmap ) +{ + m_pViewport->GetAnimationController()->StartAnimationSequence("LevelInit"); + + // Tell the Chat Interface + if ( m_pChatElement ) + { + m_pChatElement->LevelInit( newmap ); + } + + // we have to fake this event clientside, because clients connect after that + IGameEvent *event = gameeventmanager->CreateEvent( "game_newmap" ); + if ( event ) + { + event->SetString("mapname", newmap ); + gameeventmanager->FireEventClientSide( event ); + } + + // Create a vgui context for all of the in-game vgui panels... + if ( s_hVGuiContext == DEFAULT_VGUI_CONTEXT ) + { + s_hVGuiContext = vgui::ivgui()->CreateContext(); + } + + // Reset any player explosion/shock effects + CLocalPlayerFilter filter; + enginesound->SetPlayerDSP( filter, 0, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::LevelShutdown( void ) +{ + if ( m_pChatElement ) + { + m_pChatElement->LevelShutdown(); + } + if ( s_hVGuiContext != DEFAULT_VGUI_CONTEXT ) + { + vgui::ivgui()->DestroyContext( s_hVGuiContext ); + s_hVGuiContext = DEFAULT_VGUI_CONTEXT; + } + + // Reset any player explosion/shock effects + CLocalPlayerFilter filter; + enginesound->SetPlayerDSP( filter, 0, true ); +} + + +void ClientModeShared::Enable() +{ + vgui::VPANEL pRoot; + + // Add our viewport to the root panel. + if( (pRoot = VGui_GetClientDLLRootPanel() ) != NULL ) + { + m_pViewport->SetParent( pRoot ); + } + + // All hud elements should be proportional + // This sets that flag on the viewport and all child panels + m_pViewport->SetProportional( true ); + + m_pViewport->SetCursor( m_CursorNone ); + vgui::surface()->SetCursor( m_CursorNone ); + + m_pViewport->SetVisible( true ); + if ( m_pViewport->IsKeyBoardInputEnabled() ) + { + m_pViewport->RequestFocus(); + } + + Layout(); +} + + +void ClientModeShared::Disable() +{ + vgui::VPANEL pRoot; + + // Remove our viewport from the root panel. + if( ( pRoot = VGui_GetClientDLLRootPanel() ) != NULL ) + { + m_pViewport->SetParent( (vgui::VPANEL)NULL ); + } + + m_pViewport->SetVisible( false ); +} + + +void ClientModeShared::Layout() +{ + vgui::VPANEL pRoot; + int wide, tall; + + // Make the viewport fill the root panel. + if( ( pRoot = VGui_GetClientDLLRootPanel() ) != NULL ) + { + vgui::ipanel()->GetSize(pRoot, wide, tall); + + bool changed = wide != m_nRootSize[ 0 ] || tall != m_nRootSize[ 1 ]; + m_nRootSize[ 0 ] = wide; + m_nRootSize[ 1 ] = tall; + + m_pViewport->SetBounds(0, 0, wide, tall); + if ( changed ) + { + ReloadScheme(); + } + } +} + +float ClientModeShared::GetViewModelFOV( void ) +{ + return v_viewmodel_fov.GetFloat(); +} + +class CHudChat; + +bool PlayerNameNotSetYet( const char *pszName ) +{ + if ( pszName && pszName[0] ) + { + // Don't show "unconnected" if we haven't got the players name yet + if ( Q_strnicmp(pszName,"unconnected",11) == 0 ) + return true; + if ( Q_strnicmp(pszName,"NULLNAME",11) == 0 ) + return true; + } + + return false; +} + +void ClientModeShared::FireGameEvent( IGameEvent *event ) +{ + CBaseHudChat *hudChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); + + const char *eventname = event->GetName(); + + if ( Q_strcmp( "player_connect", eventname ) == 0 ) + { + if ( !hudChat ) + return; + if ( PlayerNameNotSetYet(event->GetString("name")) ) + return; + + if ( !IsInCommentaryMode() ) + { + wchar_t wszLocalized[100]; + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; + g_pVGuiLocalize->ConvertANSIToUnicode( event->GetString("name"), wszPlayerName, sizeof(wszPlayerName) ); + g_pVGuiLocalize->ConstructString( wszLocalized, sizeof( wszLocalized ), g_pVGuiLocalize->Find( "#game_player_joined_game" ), 1, wszPlayerName ); + + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof(szLocalized) ); + + hudChat->Printf( CHAT_FILTER_JOINLEAVE, "%s", szLocalized ); + } + } + else if ( Q_strcmp( "player_disconnect", eventname ) == 0 ) + { + C_BasePlayer *pPlayer = USERID2PLAYER( event->GetInt("userid") ); + + if ( !hudChat || !pPlayer ) + return; + if ( PlayerNameNotSetYet(event->GetString("name")) ) + return; + + if ( !IsInCommentaryMode() ) + { + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; + g_pVGuiLocalize->ConvertANSIToUnicode( pPlayer->GetPlayerName(), wszPlayerName, sizeof(wszPlayerName) ); + + wchar_t wszReason[64]; + g_pVGuiLocalize->ConvertANSIToUnicode( event->GetString("reason"), wszReason, sizeof(wszReason) ); + + wchar_t wszLocalized[100]; + if (IsPC()) + { + g_pVGuiLocalize->ConstructString( wszLocalized, sizeof( wszLocalized ), g_pVGuiLocalize->Find( "#game_player_left_game" ), 2, wszPlayerName, wszReason ); + } + else + { + g_pVGuiLocalize->ConstructString( wszLocalized, sizeof( wszLocalized ), g_pVGuiLocalize->Find( "#game_player_left_game" ), 1, wszPlayerName ); + } + + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof(szLocalized) ); + + hudChat->Printf( CHAT_FILTER_JOINLEAVE, "%s", szLocalized ); + } + } + else if ( Q_strcmp( "player_team", eventname ) == 0 ) + { + C_BasePlayer *pPlayer = USERID2PLAYER( event->GetInt("userid") ); + if ( !hudChat ) + return; + if ( !pPlayer ) + return; + + bool bDisconnected = event->GetBool("disconnect"); + + if ( bDisconnected ) + return; + + int team = event->GetInt( "team" ); + + const char *pszName = pPlayer->GetPlayerName(); + if ( PlayerNameNotSetYet(pszName) ) + return; + + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; + g_pVGuiLocalize->ConvertANSIToUnicode( pszName, wszPlayerName, sizeof(wszPlayerName) ); + + wchar_t wszTeam[64]; + C_Team *pTeam = GetGlobalTeam( team ); + if ( pTeam ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( pTeam->Get_Name(), wszTeam, sizeof(wszTeam) ); + } + else + { + _snwprintf ( wszTeam, sizeof( wszTeam ) / sizeof( wchar_t ), L"%d", team ); + } + + if ( !IsInCommentaryMode() ) + { + wchar_t wszLocalized[100]; + g_pVGuiLocalize->ConstructString( wszLocalized, sizeof( wszLocalized ), g_pVGuiLocalize->Find( "#game_player_joined_team" ), 2, wszPlayerName, wszTeam ); + + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof(szLocalized) ); + + hudChat->Printf( CHAT_FILTER_TEAMCHANGE, "%s", szLocalized ); + } + + if ( pPlayer->IsLocalPlayer() ) + { + // that's me + pPlayer->TeamChange( team ); + } + } + else if ( Q_strcmp( "player_changename", eventname ) == 0 ) + { + if ( !hudChat ) + return; + + const char *pszOldName = event->GetString("oldname"); + if ( PlayerNameNotSetYet(pszOldName) ) + return; + + wchar_t wszOldName[MAX_PLAYER_NAME_LENGTH]; + g_pVGuiLocalize->ConvertANSIToUnicode( pszOldName, wszOldName, sizeof(wszOldName) ); + + wchar_t wszNewName[MAX_PLAYER_NAME_LENGTH]; + g_pVGuiLocalize->ConvertANSIToUnicode( event->GetString( "newname" ), wszNewName, sizeof(wszNewName) ); + + wchar_t wszLocalized[100]; + g_pVGuiLocalize->ConstructString( wszLocalized, sizeof( wszLocalized ), g_pVGuiLocalize->Find( "#game_player_changed_name" ), 2, wszOldName, wszNewName ); + + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof(szLocalized) ); + + hudChat->Printf( CHAT_FILTER_NAMECHANGE, "%s", szLocalized ); + } + else if ( Q_strcmp( "teamplay_broadcast_audio", eventname ) == 0 ) + { + int team = event->GetInt( "team" ); + + bool bValidTeam = false; + + if ( (GetLocalTeam() && GetLocalTeam()->GetTeamNumber() == team) ) + { + bValidTeam = true; + } + + //If we're in the spectator team then we should be getting whatever messages the person I'm spectating gets. + if ( bValidTeam == false ) + { + CBasePlayer *pSpectatorTarget = UTIL_PlayerByIndex( GetSpectatorTarget() ); + + if ( pSpectatorTarget && (GetSpectatorMode() == OBS_MODE_IN_EYE || GetSpectatorMode() == OBS_MODE_CHASE) ) + { + if ( pSpectatorTarget->GetTeamNumber() == team ) + { + bValidTeam = true; + } + } + } + + if ( team == 0 && GetLocalTeam() > 0 ) + { + bValidTeam = false; + } + + if ( bValidTeam == true ) + { + CLocalPlayerFilter filter; + const char *pszSoundName = event->GetString("sound"); + C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, pszSoundName ); + } + } + else if ( Q_strcmp( "teamplay_broadcast_audio", eventname ) == 0 ) + { + int team = event->GetInt( "team" ); + if ( !team || (GetLocalTeam() && GetLocalTeam()->GetTeamNumber() == team) ) + { + CLocalPlayerFilter filter; + const char *pszSoundName = event->GetString("sound"); + C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, pszSoundName ); + } + } + else if ( Q_strcmp( "server_cvar", eventname ) == 0 ) + { + if ( !IsInCommentaryMode() ) + { + wchar_t wszCvarName[64]; + g_pVGuiLocalize->ConvertANSIToUnicode( event->GetString("cvarname"), wszCvarName, sizeof(wszCvarName) ); + + wchar_t wszCvarValue[16]; + g_pVGuiLocalize->ConvertANSIToUnicode( event->GetString("cvarvalue"), wszCvarValue, sizeof(wszCvarValue) ); + + wchar_t wszLocalized[100]; + g_pVGuiLocalize->ConstructString( wszLocalized, sizeof( wszLocalized ), g_pVGuiLocalize->Find( "#game_server_cvar_changed" ), 2, wszCvarName, wszCvarValue ); + + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof(szLocalized) ); + + hudChat->Printf( CHAT_FILTER_SERVERMSG, "%s", szLocalized ); + } + } + else if ( Q_strcmp( "achievement_earned", eventname ) == 0 ) + { + int iPlayerIndex = event->GetInt( "player" ); + C_BasePlayer *pPlayer = UTIL_PlayerByIndex( iPlayerIndex ); + int iAchievement = event->GetInt( "achievement" ); + + if ( !hudChat || !pPlayer ) + return; + + if ( !IsInCommentaryMode() ) + { + CAchievementMgr *pAchievementMgr = dynamic_cast( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + + IAchievement *pAchievement = pAchievementMgr->GetAchievementByID( iAchievement ); + if ( pAchievement ) + { + if ( !pPlayer->IsDormant() ) + { + // no particle effect if the local player is the one with the achievement or the player is dead + if ( !pPlayer->IsLocalPlayer() && pPlayer->IsAlive() ) + { + //tagES using the "head" attachment won't work for CS and DoD + pPlayer->ParticleProp()->Create( "achieved", PATTACH_POINT_FOLLOW, "head" ); + } + + pPlayer->OnAchievementAchieved( iAchievement ); + } + + if ( g_PR ) + { + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; + g_pVGuiLocalize->ConvertANSIToUnicode( g_PR->GetPlayerName( iPlayerIndex ), wszPlayerName, sizeof( wszPlayerName ) ); + + const wchar_t *pchLocalizedAchievement = ACHIEVEMENT_LOCALIZED_NAME_FROM_STR( pAchievement->GetName() ); + if ( pchLocalizedAchievement ) + { + wchar_t wszLocalizedString[128]; + g_pVGuiLocalize->ConstructString( wszLocalizedString, sizeof( wszLocalizedString ), g_pVGuiLocalize->Find( "#Achievement_Earned" ), 2, wszPlayerName, pchLocalizedAchievement ); + + char szLocalized[128]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedString, szLocalized, sizeof( szLocalized ) ); + + hudChat->ChatPrintf( iPlayerIndex, CHAT_FILTER_SERVERMSG, "%s", szLocalized ); + } + } + } + } + } + else + { + DevMsg( 2, "Unhandled GameEvent in ClientModeShared::FireGameEvent - %s\n", event->GetName() ); + } +} + + + + + +//----------------------------------------------------------------------------- +// In-game VGUI context +//----------------------------------------------------------------------------- +void ClientModeShared::ActivateInGameVGuiContext( vgui::Panel *pPanel ) +{ + vgui::ivgui()->AssociatePanelWithContext( s_hVGuiContext, pPanel->GetVPanel() ); + vgui::ivgui()->ActivateContext( s_hVGuiContext ); +} + +void ClientModeShared::DeactivateInGameVGuiContext() +{ + vgui::ivgui()->ActivateContext( DEFAULT_VGUI_CONTEXT ); +} + diff --git a/game/client/clientmode_shared.h b/game/client/clientmode_shared.h new file mode 100644 index 00000000..1bf75207 --- /dev/null +++ b/game/client/clientmode_shared.h @@ -0,0 +1,110 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( CLIENTMODE_NORMAL_H ) +#define CLIENTMODE_NORMAL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "iclientmode.h" +#include "gameeventlistener.h" +#include + +class CBaseHudChat; +class CBaseHudWeaponSelection; +class CViewSetup; +class C_BaseEntity; +class C_BasePlayer; + +namespace vgui +{ +class Panel; +} + +#define USERID2PLAYER(i) ToBasePlayer( ClientEntityList().GetEnt( engine->GetPlayerForUserID( i ) ) ) + +extern IClientMode *GetClientModeNormal(); // must be implemented + +// This class implements client mode functionality common to HL2 and TF2. +class ClientModeShared : public IClientMode, public CGameEventListener +{ +// IClientMode overrides. +public: + DECLARE_CLASS_NOBASE( ClientModeShared ); + + ClientModeShared(); + virtual ~ClientModeShared(); + + virtual void Init(); + virtual void InitViewport(); + virtual void VGui_Shutdown(); + virtual void Shutdown(); + + virtual void LevelInit( const char *newmap ); + virtual void LevelShutdown( void ); + + virtual void Enable(); + virtual void Disable(); + virtual void Layout(); + + virtual void ReloadScheme( void ); + virtual void OverrideView( CViewSetup *pSetup ); + virtual bool ShouldDrawDetailObjects( ); + virtual bool ShouldDrawEntity(C_BaseEntity *pEnt); + virtual bool ShouldDrawLocalPlayer( C_BasePlayer *pPlayer ); + virtual bool ShouldDrawViewModel(); + virtual bool ShouldDrawParticles( ); + virtual bool ShouldDrawCrosshair( void ); + virtual void AdjustEngineViewport( int& x, int& y, int& width, int& height ); + virtual void PreRender(CViewSetup *pSetup); + virtual void PostRender(); + virtual void PostRenderVGui(); + virtual void ProcessInput(bool bActive); + virtual bool CreateMove( float flInputSampleTime, CUserCmd *cmd ); + virtual void Update(); + + // Input + virtual int KeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ); + virtual int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ); + virtual void OverrideMouseInput( float *x, float *y ); + virtual void StartMessageMode( int iMessageModeType ); + virtual vgui::Panel *GetMessagePanel(); + + virtual void ActivateInGameVGuiContext( vgui::Panel *pPanel ); + virtual void DeactivateInGameVGuiContext(); + + // The mode can choose to not draw fog + virtual bool ShouldDrawFog( void ); + + virtual float GetViewModelFOV( void ); + virtual vgui::Panel* GetViewport() { return m_pViewport; } + // Gets at the viewports vgui panel animation controller, if there is one... + virtual vgui::AnimationController *GetViewportAnimationController() + { return m_pViewport->GetAnimationController(); } + + virtual void FireGameEvent( IGameEvent *event ); + + virtual bool CanRecordDemo( char *errorMsg, int length ) const { return true; } + + virtual int HandleSpectatorKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ); + +protected: + CBaseViewport *m_pViewport; + +private: + // Message mode handling + // All modes share a common chat interface + CBaseHudChat *m_pChatElement; + vgui::HCursor m_CursorNone; + CBaseHudWeaponSelection *m_pWeaponSelection; + int m_nRootSize[2]; +}; + +#endif // CLIENTMODE_NORMAL_H + diff --git a/game/client/clientshadowmgr.cpp b/game/client/clientshadowmgr.cpp new file mode 100644 index 00000000..f57a38cb --- /dev/null +++ b/game/client/clientshadowmgr.cpp @@ -0,0 +1,4327 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +// Interface to the client system responsible for dealing with shadows +// +// Boy is this complicated. OK, lets talk about how this works at the moment +// +// The ClientShadowMgr contains all of the highest-level state for rendering +// shadows, and it controls the ShadowMgr in the engine which is the central +// clearing house for rendering shadows. +// +// There are two important types of objects with respect to shadows: +// the shadow receiver, and the shadow caster. How is the association made +// between casters + the receivers? Turns out it's done slightly differently +// depending on whether the receiver is the world, or if it's an entity. +// +// In the case of the world, every time the engine's ProjectShadow() is called, +// any previous receiver state stored (namely, which world surfaces are +// receiving shadows) are cleared. Then, when ProjectShadow is called, +// the engine iterates over all nodes + leaves within the shadow volume and +// marks front-facing surfaces in them as potentially being affected by the +// shadow. Later on, if those surfaces are actually rendered, the surfaces +// are clipped by the shadow volume + rendered. +// +// In the case of entities, there are slightly different methods depending +// on whether the receiver is a brush model or a studio model. However, there +// are a couple central things that occur with both. +// +// Every time a shadow caster is moved, the ClientLeafSystem's ProjectShadow +// method is called to tell it to remove the shadow from all leaves + all +// renderables it's currently associated with. Then it marks each leaf in the +// shadow volume as being affected by that shadow, and it marks every renderable +// in that volume as being potentially affected by the shadow (the function +// AddShadowToRenderable is called for each renderable in leaves affected +// by the shadow volume). +// +// Every time a shadow receiver is moved, the ClientLeafSystem first calls +// RemoveAllShadowsFromRenderable to have it clear out its state, and then +// the ClientLeafSystem calls AddShadowToRenderable() for all shadows in all +// leaves the renderable has moved into. +// +// Now comes the difference between brush models + studio models. In the case +// of brush models, when a shadow is added to the studio model, it's done in +// the exact same way as for the world. Surfaces on the brush model are marked +// as potentially being affected by the shadow, and if those surfaces are +// rendered, the surfaces are clipped to the shadow volume. When ProjectShadow() +// is called, turns out the same operation that removes the shadow that moved +// from the world surfaces also works to remove the shadow from brush surfaces. +// +// In the case of studio models, we need a separate operation to remove +// the shadow from all studio models +//===========================================================================// + + +#include "cbase.h" +#include "engine/IShadowMgr.h" +#include "model_types.h" +#include "bitmap/imageformat.h" +#include "materialsystem/IMaterialProxy.h" +#include "materialsystem/IMaterialVar.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMesh.h" +#include "materialsystem/ITexture.h" +#include "BSPTreeData.h" +#include "utlmultilist.h" +#include "CollisionUtils.h" +#include "iviewrender.h" +#include "IVRenderView.h" +#include "tier0/vprof.h" +#include "engine/ivmodelinfo.h" +#include "view_shared.h" +#include "engine/IVDebugOverlay.h" +#include "engine/IStaticPropMgr.h" +#include "datacache/imdlcache.h" +#include "viewrender.h" +#include "tier0/ICommandLine.h" +#include "vstdlib/jobthread.h" +#include "toolframework_client.h" +#include "bonetoworldarray.h" +#include "cmodel.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar r_flashlightdrawfrustum( "r_flashlightdrawfrustum", "0" ); +static ConVar r_flashlightmodels( "r_flashlightmodels", "1" ); +static ConVar r_shadowrendertotexture( "r_shadowrendertotexture", "0" ); +static ConVar r_flashlight_version2( "r_flashlight_version2", "0" ); + +ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1" ); + +#if defined( _X360 ) +ConVar r_flashlightdepthres( "r_flashlightdepthres", "512" ); +#else +ConVar r_flashlightdepthres( "r_flashlightdepthres", "1024" ); +#endif + +ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "0" ); + +#ifdef _WIN32 +#pragma warning( disable: 4701 ) +#endif + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); + + +//----------------------------------------------------------------------------- +// A texture allocator used to batch textures together +// At the moment, the implementation simply allocates blocks of max 256x256 +// and each block stores an array of uniformly-sized textures +//----------------------------------------------------------------------------- +typedef unsigned short TextureHandle_t; +enum +{ + INVALID_TEXTURE_HANDLE = (TextureHandle_t)~0 +}; + +class CTextureAllocator +{ +public: + // Initialize the allocator with something that knows how to refresh the bits + void Init(); + void Shutdown(); + + // Resets the allocator + void Reset(); + + // Deallocates everything + void DeallocateAllTextures(); + + // Allocate, deallocate texture + TextureHandle_t AllocateTexture( int w, int h ); + void DeallocateTexture( TextureHandle_t h ); + + // Mark texture as being used... (return true if re-render is needed) + bool UseTexture( TextureHandle_t h, bool bWillRedraw, float flArea ); + bool HasValidTexture( TextureHandle_t h ); + + // Advance frame... + void AdvanceFrame(); + + // Get at the location of the texture + void GetTextureRect(TextureHandle_t handle, int& x, int& y, int& w, int& h ); + + // Get at the texture it's a part of + ITexture *GetTexture(); + + // Get at the total texture size. + void GetTotalTextureSize( int& w, int& h ); + + void DebugPrintCache( void ); + +private: + typedef unsigned short FragmentHandle_t; + + enum + { + INVALID_FRAGMENT_HANDLE = (FragmentHandle_t)~0, + TEXTURE_PAGE_SIZE = 1024, + MAX_TEXTURE_POWER = 8, +#if !defined( _X360 ) + MIN_TEXTURE_POWER = 4, +#else + MIN_TEXTURE_POWER = 5, // per resolve requirements to ensure 32x32 aligned offsets +#endif + MAX_TEXTURE_SIZE = (1 << MAX_TEXTURE_POWER), + MIN_TEXTURE_SIZE = (1 << MIN_TEXTURE_POWER), + BLOCK_SIZE = MAX_TEXTURE_SIZE, + BLOCKS_PER_ROW = (TEXTURE_PAGE_SIZE / MAX_TEXTURE_SIZE), + BLOCK_COUNT = (BLOCKS_PER_ROW * BLOCKS_PER_ROW), + }; + + struct TextureInfo_t + { + FragmentHandle_t m_Fragment; + unsigned short m_Size; + unsigned short m_Power; + }; + + struct FragmentInfo_t + { + unsigned short m_Block; + unsigned short m_Index; + TextureHandle_t m_Texture; + + // Makes sure we don't overflow + unsigned int m_FrameUsed; + }; + + struct BlockInfo_t + { + unsigned short m_FragmentPower; + }; + + struct Cache_t + { + unsigned short m_List; + }; + + // Adds a block worth of fragments to the LRU + void AddBlockToLRU( int block ); + + // Unlink fragment from cache + void UnlinkFragmentFromCache( Cache_t& cache, FragmentHandle_t fragment ); + + // Mark something as being used (MRU).. + void MarkUsed( FragmentHandle_t fragment ); + + // Mark something as being unused (LRU).. + void MarkUnused( FragmentHandle_t fragment ); + + // Disconnect texture from fragment + void DisconnectTextureFromFragment( FragmentHandle_t f ); + + // Returns the size of a particular fragment + int GetFragmentPower( FragmentHandle_t f ) const; + + // Stores the actual texture we're writing into + CTextureReference m_TexturePage; + + CUtlLinkedList< TextureInfo_t, TextureHandle_t > m_Textures; + CUtlMultiList< FragmentInfo_t, FragmentHandle_t > m_Fragments; + + Cache_t m_Cache[MAX_TEXTURE_POWER+1]; + BlockInfo_t m_Blocks[BLOCK_COUNT]; + unsigned int m_CurrentFrame; +}; + +//----------------------------------------------------------------------------- +// Allocate/deallocate the texture page +//----------------------------------------------------------------------------- +void CTextureAllocator::Init() +{ + for ( int i = 0; i <= MAX_TEXTURE_POWER; ++i ) + { + m_Cache[i].m_List = m_Fragments.InvalidIndex(); + } + +#if !defined( _X360 ) + // don't need depth buffer for shadows + m_TexturePage.InitRenderTarget( TEXTURE_PAGE_SIZE, TEXTURE_PAGE_SIZE, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false, "_rt_Shadows" ); +#else + // unfortunate explicit management required for this render target + // 32bpp edram is only largest shadow fragment, but resolved to actual shadow atlas + // because full-res 1024x1024 shadow buffer is too large for EDRAM + m_TexturePage.InitRenderTargetTexture( TEXTURE_PAGE_SIZE, TEXTURE_PAGE_SIZE, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false, "_rt_Shadows" ); + + // edram footprint is only 256x256x4 = 256K + m_TexturePage.InitRenderTargetSurface( MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, IMAGE_FORMAT_ARGB8888, false ); + + // due to texture/surface size mismatch, ensure texture page is entirely cleared translucent + // otherwise border artifacts at edge of shadows due to pixel shader averaging of unwanted bits + m_TexturePage->ClearTexture( 0, 0, 0, 0 ); +#endif +} + +void CTextureAllocator::Shutdown() +{ + m_TexturePage.Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Initialize the allocator with something that knows how to refresh the bits +//----------------------------------------------------------------------------- +void CTextureAllocator::Reset() +{ + DeallocateAllTextures(); + + m_Textures.EnsureCapacity(256); + m_Fragments.EnsureCapacity(256); + + // Set up the block sizes.... + // FIXME: Improve heuristic?!? +#if !defined( _X360 ) + m_Blocks[0].m_FragmentPower = MAX_TEXTURE_POWER-4; // 128 cells at ExE resolution +#else + m_Blocks[0].m_FragmentPower = MAX_TEXTURE_POWER-3; // 64 cells at DxD resolution +#endif + m_Blocks[1].m_FragmentPower = MAX_TEXTURE_POWER-3; // 64 cells at DxD resolution + m_Blocks[2].m_FragmentPower = MAX_TEXTURE_POWER-2; // 32 cells at CxC resolution + m_Blocks[3].m_FragmentPower = MAX_TEXTURE_POWER-2; + m_Blocks[4].m_FragmentPower = MAX_TEXTURE_POWER-1; // 24 cells at BxB resolution + m_Blocks[5].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[6].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[7].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[8].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[9].m_FragmentPower = MAX_TEXTURE_POWER-1; + m_Blocks[10].m_FragmentPower = MAX_TEXTURE_POWER; // 6 cells at AxA resolution + m_Blocks[11].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[12].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[13].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[14].m_FragmentPower = MAX_TEXTURE_POWER; + m_Blocks[15].m_FragmentPower = MAX_TEXTURE_POWER; + + // Initialize the LRU + int i; + for ( i = 0; i <= MAX_TEXTURE_POWER; ++i ) + { + m_Cache[i].m_List = m_Fragments.CreateList(); + } + + // Now that the block sizes are allocated, create LRUs for the various block sizes + for ( i = 0; i < BLOCK_COUNT; ++i) + { + // Initialize LRU + AddBlockToLRU( i ); + } + + m_CurrentFrame = 0; +} + +void CTextureAllocator::DeallocateAllTextures() +{ + m_Textures.Purge(); + m_Fragments.Purge(); + for ( int i = 0; i <= MAX_TEXTURE_POWER; ++i ) + { + m_Cache[i].m_List = m_Fragments.InvalidIndex(); + } +} + + +//----------------------------------------------------------------------------- +// Dump the state of the cache to debug out +//----------------------------------------------------------------------------- +void CTextureAllocator::DebugPrintCache( void ) +{ + // For each fragment + int nNumFragments = m_Fragments.TotalCount(); + int nNumInvalidFragments = 0; + + Warning("Fragments (%d):\n===============\n", nNumFragments); + + for ( int f = 0; f < nNumFragments; f++ ) + { + if ( ( m_Fragments[f].m_FrameUsed != 0 ) && ( m_Fragments[f].m_Texture != INVALID_TEXTURE_HANDLE ) ) + Warning("Fragment %d, Block: %d, Index: %d, Texture: %d Frame Used: %d\n", f, m_Fragments[f].m_Block, m_Fragments[f].m_Index, m_Fragments[f].m_Texture, m_Fragments[f].m_FrameUsed ); + else + nNumInvalidFragments++; + } + + Warning("Invalid Fragments: %d\n", nNumInvalidFragments); + +// for ( int c = 0; c <= MAX_TEXTURE_POWER; ++c ) +// { +// Warning("Cache Index (%d)\n", m_Cache[c].m_List); +// } + +} + + +//----------------------------------------------------------------------------- +// Adds a block worth of fragments to the LRU +//----------------------------------------------------------------------------- +void CTextureAllocator::AddBlockToLRU( int block ) +{ + int power = m_Blocks[block].m_FragmentPower; + int size = (1 << power); + + // Compute the number of fragments in this block + int fragmentCount = MAX_TEXTURE_SIZE / size; + fragmentCount *= fragmentCount; + + // For each fragment, indicate which block it's a part of (and the index) + // and then stick in at the top of the LRU + while (--fragmentCount >= 0 ) + { + FragmentHandle_t f = m_Fragments.Alloc( ); + m_Fragments[f].m_Block = block; + m_Fragments[f].m_Index = fragmentCount; + m_Fragments[f].m_Texture = INVALID_TEXTURE_HANDLE; + m_Fragments[f].m_FrameUsed = 0xFFFFFFFF; + m_Fragments.LinkToHead( m_Cache[power].m_List, f ); + } +} + + +//----------------------------------------------------------------------------- +// Unlink fragment from cache +//----------------------------------------------------------------------------- +void CTextureAllocator::UnlinkFragmentFromCache( Cache_t& cache, FragmentHandle_t fragment ) +{ + m_Fragments.Unlink( cache.m_List, fragment); +} + + +//----------------------------------------------------------------------------- +// Mark something as being used (MRU).. +//----------------------------------------------------------------------------- +void CTextureAllocator::MarkUsed( FragmentHandle_t fragment ) +{ + int block = m_Fragments[fragment].m_Block; + int power = m_Blocks[block].m_FragmentPower; + + // Hook it at the end of the LRU + Cache_t& cache = m_Cache[power]; + m_Fragments.LinkToTail( cache.m_List, fragment ); + m_Fragments[fragment].m_FrameUsed = m_CurrentFrame; +} + + +//----------------------------------------------------------------------------- +// Mark something as being unused (LRU).. +//----------------------------------------------------------------------------- +void CTextureAllocator::MarkUnused( FragmentHandle_t fragment ) +{ + int block = m_Fragments[fragment].m_Block; + int power = m_Blocks[block].m_FragmentPower; + + // Hook it at the end of the LRU + Cache_t& cache = m_Cache[power]; + m_Fragments.LinkToHead( cache.m_List, fragment ); +} + + +//----------------------------------------------------------------------------- +// Allocate, deallocate texture +//----------------------------------------------------------------------------- +TextureHandle_t CTextureAllocator::AllocateTexture( int w, int h ) +{ + // Implementational detail for now + Assert( w == h ); + + // Clamp texture size + if (w < MIN_TEXTURE_SIZE) + w = MIN_TEXTURE_SIZE; + else if (w > MAX_TEXTURE_SIZE) + w = MAX_TEXTURE_SIZE; + + TextureHandle_t handle = m_Textures.AddToTail(); + m_Textures[handle].m_Fragment = INVALID_FRAGMENT_HANDLE; + m_Textures[handle].m_Size = w; + + // Find the power of two + int power = 0; + int size = 1; + while(size < w) + { + size <<= 1; + ++power; + } + Assert( size == w ); + + m_Textures[handle].m_Power = power; + + return handle; +} + +void CTextureAllocator::DeallocateTexture( TextureHandle_t h ) +{ +// Warning("Beginning of DeallocateTexture\n"); +// DebugPrintCache(); + + if (m_Textures[h].m_Fragment != INVALID_FRAGMENT_HANDLE) + { + MarkUnused(m_Textures[h].m_Fragment); + m_Fragments[m_Textures[h].m_Fragment].m_FrameUsed = 0xFFFFFFFF; // non-zero frame + DisconnectTextureFromFragment( m_Textures[h].m_Fragment ); + } + m_Textures.Remove(h); + +// Warning("End of DeallocateTexture\n"); +// DebugPrintCache(); +} + + +//----------------------------------------------------------------------------- +// Disconnect texture from fragment +//----------------------------------------------------------------------------- +void CTextureAllocator::DisconnectTextureFromFragment( FragmentHandle_t f ) +{ +// Warning( "Beginning of DisconnectTextureFromFragment\n" ); +// DebugPrintCache(); + + FragmentInfo_t& info = m_Fragments[f]; + if (info.m_Texture != INVALID_TEXTURE_HANDLE) + { + m_Textures[info.m_Texture].m_Fragment = INVALID_FRAGMENT_HANDLE; + info.m_Texture = INVALID_TEXTURE_HANDLE; + } + + +// Warning( "End of DisconnectTextureFromFragment\n" ); +// DebugPrintCache(); +} + + +//----------------------------------------------------------------------------- +// Do we have a valid texture assigned? +//----------------------------------------------------------------------------- +bool CTextureAllocator::HasValidTexture( TextureHandle_t h ) +{ + TextureInfo_t& info = m_Textures[h]; + FragmentHandle_t currentFragment = info.m_Fragment; + return (currentFragment != INVALID_FRAGMENT_HANDLE); +} + + +//----------------------------------------------------------------------------- +// Mark texture as being used... +//----------------------------------------------------------------------------- +bool CTextureAllocator::UseTexture( TextureHandle_t h, bool bWillRedraw, float flArea ) +{ +// Warning( "Top of UseTexture\n" ); +// DebugPrintCache(); + + TextureInfo_t& info = m_Textures[h]; + + // spin up to the best fragment size + int nDesiredPower = MIN_TEXTURE_POWER; + int nDesiredWidth = MIN_TEXTURE_SIZE; + while ( (nDesiredWidth * nDesiredWidth) < flArea ) + { + if ( nDesiredPower >= info.m_Power ) + { + nDesiredPower = info.m_Power; + break; + } + + ++nDesiredPower; + nDesiredWidth <<= 1; + } + + // If we've got a valid fragment for this texture, no worries! + int nCurrentPower = -1; + FragmentHandle_t currentFragment = info.m_Fragment; + if (currentFragment != INVALID_FRAGMENT_HANDLE) + { + // If the current fragment is at or near the desired power, we're done + nCurrentPower = GetFragmentPower(info.m_Fragment); + Assert( nCurrentPower <= info.m_Power ); + bool bShouldKeepTexture = (!bWillRedraw) && (nDesiredPower < 8) && (nDesiredPower - nCurrentPower <= 1); + if ((nCurrentPower == nDesiredPower) || bShouldKeepTexture) + { + // Move to the back of the LRU + MarkUsed( currentFragment ); + return false; + } + } + +// Warning( "\n\nUseTexture B\n" ); +// DebugPrintCache(); + + // Grab the LRU fragment from the appropriate cache + // If that fragment is connected to a texture, disconnect it. + int power = nDesiredPower; + + FragmentHandle_t f = INVALID_FRAGMENT_HANDLE; + bool done = false; + while (!done && power >= 0) + { + f = m_Fragments.Head( m_Cache[power].m_List ); + + // This represents an overflow condition (used too many textures of + // the same size in a single frame). It that happens, just use a texture + // of lower res. + if ( (f != m_Fragments.InvalidIndex()) && (m_Fragments[f].m_FrameUsed != m_CurrentFrame) ) + { + done = true; + } + else + { + --power; + } + } + + +// Warning( "\n\nUseTexture C\n" ); +// DebugPrintCache(); + + // Ok, lets see if we're better off than we were... + if (currentFragment != INVALID_FRAGMENT_HANDLE) + { + if (power <= nCurrentPower) + { + // Oops... we're not. Let's leave well enough alone + // Move to the back of the LRU + MarkUsed( currentFragment ); + return false; + } + else + { + // Clear out the old fragment + DisconnectTextureFromFragment(currentFragment); + } + } + + if ( f == INVALID_FRAGMENT_HANDLE ) + { + return false; + } + + // Disconnect existing texture from this fragment (if necessary) + DisconnectTextureFromFragment(f); + + // Connnect new texture to this fragment + info.m_Fragment = f; + m_Fragments[f].m_Texture = h; + + // Move to the back of the LRU + MarkUsed( f ); + + // Indicate we need a redraw + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the size of a particular fragment +//----------------------------------------------------------------------------- +int CTextureAllocator::GetFragmentPower( FragmentHandle_t f ) const +{ + return m_Blocks[m_Fragments[f].m_Block].m_FragmentPower; +} + + +//----------------------------------------------------------------------------- +// Advance frame... +//----------------------------------------------------------------------------- +void CTextureAllocator::AdvanceFrame() +{ + // Be sure that this is called as infrequently as possible (i.e. once per frame, + // NOT once per view) to prevent cache thrash when rendering multiple views in a single frame + m_CurrentFrame++; +} + + +//----------------------------------------------------------------------------- +// Prepare to render into texture... +//----------------------------------------------------------------------------- +ITexture* CTextureAllocator::GetTexture() +{ + return m_TexturePage; +} + +//----------------------------------------------------------------------------- +// Get at the total texture size. +//----------------------------------------------------------------------------- +void CTextureAllocator::GetTotalTextureSize( int& w, int& h ) +{ + w = h = TEXTURE_PAGE_SIZE; +} + + +//----------------------------------------------------------------------------- +// Returns the rectangle the texture lives in.. +//----------------------------------------------------------------------------- +void CTextureAllocator::GetTextureRect(TextureHandle_t handle, int& x, int& y, int& w, int& h ) +{ + TextureInfo_t& info = m_Textures[handle]; + Assert( info.m_Fragment != INVALID_FRAGMENT_HANDLE ); + + // Compute the position of the fragment in the page + FragmentInfo_t& fragment = m_Fragments[info.m_Fragment]; + int blockY = fragment.m_Block / BLOCKS_PER_ROW; + int blockX = fragment.m_Block - blockY * BLOCKS_PER_ROW; + + int fragmentSize = (1 << m_Blocks[fragment.m_Block].m_FragmentPower); + int fragmentsPerRow = BLOCK_SIZE / fragmentSize; + int fragmentY = fragment.m_Index / fragmentsPerRow; + int fragmentX = fragment.m_Index - fragmentY * fragmentsPerRow; + + x = blockX * BLOCK_SIZE + fragmentX * fragmentSize; + y = blockY * BLOCK_SIZE + fragmentY * fragmentSize; + w = fragmentSize; + h = fragmentSize; +} + + +//----------------------------------------------------------------------------- +// Defines how big of a shadow texture we should be making per caster... +//----------------------------------------------------------------------------- +#define TEXEL_SIZE_PER_CASTER_SIZE 2.0f +#define MAX_FALLOFF_AMOUNT 240 +#define MAX_CLIP_PLANE_COUNT 4 +#define SHADOW_CULL_TOLERANCE 0.5f + +static ConVar r_shadows( "r_shadows", "1" ); // hook into engine's cvars.. +static ConVar r_shadowmaxrendered("r_shadowmaxrendered", "32"); +static ConVar r_shadows_gamecontrol( "r_shadows_gamecontrol", "-1", FCVAR_CHEAT ); // hook into engine's cvars.. + +//----------------------------------------------------------------------------- +// The class responsible for dealing with shadows on the client side +// Oh, and let's take a moment and notice how happy Robin and John must be +// owing to the lack of space between this lovely comment and the class name =) +//----------------------------------------------------------------------------- +class CClientShadowMgr : public IClientShadowMgr +{ +public: + CClientShadowMgr(); + + virtual char const *Name() { return "CCLientShadowMgr"; } + + // Inherited from IClientShadowMgr + virtual bool Init(); + virtual void PostInit() {} + virtual void Shutdown(); + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity() {} + virtual void LevelShutdownPreEntity() {} + virtual void LevelShutdownPostEntity(); + + virtual bool IsPerFrame() { return true; } + + virtual void PreRender(); + virtual void Update( float frametime ) { } + virtual void PostRender() {} + + virtual void OnSave() {} + virtual void OnRestore() {} + virtual void SafeRemoveIfDesired() {} + + virtual ClientShadowHandle_t CreateShadow( ClientEntityHandle_t entity, int flags ); + virtual void DestroyShadow( ClientShadowHandle_t handle ); + + // Create flashlight (projected texture light source) + virtual ClientShadowHandle_t CreateFlashlight( const FlashlightState_t &lightState ); + virtual void UpdateFlashlightState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &lightState ); + virtual void DestroyFlashlight( ClientShadowHandle_t shadowHandle ); + + // Update a shadow + virtual void UpdateProjectedTexture( ClientShadowHandle_t handle, bool force ); + + void ComputeBoundingSphere( IClientRenderable* pRenderable, Vector& origin, float& radius ); + + virtual void AddToDirtyShadowList( ClientShadowHandle_t handle, bool bForce ); + virtual void AddToDirtyShadowList( IClientRenderable *pRenderable, bool force ); + + // Marks the render-to-texture shadow as needing to be re-rendered + virtual void MarkRenderToTextureShadowDirty( ClientShadowHandle_t handle ); + + // deals with shadows being added to shadow receivers + void AddShadowToReceiver( ClientShadowHandle_t handle, + IClientRenderable* pRenderable, ShadowReceiver_t type ); + + // deals with shadows being added to shadow receivers + void RemoveAllShadowsFromReceiver( IClientRenderable* pRenderable, ShadowReceiver_t type ); + + // Re-renders all shadow textures for shadow casters that lie in the leaf list + void ComputeShadowTextures( const CViewSetup &view, int leafCount, LeafIndex_t* pLeafList ); + + // Kicks off rendering into shadow depth maps (if any) + void ComputeShadowDepthTextures( const CViewSetup &view ); + + // Frees shadow depth textures for use in subsequent view/frame + void FreeShadowDepthTextures(); + + // Returns the shadow texture + ITexture* GetShadowTexture( unsigned short h ); + + // Returns shadow information + const ShadowInfo_t& GetShadowInfo( ClientShadowHandle_t h ); + + // Renders the shadow texture to screen... + void RenderShadowTexture( int w, int h ); + + // Sets the shadow direction + virtual void SetShadowDirection( const Vector& dir ); + const Vector &GetShadowDirection() const; + + // Sets the shadow color + virtual void SetShadowColor( unsigned char r, unsigned char g, unsigned char b ); + void GetShadowColor( unsigned char *r, unsigned char *g, unsigned char *b ) const; + + // Sets the shadow distance + virtual void SetShadowDistance( float flMaxDistance ); + float GetShadowDistance( ) const; + + // Sets the screen area at which blobby shadows are always used + virtual void SetShadowBlobbyCutoffArea( float flMinArea ); + float GetBlobbyCutoffArea( ) const; + + // Set the darkness falloff bias + virtual void SetFalloffBias( ClientShadowHandle_t handle, unsigned char ucBias ); + + void RestoreRenderState(); + + // Computes a rough bounding box encompassing the volume of the shadow + void ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ); + + bool WillParentRenderBlobbyShadow( IClientRenderable *pRenderable ); + + // Are we the child of a shadow with render-to-texture? + bool ShouldUseParentShadow( IClientRenderable *pRenderable ); + + void SetShadowsDisabled( bool bDisabled ) + { + r_shadows_gamecontrol.SetValue( bDisabled != 1 ); + } + +private: + enum + { + SHADOW_FLAGS_TEXTURE_DIRTY = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 1), + SHADOW_FLAGS_BRUSH_MODEL = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 2), + SHADOW_FLAGS_USING_LOD_SHADOW = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 3), + SHADOW_FLAGS_LIGHT_WORLD = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 4), + }; + + struct ClientShadow_t + { + ClientEntityHandle_t m_Entity; + ShadowHandle_t m_ShadowHandle; + ClientLeafShadowHandle_t m_ClientLeafShadowHandle; + unsigned short m_Flags; + VMatrix m_WorldToShadow; + Vector2D m_WorldSize; + Vector m_LastOrigin; + QAngle m_LastAngles; + TextureHandle_t m_ShadowTexture; + CTextureReference m_ShadowDepthTexture; + int m_nRenderFrame; + EHANDLE m_hTargetEntity; + }; + +private: + // Shadow update functions + void UpdateStudioShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ); + void UpdateBrushShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ); + void UpdateShadow( ClientShadowHandle_t handle, bool force ); + + // Gets the entity whose shadow this shadow will render into + IClientRenderable *GetParentShadowEntity( ClientShadowHandle_t handle ); + + // Adds the child bounds to the bounding box + void AddChildBounds( matrix3x4_t &matWorldToBBox, IClientRenderable* pParent, Vector &vecMins, Vector &vecMaxs ); + + // Compute a bounds for the entity + children + void ComputeHierarchicalBounds( IClientRenderable *pRenderable, Vector &vecMins, Vector &vecMaxs ); + + // Builds matrices transforming from world space to shadow space + void BuildGeneralWorldToShadowMatrix( VMatrix& matWorldToShadow, + const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec ); + + void BuildWorldToShadowMatrix( VMatrix& matWorldToShadow, const Vector& origin, const Quaternion& quatOrientation ); + + void BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ); + + // Update a shadow + void UpdateProjectedTextureInternal( ClientShadowHandle_t handle, bool force ); + + // Compute the shadow origin and attenuation start distance + float ComputeLocalShadowOrigin( IClientRenderable* pRenderable, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir, float backupFactor, Vector& origin ); + + // Remove a shadow from the dirty list + void RemoveShadowFromDirtyList( ClientShadowHandle_t handle ); + + // NOTE: this will ONLY return SHADOWS_NONE, SHADOWS_SIMPLE, or SHADOW_RENDER_TO_TEXTURE. + ShadowType_t GetActualShadowCastType( ClientShadowHandle_t handle ) const; + ShadowType_t GetActualShadowCastType( IClientRenderable *pRenderable ) const; + + // Builds a simple blobby shadow + void BuildOrthoShadow( IClientRenderable* pRenderable, ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs); + + // Builds a more complex shadow... + void BuildRenderToTextureShadow( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs ); + + // Build a projected-texture flashlight + void BuildFlashlight( ClientShadowHandle_t handle ); + + // Does all the lovely stuff we need to do to have render-to-texture shadows + void SetupRenderToTextureShadow( ClientShadowHandle_t h ); + void CleanUpRenderToTextureShadow( ClientShadowHandle_t h ); + + // Compute the extra shadow planes + void ComputeExtraClipPlanes( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector* vec, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir ); + + // Set extra clip planes related to shadows... + void ClearExtraClipPlanes( ClientShadowHandle_t h ); + void AddExtraClipPlane( ClientShadowHandle_t h, const Vector& normal, float dist ); + + // Cull if the origin is on the wrong side of a shadow clip plane.... + bool CullReceiver( ClientShadowHandle_t handle, IClientRenderable* pRenderable, IClientRenderable* pSourceRenderable ); + + bool ComputeSeparatingPlane( IClientRenderable* pRend1, IClientRenderable* pRend2, cplane_t* pPlane ); + + // Causes all shadows to be re-updated + void UpdateAllShadows(); + + // One of these gets called with every shadow that potentially will need to re-render + bool DrawRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ); + void DrawRenderToTextureShadowLOD( unsigned short clientShadowHandle ); + + // Draws all children shadows into our own + bool DrawShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild = false ); + + // Setup stage for threading + bool BuildSetupListForRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ); + bool BuildSetupShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild = false ); + + // Computes + sets the render-to-texture texcoords + void SetRenderToTextureShadowTexCoords( ShadowHandle_t handle, int x, int y, int w, int h ); + + // Visualization.... + void DrawRenderToTextureDebugInfo( IClientRenderable* pRenderable, const Vector& mins, const Vector& maxs ); + + // Advance frame + void AdvanceFrame(); + + // Returns renderable-specific shadow info + float GetShadowDistance( IClientRenderable *pRenderable ) const; + const Vector &GetShadowDirection( IClientRenderable *pRenderable ) const; + + // Initialize, shutdown render-to-texture shadows + void InitDepthTextureShadows(); + void ShutdownDepthTextureShadows(); + + // Initialize, shutdown render-to-texture shadows + void InitRenderToTextureShadows(); + void ShutdownRenderToTextureShadows(); + + static bool ShadowHandleCompareFunc( const ClientShadowHandle_t& lhs, const ClientShadowHandle_t& rhs ) + { + return lhs < rhs; + } + + ClientShadowHandle_t CreateProjectedTexture( ClientEntityHandle_t entity, int flags ); + + // Lock down the usage of a shadow depth texture...must be unlocked use on subsequent views / frames + bool LockShadowDepthTexture( CTextureReference *shadowDepthTexture ); + void UnlockAllShadowDepthTextures(); + + // Set and clear flashlight target renderable + void SetFlashlightTarget( ClientShadowHandle_t shadowHandle, EHANDLE targetEntity ); + + // Set flashlight light world flag + void SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld ); + + bool IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IClientRenderable *pRenderable ); + + // Builds a list of active shadows requiring shadow depth renders + int BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows ); + + // Sets the view's active flashlight render state + void SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights ); + +private: + Vector m_SimpleShadowDir; + color32 m_AmbientLightColor; + CMaterialReference m_SimpleShadow; + CMaterialReference m_RenderShadow; + CMaterialReference m_RenderModelShadow; + CTextureReference m_DummyColorTexture; + CUtlLinkedList< ClientShadow_t, ClientShadowHandle_t > m_Shadows; + CTextureAllocator m_ShadowAllocator; + + bool m_RenderToTextureActive; + bool m_bRenderTargetNeedsClear; + bool m_bUpdatingDirtyShadows; + bool m_bThreaded; + float m_flShadowCastDist; + float m_flMinShadowArea; + CUtlRBTree< ClientShadowHandle_t, unsigned short > m_DirtyShadows; + CUtlVector< ClientShadowHandle_t > m_TransparentShadows; + + // These members maintain current state of depth texturing (size and global active state) + // If either changes in a frame, PreRender() will catch it and do the appropriate allocation, deallocation or reallocation + bool m_bDepthTextureActive; + int m_nDepthTextureResolution; // Assume square (height == width) + + CUtlVector< CTextureReference > m_DepthTextureCache; + CUtlVector< bool > m_DepthTextureCacheLocks; + int m_nMaxDepthTextureShadows; + + friend class CVisibleShadowList; + friend class CVisibleShadowFrustumList; +}; + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +static CClientShadowMgr s_ClientShadowMgr; +IClientShadowMgr* g_pClientShadowMgr = &s_ClientShadowMgr; + + +//----------------------------------------------------------------------------- +// Builds a list of potential shadows that lie within our PVS + view frustum +//----------------------------------------------------------------------------- +struct VisibleShadowInfo_t +{ + ClientShadowHandle_t m_hShadow; + float m_flArea; + Vector m_vecAbsCenter; +}; + +class CVisibleShadowList : public IClientLeafShadowEnum +{ +public: + + CVisibleShadowList(); + int FindShadows( const CViewSetup *pView, int nLeafCount, LeafIndex_t *pLeafList ); + int GetVisibleShadowCount() const; + + const VisibleShadowInfo_t &GetVisibleShadow( int i ) const; + +private: + void EnumShadow( unsigned short clientShadowHandle ); + float ComputeScreenArea( const Vector &vecCenter, float r ) const; + void PrioritySort(); + + CUtlVector m_ShadowsInView; + CUtlVector m_PriorityIndex; +}; + + +//----------------------------------------------------------------------------- +// Singleton instances of shadow and shadow frustum lists +//----------------------------------------------------------------------------- +static CVisibleShadowList s_VisibleShadowList; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static CUtlVector s_NPCShadowBoneSetups; +static CUtlVector s_NonNPCShadowBoneSetups; + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Constructor and Accessors +//----------------------------------------------------------------------------- +CVisibleShadowList::CVisibleShadowList() : m_ShadowsInView( 0, 64 ), m_PriorityIndex( 0, 64 ) +{ +} + +int CVisibleShadowList::GetVisibleShadowCount() const +{ + return m_ShadowsInView.Count(); +} + +const VisibleShadowInfo_t &CVisibleShadowList::GetVisibleShadow( int i ) const +{ + return m_ShadowsInView[m_PriorityIndex[i]]; +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Computes approximate screen area of the shadow +//----------------------------------------------------------------------------- +float CVisibleShadowList::ComputeScreenArea( const Vector &vecCenter, float r ) const +{ + CMatRenderContextPtr pRenderContext( materials ); + float flScreenDiameter = pRenderContext->ComputePixelDiameterOfSphere( vecCenter, r ); + return flScreenDiameter * flScreenDiameter; +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Visits every shadow in the list of leaves +//----------------------------------------------------------------------------- +void CVisibleShadowList::EnumShadow( unsigned short clientShadowHandle ) +{ + CClientShadowMgr::ClientShadow_t& shadow = s_ClientShadowMgr.m_Shadows[clientShadowHandle]; + + // Don't bother if we rendered it this frame, no matter which view it was rendered for + if ( shadow.m_nRenderFrame == gpGlobals->framecount ) + return; + + // We don't need to bother with it if it's not render-to-texture + if ( s_ClientShadowMgr.GetActualShadowCastType( clientShadowHandle ) != SHADOWS_RENDER_TO_TEXTURE ) + return; + + // Don't bother with it if the shadow is totally transparent + const ShadowInfo_t &shadowInfo = shadowmgr->GetInfo( shadow.m_ShadowHandle ); + if ( shadowInfo.m_FalloffBias == 255 ) + return; + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + Assert( pRenderable ); + + // Don't bother with children of hierarchy; they will be drawn with their parents + if ( s_ClientShadowMgr.ShouldUseParentShadow( pRenderable ) || s_ClientShadowMgr.WillParentRenderBlobbyShadow( pRenderable ) ) + return; + + // Compute a sphere surrounding the shadow + // FIXME: This doesn't account for children of hierarchy... too bad! + Vector vecAbsCenter; + float flRadius; + s_ClientShadowMgr.ComputeBoundingSphere( pRenderable, vecAbsCenter, flRadius ); + + // Compute a box surrounding the shadow + Vector vecAbsMins, vecAbsMaxs; + s_ClientShadowMgr.ComputeShadowBBox( pRenderable, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs ); + + // FIXME: Add distance check here? + + // Make sure it's in the frustum. If it isn't it's not interesting + if (engine->CullBox( vecAbsMins, vecAbsMaxs )) + return; + + int i = m_ShadowsInView.AddToTail( ); + VisibleShadowInfo_t &info = m_ShadowsInView[i]; + info.m_hShadow = clientShadowHandle; + m_ShadowsInView[i].m_flArea = ComputeScreenArea( vecAbsCenter, flRadius ); + + // Har, har. When water is rendering (or any multipass technique), + // we may well initially render from a viewpoint which doesn't include this shadow. + // That doesn't mean we shouldn't check it again though. Sucks that we need to compute + // the sphere + bbox multiply times though. + shadow.m_nRenderFrame = gpGlobals->framecount; +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Sort based on screen area/priority +//----------------------------------------------------------------------------- +void CVisibleShadowList::PrioritySort() +{ + int nCount = m_ShadowsInView.Count(); + m_PriorityIndex.EnsureCapacity( nCount ); + + m_PriorityIndex.RemoveAll(); + + int i, j; + for ( i = 0; i < nCount; ++i ) + { + m_PriorityIndex.AddToTail(i); + } + + for ( i = 0; i < nCount - 1; ++i ) + { + int nLargestInd = i; + float flLargestArea = m_ShadowsInView[m_PriorityIndex[i]].m_flArea; + for ( j = i + 1; j < nCount; ++j ) + { + int nIndex = m_PriorityIndex[j]; + if ( flLargestArea < m_ShadowsInView[nIndex].m_flArea ) + { + nLargestInd = j; + flLargestArea = m_ShadowsInView[nIndex].m_flArea; + } + } + swap( m_PriorityIndex[i], m_PriorityIndex[nLargestInd] ); + } +} + + +//----------------------------------------------------------------------------- +// CVisibleShadowList - Main entry point for finding shadows in the leaf list +//----------------------------------------------------------------------------- +int CVisibleShadowList::FindShadows( const CViewSetup *pView, int nLeafCount, LeafIndex_t *pLeafList ) +{ + VPROF_BUDGET( "CVisibleShadowList::FindShadows", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + m_ShadowsInView.RemoveAll(); + ClientLeafSystem()->EnumerateShadowsInLeaves( nLeafCount, pLeafList, this ); + int nCount = m_ShadowsInView.Count(); + if (nCount != 0) + { + // Sort based on screen area/priority + PrioritySort(); + } + return nCount; +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CClientShadowMgr::CClientShadowMgr() : + m_DirtyShadows( 0, 0, ShadowHandleCompareFunc ), + m_RenderToTextureActive( false ), + m_bDepthTextureActive( false ) +{ + m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); + m_bThreaded = false; +} + + +//----------------------------------------------------------------------------- +// Changes the shadow direction... +//----------------------------------------------------------------------------- +CON_COMMAND_F( r_shadowdir, "Set shadow direction", FCVAR_CHEAT ) +{ + Vector dir; + if ( args.ArgC() == 1 ) + { + Vector dir = s_ClientShadowMgr.GetShadowDirection(); + Msg( "%.2f %.2f %.2f\n", dir.x, dir.y, dir.z ); + return; + } + + if ( args.ArgC() == 4 ) + { + dir.x = atof( args[1] ); + dir.y = atof( args[2] ); + dir.z = atof( args[3] ); + s_ClientShadowMgr.SetShadowDirection(dir); + } +} + +CON_COMMAND_F( r_shadowangles, "Set shadow angles", FCVAR_CHEAT ) +{ + Vector dir; + QAngle angles; + if (args.ArgC() == 1) + { + Vector dir = s_ClientShadowMgr.GetShadowDirection(); + QAngle angles; + VectorAngles( dir, angles ); + Msg( "%.2f %.2f %.2f\n", angles.x, angles.y, angles.z ); + return; + } + + if (args.ArgC() == 4) + { + angles.x = atof( args[1] ); + angles.y = atof( args[2] ); + angles.z = atof( args[3] ); + AngleVectors( angles, &dir ); + s_ClientShadowMgr.SetShadowDirection(dir); + } +} + +CON_COMMAND_F( r_shadowcolor, "Set shadow color", FCVAR_CHEAT ) +{ + if (args.ArgC() == 1) + { + unsigned char r, g, b; + s_ClientShadowMgr.GetShadowColor( &r, &g, &b ); + Msg( "Shadow color %d %d %d\n", r, g, b ); + return; + } + + if (args.ArgC() == 4) + { + int r = atoi( args[1] ); + int g = atoi( args[2] ); + int b = atoi( args[3] ); + s_ClientShadowMgr.SetShadowColor(r, g, b); + } +} + +CON_COMMAND_F( r_shadowdist, "Set shadow distance", FCVAR_CHEAT ) +{ + if (args.ArgC() == 1) + { + float flDist = s_ClientShadowMgr.GetShadowDistance( ); + Msg( "Shadow distance %.2f\n", flDist ); + return; + } + + if (args.ArgC() == 2) + { + float flDistance = atof( args[1] ); + s_ClientShadowMgr.SetShadowDistance( flDistance ); + } +} + +CON_COMMAND_F( r_shadowblobbycutoff, "some shadow stuff", FCVAR_CHEAT ) +{ + if (args.ArgC() == 1) + { + float flArea = s_ClientShadowMgr.GetBlobbyCutoffArea( ); + Msg( "Cutoff area %.2f\n", flArea ); + return; + } + + if (args.ArgC() == 2) + { + float flArea = atof( args[1] ); + s_ClientShadowMgr.SetShadowBlobbyCutoffArea( flArea ); + } +} + +static void ShadowRestoreFunc( int nChangeFlags ) +{ + s_ClientShadowMgr.RestoreRenderState(); +} + +//----------------------------------------------------------------------------- +// Initialization, shutdown +//----------------------------------------------------------------------------- +bool CClientShadowMgr::Init() +{ + m_bRenderTargetNeedsClear = false; + m_SimpleShadow.Init( "decals/simpleshadow", TEXTURE_GROUP_DECAL ); + + Vector dir( 0.1, 0.1, -1 ); + SetShadowDirection(dir); + SetShadowDistance( 50 ); + + SetShadowBlobbyCutoffArea( 0.005 ); + + bool bTools = CommandLine()->CheckParm( "-tools" ) != NULL; + m_nMaxDepthTextureShadows = bTools ? 4 : 1; // Just one shadow depth texture in games, more in tools + + bool bLowEnd = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ); + + if ( !bLowEnd && r_shadowrendertotexture.GetBool() ) + { + InitRenderToTextureShadows(); + } + + // If someone turned shadow depth mapping on but we can't do it, force it off + if ( r_flashlightdepthtexture.GetBool() && !materials->SupportsShadowDepthTextures() ) + { + r_flashlightdepthtexture.SetValue( 0 ); + ShutdownDepthTextureShadows(); + } + + if ( !bLowEnd && r_flashlightdepthtexture.GetBool() ) + { + InitDepthTextureShadows(); + } + + materials->AddRestoreFunc( ShadowRestoreFunc ); + + return true; +} + +void CClientShadowMgr::Shutdown() +{ + m_SimpleShadow.Shutdown(); + m_Shadows.RemoveAll(); + ShutdownRenderToTextureShadows(); + + ShutdownDepthTextureShadows(); + + materials->RemoveRestoreFunc( ShadowRestoreFunc ); +} + + +//----------------------------------------------------------------------------- +// Initialize, shutdown depth-texture shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::InitDepthTextureShadows() +{ + VPROF_BUDGET( "CClientShadowMgr::InitDepthTextureShadows", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if( !m_bDepthTextureActive ) + { + m_bDepthTextureActive = true; + + ImageFormat dstFormat = materials->GetShadowDepthTextureFormat(); // Vendor-dependent depth texture format +#if !defined( _X360 ) + ImageFormat nullFormat = materials->GetNullTextureFormat(); // Vendor-dependent null texture format (takes as little memory as possible) +#endif + materials->BeginRenderTargetAllocation(); + +#if defined( _X360 ) + // For the 360, we'll be rendering depth directly into the dummy depth and Resolve()ing to the depth texture. + // only need the dummy surface, don't care about color results + m_DummyColorTexture.InitRenderTargetTexture( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, IMAGE_FORMAT_BGR565, MATERIAL_RT_DEPTH_SHARED, false, "_rt_ShadowDummy" ); + m_DummyColorTexture.InitRenderTargetSurface( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), IMAGE_FORMAT_BGR565, true ); +#else + m_DummyColorTexture.InitRenderTarget( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" ); +#endif + + // Create some number of depth-stencil textures + m_DepthTextureCache.Purge(); + m_DepthTextureCacheLocks.Purge(); + for( int i=0; i < m_nMaxDepthTextureShadows; i++ ) + { + CTextureReference depthTex; // Depth-stencil surface + bool bFalse = false; + + char strRTName[64]; + sprintf( strRTName, "_rt_ShadowDepthTexture_%d", i ); + +#if defined( _X360 ) + // create a render target to use as a resolve target to get the shared depth buffer + // surface is effectively never used + depthTex.InitRenderTargetTexture( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); + depthTex.InitRenderTargetSurface( 1, 1, dstFormat, false ); +#else + depthTex.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); +#endif + + if ( i == 0 ) + { + // Shadow may be resized during allocation (due to resolution constraints etc) + m_nDepthTextureResolution = depthTex->GetActualWidth(); + r_flashlightdepthres.SetValue( m_nDepthTextureResolution ); + } + + m_DepthTextureCache.AddToTail( depthTex ); + m_DepthTextureCacheLocks.AddToTail( bFalse ); + } + + materials->EndRenderTargetAllocation(); + } +} + +void CClientShadowMgr::ShutdownDepthTextureShadows() +{ + if( m_bDepthTextureActive ) + { + // Shut down the dummy texture + m_DummyColorTexture.Shutdown(); + + while( m_DepthTextureCache.Count() ) + { + m_DepthTextureCache[ m_DepthTextureCache.Count()-1 ].Shutdown(); + + m_DepthTextureCacheLocks.Remove( m_DepthTextureCache.Count()-1 ); + m_DepthTextureCache.Remove( m_DepthTextureCache.Count()-1 ); + } + + m_bDepthTextureActive = false; + } +} + +//----------------------------------------------------------------------------- +// Initialize, shutdown render-to-texture shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::InitRenderToTextureShadows() +{ + if ( !m_RenderToTextureActive ) + { + m_RenderToTextureActive = true; + m_RenderShadow.Init( "decals/rendershadow", TEXTURE_GROUP_DECAL ); + m_RenderModelShadow.Init( "decals/rendermodelshadow", TEXTURE_GROUP_DECAL ); + m_ShadowAllocator.Init(); + + m_ShadowAllocator.Reset(); + m_bRenderTargetNeedsClear = true; + + float fr = (float)m_AmbientLightColor.r / 255.0f; + float fg = (float)m_AmbientLightColor.g / 255.0f; + float fb = (float)m_AmbientLightColor.b / 255.0f; + m_RenderShadow->ColorModulate( fr, fg, fb ); + m_RenderModelShadow->ColorModulate( fr, fg, fb ); + + // Iterate over all existing textures and allocate shadow textures + for (ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + if ( shadow.m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE ) + { + SetupRenderToTextureShadow( i ); + MarkRenderToTextureShadowDirty( i ); + + // Switch the material to use render-to-texture shadows + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_RenderShadow, m_RenderModelShadow, (void*)i ); + } + } + } +} + +void CClientShadowMgr::ShutdownRenderToTextureShadows() +{ + if (m_RenderToTextureActive) + { + // Iterate over all existing textures and deallocate shadow textures + for (ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + CleanUpRenderToTextureShadow( i ); + + // Switch the material to use blobby shadows + ClientShadow_t& shadow = m_Shadows[i]; + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_SimpleShadow, m_SimpleShadow, (void*)CLIENTSHADOW_INVALID_HANDLE ); + shadowmgr->SetShadowTexCoord( shadow.m_ShadowHandle, 0, 0, 1, 1 ); + ClearExtraClipPlanes( i ); + } + + m_RenderShadow.Shutdown(); + m_RenderModelShadow.Shutdown(); + + m_ShadowAllocator.DeallocateAllTextures(); + m_ShadowAllocator.Shutdown(); + + // Cause the render target to go away + materials->UncacheUnusedMaterials(); + + m_RenderToTextureActive = false; + } +} + + +//----------------------------------------------------------------------------- +// Sets the shadow color +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowColor( unsigned char r, unsigned char g, unsigned char b ) +{ + float fr = (float)r / 255.0f; + float fg = (float)g / 255.0f; + float fb = (float)b / 255.0f; + + // Hook the shadow color into the shadow materials + m_SimpleShadow->ColorModulate( fr, fg, fb ); + + if (m_RenderToTextureActive) + { + m_RenderShadow->ColorModulate( fr, fg, fb ); + m_RenderModelShadow->ColorModulate( fr, fg, fb ); + } + + m_AmbientLightColor.r = r; + m_AmbientLightColor.g = g; + m_AmbientLightColor.b = b; +} + +void CClientShadowMgr::GetShadowColor( unsigned char *r, unsigned char *g, unsigned char *b ) const +{ + *r = m_AmbientLightColor.r; + *g = m_AmbientLightColor.g; + *b = m_AmbientLightColor.b; +} + + +//----------------------------------------------------------------------------- +// Level init... get the shadow color +//----------------------------------------------------------------------------- +void CClientShadowMgr::LevelInitPreEntity() +{ + m_bUpdatingDirtyShadows = false; + + Vector ambientColor; + engine->GetAmbientLightColor( ambientColor ); + ambientColor *= 3; + ambientColor += Vector( 0.3f, 0.3f, 0.3f ); + + unsigned char r = ambientColor[0] > 1.0 ? 255 : 255 * ambientColor[0]; + unsigned char g = ambientColor[1] > 1.0 ? 255 : 255 * ambientColor[1]; + unsigned char b = ambientColor[2] > 1.0 ? 255 : 255 * ambientColor[2]; + + SetShadowColor(r, g, b); + + // Set up the texture allocator + if ( m_RenderToTextureActive ) + { + m_ShadowAllocator.Reset(); + m_bRenderTargetNeedsClear = true; + } +} + + +//----------------------------------------------------------------------------- +// Clean up all shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::LevelShutdownPostEntity() +{ + // All shadows *should* have been cleaned up when the entities went away + // but, just in case.... + Assert( m_Shadows.Count() == 0 ); + + ClientShadowHandle_t h = m_Shadows.Head(); + while (h != CLIENTSHADOW_INVALID_HANDLE) + { + ClientShadowHandle_t next = m_Shadows.Next(h); + DestroyShadow( h ); + h = next; + } + + // Deallocate all textures + if (m_RenderToTextureActive) + { + m_ShadowAllocator.DeallocateAllTextures(); + } + + r_shadows_gamecontrol.SetValue( -1 ); +} + + +//----------------------------------------------------------------------------- +// Deals with alt-tab +//----------------------------------------------------------------------------- +void CClientShadowMgr::RestoreRenderState() +{ + // Mark all shadows dirty; they need to regenerate their state + ClientShadowHandle_t h; + for ( h = m_Shadows.Head(); h != m_Shadows.InvalidIndex(); h = m_Shadows.Next(h) ) + { + m_Shadows[h].m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + } + + SetShadowColor( m_AmbientLightColor.r, m_AmbientLightColor.g, m_AmbientLightColor.b ); + m_bRenderTargetNeedsClear = true; +} + + +//----------------------------------------------------------------------------- +// Does all the lovely stuff we need to do to have render-to-texture shadows +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetupRenderToTextureShadow( ClientShadowHandle_t h ) +{ + // First, compute how much texture memory we want to use. + ClientShadow_t& shadow = m_Shadows[h]; + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( !pRenderable ) + return; + + Vector mins, maxs; + pRenderable->GetShadowRenderBounds( mins, maxs, GetActualShadowCastType( h ) ); + + // Compute the maximum dimension + Vector size; + VectorSubtract( maxs, mins, size ); + float maxSize = max( size.x, size.y ); + maxSize = max( maxSize, size.z ); + + // Figure out the texture size + // For now, we're going to assume a fixed number of shadow texels + // per shadow-caster size; add in some extra space at the boundary. + int texelCount = TEXEL_SIZE_PER_CASTER_SIZE * maxSize; + + // Pick the first power of 2 larger... + int textureSize = 1; + while (textureSize < texelCount) + { + textureSize <<= 1; + } + + shadow.m_ShadowTexture = m_ShadowAllocator.AllocateTexture( textureSize, textureSize ); +} + + +void CClientShadowMgr::CleanUpRenderToTextureShadow( ClientShadowHandle_t h ) +{ + ClientShadow_t& shadow = m_Shadows[h]; + if (m_RenderToTextureActive && (shadow.m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE)) + { + m_ShadowAllocator.DeallocateTexture( shadow.m_ShadowTexture ); + shadow.m_ShadowTexture = INVALID_TEXTURE_HANDLE; + } +} + + +//----------------------------------------------------------------------------- +// Causes all shadows to be re-updated +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateAllShadows() +{ + for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + + // Don't bother with flashlights + if ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) != 0 ) + continue; + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( !pRenderable ) + continue; + + Assert( pRenderable->GetShadowHandle() == i ); + AddToDirtyShadowList( pRenderable, true ); + } +} + + +//----------------------------------------------------------------------------- +// Sets the shadow direction +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowDirection( const Vector& dir ) +{ + VectorCopy( dir, m_SimpleShadowDir ); + VectorNormalize( m_SimpleShadowDir ); + + if ( m_RenderToTextureActive ) + { + UpdateAllShadows(); + } +} + +const Vector &CClientShadowMgr::GetShadowDirection() const +{ + // This will cause blobby shadows to always project straight down + static Vector s_vecDown( 0, 0, -1 ); + if ( !m_RenderToTextureActive ) + return s_vecDown; + + return m_SimpleShadowDir; +} + + +//----------------------------------------------------------------------------- +// Gets shadow information for a particular renderable +//----------------------------------------------------------------------------- +float CClientShadowMgr::GetShadowDistance( IClientRenderable *pRenderable ) const +{ + float flDist = m_flShadowCastDist; + + // Allow the renderable to override the default + pRenderable->GetShadowCastDistance( &flDist, GetActualShadowCastType( pRenderable ) ); + + return flDist; +} + +const Vector &CClientShadowMgr::GetShadowDirection( IClientRenderable *pRenderable ) const +{ + Vector &vecResult = AllocTempVector(); + vecResult = GetShadowDirection(); + + // Allow the renderable to override the default + pRenderable->GetShadowCastDirection( &vecResult, GetActualShadowCastType( pRenderable ) ); + + return vecResult; +} + + +//----------------------------------------------------------------------------- +// Sets the shadow distance +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowDistance( float flMaxDistance ) +{ + m_flShadowCastDist = flMaxDistance; + UpdateAllShadows(); +} + +float CClientShadowMgr::GetShadowDistance( ) const +{ + return m_flShadowCastDist; +} + + +//----------------------------------------------------------------------------- +// Sets the screen area at which blobby shadows are always used +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetShadowBlobbyCutoffArea( float flMinArea ) +{ + m_flMinShadowArea = flMinArea; +} + +float CClientShadowMgr::GetBlobbyCutoffArea( ) const +{ + return m_flMinShadowArea; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetFalloffBias( ClientShadowHandle_t handle, unsigned char ucBias ) +{ + shadowmgr->SetFalloffBias( m_Shadows[handle].m_ShadowHandle, ucBias ); +} + +//----------------------------------------------------------------------------- +// Returns the shadow texture +//----------------------------------------------------------------------------- +ITexture* CClientShadowMgr::GetShadowTexture( unsigned short h ) +{ + return m_ShadowAllocator.GetTexture(); +} + + +//----------------------------------------------------------------------------- +// Returns information needed by the model proxy +//----------------------------------------------------------------------------- +const ShadowInfo_t& CClientShadowMgr::GetShadowInfo( ClientShadowHandle_t h ) +{ + return shadowmgr->GetInfo( m_Shadows[h].m_ShadowHandle ); +} + + +//----------------------------------------------------------------------------- +// Renders the shadow texture to screen... +//----------------------------------------------------------------------------- +void CClientShadowMgr::RenderShadowTexture( int w, int h ) +{ + if (m_RenderToTextureActive) + { + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( m_RenderShadow ); + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( w, 0.0f, 0.0f ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( w, h, 0.0f ); + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, h, 0.0f ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + } +} + + +//----------------------------------------------------------------------------- +// Create/destroy a shadow +//----------------------------------------------------------------------------- +ClientShadowHandle_t CClientShadowMgr::CreateProjectedTexture( ClientEntityHandle_t entity, int flags ) +{ + // We need to know if it's a brush model for shadows + if( !( flags & SHADOW_FLAGS_FLASHLIGHT ) ) + { + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( entity ); + int modelType = modelinfo->GetModelType( pRenderable->GetModel() ); + if (modelType == mod_brush) + { + flags |= SHADOW_FLAGS_BRUSH_MODEL; + } + } + + ClientShadowHandle_t h = m_Shadows.AddToTail(); + ClientShadow_t& shadow = m_Shadows[h]; + shadow.m_Entity = entity; + shadow.m_ClientLeafShadowHandle = ClientLeafSystem()->AddShadow( h, flags ); + shadow.m_Flags = flags; + shadow.m_nRenderFrame = -1; + shadow.m_LastOrigin.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + shadow.m_LastAngles.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + Assert( ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) != + ( ( shadow.m_Flags & SHADOW_FLAGS_SHADOW ) == 0 ) ); + + // Set up the flags.... + IMaterial* pShadowMaterial = m_SimpleShadow; + IMaterial* pShadowModelMaterial = m_SimpleShadow; + void* pShadowProxyData = (void*)CLIENTSHADOW_INVALID_HANDLE; + + if ( m_RenderToTextureActive && (flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE) ) + { + SetupRenderToTextureShadow(h); + + pShadowMaterial = m_RenderShadow; + pShadowModelMaterial = m_RenderModelShadow; + pShadowProxyData = (void*)h; + } + + if( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) + { + pShadowMaterial = m_RenderShadow; + pShadowModelMaterial = m_RenderModelShadow; + pShadowProxyData = (void*)h; + } + + int createShadowFlags; + if( flags & SHADOW_FLAGS_FLASHLIGHT ) + { + // don't use SHADOW_CACHE_VERTS with projective lightsources since we expect that they will change every frame. + // FIXME: might want to make it cache optionally if it's an entity light that is static. + createShadowFlags = SHADOW_FLASHLIGHT; + } + else + { + createShadowFlags = SHADOW_CACHE_VERTS; + } + shadow.m_ShadowHandle = shadowmgr->CreateShadowEx( pShadowMaterial, pShadowModelMaterial, pShadowProxyData, createShadowFlags ); + return h; +} + +ClientShadowHandle_t CClientShadowMgr::CreateFlashlight( const FlashlightState_t &lightState ) +{ + // We don't really need a model entity handle for a projective light source, so use an invalid one. + static ClientEntityHandle_t invalidHandle = INVALID_CLIENTENTITY_HANDLE; + + int shadowFlags = SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_LIGHT_WORLD; + if( lightState.m_bEnableShadows && r_flashlightdepthtexture.GetBool() ) + { + shadowFlags |= SHADOW_FLAGS_USE_DEPTH_TEXTURE; + } + + ClientShadowHandle_t shadowHandle = CreateProjectedTexture( invalidHandle, shadowFlags ); + + UpdateFlashlightState( shadowHandle, lightState ); + UpdateProjectedTexture( shadowHandle, true ); + return shadowHandle; +} + +ClientShadowHandle_t CClientShadowMgr::CreateShadow( ClientEntityHandle_t entity, int flags ) +{ + // We don't really need a model entity handle for a projective light source, so use an invalid one. + flags &= ~SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK; + flags |= SHADOW_FLAGS_SHADOW | SHADOW_FLAGS_TEXTURE_DIRTY; + ClientShadowHandle_t shadowHandle = CreateProjectedTexture( entity, flags ); + + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( entity ); + if ( pRenderable ) + { + Assert( !pRenderable->IsShadowDirty( ) ); + pRenderable->MarkShadowDirty( true ); + } + + // NOTE: We *have* to call the version that takes a shadow handle + // even if we have an entity because this entity hasn't set its shadow handle yet + AddToDirtyShadowList( shadowHandle, true ); + return shadowHandle; +} + + +//----------------------------------------------------------------------------- +// Updates the flashlight direction and re-computes surfaces it should lie on +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateFlashlightState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &flashlightState ) +{ + VPROF_BUDGET( "CClientShadowMgr::UpdateFlashlightState", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState ); + + shadowmgr->UpdateFlashlightState( m_Shadows[shadowHandle].m_ShadowHandle, flashlightState ); +} + +void CClientShadowMgr::DestroyFlashlight( ClientShadowHandle_t shadowHandle ) +{ + DestroyShadow( shadowHandle ); +} + +//----------------------------------------------------------------------------- +// Remove a shadow from the dirty list +//----------------------------------------------------------------------------- +void CClientShadowMgr::RemoveShadowFromDirtyList( ClientShadowHandle_t handle ) +{ + int idx = m_DirtyShadows.Find( handle ); + if ( idx != m_DirtyShadows.InvalidIndex() ) + { + // Clean up the shadow update bit. + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity ); + if ( pRenderable ) + { + pRenderable->MarkShadowDirty( false ); + } + m_DirtyShadows.RemoveAt( idx ); + } +} + + +//----------------------------------------------------------------------------- +// Remove a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::DestroyShadow( ClientShadowHandle_t handle ) +{ + Assert( m_Shadows.IsValidIndex(handle) ); + RemoveShadowFromDirtyList( handle ); + shadowmgr->DestroyShadow( m_Shadows[handle].m_ShadowHandle ); + ClientLeafSystem()->RemoveShadow( m_Shadows[handle].m_ClientLeafShadowHandle ); + CleanUpRenderToTextureShadow( handle ); + m_Shadows.Remove(handle); +} + + +//----------------------------------------------------------------------------- +// Build the worldtotexture matrix +//----------------------------------------------------------------------------- +void CClientShadowMgr::BuildGeneralWorldToShadowMatrix( VMatrix& matWorldToShadow, + const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec ) +{ + // We're assuming here that xvec + yvec aren't necessary perpendicular + + // The shadow->world matrix is pretty simple: + // Just stick the origin in the translation component + // and the vectors in the columns... + matWorldToShadow.SetBasisVectors( xvec, yvec, dir ); + matWorldToShadow.SetTranslation( origin ); + matWorldToShadow[3][0] = matWorldToShadow[3][1] = matWorldToShadow[3][2] = 0.0f; + matWorldToShadow[3][3] = 1.0f; + + // Now do a general inverse to get matWorldToShadow + MatrixInverseGeneral( matWorldToShadow, matWorldToShadow ); +} + +void CClientShadowMgr::BuildWorldToShadowMatrix( VMatrix& matWorldToShadow, const Vector& origin, const Quaternion& quatOrientation ) +{ + // The shadow->world matrix is pretty simple: + // Just stick the origin in the translation component + // and the vectors in the columns... + // The inverse of this transposes the rotational component + // and the translational component = - (rotation transpose) * origin + + matrix3x4_t matOrientation; + QuaternionMatrix( quatOrientation, matOrientation ); // Convert quat to matrix3x4 + PositionMatrix( vec3_origin, matOrientation ); // Zero out translation elements + + VMatrix matBasis( matOrientation ); // Convert matrix3x4 to VMatrix + + Vector vForward, vLeft, vUp; + matBasis.GetBasisVectors( vForward, vLeft, vUp ); + matBasis.SetForward( vLeft ); // Bizarre vector flip inherited from earlier code, WTF? + matBasis.SetLeft( vUp ); + matBasis.SetUp( vForward ); + matWorldToShadow = matBasis.Transpose(); // Transpose + + Vector translation; + Vector3DMultiply( matWorldToShadow, origin, translation ); + + translation *= -1.0f; + matWorldToShadow.SetTranslation( translation ); + + // The the bottom row. + matWorldToShadow[3][0] = matWorldToShadow[3][1] = matWorldToShadow[3][2] = 0.0f; + matWorldToShadow[3][3] = 1.0f; +} + +void CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ) +{ + VPROF_BUDGET( "CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + // Buildworld to shadow matrix, then perspective projection and concatenate + VMatrix matWorldToShadowView, matPerspective; + BuildWorldToShadowMatrix( matWorldToShadowView, flashlightState.m_vecLightOrigin, + flashlightState.m_quatOrientation ); + + MatrixBuildPerspective( matPerspective, flashlightState.m_fHorizontalFOVDegrees, + flashlightState.m_fVerticalFOVDegrees, + flashlightState.m_NearZ, flashlightState.m_FarZ ); + + MatrixMultiply( matPerspective, matWorldToShadowView, matWorldToShadow ); +} + +//----------------------------------------------------------------------------- +// Compute the shadow origin and attenuation start distance +//----------------------------------------------------------------------------- +float CClientShadowMgr::ComputeLocalShadowOrigin( IClientRenderable* pRenderable, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir, float backupFactor, Vector& origin ) +{ + // Compute the centroid of the object... + Vector vecCentroid; + VectorAdd( mins, maxs, vecCentroid ); + vecCentroid *= 0.5f; + + Vector vecSize; + VectorSubtract( maxs, mins, vecSize ); + float flRadius = vecSize.Length() * 0.5f; + + // NOTE: The *origin* of the shadow cast is a point on a line passing through + // the centroid of the caster. The direction of this line is the shadow cast direction, + // and the point on that line corresponds to the endpoint of the box that is + // furthest *back* along the shadow direction + + // For the first point at which the shadow could possibly start falling off, + // we need to use the point at which the ray described above leaves the + // bounding sphere surrounding the entity. This is necessary because otherwise, + // tall, thin objects would have their shadows appear + disappear as then spun about their origin + + // Figure out the corner corresponding to the min + max projection + // along the shadow direction + + // We're basically finding the point on the cube that has the largest and smallest + // dot product with the local shadow dir. Then we're taking the dot product + // of that with the localShadowDir. lastly, we're subtracting out the + // centroid projection to give us a distance along the localShadowDir to + // the front and back of the cube along the direction of the ray. + float centroidProjection = DotProduct( vecCentroid, localShadowDir ); + float minDist = -centroidProjection; + for (int i = 0; i < 3; ++i) + { + if ( localShadowDir[i] > 0.0f ) + { + minDist += localShadowDir[i] * mins[i]; + } + else + { + minDist += localShadowDir[i] * maxs[i]; + } + } + + minDist *= backupFactor; + + VectorMA( vecCentroid, minDist, localShadowDir, origin ); + + return flRadius - minDist; +} + + +//----------------------------------------------------------------------------- +// Sorts the components of a vector +//----------------------------------------------------------------------------- +static inline void SortAbsVectorComponents( const Vector& src, int* pVecIdx ) +{ + Vector absVec( fabs(src[0]), fabs(src[1]), fabs(src[2]) ); + + int maxIdx = (absVec[0] > absVec[1]) ? 0 : 1; + if (absVec[2] > absVec[maxIdx]) + { + maxIdx = 2; + } + + // always choose something right-handed.... + switch( maxIdx ) + { + case 0: + pVecIdx[0] = 1; + pVecIdx[1] = 2; + pVecIdx[2] = 0; + break; + case 1: + pVecIdx[0] = 2; + pVecIdx[1] = 0; + pVecIdx[2] = 1; + break; + case 2: + pVecIdx[0] = 0; + pVecIdx[1] = 1; + pVecIdx[2] = 2; + break; + } +} + + +//----------------------------------------------------------------------------- +// Build the worldtotexture matrix +//----------------------------------------------------------------------------- +static void BuildWorldToTextureMatrix( const VMatrix& matWorldToShadow, + const Vector2D& size, VMatrix& matWorldToTexture ) +{ + // Build a matrix that maps from shadow space to (u,v) coordinates + VMatrix shadowToUnit; + MatrixBuildScale( shadowToUnit, 1.0f / size.x, 1.0f / size.y, 1.0f ); + shadowToUnit[0][3] = shadowToUnit[1][3] = 0.5f; + + // Store off the world to (u,v) transformation + MatrixMultiply( shadowToUnit, matWorldToShadow, matWorldToTexture ); +} + + + +static void BuildOrthoWorldToShadowMatrix( VMatrix& worldToShadow, + const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec ) +{ + // This version is faster and assumes dir, xvec, yvec are perpendicular + AssertFloatEquals( DotProduct( dir, xvec ), 0.0f, 1e-3 ); + AssertFloatEquals( DotProduct( dir, yvec ), 0.0f, 1e-3 ); + AssertFloatEquals( DotProduct( xvec, yvec ), 0.0f, 1e-3 ); + + // The shadow->world matrix is pretty simple: + // Just stick the origin in the translation component + // and the vectors in the columns... + // The inverse of this transposes the rotational component + // and the translational component = - (rotation transpose) * origin + worldToShadow.SetBasisVectors( xvec, yvec, dir ); + MatrixTranspose( worldToShadow, worldToShadow ); + + Vector translation; + Vector3DMultiply( worldToShadow, origin, translation ); + + translation *= -1.0f; + worldToShadow.SetTranslation( translation ); + + // The the bottom row. + worldToShadow[3][0] = worldToShadow[3][1] = worldToShadow[3][2] = 0.0f; + worldToShadow[3][3] = 1.0f; +} + + +//----------------------------------------------------------------------------- +// Set extra clip planes related to shadows... +//----------------------------------------------------------------------------- +void CClientShadowMgr::ClearExtraClipPlanes( ClientShadowHandle_t h ) +{ + shadowmgr->ClearExtraClipPlanes( m_Shadows[h].m_ShadowHandle ); +} + +void CClientShadowMgr::AddExtraClipPlane( ClientShadowHandle_t h, const Vector& normal, float dist ) +{ + shadowmgr->AddExtraClipPlane( m_Shadows[h].m_ShadowHandle, normal, dist ); +} + + +//----------------------------------------------------------------------------- +// Compute the extra shadow planes +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeExtraClipPlanes( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector* vec, + const Vector& mins, const Vector& maxs, const Vector& localShadowDir ) +{ + // Compute the world-space position of the corner of the bounding box + // that's got the highest dotproduct with the local shadow dir... + Vector origin = pRenderable->GetRenderOrigin( ); + float dir[3]; + + int i; + for ( i = 0; i < 3; ++i ) + { + if (localShadowDir[i] < 0.0f) + { + VectorMA( origin, maxs[i], vec[i], origin ); + dir[i] = 1; + } + else + { + VectorMA( origin, mins[i], vec[i], origin ); + dir[i] = -1; + } + } + + // Now that we have it, create 3 planes... + Vector normal; + ClearExtraClipPlanes(handle); + for ( i = 0; i < 3; ++i ) + { + VectorMultiply( vec[i], dir[i], normal ); + float dist = DotProduct( normal, origin ); + AddExtraClipPlane( handle, normal, dist ); + } + + ClientShadow_t& shadow = m_Shadows[handle]; + C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( shadow.m_Entity ); + if ( pEntity && pEntity->m_bEnableRenderingClipPlane ) + { + normal[ 0 ] = -pEntity->m_fRenderingClipPlane[ 0 ]; + normal[ 1 ] = -pEntity->m_fRenderingClipPlane[ 1 ]; + normal[ 2 ] = -pEntity->m_fRenderingClipPlane[ 2 ]; + AddExtraClipPlane( handle, normal, -pEntity->m_fRenderingClipPlane[ 3 ] - 0.5f ); + } +} + + +inline ShadowType_t CClientShadowMgr::GetActualShadowCastType( ClientShadowHandle_t handle ) const +{ + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + { + return SHADOWS_NONE; + } + + if ( m_Shadows[handle].m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE ) + { + return ( m_RenderToTextureActive ? SHADOWS_RENDER_TO_TEXTURE : SHADOWS_SIMPLE ); + } + else if( m_Shadows[handle].m_Flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) + { + return SHADOWS_RENDER_TO_DEPTH_TEXTURE; + } + else + { + return SHADOWS_SIMPLE; + } +} + +inline ShadowType_t CClientShadowMgr::GetActualShadowCastType( IClientRenderable *pEnt ) const +{ + return GetActualShadowCastType( pEnt->GetShadowHandle() ); +} + + +//----------------------------------------------------------------------------- +// Adds a shadow to all leaves along a ray +//----------------------------------------------------------------------------- +class CShadowLeafEnum : public ISpatialLeafEnumerator +{ +public: + bool EnumerateLeaf( int leaf, int context ) + { + m_LeafList.AddToTail( leaf ); + return true; + } + + CUtlVectorFixedGrowable< int, 512 > m_LeafList; +}; + + +//----------------------------------------------------------------------------- +// Builds a list of leaves inside the shadow volume +//----------------------------------------------------------------------------- +static void BuildShadowLeafList( CShadowLeafEnum *pEnum, const Vector& origin, + const Vector& dir, const Vector2D& size, float maxDist ) +{ + Ray_t ray; + VectorCopy( origin, ray.m_Start ); + VectorMultiply( dir, maxDist, ray.m_Delta ); + ray.m_StartOffset.Init( 0, 0, 0 ); + + float flRadius = sqrt( size.x * size.x + size.y * size.y ) * 0.5f; + ray.m_Extents.Init( flRadius, flRadius, flRadius ); + ray.m_IsRay = false; + ray.m_IsSwept = true; + + ISpatialQuery* pQuery = engine->GetBSPTreeQuery(); + pQuery->EnumerateLeavesAlongRay( ray, pEnum, 0 ); +} + + +//----------------------------------------------------------------------------- +// Builds a simple blobby shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::BuildOrthoShadow( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs) +{ + // Get the object's basis + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + + Vector vecShadowDir = GetShadowDirection( pRenderable ); + + // Project the shadow casting direction into the space of the object + Vector localShadowDir; + localShadowDir[0] = DotProduct( vec[0], vecShadowDir ); + localShadowDir[1] = DotProduct( vec[1], vecShadowDir ); + localShadowDir[2] = DotProduct( vec[2], vecShadowDir ); + + // Figure out which vector has the largest component perpendicular + // to the shadow handle... + // Sort by how perpendicular it is + int vecIdx[3]; + SortAbsVectorComponents( localShadowDir, vecIdx ); + + // Here's our shadow basis vectors; namely the ones that are + // most perpendicular to the shadow casting direction + Vector xvec = vec[vecIdx[0]]; + Vector yvec = vec[vecIdx[1]]; + + // Project them into a plane perpendicular to the shadow direction + xvec -= vecShadowDir * DotProduct( vecShadowDir, xvec ); + yvec -= vecShadowDir * DotProduct( vecShadowDir, yvec ); + VectorNormalize( xvec ); + VectorNormalize( yvec ); + + // Compute the box size + Vector boxSize; + VectorSubtract( maxs, mins, boxSize ); + + // We project the two longest sides into the vectors perpendicular + // to the projection direction, then add in the projection of the perp direction + Vector2D size( boxSize[vecIdx[0]], boxSize[vecIdx[1]] ); + size.x *= fabs( DotProduct( vec[vecIdx[0]], xvec ) ); + size.y *= fabs( DotProduct( vec[vecIdx[1]], yvec ) ); + + // Add the third component into x and y + size.x += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], xvec ) ); + size.y += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], yvec ) ); + + // Bloat a bit, since the shadow wants to extend outside the model a bit + size.x += 10.0f; + size.y += 10.0f; + + // Clamp the minimum size + Vector2DMax( size, Vector2D(10.0f, 10.0f), size ); + + // Place the origin at the point with min dot product with shadow dir + Vector org; + float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 2.0f, org ); + + // Transform the local origin into world coordinates + Vector worldOrigin = pRenderable->GetRenderOrigin( ); + VectorMA( worldOrigin, org.x, vec[0], worldOrigin ); + VectorMA( worldOrigin, org.y, vec[1], worldOrigin ); + VectorMA( worldOrigin, org.z, vec[2], worldOrigin ); + + // FUNKY: A trick to reduce annoying texelization artifacts!? + float dx = 1.0f / TEXEL_SIZE_PER_CASTER_SIZE; + worldOrigin.x = (int)(worldOrigin.x / dx) * dx; + worldOrigin.y = (int)(worldOrigin.y / dx) * dx; + worldOrigin.z = (int)(worldOrigin.z / dx) * dx; + + // NOTE: We gotta use the general matrix because xvec and yvec aren't perp + VMatrix matWorldToShadow, matWorldToTexture; + BuildGeneralWorldToShadowMatrix( m_Shadows[handle].m_WorldToShadow, worldOrigin, vecShadowDir, xvec, yvec ); + BuildWorldToTextureMatrix( m_Shadows[handle].m_WorldToShadow, size, matWorldToTexture ); + Vector2DCopy( size, m_Shadows[handle].m_WorldSize ); + + // Compute the falloff attenuation + // Area computation isn't exact since xvec is not perp to yvec, but close enough +// float shadowArea = size.x * size.y; + + // The entity may be overriding our shadow cast distance + float flShadowCastDistance = GetShadowDistance( pRenderable ); + float maxHeight = flShadowCastDistance + falloffStart; //3.0f * sqrt( shadowArea ); + + CShadowLeafEnum leafList; + BuildShadowLeafList( &leafList, worldOrigin, vecShadowDir, size, maxHeight ); + int nCount = leafList.m_LeafList.Count(); + const int *pLeafList = leafList.m_LeafList.Base(); + + shadowmgr->ProjectShadow( m_Shadows[handle].m_ShadowHandle, worldOrigin, + vecShadowDir, matWorldToTexture, size, nCount, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, pRenderable->GetRenderOrigin() ); + + // Compute extra clip planes to prevent poke-thru +// FIXME!!!!!!!!!!!!!! Removing this for now since it seems to mess up the blobby shadows. +// ComputeExtraClipPlanes( pEnt, handle, vec, mins, maxs, localShadowDir ); + + // Add the shadow to the client leaf system so it correctly marks + // leafs as being affected by a particular shadow + ClientLeafSystem()->ProjectShadow( m_Shadows[handle].m_ClientLeafShadowHandle, nCount, pLeafList ); +} + + +//----------------------------------------------------------------------------- +// Visualization.... +//----------------------------------------------------------------------------- +void CClientShadowMgr::DrawRenderToTextureDebugInfo( IClientRenderable* pRenderable, const Vector& mins, const Vector& maxs ) +{ + // Get the object's basis + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + + Vector vecSize; + VectorSubtract( maxs, mins, vecSize ); + + Vector vecOrigin = pRenderable->GetRenderOrigin(); + Vector start, end, end2; + + VectorMA( vecOrigin, mins.x, vec[0], start ); + VectorMA( start, mins.y, vec[1], start ); + VectorMA( start, mins.z, vec[2], start ); + + VectorMA( start, vecSize.x, vec[0], end ); + VectorMA( end, vecSize.z, vec[2], end2 ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, vecSize.y, vec[1], end ); + VectorMA( end, vecSize.z, vec[2], end2 ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, vecSize.z, vec[2], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + start = end; + VectorMA( start, vecSize.x, vec[0], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, vecSize.y, vec[1], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( end, vecSize.x, vec[0], start ); + VectorMA( start, -vecSize.x, vec[0], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, -vecSize.y, vec[1], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, -vecSize.z, vec[2], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + start = end; + VectorMA( start, -vecSize.x, vec[0], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + VectorMA( start, -vecSize.y, vec[1], end ); + debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 ); + + C_BaseEntity *pEnt = pRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEnt ) + { + debugoverlay->AddTextOverlay( vecOrigin, 0, "%d", pEnt->entindex() ); + } + else + { + debugoverlay->AddTextOverlay( vecOrigin, 0, "%X", (size_t)pRenderable ); + } +} + + +extern ConVar cl_drawshadowtexture; +extern ConVar cl_shadowtextureoverlaysize; + +//----------------------------------------------------------------------------- +// Builds a more complex shadow... +//----------------------------------------------------------------------------- +void CClientShadowMgr::BuildRenderToTextureShadow( IClientRenderable* pRenderable, + ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs) +{ + if ( cl_drawshadowtexture.GetInt() ) + { + // Red wireframe bounding box around objects whose RTT shadows are being updated that frame + DrawRenderToTextureDebugInfo( pRenderable, mins, maxs ); + } + + // Get the object's basis + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + + Vector vecShadowDir = GetShadowDirection( pRenderable ); + +// Debugging aid +// const model_t *pModel = pRenderable->GetModel(); +// const char *pDebugName = modelinfo->GetModelName( pModel ); + + // Project the shadow casting direction into the space of the object + Vector localShadowDir; + localShadowDir[0] = DotProduct( vec[0], vecShadowDir ); + localShadowDir[1] = DotProduct( vec[1], vecShadowDir ); + localShadowDir[2] = DotProduct( vec[2], vecShadowDir ); + + // Compute the box size + Vector boxSize; + VectorSubtract( maxs, mins, boxSize ); + + Vector yvec; + float fProjMax = 0.0f; + for( int i = 0; i != 3; ++i ) + { + Vector test = vec[i] - ( vecShadowDir * DotProduct( vecShadowDir, vec[i] ) ); + test *= boxSize[i]; //doing after the projection to simplify projection math + float fLengthSqr = test.LengthSqr(); + if( fLengthSqr > fProjMax ) + { + fProjMax = fLengthSqr; + yvec = test; + } + } + + VectorNormalize( yvec ); + + // Compute the x vector + Vector xvec; + CrossProduct( yvec, vecShadowDir, xvec ); + + // We project the two longest sides into the vectors perpendicular + // to the projection direction, then add in the projection of the perp direction + Vector2D size; + size.x = boxSize.x * fabs( DotProduct( vec[0], xvec ) ) + + boxSize.y * fabs( DotProduct( vec[1], xvec ) ) + + boxSize.z * fabs( DotProduct( vec[2], xvec ) ); + size.y = boxSize.x * fabs( DotProduct( vec[0], yvec ) ) + + boxSize.y * fabs( DotProduct( vec[1], yvec ) ) + + boxSize.z * fabs( DotProduct( vec[2], yvec ) ); + + size.x += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE; + size.y += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE; + + // Place the origin at the point with min dot product with shadow dir + Vector org; + float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 1.0f, org ); + + // Transform the local origin into world coordinates + Vector worldOrigin = pRenderable->GetRenderOrigin( ); + VectorMA( worldOrigin, org.x, vec[0], worldOrigin ); + VectorMA( worldOrigin, org.y, vec[1], worldOrigin ); + VectorMA( worldOrigin, org.z, vec[2], worldOrigin ); + + VMatrix matWorldToTexture; + BuildOrthoWorldToShadowMatrix( m_Shadows[handle].m_WorldToShadow, worldOrigin, vecShadowDir, xvec, yvec ); + BuildWorldToTextureMatrix( m_Shadows[handle].m_WorldToShadow, size, matWorldToTexture ); + Vector2DCopy( size, m_Shadows[handle].m_WorldSize ); + + // Compute the falloff attenuation + // Area computation isn't exact since xvec is not perp to yvec, but close enough + // Extra factor of 4 in the maxHeight due to the size being half as big +// float shadowArea = size.x * size.y; + + // The entity may be overriding our shadow cast distance + float flShadowCastDistance = GetShadowDistance( pRenderable ); + float maxHeight = flShadowCastDistance + falloffStart; //3.0f * sqrt( shadowArea ); + + CShadowLeafEnum leafList; + BuildShadowLeafList( &leafList, worldOrigin, vecShadowDir, size, maxHeight ); + int nCount = leafList.m_LeafList.Count(); + const int *pLeafList = leafList.m_LeafList.Base(); + + shadowmgr->ProjectShadow( m_Shadows[handle].m_ShadowHandle, worldOrigin, + vecShadowDir, matWorldToTexture, size, nCount, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, pRenderable->GetRenderOrigin() ); + + // Compute extra clip planes to prevent poke-thru + ComputeExtraClipPlanes( pRenderable, handle, vec, mins, maxs, localShadowDir ); + + // Add the shadow to the client leaf system so it correctly marks + // leafs as being affected by a particular shadow + ClientLeafSystem()->ProjectShadow( m_Shadows[handle].m_ClientLeafShadowHandle, nCount, pLeafList ); +} + +static void LineDrawHelper( const Vector &startShadowSpace, const Vector &endShadowSpace, + const VMatrix &shadowToWorld, unsigned char r = 255, unsigned char g = 255, + unsigned char b = 255 ) +{ + Vector startWorldSpace, endWorldSpace; + Vector3DMultiplyPositionProjective( shadowToWorld, startShadowSpace, startWorldSpace ); + Vector3DMultiplyPositionProjective( shadowToWorld, endShadowSpace, endWorldSpace ); + + debugoverlay->AddLineOverlay( startWorldSpace + Vector( 0.0f, 0.0f, 1.0f ), + endWorldSpace + Vector( 0.0f, 0.0f, 1.0f ), r, g, b, false, -1 ); +} + +static void DebugDrawFrustum( const Vector &vOrigin, const VMatrix &matWorldToFlashlight ) +{ + VMatrix flashlightToWorld; + MatrixInverseGeneral( matWorldToFlashlight, flashlightToWorld ); + + // Draw boundaries of frustum + LineDrawHelper( Vector( 0.0f, 0.0f, 0.0f ), Vector( 0.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 0.0f, 1.0f ), Vector( 0.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 1.0f ), Vector( 0.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 0.0f ), Vector( 0.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 0.0f, 0.0f ), Vector( 1.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 0.0f, 1.0f ), Vector( 1.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 1.0f, 1.0f, 0.0f ), Vector( 1.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 0.0f, 0.0f ), Vector( 1.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 0.0f, 1.0f ), Vector( 1.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 ); + LineDrawHelper( Vector( 0.0f, 1.0f, 0.0f ), Vector( 1.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 ); + + // Draw RGB triad at front plane + LineDrawHelper( Vector( 0.5f, 0.5f, 0.0f ), Vector( 1.0f, 0.5f, 0.0f ), flashlightToWorld, 255, 0, 0 ); + LineDrawHelper( Vector( 0.5f, 0.5f, 0.0f ), Vector( 0.5f, 1.0f, 0.0f ), flashlightToWorld, 0, 255, 0 ); + LineDrawHelper( Vector( 0.5f, 0.5f, 0.0f ), Vector( 0.5f, 0.5f, 0.35f ), flashlightToWorld, 0, 0, 255 ); +} + + +//----------------------------------------------------------------------------- +// Builds a list of leaves inside the flashlight volume +//----------------------------------------------------------------------------- +static void BuildFlashlightLeafList( CShadowLeafEnum *pEnum, const VMatrix &worldToShadow ) +{ + // Use an AABB around the frustum to enumerate leaves. + Vector mins, maxs; + CalculateAABBFromProjectionMatrix( worldToShadow, &mins, &maxs ); + ISpatialQuery* pQuery = engine->GetBSPTreeQuery(); + pQuery->EnumerateLeavesInBox( mins, maxs, pEnum, 0 ); +} + + +void CClientShadowMgr::BuildFlashlight( ClientShadowHandle_t handle ) +{ + // For the 360, we just draw flashlights with the main geometry + // and bypass the entire shadow casting system. + ClientShadow_t &shadow = m_Shadows[handle]; + if ( IsX360() || r_flashlight_version2.GetInt() ) + { + // This will update the matrices, but not do work to add the flashlight to surfaces + shadowmgr->ProjectFlashlight( shadow.m_ShadowHandle, shadow.m_WorldToShadow, 0, NULL ); + return; + } + + VPROF_BUDGET( "CClientShadowMgr::BuildFlashlight", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + bool bLightModels = r_flashlightmodels.GetBool(); + bool bLightSpecificEntity = shadow.m_hTargetEntity.Get() != NULL; + bool bLightWorld = ( shadow.m_Flags & SHADOW_FLAGS_LIGHT_WORLD ) != 0; + int nCount = 0; + const int *pLeafList = 0; + + CShadowLeafEnum leafList; + if ( bLightWorld || ( bLightModels && !bLightSpecificEntity ) ) + { + BuildFlashlightLeafList( &leafList, shadow.m_WorldToShadow ); + nCount = leafList.m_LeafList.Count(); + pLeafList = leafList.m_LeafList.Base(); + } + + if( bLightWorld ) + { + shadowmgr->ProjectFlashlight( shadow.m_ShadowHandle, shadow.m_WorldToShadow, nCount, pLeafList ); + } + else + { + // This should clear all models and surfaces from this shadow + shadowmgr->EnableShadow( shadow.m_ShadowHandle, false ); + shadowmgr->EnableShadow( shadow.m_ShadowHandle, true ); + } + + if ( !bLightModels ) + return; + + if ( !bLightSpecificEntity ) + { + // Add the shadow to the client leaf system so it correctly marks + // leafs as being affected by a particular shadow + ClientLeafSystem()->ProjectFlashlight( shadow.m_ClientLeafShadowHandle, nCount, pLeafList ); + return; + } + + // We know what we are focused on, so just add the shadow directly to that receiver + Assert( shadow.m_hTargetEntity->GetModel() ); + + C_BaseEntity *pChild = shadow.m_hTargetEntity->FirstMoveChild(); + while( pChild ) + { + int modelType = modelinfo->GetModelType( pChild->GetModel() ); + if (modelType == mod_brush) + { + AddShadowToReceiver( handle, pChild, SHADOW_RECEIVER_BRUSH_MODEL ); + } + else if ( modelType == mod_studio ) + { + AddShadowToReceiver( handle, pChild, SHADOW_RECEIVER_STUDIO_MODEL ); + } + + pChild = pChild->NextMovePeer(); + } + + int modelType = modelinfo->GetModelType( shadow.m_hTargetEntity->GetModel() ); + if (modelType == mod_brush) + { + AddShadowToReceiver( handle, shadow.m_hTargetEntity, SHADOW_RECEIVER_BRUSH_MODEL ); + } + else if ( modelType == mod_studio ) + { + AddShadowToReceiver( handle, shadow.m_hTargetEntity, SHADOW_RECEIVER_STUDIO_MODEL ); + } +} + + +//----------------------------------------------------------------------------- +// Adds the child bounds to the bounding box +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddChildBounds( matrix3x4_t &matWorldToBBox, IClientRenderable* pParent, Vector &vecMins, Vector &vecMaxs ) +{ + Vector vecChildMins, vecChildMaxs; + Vector vecNewChildMins, vecNewChildMaxs; + matrix3x4_t childToBBox; + + IClientRenderable *pChild = pParent->FirstShadowChild(); + while( pChild ) + { + // Transform the child bbox into the space of the main bbox + // FIXME: Optimize this? + if ( GetActualShadowCastType( pChild ) != SHADOWS_NONE) + { + pChild->GetShadowRenderBounds( vecChildMins, vecChildMaxs, SHADOWS_RENDER_TO_TEXTURE ); + ConcatTransforms( matWorldToBBox, pChild->RenderableToWorldTransform(), childToBBox ); + TransformAABB( childToBBox, vecChildMins, vecChildMaxs, vecNewChildMins, vecNewChildMaxs ); + VectorMin( vecMins, vecNewChildMins, vecMins ); + VectorMax( vecMaxs, vecNewChildMaxs, vecMaxs ); + } + + AddChildBounds( matWorldToBBox, pChild, vecMins, vecMaxs ); + pChild = pChild->NextShadowPeer(); + } +} + + +//----------------------------------------------------------------------------- +// Compute a bounds for the entity + children +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeHierarchicalBounds( IClientRenderable *pRenderable, Vector &vecMins, Vector &vecMaxs ) +{ + ShadowType_t shadowType = GetActualShadowCastType( pRenderable ); + + pRenderable->GetShadowRenderBounds( vecMins, vecMaxs, shadowType ); + + // We could use a good solution for this in the regular PC build, since + // it causes lots of extra bone setups for entities you can't see. + if ( IsPC() ) + { + IClientRenderable *pChild = pRenderable->FirstShadowChild(); + + // Don't recurse down the tree when we hit a blobby shadow + if ( pChild && shadowType != SHADOWS_SIMPLE ) + { + matrix3x4_t matWorldToBBox; + MatrixInvert( pRenderable->RenderableToWorldTransform(), matWorldToBBox ); + AddChildBounds( matWorldToBBox, pRenderable, vecMins, vecMaxs ); + } + } +} + + +//----------------------------------------------------------------------------- +// Shadow update functions +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateStudioShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ) +{ + if( !( m_Shadows[handle].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) ) + { + Vector mins, maxs; + ComputeHierarchicalBounds( pRenderable, mins, maxs ); + + ShadowType_t shadowType = GetActualShadowCastType( handle ); + if ( shadowType != SHADOWS_RENDER_TO_TEXTURE ) + { + BuildOrthoShadow( pRenderable, handle, mins, maxs ); + } + else + { + BuildRenderToTextureShadow( pRenderable, handle, mins, maxs ); + } + } + else + { + BuildFlashlight( handle ); + } +} + +void CClientShadowMgr::UpdateBrushShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ) +{ + if( !( m_Shadows[handle].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) ) + { + // Compute the bounding box in the space of the shadow... + Vector mins, maxs; + ComputeHierarchicalBounds( pRenderable, mins, maxs ); + + ShadowType_t shadowType = GetActualShadowCastType( handle ); + if ( shadowType != SHADOWS_RENDER_TO_TEXTURE ) + { + BuildOrthoShadow( pRenderable, handle, mins, maxs ); + } + else + { + BuildRenderToTextureShadow( pRenderable, handle, mins, maxs ); + } + } + else + { + VPROF_BUDGET( "CClientShadowMgr::UpdateBrushShadow", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + BuildFlashlight( handle ); + } +} + + +#ifdef _DEBUG + +static bool s_bBreak = false; + +void ShadowBreak_f() +{ + s_bBreak = true; +} + +static ConCommand r_shadowbreak("r_shadowbreak", ShadowBreak_f); + +#endif // _DEBUG + + +bool CClientShadowMgr::WillParentRenderBlobbyShadow( IClientRenderable *pRenderable ) +{ + if ( !pRenderable ) + return false; + + IClientRenderable *pShadowParent = pRenderable->GetShadowParent(); + if ( !pShadowParent ) + return false; + + // If there's *no* shadow casting type, then we want to see if we can render into its parent + ShadowType_t shadowType = GetActualShadowCastType( pShadowParent ); + if ( shadowType == SHADOWS_NONE ) + return WillParentRenderBlobbyShadow( pShadowParent ); + + return shadowType == SHADOWS_SIMPLE; +} + + +//----------------------------------------------------------------------------- +// Are we the child of a shadow with render-to-texture? +//----------------------------------------------------------------------------- +bool CClientShadowMgr::ShouldUseParentShadow( IClientRenderable *pRenderable ) +{ + if ( !pRenderable ) + return false; + + IClientRenderable *pShadowParent = pRenderable->GetShadowParent(); + if ( !pShadowParent ) + return false; + + // Can't render into the parent if the parent is blobby + ShadowType_t shadowType = GetActualShadowCastType( pShadowParent ); + if ( shadowType == SHADOWS_SIMPLE ) + return false; + + // If there's *no* shadow casting type, then we want to see if we can render into its parent + if ( shadowType == SHADOWS_NONE ) + return ShouldUseParentShadow( pShadowParent ); + + // Here, the parent uses a render-to-texture shadow + return true; +} + + +//----------------------------------------------------------------------------- +// Before we render any view, make sure all shadows are re-projected vs world +//----------------------------------------------------------------------------- +void CClientShadowMgr::PreRender() +{ + VPROF_BUDGET( "CClientShadowMgr::PreRender", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + MDLCACHE_CRITICAL_SECTION(); + + // + // -- Shadow Depth Textures ----------------------- + // + + { + // VPROF scope + VPROF_BUDGET( "CClientShadowMgr::PreRender", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + // If someone turned shadow depth mapping on but we can't do it, force it off + if ( r_flashlightdepthtexture.GetBool() && !materials->SupportsShadowDepthTextures() ) + { + r_flashlightdepthtexture.SetValue( 0 ); + ShutdownDepthTextureShadows(); + } + + bool bDepthTextureActive = r_flashlightdepthtexture.GetBool(); + int nDepthTextureResolution = r_flashlightdepthres.GetInt(); + + // If shadow depth texture size or enable/disable changed, do appropriate deallocation/(re)allocation + if ( ( bDepthTextureActive != m_bDepthTextureActive ) || ( nDepthTextureResolution != m_nDepthTextureResolution ) ) + { + // If shadow depth texturing remains on, but resolution changed, shut down and reinitialize depth textures + if ( ( bDepthTextureActive == true ) && ( m_bDepthTextureActive == true ) && + ( nDepthTextureResolution != m_nDepthTextureResolution ) ) + { + ShutdownDepthTextureShadows(); + InitDepthTextureShadows(); + } + else + { + if ( m_bDepthTextureActive && !bDepthTextureActive ) // Turning off shadow depth texturing + { + ShutdownDepthTextureShadows(); + } + else if ( bDepthTextureActive && !m_bDepthTextureActive) // Turning on shadow depth mapping + { + InitDepthTextureShadows(); + } + } + } + } + + // + // -- Render to Texture Shadows ----------------------- + // + + bool bRenderToTextureActive = r_shadowrendertotexture.GetBool(); + if ( bRenderToTextureActive != m_RenderToTextureActive ) + { + if ( m_RenderToTextureActive ) + { + ShutdownRenderToTextureShadows(); + } + else + { + InitRenderToTextureShadows(); + } + + UpdateAllShadows(); + return; + } + + m_bUpdatingDirtyShadows = true; + + unsigned short i = m_DirtyShadows.FirstInorder(); + while ( i != m_DirtyShadows.InvalidIndex() ) + { + MDLCACHE_CRITICAL_SECTION(); + ClientShadowHandle_t& handle = m_DirtyShadows[ i ]; + Assert( m_Shadows.IsValidIndex( handle ) ); + UpdateProjectedTextureInternal( handle, false ); + i = m_DirtyShadows.NextInorder(i); + } + m_DirtyShadows.RemoveAll(); + + // Transparent shadows must remain dirty, since they were not re-projected + int nCount = m_TransparentShadows.Count(); + for ( int i = 0; i < nCount; ++i ) + { + m_DirtyShadows.Insert( m_TransparentShadows[i] ); + } + m_TransparentShadows.RemoveAll(); + + m_bUpdatingDirtyShadows = false; +} + + +//----------------------------------------------------------------------------- +// Gets the entity whose shadow this shadow will render into +//----------------------------------------------------------------------------- +IClientRenderable *CClientShadowMgr::GetParentShadowEntity( ClientShadowHandle_t handle ) +{ + ClientShadow_t& shadow = m_Shadows[handle]; + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( pRenderable ) + { + if ( ShouldUseParentShadow( pRenderable ) ) + { + IClientRenderable *pParent = pRenderable->GetShadowParent(); + while ( GetActualShadowCastType( pParent ) == SHADOWS_NONE ) + { + pParent = pParent->GetShadowParent(); + Assert( pParent ); + } + return pParent; + } + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Marks a shadow as needing re-projection +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddToDirtyShadowList( ClientShadowHandle_t handle, bool bForce ) +{ + // Don't add to the dirty shadow list while we're iterating over it + // The only way this can happen is if a child is being rendered into a parent + // shadow, and we don't need it to be added to the dirty list in that case. + if ( m_bUpdatingDirtyShadows ) + return; + + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + return; + + Assert( m_DirtyShadows.Find( handle ) == m_DirtyShadows.InvalidIndex() ); + m_DirtyShadows.Insert( handle ); + + // This pretty much guarantees we'll recompute the shadow + if ( bForce ) + { + m_Shadows[handle].m_LastAngles.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + } + + // If we use our parent shadow, then it's dirty too... + IClientRenderable *pParent = GetParentShadowEntity( handle ); + if ( pParent ) + { + AddToDirtyShadowList( pParent, bForce ); + } +} + + +//----------------------------------------------------------------------------- +// Marks a shadow as needing re-projection +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddToDirtyShadowList( IClientRenderable *pRenderable, bool bForce ) +{ + // Don't add to the dirty shadow list while we're iterating over it + // The only way this can happen is if a child is being rendered into a parent + // shadow, and we don't need it to be added to the dirty list in that case. + if ( m_bUpdatingDirtyShadows ) + return; + + // Are we already in the dirty list? + if ( pRenderable->IsShadowDirty( ) ) + return; + + ClientShadowHandle_t handle = pRenderable->GetShadowHandle(); + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + return; + +#ifdef _DEBUG + // Make sure everything's consistent + if ( handle != CLIENTSHADOW_INVALID_HANDLE ) + { + IClientRenderable *pShadowRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity ); + Assert( pRenderable == pShadowRenderable ); + } +#endif + + pRenderable->MarkShadowDirty( true ); + AddToDirtyShadowList( handle, bForce ); +} + + +//----------------------------------------------------------------------------- +// Marks the render-to-texture shadow as needing to be re-rendered +//----------------------------------------------------------------------------- +void CClientShadowMgr::MarkRenderToTextureShadowDirty( ClientShadowHandle_t handle ) +{ + // Don't add bogus handles! + if (handle != CLIENTSHADOW_INVALID_HANDLE) + { + // Mark the shadow has having a dirty renter-to-texture + ClientShadow_t& shadow = m_Shadows[handle]; + shadow.m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + + // If we use our parent shadow, then it's dirty too... + IClientRenderable *pParent = GetParentShadowEntity( handle ); + if ( pParent ) + { + ClientShadowHandle_t parentHandle = pParent->GetShadowHandle(); + if ( parentHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + m_Shadows[parentHandle].m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Update a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateShadow( ClientShadowHandle_t handle, bool force ) +{ + ClientShadow_t& shadow = m_Shadows[handle]; + + // Get the client entity.... + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + if ( !pRenderable ) + { + // Retire the shadow if the entity is gone + DestroyShadow( handle ); + return; + } + + // Don't bother if there's no model on the renderable + if ( !pRenderable->GetModel() ) + { + pRenderable->MarkShadowDirty( false ); + return; + } + + // FIXME: NOTE! Because this is called from PreRender, the falloff bias is + // off by a frame. We could move the code in PreRender to occur after world + // list building is done to fix this issue. + // Don't bother with it if the shadow is totally transparent + const ShadowInfo_t &shadowInfo = shadowmgr->GetInfo( shadow.m_ShadowHandle ); + if ( shadowInfo.m_FalloffBias == 255 ) + { + shadowmgr->EnableShadow( shadow.m_ShadowHandle, false ); + m_TransparentShadows.AddToTail( handle ); + return; + } + +#ifdef _DEBUG + if (s_bBreak) + { + s_bBreak = false; + } +#endif + // Hierarchical children shouldn't be projecting shadows... + // Check to see if it's a child of an entity with a render-to-texture shadow... + if ( ShouldUseParentShadow( pRenderable ) || WillParentRenderBlobbyShadow( pRenderable ) ) + { + shadowmgr->EnableShadow( shadow.m_ShadowHandle, false ); + pRenderable->MarkShadowDirty( false ); + return; + } + + shadowmgr->EnableShadow( shadow.m_ShadowHandle, true ); + + // Figure out if the shadow moved... + // Even though we have dirty bits, some entities + // never clear those dirty bits + const Vector& origin = pRenderable->GetRenderOrigin(); + const QAngle& angles = pRenderable->GetRenderAngles(); + + if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles)) + { + // Store off the new pos/orientation + VectorCopy( origin, shadow.m_LastOrigin ); + VectorCopy( angles, shadow.m_LastAngles ); + + CMatRenderContextPtr pRenderContext( materials ); + const model_t *pModel = pRenderable->GetModel(); + MaterialFogMode_t fogMode = pRenderContext->GetFogMode(); + pRenderContext->FogMode( MATERIAL_FOG_NONE ); + switch( modelinfo->GetModelType( pModel ) ) + { + case mod_brush: + UpdateBrushShadow( pRenderable, handle ); + break; + + case mod_studio: + UpdateStudioShadow( pRenderable, handle ); + break; + + default: + // Shouldn't get here if not a brush or studio + Assert(0); + break; + } + pRenderContext->FogMode( fogMode ); + } + + // NOTE: We can't do this earlier because pEnt->GetRenderOrigin() can + // provoke a recomputation of render origin, which, for aiments, can cause everything + // to be marked as dirty. So don't clear the flag until this point. + pRenderable->MarkShadowDirty( false ); +} + + +//----------------------------------------------------------------------------- +// Update a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateProjectedTextureInternal( ClientShadowHandle_t handle, bool force ) +{ + ClientShadow_t& shadow = m_Shadows[handle]; + + if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::UpdateProjectedTextureInternal", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + Assert( ( shadow.m_Flags & SHADOW_FLAGS_SHADOW ) == 0 ); + ClientShadow_t& shadow = m_Shadows[handle]; + + shadowmgr->EnableShadow( shadow.m_ShadowHandle, true ); + + // FIXME: What's the difference between brush and model shadows for light projectors? Answer: nothing. + UpdateBrushShadow( NULL, handle ); + } + else + { + Assert( shadow.m_Flags & SHADOW_FLAGS_SHADOW ); + Assert( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ); + UpdateShadow( handle, force ); + } +} + + +//----------------------------------------------------------------------------- +// Update a shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateProjectedTexture( ClientShadowHandle_t handle, bool force ) +{ + VPROF_BUDGET( "CClientShadowMgr::UpdateProjectedTexture", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if ( handle == CLIENTSHADOW_INVALID_HANDLE ) + return; + + // NOTE: This can only work for flashlights, since UpdateProjectedTextureInternal + // depends on the falloff offset to cull shadows. + ClientShadow_t &shadow = m_Shadows[ handle ]; + if( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + { + Warning( "CClientShadowMgr::UpdateProjectedTexture can only be used with flashlights!\n" ); + return; + } + + UpdateProjectedTextureInternal( handle, force ); + RemoveShadowFromDirtyList( handle ); +} + + +//----------------------------------------------------------------------------- +// Computes bounding sphere +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeBoundingSphere( IClientRenderable* pRenderable, Vector& origin, float& radius ) +{ + Assert( pRenderable ); + Vector mins, maxs; + pRenderable->GetShadowRenderBounds( mins, maxs, GetActualShadowCastType( pRenderable ) ); + Vector size; + VectorSubtract( maxs, mins, size ); + radius = size.Length() * 0.5f; + + // Compute centroid (local space) + Vector centroid; + VectorAdd( mins, maxs, centroid ); + centroid *= 0.5f; + + // Transform centroid into world space + Vector vec[3]; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + vec[1] *= -1.0f; + + VectorCopy( pRenderable->GetRenderOrigin(), origin ); + VectorMA( origin, centroid.x, vec[0], origin ); + VectorMA( origin, centroid.y, vec[1], origin ); + VectorMA( origin, centroid.z, vec[2], origin ); +} + + +//----------------------------------------------------------------------------- +// Computes a rough AABB encompassing the volume of the shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ) +{ + // This is *really* rough. Basically we simply determine the + // maximum shadow casting length and extrude the box by that distance + + Vector vecShadowDir = GetShadowDirection( pRenderable ); + for (int i = 0; i < 3; ++i) + { + float flShadowCastDistance = GetShadowDistance( pRenderable ); + float flDist = flShadowCastDistance * vecShadowDir[i]; + + if (vecShadowDir[i] < 0) + { + (*pAbsMins)[i] = vecAbsCenter[i] - flRadius + flDist; + (*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius; + } + else + { + (*pAbsMins)[i] = vecAbsCenter[i] - flRadius; + (*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius + flDist; + } + } +} + + +//----------------------------------------------------------------------------- +// Compute a separating axis... +//----------------------------------------------------------------------------- +bool CClientShadowMgr::ComputeSeparatingPlane( IClientRenderable* pRend1, IClientRenderable* pRend2, cplane_t* pPlane ) +{ + Vector min1, max1, min2, max2; + pRend1->GetShadowRenderBounds( min1, max1, GetActualShadowCastType( pRend1 ) ); + pRend2->GetShadowRenderBounds( min2, max2, GetActualShadowCastType( pRend2 ) ); + return ::ComputeSeparatingPlane( + pRend1->GetRenderOrigin(), pRend1->GetRenderAngles(), min1, max1, + pRend2->GetRenderOrigin(), pRend2->GetRenderAngles(), min2, max2, + 3.0f, pPlane ); +} + + +//----------------------------------------------------------------------------- +// Cull shadows based on rough bounding volumes +//----------------------------------------------------------------------------- +bool CClientShadowMgr::CullReceiver( ClientShadowHandle_t handle, IClientRenderable* pRenderable, + IClientRenderable* pSourceRenderable ) +{ + // check flags here instead and assert !pSourceRenderable + if( m_Shadows[handle].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::CullReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + Assert( !pSourceRenderable ); + const Frustum_t &frustum = shadowmgr->GetFlashlightFrustum( m_Shadows[handle].m_ShadowHandle ); + + Vector mins, maxs; + pRenderable->GetRenderBoundsWorldspace( mins, maxs ); + + return R_CullBox( mins, maxs, frustum ); + } + + Assert( pSourceRenderable ); + // Compute a bounding sphere for the renderable + Vector origin; + float radius; + ComputeBoundingSphere( pRenderable, origin, radius ); + + // Transform the sphere center into the space of the shadow + Vector localOrigin; + const ClientShadow_t& shadow = m_Shadows[handle]; + const ShadowInfo_t& info = shadowmgr->GetInfo( shadow.m_ShadowHandle ); + Vector3DMultiplyPosition( shadow.m_WorldToShadow, origin, localOrigin ); + + // Compute a rough bounding box for the shadow (in shadow space) + Vector shadowMin, shadowMax; + shadowMin.Init( -shadow.m_WorldSize.x * 0.5f, -shadow.m_WorldSize.y * 0.5f, 0 ); + shadowMax.Init( shadow.m_WorldSize.x * 0.5f, shadow.m_WorldSize.y * 0.5f, info.m_MaxDist ); + + // If the bounding sphere doesn't intersect with the shadow volume, cull + if (!IsBoxIntersectingSphere( shadowMin, shadowMax, localOrigin, radius )) + return true; + + Vector originSource; + float radiusSource; + ComputeBoundingSphere( pSourceRenderable, originSource, radiusSource ); + + // Fast check for separating plane... + bool foundSeparatingPlane = false; + cplane_t plane; + if (!IsSphereIntersectingSphere( originSource, radiusSource, origin, radius )) + { + foundSeparatingPlane = true; + + // the plane normal doesn't need to be normalized... + VectorSubtract( origin, originSource, plane.normal ); + } + else + { + foundSeparatingPlane = ComputeSeparatingPlane( pRenderable, pSourceRenderable, &plane ); + } + + if (foundSeparatingPlane) + { + // Compute which side of the plane the renderable is on.. + Vector vecShadowDir = GetShadowDirection( pSourceRenderable ); + float shadowDot = DotProduct( vecShadowDir, plane.normal ); + float receiverDot = DotProduct( plane.normal, origin ); + float sourceDot = DotProduct( plane.normal, originSource ); + + if (shadowDot > 0.0f) + { + if (receiverDot <= sourceDot) + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f ); + return true; + } + else + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f ); + } + } + else + { + if (receiverDot >= sourceDot) + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), -50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f ); + return true; + } + else + { +// Vector dest; +// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest ); +// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f ); + } + } + } + + // No additional clip planes? ok then it's a valid receiver + /* + if (shadow.m_ClipPlaneCount == 0) + return false; + + // Check the additional cull planes + int i; + for ( i = 0; i < shadow.m_ClipPlaneCount; ++i) + { + // Fast sphere cull + if (DotProduct( origin, shadow.m_ClipPlane[i] ) - radius > shadow.m_ClipDist[i]) + return true; + } + + // More expensive box on plane side cull... + Vector vec[3]; + Vector mins, maxs; + cplane_t plane; + AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); + pRenderable->GetBounds( mins, maxs ); + + for ( i = 0; i < shadow.m_ClipPlaneCount; ++i) + { + // Transform the plane into the space of the receiver + plane.normal.x = DotProduct( vec[0], shadow.m_ClipPlane[i] ); + plane.normal.y = DotProduct( vec[1], shadow.m_ClipPlane[i] ); + plane.normal.z = DotProduct( vec[2], shadow.m_ClipPlane[i] ); + + plane.dist = shadow.m_ClipDist[i] - DotProduct( shadow.m_ClipPlane[i], pRenderable->GetRenderOrigin() ); + + // If the box is on the front side of the plane, we're done. + if (BoxOnPlaneSide2( mins, maxs, &plane, 3.0f ) == 1) + return true; + } + */ + + return false; +} + + +//----------------------------------------------------------------------------- +// deals with shadows being added to shadow receivers +//----------------------------------------------------------------------------- +void CClientShadowMgr::AddShadowToReceiver( ClientShadowHandle_t handle, + IClientRenderable* pRenderable, ShadowReceiver_t type ) +{ + ClientShadow_t &shadow = m_Shadows[handle]; + + // Don't add a shadow cast by an object to itself... + IClientRenderable* pSourceRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + // NOTE: if pSourceRenderable == NULL, the source is probably a flashlight since there is no entity. + if (pSourceRenderable == pRenderable) + return; + + // Don't bother if this renderable doesn't receive shadows or light from flashlights + if( !pRenderable->ShouldReceiveProjectedTextures( SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) ) + return; + + // Cull if the origin is on the wrong side of a shadow clip plane.... + if ( CullReceiver( handle, pRenderable, pSourceRenderable ) ) + return; + + // Do different things depending on the receiver type + switch( type ) + { + case SHADOW_RECEIVER_BRUSH_MODEL: + + if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) ) + { + shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle, + const_cast(pRenderable->GetModel()), + pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() ); + + shadowmgr->AddFlashlightRenderable( shadow.m_ShadowHandle, pRenderable ); + } + } + else + { + shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle, + const_cast(pRenderable->GetModel()), + pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() ); + } + break; + + case SHADOW_RECEIVER_STATIC_PROP: + // Don't add shadows to props if we're not using render-to-texture + if ( GetActualShadowCastType( handle ) == SHADOWS_RENDER_TO_TEXTURE ) + { + // Also don't add them unless an NPC or player casts them.. + // They are wickedly expensive!!! + C_BaseEntity *pEnt = pSourceRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEnt && ( pEnt->GetFlags() & (FL_NPC | FL_CLIENT)) ) + { + staticpropmgr->AddShadowToStaticProp( shadow.m_ShadowHandle, pRenderable ); + } + } + else if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) ) + { + staticpropmgr->AddShadowToStaticProp( shadow.m_ShadowHandle, pRenderable ); + + shadowmgr->AddFlashlightRenderable( shadow.m_ShadowHandle, pRenderable ); + } + } + break; + + case SHADOW_RECEIVER_STUDIO_MODEL: + if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) + { + VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) ) + { + pRenderable->CreateModelInstance(); + shadowmgr->AddShadowToModel( shadow.m_ShadowHandle, pRenderable->GetModelInstance() ); + shadowmgr->AddFlashlightRenderable( shadow.m_ShadowHandle, pRenderable ); + } + } + break; +// default: + } +} + + +//----------------------------------------------------------------------------- +// deals with shadows being added to shadow receivers +//----------------------------------------------------------------------------- +void CClientShadowMgr::RemoveAllShadowsFromReceiver( + IClientRenderable* pRenderable, ShadowReceiver_t type ) +{ + // Don't bother if this renderable doesn't receive shadows + if ( !pRenderable->ShouldReceiveProjectedTextures( SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) ) + return; + + // Do different things depending on the receiver type + switch( type ) + { + case SHADOW_RECEIVER_BRUSH_MODEL: + { + model_t* pModel = const_cast(pRenderable->GetModel()); + shadowmgr->RemoveAllShadowsFromBrushModel( pModel ); + } + break; + + case SHADOW_RECEIVER_STATIC_PROP: + staticpropmgr->RemoveAllShadowsFromStaticProp(pRenderable); + break; + + case SHADOW_RECEIVER_STUDIO_MODEL: + if( pRenderable && pRenderable->GetModelInstance() != MODEL_INSTANCE_INVALID ) + { + shadowmgr->RemoveAllShadowsFromModel( pRenderable->GetModelInstance() ); + } + break; + +// default: +// // FIXME: How do deal with this stuff? Add a method to IClientRenderable? +// C_BaseEntity* pEnt = static_cast(pRenderable); +// pEnt->RemoveAllShadows(); + } +} + + +//----------------------------------------------------------------------------- +// Computes + sets the render-to-texture texcoords +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetRenderToTextureShadowTexCoords( ShadowHandle_t handle, int x, int y, int w, int h ) +{ + // Let the shadow mgr know about the texture coordinates... + // That way it'll be able to batch rendering better. + int textureW, textureH; + m_ShadowAllocator.GetTotalTextureSize( textureW, textureH ); + + // Go in a half-pixel to avoid blending with neighboring textures.. + float u, v, du, dv; + + u = ((float)x + 0.5f) / (float)textureW; + v = ((float)y + 0.5f) / (float)textureH; + du = ((float)w - 1) / (float)textureW; + dv = ((float)h - 1) / (float)textureH; + + shadowmgr->SetShadowTexCoord( handle, u, v, du, dv ); +} + + +//----------------------------------------------------------------------------- +// Setup all children shadows +//----------------------------------------------------------------------------- +bool CClientShadowMgr::BuildSetupShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild ) +{ + bool bDrewTexture = false; + + // Stop traversing when we hit a blobby shadow + ShadowType_t shadowType = GetActualShadowCastType( pRenderable ); + if ( pRenderable && shadowType == SHADOWS_SIMPLE ) + return false; + + if ( !pRenderable || shadowType != SHADOWS_NONE ) + { + bool bDrawModelShadow; + if ( !bChild ) + { + bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0); + } + else + { + int nModelType = modelinfo->GetModelType( pRenderable->GetModel() ); + bDrawModelShadow = nModelType == mod_studio; + } + + if ( bDrawModelShadow ) + { + C_BaseEntity *pEntity = pRenderable->GetIClientUnknown()->GetBaseEntity(); + if ( pEntity ) + { + if ( pEntity->IsNPC() ) + { + s_NPCShadowBoneSetups.AddToTail( assert_cast( pEntity ) ); + } + else if ( pEntity->GetBaseAnimating() ) + { + s_NonNPCShadowBoneSetups.AddToTail( assert_cast( pEntity ) ); + } + + } + bDrewTexture = true; + } + } + + if ( !pRenderable ) + return bDrewTexture; + + IClientRenderable *pChild; + for ( pChild = pRenderable->FirstShadowChild(); pChild; pChild = pChild->NextShadowPeer() ) + { + if ( BuildSetupShadowHierarchy( pChild, shadow, true ) ) + { + bDrewTexture = true; + } + } + return bDrewTexture; +} + +//----------------------------------------------------------------------------- +// Draws all children shadows into our own +//----------------------------------------------------------------------------- +bool CClientShadowMgr::DrawShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild ) +{ + bool bDrewTexture = false; + + // Stop traversing when we hit a blobby shadow + ShadowType_t shadowType = GetActualShadowCastType( pRenderable ); + if ( pRenderable && shadowType == SHADOWS_SIMPLE ) + return false; + + if ( !pRenderable || shadowType != SHADOWS_NONE ) + { + bool bDrawModelShadow; + bool bDrawBrushShadow; + if ( !bChild ) + { + bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0); + bDrawBrushShadow = !bDrawModelShadow; + } + else + { + int nModelType = modelinfo->GetModelType( pRenderable->GetModel() ); + bDrawModelShadow = nModelType == mod_studio; + bDrawBrushShadow = nModelType == mod_brush; + } + + if ( bDrawModelShadow ) + { + DrawModelInfo_t info; + matrix3x4_t *pBoneToWorld = modelrender->DrawModelShadowSetup( pRenderable, pRenderable->GetBody(), pRenderable->GetSkin(), &info ); + if ( pBoneToWorld ) + { + modelrender->DrawModelShadow( pRenderable, info, pBoneToWorld ); + } + bDrewTexture = true; + } + else if ( bDrawBrushShadow ) + { + render->DrawBrushModelShadow( pRenderable ); + bDrewTexture = true; + } + } + + if ( !pRenderable ) + return bDrewTexture; + + IClientRenderable *pChild; + for ( pChild = pRenderable->FirstShadowChild(); pChild; pChild = pChild->NextShadowPeer() ) + { + if ( DrawShadowHierarchy( pChild, shadow, true ) ) + { + bDrewTexture = true; + } + } + return bDrewTexture; +} + +//----------------------------------------------------------------------------- +// This gets called with every shadow that potentially will need to re-render +//----------------------------------------------------------------------------- +bool CClientShadowMgr::BuildSetupListForRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ) +{ + ClientShadow_t& shadow = m_Shadows[clientShadowHandle]; + bool bDirtyTexture = (shadow.m_Flags & SHADOW_FLAGS_TEXTURE_DIRTY) != 0; + bool bNeedsRedraw = m_ShadowAllocator.UseTexture( shadow.m_ShadowTexture, bDirtyTexture, flArea ); + if ( bNeedsRedraw || bDirtyTexture ) + { + shadow.m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY; + + if ( !m_ShadowAllocator.HasValidTexture( shadow.m_ShadowTexture ) ) + return false; + + // shadow to be redrawn; for now, we'll always do it. + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + if ( BuildSetupShadowHierarchy( pRenderable, shadow ) ) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// This gets called with every shadow that potentially will need to re-render +//----------------------------------------------------------------------------- +bool CClientShadowMgr::DrawRenderToTextureShadow( unsigned short clientShadowHandle, float flArea ) +{ + ClientShadow_t& shadow = m_Shadows[clientShadowHandle]; + + // If we were previously using the LOD shadow, set the material + bool bPreviouslyUsingLODShadow = ( shadow.m_Flags & SHADOW_FLAGS_USING_LOD_SHADOW ) != 0; + shadow.m_Flags &= ~SHADOW_FLAGS_USING_LOD_SHADOW; + if ( bPreviouslyUsingLODShadow ) + { + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_RenderShadow, m_RenderModelShadow, (void*)clientShadowHandle ); + } + + // Mark texture as being used... + bool bDirtyTexture = (shadow.m_Flags & SHADOW_FLAGS_TEXTURE_DIRTY) != 0; + bool bDrewTexture = false; + bool bNeedsRedraw = ( !m_bThreaded && m_ShadowAllocator.UseTexture( shadow.m_ShadowTexture, bDirtyTexture, flArea ) ); + + if ( !m_ShadowAllocator.HasValidTexture( shadow.m_ShadowTexture ) ) + { + DrawRenderToTextureShadowLOD( clientShadowHandle ); + return false; + } + + if ( bNeedsRedraw || bDirtyTexture ) + { + // shadow to be redrawn; for now, we'll always do it. + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + CMatRenderContextPtr pRenderContext( materials ); + + // Sets the viewport state + int x, y, w, h; + m_ShadowAllocator.GetTextureRect( shadow.m_ShadowTexture, x, y, w, h ); + pRenderContext->Viewport( IsX360() ? 0 : x, IsX360() ? 0 : y, w, h ); + + // Clear the selected viewport only (don't need to clear depth) + pRenderContext->ClearBuffers( true, false ); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->LoadMatrix( shadowmgr->GetInfo( shadow.m_ShadowHandle ).m_WorldToShadow ); + + if ( DrawShadowHierarchy( pRenderable, shadow ) ) + { + bDrewTexture = true; + if ( IsX360() ) + { + // resolve render target to system memory texture + Rect_t srcRect = { 0, 0, w, h }; + Rect_t dstRect = { x, y, w, h }; + pRenderContext->CopyRenderTargetToTextureEx( m_ShadowAllocator.GetTexture(), 0, &srcRect, &dstRect ); + } + } + else + { + // NOTE: Think the flags reset + texcoord set should only happen in DrawShadowHierarchy + // but it's 2 days before 360 ship.. not going to change this now. + DevMsg( "Didn't draw shadow hierarchy.. bad shadow texcoords probably going to happen..grab Brian!\n" ); + } + + // Only clear the dirty flag if the caster isn't animating + if ( (shadow.m_Flags & SHADOW_FLAGS_ANIMATING_SOURCE) == 0 ) + { + shadow.m_Flags &= ~SHADOW_FLAGS_TEXTURE_DIRTY; + } + + SetRenderToTextureShadowTexCoords( shadow.m_ShadowHandle, x, y, w, h ); + } + else if ( bPreviouslyUsingLODShadow ) + { + // In this case, we were previously using the LOD shadow, but we didn't + // have to reconstitute the texture. In this case, we need to reset the texcoord + int x, y, w, h; + m_ShadowAllocator.GetTextureRect( shadow.m_ShadowTexture, x, y, w, h ); + SetRenderToTextureShadowTexCoords( shadow.m_ShadowHandle, x, y, w, h ); + } + + return bDrewTexture; +} + + +//----------------------------------------------------------------------------- +// "Draws" the shadow LOD, which really means just set up the blobby shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::DrawRenderToTextureShadowLOD( unsigned short clientShadowHandle ) +{ + ClientShadow_t &shadow = m_Shadows[clientShadowHandle]; + if ( (shadow.m_Flags & SHADOW_FLAGS_USING_LOD_SHADOW) == 0 ) + { + shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_SimpleShadow, m_SimpleShadow, (void*)CLIENTSHADOW_INVALID_HANDLE ); + shadowmgr->SetShadowTexCoord( shadow.m_ShadowHandle, 0, 0, 1, 1 ); + ClearExtraClipPlanes( shadow.m_ShadowHandle ); + shadow.m_Flags |= SHADOW_FLAGS_USING_LOD_SHADOW; + } +} + + +//----------------------------------------------------------------------------- +// Advances to the next frame, +//----------------------------------------------------------------------------- +void CClientShadowMgr::AdvanceFrame() +{ + // We're starting the next frame + m_ShadowAllocator.AdvanceFrame(); +} + + +//----------------------------------------------------------------------------- +// Re-render shadow depth textures that lie in the leaf list +//----------------------------------------------------------------------------- +int CClientShadowMgr::BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows ) +{ + int nActiveDepthShadowCount = 0; + for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + + // If this is not a flashlight which should use a shadow depth texture, skip! + if ( ( shadow.m_Flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) == 0 ) + continue; + + const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ); + + // Bail if this flashlight doesn't want shadows + if ( !flashlightState.m_bEnableShadows ) + continue; + + // Calculate an AABB around the shadow frustum + Vector vecAbsMins, vecAbsMaxs; + CalculateAABBFromProjectionMatrix( shadow.m_WorldToShadow, &vecAbsMins, &vecAbsMaxs ); + + Frustum_t viewFrustum; + GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum ); + + // FIXME: Could do other sorts of culling here, such as frustum-frustum test, distance etc. + // If it's not in the view frustum, move on + if ( R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) + { + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); + continue; + } + + if ( nActiveDepthShadowCount >= nMaxDepthShadows ) + { + static bool s_bOverflowWarning = false; + if ( !s_bOverflowWarning ) + { + Warning( "Too many depth textures rendered in a single view!\n" ); + Assert( 0 ); + s_bOverflowWarning = true; + } + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); + continue; + } + + pActiveDepthShadows[nActiveDepthShadowCount++] = i; + } + return nActiveDepthShadowCount; +} + + +//----------------------------------------------------------------------------- +// Sets the view's active flashlight render state +//----------------------------------------------------------------------------- +void CClientShadowMgr::SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights ) +{ + // NOTE: On the 360, we render the entire scene with the flashlight state + // set and don't render flashlights additively in the shadow mgr at a far later time + // because the CPU costs are prohibitive + if ( !IsX360() && !r_flashlight_version2.GetInt() ) + return; + + Assert( nActiveFlashlightCount<= 1 ); + if ( nActiveFlashlightCount > 0 ) + { + Assert( ( m_Shadows[ pActiveFlashlights[0] ].m_Flags & SHADOW_FLAGS_FLASHLIGHT ) != 0 ); + shadowmgr->SetFlashlightRenderState( pActiveFlashlights[0] ); + } + else + { + shadowmgr->SetFlashlightRenderState( SHADOW_HANDLE_INVALID ); + } +} + + +//----------------------------------------------------------------------------- +// Re-render shadow depth textures that lie in the leaf list +//----------------------------------------------------------------------------- +void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup ) +{ + VPROF_BUDGET( "CClientShadowMgr::ComputeShadowDepthTextures", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + CMatRenderContextPtr pRenderContext( materials ); + PIXEVENT( pRenderContext, "Shadow Depth Textures" ); + + // Build list of active render-to-texture shadows + ClientShadowHandle_t pActiveDepthShadows[1024]; + int nActiveDepthShadowCount = BuildActiveShadowDepthList( viewSetup, ARRAYSIZE( pActiveDepthShadows ), pActiveDepthShadows ); + + // Iterate over all existing textures and allocate shadow textures + bool bDebugFrustum = r_flashlightdrawfrustum.GetBool(); + for ( int j = 0; j < nActiveDepthShadowCount; ++j ) + { + ClientShadow_t& shadow = m_Shadows[ pActiveDepthShadows[j] ]; + + CTextureReference shadowDepthTexture; + bool bGotShadowDepthTexture = LockShadowDepthTexture( &shadowDepthTexture ); + if ( !bGotShadowDepthTexture ) + { + // If we don't get one, that means we have too many this frame so bind no depth texture + static int bitchCount = 0; + if( bitchCount < 10 ) + { + Warning( "Too many shadow maps this frame!\n" ); + bitchCount++; + } + + Assert(0); + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); + continue; + } + + CViewSetup shadowView; + shadowView.m_flAspectRatio = 1.0f; + shadowView.x = shadowView.y = 0; + shadowView.width = shadowDepthTexture->GetActualWidth(); + shadowView.height = shadowDepthTexture->GetActualHeight(); + shadowView.m_bOrtho = false; + shadowView.m_bDoBloomAndToneMapping = false; + + // Copy flashlight parameters + const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ); + shadowView.fov = shadowView.fovViewmodel = flashlightState.m_fHorizontalFOVDegrees; + shadowView.origin = flashlightState.m_vecLightOrigin; + QuaternionAngles( flashlightState.m_quatOrientation, shadowView.angles ); // Convert from Quaternion to QAngle + + shadowView.zNear = shadowView.zNearViewmodel = flashlightState.m_NearZ; + shadowView.zFar = shadowView.zFarViewmodel = flashlightState.m_FarZ; + + // Can turn on all light frustum overlays or per light with flashlightState parameter... + if ( bDebugFrustum || flashlightState.m_bDrawShadowFrustum ) + { + DebugDrawFrustum( shadowView.origin, shadow.m_WorldToShadow ); + } + + // Set depth bias factors specific to this flashlight + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->SetShadowDepthBiasFactors( flashlightState.m_flShadowSlopeScaleDepthBias, flashlightState.m_flShadowDepthBias ); + + // Render to the shadow depth texture with appropriate view + view->UpdateShadowDepthTexture( m_DummyColorTexture, shadowDepthTexture, shadowView ); + + // Associate the shadow depth texture and stencil bit with the flashlight for use during scene rendering + shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, shadowDepthTexture, 0 ); + } + + SetViewFlashlightState( nActiveDepthShadowCount, pActiveDepthShadows ); +} + + +//----------------------------------------------------------------------------- +// Re-renders all shadow textures for shadow casters that lie in the leaf list +//----------------------------------------------------------------------------- +static void SetupBonesOnBaseAnimating( C_BaseAnimating *&pBaseAnimating ) +{ + pBaseAnimating->SetupBones( NULL, -1, -1, gpGlobals->curtime ); +} + + +void CClientShadowMgr::ComputeShadowTextures( const CViewSetup &view, int leafCount, LeafIndex_t* pLeafList ) +{ + VPROF_BUDGET( "CClientShadowMgr::ComputeShadowTextures", VPROF_BUDGETGROUP_SHADOW_RENDERING ); + + if ( !m_RenderToTextureActive || (r_shadows.GetInt() == 0) || r_shadows_gamecontrol.GetInt() == 0 ) + return; + + m_bThreaded = ( r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads() ); + + MDLCACHE_CRITICAL_SECTION(); + // First grab all shadow textures we may want to render + int nCount = s_VisibleShadowList.FindShadows( &view, leafCount, pLeafList ); + if ( nCount == 0 ) + return; + + // FIXME: Add heuristics based on distance, etc. to futz with + // the shadow allocator + to select blobby shadows + + CMatRenderContextPtr pRenderContext( materials ); + + PIXEVENT( pRenderContext, "Render-To-Texture Shadows" ); + + // Clear to white (color unused), black alpha + pRenderContext->ClearColor4ub( 255, 255, 255, 0 ); + + // No height clip mode please. + MaterialHeightClipMode_t oldHeightClipMode = pRenderContext->GetHeightClipMode(); + pRenderContext->SetHeightClipMode( MATERIAL_HEIGHTCLIPMODE_DISABLE ); + + // No projection matrix (orthographic mode) + // FIXME: Make it work for projective shadows? + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + pRenderContext->Scale( 1, -1, 1 ); + pRenderContext->Ortho( 0, 0, 1, 1, -9999, 0 ); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + + pRenderContext->PushRenderTargetAndViewport( m_ShadowAllocator.GetTexture() ); + + if ( !IsX360() && m_bRenderTargetNeedsClear ) + { + // don't need to clear absent depth buffer + pRenderContext->ClearBuffers( true, false ); + m_bRenderTargetNeedsClear = false; + } + + int nMaxShadows = r_shadowmaxrendered.GetInt(); + int nModelsRendered = 0; + int i; + + if ( m_bThreaded && g_pThreadPool->NumIdleThreads() ) + { + s_NPCShadowBoneSetups.RemoveAll(); + s_NonNPCShadowBoneSetups.RemoveAll(); + + for (i = 0; i < nCount; ++i) + { + const VisibleShadowInfo_t &info = s_VisibleShadowList.GetVisibleShadow(i); + if ( nModelsRendered < nMaxShadows ) + { + if ( BuildSetupListForRenderToTextureShadow( info.m_hShadow, info.m_flArea ) ) + { + ++nModelsRendered; + } + } + } + + ParallelProcess( s_NPCShadowBoneSetups.Base(), s_NPCShadowBoneSetups.Count(), &SetupBonesOnBaseAnimating ); + ParallelProcess( s_NonNPCShadowBoneSetups.Base(), s_NonNPCShadowBoneSetups.Count(), &SetupBonesOnBaseAnimating ); + + nModelsRendered = 0; + } + + for (i = 0; i < nCount; ++i) + { + const VisibleShadowInfo_t &info = s_VisibleShadowList.GetVisibleShadow(i); + if ( nModelsRendered < nMaxShadows ) + { + if ( DrawRenderToTextureShadow( info.m_hShadow, info.m_flArea ) ) + { + ++nModelsRendered; + } + } + else + { + DrawRenderToTextureShadowLOD( info.m_hShadow ); + } + } + + // Render to the backbuffer again + pRenderContext->PopRenderTargetAndViewport(); + + // Restore the matrices + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); + + pRenderContext->SetHeightClipMode( oldHeightClipMode ); + + pRenderContext->SetHeightClipMode( oldHeightClipMode ); + + // Restore the clear color + pRenderContext->ClearColor3ub( 0, 0, 0 ); +} + +//------------------------------------------------------------------------------------------------------- +// Lock down the usage of a shadow depth texture...must be unlocked for use on subsequent views / frames +//------------------------------------------------------------------------------------------------------- +bool CClientShadowMgr::LockShadowDepthTexture( CTextureReference *shadowDepthTexture ) +{ + for ( int i=0; i < m_DepthTextureCache.Count(); i++ ) // Search for cached shadow depth texture + { + if ( m_DepthTextureCacheLocks[i] == false ) // If a free one is found + { + *shadowDepthTexture = m_DepthTextureCache[i]; + m_DepthTextureCacheLocks[i] = true; + return true; + } + } + + return false; // Didn't find it... +} + +//------------------------------------------------------------------ +// Unlock shadow depth texture for use on subsequent views / frames +//------------------------------------------------------------------ +void CClientShadowMgr::UnlockAllShadowDepthTextures() +{ + for (int i=0; i< m_DepthTextureCache.Count(); i++ ) + { + m_DepthTextureCacheLocks[i] = false; + } + SetViewFlashlightState( 0, NULL ); +} + +void CClientShadowMgr::SetFlashlightTarget( ClientShadowHandle_t shadowHandle, EHANDLE targetEntity ) +{ + Assert( m_Shadows.IsValidIndex( shadowHandle ) ); + + CClientShadowMgr::ClientShadow_t &shadow = m_Shadows[ shadowHandle ]; + if( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + return; + +// shadow.m_pTargetRenderable = pRenderable; + shadow.m_hTargetEntity = targetEntity; +} + + +void CClientShadowMgr::SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld ) +{ + Assert( m_Shadows.IsValidIndex( shadowHandle ) ); + + ClientShadow_t &shadow = m_Shadows[ shadowHandle ]; + if( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + return; + + if ( bLightWorld ) + { + shadow.m_Flags |= SHADOW_FLAGS_LIGHT_WORLD; + } + else + { + shadow.m_Flags &= ~SHADOW_FLAGS_LIGHT_WORLD; + } +} + + +bool CClientShadowMgr::IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IClientRenderable *pRenderable ) +{ + ClientShadow_t &shadow = m_Shadows[ shadowHandle ]; + + if( shadow.m_hTargetEntity->GetClientRenderable() == pRenderable ) + return true; + + C_BaseEntity *pChild = shadow.m_hTargetEntity->FirstMoveChild(); + while( pChild ) + { + if( pChild->GetClientRenderable()==pRenderable ) + return true; + + pChild = pChild->NextMovePeer(); + } + + return false; +} + +//----------------------------------------------------------------------------- +// A material proxy that resets the base texture to use the rendered shadow +//----------------------------------------------------------------------------- +class CShadowProxy : public IMaterialProxy +{ +public: + CShadowProxy(); + virtual ~CShadowProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pProxyData ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +private: + IMaterialVar* m_BaseTextureVar; +}; + +CShadowProxy::CShadowProxy() +{ + m_BaseTextureVar = NULL; +} + +CShadowProxy::~CShadowProxy() +{ +} + + +bool CShadowProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool foundVar; + m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); + return foundVar; +} + +void CShadowProxy::OnBind( void *pProxyData ) +{ + unsigned short clientShadowHandle = ( unsigned short )pProxyData; + ITexture* pTex = s_ClientShadowMgr.GetShadowTexture( clientShadowHandle ); + m_BaseTextureVar->SetTextureValue( pTex ); + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CShadowProxy::GetMaterial() +{ + return m_BaseTextureVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CShadowProxy, IMaterialProxy, "Shadow" IMATERIAL_PROXY_INTERFACE_VERSION ); + + + +//----------------------------------------------------------------------------- +// A material proxy that resets the base texture to use the rendered shadow +//----------------------------------------------------------------------------- +class CShadowModelProxy : public IMaterialProxy +{ +public: + CShadowModelProxy(); + virtual ~CShadowModelProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pProxyData ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +private: + IMaterialVar* m_BaseTextureVar; + IMaterialVar* m_BaseTextureOffsetVar; + IMaterialVar* m_BaseTextureScaleVar; + IMaterialVar* m_BaseTextureMatrixVar; + IMaterialVar* m_FalloffOffsetVar; + IMaterialVar* m_FalloffDistanceVar; + IMaterialVar* m_FalloffAmountVar; +}; + +CShadowModelProxy::CShadowModelProxy() +{ + m_BaseTextureVar = NULL; + m_BaseTextureOffsetVar = NULL; + m_BaseTextureScaleVar = NULL; + m_BaseTextureMatrixVar = NULL; + m_FalloffOffsetVar = NULL; + m_FalloffDistanceVar = NULL; + m_FalloffAmountVar = NULL; +} + +CShadowModelProxy::~CShadowModelProxy() +{ +} + + +bool CShadowModelProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool foundVar; + m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); + if (!foundVar) + return false; + m_BaseTextureOffsetVar = pMaterial->FindVar( "$basetextureoffset", &foundVar, false ); + if (!foundVar) + return false; + m_BaseTextureScaleVar = pMaterial->FindVar( "$basetexturescale", &foundVar, false ); + if (!foundVar) + return false; + m_BaseTextureMatrixVar = pMaterial->FindVar( "$basetexturetransform", &foundVar, false ); + if (!foundVar) + return false; + m_FalloffOffsetVar = pMaterial->FindVar( "$falloffoffset", &foundVar, false ); + if (!foundVar) + return false; + m_FalloffDistanceVar = pMaterial->FindVar( "$falloffdistance", &foundVar, false ); + if (!foundVar) + return false; + m_FalloffAmountVar = pMaterial->FindVar( "$falloffamount", &foundVar, false ); + return foundVar; +} + +void CShadowModelProxy::OnBind( void *pProxyData ) +{ + unsigned short clientShadowHandle = ( unsigned short )pProxyData; + ITexture* pTex = s_ClientShadowMgr.GetShadowTexture( clientShadowHandle ); + m_BaseTextureVar->SetTextureValue( pTex ); + + const ShadowInfo_t& info = s_ClientShadowMgr.GetShadowInfo( clientShadowHandle ); + m_BaseTextureMatrixVar->SetMatrixValue( info.m_WorldToShadow ); + m_BaseTextureOffsetVar->SetVecValue( info.m_TexOrigin.Base(), 2 ); + m_BaseTextureScaleVar->SetVecValue( info.m_TexSize.Base(), 2 ); + m_FalloffOffsetVar->SetFloatValue( info.m_FalloffOffset ); + m_FalloffDistanceVar->SetFloatValue( info.m_MaxDist ); + m_FalloffAmountVar->SetFloatValue( info.m_FalloffAmount ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +IMaterial *CShadowModelProxy::GetMaterial() +{ + return m_BaseTextureVar->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CShadowModelProxy, IMaterialProxy, "ShadowModel" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/clientsideeffects.cpp b/game/client/clientsideeffects.cpp new file mode 100644 index 00000000..daf14f23 --- /dev/null +++ b/game/client/clientsideeffects.cpp @@ -0,0 +1,234 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "clientsideeffects.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +bool g_FXCreationAllowed = false; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void SetFXCreationAllowed( bool state ) +{ + g_FXCreationAllowed = state; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FXCreationAllowed( void ) +{ + return g_FXCreationAllowed; +} + +// TODO: Sort effects and their children back to front from view positon? At least with buckets or something. + +// +//----------------------------------------------------------------------------- +// Purpose: Construct and activate effect +// Input : *name - +//----------------------------------------------------------------------------- +CClientSideEffect::CClientSideEffect( const char *name ) +{ + m_pszName = name; + Assert( name ); + + m_bActive = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Destroy effect +//----------------------------------------------------------------------------- +CClientSideEffect::~CClientSideEffect( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get name of effect +// Output : const char +//----------------------------------------------------------------------------- +const char *CClientSideEffect::GetName( void ) +{ + return m_pszName; +} + +//----------------------------------------------------------------------------- +// Purpose: Is effect still active? +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CClientSideEffect::IsActive( void ) +{ + return m_bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: Mark effect for destruction +//----------------------------------------------------------------------------- +void CClientSideEffect::Destroy( void ) +{ + m_bActive = false; +} + +#define MAX_EFFECTS 256 + +//----------------------------------------------------------------------------- +// Purpose: Implements effects list interface +//----------------------------------------------------------------------------- +class CEffectsList : public IEffectsList +{ +public: + CEffectsList( void ); + virtual ~CEffectsList( void ); + + // Add an effect to the effects list + void AddEffect( CClientSideEffect *effect ); + // Remove the specified effect + // Draw/update all effects in the current list + void DrawEffects( double frametime ); + // Flush out all effects from the list + void Flush( void ); +private: + void RemoveEffect( int effectIndex ); + // Current number of effects + int m_nEffects; + // Pointers to current effects + CClientSideEffect *m_rgEffects[ MAX_EFFECTS ]; +}; + +// Implements effects list and exposes interface +static CEffectsList g_EffectsList; +// Public interface +IEffectsList *clienteffects = ( IEffectsList * )&g_EffectsList; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEffectsList::CEffectsList( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEffectsList::~CEffectsList( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Add effect to effects list +// Input : *effect - +//----------------------------------------------------------------------------- +void CEffectsList::AddEffect( CClientSideEffect *effect ) +{ +#if 0 + if ( FXCreationAllowed() == false ) + { + //NOTENOTE: If you've hit this, you may not add a client effect where you have attempted to. + // Most often this means that you have added it in an entity's DrawModel function. + // Move this to the ClientThink function instead! + + Assert(0); + return; + } +#endif + + if ( effect == NULL ) + return; + + if ( m_nEffects >= MAX_EFFECTS ) + { + DevWarning( 1, "No room for effect %s\n", effect->GetName() ); + return; + } + + m_rgEffects[ m_nEffects++ ] = effect; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove specified effect by index +// Input : effectIndex - +//----------------------------------------------------------------------------- +void CEffectsList::RemoveEffect( int effectIndex ) +{ + if ( effectIndex >= m_nEffects || effectIndex < 0 ) + return; + + CClientSideEffect *pEffect = m_rgEffects[effectIndex]; + m_nEffects--; + + if ( m_nEffects > 0 && effectIndex != m_nEffects ) + { + // move the last one down to fill the empty slot + m_rgEffects[effectIndex] = m_rgEffects[m_nEffects]; + } + + pEffect->Destroy(); + + delete pEffect; //FIXME: Yes, no? +} + +//----------------------------------------------------------------------------- +// Purpose: Iterate through list and simulate/draw stuff +// Input : frametime - +//----------------------------------------------------------------------------- +void CEffectsList::DrawEffects( double frametime ) +{ + VPROF_BUDGET( "CEffectsList::DrawEffects", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + int i; + CClientSideEffect *effect; + + // Go backwards so deleting effects doesn't screw up + for ( i = m_nEffects - 1 ; i >= 0; i-- ) + { + effect = m_rgEffects[ i ]; + if ( !effect ) + continue; + + // Simulate + effect->Draw( frametime ); + + // Remove it if needed + if ( !effect->IsActive() ) + { + RemoveEffect( i ); + } + } +} + +//================================================== +// Purpose: +// Input: +//================================================== + +void CEffectsList::Flush( void ) +{ + int i; + CClientSideEffect *effect; + + // Go backwards so deleting effects doesn't screw up + for ( i = m_nEffects - 1 ; i >= 0; i-- ) + { + effect = m_rgEffects[ i ]; + + if ( effect == NULL ) + continue; + + RemoveEffect( i ); + } +} diff --git a/game/client/clientsideeffects.h b/game/client/clientsideeffects.h new file mode 100644 index 00000000..e9c1167d --- /dev/null +++ b/game/client/clientsideeffects.h @@ -0,0 +1,93 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CLIENTSIDEEFFECTS_H +#define CLIENTSIDEEFFECTS_H +#ifdef _WIN32 +#pragma once +#endif + +class Vector; +struct FXQuadData_t; +struct FXLineData_t; +//----------------------------------------------------------------------------- +// Purpose: Base class for client side effects +//----------------------------------------------------------------------------- +abstract_class CClientSideEffect +{ +public: + // Constructs the named effect + CClientSideEffect( const char *name ); + virtual ~CClientSideEffect( void ); + + // Update/Draw the effect + // Derived classes must implement this method! + virtual void Draw( double frametime ) = 0; + // Returns name of effect + virtual const char *GetName( void ); + // Retuns whether the effect is still active + virtual bool IsActive( void ); + // Sets the effect to inactive so it can be destroed + virtual void Destroy( void ); + +private: + // Name of effect ( static data ) + const char *m_pszName; + // Is the effect active + bool m_bActive; +}; + +//----------------------------------------------------------------------------- +// Purpose: Base interface to effects list +//----------------------------------------------------------------------------- +abstract_class IEffectsList +{ +public: + virtual ~IEffectsList( void ) {} + + // Add an effect to the list of effects + virtual void AddEffect( CClientSideEffect *effect ) = 0; + // Simulate/Update/Draw effects on list + virtual void DrawEffects( double frametime ) = 0; + // Flush out all effects fbrom the list + virtual void Flush( void ) = 0; +}; + +extern IEffectsList *clienteffects; + +class IMaterialSystem; +extern IMaterialSystem *materials; + +//Actual function references +void FX_AddCube( const Vector &mins, const Vector &maxs, const Vector &vColor, float life, const char *materialName ); +void FX_AddCenteredCube( const Vector ¢er, float size, const Vector &vColor, float life, const char *materialName ); +void FX_AddStaticLine( const Vector& start, const Vector& end, float scale, float life, const char *materialName, unsigned char flags ); +void FX_AddDiscreetLine( const Vector& start, const Vector& direction, float velocity, float length, float clipLength, float scale, float life, const char *shader ); + +void FX_AddLine( const FXLineData_t &data ); + +void FX_AddQuad( const FXQuadData_t &data ); + +void FX_AddQuad( const Vector &origin, + const Vector &normal, + float startSize, + float endSize, + float sizeBias, + float startAlpha, + float endAlpha, + float alphaBias, + float yaw, + float deltaYaw, + const Vector &color, + float lifeTime, + const char *shader, + unsigned int flags ); + +// For safe addition of client effects +void SetFXCreationAllowed( bool state ); +bool FXCreationAllowed( void ); + +#endif // CLIENTSIDEEFFECTS_H diff --git a/game/client/clientsideeffects_test.cpp b/game/client/clientsideeffects_test.cpp new file mode 100644 index 00000000..93a74c24 --- /dev/null +++ b/game/client/clientsideeffects_test.cpp @@ -0,0 +1,316 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "engine/IEngineSound.h" +#include "view.h" + +#include "fx_line.h" +#include "fx_discreetline.h" +#include "fx_quad.h" +#include "clientsideeffects.h" + +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "tier0/vprof.h" +#include "CollisionUtils.h" +#include "ClientEffectPrecacheSystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//FIXME: All these functions will be moved out to FX_Main.CPP or a individual folder + +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectsTest ) +CLIENTEFFECT_MATERIAL( "effects/spark" ) +CLIENTEFFECT_MATERIAL( "effects/gunshiptracer" ) +CLIENTEFFECT_MATERIAL( "effects/bluespark" ) +CLIENTEFFECT_REGISTER_END() + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void FX_AddLine( const FXLineData_t &data ) +{ + CFXLine *t = new CFXLine( "Line", data ); + assert( t ); + + //Throw it into the list + clienteffects->AddEffect( t ); +} + +/* +================================================== +FX_AddStaticLine +================================================== +*/ + +void FX_AddStaticLine( const Vector& start, const Vector& end, float scale, float life, const char *materialName, unsigned char flags ) +{ + CFXStaticLine *t = new CFXStaticLine( "StaticLine", start, end, scale, life, materialName, flags ); + assert( t ); + + //Throw it into the list + clienteffects->AddEffect( t ); +} + +/* +================================================== +FX_AddDiscreetLine +================================================== +*/ + +void FX_AddDiscreetLine( const Vector& start, const Vector& direction, float velocity, float length, float clipLength, float scale, float life, const char *shader ) +{ + CFXDiscreetLine *t = new CFXDiscreetLine( "Line", start, direction, velocity, length, clipLength, scale, life, shader ); + assert( t ); + + //Throw it into the list + clienteffects->AddEffect( t ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void FX_AddQuad( const FXQuadData_t &data ) +{ + CFXQuad *quad = new CFXQuad( data ); + + Assert( quad != NULL ); + + clienteffects->AddEffect( quad ); +} +//----------------------------------------------------------------------------- +// Purpose: Parameter heavy version +//----------------------------------------------------------------------------- +void FX_AddQuad( const Vector &origin, + const Vector &normal, + float startSize, + float endSize, + float sizeBias, + float startAlpha, + float endAlpha, + float alphaBias, + float yaw, + float deltaYaw, + const Vector &color, + float lifeTime, + const char *shader, + unsigned int flags ) +{ + FXQuadData_t data; + + //Setup the data + data.SetAlpha( startAlpha, endAlpha ); + data.SetScale( startSize, endSize ); + data.SetFlags( flags ); + data.SetMaterial( shader ); + data.SetNormal( normal ); + data.SetOrigin( origin ); + data.SetLifeTime( lifeTime ); + data.SetColor( color[0], color[1], color[2] ); + data.SetScaleBias( sizeBias ); + data.SetAlphaBias( alphaBias ); + data.SetYaw( yaw, deltaYaw ); + + //Output it + FX_AddQuad( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: Creates a test effect +//----------------------------------------------------------------------------- +void CreateTestEffect( void ) +{ + //NOTENOTE: Add any test effects here + //FX_BulletPass( NULL, NULL ); +} + + +/* +================================================== +FX_PlayerTracer +================================================== +*/ + +#define TRACER_BASE_OFFSET 8 + +void FX_PlayerTracer( Vector& start, Vector& end ) +{ + VPROF_BUDGET( "FX_PlayerTracer", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector shotDir, dStart, dEnd; + float length; + + //Find the direction of the tracer + VectorSubtract( end, start, shotDir ); + length = VectorNormalize( shotDir ); + + //We don't want to draw them if they're too close to us + if ( length < 256 ) + return; + + //Randomly place the tracer along this line, with a random length + VectorMA( start, TRACER_BASE_OFFSET + random->RandomFloat( -24.0f, 64.0f ), shotDir, dStart ); + VectorMA( dStart, ( length * random->RandomFloat( 0.1f, 0.6f ) ), shotDir, dEnd ); + + //Create the line + CFXStaticLine *t; + const char *materialName; + + //materialName = ( random->RandomInt( 0, 1 ) ) ? "effects/tracer_middle" : "effects/tracer_middle2"; + materialName = "effects/spark"; + + t = new CFXStaticLine( "Tracer", dStart, dEnd, random->RandomFloat( 0.5f, 0.75f ), 0.01f, materialName, 0 ); + assert( t ); + + //Throw it into the list + clienteffects->AddEffect( t ); +} + +/* +================================================== +FX_Tracer +================================================== +*/ +// Tracer must be this close to be considered for hearing +#define TRACER_MAX_HEAR_DIST (6*12) +#define TRACER_SOUND_TIME_MIN 0.1f +#define TRACER_SOUND_TIME_MAX 0.1f + + +class CBulletWhizTimer : public CAutoGameSystem +{ +public: + CBulletWhizTimer( char const *name ) : CAutoGameSystem( name ) + { + } + + void LevelInitPreEntity() + { + m_nextWhizTime = 0; + } + + float m_nextWhizTime; +}; + +// Client-side tracking for whiz noises +CBulletWhizTimer g_BulletWhiz( "CBulletWhizTimer" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecStart - +// &vecDir - +// iTracerType - +//----------------------------------------------------------------------------- +#define LISTENER_HEIGHT 24 + +void FX_TracerSound( const Vector &start, const Vector &end, int iTracerType ) +{ + const char *pszSoundName = NULL; + float flWhizDist = TRACER_MAX_HEAR_DIST; + float flMinWhizTime = TRACER_SOUND_TIME_MIN; + float flMaxWhizTime = TRACER_SOUND_TIME_MAX; + Vector vecListenOrigin = MainViewOrigin(); + switch( iTracerType ) + { + case TRACER_TYPE_DEFAULT: + { + pszSoundName = "Bullets.DefaultNearmiss"; + flWhizDist = 24; + + Ray_t bullet, listener; + bullet.Init( start, end ); + + Vector vecLower = vecListenOrigin; + vecLower.z -= LISTENER_HEIGHT; + listener.Init( vecListenOrigin, vecLower ); + + float s, t; + IntersectRayWithRay( bullet, listener, s, t ); + t = clamp( t, 0, 1 ); + vecListenOrigin.z -= t * LISTENER_HEIGHT; + } + break; + + case TRACER_TYPE_GUNSHIP: + pszSoundName = "Bullets.GunshipNearmiss"; + break; + + case TRACER_TYPE_STRIDER: + pszSoundName = "Bullets.StriderNearmiss"; + break; + + case TRACER_TYPE_WATERBULLET: + pszSoundName = "Underwater.BulletImpact"; + flWhizDist = 48; + flMinWhizTime = 0.3f; + flMaxWhizTime = 0.6f; + break; + + default: + return; + } + + if( !pszSoundName ) + return; + + // Is it time yet? + float dt = g_BulletWhiz.m_nextWhizTime - gpGlobals->curtime; + if ( dt > 0 ) + return; + + // Did the thing pass close enough to our head? + float vDist = CalcDistanceSqrToLineSegment( vecListenOrigin, start, end ); + if ( vDist >= (flWhizDist * flWhizDist) ) + return; + + CSoundParameters params; + if( C_BaseEntity::GetParametersForSound( pszSoundName, params, NULL ) ) + { + // Get shot direction + Vector shotDir; + VectorSubtract( end, start, shotDir ); + VectorNormalize( shotDir ); + + CLocalPlayerFilter filter; + enginesound->EmitSound( filter, SOUND_FROM_WORLD, CHAN_STATIC, params.soundname, + params.volume, SNDLVL_TO_ATTN(params.soundlevel), 0, params.pitch, &start, &shotDir, false); + } + + // FIXME: This has a bad behavior when both bullet + strider shots are whizzing by at the same time + // Could use different timers for the different types. + + // Don't play another bullet whiz for this client until this time has run out + g_BulletWhiz.m_nextWhizTime = gpGlobals->curtime + random->RandomFloat( flMinWhizTime, flMaxWhizTime ); +} + + +void FX_Tracer( Vector& start, Vector& end, int velocity, bool makeWhiz ) +{ + VPROF_BUDGET( "FX_Tracer", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + //Don't make small tracers + float dist; + Vector dir; + + VectorSubtract( end, start, dir ); + dist = VectorNormalize( dir ); + + // Don't make short tracers. + if ( dist >= 256 ) + { + float length = random->RandomFloat( 64.0f, 128.0f ); + float life = ( dist + length ) / velocity; //NOTENOTE: We want the tail to finish its run as well + + //Add it + FX_AddDiscreetLine( start, dir, velocity, length, dist, random->RandomFloat( 0.75f, 0.9f ), life, "effects/spark" ); + } + + if( makeWhiz ) + { + FX_TracerSound( start, end, TRACER_TYPE_DEFAULT ); + } +} diff --git a/game/client/colorcorrectionmgr.cpp b/game/client/colorcorrectionmgr.cpp new file mode 100644 index 00000000..287d00cc --- /dev/null +++ b/game/client/colorcorrectionmgr.cpp @@ -0,0 +1,119 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose : Singleton manager for color correction on the client +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "tier0/vprof.h" +#include "colorcorrectionmgr.h" + + +//------------------------------------------------------------------------------ +// Singleton access +//------------------------------------------------------------------------------ +static CColorCorrectionMgr s_ColorCorrectionMgr; +CColorCorrectionMgr *g_pColorCorrectionMgr = &s_ColorCorrectionMgr; + + +//------------------------------------------------------------------------------ +// Constructor +//------------------------------------------------------------------------------ +CColorCorrectionMgr::CColorCorrectionMgr() +{ + m_nActiveWeightCount = 0; +} + + +//------------------------------------------------------------------------------ +// Creates, destroys color corrections +//------------------------------------------------------------------------------ +ClientCCHandle_t CColorCorrectionMgr::AddColorCorrection( const char *pName, const char *pFileName ) +{ + if ( !pFileName ) + { + pFileName = pName; + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + ColorCorrectionHandle_t ccHandle = pRenderContext->AddLookup( pName ); + if ( ccHandle ) + { + pRenderContext->LockLookup( ccHandle ); + pRenderContext->LoadLookup( ccHandle, pFileName ); + pRenderContext->UnlockLookup( ccHandle ); + } + else + { + Warning("Cannot find color correction lookup file: '%s'\n", pFileName ); + } + + return (ClientCCHandle_t)ccHandle; +} + +void CColorCorrectionMgr::RemoveColorCorrection( ClientCCHandle_t h ) +{ + if ( h != INVALID_CLIENT_CCHANDLE ) + { + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + ColorCorrectionHandle_t ccHandle = (ColorCorrectionHandle_t)h; + pRenderContext->RemoveLookup( ccHandle ); + } +} + + +//------------------------------------------------------------------------------ +// Modify color correction weights +//------------------------------------------------------------------------------ +void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight ) +{ + if ( h != INVALID_CLIENT_CCHANDLE ) + { + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + ColorCorrectionHandle_t ccHandle = (ColorCorrectionHandle_t)h; + pRenderContext->SetLookupWeight( ccHandle, flWeight ); + + // FIXME: NOTE! This doesn't work if the same handle has + // its weight set twice with no intervening calls to ResetColorCorrectionWeights + // which, at the moment, is true + if ( flWeight != 0.0f ) + { + ++m_nActiveWeightCount; + } + } +} + +void CColorCorrectionMgr::ResetColorCorrectionWeights() +{ + VPROF_("ResetColorCorrectionWeights", 2, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, 0); + // FIXME: Where should I put this? It needs to happen prior to SimulateEntities() + // which is where the client thinks for c_colorcorrection + c_colorcorrectionvolumes + // update the color correction weights. + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->ResetLookupWeights(); + m_nActiveWeightCount = 0; +} + +void CColorCorrectionMgr::SetResetable( ClientCCHandle_t h, bool bResetable ) +{ + // NOTE: Setting stuff to be not resettable doesn't work when in queued mode + // because the logic that sets m_nActiveWeightCount to 0 in ResetColorCorrectionWeights + // is no longer valid when stuff is not resettable. + Assert( bResetable || !g_pMaterialSystem->GetThreadMode() == MATERIAL_SINGLE_THREADED ); + if ( h != INVALID_CLIENT_CCHANDLE ) + { + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + ColorCorrectionHandle_t ccHandle = (ColorCorrectionHandle_t)h; + pRenderContext->SetResetable( ccHandle, bResetable ); + } +} + + +//------------------------------------------------------------------------------ +// Is color correction active? +//------------------------------------------------------------------------------ +bool CColorCorrectionMgr::HasNonZeroColorCorrectionWeights() const +{ + return ( m_nActiveWeightCount != 0 ); +} diff --git a/game/client/colorcorrectionmgr.h b/game/client/colorcorrectionmgr.h new file mode 100644 index 00000000..1a62c449 --- /dev/null +++ b/game/client/colorcorrectionmgr.h @@ -0,0 +1,57 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose : Singleton manager for color correction on the client +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef COLORCORRECTIONMGR_H +#define COLORCORRECTIONMGR_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "igamesystem.h" + + +//------------------------------------------------------------------------------ +// Purpose : Singleton manager for color correction on the client +//------------------------------------------------------------------------------ +DECLARE_POINTER_HANDLE( ClientCCHandle_t ); +#define INVALID_CLIENT_CCHANDLE ( (ClientCCHandle_t)0 ) + +class CColorCorrectionMgr : public CBaseGameSystem +{ + // Inherited from IGameSystemPerFrame +public: + virtual char const *Name() { return "Color Correction Mgr"; } + + // Other public methods +public: + CColorCorrectionMgr(); + + // Create, destroy color correction + ClientCCHandle_t AddColorCorrection( const char *pName, const char *pFileName = NULL ); + void RemoveColorCorrection( ClientCCHandle_t ); + + // Modify color correction weights + void SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight ); + void ResetColorCorrectionWeights(); + void SetResetable( ClientCCHandle_t h, bool bResetable ); + + // Is color correction active? + bool HasNonZeroColorCorrectionWeights() const; + +private: + int m_nActiveWeightCount; +}; + + +//------------------------------------------------------------------------------ +// Singleton access +//------------------------------------------------------------------------------ +extern CColorCorrectionMgr *g_pColorCorrectionMgr; + + +#endif // COLORCORRECTIONMGR_H diff --git a/game/client/commentary_modelviewer.cpp b/game/client/commentary_modelviewer.cpp new file mode 100644 index 00000000..84a3ce48 --- /dev/null +++ b/game/client/commentary_modelviewer.cpp @@ -0,0 +1,277 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "vgui/IInput.h" +#include +#include "commentary_modelviewer.h" +#include "iclientmode.h" +#include "baseviewport.h" + +DECLARE_BUILD_FACTORY( CCommentaryModelPanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommentaryModelPanel::CCommentaryModelPanel( vgui::Panel *parent, const char *name ) : CModelPanel( parent, name ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommentaryModelViewer::CCommentaryModelViewer(IViewPort *pViewPort) : Frame(NULL, PANEL_COMMENTARY_MODELVIEWER ) +{ + m_pViewPort = pViewPort; + m_pModelPanel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommentaryModelViewer::~CCommentaryModelViewer() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "Resource/UI/CommentaryModelViewer.res" ); + + m_pModelPanel = dynamic_cast( FindChildByName( "modelpanel" ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::PerformLayout( void ) +{ + int w,h; + GetParent()->GetSize( w, h ); + SetBounds(0,0,w,h); + + if ( m_pModelPanel ) + { + m_pModelPanel->SetBounds(0,0,w,h); + } + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::SetModel( const char *pszName, const char *pszAttached ) +{ + if ( !m_pModelPanel ) + return; + + m_pModelPanel->SwapModel( pszName, pszAttached ); + + m_flYawSpeed = 0; + m_flZoomSpeed = 0; + m_bTranslating = false; + + m_vecResetPos = m_pModelPanel->m_pModelInfo->m_vecOriginOffset; + m_vecResetAngles = m_pModelPanel->m_pModelInfo->m_vecAbsAngles; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::ShowPanel(bool bShow) +{ + if ( BaseClass::IsVisible() == bShow ) + return; + + if ( bShow ) + { + Activate(); + SetMouseInputEnabled( true ); + } + else + { + SetVisible( false ); + SetMouseInputEnabled( false ); + } + + m_pViewPort->ShowBackGround( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::OnCommand( const char *command ) +{ + if ( Q_stricmp( command, "vguicancel" ) ) + { + engine->ClientCmd( const_cast( command ) ); + } + + Close(); + m_pViewPort->ShowBackGround( false ); + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::OnKeyCodePressed( vgui::KeyCode code ) +{ + if ( code == KEY_ENTER ) + { + Close(); + m_pViewPort->ShowBackGround( false ); + } + else if ( code == KEY_SPACE ) + { + m_bTranslating = !m_bTranslating; + } + else if ( code == KEY_R ) + { + m_pModelPanel->m_pModelInfo->m_vecOriginOffset = m_vecResetPos; + m_pModelPanel->m_pModelInfo->m_vecAbsAngles = m_vecResetAngles; + m_flYawSpeed = 0; + m_flZoomSpeed = 0; + + m_pModelPanel->ZoomToFrameDistance(); + } + else + { + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::OnThink( void ) +{ + HandleMovementInput(); + + BaseClass::OnThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryModelViewer::HandleMovementInput( void ) +{ + bool bLeftDown = input()->IsKeyDown(KEY_LEFT); + bool bRightDown = input()->IsKeyDown(KEY_RIGHT); + bool bForwardDown = input()->IsKeyDown(KEY_UP); + bool bBackDown = input()->IsKeyDown(KEY_DOWN); + + float flAccel = 0.05; + + // Rotation around Z + if ( bLeftDown ) + { + if ( m_flYawSpeed > 0 ) + { + m_flYawSpeed = 0; + } + m_flYawSpeed = max(m_flYawSpeed-flAccel, -3.0); + } + else if ( bRightDown ) + { + if ( m_flYawSpeed < 0 ) + { + m_flYawSpeed = 0; + } + m_flYawSpeed = min(m_flYawSpeed+flAccel, 3.0); + } + if ( m_flYawSpeed != 0 ) + { + if ( m_bTranslating ) + { + m_pModelPanel->m_pModelInfo->m_vecOriginOffset.y = clamp( m_pModelPanel->m_pModelInfo->m_vecOriginOffset.y + m_flYawSpeed, -100, 100 ); + } + else + { + m_pModelPanel->m_pModelInfo->m_vecAbsAngles.y = anglemod( m_pModelPanel->m_pModelInfo->m_vecAbsAngles.y + m_flYawSpeed ); + } + + if ( !bLeftDown && !bRightDown ) + { + m_flYawSpeed = ( m_flYawSpeed > 0 ) ? max(0,m_flYawSpeed-0.1) : min(0,m_flYawSpeed+0.1); + } + } + + // Zooming + if ( bForwardDown ) + { + if ( m_flZoomSpeed > 0 ) + { + m_flZoomSpeed = 0; + } + m_flZoomSpeed = max(m_flZoomSpeed-flAccel, -3.0); + } + else if ( bBackDown ) + { + if ( m_flZoomSpeed < 0 ) + { + m_flZoomSpeed = 0; + } + m_flZoomSpeed = min(m_flZoomSpeed+flAccel, 3.0); + } + if ( m_flZoomSpeed != 0 ) + { + if ( m_bTranslating ) + { + m_pModelPanel->m_pModelInfo->m_vecOriginOffset.z = clamp( m_pModelPanel->m_pModelInfo->m_vecOriginOffset.z + m_flZoomSpeed, -100, 300 ); + } + else + { + float flZoomMin = m_pModelPanel->m_flFrameDistance * 0.75; + float flZoomMax = m_pModelPanel->m_flFrameDistance * 1.5; + m_pModelPanel->m_pModelInfo->m_vecOriginOffset.x = clamp( m_pModelPanel->m_pModelInfo->m_vecOriginOffset.x + m_flZoomSpeed, flZoomMin, flZoomMax ); + } + + if ( !bForwardDown && !bBackDown ) + { + m_flZoomSpeed = ( m_flZoomSpeed > 0 ) ? max(0,m_flZoomSpeed-0.1) : min(0,m_flZoomSpeed+0.1); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommentaryShowModelViewer( const CCommand &args ) +{ + if ( args.ArgC() < 2 ) + { + ConMsg( "Usage: commentary_showmodelviewer \n" ); + return; + } + + CBaseViewport *pViewport = dynamic_cast( g_pClientMode->GetViewport() ); + if ( pViewport ) + { + IViewPortPanel *pCommentaryPanel = pViewport->FindPanelByName( PANEL_COMMENTARY_MODELVIEWER ); + if ( !pCommentaryPanel ) + { + pCommentaryPanel = pViewport->CreatePanelByName( PANEL_COMMENTARY_MODELVIEWER ); + pViewport->AddNewPanel( pCommentaryPanel, "PANEL_COMMENTARY_MODELVIEWER" ); + } + + if ( pCommentaryPanel ) + { + //((CCommentaryModelViewer*)pCommentaryPanel)->InvalidateLayout( false, true ); + ((CCommentaryModelViewer*)pCommentaryPanel)->SetModel( args[1], args[2] ); + pViewport->ShowPanel( pCommentaryPanel, true ); + } + } +} + +ConCommand commentary_showmodelviewer( "commentary_showmodelviewer", CommentaryShowModelViewer, "Display the commentary model viewer. Usage: commentary_showmodelviewer ", FCVAR_NONE ); diff --git a/game/client/commentary_modelviewer.h b/game/client/commentary_modelviewer.h new file mode 100644 index 00000000..148be116 --- /dev/null +++ b/game/client/commentary_modelviewer.h @@ -0,0 +1,75 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef COMMENTARY_MODELVIEWER_H +#define COMMENTARY_MODELVIEWER_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include "basemodelpanel.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCommentaryModelPanel : public CModelPanel +{ +public: + DECLARE_CLASS_SIMPLE( CCommentaryModelPanel, CModelPanel ); + + CCommentaryModelPanel( vgui::Panel *parent, const char *name ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCommentaryModelViewer : public vgui::Frame, public IViewPortPanel +{ + DECLARE_CLASS_SIMPLE( CCommentaryModelViewer, vgui::Frame ); +public: + CCommentaryModelViewer(IViewPort *pViewPort); + virtual ~CCommentaryModelViewer(); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void OnCommand( const char *command ); + virtual void OnKeyCodePressed( vgui::KeyCode code ); + virtual void OnThink( void ); + + void SetModel( const char *pszName, const char *pszAttached ); + + void HandleMovementInput( void ); + + // IViewPortPanel +public: + virtual const char *GetName( void ) { return PANEL_COMMENTARY_MODELVIEWER; } + virtual void SetData(KeyValues *data) {}; + virtual void Reset() {}; + virtual void Update() {}; + virtual bool NeedsUpdate( void ) { return false; } + virtual bool HasInputElements( void ) { return true; } + virtual void ShowPanel( bool bShow ); + + // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui + vgui::VPANEL GetVPanel( void ) { return BaseClass::GetVPanel(); } + virtual bool IsVisible() { return BaseClass::IsVisible(); } + virtual void SetParent( vgui::VPANEL parent ) { BaseClass::SetParent( parent ); } + +private: + IViewPort *m_pViewPort; + CCommentaryModelPanel *m_pModelPanel; + + Vector m_vecResetPos; + Vector m_vecResetAngles; + bool m_bTranslating; + float m_flYawSpeed; + float m_flZoomSpeed; +}; + +#endif // COMMENTARY_MODELVIEWER_H diff --git a/game/client/death.cpp b/game/client/death.cpp new file mode 100644 index 00000000..d1b76bfb --- /dev/null +++ b/game/client/death.cpp @@ -0,0 +1,300 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Draws the death notices +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "hud_macros.h" +#include "hudelement.h" +#include "c_playerresource.h" +#include "iclientmode.h" +#include +#include +#include +#include +#include +#include +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CHudDeathNotice : public CHudElement, public vgui::Panel +{ + DECLARE_CLASS_SIMPLE( CHudDeathNotice, vgui::Panel ); +public: + CHudDeathNotice( const char *pElementName ); + void Init( void ); + void VidInit( void ); + bool ShouldDraw( void ); + virtual void Paint(); + + virtual void CHudDeathNotice::ApplySchemeSettings( vgui::IScheme *scheme ); + + void FireGameEvent( KeyValues * event); + +private: + CHudTexture *m_iconD_skull; // sprite index of skull icon + + vgui::HFont m_hTextFont; + Color m_clrText; +}; + +using namespace vgui; + +// +//----------------------------------------------------- +// + +DECLARE_HUDELEMENT( CHudDeathNotice ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHudDeathNotice::CHudDeathNotice( const char *pElementName ) : + CHudElement( pElementName ), BaseClass( NULL, "HudDeathNotice" ) +{ + vgui::Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + SetHiddenBits( HIDEHUD_MISCSTATUS ); +} + +void CHudDeathNotice::ApplySchemeSettings( IScheme *scheme ) +{ + BaseClass::ApplySchemeSettings( scheme ); + + m_hTextFont = scheme->GetFont( "HudNumbersSmall" ); + m_clrText = scheme->GetColor( "FgColor", GetFgColor() ); + + SetPaintBackgroundEnabled( false ); +} + +//----------------------------------------------------- +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH]; + char szVictim[MAX_PLAYER_NAME_LENGTH]; + CHudTexture *iconDeath; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + float flDisplayTime; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +// Robin HACKHACK: HL2 doesn't use deathmsgs, so I just forced these down below our minimap. +// It should be positioned by TF2/HL2 separately, and TF2 should position it according to the minimap position +#define DEATHNOTICE_TOP YRES( 140 ) // Was: 20 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +static ConVar hud_deathnotice_time( "hud_deathnotice_time", "6", 0 ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudDeathNotice::Init( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudDeathNotice::VidInit( void ) +{ + m_iconD_skull = gHUD.GetIcon( "d_skull" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw if we've got at least one death notice in the queue +//----------------------------------------------------------------------------- +bool CHudDeathNotice::ShouldDraw( void ) +{ + return ( CHudElement::ShouldDraw() && ( rgDeathNoticeList[0].iconDeath ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudDeathNotice::Paint() +{ + int x, y; + + surface()->DrawSetTextFont( m_hTextFont ); + surface()->DrawSetTextColor( m_clrText ); + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + // we've gone through them all + if ( rgDeathNoticeList[i].iconDeath == NULL ) + break; + + // display time has expired + // remove the current item from the list + if ( rgDeathNoticeList[i].flDisplayTime < gpGlobals->curtime ) + { + Q_memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + // continue on the next item; stop the counter getting incremented + i--; + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gpGlobals->curtime + DEATHNOTICE_DISPLAY_TIME ); + + // Draw the death notice + y = DEATHNOTICE_TOP + (20 * i) + 100; //!!! + + CHudTexture *icon = rgDeathNoticeList[i].iconDeath; + if ( !icon ) + continue; + + wchar_t victim[ 256 ]; + wchar_t killer[ 256 ]; + + g_pVGuiLocalize->ConvertANSIToUnicode( rgDeathNoticeList[i].szVictim, victim, sizeof( victim ) ); + g_pVGuiLocalize->ConvertANSIToUnicode( rgDeathNoticeList[i].szKiller, killer, sizeof( killer ) ); + + int len = UTIL_ComputeStringWidth( m_hTextFont, victim ); + + x = ScreenWidth() - len - icon->Width() - 5; + + if ( !rgDeathNoticeList[i].iSuicide ) + { + int lenkiller = UTIL_ComputeStringWidth( m_hTextFont, killer ); + + x -= (5 + lenkiller ); + + // Draw killer's name + surface()->DrawSetTextPos( x, y ); + surface()->DrawUnicodeString( killer ); + surface()->DrawGetTextPos( x, y ); + x += 5; + } + + Color iconColor( 255, 80, 0, 255 ); + + if ( rgDeathNoticeList[i].iTeamKill ) + { + // display it in sickly green + iconColor = Color( 10, 240, 10, 255 ); + } + + // Draw death weapon + icon->DrawSelf( x, y, iconColor ); + + x += icon->Width(); + + // Draw victims name + surface()->DrawSetTextPos( x, y ); + surface()->DrawUnicodeString( victim ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: This message handler may be better off elsewhere +//----------------------------------------------------------------------------- +void CHudDeathNotice::FireGameEvent( KeyValues * event) +{ + // Got message during connection + if ( !g_PR ) + return; + + int killer = engine->GetPlayerForUserID( event->GetInt("killer") ); + int victim = engine->GetPlayerForUserID( event->GetInt("victim") ); + + char killedwith[32]; + Q_snprintf( killedwith, sizeof( killedwith ), "d_%s", event->GetString("weapon") ); + + int i; + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iconDeath == NULL ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + Q_memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + // Get the names of the players + const char *killer_name = g_PR->GetPlayerName( killer ); + const char *victim_name = g_PR->GetPlayerName( victim ); + if ( !killer_name ) + killer_name = ""; + if ( !victim_name ) + victim_name = ""; + + Q_strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + Q_strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = true; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = true; + + // try and find the death identifier in the icon list + rgDeathNoticeList[i].iconDeath = gHUD.GetIcon( killedwith ); + if ( !rgDeathNoticeList[i].iconDeath ) + { + // can't find it, so use the default skull & crossbones icon + rgDeathNoticeList[i].iconDeath = m_iconD_skull; + } + + DEATHNOTICE_DISPLAY_TIME = hud_deathnotice_time.GetFloat(); + + rgDeathNoticeList[i].flDisplayTime = gpGlobals->curtime + DEATHNOTICE_DISPLAY_TIME; + + // record the death notice in the console + if ( rgDeathNoticeList[i].iSuicide ) + { + Msg( "%s", rgDeathNoticeList[i].szVictim ); + + if ( !strcmp( killedwith, "d_world" ) ) + { + Msg( " died" ); + } + else + { + Msg( " killed self" ); + } + } + else if ( rgDeathNoticeList[i].iTeamKill ) + { + Msg( "%s", rgDeathNoticeList[i].szKiller ); + Msg( " killed his teammate " ); + Msg( "%s", rgDeathNoticeList[i].szVictim ); + } + else + { + Msg( "%s", rgDeathNoticeList[i].szKiller ); + Msg( " killed " ); + Msg( "%s", rgDeathNoticeList[i].szVictim ); + } + + if ( killedwith && *killedwith && (*killedwith > 13 ) && strcmp( killedwith, "d_world" ) && !rgDeathNoticeList[i].iTeamKill ) + { + Msg( " with " ); + + // replace the code names with the 'real' names + if ( !strcmp( killedwith+2, "egon" ) ) + Q_strncpy( killedwith, "d_gluon gun", sizeof( killedwith ) ); + if ( !strcmp( killedwith+2, "gauss" ) ) + Q_strncpy( killedwith, "d_tau cannon", sizeof( killedwith ) ); + + Msg( killedwith+2 ); // skip over the "d_" part + } + + Msg( "\n" ); +} + + + + diff --git a/game/client/detailobjectsystem.cpp b/game/client/detailobjectsystem.cpp new file mode 100644 index 00000000..82115bce --- /dev/null +++ b/game/client/detailobjectsystem.cpp @@ -0,0 +1,2794 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Draws grasses and other small objects +// +// $Revision: $ +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include +#include "DetailObjectSystem.h" +#include "GameBspFile.h" +#include "UtlBuffer.h" +#include "tier1/utlmap.h" +#include "view.h" +#include "ClientMode.h" +#include "IViewRender.h" +#include "BSPTreeData.h" +#include "tier0/vprof.h" +#include "engine/ivmodelinfo.h" +#include "materialsystem/IMesh.h" +#include "model_types.h" +#include "env_detail_controller.h" +#include "tier0/icommandline.h" +#include "c_world.h" + +#if defined(DOD_DLL) || defined(CSTRIKE_DLL) +#define USE_DETAIL_SHAPES +#endif + +#ifdef USE_DETAIL_SHAPES +#include "engine/ivdebugoverlay.h" +#include "playerenumerator.h" +#endif + +#include "materialsystem/imaterialsystemhardwareconfig.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define DETAIL_SPRITE_MATERIAL "detail/detailsprites" + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- +struct model_t; + + +ConVar cl_detaildist( "cl_detaildist", "1200", 0, "Distance at which detail props are no longer visible" ); +ConVar cl_detailfade( "cl_detailfade", "400", 0, "Distance across which detail props fade in" ); +#if defined( USE_DETAIL_SHAPES ) +ConVar cl_detail_max_sway( "cl_detail_max_sway", "0", FCVAR_ARCHIVE, "Amplitude of the detail prop sway" ); +ConVar cl_detail_avoid_radius( "cl_detail_avoid_radius", "0", FCVAR_ARCHIVE, "radius around detail sprite to avoid players" ); +ConVar cl_detail_avoid_force( "cl_detail_avoid_force", "0", FCVAR_ARCHIVE, "force with which to avoid players ( in units, percentage of the width of the detail sprite )" ); +ConVar cl_detail_avoid_recover_speed( "cl_detail_avoid_recover_speed", "0", FCVAR_ARCHIVE, "how fast to recover position after avoiding players" ); +#endif + +// Per detail instance information +struct DetailModelAdvInfo_t +{ + // precaculated angles for shaped sprites + Vector m_vecAnglesForward[3]; + Vector m_vecAnglesRight[3]; // better to save this mem and calc per sprite ? + Vector m_vecAnglesUp[3]; + + // direction we're avoiding the player + Vector m_vecCurrentAvoid; + + // yaw to sway on + float m_flSwayYaw; + + // size of the shape + float m_flShapeSize; + + int m_iShapeAngle; + float m_flSwayAmount; + +}; + +class CDetailObjectSystemPerLeafData +{ + unsigned short m_FirstDetailProp; + unsigned short m_DetailPropCount; + int m_DetailPropRenderFrame; + + CDetailObjectSystemPerLeafData( void ) + { + m_FirstDetailProp = 0; + m_DetailPropCount = 0; + m_DetailPropRenderFrame = -1; + } +}; + +//----------------------------------------------------------------------------- +// Detail models +//----------------------------------------------------------------------------- +struct SptrintInfo_t +{ + unsigned short m_nSpriteIndex; + float16 m_flScale; +}; + +class CDetailModel : public IClientUnknown, public IClientRenderable +{ + DECLARE_CLASS_NOBASE( CDetailModel ); + +public: + CDetailModel(); + ~CDetailModel(); + + + // Initialization + bool InitCommon( int index, const Vector& org, const QAngle& angles ); + bool Init( int index, const Vector& org, const QAngle& angles, model_t* pModel, + ColorRGBExp32 lighting, int lightstyle, unsigned char lightstylecount, int orientation ); + + bool InitSprite( int index, bool bFlipped, const Vector& org, const QAngle& angles, + unsigned short nSpriteIndex, + ColorRGBExp32 lighting, int lightstyle, unsigned char lightstylecount, + int orientation, float flScale, unsigned char type, + unsigned char shapeAngle, unsigned char shapeSize, unsigned char swayAmount ); + + void SetAlpha( unsigned char alpha ) { m_Alpha = alpha; } + + + // IClientUnknown overrides. +public: + + virtual IClientUnknown* GetIClientUnknown() { return this; } + virtual ICollideable* GetCollideable() { return 0; } // Static props DO implement this. + virtual IClientNetworkable* GetClientNetworkable() { return 0; } + virtual IClientRenderable* GetClientRenderable() { return this; } + virtual IClientEntity* GetIClientEntity() { return 0; } + virtual C_BaseEntity* GetBaseEntity() { return 0; } + virtual IClientThinkable* GetClientThinkable() { return 0; } + + + // IClientRenderable overrides. +public: + + virtual int GetBody() { return 0; } + virtual const Vector& GetRenderOrigin( ); + virtual const QAngle& GetRenderAngles( ); + virtual const matrix3x4_t & RenderableToWorldTransform(); + virtual bool ShouldDraw(); + virtual bool IsTwoPass( void ) { return false; } + virtual void OnThreadedDrawSetup() {} + virtual bool IsTransparent( void ); + virtual const model_t* GetModel( ) const; + virtual int DrawModel( int flags ); + virtual void ComputeFxBlend( ); + virtual int GetFxBlend( ); + virtual bool SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ); + virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ); + virtual bool UsesFlexDelayedWeights() { return false; } + virtual void DoAnimationEvents( void ); + virtual void GetRenderBounds( Vector& mins, Vector& maxs ); + virtual IPVSNotify* GetPVSNotifyInterface(); + virtual void GetRenderBoundsWorldspace( Vector& mins, Vector& maxs ); + virtual bool ShouldReceiveProjectedTextures( int flags ); + virtual bool GetShadowCastDistance( float *pDist, ShadowType_t shadowType ) const { return false; } + virtual bool GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const { return false; } + virtual bool UsesPowerOfTwoFrameBufferTexture(); + virtual bool UsesFullFrameBufferTexture(); + virtual bool LODTest() { return true; } + + virtual ClientShadowHandle_t GetShadowHandle() const; + virtual ClientRenderHandle_t& RenderHandle(); + virtual void GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType ); + virtual bool IsShadowDirty( ) { return false; } + virtual void MarkShadowDirty( bool bDirty ) {} + virtual IClientRenderable *GetShadowParent() { return NULL; } + virtual IClientRenderable *FirstShadowChild(){ return NULL; } + virtual IClientRenderable *NextShadowPeer() { return NULL; } + virtual ShadowType_t ShadowCastType() { return SHADOWS_NONE; } + virtual void CreateModelInstance() {} + virtual ModelInstanceHandle_t GetModelInstance() { return MODEL_INSTANCE_INVALID; } + virtual int LookupAttachment( const char *pAttachmentName ) { return -1; } + virtual bool GetAttachment( int number, matrix3x4_t &matrix ); + virtual bool GetAttachment( int number, Vector &origin, QAngle &angles ); + virtual float * GetRenderClipPlane() { return NULL; } + virtual int GetSkin() { return 0; } + virtual void RecordToolMessage() {} + + void GetColorModulation( float* color ); + + // Computes the render angles for screen alignment + void ComputeAngles( void ); + + // Calls the correct rendering func + void DrawSprite( CMeshBuilder &meshBuilder ); + + // Returns the number of quads the sprite will draw + int QuadsToDraw() const; + + // Draw functions for the different types of sprite + void DrawTypeSprite( CMeshBuilder &meshBuilder ); + + +#ifdef USE_DETAIL_SHAPES + void DrawTypeShapeCross( CMeshBuilder &meshBuilder ); + void DrawTypeShapeTri( CMeshBuilder &meshBuilder ); + + // check for players nearby and angle away from them + void UpdatePlayerAvoid( void ); + + void InitShapedSprite( unsigned char shapeAngle, unsigned char shapeSize, unsigned char swayAmount ); + void InitShapeTri(); + void InitShapeCross(); + + void DrawSwayingQuad( CMeshBuilder &meshBuilder, Vector vecOrigin, Vector vecSway, Vector2D texul, Vector2D texlr, unsigned char *color, + Vector width, Vector height ); +#endif + + int GetType() const { return m_Type; } + unsigned char GetAlpha() const { return m_Alpha; } + + bool IsDetailModelTranslucent(); + + // IHandleEntity stubs. +public: + virtual void SetRefEHandle( const CBaseHandle &handle ) { Assert( false ); } + virtual const CBaseHandle& GetRefEHandle() const { Assert( false ); return *((CBaseHandle*)0); } + + //--------------------------------- + struct LightStyleInfo_t + { + unsigned int m_LightStyle:24; + unsigned int m_LightStyleCount:8; + }; + +protected: + Vector m_Origin; + QAngle m_Angles; + + ColorRGBExp32 m_Color; + + unsigned char m_Orientation:2; + unsigned char m_Type:2; + unsigned char m_bHasLightStyle:1; + unsigned char m_bFlipped:1; + + unsigned char m_Alpha; + + static CUtlMap gm_LightStylesMap; + +#pragma warning( disable : 4201 ) //warning C4201: nonstandard extension used : nameless struct/union + union + { + model_t* m_pModel; + SptrintInfo_t m_SpriteInfo; + }; +#pragma warning( default : 4201 ) + +#ifdef USE_DETAIL_SHAPES + // pointer to advanced properties + DetailModelAdvInfo_t *m_pAdvInfo; +#endif +}; + +static ConVar mat_fullbright( "mat_fullbright", "0", FCVAR_CHEAT ); // hook into engine's cvars.. +extern ConVar r_DrawDetailProps; + + +//----------------------------------------------------------------------------- +// Dictionary for detail sprites +//----------------------------------------------------------------------------- +struct DetailPropSpriteDict_t +{ + Vector2D m_UL; // Coordinate of upper left + Vector2D m_LR; // Coordinate of lower right + Vector2D m_TexUL; // Texcoords of upper left + Vector2D m_TexLR; // Texcoords of lower left +}; + +struct FastSpriteX4_t +{ + // mess with this structure without care and you'll be in a world of trouble. layout matters. + FourVectors m_Pos; + fltx4 m_HalfWidth; + fltx4 m_Height; + uint8 m_RGBColor[4][4]; + DetailPropSpriteDict_t *m_pSpriteDefs[4]; + + void ReplicateFirstEntryToOthers( void ) + { + m_HalfWidth = ReplicateX4( SubFloat( m_HalfWidth, 0 ) ); + m_Height = ReplicateX4( SubFloat( m_Height, 0 ) ); + + for( int i = 1; i < 4; i++ ) + for( int j = 0; j < 4; j++ ) + { + m_RGBColor[i][j] = m_RGBColor[0][j]; + } + m_Pos.x = ReplicateX4( SubFloat( m_Pos.x, 0 ) ); + m_Pos.y = ReplicateX4( SubFloat( m_Pos.y, 0 ) ); + m_Pos.z = ReplicateX4( SubFloat( m_Pos.z, 0 ) ); + } + +}; + + +struct FastSpriteQuadBuildoutBufferX4_t +{ + // mess with this structure without care and you'll be in a world of trouble. layout matters. + FourVectors m_Coords[4]; + uint8 m_RGBColor[4][4]; + fltx4 m_Alpha; + DetailPropSpriteDict_t *m_pSpriteDefs[4]; +}; + +struct FastSpriteQuadBuildoutBufferNonSIMDView_t +{ + // mess with this structure without care and you'll be in a world of trouble. layout matters. + float m_flX0[4], m_flY0[4], m_flZ0[4]; + float m_flX1[4], m_flY1[4], m_flZ1[4]; + float m_flX2[4], m_flY2[4], m_flZ2[4]; + float m_flX3[4], m_flY3[4], m_flZ3[4]; + + uint8 m_RGBColor[4][4]; + float m_Alpha[4]; + DetailPropSpriteDict_t *m_pSpriteDefs[4]; +}; + + +class CFastDetailLeafSpriteList : public CClientLeafSubSystemData +{ + friend class CDetailObjectSystem; + int m_nNumSprites; + int m_nNumSIMDSprites; // #sprites/4, rounded up + // simd pointers into larger array - don't free individually or you will be sad + FastSpriteX4_t *m_pSprites; + + // state for partially drawn sprite lists + int m_nNumPendingSprites; + int m_nStartSpriteIndex; + + CFastDetailLeafSpriteList( void ) + { + m_nNumPendingSprites = 0; + m_nStartSpriteIndex = 0; + } + +}; + + + + +//----------------------------------------------------------------------------- +// Responsible for managing detail objects +//----------------------------------------------------------------------------- +class CDetailObjectSystem : public IDetailObjectSystem, public ISpatialLeafEnumerator +{ +public: + char const *Name() { return "DetailObjectSystem"; } + + // constructor, destructor + CDetailObjectSystem(); + ~CDetailObjectSystem(); + + bool IsPerFrame() { return false; } + + // Init, shutdown + bool Init() + { + m_flDefaultFadeStart = cl_detailfade.GetFloat(); + m_flDefaultFadeEnd = cl_detaildist.GetFloat(); + return true; + } + void PostInit() {} + void Shutdown() {} + + // Level init, shutdown + void LevelInitPreEntity(); + void LevelInitPostEntity(); + void LevelShutdownPreEntity(); + void LevelShutdownPostEntity(); + + void OnSave() {} + void OnRestore() {} + void SafeRemoveIfDesired() {} + + // Gets a particular detail object + IClientRenderable* GetDetailModel( int idx ); + + // Prepares detail for rendering + void BuildDetailObjectRenderLists( const Vector &vViewOrigin ); + + // Renders all opaque detail objects in a particular set of leaves + void RenderOpaqueDetailObjects( int nLeafCount, LeafIndex_t *pLeafList ); + + // Renders all translucent detail objects in a particular set of leaves + void RenderTranslucentDetailObjects( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeafCount, LeafIndex_t *pLeafList ); + + // Renders all translucent detail objects in a particular leaf up to a particular point + void RenderTranslucentDetailObjectsInLeaf( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeaf, const Vector *pVecClosestPoint ); + void RenderFastTranslucentDetailObjectsInLeaf( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeaf, const Vector *pVecClosestPoint ); + + + + // Call this before rendering translucent detail objects + void BeginTranslucentDetailRendering( ); + + // Method of ISpatialLeafEnumerator + bool EnumerateLeaf( int leaf, int context ); + + DetailPropLightstylesLump_t& DetailLighting( int i ) { return m_DetailLighting[i]; } + DetailPropSpriteDict_t& DetailSpriteDict( int i ) { return m_DetailSpriteDict[i]; } + +private: + struct DetailModelDict_t + { + model_t* m_pModel; + }; + + struct EnumContext_t + { + Vector m_vViewOrigin; + int m_BuildWorldListNumber; + }; + + struct SortInfo_t + { + int m_nIndex; + float m_flDistance; + }; + + int BuildOutSortedSprites( CFastDetailLeafSpriteList *pData, + Vector const &viewOrigin, + Vector const &viewForward, + Vector const &viewRight, + Vector const &viewUp ); + + void RenderFastSprites( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeafCount, LeafIndex_t const * pLeafList ); + + void UnserializeFastSprite( FastSpriteX4_t *pSpritex4, int nSubField, DetailObjectLump_t const &lump, bool bFlipped, Vector const &posOffset ); + + // Unserialization + void ScanForCounts( CUtlBuffer& buf, int *pNumOldStyleObjects, + int *pNumFastSpritesToAllocate, int *nMaxOldInLeaf, + int *nMaxFastInLeaf ) const; + + void UnserializeModelDict( CUtlBuffer& buf ); + void UnserializeDetailSprites( CUtlBuffer& buf ); + void UnserializeModels( CUtlBuffer& buf ); + void UnserializeModelLighting( CUtlBuffer& buf ); + + Vector GetSpriteMiddleBottomPosition( DetailObjectLump_t const &lump ) const; + // Count the number of detail sprites in the leaf list + int CountSpritesInLeafList( int nLeafCount, LeafIndex_t *pLeafList ) const; + + // Count the number of detail sprite quads in the leaf list + int CountSpriteQuadsInLeafList( int nLeafCount, LeafIndex_t *pLeafList ) const; + + int CountFastSpritesInLeafList( int nLeafCount, LeafIndex_t const *pLeafList, int *nMaxInLeaf ) const; + + void FreeSortBuffers( void ); + + // Sorts sprites in back-to-front order + static bool SortLessFunc( const SortInfo_t &left, const SortInfo_t &right ); + int SortSpritesBackToFront( int nLeaf, const Vector &viewOrigin, const Vector &viewForward, SortInfo_t *pSortInfo ); + + // For fast detail object insertion + IterationRetval_t EnumElement( int userId, int context ); + + CUtlVector m_DetailObjectDict; + CUtlVector m_DetailObjects; + CUtlVector m_DetailSpriteDict; + CUtlVector m_DetailSpriteDictFlipped; + CUtlVector m_DetailLighting; + FastSpriteX4_t *m_pFastSpriteData; + + // Necessary to get sprites to batch correctly + CMaterialReference m_DetailSpriteMaterial; + CMaterialReference m_DetailWireframeMaterial; + + // State stored off for rendering detail sprites in a single leaf + int m_nSpriteCount; + int m_nFirstSprite; + int m_nSortedLeaf; + int m_nSortedFastLeaf; + SortInfo_t *m_pSortInfo; + SortInfo_t *m_pFastSortInfo; + FastSpriteQuadBuildoutBufferX4_t *m_pBuildoutBuffer; + + float m_flDefaultFadeStart; + float m_flDefaultFadeEnd; + + + // pre calcs for the current render frame + float m_flCurMaxSqDist; + float m_flCurFadeSqDist; + float m_flCurFalloffFactor; + +}; + + +//----------------------------------------------------------------------------- +// System for dealing with detail objects +//----------------------------------------------------------------------------- +static CDetailObjectSystem s_DetailObjectSystem; + +IDetailObjectSystem* DetailObjectSystem() +{ + return &s_DetailObjectSystem; +} + + + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- + +CUtlMap CDetailModel::gm_LightStylesMap( DefLessFunc( CDetailModel * ) ); + +bool CDetailModel::InitCommon( int index, const Vector& org, const QAngle& angles ) +{ + VectorCopy( org, m_Origin ); + VectorCopy( angles, m_Angles ); + m_Alpha = 255; + + return true; +} + + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- + +// NOTE: If DetailPropType_t enum changes, change CDetailModel::QuadsToDraw +static int s_pQuadCount[4] = +{ + 0, //DETAIL_PROP_TYPE_MODEL + 1, //DETAIL_PROP_TYPE_SPRITE + 4, //DETAIL_PROP_TYPE_SHAPE_CROSS + 3, //DETAIL_PROP_TYPE_SHAPE_TRI +}; + +inline int CDetailModel::QuadsToDraw() const +{ + return s_pQuadCount[m_Type]; +} + + +//----------------------------------------------------------------------------- +// Data accessors +//----------------------------------------------------------------------------- +const Vector& CDetailModel::GetRenderOrigin( void ) +{ + return m_Origin; +} + +const QAngle& CDetailModel::GetRenderAngles( void ) +{ + return m_Angles; +} + +const matrix3x4_t &CDetailModel::RenderableToWorldTransform() +{ + // Setup our transform. + static matrix3x4_t mat; + AngleMatrix( GetRenderAngles(), GetRenderOrigin(), mat ); + return mat; +} + +bool CDetailModel::GetAttachment( int number, matrix3x4_t &matrix ) +{ + MatrixCopy( RenderableToWorldTransform(), matrix ); + return true; +} + +bool CDetailModel::GetAttachment( int number, Vector &origin, QAngle &angles ) +{ + origin = m_Origin; + angles = m_Angles; + return true; +} + +bool CDetailModel::IsTransparent( void ) +{ + return (m_Alpha < 255) || modelinfo->IsTranslucent(m_pModel); +} + +bool CDetailModel::ShouldDraw() +{ + // Don't draw in commander mode + return g_pClientMode->ShouldDrawDetailObjects(); +} + +void CDetailModel::GetRenderBounds( Vector& mins, Vector& maxs ) +{ + int nModelType = modelinfo->GetModelType( m_pModel ); + if (nModelType == mod_studio || nModelType == mod_brush) + { + modelinfo->GetModelRenderBounds( GetModel(), mins, maxs ); + } + else + { + mins.Init( 0,0,0 ); + maxs.Init( 0,0,0 ); + } +} + +IPVSNotify* CDetailModel::GetPVSNotifyInterface() +{ + return NULL; +} + +void CDetailModel::GetRenderBoundsWorldspace( Vector& mins, Vector& maxs ) +{ + DefaultRenderBoundsWorldspace( this, mins, maxs ); +} + +bool CDetailModel::ShouldReceiveProjectedTextures( int flags ) +{ + return false; +} + +bool CDetailModel::UsesPowerOfTwoFrameBufferTexture() +{ + return false; +} + +bool CDetailModel::UsesFullFrameBufferTexture() +{ + return false; +} + +void CDetailModel::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType ) +{ + GetRenderBounds( mins, maxs ); +} + +ClientShadowHandle_t CDetailModel::GetShadowHandle() const +{ + return CLIENTSHADOW_INVALID_HANDLE; +} + +ClientRenderHandle_t& CDetailModel::RenderHandle() +{ + AssertMsg( 0, "CDetailModel has no render handle" ); + return *((ClientRenderHandle_t*)NULL); +} + + +//----------------------------------------------------------------------------- +// Render setup +//----------------------------------------------------------------------------- +bool CDetailModel::SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ) +{ + if (!m_pModel) + return false; + + // Setup our transform. + matrix3x4_t parentTransform; + const QAngle &vRenderAngles = GetRenderAngles(); + const Vector &vRenderOrigin = GetRenderOrigin(); + AngleMatrix( vRenderAngles, parentTransform ); + parentTransform[0][3] = vRenderOrigin.x; + parentTransform[1][3] = vRenderOrigin.y; + parentTransform[2][3] = vRenderOrigin.z; + + // Just copy it on down baby + studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( m_pModel ); + for (int i = 0; i < pStudioHdr->numbones; i++) + { + MatrixCopy( parentTransform, pBoneToWorldOut[i] ); + } + + return true; +} + +void CDetailModel::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ +} + +void CDetailModel::DoAnimationEvents( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Render baby! +//----------------------------------------------------------------------------- +const model_t* CDetailModel::GetModel( ) const +{ + return m_pModel; +} + +int CDetailModel::DrawModel( int flags ) +{ + if ((m_Alpha == 0) || (!m_pModel)) + return 0; + + int drawn = modelrender->DrawModel( + flags, + this, + MODEL_INSTANCE_INVALID, + -1, // no entity index + m_pModel, + m_Origin, + m_Angles, + 0, // skin + 0, // body + 0 // hitboxset + ); + return drawn; +} + + +//----------------------------------------------------------------------------- +// Determine alpha and blend amount for transparent objects based on render state info +//----------------------------------------------------------------------------- +void CDetailModel::ComputeFxBlend( ) +{ + // Do nothing, it's already calculate in our m_Alpha +} + +int CDetailModel::GetFxBlend( ) +{ + return m_Alpha; +} + +//----------------------------------------------------------------------------- +// Detail models stuff +//----------------------------------------------------------------------------- +CDetailModel::CDetailModel() +{ + m_Color.r = m_Color.g = m_Color.b = 255; + m_Color.exponent = 0; + m_bFlipped = 0; + m_bHasLightStyle = 0; + +#ifdef USE_DETAIL_SHAPES + m_pAdvInfo = NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +CDetailModel::~CDetailModel() +{ +#ifdef USE_DETAIL_SHAPES + // delete advanced + if ( m_pAdvInfo ) + { + delete m_pAdvInfo; + m_pAdvInfo = NULL; + } +#endif + + if ( m_bHasLightStyle ) + gm_LightStylesMap.Remove( this ); +} + + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +bool CDetailModel::Init( int index, const Vector& org, const QAngle& angles, + model_t* pModel, ColorRGBExp32 lighting, int lightstyle, unsigned char lightstylecount, + int orientation) +{ + m_Color = lighting; + if ( lightstylecount > 0) + { + m_bHasLightStyle = 1; + int iInfo = gm_LightStylesMap.Insert( this ); + if ( lightstyle >= 0x1000000 || lightstylecount >= 100 ) + Error( "Light style overflow\n" ); + gm_LightStylesMap[iInfo].m_LightStyle = lightstyle; + gm_LightStylesMap[iInfo].m_LightStyleCount = lightstylecount; + } + m_Orientation = orientation; + m_Type = DETAIL_PROP_TYPE_MODEL; + m_pModel = pModel; + return InitCommon( index, org, angles ); +} + +bool CDetailModel::InitSprite( int index, bool bFlipped, const Vector& org, const QAngle& angles, unsigned short nSpriteIndex, + ColorRGBExp32 lighting, int lightstyle, unsigned char lightstylecount, int orientation, float flScale, + unsigned char type, unsigned char shapeAngle, unsigned char shapeSize, unsigned char swayAmount ) +{ + m_Color = lighting; + if ( lightstylecount > 0) + { + m_bHasLightStyle = 1; + int iInfo = gm_LightStylesMap.Insert( this ); + if ( lightstyle >= 0x1000000 || lightstylecount >= 100 ) + Error( "Light style overflow\n" ); + gm_LightStylesMap[iInfo].m_LightStyle = lightstyle; + gm_LightStylesMap[iInfo].m_LightStyleCount = lightstylecount; + } + m_Orientation = orientation; + m_SpriteInfo.m_nSpriteIndex = nSpriteIndex; + m_Type = type; + m_SpriteInfo.m_flScale.SetFloat( flScale ); + +#ifdef USE_DETAIL_SHAPES + m_pAdvInfo = NULL; + Assert( type <= 3 ); + // precalculate angles for shapes + if ( type == DETAIL_PROP_TYPE_SHAPE_TRI || type == DETAIL_PROP_TYPE_SHAPE_CROSS || swayAmount > 0 ) + { + m_Angles = angles; + InitShapedSprite( shapeAngle, shapeSize, swayAmount); + } + +#endif + + m_bFlipped = bFlipped; + return InitCommon( index, org, angles ); +} + +#ifdef USE_DETAIL_SHAPES +void CDetailModel::InitShapedSprite( unsigned char shapeAngle, unsigned char shapeSize, unsigned char swayAmount ) +{ + // Set up pointer to advanced shape properties object ( per instance ) + Assert( m_pAdvInfo == NULL ); + m_pAdvInfo = new DetailModelAdvInfo_t; + Assert( m_pAdvInfo ); + + if ( m_pAdvInfo ) + { + m_pAdvInfo->m_iShapeAngle = shapeAngle; + m_pAdvInfo->m_flSwayAmount = (float)swayAmount / 255.0f; + m_pAdvInfo->m_flShapeSize = (float)shapeSize / 255.0f; + m_pAdvInfo->m_vecCurrentAvoid = vec3_origin; + m_pAdvInfo->m_flSwayYaw = random->RandomFloat( 0, 180 ); + } + + switch ( m_Type ) + { + case DETAIL_PROP_TYPE_SHAPE_TRI: + InitShapeTri(); + break; + + case DETAIL_PROP_TYPE_SHAPE_CROSS: + InitShapeCross(); + break; + + default: // sprite will get here + break; + } +} + +void CDetailModel::InitShapeTri( void ) +{ + // store the three sets of directions + matrix3x4_t matrix; + + // Convert roll/pitch only to matrix + AngleMatrix( m_Angles, matrix ); + + // calculate the vectors for the three sides so they can be used in the sorting test + // as well as in drawing + for ( int i=0; i<3; i++ ) + { + // Convert desired rotation to angles + QAngle anglesRotated( m_pAdvInfo->m_iShapeAngle, i*120, 0 ); + + Vector rotForward, rotRight, rotUp; + AngleVectors( anglesRotated, &rotForward, &rotRight, &rotUp ); + + // Rotate direction vectors + VectorRotate( rotForward, matrix, m_pAdvInfo->m_vecAnglesForward[i] ); + VectorRotate( rotRight, matrix, m_pAdvInfo->m_vecAnglesRight[i] ); + VectorRotate( rotUp, matrix, m_pAdvInfo->m_vecAnglesUp[i] ); + } +} + +void CDetailModel::InitShapeCross( void ) +{ + AngleVectors( m_Angles, + &m_pAdvInfo->m_vecAnglesForward[0], + &m_pAdvInfo->m_vecAnglesRight[0], + &m_pAdvInfo->m_vecAnglesUp[0] ); +} +#endif + +//----------------------------------------------------------------------------- +// Color, alpha modulation +//----------------------------------------------------------------------------- +void CDetailModel::GetColorModulation( float *color ) +{ + if (mat_fullbright.GetInt() == 1) + { + color[0] = color[1] = color[2] = 1.0f; + return; + } + + Vector tmp; + Vector normal( 1, 0, 0); + engine->ComputeDynamicLighting( m_Origin, &normal, tmp ); + + float val = engine->LightStyleValue( 0 ); + color[0] = tmp[0] + val * TexLightToLinear( m_Color.r, m_Color.exponent ); + color[1] = tmp[1] + val * TexLightToLinear( m_Color.g, m_Color.exponent ); + color[2] = tmp[2] + val * TexLightToLinear( m_Color.b, m_Color.exponent ); + + // Add in the lightstyles + if ( m_bHasLightStyle ) + { + int iInfo = gm_LightStylesMap.Find( this ); + Assert( iInfo != gm_LightStylesMap.InvalidIndex() ); + if ( iInfo != gm_LightStylesMap.InvalidIndex() ) + { + int nLightStyles = gm_LightStylesMap[iInfo].m_LightStyleCount; + int iLightStyle = gm_LightStylesMap[iInfo].m_LightStyle; + for (int i = 0; i < nLightStyles; ++i) + { + DetailPropLightstylesLump_t& lighting = s_DetailObjectSystem.DetailLighting( iLightStyle + i ); + val = engine->LightStyleValue( lighting.m_Style ); + if (val != 0) + { + color[0] += val * TexLightToLinear( lighting.m_Lighting.r, lighting.m_Lighting.exponent ); + color[1] += val * TexLightToLinear( lighting.m_Lighting.g, lighting.m_Lighting.exponent ); + color[2] += val * TexLightToLinear( lighting.m_Lighting.b, lighting.m_Lighting.exponent ); + } + } + } + } + + // Gamma correct.... + engine->LinearToGamma( color, color ); +} + + +//----------------------------------------------------------------------------- +// Is the model itself translucent, regardless of modulation? +//----------------------------------------------------------------------------- +bool CDetailModel::IsDetailModelTranslucent() +{ + // FIXME: This is only true for my first pass of this feature + if (m_Type >= DETAIL_PROP_TYPE_SPRITE) + return true; + + return modelinfo->IsTranslucent(GetModel()); +} + + +//----------------------------------------------------------------------------- +// Computes the render angles for screen alignment +//----------------------------------------------------------------------------- +void CDetailModel::ComputeAngles( void ) +{ + switch( m_Orientation ) + { + case 0: + break; + + case 1: + { + Vector vecDir; + VectorSubtract( CurrentViewOrigin(), m_Origin, vecDir ); + VectorAngles( vecDir, m_Angles ); + } + break; + + case 2: + { + Vector vecDir; + VectorSubtract( CurrentViewOrigin(), m_Origin, vecDir ); + vecDir.z = 0.0f; + VectorAngles( vecDir, m_Angles ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Select which rendering func to call +//----------------------------------------------------------------------------- +void CDetailModel::DrawSprite( CMeshBuilder &meshBuilder ) +{ + switch( m_Type ) + { +#ifdef USE_DETAIL_SHAPES + case DETAIL_PROP_TYPE_SHAPE_CROSS: + DrawTypeShapeCross( meshBuilder ); + break; + + case DETAIL_PROP_TYPE_SHAPE_TRI: + DrawTypeShapeTri( meshBuilder ); + break; +#endif + case DETAIL_PROP_TYPE_SPRITE: + DrawTypeSprite( meshBuilder ); + break; + + default: + Assert(0); + break; + } +} + + +//----------------------------------------------------------------------------- +// Draws the single sprite type +//----------------------------------------------------------------------------- +void CDetailModel::DrawTypeSprite( CMeshBuilder &meshBuilder ) +{ + Assert( m_Type == DETAIL_PROP_TYPE_SPRITE ); + + Vector vecColor; + GetColorModulation( vecColor.Base() ); + + unsigned char color[4]; + color[0] = (unsigned char)(vecColor[0] * 255.0f); + color[1] = (unsigned char)(vecColor[1] * 255.0f); + color[2] = (unsigned char)(vecColor[2] * 255.0f); + color[3] = m_Alpha; + + DetailPropSpriteDict_t &dict = s_DetailObjectSystem.DetailSpriteDict( m_SpriteInfo.m_nSpriteIndex ); + + Vector vecOrigin, dx, dy; + AngleVectors( m_Angles, NULL, &dx, &dy ); + + Vector2D ul, lr; + float scale = m_SpriteInfo.m_flScale.GetFloat(); + Vector2DMultiply( dict.m_UL, scale, ul ); + Vector2DMultiply( dict.m_LR, scale, lr ); + +#ifdef USE_DETAIL_SHAPES + UpdatePlayerAvoid(); + + Vector vecSway = vec3_origin; + + if ( m_pAdvInfo ) + { + vecSway = m_pAdvInfo->m_vecCurrentAvoid * m_SpriteInfo.m_flScale.GetFloat(); + float flSwayAmplitude = m_pAdvInfo->m_flSwayAmount * cl_detail_max_sway.GetFloat(); + if ( flSwayAmplitude > 0 ) + { + // sway based on time plus a random seed that is constant for this instance of the sprite + vecSway += dx * sin(gpGlobals->curtime+m_Origin.x) * flSwayAmplitude; + } + } +#endif + + VectorMA( m_Origin, ul.x, dx, vecOrigin ); + VectorMA( vecOrigin, ul.y, dy, vecOrigin ); + dx *= (lr.x - ul.x); + dy *= (lr.y - ul.y); + + Vector2D texul, texlr; + texul = dict.m_TexUL; + texlr = dict.m_TexLR; + + if ( !m_bFlipped ) + { + texul.x = dict.m_TexLR.x; + texlr.x = dict.m_TexUL.x; + } + +#ifndef USE_DETAIL_SHAPES + meshBuilder.Position3fv( vecOrigin.Base() ); +#else + meshBuilder.Position3fv( (vecOrigin+vecSway).Base() ); +#endif + + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2fv( 0, texul.Base() ); + meshBuilder.AdvanceVertex(); + + vecOrigin += dy; + meshBuilder.Position3fv( vecOrigin.Base() ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, texul.x, texlr.y ); + meshBuilder.AdvanceVertex(); + + vecOrigin += dx; + meshBuilder.Position3fv( vecOrigin.Base() ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2fv( 0, texlr.Base() ); + meshBuilder.AdvanceVertex(); + + vecOrigin -= dy; +#ifndef USE_DETAIL_SHAPES + meshBuilder.Position3fv( vecOrigin.Base() ); +#else + meshBuilder.Position3fv( (vecOrigin+vecSway).Base() ); +#endif + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, texlr.x, texul.y ); + meshBuilder.AdvanceVertex(); +} + +//----------------------------------------------------------------------------- +// draws a procedural model, cross shape +// two perpendicular sprites +//----------------------------------------------------------------------------- +#ifdef USE_DETAIL_SHAPES +void CDetailModel::DrawTypeShapeCross( CMeshBuilder &meshBuilder ) +{ + Assert( m_Type == DETAIL_PROP_TYPE_SHAPE_CROSS ); + + Vector vecColor; + GetColorModulation( vecColor.Base() ); + + unsigned char color[4]; + color[0] = (unsigned char)(vecColor[0] * 255.0f); + color[1] = (unsigned char)(vecColor[1] * 255.0f); + color[2] = (unsigned char)(vecColor[2] * 255.0f); + color[3] = m_Alpha; + + DetailPropSpriteDict_t &dict = s_DetailObjectSystem.DetailSpriteDict( m_SpriteInfo.m_nSpriteIndex ); + + Vector2D texul, texlr; + texul = dict.m_TexUL; + texlr = dict.m_TexLR; + + // What a shameless re-use of bits (m_pModel == 0 when it should be flipped horizontally) + if ( !m_pModel ) + { + texul.x = dict.m_TexLR.x; + texlr.x = dict.m_TexUL.x; + } + + Vector2D texumid, texlmid; + texumid.y = texul.y; + texlmid.y = texlr.y; + texumid.x = texlmid.x = ( texul.x + texlr.x ) / 2; + + Vector2D texll; + texll.x = texul.x; + texll.y = texlr.y; + + Vector2D ul, lr; + float flScale = m_SpriteInfo.m_flScale.GetFloat(); + Vector2DMultiply( dict.m_UL, flScale, ul ); + Vector2DMultiply( dict.m_LR, flScale, lr ); + + float flSizeX = ( lr.x - ul.x ) / 2; + float flSizeY = ( lr.y - ul.y ); + + UpdatePlayerAvoid(); + + // sway based on time plus a random seed that is constant for this instance of the sprite + Vector vecSway = ( m_pAdvInfo->m_vecCurrentAvoid * flSizeX * 2 ); + float flSwayAmplitude = m_pAdvInfo->m_flSwayAmount * cl_detail_max_sway.GetFloat(); + if ( flSwayAmplitude > 0 ) + { + vecSway += UTIL_YawToVector( m_pAdvInfo->m_flSwayYaw ) * sin(gpGlobals->curtime+m_Origin.x) * flSwayAmplitude; + } + + Vector vecOrigin; + VectorMA( m_Origin, ul.y, m_pAdvInfo->m_vecAnglesUp[0], vecOrigin ); + + Vector forward, right, up; + forward = m_pAdvInfo->m_vecAnglesForward[0] * flSizeX; + right = m_pAdvInfo->m_vecAnglesRight[0] * flSizeX; + up = m_pAdvInfo->m_vecAnglesUp[0] * flSizeY; + + // figure out drawing order so the branches sort properly + // do dot products with the forward and right vectors to determine the quadrant the viewer is in + // assume forward points North , right points East + /* + N + | + 3 | 0 + W---------E + 2 | 1 + | + S + */ + // eg if they are in quadrant 0, set iBranch to 0, and the draw order will be + // 0, 1, 2, 3, or South, west, north, east + Vector viewOffset = CurrentViewOrigin() - m_Origin; + bool bForward = ( DotProduct( forward, viewOffset ) > 0 ); + bool bRight = ( DotProduct( right, viewOffset ) > 0 ); + int iBranch = bForward ? ( bRight ? 0 : 3 ) : ( bRight ? 1 : 2 ); + + //debugoverlay->AddLineOverlay( m_Origin, m_Origin + right * 20, 255, 0, 0, true, 0.01 ); + //debugoverlay->AddLineOverlay( m_Origin, m_Origin + forward * 20, 0, 0, 255, true, 0.01 ); + + int iDrawn = 0; + while( iDrawn < 4 ) + { + switch( iBranch ) + { + case 0: // south + DrawSwayingQuad( meshBuilder, vecOrigin, vecSway, texumid, texlr, color, -forward, up ); + break; + case 1: // west + DrawSwayingQuad( meshBuilder, vecOrigin, vecSway, texumid, texll, color, -right, up ); + break; + case 2: // north + DrawSwayingQuad( meshBuilder, vecOrigin, vecSway, texumid, texll, color, forward, up ); + break; + case 3: // east + DrawSwayingQuad( meshBuilder, vecOrigin, vecSway, texumid, texlr, color, right, up ); + break; + } + + iDrawn++; + iBranch++; + if ( iBranch > 3 ) + iBranch = 0; + } +} +#endif + +//----------------------------------------------------------------------------- +// draws a procedural model, tri shape +//----------------------------------------------------------------------------- +#ifdef USE_DETAIL_SHAPES +void CDetailModel::DrawTypeShapeTri( CMeshBuilder &meshBuilder ) +{ + Assert( m_Type == DETAIL_PROP_TYPE_SHAPE_TRI ); + + Vector vecColor; + GetColorModulation( vecColor.Base() ); + + unsigned char color[4]; + color[0] = (unsigned char)(vecColor[0] * 255.0f); + color[1] = (unsigned char)(vecColor[1] * 255.0f); + color[2] = (unsigned char)(vecColor[2] * 255.0f); + color[3] = m_Alpha; + + DetailPropSpriteDict_t &dict = s_DetailObjectSystem.DetailSpriteDict( m_SpriteInfo.m_nSpriteIndex ); + + Vector2D texul, texlr; + texul = dict.m_TexUL; + texlr = dict.m_TexLR; + + // What a shameless re-use of bits (m_pModel == 0 when it should be flipped horizontally) + if ( !m_pModel ) + { + texul.x = dict.m_TexLR.x; + texlr.x = dict.m_TexUL.x; + } + + Vector2D ul, lr; + float flScale = m_SpriteInfo.m_flScale.GetFloat(); + Vector2DMultiply( dict.m_UL, flScale, ul ); + Vector2DMultiply( dict.m_LR, flScale, lr ); + + // sort the sides relative to the view origin + Vector viewOffset = CurrentViewOrigin() - m_Origin; + + // three sides, A, B, C, counter-clockwise from A is the unrotated side + bool bOutsideA = DotProduct( m_pAdvInfo->m_vecAnglesForward[0], viewOffset ) > 0; + bool bOutsideB = DotProduct( m_pAdvInfo->m_vecAnglesForward[1], viewOffset ) > 0; + bool bOutsideC = DotProduct( m_pAdvInfo->m_vecAnglesForward[2], viewOffset ) > 0; + + int iBranch = 0; + if ( bOutsideA && !bOutsideB ) + iBranch = 1; + else if ( bOutsideB && !bOutsideC ) + iBranch = 2; + + float flHeight, flWidth; + flHeight = (lr.y - ul.y); + flWidth = (lr.x - ul.x); + + Vector vecSway; + Vector vecOrigin; + Vector vecHeight, vecWidth; + + UpdatePlayerAvoid(); + + Vector vecSwayYaw = UTIL_YawToVector( m_pAdvInfo->m_flSwayYaw ); + float flSwayAmplitude = m_pAdvInfo->m_flSwayAmount * cl_detail_max_sway.GetFloat(); + + int iDrawn = 0; + while( iDrawn < 3 ) + { + vecHeight = m_pAdvInfo->m_vecAnglesUp[iBranch] * flHeight; + vecWidth = m_pAdvInfo->m_vecAnglesRight[iBranch] * flWidth; + + VectorMA( m_Origin, ul.x, m_pAdvInfo->m_vecAnglesRight[iBranch], vecOrigin ); + VectorMA( vecOrigin, ul.y, m_pAdvInfo->m_vecAnglesUp[iBranch], vecOrigin ); + VectorMA( vecOrigin, m_pAdvInfo->m_flShapeSize*flWidth, m_pAdvInfo->m_vecAnglesForward[iBranch], vecOrigin ); + + // sway is calculated per side so they don't sway exactly the same + Vector vecSway = ( m_pAdvInfo->m_vecCurrentAvoid * flWidth ) + + vecSwayYaw * sin(gpGlobals->curtime+m_Origin.x+iBranch) * flSwayAmplitude; + + DrawSwayingQuad( meshBuilder, vecOrigin, vecSway, texul, texlr, color, vecWidth, vecHeight ); + + iDrawn++; + iBranch++; + if ( iBranch > 2 ) + iBranch = 0; + } +} +#endif + +//----------------------------------------------------------------------------- +// checks for nearby players and pushes the detail to the side +//----------------------------------------------------------------------------- +#ifdef USE_DETAIL_SHAPES +void CDetailModel::UpdatePlayerAvoid( void ) +{ + float flForce = cl_detail_avoid_force.GetFloat(); + + if ( flForce < 0.1 ) + return; + + if ( m_pAdvInfo == NULL ) + return; + + // get players in a radius + float flRadius = cl_detail_avoid_radius.GetFloat(); + float flRecoverSpeed = cl_detail_avoid_recover_speed.GetFloat(); + + Vector vecAvoid; + C_BaseEntity *pEnt; + + float flMaxForce = 0; + Vector vecMaxAvoid(0,0,0); + + CPlayerEnumerator avoid( flRadius, m_Origin ); + partition->EnumerateElementsInSphere( PARTITION_CLIENT_SOLID_EDICTS, m_Origin, flRadius, false, &avoid ); + + // Okay, decide how to avoid if there's anything close by + int c = avoid.GetObjectCount(); + for ( int i=0; iGetAbsOrigin(); + vecAvoid.z = 0; + + float flDist = vecAvoid.Length2D(); + + if ( flDist > flRadius ) + continue; + + float flForceScale = RemapValClamped( flDist, 0, flRadius, flForce, 0.0 ); + + if ( flForceScale > flMaxForce ) + { + flMaxForce = flForceScale; + vecAvoid.NormalizeInPlace(); + vecAvoid *= flMaxForce; + vecMaxAvoid = vecAvoid; + } + } + + // if we are being moved, move fast. Else we recover at a slow rate + if ( vecMaxAvoid.Length2D() > m_pAdvInfo->m_vecCurrentAvoid.Length2D() ) + flRecoverSpeed = 10; // fast approach + + m_pAdvInfo->m_vecCurrentAvoid[0] = Approach( vecMaxAvoid[0], m_pAdvInfo->m_vecCurrentAvoid[0], flRecoverSpeed ); + m_pAdvInfo->m_vecCurrentAvoid[1] = Approach( vecMaxAvoid[1], m_pAdvInfo->m_vecCurrentAvoid[1], flRecoverSpeed ); + m_pAdvInfo->m_vecCurrentAvoid[2] = Approach( vecMaxAvoid[2], m_pAdvInfo->m_vecCurrentAvoid[2], flRecoverSpeed ); +} +#endif + +//----------------------------------------------------------------------------- +// draws a quad that sways on the top two vertices +// pass vecOrigin as the top left vertex position +//----------------------------------------------------------------------------- +#ifdef USE_DETAIL_SHAPES +void CDetailModel::DrawSwayingQuad( CMeshBuilder &meshBuilder, Vector vecOrigin, Vector vecSway, Vector2D texul, Vector2D texlr, unsigned char *color, + Vector width, Vector height ) +{ + meshBuilder.Position3fv( (vecOrigin + vecSway).Base() ); + meshBuilder.TexCoord2fv( 0, texul.Base() ); + meshBuilder.Color4ubv( color ); + meshBuilder.AdvanceVertex(); + + vecOrigin += height; + meshBuilder.Position3fv( vecOrigin.Base() ); + meshBuilder.TexCoord2f( 0, texul.x, texlr.y ); + meshBuilder.Color4ubv( color ); + meshBuilder.AdvanceVertex(); + + vecOrigin += width; + meshBuilder.Position3fv( vecOrigin.Base() ); + meshBuilder.TexCoord2fv( 0, texlr.Base() ); + meshBuilder.Color4ubv( color ); + meshBuilder.AdvanceVertex(); + + vecOrigin -= height; + meshBuilder.Position3fv( (vecOrigin + vecSway).Base() ); + meshBuilder.TexCoord2f( 0, texlr.x, texul.y ); + meshBuilder.Color4ubv( color ); + meshBuilder.AdvanceVertex(); +} +#endif + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +CDetailObjectSystem::CDetailObjectSystem() : m_DetailSpriteDict( 0, 32 ), m_DetailObjectDict( 0, 32 ), m_DetailSpriteDictFlipped( 0, 32 ) +{ + m_pFastSpriteData = NULL; + m_pSortInfo = NULL; + m_pFastSortInfo = NULL; + m_pBuildoutBuffer = NULL; +} + +void CDetailObjectSystem::FreeSortBuffers( void ) +{ + if ( m_pSortInfo ) + { + MemAlloc_FreeAligned( m_pSortInfo ); + m_pSortInfo = NULL; + } + if ( m_pFastSortInfo ) + { + MemAlloc_FreeAligned( m_pFastSortInfo ); + m_pFastSortInfo = NULL; + } + if ( m_pBuildoutBuffer ) + { + MemAlloc_FreeAligned( m_pBuildoutBuffer ); + m_pBuildoutBuffer = NULL; + } +} + +CDetailObjectSystem::~CDetailObjectSystem() +{ + if ( m_pFastSpriteData ) + { + MemAlloc_FreeAligned( m_pFastSpriteData ); + m_pFastSpriteData = NULL; + } + FreeSortBuffers(); + +} + + +//----------------------------------------------------------------------------- +// Level init, shutdown +//----------------------------------------------------------------------------- +void CDetailObjectSystem::LevelInitPreEntity() +{ + // Prepare the translucent detail sprite material; we only have 1! + m_DetailSpriteMaterial.Init( "detail/detailsprites", TEXTURE_GROUP_OTHER ); + m_DetailWireframeMaterial.Init( "debug/debugspritewireframe", TEXTURE_GROUP_OTHER ); + + // Version check + if (engine->GameLumpVersion( GAMELUMP_DETAIL_PROPS ) < 4) + { + Warning("Map uses old detail prop file format.. ignoring detail props\n"); + return; + } + + MEM_ALLOC_CREDIT(); + + // Unserialize + int size = engine->GameLumpSize( GAMELUMP_DETAIL_PROPS ); + CUtlMemory fileMemory; + fileMemory.EnsureCapacity( size ); + if (engine->LoadGameLump( GAMELUMP_DETAIL_PROPS, fileMemory.Base(), size )) + { + CUtlBuffer buf( fileMemory.Base(), size, CUtlBuffer::READ_ONLY ); + UnserializeModelDict( buf ); + + switch (engine->GameLumpVersion( GAMELUMP_DETAIL_PROPS ) ) + { + case 4: + UnserializeDetailSprites( buf ); + UnserializeModels( buf ); + break; + } + } + + if ( m_DetailObjects.Count() || m_DetailSpriteDict.Count() ) + { + // There are detail objects in the level, so precache the material + PrecacheMaterial( DETAIL_SPRITE_MATERIAL ); + IMaterial *pMat = m_DetailSpriteMaterial; + // adjust for non-square textures (cropped) + float flRatio = pMat->GetMappingWidth() / pMat->GetMappingHeight(); + if ( flRatio > 1.0 ) + { + for( int i = 0; iGetHDRType() != HDR_TYPE_NONE ) + { + detailPropLightingLump = GAMELUMP_DETAIL_PROP_LIGHTING_HDR; + } + else + { + detailPropLightingLump = GAMELUMP_DETAIL_PROP_LIGHTING; + } + size = engine->GameLumpSize( detailPropLightingLump ); + + fileMemory.EnsureCapacity( size ); + if (engine->LoadGameLump( detailPropLightingLump, fileMemory.Base(), size )) + { + CUtlBuffer buf( fileMemory.Base(), size, CUtlBuffer::READ_ONLY ); + UnserializeModelLighting( buf ); + } +} + + +void CDetailObjectSystem::LevelInitPostEntity() +{ + const char *pDetailSpriteMaterial = DETAIL_SPRITE_MATERIAL; + C_World *pWorld = GetClientWorldEntity(); + if ( pWorld && pWorld->GetDetailSpriteMaterial() && *(pWorld->GetDetailSpriteMaterial()) ) + { + pDetailSpriteMaterial = pWorld->GetDetailSpriteMaterial(); + } + m_DetailSpriteMaterial.Init( pDetailSpriteMaterial, TEXTURE_GROUP_OTHER ); + + if ( GetDetailController() ) + { + cl_detailfade.SetValue( min( m_flDefaultFadeStart, GetDetailController()->m_flFadeStartDist ) ); + cl_detaildist.SetValue( min( m_flDefaultFadeEnd, GetDetailController()->m_flFadeEndDist ) ); + } + else + { + // revert to default values if the map doesn't specify + cl_detailfade.SetValue( m_flDefaultFadeStart ); + cl_detaildist.SetValue( m_flDefaultFadeEnd ); + } +} + +void CDetailObjectSystem::LevelShutdownPreEntity() +{ + m_DetailObjects.Purge(); + m_DetailObjectDict.Purge(); + m_DetailSpriteDict.Purge(); + m_DetailSpriteDictFlipped.Purge(); + m_DetailLighting.Purge(); + m_DetailSpriteMaterial.Shutdown(); + if ( m_pFastSpriteData ) + { + MemAlloc_FreeAligned( m_pFastSpriteData ); + m_pFastSpriteData = NULL; + } + FreeSortBuffers(); + +} + +void CDetailObjectSystem::LevelShutdownPostEntity() +{ + m_DetailWireframeMaterial.Shutdown(); +} + +//----------------------------------------------------------------------------- +// Before each view, blat out the stored detail sprite state +//----------------------------------------------------------------------------- +void CDetailObjectSystem::BeginTranslucentDetailRendering( ) +{ + m_nSortedLeaf = -1; + m_nSortedFastLeaf = -1; + m_nSpriteCount = m_nFirstSprite = 0; +} + + +//----------------------------------------------------------------------------- +// Gets a particular detail object +//----------------------------------------------------------------------------- +IClientRenderable* CDetailObjectSystem::GetDetailModel( int idx ) +{ + // FIXME: This is necessary because we have intermixed models + sprites + // in a single list (m_DetailObjects) + if (m_DetailObjects[idx].GetType() != DETAIL_PROP_TYPE_MODEL) + return NULL; + + return &m_DetailObjects[idx]; +} + + +//----------------------------------------------------------------------------- +// Unserialization +//----------------------------------------------------------------------------- +void CDetailObjectSystem::UnserializeModelDict( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + m_DetailObjectDict.EnsureCapacity( count ); + while ( --count >= 0 ) + { + DetailObjectDictLump_t lump; + buf.Get( &lump, sizeof(DetailObjectDictLump_t) ); + + DetailModelDict_t dict; + dict.m_pModel = (model_t *)engine->LoadModel( lump.m_Name, true ); + + // Don't allow vertex-lit models + if (modelinfo->IsModelVertexLit(dict.m_pModel)) + { + Warning("Detail prop model %s is using vertex-lit materials!\nIt must use unlit materials!\n", lump.m_Name ); + dict.m_pModel = (model_t *)engine->LoadModel( "models/error.mdl" ); + } + + m_DetailObjectDict.AddToTail( dict ); + } +} + +void CDetailObjectSystem::UnserializeDetailSprites( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + m_DetailSpriteDict.EnsureCapacity( count ); + m_DetailSpriteDictFlipped.EnsureCapacity( count ); + while ( --count >= 0 ) + { + int i = m_DetailSpriteDict.AddToTail(); + buf.Get( &m_DetailSpriteDict[i], sizeof(DetailSpriteDictLump_t) ); + int flipi = m_DetailSpriteDictFlipped.AddToTail(); + m_DetailSpriteDictFlipped[flipi] = m_DetailSpriteDict[i]; + swap( m_DetailSpriteDictFlipped[flipi].m_TexUL.x, m_DetailSpriteDictFlipped[flipi].m_TexLR.x ); + } +} + + +void CDetailObjectSystem::UnserializeModelLighting( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + m_DetailLighting.EnsureCapacity( count ); + while ( --count >= 0 ) + { + int i = m_DetailLighting.AddToTail(); + buf.Get( &m_DetailLighting[i], sizeof(DetailPropLightstylesLump_t) ); + } +} + + +ConVar cl_detail_multiplier( "cl_detail_multiplier", "1", FCVAR_CHEAT, "extra details to create" ); + +#define SPRITE_MULTIPLIER ( cl_detail_multiplier.GetInt() ) + +ConVar cl_fastdetailsprites( "cl_fastdetailsprites", "1", FCVAR_CHEAT, "whether to use new detail sprite system"); + +static bool DetailObjectIsFastSprite( DetailObjectLump_t const & lump ) +{ + return ( + ( cl_fastdetailsprites.GetInt() ) && + ( lump.m_Type == DETAIL_PROP_TYPE_SPRITE ) && + ( lump.m_LightStyleCount == 0 ) && + ( lump.m_Orientation == 2 ) && + ( lump.m_ShapeAngle == 0 ) && + ( lump.m_ShapeSize == 0 ) && + ( lump.m_SwayAmount == 0 ) ); +} + + +void CDetailObjectSystem::ScanForCounts( CUtlBuffer& buf, + int *pNumOldStyleObjects, + int *pNumFastSpritesToAllocate, + int *nMaxNumOldSpritesInLeaf, + int *nMaxNumFastSpritesInLeaf + ) const +{ + int oldpos = buf.TellGet(); // we need to seek back + int count = buf.GetInt(); + + int nOld = 0; + int nFast = 0; + int detailObjectLeaf = -1; + + int nNumOldInLeaf = 0; + int nNumFastInLeaf = 0; + int nMaxOld = 0; + int nMaxFast = 0; + while ( --count >= 0 ) + { + DetailObjectLump_t lump; + buf.Get( &lump, sizeof(DetailObjectLump_t) ); + + // We rely on the fact that details objects are sorted by leaf in the + // bsp file for this + if ( detailObjectLeaf != lump.m_Leaf ) + { + // need to pad nfast to next sse boundary + nFast += ( 0 - nFast ) & 3; + nMaxFast = max( nMaxFast, nNumFastInLeaf ); + nMaxOld = max( nMaxOld, nNumOldInLeaf ); + nNumOldInLeaf = 0; + nNumFastInLeaf = 0; + detailObjectLeaf = lump.m_Leaf; + + } + + if ( DetailObjectIsFastSprite( lump ) ) + { + nFast += SPRITE_MULTIPLIER; + nNumFastInLeaf += SPRITE_MULTIPLIER; + } + else + { + nOld += SPRITE_MULTIPLIER; + nNumOldInLeaf += SPRITE_MULTIPLIER; + } + } + + // need to pad nfast to next sse boundary + nFast += ( 0 - nFast ) & 3; + nMaxFast = max( nMaxFast, nNumFastInLeaf ); + nMaxOld = max( nMaxOld, nNumOldInLeaf ); + + buf.SeekGet( CUtlBuffer::SEEK_HEAD, oldpos ); + *pNumFastSpritesToAllocate = nFast; + *pNumOldStyleObjects = nOld; + nMaxFast = ( 3 + nMaxFast ) & ~3; + *nMaxNumOldSpritesInLeaf = nMaxOld; + *nMaxNumFastSpritesInLeaf = nMaxFast; + +} + +//----------------------------------------------------------------------------- +// Unserialize all models +//----------------------------------------------------------------------------- +void CDetailObjectSystem::UnserializeModels( CUtlBuffer& buf ) +{ + int firstDetailObject = 0; + int detailObjectCount = 0; + int detailObjectLeaf = -1; + + int nNumOldStyleObjects; + int nNumFastSpritesToAllocate; + int nMaxOldInLeaf; + int nMaxFastInLeaf; + ScanForCounts( buf, &nNumOldStyleObjects, &nNumFastSpritesToAllocate, &nMaxOldInLeaf, &nMaxFastInLeaf ); + + FreeSortBuffers(); + + if ( nMaxOldInLeaf ) + { + m_pSortInfo = reinterpret_cast ( + MemAlloc_AllocAligned( (3 + nMaxOldInLeaf ) * sizeof( SortInfo_t ), sizeof( fltx4 ) ) ); + } + if ( nMaxFastInLeaf ) + { + m_pFastSortInfo = reinterpret_cast ( + MemAlloc_AllocAligned( (3 + nMaxFastInLeaf ) * sizeof( SortInfo_t ), sizeof( fltx4 ) ) ); + + m_pBuildoutBuffer = reinterpret_cast ( + MemAlloc_AllocAligned( + ( 1 + nMaxFastInLeaf / 4 ) * sizeof( FastSpriteQuadBuildoutBufferX4_t ), + sizeof( fltx4 ) ) ); + } + + if ( nNumFastSpritesToAllocate ) + { + Assert( ( nNumFastSpritesToAllocate & 3 ) == 0 ); + Assert( ! m_pFastSpriteData ); // wtf? didn't free? + m_pFastSpriteData = reinterpret_cast ( + MemAlloc_AllocAligned( + ( nNumFastSpritesToAllocate >> 2 ) * sizeof( FastSpriteX4_t ), + sizeof( fltx4 ) ) ); + } + + m_DetailObjects.EnsureCapacity( nNumOldStyleObjects ); + + int count = buf.GetInt(); + + int nCurFastObject = 0; + int nNumFastObjectsInCurLeaf = 0; + FastSpriteX4_t *pCurFastSpriteOut = m_pFastSpriteData; + + bool bFlipped = true; + while ( --count >= 0 ) + { + bFlipped = !bFlipped; + DetailObjectLump_t lump; + buf.Get( &lump, sizeof(DetailObjectLump_t) ); + + // We rely on the fact that details objects are sorted by leaf in the + // bsp file for this + if ( detailObjectLeaf != lump.m_Leaf ) + { + if (detailObjectLeaf != -1) + { + if ( nNumFastObjectsInCurLeaf ) + { + CFastDetailLeafSpriteList *pNew = new CFastDetailLeafSpriteList; + pNew->m_nNumSprites = nNumFastObjectsInCurLeaf; + pNew->m_nNumSIMDSprites = ( 3 + nNumFastObjectsInCurLeaf ) >> 2; + pNew->m_pSprites = pCurFastSpriteOut; + pCurFastSpriteOut += pNew->m_nNumSIMDSprites; + ClientLeafSystem()->SetSubSystemDataInLeaf( + detailObjectLeaf, CLSUBSYSTEM_DETAILOBJECTS, pNew ); + // round to see boundary + nCurFastObject += ( 0 - nCurFastObject ) & 3; + nNumFastObjectsInCurLeaf = 0; + } + ClientLeafSystem()->SetDetailObjectsInLeaf( detailObjectLeaf, + firstDetailObject, detailObjectCount ); + } + + detailObjectLeaf = lump.m_Leaf; + firstDetailObject = m_DetailObjects.Count(); + detailObjectCount = 0; + } + + if ( DetailObjectIsFastSprite( lump ) ) + { + for( int i =0 ; i < SPRITE_MULTIPLIER ; i++) + { + FastSpriteX4_t *pSpritex4 = m_pFastSpriteData + (nCurFastObject >> 2 ); + int nSubField = ( nCurFastObject & 3 ); + Vector pos(0,0,0); + if ( i ) + { + pos += RandomVector( -50, 50 ); + pos.z = 0; + } + UnserializeFastSprite( pSpritex4, nSubField, lump, bFlipped, pos ); + if ( nSubField == 0 ) + pSpritex4->ReplicateFirstEntryToOthers(); // keep bad numbers out to prevent denormals, etc + nCurFastObject++; + nNumFastObjectsInCurLeaf++; + } + } + else + { + switch( lump.m_Type ) + { + case DETAIL_PROP_TYPE_MODEL: + { + int newObj = m_DetailObjects.AddToTail(); + m_DetailObjects[newObj].Init( + newObj, lump.m_Origin, lump.m_Angles, + m_DetailObjectDict[lump.m_DetailModel].m_pModel, lump.m_Lighting, + lump.m_LightStyles, lump.m_LightStyleCount, lump.m_Orientation ); + ++detailObjectCount; + } + break; + + case DETAIL_PROP_TYPE_SPRITE: + case DETAIL_PROP_TYPE_SHAPE_CROSS: + case DETAIL_PROP_TYPE_SHAPE_TRI: + { + for( int i=0;im_nNumSprites = nNumFastObjectsInCurLeaf; + pNew->m_nNumSIMDSprites = ( 3 + nNumFastObjectsInCurLeaf ) >> 2; + pNew->m_pSprites = pCurFastSpriteOut; + pCurFastSpriteOut += pNew->m_nNumSIMDSprites; + ClientLeafSystem()->SetSubSystemDataInLeaf( + detailObjectLeaf, CLSUBSYSTEM_DETAILOBJECTS, pNew ); + } + ClientLeafSystem()->SetDetailObjectsInLeaf( detailObjectLeaf, + firstDetailObject, detailObjectCount ); + } +} + + +Vector CDetailObjectSystem::GetSpriteMiddleBottomPosition( DetailObjectLump_t const &lump ) const +{ + DetailPropSpriteDict_t &dict = s_DetailObjectSystem.DetailSpriteDict( lump.m_DetailModel ); + + Vector vecDir; + QAngle Angles; + + VectorSubtract( lump.m_Origin + Vector(0,-100,0), lump.m_Origin, vecDir ); + vecDir.z = 0.0f; + VectorAngles( vecDir, Angles ); + + Vector vecOrigin, dx, dy; + AngleVectors( Angles, NULL, &dx, &dy ); + + Vector2D ul, lr; + float scale = lump.m_flScale; + Vector2DMultiply( dict.m_UL, scale, ul ); + Vector2DMultiply( dict.m_LR, scale, lr ); + + VectorMA( lump.m_Origin, ul.x, dx, vecOrigin ); + VectorMA( vecOrigin, ul.y, dy, vecOrigin ); + dx *= (lr.x - ul.x); + dy *= (lr.y - ul.y); + + Vector2D texul, texlr; + texul = dict.m_TexUL; + texlr = dict.m_TexLR; + + return vecOrigin + dy + 0.5 * dx; +} + + +void CDetailObjectSystem::UnserializeFastSprite( FastSpriteX4_t *pSpritex4, int nSubField, DetailObjectLump_t const &lump, bool bFlipped, Vector const &posOffset ) +{ + Vector pos = lump.m_Origin + posOffset; + pos = GetSpriteMiddleBottomPosition( lump ) + posOffset; + + pSpritex4->m_Pos.X( nSubField ) = pos.x; + pSpritex4->m_Pos.Y( nSubField ) = pos.y; + pSpritex4->m_Pos.Z( nSubField ) = pos.z; + DetailPropSpriteDict_t *pSDef = &m_DetailSpriteDict[lump.m_DetailModel]; + + SubFloat( pSpritex4->m_HalfWidth, nSubField ) = 0.5 * lump.m_flScale * ( pSDef->m_LR.x - pSDef->m_UL.x ); + SubFloat( pSpritex4->m_Height, nSubField ) = lump.m_flScale * ( pSDef->m_LR.y - pSDef->m_UL.y ); + if ( !bFlipped ) + { + pSDef = &m_DetailSpriteDictFlipped[lump.m_DetailModel]; + } + // do packed color + ColorRGBExp32 rgbcolor = lump.m_Lighting; + float color[4]; + color[0] = TexLightToLinear( rgbcolor.r, rgbcolor.exponent ); + color[1] = TexLightToLinear( rgbcolor.g, rgbcolor.exponent ); + color[2] = TexLightToLinear( rgbcolor.b, rgbcolor.exponent ); + color[3] = 255; + engine->LinearToGamma( color, color ); + pSpritex4->m_RGBColor[nSubField][0] = 255.0 * color[0]; + pSpritex4->m_RGBColor[nSubField][1] = 255.0 * color[1]; + pSpritex4->m_RGBColor[nSubField][2] = 255.0 * color[2]; + pSpritex4->m_RGBColor[nSubField][3] = 255; + + pSpritex4->m_pSpriteDefs[nSubField] = pSDef; +} + + + +//----------------------------------------------------------------------------- +// Renders all opaque detail objects in a particular set of leaves +//----------------------------------------------------------------------------- +void CDetailObjectSystem::RenderOpaqueDetailObjects( int nLeafCount, LeafIndex_t *pLeafList ) +{ + // FIXME: Implement! +} + + +//----------------------------------------------------------------------------- +// Count the number of detail sprites in the leaf list +//----------------------------------------------------------------------------- +int CDetailObjectSystem::CountSpritesInLeafList( int nLeafCount, LeafIndex_t *pLeafList ) const +{ + VPROF_BUDGET( "CDetailObjectSystem::CountSpritesInLeafList", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + int nPropCount = 0; + int nFirstDetailObject, nDetailObjectCount; + for ( int i = 0; i < nLeafCount; ++i ) + { + // FIXME: This actually counts *everything* in the leaf, which is ok for now + // given how we're using it + ClientLeafSystem()->GetDetailObjectsInLeaf( pLeafList[i], nFirstDetailObject, nDetailObjectCount ); + nPropCount += nDetailObjectCount; + } + + return nPropCount; +} + +//----------------------------------------------------------------------------- +// Count the number of fast sprites in the leaf list +//----------------------------------------------------------------------------- +int CDetailObjectSystem::CountFastSpritesInLeafList( int nLeafCount, LeafIndex_t const *pLeafList, + int *nMaxFoundInLeaf ) const +{ + VPROF_BUDGET( "CDetailObjectSystem::CountSpritesInLeafList", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + int nCount = 0; + int nMax = 0; + for ( int i = 0; i < nLeafCount; ++i ) + { + CFastDetailLeafSpriteList *pData = reinterpret_cast< CFastDetailLeafSpriteList *> ( + ClientLeafSystem()->GetSubSystemDataInLeaf( pLeafList[i], CLSUBSYSTEM_DETAILOBJECTS ) ); + if ( pData ) + { + nCount += pData->m_nNumSprites; + nMax = max( nMax, pData->m_nNumSprites ); + } + } + *nMaxFoundInLeaf = ( nMax + 3 ) & ~3; // round up + return nCount; +} + + +//----------------------------------------------------------------------------- +// Count the number of detail sprite quads in the leaf list +//----------------------------------------------------------------------------- +int CDetailObjectSystem::CountSpriteQuadsInLeafList( int nLeafCount, LeafIndex_t *pLeafList ) const +{ +#ifdef USE_DETAIL_SHAPES + VPROF_BUDGET( "CDetailObjectSystem::CountSpritesInLeafList", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + int nQuadCount = 0; + int nFirstDetailObject, nDetailObjectCount; + for ( int i = 0; i < nLeafCount; ++i ) + { + // FIXME: This actually counts *everything* in the leaf, which is ok for now + // given how we're using it + ClientLeafSystem()->GetDetailObjectsInLeaf( pLeafList[i], nFirstDetailObject, nDetailObjectCount ); + for ( int j = 0; j < nDetailObjectCount; ++j ) + { + nQuadCount += m_DetailObjects[j + nFirstDetailObject].QuadsToDraw(); + } + } + + return nQuadCount; +#else + return CountSpritesInLeafList( nLeafCount, pLeafList ); +#endif +} + + +#define TREATASINT(x) ( *( ( (int32 const *)( &(x) ) ) ) ) + +//----------------------------------------------------------------------------- +// Sorts sprites in back-to-front order +//----------------------------------------------------------------------------- +inline bool CDetailObjectSystem::SortLessFunc( const CDetailObjectSystem::SortInfo_t &left, const CDetailObjectSystem::SortInfo_t &right ) +{ + return TREATASINT( left.m_flDistance ) > TREATASINT( right.m_flDistance ); +// return left.m_flDistance > right.m_flDistance; +} + + +int CDetailObjectSystem::SortSpritesBackToFront( int nLeaf, const Vector &viewOrigin, const Vector &viewForward, SortInfo_t *pSortInfo ) +{ + VPROF_BUDGET( "CDetailObjectSystem::SortSpritesBackToFront", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + int nFirstDetailObject, nDetailObjectCount; + ClientLeafSystem()->GetDetailObjectsInLeaf( nLeaf, nFirstDetailObject, nDetailObjectCount ); + + float flFactor = 1.0f; + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + flFactor = 1.0 / pLocalPlayer->GetFOVDistanceAdjustFactor(); + } + + float flMaxSqDist; + float flFadeSqDist; + float flDetailDist = cl_detaildist.GetFloat(); + + flMaxSqDist = flDetailDist * flDetailDist; + flFadeSqDist = flDetailDist - cl_detailfade.GetFloat(); + flMaxSqDist *= flFactor; + flFadeSqDist *= flFactor; + if (flFadeSqDist > 0) + { + flFadeSqDist *= flFadeSqDist; + } + else + { + flFadeSqDist = 0; + } + float flFalloffFactor = 255.0f / (flMaxSqDist - flFadeSqDist); + + Vector vecDelta; + int nCount = 0; + nDetailObjectCount += nFirstDetailObject; + for ( int j = nFirstDetailObject; j < nDetailObjectCount; ++j ) + { + CDetailModel &model = m_DetailObjects[j]; + + Vector v; + VectorSubtract( model.GetRenderOrigin(), viewOrigin, vecDelta ); + float flSqDist = vecDelta.LengthSqr(); + if ( flSqDist >= flMaxSqDist ) + continue; + + if ((flFadeSqDist > 0) && (flSqDist > flFadeSqDist)) + { + model.SetAlpha( flFalloffFactor * ( flMaxSqDist - flSqDist ) ); + } + else + { + model.SetAlpha( 255 ); + } + + if ( (model.GetType() == DETAIL_PROP_TYPE_MODEL) || (model.GetAlpha() == 0) ) + continue; + + // Perform screen alignment if necessary. + model.ComputeAngles(); + SortInfo_t *pSortInfoCurrent = &pSortInfo[nCount]; + + pSortInfoCurrent->m_nIndex = j; + + // Compute distance from the camera to each object + pSortInfoCurrent->m_flDistance = flSqDist; + ++nCount; + } + + if ( nCount ) + { + VPROF( "CDetailObjectSystem::SortSpritesBackToFront -- Sort" ); + std::make_heap( pSortInfo, pSortInfo + nCount, SortLessFunc ); + std::sort_heap( pSortInfo, pSortInfo + nCount, SortLessFunc ); + } + + return nCount; +} + + +#define MAGIC_NUMBER (1<<23) +#ifdef BIG_ENDIAN +#define MANTISSA_LSB_OFFSET 3 +#else +#define MANTISSA_LSB_OFFSET 0 +#endif +static fltx4 Four_MagicNumbers={ MAGIC_NUMBER, MAGIC_NUMBER, MAGIC_NUMBER, MAGIC_NUMBER }; +static fltx4 Four_255s={ 255.0, 255.0, 255.0, 255.0 }; + +static __declspec(align(16)) int32 And255Mask[4]= {0xff,0xff,0xff,0xff}; +#define PIXMASK ( * ( reinterpret_cast< fltx4 *>( &And255Mask ) ) ) + +int CDetailObjectSystem::BuildOutSortedSprites( CFastDetailLeafSpriteList *pData, + Vector const &viewOrigin, + Vector const &viewForward, + Vector const &viewRight, + Vector const &viewUp ) +{ + // part 1 - do all vertex math, fading, etc into a buffer, using as much simd as we can + int nSIMDSprites = pData->m_nNumSIMDSprites; + FastSpriteX4_t const *pSprites = pData->m_pSprites; + SortInfo_t *pOut = m_pFastSortInfo; + FastSpriteQuadBuildoutBufferX4_t *pQuadBufferOut = m_pBuildoutBuffer; + int curidx = 0; + int nLastBfMask = 0; + + FourVectors vecViewPos; + vecViewPos.DuplicateVector( viewOrigin ); + fltx4 maxsqdist = ReplicateX4( m_flCurMaxSqDist ); + + fltx4 falloffFactor = ReplicateX4( 1.0/ ( m_flCurMaxSqDist - m_flCurFadeSqDist ) ); + fltx4 startFade = ReplicateX4( m_flCurFadeSqDist ); + + FourVectors vecUp; + vecUp.DuplicateVector(Vector(0,0,1) ); + FourVectors vecFwd; + vecFwd.DuplicateVector( viewForward ); + + do + { + // calculate alpha + FourVectors ofs = pSprites->m_Pos; + ofs -= vecViewPos; + fltx4 ofsDotFwd = ofs * vecFwd; + fltx4 distanceSquared = ofs * ofs; + nLastBfMask = TestSignSIMD( OrSIMD( ofsDotFwd, CmpGtSIMD( distanceSquared, maxsqdist ) ) ); // cull + if ( nLastBfMask != 0xf ) + { + FourVectors dx1; + dx1.x = fnegate( ofs.y ); + dx1.y = ( ofs.x ); + dx1.z = Four_Zeros; + dx1.VectorNormalizeFast(); + + FourVectors vecDx = dx1; + FourVectors vecDy = vecUp; + + FourVectors vecPos0 = pSprites->m_Pos; + + vecDx *= pSprites->m_HalfWidth; + vecDy *= pSprites->m_Height; + fltx4 alpha = MulSIMD( falloffFactor, SubSIMD( distanceSquared, startFade ) ); + alpha = SubSIMD( Four_Ones, MinSIMD( MaxSIMD( alpha, Four_Zeros), Four_Ones ) ); + + pQuadBufferOut->m_Alpha = AddSIMD( Four_MagicNumbers, + MulSIMD( Four_255s,alpha ) ); + + vecPos0 += vecDx; + pQuadBufferOut->m_Coords[0] = vecPos0; + vecPos0 -= vecDy; + pQuadBufferOut->m_Coords[1] = vecPos0; + vecPos0 -= vecDx; + vecPos0 -= vecDx; + pQuadBufferOut->m_Coords[2] = vecPos0; + vecPos0 += vecDy; + pQuadBufferOut->m_Coords[3] = vecPos0; + + fltx4 fetch4 = *( ( fltx4 *) ( &pSprites->m_pSpriteDefs[0] ) ); + *( (fltx4 *) ( & ( pQuadBufferOut->m_pSpriteDefs[0] ) ) ) = fetch4; + + fetch4 = *( ( fltx4 *) ( &pSprites->m_RGBColor[0][0] ) ); + *( (fltx4 *) ( & ( pQuadBufferOut->m_RGBColor[0][0] ) ) ) = fetch4; + + //!! bug!! store distance + // !! speed!! simd? + pOut[0].m_nIndex = curidx; + pOut[0].m_flDistance = SubFloat( distanceSquared, 0 ); + pOut[1].m_nIndex = curidx+1; + pOut[1].m_flDistance = SubFloat( distanceSquared, 1 ); + pOut[2].m_nIndex = curidx+2; + pOut[2].m_flDistance = SubFloat( distanceSquared, 2 ); + pOut[3].m_nIndex = curidx+3; + pOut[3].m_flDistance = SubFloat( distanceSquared, 3 ); + curidx += 4; + pOut += 4; + pQuadBufferOut++; + } + pSprites++; + } while( --nSIMDSprites ); + + // adjust count for tail + int nCount = pOut - m_pFastSortInfo; + if ( nLastBfMask != 0xf ) // if last not skipped + nCount -= ( 0 - pData->m_nNumSprites ) & 3; + + // part 2 - sort + if ( nCount ) + { + VPROF( "CDetailObjectSystem::SortSpritesBackToFront -- Sort" ); + std::make_heap( m_pFastSortInfo, m_pFastSortInfo + nCount, SortLessFunc ); + std::sort_heap( m_pFastSortInfo, m_pFastSortInfo + nCount, SortLessFunc ); + } + return nCount; +} + + +void CDetailObjectSystem::RenderFastSprites( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeafCount, LeafIndex_t const * pLeafList ) +{ + // Here, we must draw all detail objects back-to-front + // FIXME: Cache off a sorted list so we don't have to re-sort every frame + + // Count the total # of detail quads we possibly could render + int nMaxInLeaf; + + int nQuadCount = CountFastSpritesInLeafList( nLeafCount, pLeafList, &nMaxInLeaf ); + if ( nQuadCount == 0 ) + return; + if ( r_DrawDetailProps.GetInt() == 0 ) + return; + + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + IMaterial *pMaterial = m_DetailSpriteMaterial; + if ( ShouldDrawInWireFrameMode() || r_DrawDetailProps.GetInt() == 2 ) + { + pMaterial = m_DetailWireframeMaterial; + } + + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + + int nMaxVerts, nMaxIndices; + pRenderContext->GetMaxToRender( pMesh, false, &nMaxVerts, &nMaxIndices ); + int nMaxQuadsToDraw = nMaxIndices / 6; + if ( nMaxQuadsToDraw > nMaxVerts / 4 ) + { + nMaxQuadsToDraw = nMaxVerts / 4; + } + + int nQuadsToDraw = min( nQuadCount, nMaxQuadsToDraw ); + int nQuadsRemaining = nQuadsToDraw; + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + + + + // Sort detail sprites in each leaf independently; then render them + for ( int i = 0; i < nLeafCount; ++i ) + { + int nLeaf = pLeafList[i]; + + CFastDetailLeafSpriteList *pData = reinterpret_cast ( + ClientLeafSystem()->GetSubSystemDataInLeaf( nLeaf, CLSUBSYSTEM_DETAILOBJECTS ) ); + + if ( pData ) + { + Assert( pData->m_nNumSprites ); // ptr with no sprites? + + int nCount = BuildOutSortedSprites( pData, viewOrigin, viewForward, viewRight, viewUp ); + + // part 3 - stuff the sorted sprites into the vb + SortInfo_t const *pDraw = m_pFastSortInfo; + FastSpriteQuadBuildoutBufferNonSIMDView_t const *pQuadBuffer = + ( FastSpriteQuadBuildoutBufferNonSIMDView_t const *) m_pBuildoutBuffer; + + COMPILE_TIME_ASSERT( sizeof( FastSpriteQuadBuildoutBufferNonSIMDView_t ) == + sizeof( FastSpriteQuadBuildoutBufferX4_t ) ); + + while( nCount ) + { + if ( ! nQuadsRemaining ) // no room left? + { + meshBuilder.End(); + pMesh->Draw(); + nQuadsRemaining = nQuadsToDraw; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + } + int nToDraw = min( nCount, nQuadsRemaining ); + nCount -= nToDraw; + nQuadsRemaining -= nToDraw; + while( nToDraw-- ) + { + // draw the sucker + int nSIMDIdx = pDraw->m_nIndex >> 2; + int nSubIdx = pDraw->m_nIndex & 3; + + FastSpriteQuadBuildoutBufferNonSIMDView_t const *pquad = pQuadBuffer+nSIMDIdx; + + // voodoo - since everything is in 4s, offset structure pointer by a couple of floats to handle sub-index + pquad = (FastSpriteQuadBuildoutBufferNonSIMDView_t const *) ( ( (int) ( pquad ) )+ ( nSubIdx << 2 ) ); + uint8 const *pColorsCasted = reinterpret_cast ( pquad->m_Alpha ); + + uint8 color[4]; + color[0] = pquad->m_RGBColor[0][0]; + color[1] = pquad->m_RGBColor[0][1]; + color[2] = pquad->m_RGBColor[0][2]; + color[3] = pColorsCasted[MANTISSA_LSB_OFFSET]; + + DetailPropSpriteDict_t *pDict = pquad->m_pSpriteDefs[0]; + + meshBuilder.Position3f( pquad->m_flX0[0], pquad->m_flY0[0], pquad->m_flZ0[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexLR.x, pDict->m_TexLR.y ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( pquad->m_flX1[0], pquad->m_flY1[0], pquad->m_flZ1[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexLR.x, pDict->m_TexUL.y ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( pquad->m_flX2[0], pquad->m_flY2[0], pquad->m_flZ2[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexUL.x, pDict->m_TexUL.y ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( pquad->m_flX3[0], pquad->m_flY3[0], pquad->m_flZ3[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexUL.x, pDict->m_TexLR.y ); + meshBuilder.AdvanceVertex(); + pDraw++; + } + } + } + } + meshBuilder.End(); + pMesh->Draw(); + pRenderContext->PopMatrix(); +} + + +//----------------------------------------------------------------------------- +// Renders all translucent detail objects in a particular set of leaves +//----------------------------------------------------------------------------- +void CDetailObjectSystem::RenderTranslucentDetailObjects( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeafCount, LeafIndex_t *pLeafList ) +{ + VPROF_BUDGET( "CDetailObjectSystem::RenderTranslucentDetailObjects", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + if (nLeafCount == 0) + return; + + // We better not have any partially drawn leaf of detail sprites! + Assert( m_nSpriteCount == m_nFirstSprite ); + + // Here, we must draw all detail objects back-to-front + RenderFastSprites( viewOrigin, viewForward, viewRight, viewUp, nLeafCount, pLeafList ); + + // FIXME: Cache off a sorted list so we don't have to re-sort every frame + + // Count the total # of detail quads we possibly could render + int nQuadCount = CountSpriteQuadsInLeafList( nLeafCount, pLeafList ); + if ( nQuadCount == 0 ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + IMaterial *pMaterial = m_DetailSpriteMaterial; + if ( ShouldDrawInWireFrameMode() || r_DrawDetailProps.GetInt() == 2 ) + { + pMaterial = m_DetailWireframeMaterial; + } + + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + + int nMaxVerts, nMaxIndices; + pRenderContext->GetMaxToRender( pMesh, false, &nMaxVerts, &nMaxIndices ); + int nMaxQuadsToDraw = nMaxIndices / 6; + if ( nMaxQuadsToDraw > nMaxVerts / 4 ) + { + nMaxQuadsToDraw = nMaxVerts / 4; + } + + int nQuadsToDraw = nQuadCount; + if ( nQuadsToDraw > nMaxQuadsToDraw ) + { + nQuadsToDraw = nMaxQuadsToDraw; + } + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + + int nQuadsDrawn = 0; + for ( int i = 0; i < nLeafCount; ++i ) + { + int nLeaf = pLeafList[i]; + + int nFirstDetailObject, nDetailObjectCount; + ClientLeafSystem()->GetDetailObjectsInLeaf( nLeaf, nFirstDetailObject, nDetailObjectCount ); + + // Sort detail sprites in each leaf independently; then render them + SortInfo_t *pSortInfo = m_pSortInfo; + int nCount = SortSpritesBackToFront( nLeaf, viewOrigin, viewForward, pSortInfo ); + + for ( int j = 0; j < nCount; ++j ) + { + CDetailModel &model = m_DetailObjects[ pSortInfo[j].m_nIndex ]; + int nQuadsInModel = model.QuadsToDraw(); + + // Prevent the batches from getting too large + if ( nQuadsDrawn + nQuadsInModel > nQuadsToDraw ) + { + meshBuilder.End(); + pMesh->Draw(); + + nQuadCount -= nQuadsDrawn; + nQuadsToDraw = nQuadCount; + if (nQuadsToDraw > nMaxQuadsToDraw) + { + nQuadsToDraw = nMaxQuadsToDraw; + } + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + nQuadsDrawn = 0; + } + + model.DrawSprite( meshBuilder ); + + nQuadsDrawn += nQuadsInModel; + } + } + + meshBuilder.End(); + pMesh->Draw(); + + pRenderContext->PopMatrix(); +} + + +void CDetailObjectSystem::RenderFastTranslucentDetailObjectsInLeaf( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeaf, const Vector *pVecClosestPoint ) +{ + CFastDetailLeafSpriteList *pData = reinterpret_cast< CFastDetailLeafSpriteList *> ( + ClientLeafSystem()->GetSubSystemDataInLeaf( nLeaf, CLSUBSYSTEM_DETAILOBJECTS ) ); + if ( ! pData ) + return; + + if ( m_nSortedFastLeaf != nLeaf ) + { + m_nSortedFastLeaf = nLeaf; + pData->m_nNumPendingSprites = BuildOutSortedSprites( pData, viewOrigin, viewForward, viewRight, viewUp ); + pData->m_nStartSpriteIndex = 0; + } + if ( pData->m_nNumPendingSprites == 0 ) + return; + + float flMinDistance = 0.0f; + if ( pVecClosestPoint ) + { + Vector vecDelta; + VectorSubtract( *pVecClosestPoint, viewOrigin, vecDelta ); + flMinDistance = vecDelta.LengthSqr(); + } + + if ( m_pFastSortInfo[pData->m_nStartSpriteIndex].m_flDistance < flMinDistance ) + return; + + int nCount = pData->m_nNumPendingSprites; + + if ( r_DrawDetailProps.GetInt() == 0 ) + return; + + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + IMaterial *pMaterial = m_DetailSpriteMaterial; + if ( ShouldDrawInWireFrameMode() || r_DrawDetailProps.GetInt() == 2 ) + { + pMaterial = m_DetailWireframeMaterial; + } + + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + + int nMaxVerts, nMaxIndices; + pRenderContext->GetMaxToRender( pMesh, false, &nMaxVerts, &nMaxIndices ); + int nMaxQuadsToDraw = nMaxIndices / 6; + if ( nMaxQuadsToDraw > nMaxVerts / 4 ) + { + nMaxQuadsToDraw = nMaxVerts / 4; + } + + int nQuadsToDraw = min( nCount, nMaxQuadsToDraw ); + int nQuadsRemaining = nQuadsToDraw; + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + + SortInfo_t const *pDraw = m_pFastSortInfo + pData->m_nStartSpriteIndex; + + FastSpriteQuadBuildoutBufferNonSIMDView_t const *pQuadBuffer = + ( FastSpriteQuadBuildoutBufferNonSIMDView_t const *) m_pBuildoutBuffer; + + while( nCount && ( pDraw->m_flDistance >= flMinDistance ) ) + { + if ( ! nQuadsRemaining ) // no room left? + { + meshBuilder.End(); + pMesh->Draw(); + nQuadsRemaining = nQuadsToDraw; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + } + int nToDraw = min( nCount, nQuadsRemaining ); + nCount -= nToDraw; + nQuadsRemaining -= nToDraw; + while( nToDraw-- ) + { + // draw the sucker + int nSIMDIdx = pDraw->m_nIndex >> 2; + int nSubIdx = pDraw->m_nIndex & 3; + + FastSpriteQuadBuildoutBufferNonSIMDView_t const *pquad = pQuadBuffer+nSIMDIdx; + + // voodoo - since everything is in 4s, offset structure pointer by a couple of floats to handle sub-index + pquad = (FastSpriteQuadBuildoutBufferNonSIMDView_t const *) ( ( (int) ( pquad ) )+ ( nSubIdx << 2 ) ); + uint8 const *pColorsCasted = reinterpret_cast ( pquad->m_Alpha ); + + uint8 color[4]; + color[0] = pquad->m_RGBColor[0][0]; + color[1] = pquad->m_RGBColor[0][1]; + color[2] = pquad->m_RGBColor[0][2]; + color[3] = pColorsCasted[MANTISSA_LSB_OFFSET]; + + DetailPropSpriteDict_t *pDict = pquad->m_pSpriteDefs[0]; + + meshBuilder.Position3f( pquad->m_flX0[0], pquad->m_flY0[0], pquad->m_flZ0[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexLR.x, pDict->m_TexLR.y ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( pquad->m_flX1[0], pquad->m_flY1[0], pquad->m_flZ1[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexLR.x, pDict->m_TexUL.y ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( pquad->m_flX2[0], pquad->m_flY2[0], pquad->m_flZ2[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexUL.x, pDict->m_TexUL.y ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( pquad->m_flX3[0], pquad->m_flY3[0], pquad->m_flZ3[0] ); + meshBuilder.Color4ubv( color ); + meshBuilder.TexCoord2f( 0, pDict->m_TexUL.x, pDict->m_TexLR.y ); + meshBuilder.AdvanceVertex(); + pDraw++; + } + } + pData->m_nNumPendingSprites = nCount; + pData->m_nStartSpriteIndex = pDraw - m_pFastSortInfo; + + meshBuilder.End(); + pMesh->Draw(); + pRenderContext->PopMatrix(); +} + +//----------------------------------------------------------------------------- +// Renders a subset of the detail objects in a particular leaf (for interleaving with other translucent entities) +//----------------------------------------------------------------------------- +void CDetailObjectSystem::RenderTranslucentDetailObjectsInLeaf( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeaf, const Vector *pVecClosestPoint ) +{ + VPROF_BUDGET( "CDetailObjectSystem::RenderTranslucentDetailObjectsInLeaf", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + + RenderFastTranslucentDetailObjectsInLeaf( viewOrigin, viewForward, viewRight, viewUp, nLeaf, pVecClosestPoint ); + // We may have already sorted this leaf. If not, sort the leaf. + if ( m_nSortedLeaf != nLeaf ) + { + m_nSortedLeaf = nLeaf; + m_nSpriteCount = 0; + m_nFirstSprite = 0; + + // Count the total # of detail sprites we possibly could render + LeafIndex_t nLeafIndex = nLeaf; + int nSpriteCount = CountSpritesInLeafList( 1, &nLeafIndex ); + if (nSpriteCount == 0) + return; + + // Sort detail sprites in each leaf independently; then render them + m_nSpriteCount = SortSpritesBackToFront( nLeaf, viewOrigin, viewForward, m_pSortInfo ); + Assert( m_nSpriteCount <= nSpriteCount ); + } + + // No more to draw? Bye! + if ( m_nSpriteCount == m_nFirstSprite ) + return; + + float flMinDistance = 0.0f; + if ( pVecClosestPoint ) + { + Vector vecDelta; + VectorSubtract( *pVecClosestPoint, viewOrigin, vecDelta ); + flMinDistance = vecDelta.LengthSqr(); + } + + if ( m_pSortInfo[m_nFirstSprite].m_flDistance < flMinDistance ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + IMaterial *pMaterial = m_DetailSpriteMaterial; + if ( ShouldDrawInWireFrameMode() || r_DrawDetailProps.GetInt() == 2 ) + { + pMaterial = m_DetailWireframeMaterial; + } + + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + + int nMaxVerts, nMaxIndices; + pRenderContext->GetMaxToRender( pMesh, false, &nMaxVerts, &nMaxIndices ); + + // needs to be * 4 since there are a max of 4 quads per detail object + int nQuadCount = ( m_nSpriteCount - m_nFirstSprite ) * 4; + int nMaxQuadsToDraw = nMaxIndices/6; + if ( nMaxQuadsToDraw > nMaxVerts / 4 ) + { + nMaxQuadsToDraw = nMaxVerts / 4; + } + int nQuadsToDraw = nQuadCount; + if ( nQuadsToDraw > nMaxQuadsToDraw ) + { + nQuadsToDraw = nMaxQuadsToDraw; + } + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + + int nQuadsDrawn = 0; + while ( m_nFirstSprite < m_nSpriteCount && m_pSortInfo[m_nFirstSprite].m_flDistance >= flMinDistance ) + { + CDetailModel &model = m_DetailObjects[m_pSortInfo[m_nFirstSprite].m_nIndex]; + int nQuadsInModel = model.QuadsToDraw(); + if ( nQuadsDrawn + nQuadsInModel > nQuadsToDraw ) + { + meshBuilder.End(); + pMesh->Draw(); + + nQuadCount = ( m_nSpriteCount - m_nFirstSprite ) * 4; + nQuadsToDraw = nQuadCount; + if (nQuadsToDraw > nMaxQuadsToDraw) + { + nQuadsToDraw = nMaxQuadsToDraw; + } + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nQuadsToDraw ); + nQuadsDrawn = 0; + } + + model.DrawSprite( meshBuilder ); + ++m_nFirstSprite; + nQuadsDrawn += nQuadsInModel; + } + meshBuilder.End(); + pMesh->Draw(); + + pRenderContext->PopMatrix(); +} + + +//----------------------------------------------------------------------------- +// Gets called each view +//----------------------------------------------------------------------------- +bool CDetailObjectSystem::EnumerateLeaf( int leaf, int context ) +{ + VPROF_BUDGET( "CDetailObjectSystem::EnumerateLeaf", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + Vector v; + int firstDetailObject, detailObjectCount; + + EnumContext_t* pCtx = (EnumContext_t*)context; + ClientLeafSystem()->DrawDetailObjectsInLeaf( leaf, pCtx->m_BuildWorldListNumber, + firstDetailObject, detailObjectCount ); + + // Compute the translucency. Need to do it now cause we need to + // know that when we're rendering (opaque stuff is rendered first) + for ( int i = 0; i < detailObjectCount; ++i) + { + // Calculate distance (badly) + CDetailModel& model = m_DetailObjects[firstDetailObject+i]; + VectorSubtract( model.GetRenderOrigin(), pCtx->m_vViewOrigin, v ); + + float sqDist = v.LengthSqr(); + + model.SetAlpha( 255 ); + if ( sqDist < m_flCurMaxSqDist ) + { + if ( sqDist > m_flCurFadeSqDist ) + { + model.SetAlpha( m_flCurFalloffFactor * ( m_flCurMaxSqDist - sqDist ) ); + } + else + { + model.SetAlpha( 255 ); + } + + // Perform screen alignment if necessary. + model.ComputeAngles(); + } + else + { + model.SetAlpha( 0 ); + } + } + return true; +} + + +//----------------------------------------------------------------------------- +// Gets called each view +//----------------------------------------------------------------------------- +void CDetailObjectSystem::BuildDetailObjectRenderLists( const Vector &vViewOrigin ) +{ + VPROF_BUDGET( "CDetailObjectSystem::BuildDetailObjectRenderLists", VPROF_BUDGETGROUP_DETAILPROP_RENDERING ); + + if (!g_pClientMode->ShouldDrawDetailObjects() || (r_DrawDetailProps.GetInt() == 0)) + return; + + // Don't bother doing any of this if the level doesn't have detail props. + if ( ( ! m_pFastSpriteData ) && ( m_DetailObjects.Count() == 0 ) ) + return; + + EnumContext_t ctx; + ctx.m_vViewOrigin = vViewOrigin; + ctx.m_BuildWorldListNumber = view->BuildWorldListsNumber(); + + // We need to recompute translucency information for all detail props + for (int i = m_DetailObjectDict.Size(); --i >= 0; ) + { + if (modelinfo->ModelHasMaterialProxy( m_DetailObjectDict[i].m_pModel )) + { + modelinfo->RecomputeTranslucency( m_DetailObjectDict[i].m_pModel, 0, 0, NULL ); + } + } + + float factor = 1.0f; + C_BasePlayer *local = C_BasePlayer::GetLocalPlayer(); + if ( local ) + { + factor = local->GetFOVDistanceAdjustFactor(); + } + + // Compute factors to optimize rendering of the detail models + m_flCurMaxSqDist = cl_detaildist.GetFloat() * cl_detaildist.GetFloat(); + m_flCurFadeSqDist = cl_detaildist.GetFloat() - cl_detailfade.GetFloat(); + + m_flCurMaxSqDist /= factor; + m_flCurFadeSqDist /= factor; + + if ( m_flCurFadeSqDist > 0) + { + m_flCurFadeSqDist *= m_flCurFadeSqDist; + } + else + { + m_flCurFadeSqDist = 0; + } + m_flCurFadeSqDist = min( m_flCurFadeSqDist, m_flCurMaxSqDist -1 ); + m_flCurFalloffFactor = 255.0f / ( m_flCurMaxSqDist - m_flCurFadeSqDist ); + + + ISpatialQuery* pQuery = engine->GetBSPTreeQuery(); + pQuery->EnumerateLeavesInSphere( CurrentViewOrigin(), + cl_detaildist.GetFloat(), this, (int)&ctx ); +} + diff --git a/game/client/detailobjectsystem.h b/game/client/detailobjectsystem.h new file mode 100644 index 00000000..f7403681 --- /dev/null +++ b/game/client/detailobjectsystem.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Deals with singleton +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#if !defined( DETAILOBJECTSYSTEM_H ) +#define DETAILOBJECTSYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "IGameSystem.h" +#include "IClientEntityInternal.h" +#include "engine/IVModelRender.h" +#include "mathlib/vector.h" +#include "IVRenderView.h" + +struct model_t; + + +//----------------------------------------------------------------------------- +// Responsible for managing detail objects +//----------------------------------------------------------------------------- +abstract_class IDetailObjectSystem : public IGameSystem +{ +public: + // Gets a particular detail object + virtual IClientRenderable* GetDetailModel( int idx ) = 0; + + // Gets called each view + virtual void BuildDetailObjectRenderLists( const Vector &vViewOrigin ) = 0; + + // Renders all opaque detail objects in a particular set of leaves + virtual void RenderOpaqueDetailObjects( int nLeafCount, LeafIndex_t *pLeafList ) = 0; + + // Call this before rendering translucent detail objects + virtual void BeginTranslucentDetailRendering( ) = 0; + + // Renders all translucent detail objects in a particular set of leaves + virtual void RenderTranslucentDetailObjects( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeafCount, LeafIndex_t *pLeafList ) =0; + + // Renders all translucent detail objects in a particular leaf up to a particular point + virtual void RenderTranslucentDetailObjectsInLeaf( const Vector &viewOrigin, const Vector &viewForward, const Vector &viewRight, const Vector &viewUp, int nLeaf, const Vector *pVecClosestPoint ) = 0; +}; + + +//----------------------------------------------------------------------------- +// System for dealing with detail objects +//----------------------------------------------------------------------------- +IDetailObjectSystem* DetailObjectSystem(); + + +#endif // DETAILOBJECTSYSTEM_H + diff --git a/game/client/dummyproxy.cpp b/game/client/dummyproxy.cpp new file mode 100644 index 00000000..13032c98 --- /dev/null +++ b/game/client/dummyproxy.cpp @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "materialsystem/IMaterialProxy.h" +#include "materialsystem/IMaterial.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CDummyMaterialProxy : public IMaterialProxy +{ +public: + CDummyMaterialProxy(); + virtual ~CDummyMaterialProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pC_BaseEntity ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial() { return NULL; } +}; + +CDummyMaterialProxy::CDummyMaterialProxy() +{ + DevMsg( 1, "CDummyMaterialProxy::CDummyMaterialProxy()\n" ); +} + +CDummyMaterialProxy::~CDummyMaterialProxy() +{ + DevMsg( 1, "CDummyMaterialProxy::~CDummyMaterialProxy()\n" ); +} + + +bool CDummyMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + DevMsg( 1, "CDummyMaterialProxy::Init( material = \"%s\" )\n", pMaterial->GetName() ); + return true; +} + +void CDummyMaterialProxy::OnBind( void *pC_BaseEntity ) +{ + DevMsg( 1, "CDummyMaterialProxy::OnBind( %p )\n", pC_BaseEntity ); +} + +EXPOSE_INTERFACE( CDummyMaterialProxy, IMaterialProxy, "Dummy" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/enginesprite.h b/game/client/enginesprite.h new file mode 100644 index 00000000..00b1751e --- /dev/null +++ b/game/client/enginesprite.h @@ -0,0 +1,70 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef ENGINESPRITE_H +#define ENGINESPRITE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mathlib/vector.h" +#include "avi/iavi.h" +#include "avi/ibik.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IMaterial; +class IMaterialVar; +typedef struct wrect_s wrect_t; + + +//----------------------------------------------------------------------------- +// Purpose: Sprite Models +//----------------------------------------------------------------------------- +class CEngineSprite +{ + // NOTE: don't define a constructor or destructor so that this can be allocated + // as before. +public: + int GetWidth() { return m_width; } + int GetHeight() { return m_height; } + int GetNumFrames() { return m_numFrames; } + IMaterial *GetMaterial() { return m_material; } // hack - should keep this internal and draw internally. + bool Init( const char *name ); + void Shutdown( void ); + void UnloadMaterial(); + void SetColor( float r, float g, float b ); + void SetAdditive( bool enable ); + void SetFrame( float frame ); + void SetRenderMode( int renderMode ); + int GetOrientation( void ); + void GetHUDSpriteColor( float* color ); + float GetUp() { return up; } + float GetDown() { return down; } + float GetLeft() { return left; } + float GetRight() { return right; } + void DrawFrame( int frame, int x, int y, const wrect_t *prcSubRect ); + void DrawFrameOfSize( int frame, int x, int y, int iWidth, int iHeight, const wrect_t *prcSubRect); + bool IsAVI(); + bool IsBIK(); + void GetTexCoordRange( float *pMinU, float *pMinV, float *pMaxU, float *pMaxV ); + +private: + AVIMaterial_t m_hAVIMaterial; + BIKMaterial_t m_hBIKMaterial; + int m_width; + int m_height; + int m_numFrames; + IMaterial *m_material; + int m_orientation; + float m_hudSpriteColor[3]; + float up, down, left, right; +}; + +#endif // ENGINESPRITE_H diff --git a/game/client/entity_client_tools.cpp b/game/client/entity_client_tools.cpp new file mode 100644 index 00000000..d6ed5c9a --- /dev/null +++ b/game/client/entity_client_tools.cpp @@ -0,0 +1,793 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#include "cbase.h" +#include "toolframework/itoolentity.h" +#include "tier1/KeyValues.h" +#include "sprite.h" +#include "enginesprite.h" +#include "toolframework_client.h" +#include "particles/particles.h" +#include "particle_parse.h" +#include "rendertexture.h" + +#ifdef PORTAL + #include "portalrender.h" +#endif + +#pragma warning( disable: 4355 ) // warning C4355: 'this' : used in base member initializer list + +class CClientTools; + +void DrawSpriteModel( IClientEntity *baseentity, CEngineSprite *psprite, + const Vector &origin, float fscale, float frame, + int rendermode, int r, int g, int b, int a, + const Vector& forward, const Vector& right, const Vector& up, float flHDRColorScale = 1.0f ); +float StandardGlowBlend( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle, + int rendermode, int renderfx, int alpha, float *pscale ); + + +// Interface from engine to tools for manipulating entities +class CClientTools : public IClientTools, public IClientEntityListener +{ +public: + CClientTools(); + + virtual HTOOLHANDLE AttachToEntity( EntitySearchResult entityToAttach ); + virtual void DetachFromEntity( EntitySearchResult entityToDetach ); + virtual bool IsValidHandle( HTOOLHANDLE handle ); + + virtual int GetNumRecordables(); + virtual HTOOLHANDLE GetRecordable( int index ); + + // Iterates through ALL entities (separate list for client vs. server) + virtual EntitySearchResult NextEntity( EntitySearchResult currentEnt ); + + // Use this to turn on/off the presence of an underlying game entity + virtual void SetEnabled( HTOOLHANDLE handle, bool enabled ); + + virtual void SetRecording( HTOOLHANDLE handle, bool recording ); + virtual bool ShouldRecord( HTOOLHANDLE handle ); + + virtual int GetModelIndex( HTOOLHANDLE handle ); + virtual const char* GetModelName ( HTOOLHANDLE handle ); + virtual const char* GetClassname ( HTOOLHANDLE handle ); + + virtual HTOOLHANDLE GetToolHandleForEntityByIndex( int entindex ); + + virtual void AddClientRenderable( IClientRenderable *pRenderable, int renderGroup ); + virtual void RemoveClientRenderable( IClientRenderable *pRenderable ); + virtual void SetRenderGroup( IClientRenderable *pRenderable, int renderGroup ); + virtual void MarkClientRenderableDirty( IClientRenderable *pRenderable ); + + virtual bool DrawSprite( IClientRenderable *pRenderable, + float scale, float frame, + int rendermode, int renderfx, + const Color &color, float flProxyRadius, int *pVisHandle ); + + virtual bool GetLocalPlayerEyePosition( Vector& org, QAngle& ang, float &fov ); + virtual EntitySearchResult GetLocalPlayer(); + + virtual ClientShadowHandle_t CreateShadow( CBaseHandle h, int nFlags ); + virtual void DestroyShadow( ClientShadowHandle_t h ); + virtual ClientShadowHandle_t CreateFlashlight( const FlashlightState_t &lightState ); + virtual void DestroyFlashlight( ClientShadowHandle_t h ); + virtual void UpdateFlashlightState( ClientShadowHandle_t h, const FlashlightState_t &flashlightState ); + virtual void AddToDirtyShadowList( ClientShadowHandle_t h, bool force = false ); + virtual void MarkRenderToTextureShadowDirty( ClientShadowHandle_t h ); + virtual void UpdateProjectedTexture( ClientShadowHandle_t h, bool bForce ); + + // Global toggle for recording + virtual void EnableRecordingMode( bool bEnable ); + virtual bool IsInRecordingMode() const; + + // Trigger a temp entity + virtual void TriggerTempEntity( KeyValues *pKeyValues ); + + // get owning weapon (for viewmodels) + virtual int GetOwningWeaponEntIndex( int entindex ); + virtual int GetEntIndex( EntitySearchResult entityToAttach ); + + virtual int FindGlobalFlexcontroller( char const *name ); + virtual char const *GetGlobalFlexControllerName( int idx ); + + // helper for traversing ownership hierarchy + virtual EntitySearchResult GetOwnerEntity( EntitySearchResult currentEnt ); + + // common and useful types to query for hierarchically + virtual bool IsPlayer( EntitySearchResult entityToAttach ); + virtual bool IsBaseCombatCharacter( EntitySearchResult entityToAttach ); + virtual bool IsNPC( EntitySearchResult entityToAttach ); + + virtual Vector GetAbsOrigin( HTOOLHANDLE handle ); + virtual QAngle GetAbsAngles( HTOOLHANDLE handle ); + virtual void ReloadParticleDefintions( const char *pFileName, const void *pBufData, int nLen ); + + // Sends a mesage from the tool to the client + virtual void PostToolMessage( KeyValues *pKeyValues ); + + // Indicates whether the client should render particle systems + virtual void EnableParticleSystems( bool bEnable ); + + // Is the game rendering in 3rd person mode? + virtual bool IsRenderingThirdPerson() const; + +public: + C_BaseEntity *LookupEntity( HTOOLHANDLE handle ); + + // IClientEntityListener methods + void OnEntityDeleted( C_BaseEntity *pEntity ); + void OnEntityCreated( C_BaseEntity *pEntity ); + +private: + struct HToolEntry_t + { + HToolEntry_t() : m_Handle( 0 ) {} + HToolEntry_t( int handle, C_BaseEntity *pEntity = NULL ) + : m_Handle( handle ), m_hEntity( pEntity ) + { + if ( pEntity ) + { + m_hEntity->SetToolHandle( m_Handle ); + } + } + + int m_Handle; + EHANDLE m_hEntity; + }; + + static int s_nNextHandle; + + static bool HandleLessFunc( const HToolEntry_t& lhs, const HToolEntry_t& rhs ) + { + return lhs.m_Handle < rhs.m_Handle; + } + + CUtlRBTree< HToolEntry_t > m_Handles; + CUtlVector< int > m_ActiveHandles; + bool m_bInRecordingMode; +}; + + +//----------------------------------------------------------------------------- +// Statics +//----------------------------------------------------------------------------- +int CClientTools::s_nNextHandle = 1; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CClientTools s_ClientTools; +IClientTools *clienttools = &s_ClientTools; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CClientTools, IClientTools, VCLIENTTOOLS_INTERFACE_VERSION, s_ClientTools ); + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CClientTools::CClientTools() : m_Handles( 0, 0, HandleLessFunc ) +{ + m_bInRecordingMode = false; + cl_entitylist->AddListenerEntity( this ); +} + + +//----------------------------------------------------------------------------- +// Global toggle for recording +//----------------------------------------------------------------------------- +void CClientTools::EnableRecordingMode( bool bEnable ) +{ + m_bInRecordingMode = bEnable; +} + +bool CClientTools::IsInRecordingMode() const +{ + return m_bInRecordingMode; +} + + +//----------------------------------------------------------------------------- +// Trigger a temp entity +//----------------------------------------------------------------------------- +void CClientTools::TriggerTempEntity( KeyValues *pKeyValues ) +{ + te->TriggerTempEntity( pKeyValues ); +} + + +//----------------------------------------------------------------------------- +// get owning weapon (for viewmodels) +//----------------------------------------------------------------------------- +int CClientTools::GetOwningWeaponEntIndex( int entindex ) +{ + C_BaseEntity *pEnt = C_BaseEntity::Instance( entindex ); + C_BaseViewModel *pViewModel = dynamic_cast< C_BaseViewModel* >( pEnt ); + if ( pViewModel ) + { + C_BaseCombatWeapon *pWeapon = pViewModel->GetOwningWeapon(); + if ( pWeapon ) + { + return pWeapon->entindex(); + } + } + + return -1; +} + +int CClientTools::GetEntIndex( EntitySearchResult entityToAttach ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity * >( entityToAttach ); + return ent ? ent->entindex() : 0; +} + +void CClientTools::AddClientRenderable( IClientRenderable *pRenderable, int renderGroup ) +{ + Assert( pRenderable ); + + cl_entitylist->AddNonNetworkableEntity( pRenderable->GetIClientUnknown() ); + + ClientRenderHandle_t handle = pRenderable->RenderHandle(); + if ( INVALID_CLIENT_RENDER_HANDLE == handle ) + { + // create new renderer handle + ClientLeafSystem()->AddRenderable( pRenderable, (RenderGroup_t)renderGroup ); + } + else + { + // handle already exists, just update group & origin + ClientLeafSystem()->SetRenderGroup( pRenderable->RenderHandle(), (RenderGroup_t)renderGroup ); + ClientLeafSystem()->RenderableChanged( pRenderable->RenderHandle() ); + } + +} + +void CClientTools::RemoveClientRenderable( IClientRenderable *pRenderable ) +{ + ClientRenderHandle_t handle = pRenderable->RenderHandle(); + if( handle != INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->RemoveRenderable( handle ); + } + cl_entitylist->RemoveEntity( pRenderable->GetIClientUnknown()->GetRefEHandle() ); +} + +void CClientTools::MarkClientRenderableDirty( IClientRenderable *pRenderable ) +{ + ClientRenderHandle_t handle = pRenderable->RenderHandle(); + if ( INVALID_CLIENT_RENDER_HANDLE != handle ) + { + // handle already exists, just update group & origin + ClientLeafSystem()->RenderableChanged( pRenderable->RenderHandle() ); + } +} + +void CClientTools::SetRenderGroup( IClientRenderable *pRenderable, int renderGroup ) +{ + ClientRenderHandle_t handle = pRenderable->RenderHandle(); + if ( INVALID_CLIENT_RENDER_HANDLE == handle ) + { + // create new renderer handle + ClientLeafSystem()->AddRenderable( pRenderable, (RenderGroup_t)renderGroup ); + } + else + { + // handle already exists, just update group & origin + ClientLeafSystem()->SetRenderGroup( pRenderable->RenderHandle(), (RenderGroup_t)renderGroup ); + ClientLeafSystem()->RenderableChanged( pRenderable->RenderHandle() ); + } +} + +bool CClientTools::DrawSprite( IClientRenderable *pRenderable, float scale, float frame, int rendermode, int renderfx, const Color &color, float flProxyRadius, int *pVisHandle ) +{ + Vector origin = pRenderable->GetRenderOrigin(); + QAngle angles = pRenderable->GetRenderAngles(); + + // Get extra data + CEngineSprite *psprite = (CEngineSprite *)modelinfo->GetModelExtraData( pRenderable->GetModel() ); + if ( !psprite ) + return false; + + // Get orthonormal bases for current view - re-align to current camera (vs. recorded camera) + Vector forward, right, up; + C_SpriteRenderer::GetSpriteAxes( ( C_SpriteRenderer::SPRITETYPE )psprite->GetOrientation(), origin, angles, forward, right, up ); + + int r = color.r(); + int g = color.g(); + int b = color.b(); + + float oldBlend = render->GetBlend(); + if ( rendermode != kRenderNormal ) + { + // kRenderGlow and kRenderWorldGlow have a special blending function + if (( rendermode == kRenderGlow ) || ( rendermode == kRenderWorldGlow )) + { + pixelvis_queryparams_t params; + if ( flProxyRadius != 0.0f ) + { + params.Init( origin, flProxyRadius ); + params.bSizeInScreenspace = true; + } + else + { + params.Init( origin ); + } + float blend = oldBlend * StandardGlowBlend( params, ( pixelvis_handle_t* )pVisHandle, rendermode, renderfx, color.a(), &scale ); + + if ( blend <= 0.0f ) + return false; + + //Fade out the sprite depending on distance from the view origin. + r *= blend; + g *= blend; + b *= blend; + + render->SetBlend( blend ); + } + } + + DrawSpriteModel( ( IClientEntity* )pRenderable, psprite, origin, scale, frame, rendermode, r, g, b, color.a(), forward, right, up ); + + if (( rendermode == kRenderGlow ) || ( rendermode == kRenderWorldGlow )) + { + render->SetBlend( oldBlend ); + } + + return true; +} + +HTOOLHANDLE CClientTools::AttachToEntity( EntitySearchResult entityToAttach ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity * >( entityToAttach ); + Assert( ent ); + if ( !ent ) + return (HTOOLHANDLE)0; + + HTOOLHANDLE curHandle = ent->GetToolHandle(); + if ( curHandle != 0 ) + return curHandle; // Already attaached + + HToolEntry_t newHandle( s_nNextHandle++, ent ); + + m_Handles.Insert( newHandle ); + m_ActiveHandles.AddToTail( newHandle.m_Handle ); + + return (HTOOLHANDLE)newHandle.m_Handle; +} + +void CClientTools::DetachFromEntity( EntitySearchResult entityToDetach ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity * >( entityToDetach ); + Assert( ent ); + if ( !ent ) + return; + + HTOOLHANDLE handle = ent->GetToolHandle(); + ent->SetToolHandle( (HTOOLHANDLE)0 ); + + if ( handle == (HTOOLHANDLE)0 ) + { + Assert( 0 ); + return; + } + + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + { + Assert( 0 ); + return; + } + + m_Handles.RemoveAt( idx ); + m_ActiveHandles.FindAndRemove( handle ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// Output : C_BaseEntity +//----------------------------------------------------------------------------- +C_BaseEntity *CClientTools::LookupEntity( HTOOLHANDLE handle ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return NULL; + + return m_Handles[ idx ].m_hEntity; +} + +int CClientTools::GetNumRecordables() +{ + return m_ActiveHandles.Count(); +} + +HTOOLHANDLE CClientTools::GetRecordable( int index ) +{ + if ( index < 0 || index >= m_ActiveHandles.Count() ) + { + Assert( 0 ); + return (HTOOLHANDLE)0; + } + + return m_ActiveHandles[ index ]; +} + + +//----------------------------------------------------------------------------- +// Iterates through ALL entities (separate list for client vs. server) +//----------------------------------------------------------------------------- +EntitySearchResult CClientTools::NextEntity( EntitySearchResult currentEnt ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity* >( currentEnt ); + if ( ent == NULL ) + { + ent = cl_entitylist->FirstBaseEntity(); + } + else + { + ent = cl_entitylist->NextBaseEntity( ent ); + } + return reinterpret_cast< EntitySearchResult >( ent ); +} + + +//----------------------------------------------------------------------------- +// Use this to turn on/off the presence of an underlying game entity +//----------------------------------------------------------------------------- +void CClientTools::SetEnabled( HTOOLHANDLE handle, bool enabled ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return; + + HToolEntry_t *slot = &m_Handles[ idx ]; + Assert( slot ); + if ( slot == NULL ) + return; + + C_BaseEntity *ent = slot->m_hEntity.Get(); + if ( ent == NULL || ent->entindex() == 0 ) + return; // Don't disable/enable the "world" + + ent->EnableInToolView( enabled ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientTools::SetRecording( HTOOLHANDLE handle, bool recording ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return; + + HToolEntry_t &entry = m_Handles[ idx ]; + if ( entry.m_hEntity ) + { + entry.m_hEntity->SetToolRecording( recording ); + } +} + +bool CClientTools::ShouldRecord( HTOOLHANDLE handle ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return false; + + HToolEntry_t &entry = m_Handles[ idx ]; + return entry.m_hEntity && entry.m_hEntity->ShouldRecordInTools(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CClientTools::GetModelIndex( HTOOLHANDLE handle ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return NULL; + + HToolEntry_t &entry = m_Handles[ idx ]; + if ( entry.m_hEntity ) + { + return entry.m_hEntity->GetModelIndex(); + } + Assert( 0 ); + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char* CClientTools::GetModelName( HTOOLHANDLE handle ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return NULL; + + HToolEntry_t &entry = m_Handles[ idx ]; + if ( entry.m_hEntity ) + { + return STRING( entry.m_hEntity->GetModelName() ); + } + Assert( 0 ); + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char* CClientTools::GetClassname( HTOOLHANDLE handle ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return NULL; + + HToolEntry_t &entry = m_Handles[ idx ]; + if ( entry.m_hEntity ) + { + return STRING( entry.m_hEntity->GetClassname() ); + } + Assert( 0 ); + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +//----------------------------------------------------------------------------- +bool CClientTools::IsValidHandle( HTOOLHANDLE handle ) +{ + return m_Handles.Find( HToolEntry_t( handle ) ) != m_Handles.InvalidIndex(); +} + +void CClientTools::OnEntityDeleted( CBaseEntity *pEntity ) +{ + HTOOLHANDLE handle = pEntity ? pEntity->GetToolHandle() : (HTOOLHANDLE)0; + if ( handle == (HTOOLHANDLE)0 ) + return; + + if ( m_bInRecordingMode ) + { + // Send deletion message to tool interface + KeyValues *kv = new KeyValues( "deleted" ); + ToolFramework_PostToolMessage( handle, kv ); + kv->deleteThis(); + } + + DetachFromEntity( pEntity ); +} + +void CClientTools::OnEntityCreated( CBaseEntity *pEntity ) +{ + if ( !m_bInRecordingMode ) + return; + + HTOOLHANDLE h = AttachToEntity( pEntity ); + + // Send deletion message to tool interface + KeyValues *kv = new KeyValues( "created" ); + ToolFramework_PostToolMessage( h, kv ); + kv->deleteThis(); +} + +HTOOLHANDLE CClientTools::GetToolHandleForEntityByIndex( int entindex ) +{ + C_BaseEntity *ent = C_BaseEntity::Instance( entindex ); + if ( !ent ) + return (HTOOLHANDLE)0; + + return ent->GetToolHandle(); +} + +EntitySearchResult CClientTools::GetLocalPlayer() +{ + C_BasePlayer *p = C_BasePlayer::GetLocalPlayer(); + return reinterpret_cast< EntitySearchResult >( p ); +} + +bool CClientTools::GetLocalPlayerEyePosition( Vector& org, QAngle& ang, float &fov ) +{ + C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer(); + if ( pl == NULL ) + return false; + + org = pl->EyePosition(); + ang = pl->EyeAngles(); + fov = pl->GetFOV(); + return true; +} + +//----------------------------------------------------------------------------- +// Create, destroy shadow +//----------------------------------------------------------------------------- +ClientShadowHandle_t CClientTools::CreateShadow( CBaseHandle h, int nFlags ) +{ + return g_pClientShadowMgr->CreateShadow( h, nFlags ); +} + +void CClientTools::DestroyShadow( ClientShadowHandle_t h ) +{ + g_pClientShadowMgr->DestroyShadow( h ); +} + +ClientShadowHandle_t CClientTools::CreateFlashlight( const FlashlightState_t &lightState ) +{ + return g_pClientShadowMgr->CreateFlashlight( lightState ); +} + +void CClientTools::DestroyFlashlight( ClientShadowHandle_t h ) +{ + g_pClientShadowMgr->DestroyFlashlight( h ); +} + +void CClientTools::UpdateFlashlightState( ClientShadowHandle_t h, const FlashlightState_t &lightState ) +{ + g_pClientShadowMgr->UpdateFlashlightState( h, lightState ); +} + +void CClientTools::AddToDirtyShadowList( ClientShadowHandle_t h, bool force ) +{ + g_pClientShadowMgr->AddToDirtyShadowList( h, force ); +} + +void CClientTools::MarkRenderToTextureShadowDirty( ClientShadowHandle_t h ) +{ + g_pClientShadowMgr->MarkRenderToTextureShadowDirty( h ); +} + +void CClientTools::UpdateProjectedTexture( ClientShadowHandle_t h, bool bForce ) +{ + g_pClientShadowMgr->UpdateProjectedTexture( h, bForce ); +} + +int CClientTools::FindGlobalFlexcontroller( char const *name ) +{ + return C_BaseFlex::AddGlobalFlexController( (char *)name ); +} + +char const *CClientTools::GetGlobalFlexControllerName( int idx ) +{ + return C_BaseFlex::GetGlobalFlexControllerName( idx ); +} + +//----------------------------------------------------------------------------- +// helper for traversing ownership hierarchy +//----------------------------------------------------------------------------- +EntitySearchResult CClientTools::GetOwnerEntity( EntitySearchResult currentEnt ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity* >( currentEnt ); + return ent ? ent->GetOwnerEntity() : NULL; +} +//----------------------------------------------------------------------------- +// common and useful types to query for hierarchically +//----------------------------------------------------------------------------- +bool CClientTools::IsPlayer( EntitySearchResult currentEnt ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity* >( currentEnt ); + return ent ? ent->IsPlayer() : false; +} + +bool CClientTools::IsBaseCombatCharacter( EntitySearchResult currentEnt ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity* >( currentEnt ); + return ent ? ent->IsBaseCombatCharacter() : false; +} + +bool CClientTools::IsNPC( EntitySearchResult currentEnt ) +{ + C_BaseEntity *ent = reinterpret_cast< C_BaseEntity* >( currentEnt ); + return ent ? ent->IsNPC() : false; +} + +Vector CClientTools::GetAbsOrigin( HTOOLHANDLE handle ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return vec3_origin; + + HToolEntry_t &entry = m_Handles[ idx ]; + if ( entry.m_hEntity ) + { + return entry.m_hEntity->GetAbsOrigin(); + } + Assert( 0 ); + return vec3_origin; +} + +QAngle CClientTools::GetAbsAngles( HTOOLHANDLE handle ) +{ + int idx = m_Handles.Find( HToolEntry_t( handle ) ); + if ( idx == m_Handles.InvalidIndex() ) + return vec3_angle; + + HToolEntry_t &entry = m_Handles[ idx ]; + if ( entry.m_hEntity ) + { + return entry.m_hEntity->GetAbsAngles(); + } + Assert( 0 ); + return vec3_angle; +} + + +//----------------------------------------------------------------------------- +// Sends a mesage from the tool to the client +//----------------------------------------------------------------------------- +void CClientTools::PostToolMessage( KeyValues *pKeyValues ) +{ + if ( !Q_stricmp( pKeyValues->GetName(), "QueryParticleManifest" ) ) + { + // NOTE: This cannot be done during particle system init because tools aren't set up at that point + CUtlVector files; + GetParticleManifest( files ); + int nCount = files.Count(); + for ( int i = 0; i < nCount; ++i ) + { + char pTemp[256]; + Q_snprintf( pTemp, sizeof(pTemp), "%d", i ); + KeyValues *pSubKey = pKeyValues->FindKey( pTemp, true ); + pSubKey->SetString( "file", files[i] ); + } + return; + } + + if ( !Q_stricmp( pKeyValues->GetName(), "QueryMonitorTexture" ) ) + { + pKeyValues->SetPtr( "texture", GetCameraTexture() ); + return; + } + +#ifdef PORTAL + if ( !Q_stricmp( pKeyValues->GetName(), "portals" ) ) + { + g_pPortalRender->HandlePortalPlaybackMessage( pKeyValues ); + return; + } + + if ( !Q_stricmp( pKeyValues->GetName(), "query CPortalRenderer" ) ) + { + pKeyValues->SetInt( "IsRenderingPortal", g_pPortalRender->IsRenderingPortal() ? 1 : 0 ); + return; + } +#endif +} + + +//----------------------------------------------------------------------------- +// Indicates whether the client should render particle systems +//----------------------------------------------------------------------------- +void CClientTools::EnableParticleSystems( bool bEnable ) +{ + ParticleMgr()->RenderParticleSystems( bEnable ); +} + + +//----------------------------------------------------------------------------- +// Is the game rendering in 3rd person mode? +//----------------------------------------------------------------------------- +bool CClientTools::IsRenderingThirdPerson() const +{ + return C_BasePlayer::ShouldDrawLocalPlayer(); +} + + +//----------------------------------------------------------------------------- +// Reload particle definitions +//----------------------------------------------------------------------------- +void CClientTools::ReloadParticleDefintions( const char *pFileName, const void *pBufData, int nLen ) +{ + // Remove all new effects, because we are going to free internal structures they point to + ParticleMgr()->RemoveAllNewEffects(); + + // FIXME: Use file name to determine if we care about this data + CUtlBuffer buf( pBufData, nLen, CUtlBuffer::READ_ONLY ); + g_pParticleSystemMgr->ReadParticleConfigFile( buf, true ); +} diff --git a/game/client/entityoriginmaterialproxy.cpp b/game/client/entityoriginmaterialproxy.cpp new file mode 100644 index 00000000..cba7e5b7 --- /dev/null +++ b/game/client/entityoriginmaterialproxy.cpp @@ -0,0 +1,148 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: A base class for all material proxies in the client dll +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +// identifier was truncated to '255' characters in the debug information +//#pragma warning(disable: 4786) + +#include "ProxyEntity.h" +#include "materialsystem/IMaterialVar.h" + +class CEntityOriginMaterialProxy : public CEntityMaterialProxy +{ +public: + CEntityOriginMaterialProxy() + { + m_pMaterial = NULL; + m_pOriginVar = NULL; + } + virtual ~CEntityOriginMaterialProxy() + { + } + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ) + { + m_pMaterial = pMaterial; + bool found; + m_pOriginVar = m_pMaterial->FindVar( "$entityorigin", &found ); + if( !found ) + { + m_pOriginVar = NULL; + return false; + } + return true; + } + virtual void OnBind( C_BaseEntity *pC_BaseEntity ) + { + const Vector &origin = pC_BaseEntity->GetAbsOrigin(); + m_pOriginVar->SetVecValue( origin.x, origin.y, origin.z ); + } + + virtual IMaterial *GetMaterial() + { + return m_pMaterial; + } + +protected: + IMaterial *m_pMaterial; + IMaterialVar *m_pOriginVar; +}; + +EXPOSE_INTERFACE( CEntityOriginMaterialProxy, IMaterialProxy, "EntityOrigin" IMATERIAL_PROXY_INTERFACE_VERSION ); + +//================================================================================================================= +// This is a last-minute hack to ship Orange Box on the 360! +//================================================================================================================= +class CEntityOriginAlyxMaterialProxy : public CEntityMaterialProxy +{ +public: + CEntityOriginAlyxMaterialProxy() + { + m_pMaterial = NULL; + m_pOriginVar = NULL; + } + virtual ~CEntityOriginAlyxMaterialProxy() + { + } + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ) + { + m_pMaterial = pMaterial; + bool found; + m_pOriginVar = m_pMaterial->FindVar( "$entityorigin", &found ); + if( !found ) + { + m_pOriginVar = NULL; + return false; + } + return true; + } + virtual void OnBind( C_BaseEntity *pC_BaseEntity ) + { + const Vector &origin = pC_BaseEntity->GetAbsOrigin(); + m_pOriginVar->SetVecValue( origin.x - 15.0f, origin.y, origin.z ); + } + + virtual IMaterial *GetMaterial() + { + return m_pMaterial; + } + +protected: + IMaterial *m_pMaterial; + IMaterialVar *m_pOriginVar; +}; + +EXPOSE_INTERFACE( CEntityOriginAlyxMaterialProxy, IMaterialProxy, "EntityOriginAlyx" IMATERIAL_PROXY_INTERFACE_VERSION ); + +//================================================================================================================= +// This is a last-minute hack to ship Orange Box on the 360! +//================================================================================================================= +class CEp1IntroVortRefractMaterialProxy : public CEntityMaterialProxy +{ +public: + CEp1IntroVortRefractMaterialProxy() + { + m_pMaterial = NULL; + m_pOriginVar = NULL; + } + virtual ~CEp1IntroVortRefractMaterialProxy() + { + } + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ) + { + m_pMaterial = pMaterial; + bool found; + m_pOriginVar = m_pMaterial->FindVar( "$refractamount", &found ); + if( !found ) + { + m_pOriginVar = NULL; + return false; + } + return true; + } + virtual void OnBind( C_BaseEntity *pC_BaseEntity ) + { + if ( m_pOriginVar != NULL) + { + float flTmp = ( 1.0f - m_pOriginVar->GetFloatValue() ); + flTmp *= flTmp; + flTmp *= flTmp; + flTmp = ( 1.0f - flTmp ) * 0.25f; + m_pOriginVar->SetFloatValue( flTmp ); + } + } + + virtual IMaterial *GetMaterial() + { + return m_pMaterial; + } + +protected: + IMaterial *m_pMaterial; + IMaterialVar *m_pOriginVar; +}; + +EXPOSE_INTERFACE( CEp1IntroVortRefractMaterialProxy, IMaterialProxy, "Ep1IntroVortRefract" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/episodic/c_npc_advisor.cpp b/game/client/episodic/c_npc_advisor.cpp new file mode 100644 index 00000000..601f984c --- /dev/null +++ b/game/client/episodic/c_npc_advisor.cpp @@ -0,0 +1,251 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Definition for client-side advisor. +// +//=====================================================================================// + + + +#include "cbase.h" +// this file contains the definitions for the message ID constants (eg ADVISOR_MSG_START_BEAM etc) +#include "npc_advisor_shared.h" + +#if NPC_ADVISOR_HAS_BEHAVIOR + +#include "particles_simple.h" +#include "citadel_effects_shared.h" +#include "particles_attractor.h" +#include "ClientEffectPrecacheSystem.h" +#include "c_te_effect_dispatch.h" + +#include "c_ai_basenpc.h" +#include "dlight.h" +#include "iefx.h" + + +//----------------------------------------------------------------------------- +// Purpose: unpack a networked entity index into a basehandle. +//----------------------------------------------------------------------------- +inline C_BaseEntity *IndexToEntity( int eindex ) +{ + return ClientEntityList().GetBaseEntityFromHandle(ClientEntityList().EntIndexToHandle(eindex)); +} + + + +#define ADVISOR_ELIGHT_CVARS 1 // enable/disable tuning advisor elight with console variables + +#if ADVISOR_ELIGHT_CVARS +ConVar advisor_elight_e("advisor_elight_e","3"); +ConVar advisor_elight_rfeet("advisor_elight_rfeet","52"); +#endif + + +/*! Client-side reflection of the advisor class. + */ +class C_NPC_Advisor : public C_AI_BaseNPC +{ + DECLARE_CLASS( C_NPC_Advisor, C_AI_BaseNPC ); + DECLARE_CLIENTCLASS(); + +public: + // Server to client message received + virtual void ReceiveMessage( int classID, bf_read &msg ); + virtual void ClientThink( void ); + +private: + /* + // broken into its own function so I can move it if necesasry + void Initialize(); + */ + + // start/stop beam particle effect from me to a pelting object + void StartBeamFX( C_BaseEntity *pOnEntity ); + void StopBeamFX( C_BaseEntity *pOnEntity ); + + void StartElight(); + void StopElight(); + + int m_ElightKey; // test using an elight to make the escape sequence more visible. 0 is invalid. + +}; + +IMPLEMENT_CLIENTCLASS_DT( C_NPC_Advisor, DT_NPC_Advisor, CNPC_Advisor ) + +END_RECV_TABLE() + +// Server to client message received +void C_NPC_Advisor::ReceiveMessage( int classID, bf_read &msg ) +{ + if ( classID != GetClientClass()->m_ClassID ) + { + // message is for subclass + BaseClass::ReceiveMessage( classID, msg ); + return; + } + + int messageType = msg.ReadByte(); + switch( messageType ) + { + case ADVISOR_MSG_START_BEAM: + { + int eindex = msg.ReadLong(); + StartBeamFX(IndexToEntity(eindex)); + } + break; + + case ADVISOR_MSG_STOP_BEAM: + { + int eindex = msg.ReadLong(); + StopBeamFX(IndexToEntity(eindex)); + + } + break; + + case ADVISOR_MSG_STOP_ALL_BEAMS: + { + ParticleProp()->StopEmission(); + } + break; + case ADVISOR_MSG_START_ELIGHT: + { + StartElight(); + } + break; + case ADVISOR_MSG_STOP_ELIGHT: + { + StopElight(); + } + break; + + default: + AssertMsg1( false, "Received unknown message %d", messageType); + } +} + +/// only use of the clientthink on the advisor is to update the elight +void C_NPC_Advisor::ClientThink( void ) +{ + // if the elight has gone away, bail out + if (m_ElightKey == 0) + { + SetNextClientThink( CLIENT_THINK_NEVER ); + return; + } + + // get the elight + dlight_t * el = effects->GetElightByKey(m_ElightKey); + if (!el) + { + // the elight has been invalidated. bail out. + m_ElightKey = 0; + + SetNextClientThink( CLIENT_THINK_NEVER ); + return; + } + else + { + el->origin = WorldSpaceCenter(); + +#if ADVISOR_ELIGHT_CVARS + el->color.exponent = advisor_elight_e.GetFloat(); + el->radius = advisor_elight_rfeet.GetFloat() * 12.0f; +#endif + } +} + +//----------------------------------------------------------------------------- +// Create a telekinetic beam effect from my head to an object +// TODO: use a point attachment. +//----------------------------------------------------------------------------- +void C_NPC_Advisor::StartBeamFX( C_BaseEntity *pOnEntity ) +{ + Assert(pOnEntity); + if (!pOnEntity) + return; + + CNewParticleEffect *pEffect = ParticleProp()->Create( "Advisor_Psychic_Beam", PATTACH_ABSORIGIN_FOLLOW ); + + Assert(pEffect); + if (!pEffect) return; + + ParticleProp()->AddControlPoint( pEffect, 1, pOnEntity, PATTACH_ABSORIGIN_FOLLOW ); +} + + +//----------------------------------------------------------------------------- +// terminate a telekinetic beam effect from my head to an object +//----------------------------------------------------------------------------- +void C_NPC_Advisor::StopBeamFX( C_BaseEntity *pOnEntity ) +{ + Assert(pOnEntity); + if (!pOnEntity) + return; + + ParticleProp()->StopParticlesInvolving( pOnEntity ); +} + + + + + + +void C_NPC_Advisor::StartElight() +{ + AssertMsg(m_ElightKey == 0 , "Advisor trying to create new elight on top of old one!"); + if ( m_ElightKey != 0 ) + { + Warning("Advisor tried to start his elight when it was already one.\n"); + } + else + { + m_ElightKey = LIGHT_INDEX_TE_DYNAMIC + this->entindex(); + dlight_t * el = effects->CL_AllocElight( m_ElightKey ); + + if ( el ) + { + // create an elight on top of me + el->origin = this->WorldSpaceCenter(); + + el->color.r = 235; + el->color.g = 255; + el->color.b = 255; + el->color.exponent = 3; + + el->radius = 52*12; + el->decay = 0.0f; + el->die = gpGlobals->curtime + 2000.0f; // 1000 just means " a long time " + + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } + else + { // null out the light value + m_ElightKey = 0; + } + } +} + +void C_NPC_Advisor::StopElight() +{ + AssertMsg( m_ElightKey != 0, "Advisor tried to stop elight when none existed!"); + dlight_t * el; + // note: the following conditional sets el if not short-circuited + if ( m_ElightKey == 0 || (el = effects->GetElightByKey(m_ElightKey)) == NULL ) + { + Warning("Advisor tried to stop its elight when it had none.\n"); + } + else + { + // kill the elight by setting the die value to now + el->die = gpGlobals->curtime; + } +} + + +#endif + +/****************************************************** + * Tenser, said the Tensor. * + * Tenser, said the Tensor. * + * Tension, apprehension and dissension have begun. * + ******************************************************/ diff --git a/game/client/episodic/c_npc_puppet.cpp b/game/client/episodic/c_npc_puppet.cpp new file mode 100644 index 00000000..8b976c0c --- /dev/null +++ b/game/client/episodic/c_npc_puppet.cpp @@ -0,0 +1,167 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "c_ai_basenpc.h" +#include "bone_setup.h" + +// Must be the last file included +#include "memdbgon.h" + + +extern ConVar r_sequence_debug; + +class C_NPC_Puppet : public C_AI_BaseNPC +{ + DECLARE_CLASS( C_NPC_Puppet, C_AI_BaseNPC ); +public: + + virtual void ClientThink( void ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void BuildTransformations( CStudioHdr *pStudioHdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed ); + virtual void AccumulateLayers( CStudioHdr *hdr, Vector pos[], Quaternion q[], float poseparam[], float currentTime, int boneMask ); + + EHANDLE m_hAnimationTarget; + int m_nTargetAttachment; + + DECLARE_CLIENTCLASS(); +}; + +IMPLEMENT_CLIENTCLASS_DT( C_NPC_Puppet, DT_NPC_Puppet, CNPC_Puppet ) + RecvPropEHandle( RECVINFO(m_hAnimationTarget) ), + RecvPropInt( RECVINFO(m_nTargetAttachment) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_NPC_Puppet::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: We need to slam our position! +//----------------------------------------------------------------------------- +void C_NPC_Puppet::BuildTransformations( CStudioHdr *pStudioHdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed ) +{ + if ( m_hAnimationTarget && m_nTargetAttachment != -1 ) + { + C_BaseAnimating *pTarget = m_hAnimationTarget->GetBaseAnimating(); + if ( pTarget ) + { + matrix3x4_t matTarget; + pTarget->GetAttachment( m_nTargetAttachment, matTarget ); + + MatrixCopy( matTarget, GetBoneForWrite( 0 ) ); + boneComputed.ClearAll(); // FIXME: Why is this calculated already? + boneComputed.MarkBone( 0 ); + } + } + + // Call the baseclass + BaseClass::BuildTransformations( pStudioHdr, pos, q, cameraTransform, boneMask, boneComputed ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_NPC_Puppet::ClientThink( void ) +{ + if ( m_hAnimationTarget == NULL ) + return; + + C_BaseAnimating *pTarget = m_hAnimationTarget->GetBaseAnimating(); + if ( pTarget == NULL ) + return; + + int nTargetSequence = pTarget->GetSequence(); + const char *pSequenceName = pTarget->GetSequenceName( nTargetSequence ); + + int nSequence = LookupSequence( pSequenceName ); + if ( nSequence >= 0 ) + { + if ( nSequence != GetSequence() ) + { + SetSequence( nSequence ); + UpdateVisibility(); + } + + SetCycle( pTarget->GetCycle() ); + SetPlaybackRate( pTarget->GetPlaybackRate() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_NPC_Puppet::AccumulateLayers( CStudioHdr *hdr, Vector pos[], Quaternion q[], float poseparam[], float currentTime, int boneMask ) +{ + if ( m_hAnimationTarget == NULL ) + return; + + C_BaseAnimatingOverlay *pTarget = dynamic_cast( m_hAnimationTarget->GetBaseAnimating() ); + if ( pTarget == NULL ) + return; + + // resort the layers + int layer[MAX_OVERLAYS]; + int i; + for (i = 0; i < MAX_OVERLAYS; i++) + { + layer[i] = MAX_OVERLAYS; + } + for (i = 0; i < pTarget->m_AnimOverlay.Count(); i++) + { + if (pTarget->m_AnimOverlay[i].m_nOrder < MAX_OVERLAYS) + { + layer[pTarget->m_AnimOverlay[i].m_nOrder] = i; + } + } + + int j; + for (j = 0; j < MAX_OVERLAYS; j++) + { + i = layer[ j ]; + if (i < pTarget->m_AnimOverlay.Count()) + { + float fWeight = pTarget->m_AnimOverlay[i].m_flWeight; + + if (fWeight > 0) + { + const char *pSequenceName = pTarget->GetSequenceName( pTarget->m_AnimOverlay[i].m_nSequence ); + + int nSequence = LookupSequence( pSequenceName ); + if ( nSequence >= 0 ) + { + float fCycle = pTarget->m_AnimOverlay[ i ].m_flCycle; + + fCycle = ClampCycle( fCycle, IsSequenceLooping( nSequence ) ); + + if (fWeight > 1) + fWeight = 1; + + AccumulatePose( hdr, NULL, pos, q, nSequence, fCycle, poseparam, boneMask, fWeight, currentTime ); + +#if _DEBUG + if (Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL) + { + DevMsgRT( "%6.2f : %30s : %5.3f : %4.2f : %1d\n", currentTime, hdr->pSeqdesc( nSequence ).pszLabel(), fCycle, fWeight, i ); + } +#endif + + } + } + } + } +} + diff --git a/game/client/episodic/c_prop_coreball.cpp b/game/client/episodic/c_prop_coreball.cpp new file mode 100644 index 00000000..000eea09 --- /dev/null +++ b/game/client/episodic/c_prop_coreball.cpp @@ -0,0 +1,142 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" + +class C_PropCoreBall : public C_BaseAnimating +{ + DECLARE_CLASS( C_PropCoreBall, C_BaseAnimating ); + DECLARE_CLIENTCLASS(); + DECLARE_DATADESC(); + +public: + + C_PropCoreBall(); + + void ApplyBoneMatrixTransform( matrix3x4_t& transform ); + + float m_flScaleX; + float m_flScaleY; + float m_flScaleZ; + + float m_flLerpTimeX; + float m_flLerpTimeY; + float m_flLerpTimeZ; + + float m_flGoalTimeX; + float m_flGoalTimeY; + float m_flGoalTimeZ; + + float m_flCurrentScale[3]; + bool m_bRunningScale[3]; + float m_flTargetScale[3]; + +private: + +}; + +void RecvProxy_ScaleX( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_PropCoreBall *pCoreData = (C_PropCoreBall *) pStruct; + + pCoreData->m_flScaleX = pData->m_Value.m_Float; + + if ( pCoreData->m_bRunningScale[0] == true ) + { + pCoreData->m_flTargetScale[0] = pCoreData->m_flCurrentScale[0]; + } +} + +void RecvProxy_ScaleY( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_PropCoreBall *pCoreData = (C_PropCoreBall *) pStruct; + + pCoreData->m_flScaleY = pData->m_Value.m_Float; + + if ( pCoreData->m_bRunningScale[1] == true ) + { + pCoreData->m_flTargetScale[1] = pCoreData->m_flCurrentScale[1]; + } +} + +void RecvProxy_ScaleZ( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_PropCoreBall *pCoreData = (C_PropCoreBall *) pStruct; + + pCoreData->m_flScaleZ = pData->m_Value.m_Float; + + if ( pCoreData->m_bRunningScale[2] == true ) + { + pCoreData->m_flTargetScale[2] = pCoreData->m_flCurrentScale[2]; + } +} + +IMPLEMENT_CLIENTCLASS_DT( C_PropCoreBall, DT_PropCoreBall, CPropCoreBall ) + RecvPropFloat( RECVINFO( m_flScaleX ), 0, RecvProxy_ScaleX ), + RecvPropFloat( RECVINFO( m_flScaleY ), 0, RecvProxy_ScaleY ), + RecvPropFloat( RECVINFO( m_flScaleZ ), 0, RecvProxy_ScaleZ ), + + RecvPropFloat( RECVINFO( m_flLerpTimeX ) ), + RecvPropFloat( RECVINFO( m_flLerpTimeY ) ), + RecvPropFloat( RECVINFO( m_flLerpTimeZ ) ), + + RecvPropFloat( RECVINFO( m_flGoalTimeX ) ), + RecvPropFloat( RECVINFO( m_flGoalTimeY ) ), + RecvPropFloat( RECVINFO( m_flGoalTimeZ ) ), +END_RECV_TABLE() + + +BEGIN_DATADESC( C_PropCoreBall ) + DEFINE_AUTO_ARRAY( m_flTargetScale, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_bRunningScale, FIELD_BOOLEAN ), +END_DATADESC() + +C_PropCoreBall::C_PropCoreBall( void ) +{ + m_flTargetScale[0] = 1.0f; + m_flTargetScale[1] = 1.0f; + m_flTargetScale[2] = 1.0f; + + m_bRunningScale[0] = false; + m_bRunningScale[1] = false; + m_bRunningScale[2] = false; +} + +void C_PropCoreBall::ApplyBoneMatrixTransform( matrix3x4_t& transform ) +{ + BaseClass::ApplyBoneMatrixTransform( transform ); + + float flVal[3] = { m_flTargetScale[0], m_flTargetScale[1], m_flTargetScale[2] }; + float *flTargetScale[3] = { &m_flTargetScale[0], &m_flTargetScale[1], &m_flTargetScale[2] }; + float flScale[3] = { m_flScaleX, m_flScaleY, m_flScaleZ }; + float flLerpTime[3] = { m_flLerpTimeX, m_flLerpTimeY, m_flLerpTimeZ }; + float flGoalTime[3] = { m_flGoalTimeX, m_flGoalTimeY, m_flGoalTimeZ }; + bool *bRunning[3] = { &m_bRunningScale[0], &m_bRunningScale[1], &m_bRunningScale[2] }; + + for ( int i = 0; i < 3; i++ ) + { + if ( *flTargetScale[i] != flScale[i] ) + { + float deltaTime = (float)( gpGlobals->curtime - flGoalTime[i]) / flLerpTime[i]; + float flRemapVal = SimpleSplineRemapVal( deltaTime, 0.0f, 1.0f, *flTargetScale[i], flScale[i] ); + + *bRunning[i] = true; + + if ( deltaTime >= 1.0f ) + { + *flTargetScale[i] = flScale[i]; + *bRunning[i] = false; + } + + flVal[i] = flRemapVal; + m_flCurrentScale[i] = flVal[i]; + } + } + + VectorScale( transform[0], flVal[0], transform[0] ); + VectorScale( transform[1], flVal[1], transform[1] ); + VectorScale( transform[2], flVal[2], transform[2] ); +} \ No newline at end of file diff --git a/game/client/episodic/c_prop_scalable.cpp b/game/client/episodic/c_prop_scalable.cpp new file mode 100644 index 00000000..0b0d1671 --- /dev/null +++ b/game/client/episodic/c_prop_scalable.cpp @@ -0,0 +1,196 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" + +class C_PropScalable : public C_BaseAnimating +{ + DECLARE_CLASS( C_PropScalable, C_BaseAnimating ); + DECLARE_CLIENTCLASS(); + DECLARE_DATADESC(); + +public: + + C_PropScalable(); + + virtual void ApplyBoneMatrixTransform( matrix3x4_t& transform ); + virtual void GetRenderBounds( Vector &theMins, Vector &theMaxs ); + + // Must be available to proxy functions + float m_flScaleX; + float m_flScaleY; + float m_flScaleZ; + + float m_flLerpTimeX; + float m_flLerpTimeY; + float m_flLerpTimeZ; + + float m_flGoalTimeX; + float m_flGoalTimeY; + float m_flGoalTimeZ; + + float m_flCurrentScale[3]; + bool m_bRunningScale[3]; + float m_flTargetScale[3]; + +private: + + void CalculateScale( void ); + float m_nCalcFrame; // Frame the last calculation was made at +}; + +void RecvProxy_ScaleX( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_PropScalable *pCoreData = (C_PropScalable *) pStruct; + + pCoreData->m_flScaleX = pData->m_Value.m_Float; + + if ( pCoreData->m_bRunningScale[0] == true ) + { + pCoreData->m_flTargetScale[0] = pCoreData->m_flCurrentScale[0]; + } +} + +void RecvProxy_ScaleY( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_PropScalable *pCoreData = (C_PropScalable *) pStruct; + + pCoreData->m_flScaleY = pData->m_Value.m_Float; + + if ( pCoreData->m_bRunningScale[1] == true ) + { + pCoreData->m_flTargetScale[1] = pCoreData->m_flCurrentScale[1]; + } +} + +void RecvProxy_ScaleZ( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_PropScalable *pCoreData = (C_PropScalable *) pStruct; + + pCoreData->m_flScaleZ = pData->m_Value.m_Float; + + if ( pCoreData->m_bRunningScale[2] == true ) + { + pCoreData->m_flTargetScale[2] = pCoreData->m_flCurrentScale[2]; + } +} + +IMPLEMENT_CLIENTCLASS_DT( C_PropScalable, DT_PropScalable, CPropScalable ) + RecvPropFloat( RECVINFO( m_flScaleX ), 0, RecvProxy_ScaleX ), + RecvPropFloat( RECVINFO( m_flScaleY ), 0, RecvProxy_ScaleY ), + RecvPropFloat( RECVINFO( m_flScaleZ ), 0, RecvProxy_ScaleZ ), + + RecvPropFloat( RECVINFO( m_flLerpTimeX ) ), + RecvPropFloat( RECVINFO( m_flLerpTimeY ) ), + RecvPropFloat( RECVINFO( m_flLerpTimeZ ) ), + + RecvPropFloat( RECVINFO( m_flGoalTimeX ) ), + RecvPropFloat( RECVINFO( m_flGoalTimeY ) ), + RecvPropFloat( RECVINFO( m_flGoalTimeZ ) ), +END_RECV_TABLE() + + +BEGIN_DATADESC( C_PropScalable ) + DEFINE_AUTO_ARRAY( m_flTargetScale, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_bRunningScale, FIELD_BOOLEAN ), +END_DATADESC() + +C_PropScalable::C_PropScalable( void ) +{ + m_flTargetScale[0] = 1.0f; + m_flTargetScale[1] = 1.0f; + m_flTargetScale[2] = 1.0f; + + m_bRunningScale[0] = false; + m_bRunningScale[1] = false; + m_bRunningScale[2] = false; + + m_nCalcFrame = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Calculates the scake of the object once per frame +//----------------------------------------------------------------------------- +void C_PropScalable::CalculateScale( void ) +{ + // Don't bother to calculate this for a second time in the same frame + if ( m_nCalcFrame == gpGlobals->framecount ) + return; + + // Mark that we cached this value for the frame + m_nCalcFrame = gpGlobals->framecount; + + float flVal[3] = { m_flTargetScale[0], m_flTargetScale[1], m_flTargetScale[2] }; + float *flTargetScale[3] = { &m_flTargetScale[0], &m_flTargetScale[1], &m_flTargetScale[2] }; + float flScale[3] = { m_flScaleX, m_flScaleY, m_flScaleZ }; + float flLerpTime[3] = { m_flLerpTimeX, m_flLerpTimeY, m_flLerpTimeZ }; + float flGoalTime[3] = { m_flGoalTimeX, m_flGoalTimeY, m_flGoalTimeZ }; + bool *bRunning[3] = { &m_bRunningScale[0], &m_bRunningScale[1], &m_bRunningScale[2] }; + + for ( int i = 0; i < 3; i++ ) + { + if ( *flTargetScale[i] != flScale[i] ) + { + float deltaTime = (float)( gpGlobals->curtime - flGoalTime[i]) / flLerpTime[i]; + float flRemapVal = SimpleSplineRemapValClamped( deltaTime, 0.0f, 1.0f, *flTargetScale[i], flScale[i] ); + + *bRunning[i] = true; + + if ( deltaTime >= 1.0f ) + { + *flTargetScale[i] = flScale[i]; + *bRunning[i] = false; + } + + flVal[i] = flRemapVal; + m_flCurrentScale[i] = flVal[i]; + } + else + { + m_flCurrentScale[i] = m_flTargetScale[i]; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Scales the bones based on the current scales +//----------------------------------------------------------------------------- +void C_PropScalable::ApplyBoneMatrixTransform( matrix3x4_t& transform ) +{ + BaseClass::ApplyBoneMatrixTransform( transform ); + + // Find the scale for this frame + CalculateScale(); + + VectorScale( transform[0], m_flCurrentScale[0], transform[0] ); + VectorScale( transform[1], m_flCurrentScale[1], transform[1] ); + VectorScale( transform[2], m_flCurrentScale[2], transform[2] ); + + UpdateVisibility(); +} + +//----------------------------------------------------------------------------- +// Purpose: Ensures the render bounds match the scales +//----------------------------------------------------------------------------- +void C_PropScalable::GetRenderBounds( Vector &theMins, Vector &theMaxs ) +{ + BaseClass::GetRenderBounds( theMins, theMaxs ); + + // Find the scale for this frame + CalculateScale(); + + // Extend our render bounds to encompass the scaled object + theMins.x *= m_flCurrentScale[0]; + theMins.y *= m_flCurrentScale[1]; + theMins.z *= m_flCurrentScale[2]; + + theMaxs.x *= m_flCurrentScale[0]; + theMaxs.y *= m_flCurrentScale[1]; + theMaxs.z *= m_flCurrentScale[2]; + + Assert( theMins.IsValid() && theMaxs.IsValid() ); + +} diff --git a/game/client/episodic/c_vehicle_jeep_episodic.cpp b/game/client/episodic/c_vehicle_jeep_episodic.cpp new file mode 100644 index 00000000..e62c377c --- /dev/null +++ b/game/client/episodic/c_vehicle_jeep_episodic.cpp @@ -0,0 +1,133 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "c_prop_vehicle.h" +#include "c_vehicle_jeep.h" +#include "movevars_shared.h" +#include "view.h" +#include "flashlighteffect.h" +#include "c_baseplayer.h" +#include "c_te_effect_dispatch.h" +#include "hl2_vehicle_radar.h" +#include "usermessages.h" +#include "hud_radar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//============================================================================= +// +// Client-side Episodic Jeep (Jalopy) Class +// +class C_PropJeepEpisodic : public C_PropJeep +{ + + DECLARE_CLASS( C_PropJeepEpisodic, C_PropJeep ); + +public: + DECLARE_CLIENTCLASS(); + +public: + C_PropJeepEpisodic(); + + void OnEnteredVehicle( C_BasePlayer *pPlayer ); + void Simulate( void ); + +public: + int m_iNumRadarContacts; + Vector m_vecRadarContactPos[ RADAR_MAX_CONTACTS ]; + int m_iRadarContactType[ RADAR_MAX_CONTACTS ]; +}; +C_PropJeepEpisodic *g_pJalopy = NULL; + +IMPLEMENT_CLIENTCLASS_DT( C_PropJeepEpisodic, DT_CPropJeepEpisodic, CPropJeepEpisodic ) + //CNetworkVar( int, m_iNumRadarContacts ); + RecvPropInt( RECVINFO(m_iNumRadarContacts) ), + + //CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS ); + RecvPropArray( RecvPropVector(RECVINFO(m_vecRadarContactPos[0])), m_vecRadarContactPos ), + + //CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS ); + RecvPropArray( RecvPropInt( RECVINFO(m_iRadarContactType[0] ) ), m_iRadarContactType ), + +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void __MsgFunc_UpdateJalopyRadar(bf_read &msg) +{ + // Radar code here! + if( !GetHudRadar() ) + return; + + // Sometimes we update more quickly when we need to track something in high resolution. + // Usually we do not, so default to false. + GetHudRadar()->m_bUseFastUpdate = false; + + for( int i = 0 ; i < g_pJalopy->m_iNumRadarContacts ; i++ ) + { + if( g_pJalopy->m_iRadarContactType[i] == RADAR_CONTACT_DOG ) + { + GetHudRadar()->m_bUseFastUpdate = true; + break; + } + } + + float flContactTimeToLive; + + if( GetHudRadar()->m_bUseFastUpdate ) + { + flContactTimeToLive = RADAR_UPDATE_FREQUENCY_FAST; + } + else + { + flContactTimeToLive = RADAR_UPDATE_FREQUENCY; + } + + for( int i = 0 ; i < g_pJalopy->m_iNumRadarContacts ; i++ ) + { + GetHudRadar()->AddRadarContact( g_pJalopy->m_vecRadarContactPos[i], g_pJalopy->m_iRadarContactType[i], flContactTimeToLive ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +C_PropJeepEpisodic::C_PropJeepEpisodic() +{ + if( g_pJalopy == NULL ) + { + usermessages->HookMessage( "UpdateJalopyRadar", __MsgFunc_UpdateJalopyRadar ); + } + + g_pJalopy = this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropJeepEpisodic::Simulate( void ) +{ + // Keep trying to hook to the radar. + if( GetHudRadar() != NULL ) + { + // This is not our ideal long-term solution. This will only work if you only have + // one jalopy in a given level. The Jalopy and the Radar Screen are currently both + // assumed to be singletons. This is appropriate for EP2, however. (sjb) + GetHudRadar()->SetVehicle( this ); + } + + BaseClass::Simulate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PropJeepEpisodic::OnEnteredVehicle( C_BasePlayer *pPlayer ) +{ + BaseClass::OnEnteredVehicle( pPlayer ); +} diff --git a/game/client/episodic/c_vort_charge_token.cpp b/game/client/episodic/c_vort_charge_token.cpp new file mode 100644 index 00000000..61086529 --- /dev/null +++ b/game/client/episodic/c_vort_charge_token.cpp @@ -0,0 +1,600 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#include "cbase.h" +#include "particles_simple.h" +#include "citadel_effects_shared.h" +#include "particles_attractor.h" +#include "iefx.h" +#include "dlight.h" +#include "ClientEffectPrecacheSystem.h" +#include "c_te_effect_dispatch.h" +#include "fx_quad.h" + +#include "c_ai_basenpc.h" + +// For material proxy +#include "ProxyEntity.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" + +#define NUM_INTERIOR_PARTICLES 8 + +#define DLIGHT_RADIUS (150.0f) +#define DLIGHT_MINLIGHT (40.0f/255.0f) + +class C_NPC_Vortigaunt : public C_AI_BaseNPC +{ + DECLARE_CLASS( C_NPC_Vortigaunt, C_AI_BaseNPC ); + DECLARE_CLIENTCLASS(); + +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink( void ); + virtual void ReceiveMessage( int classID, bf_read &msg ); + +public: + bool m_bIsBlue; ///< wants to fade to blue + float m_flBlueEndFadeTime; ///< when to end fading from one skin to another + + bool m_bIsBlack; ///< wants to fade to black (networked) + float m_flBlackFade; ///< [0.00 .. 1.00] where 1.00 is all black. Locally interpolated. +}; + +IMPLEMENT_CLIENTCLASS_DT( C_NPC_Vortigaunt, DT_NPC_Vortigaunt, CNPC_Vortigaunt ) + RecvPropTime( RECVINFO(m_flBlueEndFadeTime ) ), + RecvPropBool( RECVINFO(m_bIsBlue) ), + RecvPropBool( RECVINFO(m_bIsBlack) ), +END_RECV_TABLE() + + +#define VORTIGAUNT_BLUE_FADE_TIME 2.25f // takes this long to fade from green to blue or back +#define VORT_BLACK_FADE_TIME 2.2f // time to interpolate up or down in fading to black + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_NPC_Vortigaunt::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + // start thinking if we need to fade. + if ( m_flBlackFade != (m_bIsBlack ? 1.0f : 0.0f) ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_NPC_Vortigaunt::ClientThink( void ) +{ + // Don't update if our frame hasn't moved forward (paused) + if ( gpGlobals->frametime <= 0.0f ) + return; + + if ( m_bIsBlack ) + { + // are we done? + if ( m_flBlackFade >= 1.0f ) + { + m_flBlackFade = 1.0f; + SetNextClientThink( CLIENT_THINK_NEVER ); + } + else // interpolate there + { + float lerpQuant = gpGlobals->frametime / VORT_BLACK_FADE_TIME; + m_flBlackFade += lerpQuant; + if ( m_flBlackFade > 1.0f ) + { + m_flBlackFade = 1.0f; + } + } + } + else + { + // are we done? + if ( m_flBlackFade <= 0.0f ) + { + m_flBlackFade = 0.0f; + SetNextClientThink( CLIENT_THINK_NEVER ); + } + else // interpolate there + { + float lerpQuant = gpGlobals->frametime / VORT_BLACK_FADE_TIME; + m_flBlackFade -= lerpQuant; + if ( m_flBlackFade < 0.0f ) + { + m_flBlackFade = 0.0f; + } + } + } +} + +// FIXME: Move to shared code! +#define VORTFX_ZAPBEAM 0 +#define VORTFX_ARMBEAM 1 + +//----------------------------------------------------------------------------- +// Purpose: Receive messages from the server +//----------------------------------------------------------------------------- +void C_NPC_Vortigaunt::ReceiveMessage( int classID, bf_read &msg ) +{ + // Is the message for a sub-class? + if ( classID != GetClientClass()->m_ClassID ) + { + BaseClass::ReceiveMessage( classID, msg ); + return; + } + + int messageType = msg.ReadByte(); + switch( messageType ) + { + case VORTFX_ZAPBEAM: + { + // Find our attachment point + unsigned char nAttachment = msg.ReadByte(); + + // Get our attachment position + Vector vecStart; + QAngle vecAngles; + GetAttachment( nAttachment, vecStart, vecAngles ); + + // Get the final position we'll strike + Vector vecEndPos; + msg.ReadBitVec3Coord( vecEndPos ); + + // Place a beam between the two points + CNewParticleEffect *pEffect = ParticleProp()->Create( "vortigaunt_beam", PATTACH_POINT_FOLLOW, nAttachment ); + if ( pEffect ) + { + pEffect->SetControlPoint( 0, vecStart ); + pEffect->SetControlPoint( 1, vecEndPos ); + } + } + break; + + case VORTFX_ARMBEAM: + { + int nIndex = msg.ReadLong(); + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( ClientEntityList().EntIndexToHandle( nIndex ) ); + + if ( pEnt ) + { + unsigned char nAttachment = msg.ReadByte(); + Vector vecEndPos; + msg.ReadBitVec3Coord( vecEndPos ); + + Vector vecNormal; + msg.ReadBitVec3Normal( vecNormal ); + + CNewParticleEffect *pEffect = pEnt->ParticleProp()->Create( "vortigaunt_beam_charge", PATTACH_POINT_FOLLOW, nAttachment ); + if ( pEffect ) + { + // Set the control point's angles to be the surface normal we struct + Vector vecRight, vecUp; + VectorVectors( vecNormal, vecRight, vecUp ); + pEffect->SetControlPointOrientation( 1, vecNormal, vecRight, vecUp ); + pEffect->SetControlPoint( 1, vecEndPos ); + } + } + } + break; + default: + AssertMsg1( false, "Received unknown message %d", messageType); + } +} + +class C_VortigauntChargeToken : public C_BaseEntity +{ + DECLARE_CLASS( C_VortigauntChargeToken, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + +public: + virtual void UpdateOnRemove( void ); + virtual void ClientThink( void ); + virtual void NotifyShouldTransmit( ShouldTransmitState_t state ); + virtual void OnDataChanged( DataUpdateType_t type ); + + // For RecvProxy handlers + float m_flFadeOutTime; + float m_flFadeOutStart; + +private: + bool SetupEmitters( void ); + + bool m_bFadeOut; + CNewParticleEffect *m_hEffect; + dlight_t *m_pDLight; +}; + +void RecvProxy_FadeOutDuration( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_VortigauntChargeToken *pVortToken = (C_VortigauntChargeToken *) pStruct; + Assert( pOut == &pVortToken->m_flFadeOutTime ); + + pVortToken->m_flFadeOutStart = gpGlobals->curtime; + pVortToken->m_flFadeOutTime = ( pData->m_Value.m_Float - gpGlobals->curtime ); +} + +IMPLEMENT_CLIENTCLASS_DT( C_VortigauntChargeToken, DT_VortigauntChargeToken, CVortigauntChargeToken ) + RecvPropBool( RECVINFO( m_bFadeOut ) ), +END_RECV_TABLE() + +void C_VortigauntChargeToken::UpdateOnRemove( void ) +{ + if ( m_hEffect ) + { + m_hEffect->StopEmission(); + m_hEffect = NULL; + } + + if ( m_pDLight != NULL ) + { + m_pDLight->die = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Change our transmission state +//----------------------------------------------------------------------------- +void C_VortigauntChargeToken::NotifyShouldTransmit( ShouldTransmitState_t state ) +{ + BaseClass::NotifyShouldTransmit( state ); + + // Turn off + if ( state == SHOULDTRANSMIT_END ) + { + if ( m_hEffect ) + { + m_hEffect->StopEmission(); + m_hEffect = NULL; + } + } + + // Turn on + if ( state == SHOULDTRANSMIT_START ) + { + m_hEffect = ParticleProp()->Create( "vortigaunt_charge_token", PATTACH_ABSORIGIN_FOLLOW ); + m_hEffect->SetControlPointEntity( 0, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_VortigauntChargeToken::OnDataChanged( DataUpdateType_t type ) +{ + if ( m_bFadeOut ) + { + if ( m_hEffect ) + { + m_hEffect->StopEmission(); + m_hEffect = NULL; + } + } + + BaseClass::OnDataChanged( type ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_VortigauntChargeToken::ClientThink( void ) +{ + // + // -- DLight + // + + if ( m_pDLight != NULL ) + { + m_pDLight->origin = GetAbsOrigin(); + m_pDLight->radius = DLIGHT_RADIUS; + } +} + +//============================================================================= +// +// Dispel Effect +// +//============================================================================= + +class C_VortigauntEffectDispel : public C_BaseEntity +{ + DECLARE_CLASS( C_VortigauntEffectDispel, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + +public: + virtual void UpdateOnRemove( void ); + virtual void ClientThink( void ); + virtual void NotifyShouldTransmit( ShouldTransmitState_t state ); + virtual void OnDataChanged( DataUpdateType_t type ); + + // For RecvProxy handlers + float m_flFadeOutTime; + float m_flFadeOutStart; + +private: + bool SetupEmitters( void ); + + CNewParticleEffect *m_hEffect; + bool m_bFadeOut; + dlight_t *m_pDLight; +}; + +void RecvProxy_DispelFadeOutDuration( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_VortigauntEffectDispel *pVortToken = (C_VortigauntEffectDispel *) pStruct; + Assert( pOut == &pVortToken->m_flFadeOutTime ); + + pVortToken->m_flFadeOutStart = gpGlobals->curtime; + pVortToken->m_flFadeOutTime = ( pData->m_Value.m_Float - gpGlobals->curtime ); +} + +IMPLEMENT_CLIENTCLASS_DT( C_VortigauntEffectDispel, DT_VortigauntEffectDispel, CVortigauntEffectDispel ) + RecvPropBool( RECVINFO( m_bFadeOut ) ), +END_RECV_TABLE() + +void C_VortigauntEffectDispel::UpdateOnRemove( void ) +{ + if ( m_hEffect ) + { + m_hEffect->StopEmission(); + m_hEffect = NULL; + } + + if ( m_pDLight != NULL ) + { + m_pDLight->die = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_VortigauntEffectDispel::OnDataChanged( DataUpdateType_t type ) +{ + if ( m_bFadeOut ) + { + if ( m_hEffect ) + { + m_hEffect->StopEmission(); + m_hEffect = NULL; + } + } + + BaseClass::OnDataChanged( type ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_VortigauntEffectDispel::NotifyShouldTransmit( ShouldTransmitState_t state ) +{ + BaseClass::NotifyShouldTransmit( state ); + + // Turn off + if ( state == SHOULDTRANSMIT_END ) + { + if ( m_hEffect ) + { + m_hEffect->StopEmission(); + m_hEffect = NULL; + } + } + + // Turn on + if ( state == SHOULDTRANSMIT_START ) + { + m_hEffect = ParticleProp()->Create( "vortigaunt_hand_glow", PATTACH_ABSORIGIN_FOLLOW ); + m_hEffect->SetControlPointEntity( 0, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create our emitter +//----------------------------------------------------------------------------- +bool C_VortigauntEffectDispel::SetupEmitters( void ) +{ + m_pDLight = NULL; + +#ifndef _X360 + m_pDLight = effects->CL_AllocDlight ( index ); + m_pDLight->origin = GetAbsOrigin(); + m_pDLight->color.r = 64; + m_pDLight->color.g = 255; + m_pDLight->color.b = 64; + m_pDLight->radius = 0; + m_pDLight->minlight = DLIGHT_MINLIGHT; + m_pDLight->die = FLT_MAX; +#endif // _X360 + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_VortigauntEffectDispel::ClientThink( void ) +{ + if ( m_pDLight != NULL ) + { + m_pDLight->origin = GetAbsOrigin(); + m_pDLight->radius = DLIGHT_RADIUS; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DispelCallback( const CEffectData &data ) +{ + // Kaboom! + Vector startPos = data.m_vOrigin + Vector(0,0,16); + Vector endPos = data.m_vOrigin + Vector(0,0,-64); + + trace_t tr; + UTIL_TraceLine( startPos, endPos, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 1.0f ) + { + //Add a ripple quad to the surface + FX_AddQuad( tr.endpos + ( tr.plane.normal * 8.0f ), + Vector( 0, 0, 1 ), + 64.0f, + 600.0f, + 0.8f, + 1.0f, // start alpha + 0.0f, // end alpha + 0.3f, + random->RandomFloat( 0, 360 ), + 0.0f, + Vector( 0.5f, 1.0f, 0.5f ), + 0.75f, + "effects/ar2_altfire1b", + (FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA|FXQUAD_COLOR_FADE) ); + + //Add a ripple quad to the surface + FX_AddQuad( tr.endpos + ( tr.plane.normal * 8.0f ), + Vector( 0, 0, 1 ), + 16.0f, + 300.0f, + 0.9f, + 1.0f, // start alpha + 0.0f, // end alpha + 0.9f, + random->RandomFloat( 0, 360 ), + 0.0f, + Vector( 0.5f, 1.0f, 0.5f ), + 1.25f, + "effects/rollerglow", + (FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA) ); + } +} + +DECLARE_CLIENT_EFFECT( "VortDispel", DispelCallback ); + +//----------------------------------------------------------------------------- +// Purpose: Used for emissive lightning layer on vort +//----------------------------------------------------------------------------- +class CVortEmissiveProxy : public CEntityMaterialProxy +{ +public: + CVortEmissiveProxy( void ); + virtual ~CVortEmissiveProxy( void ); + virtual bool Init( IMaterial *pMaterial, KeyValues* pKeyValues ); + virtual void OnBind( C_BaseEntity *pC_BaseEntity ); + virtual IMaterial * GetMaterial(); + +private: + + IMaterialVar *m_pMatEmissiveStrength; + IMaterialVar *m_pMatDetailBlendStrength; +}; + +//----------------------------------------------------------------------------- +CVortEmissiveProxy::CVortEmissiveProxy( void ) +{ + m_pMatEmissiveStrength = NULL; + m_pMatDetailBlendStrength = NULL; +} + +CVortEmissiveProxy::~CVortEmissiveProxy( void ) +{ + // Do nothing +} + +//----------------------------------------------------------------------------- +bool CVortEmissiveProxy::Init( IMaterial *pMaterial, KeyValues* pKeyValues ) +{ + Assert( pMaterial ); + + // Need to get the material var + bool bFound; + m_pMatEmissiveStrength = pMaterial->FindVar( "$emissiveblendstrength", &bFound ); + + if ( bFound ) + { + // Optional + bool bFound2; + m_pMatDetailBlendStrength = pMaterial->FindVar( "$detailblendfactor", &bFound2 ); + } + + return bFound; +} + +//----------------------------------------------------------------------------- +void CVortEmissiveProxy::OnBind( C_BaseEntity *pEnt ) +{ + C_NPC_Vortigaunt *pVort = dynamic_cast(pEnt); + + float flBlendValue; + + if (pVort) + { + // do we need to crossfade? + if (gpGlobals->curtime < pVort->m_flBlueEndFadeTime) + { + // will be 0 when fully faded and 1 when not faded at all: + float fadeRatio = (pVort->m_flBlueEndFadeTime - gpGlobals->curtime) / VORTIGAUNT_BLUE_FADE_TIME; + if (pVort->m_bIsBlue) + { + fadeRatio = 1.0f - fadeRatio; + } + flBlendValue = clamp( fadeRatio, 0.0f, 1.0f ); + } + else // no crossfade + { + flBlendValue = pVort->m_bIsBlue ? 1.0f : 0.0f; + } + + // ALEX VLACHOS: + // The following variable varies on [0 .. 1]. 0.0 means the vort wants to be his normal + // color. 1.0 means he wants to be all black. It is interpolated in the + // C_NPC_Vortigaunt::ClientThink() function. + // + // pVort->m_flBlackFade + } + else + { // if you bind this proxy to anything non-vort (eg a ragdoll) it's always green + flBlendValue = 0.0f; + } + + + /* + // !!! Change me !!! I'm using a clamped sin wave for debugging + float flBlendValue = sinf( gpGlobals->curtime * 4.0f ) * 0.75f + 0.25f; + + // Clamp 0-1 + flBlendValue = ( flBlendValue < 0.0f ) ? 0.0f : ( flBlendValue > 1.0f ) ? 1.0f : flBlendValue; + */ + + if( m_pMatEmissiveStrength != NULL ) + { + m_pMatEmissiveStrength->SetFloatValue( flBlendValue ); + } + + if( m_pMatDetailBlendStrength != NULL ) + { + m_pMatDetailBlendStrength->SetFloatValue( flBlendValue ); + } +} + +//----------------------------------------------------------------------------- +IMaterial *CVortEmissiveProxy::GetMaterial() +{ + if ( m_pMatEmissiveStrength != NULL ) + return m_pMatEmissiveStrength->GetOwningMaterial(); + else if ( m_pMatDetailBlendStrength != NULL ) + return m_pMatDetailBlendStrength->GetOwningMaterial(); + else + return NULL; +} + +EXPOSE_INTERFACE( CVortEmissiveProxy, IMaterialProxy, "VortEmissive" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/episodic/c_weapon_hopwire.cpp b/game/client/episodic/c_weapon_hopwire.cpp new file mode 100644 index 00000000..30689d0f --- /dev/null +++ b/game/client/episodic/c_weapon_hopwire.cpp @@ -0,0 +1,421 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "basegrenade_shared.h" +#include "fx_interpvalue.h" +#include "fx_envelope.h" +#include "materialsystem/imaterialvar.h" +#include "particles_simple.h" +#include "particles_attractor.h" + +// FIXME: Move out +extern void DrawSpriteTangentSpace( const Vector &vecOrigin, float flWidth, float flHeight, color32 color ); + +#define EXPLOSION_DURATION 3.0f + +//----------------------------------------------------------------------------- +// Explosion effect for hopwire +//----------------------------------------------------------------------------- + +class C_HopwireExplosion : public C_EnvelopeFX +{ + typedef C_EnvelopeFX BaseClass; + +public: + C_HopwireExplosion( void ) : + m_hOwner( NULL ) + { + m_FXCoreScale.SetAbsolute( 0.0f ); + m_FXCoreAlpha.SetAbsolute( 0.0f ); + } + + virtual void Update( void ); + virtual int DrawModel( int flags ); + virtual void GetRenderBounds( Vector& mins, Vector& maxs ); + + bool SetupEmitters( void ); + void AddParticles( void ); + void SetOwner( C_BaseEntity *pOwner ); + void StartExplosion( void ); + void StopExplosion( void ); + void StartPreExplosion( void ); + +private: + CInterpolatedValue m_FXCoreScale; + CInterpolatedValue m_FXCoreAlpha; + + CSmartPtr m_pSimpleEmitter; + CSmartPtr m_pAttractorEmitter; + + TimedEvent m_ParticleTimer; + + CHandle m_hOwner; +}; + +//----------------------------------------------------------------------------- +// Purpose: Setup the emitters we'll be using +//----------------------------------------------------------------------------- +bool C_HopwireExplosion::SetupEmitters( void ) +{ + // Setup the basic core emitter + if ( m_pSimpleEmitter.IsValid() == false ) + { + m_pSimpleEmitter = CSimpleEmitter::Create( "hopwirecore" ); + + if ( m_pSimpleEmitter.IsValid() == false ) + return false; + } + + // Setup the attractor emitter + if ( m_pAttractorEmitter.IsValid() == false ) + { + m_pAttractorEmitter = CParticleAttractor::Create( GetRenderOrigin(), "hopwireattractor" ); + + if ( m_pAttractorEmitter.IsValid() == false ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_HopwireExplosion::AddParticles( void ) +{ + // Make sure the emitters are setup properly + if ( SetupEmitters() == false ) + return; + + float tempDelta = gpGlobals->frametime; + while( m_ParticleTimer.NextEvent( tempDelta ) ) + { + // ======================== + // Attracted dust particles + // ======================== + + // Update our attractor point + m_pAttractorEmitter->SetAttractorOrigin( GetRenderOrigin() ); + + Vector offset; + SimpleParticle *sParticle; + + offset = GetRenderOrigin() + RandomVector( -256.0f, 256.0f ); + + sParticle = (SimpleParticle *) m_pAttractorEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_Fleck_Cement[0], offset ); + + if ( sParticle == NULL ) + return; + + sParticle->m_vecVelocity = Vector(0,0,8); + sParticle->m_flDieTime = 0.5f; + sParticle->m_flLifetime = 0.0f; + + sParticle->m_flRoll = Helper_RandomInt( 0, 360 ); + sParticle->m_flRollDelta = 1.0f; + + float alpha = random->RandomFloat( 128.0f, 200.0f ); + + sParticle->m_uchColor[0] = alpha; + sParticle->m_uchColor[1] = alpha; + sParticle->m_uchColor[2] = alpha; + sParticle->m_uchStartAlpha = alpha; + sParticle->m_uchEndAlpha = alpha; + + sParticle->m_uchStartSize = random->RandomInt( 1, 4 ); + sParticle->m_uchEndSize = 0; + + // ======================== + // Core effects + // ======================== + + // Reset our sort origin + m_pSimpleEmitter->SetSortOrigin( GetRenderOrigin() ); + + // Base of the core effect + sParticle = (SimpleParticle *) m_pSimpleEmitter->AddParticle( sizeof(SimpleParticle), m_pSimpleEmitter->GetPMaterial( "effects/strider_muzzle" ), GetRenderOrigin() ); + + if ( sParticle == NULL ) + return; + + sParticle->m_vecVelocity = vec3_origin; + sParticle->m_flDieTime = 0.2f; + sParticle->m_flLifetime = 0.0f; + + sParticle->m_flRoll = Helper_RandomInt( 0, 360 ); + sParticle->m_flRollDelta = 4.0f; + + alpha = random->RandomInt( 32, 200 ); + + sParticle->m_uchColor[0] = alpha; + sParticle->m_uchColor[1] = alpha; + sParticle->m_uchColor[2] = alpha; + sParticle->m_uchStartAlpha = 0; + sParticle->m_uchEndAlpha = alpha; + + sParticle->m_uchStartSize = 255; + sParticle->m_uchEndSize = 0; + + // Make sure we encompass the complete particle here! + m_pSimpleEmitter->SetParticleCullRadius( sParticle->m_uchEndSize ); + + // ========================= + // Dust ring effect + // ========================= + + if ( random->RandomInt( 0, 5 ) != 1 ) + return; + + Vector vecDustColor; + vecDustColor.x = 0.35f; + vecDustColor.y = 0.3f; + vecDustColor.z = 0.25f; + + Vector color; + + int numRingSprites = 8; + float yaw; + Vector forward, vRight, vForward; + + vForward = Vector( 0, 1, 0 ); + vRight = Vector( 1, 0, 0 ); + + float yawOfs = random->RandomFloat( 0, 359 ); + + for ( int i = 0; i < numRingSprites; i++ ) + { + yaw = ( (float) i / (float) numRingSprites ) * 360.0f; + yaw += yawOfs; + + forward = ( vRight * sin( DEG2RAD( yaw) ) ) + ( vForward * cos( DEG2RAD( yaw ) ) ); + VectorNormalize( forward ); + + trace_t tr; + + UTIL_TraceLine( GetRenderOrigin(), GetRenderOrigin()+(Vector(0, 0, -1024)), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + offset = ( RandomVector( -4.0f, 4.0f ) + tr.endpos ) + ( forward * 512.0f ); + + sParticle = (SimpleParticle *) m_pSimpleEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[random->RandomInt(0,1)], offset ); + + if ( sParticle != NULL ) + { + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + sParticle->m_vecVelocity = forward * -random->RandomFloat( 1000, 1500 ); + sParticle->m_vecVelocity[2] += 128.0f; + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + sParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + sParticle->m_uchColor[0] = vecDustColor.x * 255.0f; + sParticle->m_uchColor[1] = vecDustColor.y * 255.0f; + sParticle->m_uchColor[2] = vecDustColor.z * 255.0f; + + sParticle->m_uchStartSize = random->RandomInt( 32, 128 ); + sParticle->m_uchEndSize = 200; + + sParticle->m_uchStartAlpha = random->RandomFloat( 16, 64 ); + sParticle->m_uchEndAlpha = 0; + + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -16.0f, 16.0f ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOwner - +//----------------------------------------------------------------------------- +void C_HopwireExplosion::SetOwner( C_BaseEntity *pOwner ) +{ + m_hOwner = pOwner; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the internal values for the effect +//----------------------------------------------------------------------------- +void C_HopwireExplosion::Update( void ) +{ + if ( m_hOwner ) + { + SetRenderOrigin( m_hOwner->GetRenderOrigin() ); + } + + BaseClass::Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates and renders all effects +//----------------------------------------------------------------------------- +int C_HopwireExplosion::DrawModel( int flags ) +{ + AddParticles(); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Flush(); + UpdateRefractTexture(); + + IMaterial *pMat = materials->FindMaterial( "effects/strider_pinch_dudv", TEXTURE_GROUP_CLIENT_EFFECTS ); + + float refract = m_FXCoreAlpha.Interp( gpGlobals->curtime ); + float scale = m_FXCoreScale.Interp( gpGlobals->curtime ); + + IMaterialVar *pVar = pMat->FindVar( "$refractamount", NULL ); + pVar->SetFloatValue( refract ); + + pRenderContext->Bind( pMat, (IClientRenderable*)this ); + + float sin1 = sinf( gpGlobals->curtime * 10 ); + float sin2 = sinf( gpGlobals->curtime ); + + float scaleY = ( sin1 * sin2 ) * 32.0f; + float scaleX = (sin2 * sin2) * 32.0f; + + // FIXME: The ball needs to sort properly at all times + static color32 white = {255,255,255,255}; + DrawSpriteTangentSpace( GetRenderOrigin() + ( CurrentViewForward() * 128.0f ), scale+scaleX, scale+scaleY, white ); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the bounds relative to the origin (render bounds) +//----------------------------------------------------------------------------- +void C_HopwireExplosion::GetRenderBounds( Vector& mins, Vector& maxs ) +{ + float scale = m_FXCoreScale.Interp( gpGlobals->curtime ); + + mins.Init( -scale, -scale, -scale ); + maxs.Init( scale, scale, scale ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_HopwireExplosion::StartExplosion( void ) +{ + m_FXCoreScale.Init( 300.0f, 500.0f, 2.0f, INTERP_SPLINE ); + m_FXCoreAlpha.Init( 0.0f, 0.1f, 1.5f, INTERP_SPLINE ); + + // Particle timer + m_ParticleTimer.Init( 60 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_HopwireExplosion::StopExplosion( void ) +{ + m_FXCoreAlpha.InitFromCurrent( 0.0f, 1.0f, INTERP_SPLINE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_HopwireExplosion::StartPreExplosion( void ) +{ +} + +//----------------------------------------------------------------------------- +// Hopwire client class +//----------------------------------------------------------------------------- + +class C_GrenadeHopwire : public C_BaseGrenade +{ + DECLARE_CLASS( C_GrenadeHopwire, C_BaseGrenade ); + DECLARE_CLIENTCLASS(); + +public: + C_GrenadeHopwire( void ); + + virtual int DrawModel( int flags ); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ReceiveMessage( int classID, bf_read &msg ); + +private: + + C_HopwireExplosion m_ExplosionEffect; // Explosion effect information and drawing +}; + +IMPLEMENT_CLIENTCLASS_DT( C_GrenadeHopwire, DT_GrenadeHopwire, CGrenadeHopwire ) +END_RECV_TABLE() + +#define HOPWIRE_START_EXPLOSION 0 +#define HOPWIRE_STOP_EXPLOSION 1 +#define HOPWIRE_START_PRE_EXPLOSION 2 + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +C_GrenadeHopwire::C_GrenadeHopwire( void ) +{ + m_ExplosionEffect.SetActive( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Receive messages from the server +// Input : classID - class to receive the message +// &msg - message in question +//----------------------------------------------------------------------------- +void C_GrenadeHopwire::ReceiveMessage( int classID, bf_read &msg ) +{ + if ( classID != GetClientClass()->m_ClassID ) + { + // Message is for subclass + BaseClass::ReceiveMessage( classID, msg ); + return; + } + + int messageType = msg.ReadByte(); + switch( messageType ) + { + case HOPWIRE_START_EXPLOSION: + { + m_ExplosionEffect.SetActive(); + m_ExplosionEffect.SetOwner( this ); + m_ExplosionEffect.StartExplosion(); + } + break; + case HOPWIRE_STOP_EXPLOSION: + { + m_ExplosionEffect.StopExplosion(); + } + break; + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_GrenadeHopwire::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + m_ExplosionEffect.Update(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flags - +//----------------------------------------------------------------------------- +int C_GrenadeHopwire::DrawModel( int flags ) +{ + if ( m_ExplosionEffect.IsActive() ) + return 1; + + return BaseClass::DrawModel( flags ); +} + diff --git a/game/client/episodic/episodic_screenspaceeffects.cpp b/game/client/episodic/episodic_screenspaceeffects.cpp new file mode 100644 index 00000000..1ea83222 --- /dev/null +++ b/game/client/episodic/episodic_screenspaceeffects.cpp @@ -0,0 +1,463 @@ +// +// Episodic screen-space effects +// + +#include "cbase.h" +#include "screenspaceeffects.h" +#include "rendertexture.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterialvar.h" +#include "cdll_client_int.h" +#include "materialsystem/itexture.h" +#include "keyvalues.h" +#include "ClientEffectPrecacheSystem.h" + +#include "episodic_screenspaceeffects.h" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef _X360 +#define STUN_TEXTURE "_rt_FullFrameFB2" +#else +#define STUN_TEXTURE "_rt_WaterRefraction" +#endif + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CStunEffect::Init( void ) +{ + m_flDuration = 0.0f; + m_flFinishTime = 0.0f; + m_bUpdateView = true; + + KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); + pVMTKeyValues->SetString( "$basetexture", STUN_TEXTURE ); + m_EffectMaterial.Init( "__stuneffect", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues ); + m_StunTexture.Init( STUN_TEXTURE, TEXTURE_GROUP_CLIENT_EFFECTS ); +} + +void CStunEffect::Shutdown( void ) +{ + m_EffectMaterial.Shutdown(); + m_StunTexture.Shutdown(); +} + +//------------------------------------------------------------------------------ +// Purpose: Pick up changes in our parameters +//------------------------------------------------------------------------------ +void CStunEffect::SetParameters( KeyValues *params ) +{ + if( params->FindKey( "duration" ) ) + { + m_flDuration = params->GetFloat( "duration" ); + m_flFinishTime = gpGlobals->curtime + m_flDuration; + m_bUpdateView = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Render the effect +//----------------------------------------------------------------------------- +void CStunEffect::Render( int x, int y, int w, int h ) +{ + // Make sure we're ready to play this effect + if ( m_flFinishTime < gpGlobals->curtime ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + // Set ourselves to the proper rendermode + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + // Draw the texture if we're using it + if ( m_bUpdateView ) + { + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + m_bUpdateView = false; + } + + float flEffectPerc = ( m_flFinishTime - gpGlobals->curtime ) / m_flDuration; + + float viewOffs = ( flEffectPerc * 32.0f ) * ( cos( gpGlobals->curtime * 40.0f ) * sin( gpGlobals->curtime * 17.0f ) ); + float vX = x + viewOffs; + + if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 ) + { + if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE ) + { + m_EffectMaterial->ColorModulate( 1.0f, 1.0f, 1.0f ); + } + else + { + // This is a stupid fix, but I don't have time to do a cleaner implementation. Since + // the introblur.vmt material uses unlit generic, it will tone map, so I need to undo the tone mapping + // using color modulate. The proper fix would be to use a different material type that + // supports alpha blending but not tone mapping, which I don't think exists. Whatever. This works when + // the tone mapping scalar is less than 1.0, which it is in the cases it's used in game. + float flUnTonemap = pow( 1.0f / pRenderContext->GetToneMappingScaleLinear().x, 1.0f / 2.2f ); + m_EffectMaterial->ColorModulate( flUnTonemap, flUnTonemap, flUnTonemap ); + } + + // Set alpha blend value + float flOverlayAlpha = clamp( ( 150.0f / 255.0f ) * flEffectPerc, 0.0f, 1.0f ); + m_EffectMaterial->AlphaModulate( flOverlayAlpha ); + + // Draw full screen alpha-blended quad + pRenderContext->DrawScreenSpaceRectangle( m_EffectMaterial, 0, 0, w, h, + vX, 0, (m_StunTexture->GetActualWidth()-1)+vX, (m_StunTexture->GetActualHeight()-1), + m_StunTexture->GetActualWidth(), m_StunTexture->GetActualHeight() ); + } + + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + + // Restore our state + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); +} + +// ================================================================================================================ +// +// Ep 1. Intro blur +// +// ================================================================================================================ + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEP1IntroEffect::Init( void ) +{ + m_flDuration = 0.0f; + m_flFinishTime = 0.0f; + m_bUpdateView = true; + m_bFadeOut = false; + + KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); + pVMTKeyValues->SetString( "$basetexture", STUN_TEXTURE ); + m_EffectMaterial.Init( "__ep1introeffect", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues ); + m_StunTexture.Init( STUN_TEXTURE, TEXTURE_GROUP_CLIENT_EFFECTS ); +} + +void CEP1IntroEffect::Shutdown( void ) +{ + m_EffectMaterial.Shutdown(); + m_StunTexture.Shutdown(); +} + + +//------------------------------------------------------------------------------ +// Purpose: Pick up changes in our parameters +//------------------------------------------------------------------------------ +void CEP1IntroEffect::SetParameters( KeyValues *params ) +{ + if( params->FindKey( "duration" ) ) + { + m_flDuration = params->GetFloat( "duration" ); + m_flFinishTime = gpGlobals->curtime + m_flDuration; + } + + if( params->FindKey( "fadeout" ) ) + { + m_bFadeOut = ( params->GetInt( "fadeout" ) == 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the alpha value depending on various factors and time +//----------------------------------------------------------------------------- +inline unsigned char CEP1IntroEffect::GetFadeAlpha( void ) +{ + // Find our percentage between fully "on" and "off" in the pulse range + float flEffectPerc = ( m_flDuration == 0.0f ) ? 0.0f : ( m_flFinishTime - gpGlobals->curtime ) / m_flDuration; + flEffectPerc = clamp( flEffectPerc, 0.0f, 1.0f ); + + if ( m_bFadeOut ) + { + // HDR requires us to be more subtle, or we get uber-brightening + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE ) + return (unsigned char) clamp( 50.0f * flEffectPerc, 0.0f, 50.0f ); + + // Non-HDR + return (unsigned char) clamp( 64.0f * flEffectPerc, 0.0f, 64.0f ); + } + else + { + // HDR requires us to be more subtle, or we get uber-brightening + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE ) + return (unsigned char) clamp( 64.0f * flEffectPerc, 50.0f, 64.0f ); + + // Non-HDR + return (unsigned char) clamp( 128.0f * flEffectPerc, 64.0f, 128.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Render the effect +//----------------------------------------------------------------------------- +void CEP1IntroEffect::Render( int x, int y, int w, int h ) +{ + if ( ( m_flFinishTime == 0 ) || ( IsEnabled() == false ) ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + // Set ourselves to the proper rendermode + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + // Draw the texture if we're using it + if ( m_bUpdateView ) + { + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + m_bUpdateView = false; + } + + byte overlaycolor[4] = { 255, 255, 255, 0 }; + + // Get our fade value depending on our fade duration + overlaycolor[3] = GetFadeAlpha(); + if ( g_pMaterialSystemHardwareConfig->UsesSRGBCorrectBlending() ) + { + // For DX10 cards, alpha blending happens in linear space, so try to adjust by hacking alpha to 50% + overlaycolor[3] *= 0.7f; + } + + // Disable overself if we're done fading out + if ( m_bFadeOut && overlaycolor[3] == 0 ) + { + // Takes effect next frame (we don't want to hose our matrix stacks here) + g_pScreenSpaceEffects->DisableScreenSpaceEffect( "episodic_intro" ); + m_bUpdateView = true; + } + + // Calculate some wavey noise to jitter the view by + float vX = 2.0f * -fabs( cosf( gpGlobals->curtime ) * cosf( gpGlobals->curtime * 6.0 ) ); + float vY = 2.0f * cosf( gpGlobals->curtime ) * cosf( gpGlobals->curtime * 5.0 ); + + // Scale percentage + float flScalePerc = 0.02f + ( 0.01f * cosf( gpGlobals->curtime * 2.0f ) * cosf( gpGlobals->curtime * 0.5f ) ); + + // Scaled offsets for the UVs (as texels) + float flUOffset = ( m_StunTexture->GetActualWidth() - 1 ) * flScalePerc * 0.5f; + float flVOffset = ( m_StunTexture->GetActualHeight() - 1 ) * flScalePerc * 0.5f; + + // New UVs with scaling offsets + float flU1 = flUOffset; + float flU2 = ( m_StunTexture->GetActualWidth() - 1 ) - flUOffset; + float flV1 = flVOffset; + float flV2 = ( m_StunTexture->GetActualHeight() - 1 ) - flVOffset; + + // Draw the "zoomed" overlay + pRenderContext->DrawScreenSpaceRectangle( m_EffectMaterial, vX, vY, w, h, + flU1, flV1, + flU2, flV2, + m_StunTexture->GetActualWidth(), m_StunTexture->GetActualHeight() ); + + render->ViewDrawFade( overlaycolor, m_EffectMaterial ); + + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + + // Restore our state + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); +} + +// ================================================================================================================ +// +// Ep 2. Groggy-player view +// +// ================================================================================================================ + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEP2StunEffect::Init( void ) +{ + m_flDuration = 0.0f; + m_flFinishTime = 0.0f; + m_bUpdateView = true; + m_bFadeOut = false; + + KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); + pVMTKeyValues->SetString( "$basetexture", STUN_TEXTURE ); + m_EffectMaterial.Init( "__ep2stuneffect", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues ); + m_StunTexture.Init( STUN_TEXTURE, TEXTURE_GROUP_CLIENT_EFFECTS ); +} + +void CEP2StunEffect::Shutdown( void ) +{ + m_EffectMaterial.Shutdown(); + m_StunTexture.Shutdown(); +} + +//------------------------------------------------------------------------------ +// Purpose: Pick up changes in our parameters +//------------------------------------------------------------------------------ +void CEP2StunEffect::SetParameters( KeyValues *params ) +{ + if( params->FindKey( "duration" ) ) + { + m_flDuration = params->GetFloat( "duration" ); + m_flFinishTime = gpGlobals->curtime + m_flDuration; + } + + if( params->FindKey( "fadeout" ) ) + { + m_bFadeOut = ( params->GetInt( "fadeout" ) == 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the alpha value depending on various factors and time +//----------------------------------------------------------------------------- +inline unsigned char CEP2StunEffect::GetFadeAlpha( void ) +{ + // Find our percentage between fully "on" and "off" in the pulse range + float flEffectPerc = ( m_flDuration == 0.0f ) ? 0.0f : ( m_flFinishTime - gpGlobals->curtime ) / m_flDuration; + flEffectPerc = clamp( flEffectPerc, 0.0f, 1.0f ); + + if ( m_bFadeOut ) + { + // HDR requires us to be more subtle, or we get uber-brightening + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE ) + return (unsigned char) clamp( 50.0f * flEffectPerc, 0.0f, 50.0f ); + + // Non-HDR + return (unsigned char) clamp( 64.0f * flEffectPerc, 0.0f, 64.0f ); + } + else + { + // HDR requires us to be more subtle, or we get uber-brightening + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE ) + return (unsigned char) clamp( 164.0f * flEffectPerc, 128.0f, 164.0f ); + + // Non-HDR + return (unsigned char) clamp( 164.0f * flEffectPerc, 128.0f, 164.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Render the effect +//----------------------------------------------------------------------------- +void CEP2StunEffect::Render( int x, int y, int w, int h ) +{ + if ( ( m_flFinishTime == 0 ) || ( IsEnabled() == false ) ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + + // Set ourselves to the proper rendermode + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + if ( m_bUpdateView ) + { + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + m_bUpdateView = false; + } + + byte overlaycolor[4] = { 255, 255, 255, 0 }; + + // Get our fade value depending on our fade duration + overlaycolor[3] = GetFadeAlpha(); + + // Disable overself if we're done fading out + if ( m_bFadeOut && overlaycolor[3] == 0 ) + { + // Takes effect next frame (we don't want to hose our matrix stacks here) + g_pScreenSpaceEffects->DisableScreenSpaceEffect( "ep2_groggy" ); + m_bUpdateView = true; + } + + // Calculate some wavey noise to jitter the view by + float vX = 4.0f * cosf( gpGlobals->curtime ) * cosf( gpGlobals->curtime * 6.0 ); + float vY = 2.0f * cosf( gpGlobals->curtime ) * cosf( gpGlobals->curtime * 5.0 ); + + float flBaseScale = 0.2f + 0.005f * sinf( gpGlobals->curtime * 4.0f ); + + // Scale percentage + float flScalePerc = flBaseScale + ( 0.01f * cosf( gpGlobals->curtime * 2.0f ) * cosf( gpGlobals->curtime * 0.5f ) ); + + // Scaled offsets for the UVs (as texels) + float flUOffset = ( m_StunTexture->GetActualWidth() - 1 ) * flScalePerc * 0.5f; + float flVOffset = ( m_StunTexture->GetActualHeight() - 1 ) * flScalePerc * 0.5f; + + // New UVs with scaling offsets + float flU1 = flUOffset; + float flU2 = ( m_StunTexture->GetActualWidth() - 1 ) - flUOffset; + float flV1 = flVOffset; + float flV2 = ( m_StunTexture->GetActualHeight() - 1 ) - flVOffset; + + // Draw the "zoomed" overlay + pRenderContext->DrawScreenSpaceRectangle( m_EffectMaterial, vX, vY, w, h, + flU1, flV1, + flU2, flV2, + m_StunTexture->GetActualWidth(), m_StunTexture->GetActualHeight() ); + + render->ViewDrawFade( overlaycolor, m_EffectMaterial ); + + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + + // Restore our state + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); +} diff --git a/game/client/episodic/episodic_screenspaceeffects.h b/game/client/episodic/episodic_screenspaceeffects.h new file mode 100644 index 00000000..2bb1fe52 --- /dev/null +++ b/game/client/episodic/episodic_screenspaceeffects.h @@ -0,0 +1,119 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef EPISODIC_SCREENSPACEEFFECTS_H +#define EPISODIC_SCREENSPACEEFFECTS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "screenspaceeffects.h" + +class CStunEffect : public IScreenSpaceEffect +{ +public: + CStunEffect( void ) : + m_flDuration( 0.0f ), + m_flFinishTime( 0.0f ), + m_bUpdateView( true ) {} + + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void SetParameters( KeyValues *params ); + virtual void Enable( bool bEnable ) {}; + virtual bool IsEnabled( ) { return true; } + + virtual void Render( int x, int y, int w, int h ); + +private: + CTextureReference m_StunTexture; + CMaterialReference m_EffectMaterial; + float m_flDuration; + float m_flFinishTime; + bool m_bUpdateView; +}; + +ADD_SCREENSPACE_EFFECT( CStunEffect, episodic_stun ); + +// +// EP1 Intro Blur +// + +class CEP1IntroEffect : public IScreenSpaceEffect +{ +public: + CEP1IntroEffect( void ) : + m_flDuration( 0.0f ), + m_flFinishTime( 0.0f ), + m_bUpdateView( true ), + m_bEnabled( false ), + m_bFadeOut( false ) {} + + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void SetParameters( KeyValues *params ); + virtual void Enable( bool bEnable ) { m_bEnabled = bEnable; } + virtual bool IsEnabled( ) { return m_bEnabled; } + + virtual void Render( int x, int y, int w, int h ); + +private: + + inline unsigned char GetFadeAlpha( void ); + + CTextureReference m_StunTexture; + CMaterialReference m_EffectMaterial; + float m_flDuration; + float m_flFinishTime; + bool m_bUpdateView; + bool m_bEnabled; + bool m_bFadeOut; +}; + +ADD_SCREENSPACE_EFFECT( CEP1IntroEffect, episodic_intro ); + +// +// EP2 Player Stunned Effect +// + +// +// EP1 Intro Blur +// + +class CEP2StunEffect : public IScreenSpaceEffect +{ +public: + CEP2StunEffect( void ) : + m_flDuration( 0.0f ), + m_flFinishTime( 0.0f ), + m_bUpdateView( true ), + m_bEnabled( false ), + m_bFadeOut( false ) {} + + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void SetParameters( KeyValues *params ); + virtual void Enable( bool bEnable ) { m_bEnabled = bEnable; } + virtual bool IsEnabled( ) { return m_bEnabled; } + + virtual void Render( int x, int y, int w, int h ); + +private: + + inline unsigned char GetFadeAlpha( void ); + + CTextureReference m_StunTexture; + CMaterialReference m_EffectMaterial; + float m_flDuration; + float m_flFinishTime; + bool m_bUpdateView; + bool m_bEnabled; + bool m_bFadeOut; +}; + +ADD_SCREENSPACE_EFFECT( CEP2StunEffect, ep2_groggy ); + +#endif // EPISODIC_SCREENSPACEEFFECTS_H diff --git a/game/client/episodic/flesh_internal_material_proxy.cpp b/game/client/episodic/flesh_internal_material_proxy.cpp new file mode 100644 index 00000000..d5a1c8c6 --- /dev/null +++ b/game/client/episodic/flesh_internal_material_proxy.cpp @@ -0,0 +1,225 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// Purpose: +// +// $NoKeywords: $ +//=====================================================================================// +#include "cbase.h" +#include "ProxyEntity.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialVar.h" +#include "debugoverlay_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_FleshEffectTarget; +void AddFleshProxyTarget( C_FleshEffectTarget *pTarget ); +void RemoveFleshProxy( C_FleshEffectTarget *pTarget ); + +//============================================================================= +// +// Flesh effect target (used for orchestrating the "Invisible Alyx" moment +// +//============================================================================= + +class C_FleshEffectTarget : public C_BaseEntity +{ + DECLARE_CLASS( C_FleshEffectTarget, C_BaseEntity ); + +public: + float GetRadius( void ) + { + if ( m_flScaleTime <= 0.0f ) + return m_flRadius; + + float dt = ( gpGlobals->curtime - m_flScaleStartTime ); + if ( dt >= m_flScaleTime ) + return m_flRadius; + + return SimpleSplineRemapVal( ( dt / m_flScaleTime ), 0.0f, 1.0f, m_flStartRadius, m_flRadius ); + } + + virtual void Release( void ) + { + // Remove us from the list of targets + RemoveFleshProxy( this ); + } + + virtual void OnDataChanged( DataUpdateType_t updateType ) + { + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + // Add us to the list of flesh proxy targets + AddFleshProxyTarget( this ); + } + } + + float m_flRadius; + float m_flStartRadius; + float m_flScaleStartTime; + float m_flScaleTime; + + DECLARE_CLIENTCLASS(); +}; + +void RecvProxy_FleshEffect_Radius( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_FleshEffectTarget *pTarget = (C_FleshEffectTarget *) pStruct; + float flRadius = pData->m_Value.m_Float; + + //If changed, update our internal information + if ( pTarget->m_flRadius != flRadius ) + { + pTarget->m_flStartRadius = pTarget->m_flRadius; + pTarget->m_flScaleStartTime = gpGlobals->curtime; + } + + pTarget->m_flRadius = flRadius; +} + +IMPLEMENT_CLIENTCLASS_DT( C_FleshEffectTarget, DT_FleshEffectTarget, CFleshEffectTarget ) + RecvPropFloat( RECVINFO(m_flRadius), 0, RecvProxy_FleshEffect_Radius ), + RecvPropFloat( RECVINFO(m_flScaleTime) ), +END_RECV_TABLE() + +CUtlVector< C_FleshEffectTarget * > g_FleshProxyTargets; + +void AddFleshProxyTarget( C_FleshEffectTarget *pTarget ) +{ + // Take it! + g_FleshProxyTargets.AddToTail( pTarget ); +} + +void RemoveFleshProxy( C_FleshEffectTarget *pTarget ) +{ + int nIndex = g_FleshProxyTargets.Find( pTarget ); + if ( nIndex != g_FleshProxyTargets.InvalidIndex() ) + { + g_FleshProxyTargets.Remove( nIndex ); + } +} + +// $sineVar : name of variable that controls the FleshInterior level (float) +class CFleshInteriorMaterialProxy : public CEntityMaterialProxy +{ +public: + CFleshInteriorMaterialProxy(); + virtual ~CFleshInteriorMaterialProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( C_BaseEntity *pEntity ); + virtual IMaterial *GetMaterial(); + +private: + IMaterialVar *m_pMaterialParamFleshEffectCenterRadius1; + IMaterialVar *m_pMaterialParamFleshEffectCenterRadius2; + IMaterialVar *m_pMaterialParamFleshEffectCenterRadius3; + IMaterialVar *m_pMaterialParamFleshEffectCenterRadius4; + IMaterialVar *m_pMaterialParamFleshGlobalOpacity; + IMaterialVar *m_pMaterialParamFleshSubsurfaceTint; +}; + +CFleshInteriorMaterialProxy::CFleshInteriorMaterialProxy() +{ + m_pMaterialParamFleshEffectCenterRadius1 = NULL; + m_pMaterialParamFleshEffectCenterRadius2 = NULL; + m_pMaterialParamFleshEffectCenterRadius3 = NULL; + m_pMaterialParamFleshEffectCenterRadius4 = NULL; + m_pMaterialParamFleshGlobalOpacity = NULL; + m_pMaterialParamFleshSubsurfaceTint = NULL; +} + +CFleshInteriorMaterialProxy::~CFleshInteriorMaterialProxy() +{ + // Do nothing +} + +bool CFleshInteriorMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + bool bFoundVar = false; + + m_pMaterialParamFleshEffectCenterRadius1 = pMaterial->FindVar( "$FleshEffectCenterRadius1", &bFoundVar, false ); + if ( bFoundVar == false) + return false; + + m_pMaterialParamFleshEffectCenterRadius2 = pMaterial->FindVar( "$FleshEffectCenterRadius2", &bFoundVar, false ); + if ( bFoundVar == false) + return false; + + m_pMaterialParamFleshEffectCenterRadius3 = pMaterial->FindVar( "$FleshEffectCenterRadius3", &bFoundVar, false ); + if ( bFoundVar == false) + return false; + + m_pMaterialParamFleshEffectCenterRadius4 = pMaterial->FindVar( "$FleshEffectCenterRadius4", &bFoundVar, false ); + if ( bFoundVar == false) + return false; + + m_pMaterialParamFleshGlobalOpacity = pMaterial->FindVar( "$FleshGlobalOpacity", &bFoundVar, false ); + if ( bFoundVar == false) + return false; + + m_pMaterialParamFleshSubsurfaceTint = pMaterial->FindVar( "$FleshSubsurfaceTint", &bFoundVar, false ); + if ( bFoundVar == false) + return false; + + return true; +} + +void CFleshInteriorMaterialProxy::OnBind( C_BaseEntity *pEnt ) +{ + IMaterialVar *pParams[] = + { + m_pMaterialParamFleshEffectCenterRadius1, + m_pMaterialParamFleshEffectCenterRadius2, + m_pMaterialParamFleshEffectCenterRadius3, + m_pMaterialParamFleshEffectCenterRadius4 + }; + + float vEffectCenterRadius[4]; + for ( int i = 0; i < ARRAYSIZE( pParams ); i++ ) + { + if ( i < g_FleshProxyTargets.Count() ) + { + // Setup the target + if ( g_FleshProxyTargets[i]->IsAbsQueriesValid() == false ) + continue; + + Vector vecAbsOrigin = g_FleshProxyTargets[i]->GetAbsOrigin(); + vEffectCenterRadius[0] = vecAbsOrigin.x; + vEffectCenterRadius[1] = vecAbsOrigin.y; + vEffectCenterRadius[2] = vecAbsOrigin.z; + vEffectCenterRadius[3] = g_FleshProxyTargets[i]->GetRadius(); + } + else + { + // Clear the target + vEffectCenterRadius[0] = vEffectCenterRadius[1] = vEffectCenterRadius[2] = vEffectCenterRadius[3] = 0.0f; + } + + // Set the value either way + pParams[i]->SetVecValue( vEffectCenterRadius, 4 ); + } + + // Subsurface texture. NOTE: This texture bleeds through the color of the flesh texture so expect + // to have to set this brighter than white to really see the subsurface texture glow through. + if ( m_pMaterialParamFleshSubsurfaceTint != NULL ) + { + float vSubsurfaceTintColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // !!! Test code. REPLACE ME! + // vSubsurfaceTintColor[0] = vSubsurfaceTintColor[1] = vSubsurfaceTintColor[2] = sinf( gpGlobals->curtime * 3.0f ) + 1.0f; // * 0.5f + 0.5f; + + m_pMaterialParamFleshSubsurfaceTint->SetVecValue( vSubsurfaceTintColor, 4 ); + } +} + +IMaterial *CFleshInteriorMaterialProxy::GetMaterial() +{ + if ( m_pMaterialParamFleshEffectCenterRadius1 == NULL) + return NULL; + + return m_pMaterialParamFleshEffectCenterRadius1->GetOwningMaterial(); +} + +EXPOSE_INTERFACE( CFleshInteriorMaterialProxy, IMaterialProxy, "FleshInterior" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/game/client/flashlighteffect.cpp b/game/client/flashlighteffect.cpp new file mode 100644 index 00000000..f654a3e8 --- /dev/null +++ b/game/client/flashlighteffect.cpp @@ -0,0 +1,540 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" +#include "flashlighteffect.h" +#include "dlight.h" +#include "iefx.h" +#include "iviewrender.h" +#include "view.h" +#include "engine/ivdebugoverlay.h" +#include "tier0/vprof.h" +#include "tier1/KeyValues.h" +#include "toolframework_client.h" + +#ifdef HL2_CLIENT_DLL +#include "c_basehlplayer.h" +#endif // HL2_CLIENT_DLL + +#if defined( _X360 ) +extern ConVar r_flashlightdepthres; +#else +extern ConVar r_flashlightdepthres; +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar r_flashlightdepthtexture; + +void r_newflashlightCallback_f( IConVar *pConVar, const char *pOldString, float flOldValue ); + +static ConVar r_newflashlight( "r_newflashlight", "1", FCVAR_CHEAT, "", r_newflashlightCallback_f ); +static ConVar r_swingflashlight( "r_swingflashlight", "1", FCVAR_CHEAT ); +static ConVar r_flashlightlockposition( "r_flashlightlockposition", "0", FCVAR_CHEAT ); +static ConVar r_flashlightfov( "r_flashlightfov", "45.0", FCVAR_CHEAT ); +static ConVar r_flashlightoffsetx( "r_flashlightoffsetx", "10.0", FCVAR_CHEAT ); +static ConVar r_flashlightoffsety( "r_flashlightoffsety", "-20.0", FCVAR_CHEAT ); +static ConVar r_flashlightoffsetz( "r_flashlightoffsetz", "24.0", FCVAR_CHEAT ); +static ConVar r_flashlightnear( "r_flashlightnear", "4.0", FCVAR_CHEAT ); +static ConVar r_flashlightfar( "r_flashlightfar", "750.0", FCVAR_CHEAT ); +static ConVar r_flashlightconstant( "r_flashlightconstant", "0.0", FCVAR_CHEAT ); +static ConVar r_flashlightlinear( "r_flashlightlinear", "100.0", FCVAR_CHEAT ); +static ConVar r_flashlightquadratic( "r_flashlightquadratic", "0.0", FCVAR_CHEAT ); +static ConVar r_flashlightvisualizetrace( "r_flashlightvisualizetrace", "0", FCVAR_CHEAT ); +static ConVar r_flashlightambient( "r_flashlightambient", "0.0", FCVAR_CHEAT ); +static ConVar r_flashlightshadowatten( "r_flashlightshadowatten", "0.35", FCVAR_CHEAT ); +static ConVar r_flashlightladderdist( "r_flashlightladderdist", "40.0", FCVAR_CHEAT ); +static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT ); +static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.0005", FCVAR_CHEAT ); + + +void r_newflashlightCallback_f( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + if( engine->GetDXSupportLevel() < 70 ) + { + r_newflashlight.SetValue( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : nEntIndex - The m_nEntIndex of the client entity that is creating us. +// vecPos - The position of the light emitter. +// vecDir - The direction of the light emission. +//----------------------------------------------------------------------------- +CFlashlightEffect::CFlashlightEffect(int nEntIndex) +{ + m_FlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + m_nEntIndex = nEntIndex; + + m_bIsOn = false; + m_pPointLight = NULL; + if( engine->GetDXSupportLevel() < 70 ) + { + r_newflashlight.SetValue( 0 ); + } + + if ( g_pMaterialSystemHardwareConfig->SupportsBorderColor() ) + { + m_FlashlightTexture.Init( "effects/flashlight_border", TEXTURE_GROUP_OTHER, true ); + } + else + { + m_FlashlightTexture.Init( "effects/flashlight001", TEXTURE_GROUP_OTHER, true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFlashlightEffect::~CFlashlightEffect() +{ + LightOff(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlashlightEffect::TurnOn() +{ + m_bIsOn = true; + m_flDistMod = 1.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlashlightEffect::TurnOff() +{ + if (m_bIsOn) + { + m_bIsOn = false; + LightOff(); + } +} + +// Custom trace filter that skips the player and the view model. +// If we don't do this, we'll end up having the light right in front of us all +// the time. +class CTraceFilterSkipPlayerAndViewModel : public CTraceFilter +{ +public: + virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) + { + // Test against the vehicle too? + // FLASHLIGHTFIXME: how do you know that you are actually inside of the vehicle? + C_BaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); + if ( !pEntity ) + return true; + + if ( ( dynamic_cast( pEntity ) != NULL ) || + ( dynamic_cast( pEntity ) != NULL ) || + pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS || + pEntity->GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS ) + { + return false; + } + + return true; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Do the headlight +//----------------------------------------------------------------------------- +void CFlashlightEffect::UpdateLightNew(const Vector &vecPos, const Vector &vecForward, const Vector &vecRight, const Vector &vecUp ) +{ + VPROF_BUDGET( "CFlashlightEffect::UpdateLightNew", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + FlashlightState_t state; + + // We will lock some of the flashlight params if player is on a ladder, to prevent oscillations due to the trace-rays + bool bPlayerOnLadder = ( C_BasePlayer::GetLocalPlayer()->GetMoveType() == MOVETYPE_LADDER ); + + const float flEpsilon = 0.1f; // Offset flashlight position along vecUp + const float flDistCutoff = 128.0f; + const float flDistDrag = 0.2; + + CTraceFilterSkipPlayerAndViewModel traceFilter; + float flOffsetY = r_flashlightoffsety.GetFloat(); + + if( r_swingflashlight.GetBool() ) + { + // This projects the view direction backwards, attempting to raise the vertical + // offset of the flashlight, but only when the player is looking down. + Vector vecSwingLight = vecPos + vecForward * -12.0f; + if( vecSwingLight.z > vecPos.z ) + { + flOffsetY += (vecSwingLight.z - vecPos.z); + } + } + + Vector vOrigin = vecPos + flOffsetY * vecUp; + + // Not on ladder...trace a hull + if ( !bPlayerOnLadder ) + { + trace_t pmOriginTrace; + UTIL_TraceHull( vecPos, vOrigin, Vector(-4, -4, -4), Vector(4, 4, 4), MASK_SOLID & ~(CONTENTS_HITBOX), &traceFilter, &pmOriginTrace ); + + if ( pmOriginTrace.DidHit() ) + { + vOrigin = vecPos; + } + } + else // on ladder...skip the above hull trace + { + vOrigin = vecPos; + } + + // Now do a trace along the flashlight direction to ensure there is nothing within range to pull back from + int iMask = MASK_OPAQUE_AND_NPCS; + iMask &= ~CONTENTS_HITBOX; + iMask |= CONTENTS_WINDOW; + + Vector vTarget = vecPos + vecForward * r_flashlightfar.GetFloat(); + + // Work with these local copies of the basis for the rest of the function + Vector vDir = vTarget - vOrigin; + Vector vRight = vecRight; + Vector vUp = vecUp; + VectorNormalize( vDir ); + VectorNormalize( vRight ); + VectorNormalize( vUp ); + + // Orthonormalize the basis, since the flashlight texture projection will require this later... + vUp -= DotProduct( vDir, vUp ) * vDir; + VectorNormalize( vUp ); + vRight -= DotProduct( vDir, vRight ) * vDir; + VectorNormalize( vRight ); + vRight -= DotProduct( vUp, vRight ) * vUp; + VectorNormalize( vRight ); + + AssertFloatEquals( DotProduct( vDir, vRight ), 0.0f, 1e-3 ); + AssertFloatEquals( DotProduct( vDir, vUp ), 0.0f, 1e-3 ); + AssertFloatEquals( DotProduct( vRight, vUp ), 0.0f, 1e-3 ); + + trace_t pmDirectionTrace; + UTIL_TraceHull( vOrigin, vTarget, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ), iMask, &traceFilter, &pmDirectionTrace ); + + if ( r_flashlightvisualizetrace.GetBool() == true ) + { + debugoverlay->AddBoxOverlay( pmDirectionTrace.endpos, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ), QAngle( 0, 0, 0 ), 0, 0, 255, 16, 0 ); + debugoverlay->AddLineOverlay( vOrigin, pmDirectionTrace.endpos, 255, 0, 0, false, 0 ); + } + + float flDist = (pmDirectionTrace.endpos - vOrigin).Length(); + if ( flDist < flDistCutoff ) + { + // We have an intersection with our cutoff range + // Determine how far to pull back, then trace to see if we are clear + float flPullBackDist = bPlayerOnLadder ? r_flashlightladderdist.GetFloat() : flDistCutoff - flDist; // Fixed pull-back distance if on ladder + m_flDistMod = Lerp( flDistDrag, m_flDistMod, flPullBackDist ); + + if ( !bPlayerOnLadder ) + { + trace_t pmBackTrace; + UTIL_TraceHull( vOrigin, vOrigin - vDir*(flPullBackDist-flEpsilon), Vector( -4, -4, -4 ), Vector( 4, 4, 4 ), iMask, &traceFilter, &pmBackTrace ); + if( pmBackTrace.DidHit() ) + { + // We have an intersection behind us as well, so limit our m_flDistMod + float flMaxDist = (pmBackTrace.endpos - vOrigin).Length() - flEpsilon; + if( m_flDistMod > flMaxDist ) + m_flDistMod = flMaxDist; + } + } + } + else + { + m_flDistMod = Lerp( flDistDrag, m_flDistMod, 0.0f ); + } + vOrigin = vOrigin - vDir * m_flDistMod; + + state.m_vecLightOrigin = vOrigin; + + BasisToQuaternion( vDir, vRight, vUp, state.m_quatOrientation ); + + state.m_fQuadraticAtten = r_flashlightquadratic.GetFloat(); + + bool bFlicker = false; + +#ifdef HL2_EPISODIC + C_BaseHLPlayer *pPlayer = (C_BaseHLPlayer *)C_BasePlayer::GetLocalPlayer(); + if ( pPlayer ) + { + float flBatteryPower = ( pPlayer->m_HL2Local.m_flFlashBattery >= 0.0f ) ? ( pPlayer->m_HL2Local.m_flFlashBattery ) : pPlayer->m_HL2Local.m_flSuitPower; + if ( flBatteryPower <= 10.0f ) + { + float flScale; + if ( flBatteryPower >= 0.0f ) + { + flScale = ( flBatteryPower <= 4.5f ) ? SimpleSplineRemapVal( flBatteryPower, 4.5f, 0.0f, 1.0f, 0.0f ) : 1.0f; + } + else + { + flScale = SimpleSplineRemapVal( flBatteryPower, 10.0f, 4.8f, 1.0f, 0.0f ); + } + + flScale = clamp( flScale, 0.0f, 1.0f ); + + if ( flScale < 0.35f ) + { + float flFlicker = cosf( gpGlobals->curtime * 6.0f ) * sinf( gpGlobals->curtime * 15.0f ); + + if ( flFlicker > 0.25f && flFlicker < 0.75f ) + { + // On + state.m_fLinearAtten = r_flashlightlinear.GetFloat() * flScale; + } + else + { + // Off + state.m_fLinearAtten = 0.0f; + } + } + else + { + float flNoise = cosf( gpGlobals->curtime * 7.0f ) * sinf( gpGlobals->curtime * 25.0f ); + state.m_fLinearAtten = r_flashlightlinear.GetFloat() * flScale + 1.5f * flNoise; + } + + state.m_fHorizontalFOVDegrees = r_flashlightfov.GetFloat() - ( 16.0f * (1.0f-flScale) ); + state.m_fVerticalFOVDegrees = r_flashlightfov.GetFloat() - ( 16.0f * (1.0f-flScale) ); + + bFlicker = true; + } + } +#endif // HL2_EPISODIC + + if ( bFlicker == false ) + { + state.m_fLinearAtten = r_flashlightlinear.GetFloat(); + state.m_fHorizontalFOVDegrees = r_flashlightfov.GetFloat(); + state.m_fVerticalFOVDegrees = r_flashlightfov.GetFloat(); + } + + state.m_fConstantAtten = r_flashlightconstant.GetFloat(); + state.m_Color[0] = 1.0f; + state.m_Color[1] = 1.0f; + state.m_Color[2] = 1.0f; + state.m_Color[3] = r_flashlightambient.GetFloat(); + state.m_NearZ = r_flashlightnear.GetFloat() + m_flDistMod; // Push near plane out so that we don't clip the world when the flashlight pulls back + state.m_FarZ = r_flashlightfar.GetFloat(); + state.m_bEnableShadows = r_flashlightdepthtexture.GetBool(); + state.m_flShadowMapResolution = r_flashlightdepthres.GetInt(); + + state.m_pSpotlightTexture = m_FlashlightTexture; + state.m_nSpotlightTextureFrame = 0; + + state.m_flShadowAtten = r_flashlightshadowatten.GetFloat(); + state.m_flShadowSlopeScaleDepthBias = mat_slopescaledepthbias_shadowmap.GetFloat(); + state.m_flShadowDepthBias = mat_depthbias_shadowmap.GetFloat(); + + if( m_FlashlightHandle == CLIENTSHADOW_INVALID_HANDLE ) + { + m_FlashlightHandle = g_pClientShadowMgr->CreateFlashlight( state ); + } + else + { + if( !r_flashlightlockposition.GetBool() ) + { + g_pClientShadowMgr->UpdateFlashlightState( m_FlashlightHandle, state ); + } + } + + g_pClientShadowMgr->UpdateProjectedTexture( m_FlashlightHandle, true ); + + // Kill the old flashlight method if we have one. + LightOffOld(); + +#ifndef NO_TOOLFRAMEWORK + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "FlashlightState" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetInt( "entindex", m_nEntIndex ); + msg->SetInt( "flashlightHandle", m_FlashlightHandle ); + msg->SetPtr( "flashlightState", &state ); + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Do the headlight +//----------------------------------------------------------------------------- +void CFlashlightEffect::UpdateLightOld(const Vector &vecPos, const Vector &vecDir, int nDistance) +{ + if ( !m_pPointLight || ( m_pPointLight->key != m_nEntIndex )) + { + // Set up the environment light + m_pPointLight = effects->CL_AllocDlight(m_nEntIndex); + m_pPointLight->flags = 0.0f; + m_pPointLight->radius = 80; + } + + // For bumped lighting + VectorCopy(vecDir, m_pPointLight->m_Direction); + + Vector end; + end = vecPos + nDistance * vecDir; + + // Trace a line outward, skipping the player model and the view model. + trace_t pm; + CTraceFilterSkipPlayerAndViewModel traceFilter; + UTIL_TraceLine( vecPos, end, MASK_ALL, &traceFilter, &pm ); + VectorCopy( pm.endpos, m_pPointLight->origin ); + + float falloff = pm.fraction * nDistance; + + if ( falloff < 500 ) + falloff = 1.0; + else + falloff = 500.0 / falloff; + + falloff *= falloff; + + m_pPointLight->radius = 80; + m_pPointLight->color.r = m_pPointLight->color.g = m_pPointLight->color.b = 255 * falloff; + m_pPointLight->color.exponent = 0; + + // Make it live for a bit + m_pPointLight->die = gpGlobals->curtime + 0.2f; + + // Update list of surfaces we influence + render->TouchLight( m_pPointLight ); + + // kill the new flashlight if we have one + LightOffNew(); +} + +//----------------------------------------------------------------------------- +// Purpose: Do the headlight +//----------------------------------------------------------------------------- +void CFlashlightEffect::UpdateLight(const Vector &vecPos, const Vector &vecDir, const Vector &vecRight, const Vector &vecUp, int nDistance) +{ + if ( !m_bIsOn ) + { + return; + } + if( r_newflashlight.GetBool() ) + { + UpdateLightNew( vecPos, vecDir, vecRight, vecUp ); + } + else + { + UpdateLightOld( vecPos, vecDir, nDistance ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlashlightEffect::LightOffNew() +{ +#ifndef NO_TOOLFRAMEWORK + if ( clienttools->IsInRecordingMode() ) + { + KeyValues *msg = new KeyValues( "FlashlightState" ); + msg->SetFloat( "time", gpGlobals->curtime ); + msg->SetInt( "entindex", m_nEntIndex ); + msg->SetInt( "flashlightHandle", m_FlashlightHandle ); + msg->SetPtr( "flashlightState", NULL ); + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); + } +#endif + + // Clear out the light + if( m_FlashlightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + g_pClientShadowMgr->DestroyFlashlight( m_FlashlightHandle ); + m_FlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlashlightEffect::LightOffOld() +{ + if ( m_pPointLight && ( m_pPointLight->key == m_nEntIndex ) ) + { + m_pPointLight->die = gpGlobals->curtime; + m_pPointLight = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlashlightEffect::LightOff() +{ + LightOffOld(); + LightOffNew(); +} + +CHeadlightEffect::CHeadlightEffect() +{ + +} + +CHeadlightEffect::~CHeadlightEffect() +{ + +} + +void CHeadlightEffect::UpdateLight( const Vector &vecPos, const Vector &vecDir, const Vector &vecRight, const Vector &vecUp, int nDistance ) +{ + if ( IsOn() == false ) + return; + + FlashlightState_t state; + Vector basisX, basisY, basisZ; + basisX = vecDir; + basisY = vecRight; + basisZ = vecUp; + VectorNormalize(basisX); + VectorNormalize(basisY); + VectorNormalize(basisZ); + + BasisToQuaternion( basisX, basisY, basisZ, state.m_quatOrientation ); + + state.m_vecLightOrigin = vecPos; + + state.m_fHorizontalFOVDegrees = 45.0f; + state.m_fVerticalFOVDegrees = 30.0f; + state.m_fQuadraticAtten = r_flashlightquadratic.GetFloat(); + state.m_fLinearAtten = r_flashlightlinear.GetFloat(); + state.m_fConstantAtten = r_flashlightconstant.GetFloat(); + state.m_Color[0] = 1.0f; + state.m_Color[1] = 1.0f; + state.m_Color[2] = 1.0f; + state.m_Color[3] = r_flashlightambient.GetFloat(); + state.m_NearZ = r_flashlightnear.GetFloat(); + state.m_FarZ = r_flashlightfar.GetFloat(); + state.m_bEnableShadows = true; + state.m_pSpotlightTexture = m_FlashlightTexture; + state.m_nSpotlightTextureFrame = 0; + + if( GetFlashlightHandle() == CLIENTSHADOW_INVALID_HANDLE ) + { + SetFlashlightHandle( g_pClientShadowMgr->CreateFlashlight( state ) ); + } + else + { + g_pClientShadowMgr->UpdateFlashlightState( GetFlashlightHandle(), state ); + } + + g_pClientShadowMgr->UpdateProjectedTexture( GetFlashlightHandle(), true ); +} + diff --git a/game/client/flashlighteffect.h b/game/client/flashlighteffect.h new file mode 100644 index 00000000..8b072a2a --- /dev/null +++ b/game/client/flashlighteffect.h @@ -0,0 +1,64 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FLASHLIGHTEFFECT_H +#define FLASHLIGHTEFFECT_H +#ifdef _WIN32 +#pragma once +#endif + +struct dlight_t; + + +class CFlashlightEffect +{ +public: + + CFlashlightEffect(int nEntIndex = 0); + ~CFlashlightEffect(); + + virtual void UpdateLight(const Vector &vecPos, const Vector &vecDir, const Vector &vecRight, const Vector &vecUp, int nDistance); + void TurnOn(); + void TurnOff(); + bool IsOn( void ) { return m_bIsOn; } + + ClientShadowHandle_t GetFlashlightHandle( void ) { return m_FlashlightHandle; } + void SetFlashlightHandle( ClientShadowHandle_t Handle ) { m_FlashlightHandle = Handle; } + +protected: + + void LightOff(); + void LightOffOld(); + void LightOffNew(); + + void UpdateLightNew(const Vector &vecPos, const Vector &vecDir, const Vector &vecRight, const Vector &vecUp); + void UpdateLightOld(const Vector &vecPos, const Vector &vecDir, int nDistance); + + bool m_bIsOn; + int m_nEntIndex; + ClientShadowHandle_t m_FlashlightHandle; + + // Vehicle headlight dynamic light pointer + dlight_t *m_pPointLight; + float m_flDistMod; + + // Texture for flashlight + CTextureReference m_FlashlightTexture; +}; + +class CHeadlightEffect : public CFlashlightEffect +{ +public: + + CHeadlightEffect(); + ~CHeadlightEffect(); + + virtual void UpdateLight(const Vector &vecPos, const Vector &vecDir, const Vector &vecRight, const Vector &vecUp, int nDistance); +}; + + + +#endif // FLASHLIGHTEFFECT_H diff --git a/game/client/fontabc.h b/game/client/fontabc.h new file mode 100644 index 00000000..abed90b8 --- /dev/null +++ b/game/client/fontabc.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FONTABC_H +#define FONTABC_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct +{ + int abcA, abcB, abcC; + int total; +} FONTABC; + +#endif // FONTABC_H diff --git a/game/client/functionproxy.cpp b/game/client/functionproxy.cpp new file mode 100644 index 00000000..1e2830a4 --- /dev/null +++ b/game/client/functionproxy.cpp @@ -0,0 +1,259 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "FunctionProxy.h" +#include +#include "materialsystem/IMaterialVar.h" +#include "materialsystem/IMaterial.h" +#include "IClientRenderable.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Helper class to deal with floating point inputs +//----------------------------------------------------------------------------- +bool CFloatInput::Init( IMaterial *pMaterial, KeyValues *pKeyValues, const char *pKeyName, float flDefault ) +{ + m_pFloatVar = NULL; + KeyValues *pSection = pKeyValues->FindKey( pKeyName ); + if (pSection) + { + if (pSection->GetDataType() == KeyValues::TYPE_STRING) + { + const char *pVarName = pSection->GetString(); + + // Look for numbers... + float flValue; + int nCount = sscanf( pVarName, "%f", &flValue ); + if (nCount == 1) + { + m_flValue = flValue; + return true; + } + + // Look for array specification... + char pTemp[256]; + if (strchr(pVarName, '[')) + { + // strip off the array... + Q_strncpy( pTemp, pVarName, 256 ); + char *pArray = strchr( pTemp, '[' ); + *pArray++ = 0; + + char* pIEnd; + m_FloatVecComp = strtol( pArray, &pIEnd, 10 ); + + // Use the version without the array... + pVarName = pTemp; + } + else + { + m_FloatVecComp = -1; + } + + bool bFoundVar; + m_pFloatVar = pMaterial->FindVar( pVarName, &bFoundVar, true ); + if (!bFoundVar) + return false; + } + else + { + m_flValue = pSection->GetFloat(); + } + } + else + { + m_flValue = flDefault; + } + return true; +} + +float CFloatInput::GetFloat() const +{ + if (!m_pFloatVar) + return m_flValue; + + if( m_FloatVecComp < 0 ) + return m_pFloatVar->GetFloatValue(); + + int iVecSize = m_pFloatVar->VectorSize(); + if ( m_FloatVecComp >= iVecSize ) + return 0; + + float v[4]; + m_pFloatVar->GetVecValue( v, iVecSize ); + return v[m_FloatVecComp]; +} + + + +//----------------------------------------------------------------------------- +// +// Result proxy; a result (with vector friendliness) +// +//----------------------------------------------------------------------------- + +CResultProxy::CResultProxy() : m_pResult(0) +{ +} + +CResultProxy::~CResultProxy() +{ +} + + +bool CResultProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + char const* pResult = pKeyValues->GetString( "resultVar" ); + if( !pResult ) + return false; + + // Look for array specification... + char pTemp[256]; + if (strchr(pResult, '[')) + { + // strip off the array... + Q_strncpy( pTemp, pResult, 256 ); + char *pArray = strchr( pTemp, '[' ); + *pArray++ = 0; + + char* pIEnd; + m_ResultVecComp = strtol( pArray, &pIEnd, 10 ); + + // Use the version without the array... + pResult = pTemp; + } + else + { + m_ResultVecComp = -1; + } + + bool foundVar; + m_pResult = pMaterial->FindVar( pResult, &foundVar, true ); + if( !foundVar ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// A little code to allow us to set single components of vectors +//----------------------------------------------------------------------------- +void CResultProxy::SetFloatResult( float result ) +{ + if (m_pResult->GetType() == MATERIAL_VAR_TYPE_VECTOR) + { + if ( m_ResultVecComp >= 0 ) + { + m_pResult->SetVecComponentValue( result, m_ResultVecComp ); + } + else + { + float v[4]; + int vecSize = m_pResult->VectorSize(); + + for (int i = 0; i < vecSize; ++i) + v[i] = result; + + m_pResult->SetVecValue( v, vecSize ); + } + } + else + { + m_pResult->SetFloatValue( result ); + } +} + +C_BaseEntity *CResultProxy::BindArgToEntity( void *pArg ) +{ + IClientRenderable *pRend = (IClientRenderable *)pArg; + return pRend->GetIClientUnknown()->GetBaseEntity(); +} + +IMaterial *CResultProxy::GetMaterial() +{ + return m_pResult->GetOwningMaterial(); +} + + +//----------------------------------------------------------------------------- +// +// Base functional proxy; two sources (one is optional) and a result +// +//----------------------------------------------------------------------------- + +CFunctionProxy::CFunctionProxy() : m_pSrc1(0), m_pSrc2(0) +{ +} + +CFunctionProxy::~CFunctionProxy() +{ +} + + +bool CFunctionProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if (!CResultProxy::Init( pMaterial, pKeyValues )) + return false; + + char const* pSrcVar1 = pKeyValues->GetString( "srcVar1" ); + if( !pSrcVar1 ) + return false; + + bool foundVar; + m_pSrc1 = pMaterial->FindVar( pSrcVar1, &foundVar, true ); + if( !foundVar ) + return false; + + // Source 2 is optional, some math ops may be single-input + char const* pSrcVar2 = pKeyValues->GetString( "srcVar2" ); + if( pSrcVar2 && (*pSrcVar2) ) + { + m_pSrc2 = pMaterial->FindVar( pSrcVar2, &foundVar, true ); + if( !foundVar ) + return false; + } + else + { + m_pSrc2 = 0; + } + + return true; +} + + +void CFunctionProxy::ComputeResultType( MaterialVarType_t& resultType, int& vecSize ) +{ + // Feh, this is ugly. Basically, don't change the result type + // unless it's undefined. + resultType = m_pResult->GetType(); + if (resultType == MATERIAL_VAR_TYPE_VECTOR) + { + if (m_ResultVecComp >= 0) + resultType = MATERIAL_VAR_TYPE_FLOAT; + vecSize = m_pResult->VectorSize(); + } + else if (resultType == MATERIAL_VAR_TYPE_UNDEFINED) + { + resultType = m_pSrc1->GetType(); + if (resultType == MATERIAL_VAR_TYPE_VECTOR) + { + vecSize = m_pSrc1->VectorSize(); + } + else if ((resultType == MATERIAL_VAR_TYPE_UNDEFINED) && m_pSrc2) + { + resultType = m_pSrc2->GetType(); + if (resultType == MATERIAL_VAR_TYPE_VECTOR) + { + vecSize = m_pSrc2->VectorSize(); + } + } + } +} + diff --git a/game/client/functionproxy.h b/game/client/functionproxy.h new file mode 100644 index 00000000..5db17ceb --- /dev/null +++ b/game/client/functionproxy.h @@ -0,0 +1,75 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: These are a couple of base proxy classes to help us with +// getting/setting source/result material vars +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FUNCTIONPROXY_H +#define FUNCTIONPROXY_H + +#include "materialsystem/IMaterialProxy.h" + + +class IMaterialVar; +enum MaterialVarType_t; +class C_BaseEntity; + + +//----------------------------------------------------------------------------- +// Helper class to deal with floating point inputs +//----------------------------------------------------------------------------- +class CFloatInput +{ +public: + bool Init( IMaterial *pMaterial, KeyValues *pKeyValues, const char *pKeyName, float flDefault = 0.0f ); + float GetFloat() const; + +private: + float m_flValue; + IMaterialVar *m_pFloatVar; + int m_FloatVecComp; +}; + + +//----------------------------------------------------------------------------- +// Result proxy; a result (with vector friendliness) +//----------------------------------------------------------------------------- +class CResultProxy : public IMaterialProxy +{ +public: + CResultProxy(); + virtual ~CResultProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void Release( void ) { delete this; } + virtual IMaterial *GetMaterial(); + +protected: + C_BaseEntity *BindArgToEntity( void *pArg ); + void SetFloatResult( float result ); + + IMaterialVar* m_pResult; + int m_ResultVecComp; +}; + + +//----------------------------------------------------------------------------- +// Base functional proxy; two sources (one is optional) and a result +//----------------------------------------------------------------------------- +class CFunctionProxy : public CResultProxy +{ +public: + CFunctionProxy(); + virtual ~CFunctionProxy(); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + +protected: + void ComputeResultType( MaterialVarType_t& resultType, int& vecSize ); + + IMaterialVar* m_pSrc1; + IMaterialVar* m_pSrc2; +}; + +#endif // FUNCTIONPROXY_H + diff --git a/game/client/fx.cpp b/game/client/fx.cpp new file mode 100644 index 00000000..24321291 --- /dev/null +++ b/game/client/fx.cpp @@ -0,0 +1,1318 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "engine/IEngineSound.h" +#include "particles_simple.h" +#include "particles_localspace.h" +#include "dlight.h" +#include "iefx.h" +#include "clientsideeffects.h" +#include "ClientEffectPrecacheSystem.h" +#include "glow_overlay.h" +#include "effect_dispatch_data.h" +#include "c_te_effect_dispatch.h" +#include "tier0/vprof.h" +#include "tier1/keyvalues.h" +#include "effect_color_tables.h" +#include "iviewrender_beams.h" +#include "view.h" +#include "ieffects.h" +#include "fx.h" +#include "c_te_legacytempents.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//Precahce the effects +#ifndef TF_CLIENT_DLL +CLIENTEFFECT_REGISTER_BEGIN( PrecacheMuzzleFlash ) +CLIENTEFFECT_MATERIAL( "effects/muzzleflash1" ) +CLIENTEFFECT_MATERIAL( "effects/muzzleflash2" ) +CLIENTEFFECT_MATERIAL( "effects/muzzleflash3" ) +CLIENTEFFECT_MATERIAL( "effects/muzzleflash4" ) +CLIENTEFFECT_MATERIAL( "effects/bluemuzzle" ) +CLIENTEFFECT_MATERIAL( "effects/gunshipmuzzle" ) +CLIENTEFFECT_MATERIAL( "effects/gunshiptracer" ) +CLIENTEFFECT_MATERIAL( "effects/huntertracer" ) +CLIENTEFFECT_MATERIAL( "sprites/physcannon_bluelight2" ) +CLIENTEFFECT_MATERIAL( "effects/combinemuzzle1" ) +CLIENTEFFECT_MATERIAL( "effects/combinemuzzle2" ) +CLIENTEFFECT_MATERIAL( "effects/combinemuzzle2_nocull" ) +CLIENTEFFECT_REGISTER_END() +#endif + +//Whether or not we should emit a dynamic light +ConVar muzzleflash_light( "muzzleflash_light", "1", FCVAR_ARCHIVE ); + +extern void FX_TracerSound( const Vector &start, const Vector &end, int iTracerType ); + + +//=================================================================== +//=================================================================== +class CImpactOverlay : public CWarpOverlay +{ +public: + + virtual bool Update( void ) + { + m_flLifetime += gpGlobals->frametime; + + const float flTotalLifetime = 0.1f; + + if ( m_flLifetime < flTotalLifetime ) + { + float flColorScale = 1.0f - ( m_flLifetime / flTotalLifetime ); + + for( int i=0; i < m_nSprites; i++ ) + { + m_Sprites[i].m_vColor = m_vBaseColors[i] * flColorScale; + + m_Sprites[i].m_flHorzSize += 1.0f * gpGlobals->frametime; + m_Sprites[i].m_flVertSize += 1.0f * gpGlobals->frametime; + } + + return true; + } + + return false; + } + +public: + + float m_flLifetime; + Vector m_vBaseColors[MAX_SUN_LAYERS]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Play random ricochet sound +// Input : *pos - +//----------------------------------------------------------------------------- +void FX_RicochetSound( const Vector& pos ) +{ + Vector org = pos; + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "FX_RicochetSound.Ricochet", &org ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : entityIndex - +// attachmentIndex - +// *origin - +// *angles - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FX_GetAttachmentTransform( ClientEntityHandle_t hEntity, int attachmentIndex, Vector *origin, QAngle *angles ) +{ + // Validate our input + if ( ( hEntity == INVALID_EHANDLE_INDEX ) || ( attachmentIndex < 1 ) ) + { + if ( origin != NULL ) + { + *origin = vec3_origin; + } + + if ( angles != NULL ) + { + *angles = QAngle(0,0,0); + } + + return false; + } + + // Get the actual entity + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( hEntity ); + if ( pRenderable ) + { + Vector attachOrigin; + QAngle attachAngles; + + // Find the attachment's matrix + pRenderable->GetAttachment( attachmentIndex, attachOrigin, attachAngles ); + + if ( origin != NULL ) + { + *origin = attachOrigin; + } + + if ( angles != NULL ) + { + *angles = attachAngles; + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : entityIndex - +// attachmentIndex - +// &transform - +//----------------------------------------------------------------------------- +bool FX_GetAttachmentTransform( ClientEntityHandle_t hEntity, int attachmentIndex, matrix3x4_t &transform ) +{ + Vector origin; + QAngle angles; + + if ( FX_GetAttachmentTransform( hEntity, attachmentIndex, &origin, &angles ) ) + { + AngleMatrix( angles, origin, transform ); + return true; + } + + // Entity doesn't exist + SetIdentityMatrix( transform ); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void FX_MuzzleEffect( + const Vector &origin, + const QAngle &angles, + float scale, + ClientEntityHandle_t hEntity, + unsigned char *pFlashColor, + bool bOneFrame ) +{ + VPROF_BUDGET( "FX_MuzzleEffect", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + CSmartPtr pSimple = CSimpleEmitter::Create( "MuzzleFlash" ); + pSimple->SetSortOrigin( origin ); + + SimpleParticle *pParticle; + Vector forward, offset; + + AngleVectors( angles, &forward ); + float flScale = random->RandomFloat( scale-0.25f, scale+0.25f ); + + if ( flScale < 0.5f ) + { + flScale = 0.5f; + } + else if ( flScale > 8.0f ) + { + flScale = 8.0f; + } + + // + // Flash + // + + int i; + for ( i = 1; i < 9; i++ ) + { + offset = origin + (forward * (i*2.0f*scale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/muzzleflash%d", random->RandomInt(1,4) ) ), offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = /*bOneFrame ? 0.0001f : */0.1f; + + pParticle->m_vecVelocity.Init(); + + if ( !pFlashColor ) + { + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + } + else + { + pParticle->m_uchColor[0] = pFlashColor[0]; + pParticle->m_uchColor[1] = pFlashColor[1]; + pParticle->m_uchColor[2] = pFlashColor[2]; + } + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 128; + + pParticle->m_uchStartSize = (random->RandomFloat( 6.0f, 9.0f ) * (12-(i))/9) * flScale; + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + // + // Smoke + // + + /* + for ( i = 0; i < 4; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "particle/particle_smokegrenade" ), origin ); + + if ( pParticle == NULL ) + return; + + alpha = random->RandomInt( 32, 84 ); + color = random->RandomInt( 64, 164 ); + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + pParticle->m_vecVelocity.Random( -0.5f, 0.5f ); + pParticle->m_vecVelocity += forward; + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= random->RandomFloat( 16.0f, 32.0f ); + pParticle->m_vecVelocity[2] += random->RandomFloat( 4.0f, 16.0f ); + + pParticle->m_uchColor[0] = color; + pParticle->m_uchColor[1] = color; + pParticle->m_uchColor[2] = color; + pParticle->m_uchStartAlpha = alpha; + pParticle->m_uchEndAlpha = 0; + pParticle->m_uchStartSize = random->RandomInt( 4, 8 ) * flScale; + pParticle->m_uchEndSize = pParticle->m_uchStartSize*2; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f ); + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : scale - +// attachmentIndex - +// bOneFrame - +//----------------------------------------------------------------------------- +void FX_MuzzleEffectAttached( + float scale, + ClientEntityHandle_t hEntity, + int attachmentIndex, + unsigned char *pFlashColor, + bool bOneFrame ) +{ + VPROF_BUDGET( "FX_MuzzleEffect", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + CSmartPtr pSimple = CLocalSpaceEmitter::Create( "MuzzleFlash", hEntity, attachmentIndex ); + Assert( pSimple ); + if ( pSimple == NULL ) + return; + + // Lock our bounding box + pSimple->GetBinding().SetBBox( -( Vector( 16, 16, 16 ) * scale ), ( Vector( 16, 16, 16 ) * scale ) ); + + SimpleParticle *pParticle; + Vector forward(1,0,0), offset; + + float flScale = random->RandomFloat( scale-0.25f, scale+0.25f ); + + if ( flScale < 0.5f ) + { + flScale = 0.5f; + } + else if ( flScale > 8.0f ) + { + flScale = 8.0f; + } + + // + // Flash + // + + int i; + for ( i = 1; i < 9; i++ ) + { + offset = (forward * (i*2.0f*scale)); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_SMG_Muzzleflash[random->RandomInt(0,3)], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = bOneFrame ? 0.0001f : 0.1f; + + pParticle->m_vecVelocity.Init(); + + if ( !pFlashColor ) + { + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + } + else + { + pParticle->m_uchColor[0] = pFlashColor[0]; + pParticle->m_uchColor[1] = pFlashColor[1]; + pParticle->m_uchColor[2] = pFlashColor[2]; + } + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 128; + + pParticle->m_uchStartSize = (random->RandomFloat( 6.0f, 9.0f ) * (12-(i))/9) * flScale; + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + + if ( !ToolsEnabled() ) + return; + + if ( !clienttools->IsInRecordingMode() ) + return; + + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( hEntity ); + if ( pEnt ) + { + pEnt->RecordToolMessage(); + } + + // NOTE: Particle system destruction message will be sent by the particle effect itself. + int nId = pSimple->AllocateToolParticleEffectId(); + + KeyValues *msg = new KeyValues( "OldParticleSystem_Create" ); + msg->SetString( "name", "FX_MuzzleEffectAttached" ); + msg->SetInt( "id", nId ); + msg->SetFloat( "time", gpGlobals->curtime ); + + KeyValues *pEmitter = msg->FindKey( "DmeSpriteEmitter", true ); + pEmitter->SetInt( "count", 9 ); + pEmitter->SetFloat( "duration", 0 ); + pEmitter->SetString( "material", "effects/muzzleflash2" ); // FIXME - create DmeMultiMaterialSpriteEmitter to support the 4 materials of muzzleflash + pEmitter->SetInt( "active", true ); + + KeyValues *pInitializers = pEmitter->FindKey( "initializers", true ); + + KeyValues *pPosition = pInitializers->FindKey( "DmeLinearAttachedPositionInitializer", true ); + pPosition->SetPtr( "entindex", (void*)pEnt->entindex() ); + pPosition->SetInt( "attachmentIndex", attachmentIndex ); + pPosition->SetFloat( "linearOffsetX", 2.0f * scale ); + + // TODO - create a DmeConstantLifetimeInitializer + KeyValues *pLifetime = pInitializers->FindKey( "DmeRandomLifetimeInitializer", true ); + pLifetime->SetFloat( "minLifetime", bOneFrame ? 1.0f / 24.0f : 0.1f ); + pLifetime->SetFloat( "maxLifetime", bOneFrame ? 1.0f / 24.0f : 0.1f ); + + KeyValues *pVelocity = pInitializers->FindKey( "DmeConstantVelocityInitializer", true ); + pVelocity->SetFloat( "velocityX", 0.0f ); + pVelocity->SetFloat( "velocityY", 0.0f ); + pVelocity->SetFloat( "velocityZ", 0.0f ); + + KeyValues *pRoll = pInitializers->FindKey( "DmeRandomRollInitializer", true ); + pRoll->SetFloat( "minRoll", 0.0f ); + pRoll->SetFloat( "maxRoll", 360.0f ); + + // TODO - create a DmeConstantRollSpeedInitializer + KeyValues *pRollSpeed = pInitializers->FindKey( "DmeRandomRollSpeedInitializer", true ); + pRollSpeed->SetFloat( "minRollSpeed", 0.0f ); + pRollSpeed->SetFloat( "maxRollSpeed", 0.0f ); + + // TODO - create a DmeConstantColorInitializer + KeyValues *pColor = pInitializers->FindKey( "DmeRandomInterpolatedColorInitializer", true ); + Color color( pFlashColor ? pFlashColor[ 0 ] : 255, pFlashColor ? pFlashColor[ 1 ] : 255, pFlashColor ? pFlashColor[ 2 ] : 255, 255 ); + pColor->SetColor( "color1", color ); + pColor->SetColor( "color2", color ); + + // TODO - create a DmeConstantAlphaInitializer + KeyValues *pAlpha = pInitializers->FindKey( "DmeRandomAlphaInitializer", true ); + pAlpha->SetInt( "minStartAlpha", 255 ); + pAlpha->SetInt( "maxStartAlpha", 255 ); + pAlpha->SetInt( "minEndAlpha", 128 ); + pAlpha->SetInt( "maxEndAlpha", 128 ); + + // size = rand(6..9) * indexed(12/9..4/9) * flScale = rand(6..9) * ( 4f + f * i ) + KeyValues *pSize = pInitializers->FindKey( "DmeMuzzleFlashSizeInitializer", true ); + float f = flScale / 9.0f; + pSize->SetFloat( "indexedBase", 4.0f * f ); + pSize->SetFloat( "indexedDelta", f ); + pSize->SetFloat( "minRandomFactor", 6.0f ); + pSize->SetFloat( "maxRandomFactor", 9.0f ); + +/* + KeyValues *pUpdaters = pEmitter->FindKey( "updaters", true ); + + pUpdaters->FindKey( "DmePositionVelocityUpdater", true ); + pUpdaters->FindKey( "DmeRollUpdater", true ); + pUpdaters->FindKey( "DmeAlphaLinearUpdater", true ); + pUpdaters->FindKey( "DmeSizeUpdater", true ); +*/ + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); + msg->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Old-style muzzle flashes +//----------------------------------------------------------------------------- +void MuzzleFlashCallback( const CEffectData &data ) +{ + Vector vecOrigin = data.m_vOrigin; + QAngle vecAngles = data.m_vAngles; + if ( data.entindex() > 0 ) + { + IClientRenderable *pRenderable = data.GetRenderable(); + if ( !pRenderable ) + return; + + if ( data.m_nAttachmentIndex ) + { + //FIXME: We also need to allocate these particles into an attachment space setup + pRenderable->GetAttachment( data.m_nAttachmentIndex, vecOrigin, vecAngles ); + } + else + { + vecOrigin = pRenderable->GetRenderOrigin(); + vecAngles = pRenderable->GetRenderAngles(); + } + } + + tempents->MuzzleFlash( vecOrigin, vecAngles, data.m_fFlags & (~MUZZLEFLASH_FIRSTPERSON), data.m_hEntity, (data.m_fFlags & MUZZLEFLASH_FIRSTPERSON) != 0 ); +} + +DECLARE_CLIENT_EFFECT( "MuzzleFlash", MuzzleFlashCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &velocity - +// scale - +// numParticles - +// *pColor - +// iAlpha - +// *pMaterial - +// flRoll - +// flRollDelta - +//----------------------------------------------------------------------------- +CSmartPtr FX_Smoke( const Vector &origin, const Vector &velocity, float scale, int numParticles, float flDietime, unsigned char *pColor, int iAlpha, const char *pMaterial, float flRoll, float flRollDelta ) +{ + VPROF_BUDGET( "FX_Smoke", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CSimpleEmitter::Create( "FX_Smoke" ); + pSimple->SetSortOrigin( origin ); + + SimpleParticle *pParticle; + + // Smoke + for ( int i = 0; i < numParticles; i++ ) + { + PMaterialHandle hMaterial = pSimple->GetPMaterial( pMaterial ); + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, origin ); + if ( pParticle == NULL ) + return NULL; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = flDietime; + pParticle->m_vecVelocity = velocity; + for( int i = 0; i < 3; ++i ) + { + pParticle->m_uchColor[i] = pColor[i]; + } + pParticle->m_uchStartAlpha = iAlpha; + pParticle->m_uchEndAlpha = 0; + pParticle->m_uchStartSize = scale; + pParticle->m_uchEndSize = pParticle->m_uchStartSize*2; + pParticle->m_flRoll = flRoll; + pParticle->m_flRollDelta = flRollDelta; + } + + return pSimple; +} + +//----------------------------------------------------------------------------- +// Purpose: Smoke puffs +//----------------------------------------------------------------------------- +void FX_Smoke( const Vector &origin, const QAngle &angles, float scale, int numParticles, unsigned char *pColor, int iAlpha ) +{ + VPROF_BUDGET( "FX_Smoke", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector vecVelocity; + Vector vecForward; + + // Smoke + for ( int i = 0; i < numParticles; i++ ) + { + // Velocity + AngleVectors( angles, &vecForward ); + vecVelocity.Random( -0.5f, 0.5f ); + vecVelocity += vecForward; + VectorNormalize( vecVelocity ); + vecVelocity *= random->RandomFloat( 16.0f, 32.0f ); + vecVelocity[2] += random->RandomFloat( 4.0f, 16.0f ); + + // Color + unsigned char particlecolor[3]; + if ( !pColor ) + { + int color = random->RandomInt( 64, 164 ); + particlecolor[0] = color; + particlecolor[1] = color; + particlecolor[2] = color; + } + else + { + particlecolor[0] = pColor[0]; + particlecolor[1] = pColor[1]; + particlecolor[2] = pColor[2]; + } + + // Alpha + int alpha = iAlpha; + if ( alpha == -1 ) + { + alpha = random->RandomInt( 10, 25 ); + } + + // Scale + int iSize = random->RandomInt( 4, 8 ) * scale; + + // Roll + float flRoll = random->RandomInt( 0, 360 ); + float flRollDelta = random->RandomFloat( -4.0f, 4.0f ); + + //pParticle->m_uchEndSize = pParticle->m_uchStartSize*2; + + FX_Smoke( origin, vecVelocity, iSize, 1, random->RandomFloat( 0.5f, 1.0f ), particlecolor, alpha, "particle/particle_smokegrenade", flRoll, flRollDelta ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Smoke Emitter +// This is an emitter that keeps spitting out particles for its lifetime +// It won't create particles if it's not in view, so it's best when short lived +//----------------------------------------------------------------------------- +class CSmokeEmitter : public CSimpleEmitter +{ + typedef CSimpleEmitter BaseClass; +public: + CSmokeEmitter( ClientEntityHandle_t hEntity, int nAttachment, const char *pDebugName ) : CSimpleEmitter( pDebugName ) + { + m_hEntity = hEntity; + m_nAttachmentIndex = nAttachment; + m_flDeathTime = 0; + m_flLastParticleSpawnTime = 0; + } + + // Create + static CSmokeEmitter *Create( ClientEntityHandle_t hEntity, int nAttachment, const char *pDebugName="smoke" ) + { + return new CSmokeEmitter( hEntity, nAttachment, pDebugName ); + } + + void SetLifeTime( float flTime ) + { + m_flDeathTime = gpGlobals->curtime + flTime; + } + + void SetSpurtAngle( QAngle &vecAngles ) + { + AngleVectors( vecAngles, &m_vecSpurtForward ); + } + + void SetSpurtColor( const Vector4D &pColor ) + { + for ( int i = 0; i <= 3; i++ ) + { + m_SpurtColor[i] = pColor[i]; + } + } + + void SetSpawnRate( float flRate ) + { + m_flSpawnRate = flRate; + } + + void CreateSpurtParticles( void ) + { + SimpleParticle *pParticle; + // PMaterialHandle hMaterial = GetPMaterial( "particle/particle_smokegrenade" ); + + Vector vecOrigin = m_vSortOrigin; + IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle(m_hEntity); + if ( pRenderable && m_nAttachmentIndex ) + { + QAngle tmp; + pRenderable->GetAttachment( m_nAttachmentIndex, vecOrigin, tmp ); + SetSortOrigin( vecOrigin ); + } + + // Smoke + int numParticles = RandomInt( 1,2 ); + for ( int i = 0; i < numParticles; i++ ) + { + pParticle = (SimpleParticle *) AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[0], vecOrigin ); + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = RandomFloat( 0.5, 1.0 ); + + // Random velocity around the angles forward + Vector vecVelocity; + vecVelocity.Random( -0.1f, 0.1f ); + vecVelocity += m_vecSpurtForward; + VectorNormalize( vecVelocity ); + vecVelocity *= RandomFloat( 160.0f, 640.0f ); + pParticle->m_vecVelocity = vecVelocity; + + // Randomize the color a little + int color[3][2]; + for( int i = 0; i < 3; ++i ) + { + color[i][0] = max( 0, m_SpurtColor[i] - 64 ); + color[i][1] = min( 255, m_SpurtColor[i] + 64 ); + } + pParticle->m_uchColor[0] = random->RandomInt( color[0][0], color[0][1] ); + pParticle->m_uchColor[1] = random->RandomInt( color[1][0], color[1][1] ); + pParticle->m_uchColor[2] = random->RandomInt( color[2][0], color[2][1] ); + + pParticle->m_uchStartAlpha = m_SpurtColor[3]; + pParticle->m_uchEndAlpha = 0; + pParticle->m_uchStartSize = RandomInt( 50, 60 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize*3; + pParticle->m_flRoll = RandomFloat( 0, 360 ); + pParticle->m_flRollDelta = RandomFloat( -4.0f, 4.0f ); + } + + m_flLastParticleSpawnTime = gpGlobals->curtime + m_flSpawnRate; + } + + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ) + { + Particle *pParticle = pIterator->GetFirst(); + while ( pParticle ) + { + // If our lifetime isn't up, create more particles + if ( m_flDeathTime > gpGlobals->curtime ) + { + if ( m_flLastParticleSpawnTime <= gpGlobals->curtime ) + { + CreateSpurtParticles(); + } + } + + pParticle = pIterator->GetNext(); + } + + BaseClass::SimulateParticles( pIterator ); + } + + +private: + float m_flDeathTime; + float m_flLastParticleSpawnTime; + float m_flSpawnRate; + Vector m_vecSpurtForward; + Vector4D m_SpurtColor; + ClientEntityHandle_t m_hEntity; + int m_nAttachmentIndex; + + CSmokeEmitter( const CSmokeEmitter & ); // not defined, not accessible +}; + +//----------------------------------------------------------------------------- +// Purpose: Small hose gas spurt +//----------------------------------------------------------------------------- +void FX_BuildSmoke( Vector &vecOrigin, QAngle &vecAngles, ClientEntityHandle_t hEntity, int nAttachment, float flLifeTime, const Vector4D &pColor ) +{ + CSmartPtr pSimple = CSmokeEmitter::Create( hEntity, nAttachment, "FX_Smoke" ); + pSimple->SetSortOrigin( vecOrigin ); + pSimple->SetLifeTime( flLifeTime ); + pSimple->SetSpurtAngle( vecAngles ); + pSimple->SetSpurtColor( pColor ); + pSimple->SetSpawnRate( 0.03 ); + pSimple->CreateSpurtParticles(); +} + +//----------------------------------------------------------------------------- +// Purpose: Green hose gas spurt +//----------------------------------------------------------------------------- +void SmokeCallback( const CEffectData &data ) +{ + Vector vecOrigin = data.m_vOrigin; + QAngle vecAngles = data.m_vAngles; + + Vector4D color( 50,50,50,255 ); + FX_BuildSmoke( vecOrigin, vecAngles, data.m_hEntity, data.m_nAttachmentIndex, 100.0, color ); +} + +DECLARE_CLIENT_EFFECT( "Smoke", SmokeCallback ); + + +//----------------------------------------------------------------------------- +// Purpose: Shockwave for gunship bullet impacts! +// Input : &pos - +// +// NOTES: -Don't draw this effect when the viewer is very far away. +//----------------------------------------------------------------------------- +void FX_GunshipImpact( const Vector &pos, const Vector &normal, float r, float g, float b ) +{ + VPROF_BUDGET( "FX_GunshipImpact", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + if ( CImpactOverlay *pOverlay = new CImpactOverlay ) + { + pOverlay->m_flLifetime = 0; + VectorMA( pos, 1.0f, normal, pOverlay->m_vPos ); // Doesn't show up on terrain if you don't do this(sjb) + pOverlay->m_nSprites = 1; + + pOverlay->m_vBaseColors[0].Init( r, g, b ); + + pOverlay->m_Sprites[0].m_flHorzSize = 0.01f; + pOverlay->m_Sprites[0].m_flVertSize = 0.01f; + + pOverlay->Activate(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : data - +//----------------------------------------------------------------------------- +void GunshipImpactCallback( const CEffectData & data ) +{ + Vector vecPosition; + + vecPosition = data.m_vOrigin; + + FX_GunshipImpact( vecPosition, Vector( 0, 0, 1 ), 100, 0, 200 ); +} +DECLARE_CLIENT_EFFECT( "GunshipImpact", GunshipImpactCallback ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CommandPointerCallback( const CEffectData & data ) +{ + int size = COLOR_TABLE_SIZE( commandercolors ); + + for( int i = 0 ; i < size ; i++ ) + { + if( commandercolors[ i ].index == data.m_nColor ) + { + FX_GunshipImpact( data.m_vOrigin, Vector( 0, 0, 1 ), commandercolors[ i ].r, commandercolors[ i ].g, commandercolors[ i ].b ); + return; + } + } +} + +DECLARE_CLIENT_EFFECT( "CommandPointer", CommandPointerCallback ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void FX_GunshipMuzzleEffect( const Vector &origin, const QAngle &angles, float scale, ClientEntityHandle_t hEntity, unsigned char *pFlashColor ) +{ + VPROF_BUDGET( "FX_GunshipMuzzleEffect", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSimple = CSimpleEmitter::Create( "MuzzleFlash" ); + pSimple->SetSortOrigin( origin ); + + SimpleParticle *pParticle; + Vector forward, offset; + + AngleVectors( angles, &forward ); + + // + // Flash + // + offset = origin; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/gunshipmuzzle" ), offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.15f; + + pParticle->m_vecVelocity.Init(); + + pParticle->m_uchStartSize = random->RandomFloat( 40.0, 50.0 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.15f; + + pParticle->m_uchColor[0] = 255; + pParticle->m_uchColor[1] = 255; + pParticle->m_uchColor[2] = 255; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 255; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +// velocity - +// makeWhiz - +//----------------------------------------------------------------------------- +void FX_GunshipTracer( Vector& start, Vector& end, int velocity, bool makeWhiz ) +{ + VPROF_BUDGET( "FX_GunshipTracer", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector vNear, dStart, dEnd, shotDir; + float totalDist; + + //Get out shot direction and length + VectorSubtract( end, start, shotDir ); + totalDist = VectorNormalize( shotDir ); + + //Don't make small tracers + if ( totalDist <= 256 ) + return; + + float length = random->RandomFloat( 128.0f, 256.0f ); + float life = ( totalDist + length ) / velocity; //NOTENOTE: We want the tail to finish its run as well + + //Add it + FX_AddDiscreetLine( start, shotDir, velocity, length, totalDist, 5.0f, life, "effects/gunshiptracer" ); + + if( makeWhiz ) + { + FX_TracerSound( start, end, TRACER_TYPE_GUNSHIP ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void FX_StriderMuzzleEffect( const Vector &origin, const QAngle &angles, float scale, ClientEntityHandle_t hEntity, unsigned char *pFlashColor ) +{ + Vector vecDir; + AngleVectors( angles, &vecDir ); + + float life = 0.3f; + float speed = 100.0f; + + for( int i = 0 ; i < 5 ; i++ ) + { + FX_AddDiscreetLine( origin, vecDir, speed, 32, speed * life, 5.0f, life, "effects/bluespark" ); + speed *= 1.5f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +// velocity - +// makeWhiz - +//----------------------------------------------------------------------------- +void FX_StriderTracer( Vector& start, Vector& end, int velocity, bool makeWhiz ) +{ + VPROF_BUDGET( "FX_StriderTracer", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector vNear, dStart, dEnd, shotDir; + float totalDist; + + //Get out shot direction and length + VectorSubtract( end, start, shotDir ); + totalDist = VectorNormalize( shotDir ); + + //Don't make small tracers + if ( totalDist <= 256 ) + return; + + float length = random->RandomFloat( 64.0f, 128.0f ); + float life = ( totalDist + length ) / velocity; //NOTENOTE: We want the tail to finish its run as well + + //Add it + FX_AddDiscreetLine( start, shotDir, velocity, length, totalDist, 2.5f, life, "effects/gunshiptracer" ); + + if( makeWhiz ) + { + FX_TracerSound( start, end, TRACER_TYPE_STRIDER ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +// velocity - +// makeWhiz - +//----------------------------------------------------------------------------- +void FX_HunterTracer( Vector& start, Vector& end, int velocity, bool makeWhiz ) +{ + VPROF_BUDGET( "FX_HunterTracer", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector vNear, dStart, dEnd, shotDir; + float totalDist; + + // Get out shot direction and length + VectorSubtract( end, start, shotDir ); + totalDist = VectorNormalize( shotDir ); + + // Make short tracers in close quarters + // float flMinLength = min( totalDist, 128.0f ); + // float flMaxLength = min( totalDist, 128.0f ); + + float length = 128.0f;//random->RandomFloat( flMinLength, flMaxLength ); + float life = ( totalDist + length ) / velocity; // NOTENOTE: We want the tail to finish its run as well + + // Add it + FX_AddDiscreetLine( start, shotDir, velocity*0.5f, length, totalDist, 2.0f, life, "effects/huntertracer" ); + + if( makeWhiz ) + { + FX_TracerSound( start, end, TRACER_TYPE_STRIDER ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +// velocity - +// makeWhiz - +//----------------------------------------------------------------------------- +void FX_GaussTracer( Vector& start, Vector& end, int velocity, bool makeWhiz ) +{ + VPROF_BUDGET( "FX_GaussTracer", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector vNear, dStart, dEnd, shotDir; + float totalDist; + + //Get out shot direction and length + VectorSubtract( end, start, shotDir ); + totalDist = VectorNormalize( shotDir ); + + //Don't make small tracers + if ( totalDist <= 256 ) + return; + + float length = random->RandomFloat( 250.0f, 500.0f ); + float life = ( totalDist + length ) / velocity; //NOTENOTE: We want the tail to finish its run as well + + //Add it + FX_AddDiscreetLine( start, shotDir, velocity, length, totalDist, random->RandomFloat( 5.0f, 8.0f ), life, "effects/spark" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Create a tesla effect between two points +//----------------------------------------------------------------------------- +void FX_BuildTesla( + C_BaseEntity *pEntity, + const Vector &vecOrigin, + const Vector &vecEnd, + const char *pModelName, + float flBeamWidth, + const Vector &vColor, + int nFlags, + float flTimeVisible ) +{ + BeamInfo_t beamInfo; + beamInfo.m_nType = TE_BEAMTESLA; + beamInfo.m_vecStart = vecOrigin; + beamInfo.m_vecEnd = vecEnd; + beamInfo.m_pszModelName = pModelName; + beamInfo.m_flHaloScale = 0.0; + beamInfo.m_flLife = flTimeVisible; + beamInfo.m_flWidth = flBeamWidth; + beamInfo.m_flEndWidth = 1; + beamInfo.m_flFadeLength = 0.3; + beamInfo.m_flAmplitude = 16; + beamInfo.m_flBrightness = 200.0; + beamInfo.m_flSpeed = 0.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 1.0; + beamInfo.m_flRed = vColor.x * 255.0; + beamInfo.m_flGreen = vColor.y * 255.0; + beamInfo.m_flBlue = vColor.z * 255.0; + beamInfo.m_nSegments = 20; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = nFlags; + + beams->CreateBeamPoints( beamInfo ); +} + +void FX_Tesla( const CTeslaInfo &teslaInfo ) +{ + C_BaseEntity *pEntity = ClientEntityList().GetEnt( teslaInfo.m_nEntIndex ); + + // Send out beams around us + int iNumBeamsAround = (teslaInfo.m_nBeams * 2) / 3; // (2/3 of the beams are placed around in a circle) + int iNumRandomBeams = teslaInfo.m_nBeams - iNumBeamsAround; + int iTotalBeams = iNumBeamsAround + iNumRandomBeams; + float flYawOffset = RandomFloat(0,360); + for ( int i = 0; i < iTotalBeams; i++ ) + { + // Make a couple of tries at it + int iTries = -1; + Vector vecForward; + trace_t tr; + do + { + iTries++; + + // Some beams are deliberatly aimed around the point, the rest are random. + if ( i < iNumBeamsAround ) + { + QAngle vecTemp = teslaInfo.m_vAngles; + vecTemp[YAW] += anglemod( flYawOffset + ((360 / iTotalBeams) * i) ); + AngleVectors( vecTemp, &vecForward ); + + // Randomly angle it up or down + vecForward.z = RandomFloat( -1, 1 ); + } + else + { + vecForward = RandomVector( -1, 1 ); + } + VectorNormalize( vecForward ); + + UTIL_TraceLine( teslaInfo.m_vPos, teslaInfo.m_vPos + (vecForward * teslaInfo.m_flRadius), MASK_SHOT, pEntity, COLLISION_GROUP_NONE, &tr ); + } while ( tr.fraction >= 1.0 && iTries < 3 ); + + Vector vecEnd = tr.endpos - (vecForward * 8); + + // Only spark & glow if we hit something + if ( tr.fraction < 1.0 ) + { + if ( !EffectOccluded( tr.endpos, 0 ) ) + { + // Move it towards the camera + Vector vecFlash = tr.endpos; + Vector vecForward; + AngleVectors( MainViewAngles(), &vecForward ); + vecFlash -= (vecForward * 8); + + g_pEffects->EnergySplash( vecFlash, -vecForward, false ); + + // End glow + CSmartPtr pSimple = CSimpleEmitter::Create( "dust" ); + pSimple->SetSortOrigin( vecFlash ); + SimpleParticle *pParticle; + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/tesla_glow_noz" ), vecFlash ); + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = RandomFloat( 0.5, 1 ); + pParticle->m_vecVelocity = vec3_origin; + Vector color( 1,1,1 ); + float colorRamp = RandomFloat( 0.75f, 1.25f ); + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + pParticle->m_uchStartSize = RandomFloat( 6,13 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize - 2; + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 10; + pParticle->m_flRoll = RandomFloat( 0,360 ); + pParticle->m_flRollDelta = 0; + } + } + } + + // Build the tesla + FX_BuildTesla( pEntity, teslaInfo.m_vPos, tr.endpos, teslaInfo.m_pszSpriteName, teslaInfo.m_flBeamWidth, teslaInfo.m_vColor, FBEAM_ONLYNOISEONCE, teslaInfo.m_flTimeVisible ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Tesla effect +//----------------------------------------------------------------------------- +void BuildTeslaCallback( const CEffectData &data ) +{ + if ( data.entindex() < 0 ) + return; + + CTeslaInfo teslaInfo; + + teslaInfo.m_vPos = data.m_vOrigin; + teslaInfo.m_vAngles = data.m_vAngles; + teslaInfo.m_nEntIndex = data.entindex(); + teslaInfo.m_flBeamWidth = 5; + teslaInfo.m_vColor.Init( 1, 1, 1 ); + teslaInfo.m_flTimeVisible = 0.3; + teslaInfo.m_flRadius = 192; + teslaInfo.m_nBeams = 6; + teslaInfo.m_pszSpriteName = "sprites/physbeam.vmt"; + + FX_Tesla( teslaInfo ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Tesla hitbox +//----------------------------------------------------------------------------- +void FX_BuildTeslaHitbox( + C_BaseEntity *pEntity, + int nStartAttachment, + int nEndAttachment, + float flBeamWidth, + const Vector &vColor, + float flTimeVisible ) +{ + // One along the body + BeamInfo_t beamInfo; + beamInfo.m_nType = TE_BEAMTESLA; + beamInfo.m_vecStart = vec3_origin; + beamInfo.m_vecEnd = vec3_origin; + beamInfo.m_pszModelName = "sprites/lgtning.vmt"; + beamInfo.m_flHaloScale = 8.0f; + beamInfo.m_flLife = 0.01f; + beamInfo.m_flWidth = random->RandomFloat( 3.0f, 6.0f ); + beamInfo.m_flEndWidth = 0.0f; + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = random->RandomInt( 16, 32 ); + beamInfo.m_flBrightness = 255.0f; + beamInfo.m_flSpeed = 32.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 30.0; + beamInfo.m_flRed = vColor.x * 255.0; + beamInfo.m_flGreen = vColor.y * 255.0; + beamInfo.m_flBlue = vColor.z * 255.0; + beamInfo.m_nSegments = 32; + beamInfo.m_bRenderable = true; + beamInfo.m_pStartEnt = beamInfo.m_pEndEnt = NULL; + + beamInfo.m_nFlags = (FBEAM_USE_HITBOXES); + + beamInfo.m_pStartEnt = pEntity; + beamInfo.m_nStartAttachment = nStartAttachment; + beamInfo.m_pEndEnt = pEntity; + beamInfo.m_nEndAttachment = nEndAttachment; + + beams->CreateBeamEntPoint( beamInfo ); + + // One out to the world + trace_t tr; + Vector randomDir; + + randomDir = RandomVector( -1.0f, 1.0f ); + VectorNormalize( randomDir ); + + UTIL_TraceLine( pEntity->WorldSpaceCenter(), pEntity->WorldSpaceCenter() + ( randomDir * 100 ), MASK_SOLID_BRUSHONLY, pEntity, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 1.0f ) + { + beamInfo.m_nType = TE_BEAMTESLA; + beamInfo.m_vecStart = vec3_origin; + beamInfo.m_vecEnd = tr.endpos; + beamInfo.m_pszModelName = "sprites/lgtning.vmt"; + beamInfo.m_flHaloScale = 8.0f; + beamInfo.m_flLife = 0.05f; + beamInfo.m_flWidth = random->RandomFloat( 2.0f, 6.0f ); + beamInfo.m_flEndWidth = 0.0f; + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = random->RandomInt( 16, 32 ); + beamInfo.m_flBrightness = 255.0f; + beamInfo.m_flSpeed = 32.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 30.0; + beamInfo.m_flRed = vColor.x * 255.0; + beamInfo.m_flGreen = vColor.y * 255.0; + beamInfo.m_flBlue = vColor.z * 255.0; + beamInfo.m_nSegments = 32; + beamInfo.m_bRenderable = true; + beamInfo.m_pStartEnt = beamInfo.m_pEndEnt = NULL; + + beamInfo.m_pStartEnt = pEntity; + beamInfo.m_nStartAttachment = nStartAttachment; + + beams->CreateBeamEntPoint( beamInfo ); + } + + // Create an elight to illuminate the target + if ( pEntity != NULL ) + { + dlight_t *el = effects->CL_AllocElight( LIGHT_INDEX_TE_DYNAMIC + pEntity->entindex() ); + + // Randomly place it + el->origin = pEntity->WorldSpaceCenter() + RandomVector( -32, 32 ); + + el->color.r = 235; + el->color.g = 235; + el->color.b = 255; + el->color.exponent = 4; + + el->radius = random->RandomInt( 32, 128 ); + el->decay = el->radius / 0.1f; + el->die = gpGlobals->curtime + 0.1f; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Tesla effect +//----------------------------------------------------------------------------- +void FX_BuildTeslaHitbox( const CEffectData &data ) +{ + Vector vColor( 1, 1, 1 ); + + C_BaseEntity *pEntity = ClientEntityList().GetEnt( data.entindex() ); + C_BaseAnimating *pAnimating = pEntity ? pEntity->GetBaseAnimating() : NULL; + if (!pAnimating) + return; + + studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() ); + if (!pStudioHdr) + return; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() ); + if ( !set ) + return; + + matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; + if ( !pAnimating->HitboxToWorldTransforms( hitboxbones ) ) + return; + + int nBeamCount = (int)(data.m_flMagnitude + 0.5f); + for ( int i = 0; i < nBeamCount; ++i ) + { + int nStartHitBox = random->RandomInt( 1, set->numhitboxes ); + int nEndHitBox = random->RandomInt( 1, set->numhitboxes ); + FX_BuildTeslaHitbox( pEntity, nStartHitBox, nEndHitBox, data.m_flScale, vColor, random->RandomFloat( 0.05f, 0.2f ) ); + } +} + +DECLARE_CLIENT_EFFECT( "TeslaHitboxes", FX_BuildTeslaHitbox ); + + +//----------------------------------------------------------------------------- +// Purpose: Tesla effect +//----------------------------------------------------------------------------- +void FX_BuildTeslaZap( const CEffectData &data ) +{ + // Build the tesla, only works on entities + C_BaseEntity *pEntity = data.GetEntity(); + if ( !pEntity ) + return; + + Vector vColor( 1, 1, 1 ); + + BeamInfo_t beamInfo; + beamInfo.m_nType = TE_BEAMTESLA; + beamInfo.m_pStartEnt = pEntity; + beamInfo.m_nStartAttachment = data.m_nAttachmentIndex; + beamInfo.m_pEndEnt = NULL; + beamInfo.m_vecEnd = data.m_vOrigin; + beamInfo.m_pszModelName = "sprites/physbeam.vmt"; + beamInfo.m_flHaloScale = 0.0; + beamInfo.m_flLife = 0.3f; + beamInfo.m_flWidth = data.m_flScale; + beamInfo.m_flEndWidth = 1; + beamInfo.m_flFadeLength = 0.3; + beamInfo.m_flAmplitude = 16; + beamInfo.m_flBrightness = 200.0; + beamInfo.m_flSpeed = 0.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 1.0; + beamInfo.m_flRed = vColor.x * 255.0; + beamInfo.m_flGreen = vColor.y * 255.0; + beamInfo.m_flBlue = vColor.z * 255.0; + beamInfo.m_nSegments = 20; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = 0; + + beams->CreateBeamEntPoint( beamInfo ); +} + +DECLARE_CLIENT_EFFECT( "TeslaZap", FX_BuildTeslaZap ); + diff --git a/game/client/fx.h b/game/client/fx.h new file mode 100644 index 00000000..42853758 --- /dev/null +++ b/game/client/fx.h @@ -0,0 +1,109 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// +#if !defined( FX_H ) +#define FX_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mathlib/vector.h" +#include "particles_simple.h" +#include "c_pixel_visibility.h" + +class Vector; +class CGameTrace; +typedef CGameTrace trace_t; +//struct trace_t; + +enum +{ + FX_ENERGYSPLASH_EXPLOSIVE = 0x1, + FX_ENERGYSPLASH_SMOKE = 0x2, + FX_ENERGYSPLASH_LITTLESPARKS = 0x4, + FX_ENERGYSPLASH_BIGSPARKS = 0x8, + FX_ENERGYSPLASH_BIGSPARKSCOLLIDE = 0x10, + FX_ENERGYSPLASH_ENERGYBALLS = 0x20, + FX_ENERGYSPLASH_DLIGHT = 0x40, + + FX_ENERGYSPLASH_DEFAULT = ~FX_ENERGYSPLASH_EXPLOSIVE, + FX_ENERGYSPLASH_DEFAULT_EXPLOSIVE = ~0, +}; + +bool FX_GetAttachmentTransform( ClientEntityHandle_t hEntity, int attachmentIndex, matrix3x4_t &transform ); +bool FX_GetAttachmentTransform( ClientEntityHandle_t hEntity, int attachmentIndex, Vector *origin, QAngle *angles ); + +void FX_RicochetSound( const Vector& pos ); + +void FX_AntlionImpact( const Vector &pos, trace_t *tr ); +void FX_DebrisFlecks( const Vector& origin, trace_t *trace, char materialType, int iScale, bool bNoFlecks = false ); +void FX_Tracer( Vector& start, Vector& end, int velocity, bool makeWhiz = true ); +void FX_GunshipTracer( Vector& start, Vector& end, int velocity, bool makeWhiz = true ); +void FX_StriderTracer( Vector& start, Vector& end, int velocity, bool makeWhiz = true ); +void FX_HunterTracer( Vector& start, Vector& end, int velocity, bool makeWhiz = true ); +void FX_PlayerTracer( Vector& start, Vector& end ); +void FX_BulletPass( Vector& start, Vector& end ); +void FX_MetalSpark( const Vector &position, const Vector &direction, const Vector &surfaceNormal, int iScale = 1 ); +void FX_MetalScrape( Vector &position, Vector &normal ); +void FX_Sparks( const Vector &pos, int nMagnitude, int nTrailLength, const Vector &vecDir, float flWidth, float flMinSpeed, float flMaxSpeed, char *pSparkMaterial = NULL ); +void FX_ElectricSpark( const Vector &pos, int nMagnitude, int nTrailLength, const Vector *vecDir ); +void FX_BugBlood( Vector &pos, Vector &dir, Vector &vWorldMins, Vector &vWorldMaxs ); +void FX_Blood( Vector &pos, Vector &dir, float r, float g, float b, float a ); +void FX_CreateImpactDust( Vector &origin, Vector &normal ); +void FX_EnergySplash( const Vector &pos, const Vector &normal, int nFlags = FX_ENERGYSPLASH_DEFAULT ); +void FX_MicroExplosion( Vector &position, Vector &normal ); +void FX_Explosion( Vector& origin, Vector& normal, char materialType ); +void FX_ConcussiveExplosion( Vector& origin, Vector& normal ); +void FX_DustImpact( const Vector &origin, trace_t *tr, int iScale ); +void FX_DustImpact( const Vector &origin, trace_t *tr, float flScale ); +void FX_MuzzleEffect( const Vector &origin, const QAngle &angles, float scale, ClientEntityHandle_t hEntity, unsigned char *pFlashColor = NULL, bool bOneFrame = false ); +void FX_MuzzleEffectAttached( float scale, ClientEntityHandle_t hEntity, int attachmentIndex, unsigned char *pFlashColor = NULL, bool bOneFrame = false ); +void FX_StriderMuzzleEffect( const Vector &origin, const QAngle &angles, float scale, ClientEntityHandle_t hEntity, unsigned char *pFlashColor = NULL ); +void FX_GunshipMuzzleEffect( const Vector &origin, const QAngle &angles, float scale, ClientEntityHandle_t hEntity, unsigned char *pFlashColor = NULL ); +CSmartPtr FX_Smoke( const Vector &origin, const Vector &velocity, float scale, int numParticles, float flDietime, unsigned char *pColor, int iAlpha, const char *pMaterial, float flRoll, float flRollDelta ); +void FX_Smoke( const Vector &origin, const QAngle &angles, float scale, int numParticles, unsigned char *pColor = NULL, int iAlpha = -1 ); +void FX_Dust( const Vector &vecOrigin, const Vector &vecDirection, float flSize, float flSpeed ); +void FX_CreateGaussExplosion( const Vector &pos, const Vector &dir, int type ); +void FX_GaussTracer( Vector& start, Vector& end, int velocity, bool makeWhiz = true ); +void FX_TracerSound( const Vector &start, const Vector &end, int iTracerType ); + +// Lighting information utility +void UTIL_GetNormalizedColorTintAndLuminosity( const Vector &color, Vector *tint = NULL, float *luminosity = NULL ); + +// Useful function for testing whether to draw noZ effects +bool EffectOccluded( const Vector &pos, pixelvis_handle_t *queryHandle = 0 ); + +class CTeslaInfo +{ +public: + Vector m_vPos; + QAngle m_vAngles; + int m_nEntIndex; + char *m_pszSpriteName; + float m_flBeamWidth; + int m_nBeams; + Vector m_vColor; + float m_flTimeVisible; + float m_flRadius; +}; + +void FX_Tesla( const CTeslaInfo &teslaInfo ); +extern ConVar r_decals; + +extern void FX_CacheMaterialHandles( void ); + +extern PMaterialHandle g_Mat_Fleck_Wood[2]; +extern PMaterialHandle g_Mat_Fleck_Cement[2]; +extern PMaterialHandle g_Mat_Fleck_Antlion[2]; +extern PMaterialHandle g_Mat_Fleck_Tile[2]; +extern PMaterialHandle g_Mat_DustPuff[2]; +extern PMaterialHandle g_Mat_BloodPuff[2]; +extern PMaterialHandle g_Mat_Fleck_Glass[2]; +extern PMaterialHandle g_Mat_SMG_Muzzleflash[4]; +extern PMaterialHandle g_Mat_Combine_Muzzleflash[3]; +#endif // FX_H diff --git a/game/client/fx_blood.cpp b/game/client/fx_blood.cpp new file mode 100644 index 00000000..ae775a1c --- /dev/null +++ b/game/client/fx_blood.cpp @@ -0,0 +1,591 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: A blood spray effect, like a big exit wound, used when people are +// violently impaled, skewered, eviscerated, etc. +// +//=============================================================================// + +#include "cbase.h" +#include "ClientEffectPrecacheSystem.h" +#include "FX_Sparks.h" +#include "iefx.h" +#include "c_te_effect_dispatch.h" +#include "particles_ez.h" +#include "decals.h" +#include "engine/IEngineSound.h" +#include "fx_quad.h" +#include "engine/IVDebugOverlay.h" +#include "shareddefs.h" +#include "fx.h" +#include "fx_blood.h" +#include "effect_color_tables.h" +#include "particle_simple3D.h" +#include "particle_parse.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectBloodSpray ) +CLIENTEFFECT_MATERIAL( "effects/blood_core" ) +CLIENTEFFECT_MATERIAL( "effects/blood_gore" ) +CLIENTEFFECT_MATERIAL( "effects/blood_drop" ) +CLIENTEFFECT_MATERIAL( "effects/blood_puff" ) +CLIENTEFFECT_REGISTER_END() + +// Cached material handles +PMaterialHandle g_Blood_Core = NULL; +PMaterialHandle g_Blood_Gore = NULL; +PMaterialHandle g_Blood_Drops = NULL; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bloodtype - +// r - +// g - +// b - +//----------------------------------------------------------------------------- +void GetBloodColor( int bloodtype, colorentry_t &color ) +{ + int i; + + for( i = 0 ; i < COLOR_TABLE_SIZE( bloodcolors ) ; i++ ) + { + if( bloodcolors[i].index == bloodtype ) + { + color = bloodcolors[ i ]; + return; + } + } + + // build a ridiculous default color + color.r = 255; + color.g = 0; + color.b = 255; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &normal - +// scale - +// r - +// g - +// b - +// flags - +//----------------------------------------------------------------------------- +void FX_BloodSpray( const Vector &origin, const Vector &normal, float scale, unsigned char r, unsigned char g, unsigned char b, int flags ) +{ + if ( UTIL_IsLowViolence() ) + return; + + //debugoverlay->AddLineOverlay( origin, origin + normal * 72, 255, 255, 255, true, 10 ); + + Vector offset; + float spread = 0.2f; + + //Find area ambient light color and use it to tint smoke + Vector worldLight = WorldGetLightForPoint( origin, true ); + Vector color = Vector( (float)(worldLight[0] * r) / 255.0f, (float)(worldLight[1] * g) / 255.0f, (float)(worldLight[2] * b) / 255.0f ); + float colorRamp; + + int i; + + Vector offDir; + + Vector right; + Vector up; + + if (normal != Vector(0, 0, 1) ) + { + right = normal.Cross( Vector(0, 0, 1) ); + up = right.Cross( normal ); + } + else + { + right = Vector(0, 0, 1); + up = right.Cross( normal ); + } + + // + // Dump out drops + // + if (flags & FX_BLOODSPRAY_DROPS) + { + TrailParticle *tParticle; + + CSmartPtr pTrailEmitter = CTrailParticles::Create( "blooddrops" ); + if ( !pTrailEmitter ) + return; + + pTrailEmitter->SetSortOrigin( origin ); + + // Partial gravity on blood drops. + pTrailEmitter->SetGravity( 600.0 ); + + pTrailEmitter->GetBinding().SetBBox( origin - Vector( 32, 32, 32 ), origin + Vector( 32, 32, 32 ) ); + pTrailEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + pTrailEmitter->SetVelocityDampen( 0.2f ); + + PMaterialHandle hMaterial = ParticleMgr()->GetPMaterial( "effects/blood_drop" ); + + // + // Long stringy drops of blood. + // + for ( i = 0; i < 14; i++ ) + { + // Originate from within a circle 'scale' inches in diameter. + offset = origin; + offset += right * random->RandomFloat( -0.5f, 0.5f ) * scale; + offset += up * random->RandomFloat( -0.5f, 0.5f ) * scale; + + tParticle = (TrailParticle *) pTrailEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + + offDir = normal + RandomVector( -0.3f, 0.3f ); + + tParticle->m_vecVelocity = offDir * random->RandomFloat( 4.0f * scale, 40.0f * scale ); + tParticle->m_vecVelocity[2] += random->RandomFloat( 4.0f, 16.0f ) * scale; + + tParticle->m_flWidth = random->RandomFloat( 0.125f, 0.275f ) * scale; + tParticle->m_flLength = random->RandomFloat( 0.02f, 0.03f ) * scale; + tParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + FloatToColor32( tParticle->m_color, color[0], color[1], color[2], 1.0f ); + } + + // + // Shorter droplets. + // + for ( i = 0; i < 24; i++ ) + { + // Originate from within a circle 'scale' inches in diameter. + offset = origin; + offset += right * random->RandomFloat( -0.5f, 0.5f ) * scale; + offset += up * random->RandomFloat( -0.5f, 0.5f ) * scale; + + tParticle = (TrailParticle *) pTrailEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + + offDir = normal + RandomVector( -1.0f, 1.0f ); + offDir[2] += random->RandomFloat(0, 1.0f); + + tParticle->m_vecVelocity = offDir * random->RandomFloat( 2.0f * scale, 25.0f * scale ); + tParticle->m_vecVelocity[2] += random->RandomFloat( 4.0f, 16.0f ) * scale; + + tParticle->m_flWidth = random->RandomFloat( 0.25f, 0.375f ) * scale; + tParticle->m_flLength = random->RandomFloat( 0.0025f, 0.005f ) * scale; + tParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + FloatToColor32( tParticle->m_color, color[0], color[1], color[2], 1.0f ); + } + } + + if ((flags & FX_BLOODSPRAY_GORE) || (flags & FX_BLOODSPRAY_CLOUD)) + { + CSmartPtr pSimple = CBloodSprayEmitter::Create( "bloodgore" ); + if ( !pSimple ) + return; + + pSimple->SetSortOrigin( origin ); + pSimple->SetGravity( 0 ); + + PMaterialHandle hMaterial; + + // + // Tight blossom of blood at the center. + // + if (flags & FX_BLOODSPRAY_GORE) + { + hMaterial = ParticleMgr()->GetPMaterial( "effects/blood_gore" ); + + SimpleParticle *pParticle; + + for ( i = 0; i < 6; i++ ) + { + // Originate from within a circle 'scale' inches in diameter. + offset = origin + ( 0.5 * scale * normal ); + offset += right * random->RandomFloat( -0.5f, 0.5f ) * scale; + offset += up * random->RandomFloat( -0.5f, 0.5f ) * scale; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.3f; + + spread = 0.2f; + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += normal * random->RandomInt( 10, 100 ); + //VectorNormalize( pParticle->m_vecVelocity ); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomFloat( scale * 0.25, scale ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2; + + pParticle->m_uchStartAlpha = random->RandomInt( 200, 255 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + } + } + + // + // Diffuse cloud just in front of the exit wound. + // + if (flags & FX_BLOODSPRAY_CLOUD) + { + hMaterial = ParticleMgr()->GetPMaterial( "effects/blood_puff" ); + + SimpleParticle *pParticle; + + for ( i = 0; i < 6; i++ ) + { + // Originate from within a circle '2 * scale' inches in diameter. + offset = origin + ( scale * normal ); + offset += right * random->RandomFloat( -1, 1 ) * scale; + offset += up * random->RandomFloat( -1, 1 ) * scale; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 0.8f); + + spread = 0.5f; + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += normal * random->RandomInt( 100, 200 ); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomFloat( scale * 1.5f, scale * 2.0f ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = random->RandomInt( 80, 128 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + } + } + } + + // TODO: Play a sound? + //CLocalPlayerFilter filter; + //C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, CHAN_VOICE, "Physics.WaterSplash", 1.0, ATTN_NORM, 0, 100, &origin ); +} + +//----------------------------------------------------------------------------- +// Purpose: Used for bullets hitting bleeding surfaces +// Input : origin - +// normal - +// scale - This parameter is not currently used +//----------------------------------------------------------------------------- +void FX_BloodBulletImpact( const Vector &origin, const Vector &normal, float scale /*NOTE: Unused!*/, unsigned char r, unsigned char g, unsigned char b ) +{ + if ( UTIL_IsLowViolence() ) + return; + + Vector offset; + + //Find area ambient light color and use it to tint smoke + Vector worldLight = WorldGetLightForPoint( origin, true ); + + if ( gpGlobals->maxClients > 1 ) + { + worldLight = Vector( 1.0, 1.0, 1.0 ); + r = 96; + g = 0; + b = 10; + } + + Vector color = Vector( (float)(worldLight[0] * r) / 255.0f, (float)(worldLight[1] * g) / 255.0f, (float)(worldLight[2] * b) / 255.0f ); + float colorRamp; + + Vector offDir; + + CSmartPtr pSimple = CBloodSprayEmitter::Create( "bloodgore" ); + if ( !pSimple ) + return; + + pSimple->SetSortOrigin( origin ); + pSimple->SetGravity( 200 ); + + // Setup a bounding box to contain the particles without (stops auto-updating) + pSimple->GetBinding().SetBBox( origin - Vector( 16, 16, 16 ), origin + Vector( 16, 16, 16 ) ); + + // Cache the material if we haven't already + if ( g_Blood_Core == NULL ) + { + g_Blood_Core = ParticleMgr()->GetPMaterial( "effects/blood_core" ); + } + + SimpleParticle *pParticle; + + Vector dir = normal * RandomVector( -0.5f, 0.5f ); + + offset = origin + ( 2.0f * normal ); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Blood_Core, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f); + + pParticle->m_vecVelocity = dir * random->RandomFloat( 16.0f, 32.0f ); + pParticle->m_vecVelocity[2] -= random->RandomFloat( 8.0f, 16.0f ); + + colorRamp = random->RandomFloat( 0.75f, 2.0f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomInt( 2, 4 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 8; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + + // Cache the material if we haven't already + if ( g_Blood_Gore == NULL ) + { + g_Blood_Gore = ParticleMgr()->GetPMaterial( "effects/blood_gore" ); + } + + for ( int i = 0; i < 4; i++ ) + { + offset = origin + ( 2.0f * normal ); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Blood_Gore, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 0.75f); + + pParticle->m_vecVelocity = dir * random->RandomFloat( 16.0f, 32.0f )*(i+1); + pParticle->m_vecVelocity[2] -= random->RandomFloat( 32.0f, 64.0f )*(i+1); + + colorRamp = random->RandomFloat( 0.75f, 2.0f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = random->RandomInt( 2, 4 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = 0.0f; + } + } + + // + // Dump out drops + // + TrailParticle *tParticle; + + CSmartPtr pTrailEmitter = CTrailParticles::Create( "blooddrops" ); + if ( !pTrailEmitter ) + return; + + pTrailEmitter->SetSortOrigin( origin ); + + // Partial gravity on blood drops + pTrailEmitter->SetGravity( 400.0 ); + + // Enable simple collisions with nearby surfaces + pTrailEmitter->Setup(origin, &normal, 1, 10, 100, 400, 0.2, 0 ); + + if ( g_Blood_Drops == NULL ) + { + g_Blood_Drops = ParticleMgr()->GetPMaterial( "effects/blood_drop" ); + } + + // + // Shorter droplets + // + for ( int i = 0; i < 8; i++ ) + { + // Originate from within a circle 'scale' inches in diameter + offset = origin; + + tParticle = (TrailParticle *) pTrailEmitter->AddParticle( sizeof(TrailParticle), g_Blood_Drops, offset ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + + offDir = RandomVector( -1.0f, 1.0f ); + + tParticle->m_vecVelocity = offDir * random->RandomFloat( 64.0f, 128.0f ); + + tParticle->m_flWidth = random->RandomFloat( 0.5f, 2.0f ); + tParticle->m_flLength = random->RandomFloat( 0.05f, 0.15f ); + tParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + FloatToColor32( tParticle->m_color, color[0], color[1], color[2], 1.0f ); + } + + // TODO: Play a sound? + //CLocalPlayerFilter filter; + //C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, CHAN_VOICE, "Physics.WaterSplash", 1.0, ATTN_NORM, 0, 100, &origin ); +} + +// FIXME: This will be simplified when the initializer can take color parameters as an input +// For now, we use different systems + +struct ParticleForBlood_t +{ + int nColor; + const char *lpszParticleSystemName; +}; + +ParticleForBlood_t bloodCallbacks[] = +{ + { BLOOD_COLOR_RED, "blood_impact_red_01" }, + { BLOOD_COLOR_GREEN, "blood_impact_green_01" }, + { BLOOD_COLOR_YELLOW, "blood_impact_yellow_01" }, +#if defined( HL2_EPISODIC ) + { BLOOD_COLOR_ANTLION, "blood_impact_antlion_01" }, // FIXME: Move to Base HL2 + { BLOOD_COLOR_ZOMBIE, "blood_impact_zombie_01" }, // FIXME: Move to Base HL2 + { BLOOD_COLOR_ANTLION_WORKER, "blood_impact_antlion_worker_01" }, +#endif // HL2_EPISODIC +}; + +//----------------------------------------------------------------------------- +// Purpose: Intercepts the blood spray message. +//----------------------------------------------------------------------------- +void BloodSprayCallback( const CEffectData &data ) +{ + colorentry_t color; + + GetBloodColor( data.m_nColor, color ); + FX_BloodSpray( data.m_vOrigin, data.m_vNormal, data.m_flScale, color.r, color.g, color.b, data.m_fFlags ); +} + +DECLARE_CLIENT_EFFECT( "bloodspray", BloodSprayCallback ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void BloodImpactCallback( const CEffectData & data ) +{ + bool bFoundBlood = false; + + // Find which sort of blood we are + for ( int i = 0; i < ARRAYSIZE( bloodCallbacks ); i++ ) + { + if ( bloodCallbacks[i].nColor == data.m_nColor ) + { + QAngle vecAngles; + VectorAngles( -data.m_vNormal, vecAngles ); + DispatchParticleEffect( bloodCallbacks[i].lpszParticleSystemName, data.m_vOrigin, vecAngles ); + bFoundBlood = true; + break; + } + } + + if ( bFoundBlood == false ) + { + Vector vecPosition; + vecPosition = data.m_vOrigin; + + // Fetch the blood color. + colorentry_t color; + GetBloodColor( data.m_nColor, color ); + + FX_BloodBulletImpact( vecPosition, data.m_vNormal, data.m_flScale, color.r, color.g, color.b ); + } +} + +DECLARE_CLIENT_EFFECT( "BloodImpact", BloodImpactCallback ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void HunterDamageCallback( const CEffectData &data ) +{ + CSmartPtr pGlassEmitter = CSimple3DEmitter::Create( "HunterDamage" ); + if ( pGlassEmitter == NULL ) + return; + + pGlassEmitter->SetSortOrigin( data.m_vOrigin ); + + // Handle increased scale + const float flMaxSpeed = 400.0f; + const float flMinSpeed = 50.0f; + float flAngularSpray = 1.0f; + + // Setup our collision information + pGlassEmitter->m_ParticleCollision.Setup( data.m_vOrigin, &data.m_vNormal, flAngularSpray, flMinSpeed, flMaxSpeed, 600.0f, 0.2f ); + + Vector dir, end; + + int numFlecks = 32; + + Particle3D *pFleckParticle; + Vector spawnOffset; + + //Dump out flecks + for ( int i = 0; i < numFlecks; i++ ) + { + spawnOffset = data.m_vOrigin + RandomVector( -32.0f, 32.0f ); + pFleckParticle = (Particle3D *) pGlassEmitter->AddParticle( sizeof(Particle3D), g_Mat_Fleck_Antlion[random->RandomInt(0,1)], spawnOffset ); + + if ( pFleckParticle == NULL ) + break; + + pFleckParticle->m_flLifeRemaining = random->RandomFloat( 2.0f, 3.0f ); + + dir[0] = data.m_vNormal[0] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + dir[1] = data.m_vNormal[1] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + dir[2] = data.m_vNormal[2] + random->RandomFloat( -flAngularSpray, flAngularSpray ); + + pFleckParticle->m_uchSize = random->RandomInt( 3, 8 ); + + pFleckParticle->m_vecVelocity = dir * random->RandomFloat( flMinSpeed, flMaxSpeed); + + pFleckParticle->m_vAngles = RandomAngle( 0, 360 ); + pFleckParticle->m_flAngSpeed = random->RandomFloat( -800, 800 ); + + unsigned char color = 255; + pFleckParticle->m_uchFrontColor[0] = color; + pFleckParticle->m_uchFrontColor[1] = color; + pFleckParticle->m_uchFrontColor[2] = color; + pFleckParticle->m_uchBackColor[0] = color * 0.25f; + pFleckParticle->m_uchBackColor[1] = color * 0.25f; + pFleckParticle->m_uchBackColor[2] = color * 0.25f; + } +} + +DECLARE_CLIENT_EFFECT( "HunterDamage", HunterDamageCallback ); diff --git a/game/client/fx_blood.h b/game/client/fx_blood.h new file mode 100644 index 00000000..e5e105e8 --- /dev/null +++ b/game/client/fx_blood.h @@ -0,0 +1,76 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FX_BLOOD_H +#define FX_BLOOD_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/memdbgon.h" + +class CBloodSprayEmitter : public CSimpleEmitter +{ +public: + + CBloodSprayEmitter( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + static CBloodSprayEmitter *Create( const char *pDebugName ) + { + return new CBloodSprayEmitter( pDebugName ); + } + + inline void SetGravity( float flGravity ) + { + m_flGravity = flGravity; + } + + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -4.0f ); + + //Cap the minimum roll + /* + if ( fabs( pParticle->m_flRollDelta ) < 0.5f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f; + } + */ + + return pParticle->m_flRoll; + } + + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) + { + if ( !( pParticle->m_iFlags & SIMPLE_PARTICLE_FLAG_NO_VEL_DECAY ) ) + { + //Decelerate + static float dtime; + static float decay; + + if ( dtime != timeDelta ) + { + decay = ExponentialDecay( 0.1, 0.4f, dtime ); + dtime = timeDelta; + } + + pParticle->m_vecVelocity *= decay; + pParticle->m_vecVelocity[2] -= ( m_flGravity * timeDelta ); + } + } + +private: + + float m_flGravity; + + CBloodSprayEmitter( const CBloodSprayEmitter & ); +}; + +#include "tier0/memdbgoff.h" + +#endif // FX_BLOOD_H diff --git a/game/client/fx_cube.cpp b/game/client/fx_cube.cpp new file mode 100644 index 00000000..7fc59b79 --- /dev/null +++ b/game/client/fx_cube.cpp @@ -0,0 +1,136 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "clientsideeffects.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imesh.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class FX_Cube : public CClientSideEffect +{ +public: + FX_Cube(IMaterial *pMaterial) : CClientSideEffect("FX_Cube") + { + m_pMaterial = pMaterial; + if(m_pMaterial) + m_pMaterial->IncrementReferenceCount(); + } + + virtual ~FX_Cube() + { + if(m_pMaterial) + m_pMaterial->DecrementReferenceCount(); + + } + + void SetupVec(Vector& v, int dim1, int dim2, int fixedDim, float dim1Val, float dim2Val, float fixedDimVal) + { + v[dim1] = dim1Val; + v[dim2] = dim2Val; + v[fixedDim] = fixedDimVal; + } + + void DrawBoxSide( + int dim1, int dim2, int fixedDim, + float minX, float minY, + float maxX, float maxY, + float fixedDimVal, + bool bFlip, + float shade) + { + Vector v; + Vector color; + VectorScale( m_vColor, shade, color ); + + CMatRenderContextPtr pRenderContext( materials ); + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + + CMeshBuilder builder; + builder.Begin(pMesh, MATERIAL_TRIANGLE_STRIP, 2); + + SetupVec(v, dim1, dim2, fixedDim, minX, maxY, fixedDimVal); + builder.Position3fv(v.Base()); + builder.Color3fv(color.Base()); + builder.AdvanceVertex(); + + SetupVec(v, dim1, dim2, fixedDim, bFlip ? maxX : minX, bFlip ? maxY : minY, fixedDimVal); + builder.Position3fv(v.Base()); + builder.Color3fv(color.Base()); + builder.AdvanceVertex(); + + SetupVec(v, dim1, dim2, fixedDim, bFlip ? minX : maxX, bFlip ? minY : maxY, fixedDimVal); + builder.Position3fv(v.Base()); + builder.Color3fv(color.Base()); + builder.AdvanceVertex(); + + SetupVec(v, dim1, dim2, fixedDim, maxX, minY, fixedDimVal); + builder.Position3fv(v.Base()); + builder.Color3fv(color.Base()); + builder.AdvanceVertex(); + + builder.End(); + pMesh->Draw(); + } + + virtual void Draw( double frametime ) + { + CMatRenderContextPtr pRenderContext( materials ); + // Draw it. + pRenderContext->Bind( m_pMaterial ); + + Vector vLightDir(-1,-2,-3); + VectorNormalize( vLightDir ); + + DrawBoxSide(1, 2, 0, m_mins[1], m_mins[2], m_maxs[1], m_maxs[2], m_mins[0], false, vLightDir.x * 0.5f + 0.5f); + DrawBoxSide(1, 2, 0, m_mins[1], m_mins[2], m_maxs[1], m_maxs[2], m_maxs[0], true, -vLightDir.x * 0.5f + 0.5f); + + DrawBoxSide(0, 2, 1, m_mins[0], m_mins[2], m_maxs[0], m_maxs[2], m_mins[1], true, vLightDir.y * 0.5f + 0.5f); + DrawBoxSide(0, 2, 1, m_mins[0], m_mins[2], m_maxs[0], m_maxs[2], m_maxs[1], false, -vLightDir.y * 0.5f + 0.5f); + + DrawBoxSide(0, 1, 2, m_mins[0], m_mins[1], m_maxs[0], m_maxs[1], m_mins[2], false, vLightDir.z * 0.5f + 0.5f); + DrawBoxSide(0, 1, 2, m_mins[0], m_mins[1], m_maxs[0], m_maxs[1], m_maxs[2], true, -vLightDir.z * 0.5f + 0.5f); + + // Decrease lifetime. + m_Life -= frametime; + } + + bool IsActive( void ) + { + return m_Life > 0.0; + } + +public: + Vector m_mins; + Vector m_maxs; + Vector m_vColor; + float m_Life; + IMaterial *m_pMaterial; +}; + + +void FX_AddCube( const Vector &mins, const Vector &maxs, const Vector &vColor, float life, const char *materialName ) +{ + IMaterial *mat = materials->FindMaterial(materialName, TEXTURE_GROUP_CLIENT_EFFECTS); + + FX_Cube *pCube = new FX_Cube(mat); + pCube->m_mins = mins; + pCube->m_maxs = maxs; + pCube->m_vColor = vColor; + pCube->m_Life = life; + + clienteffects->AddEffect(pCube); +} + +void FX_AddCenteredCube( const Vector ¢er, float size, const Vector &vColor, float life, const char *materialName ) +{ + FX_AddCube(center-Vector(size,size,size), center+Vector(size,size,size), vColor, life, materialName); +} + + diff --git a/game/client/fx_discreetline.cpp b/game/client/fx_discreetline.cpp new file mode 100644 index 00000000..89454d74 --- /dev/null +++ b/game/client/fx_discreetline.cpp @@ -0,0 +1,288 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "FX_DiscreetLine.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMesh.h" +#include "view.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +/* +================================================== +CFXLine +================================================== +*/ + +CFXDiscreetLine::CFXDiscreetLine( const char *name, const Vector& start, const Vector& direction, + float velocity, float length, float clipLength, float scale, float life, const char *shader ) +: CClientSideEffect( name ) +{ + assert( materials ); + if ( materials == NULL ) + return; + + // Create a material... + m_pMaterial = materials->FindMaterial( shader, TEXTURE_GROUP_CLIENT_EFFECTS ); + m_pMaterial->IncrementReferenceCount(); + + m_vecOrigin = start; + m_vecDirection = direction; + m_fVelocity = velocity; + m_fClipLength = clipLength; + m_fScale = scale; + m_fLife = life; + m_fStartTime = 0.0f; + m_fLength = length; +} + +CFXDiscreetLine::~CFXDiscreetLine( void ) +{ + Destroy(); +} + +// Does extra calculations to make them more visible over distance +ConVar tracer_extra( "tracer_extra", "1" ); + +/* +================================================== +Draw +================================================== +*/ + +void CFXDiscreetLine::Draw( double frametime ) +{ + Vector lineDir, viewDir, cross; + + Vector vecEnd, vecStart; + Vector tmp; + + // Update the effect + Update( frametime ); + + // Calculate our distance along our path + float sDistance = m_fVelocity * m_fStartTime; + float eDistance = sDistance - m_fLength; + + //Clip to start + sDistance = max( 0.0f, sDistance ); + eDistance = max( 0.0f, eDistance ); + + if ( ( sDistance == 0.0f ) && ( eDistance == 0.0f ) ) + return; + + // Clip it + if ( m_fClipLength != 0.0f ) + { + sDistance = min( sDistance, m_fClipLength ); + eDistance = min( eDistance, m_fClipLength ); + } + + // Get our delta to calculate the tc offset + float dDistance = fabs( sDistance - eDistance ); + float dTotal = ( m_fLength != 0.0f ) ? m_fLength : 0.01f; + float fOffset = ( dDistance / dTotal ); + + // Find our points along our path + VectorMA( m_vecOrigin, sDistance, m_vecDirection, vecEnd ); + VectorMA( m_vecOrigin, eDistance, m_vecDirection, vecStart ); + + //Setup our info for drawing the line + VectorSubtract( vecEnd, vecStart, lineDir ); + VectorSubtract( vecEnd, CurrentViewOrigin(), viewDir ); + + cross = lineDir.Cross( viewDir ); + VectorNormalize( cross ); + + CMeshBuilder meshBuilder; + IMesh *pMesh; + + CMatRenderContextPtr pRenderContext( materials ); + + // Better, more visible tracers + if ( tracer_extra.GetBool() ) + { + float flScreenWidth = ScreenWidth(); + float flHalfScreenWidth = flScreenWidth * 0.5f; + + float zCoord = CurrentViewForward().Dot( vecStart - CurrentViewOrigin() ); + float flScreenSpaceWidth = m_fScale * flHalfScreenWidth / zCoord; + + float flAlpha; + float flScale; + + if ( flScreenSpaceWidth < 0.5f ) + { + flAlpha = RemapVal( flScreenSpaceWidth, 0.25f, 2.0f, 0.3f, 1.0f ); + flAlpha = clamp( flAlpha, 0.25f, 1.0f ); + flScale = 0.5f * zCoord / flHalfScreenWidth; + } + else + { + flAlpha = 1.0f; + flScale = m_fScale; + } + + //Bind the material + pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_pMaterial ); + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 2 ); + + float color = (int) 255.0f * flAlpha; + + //FIXME: for now no coloration + VectorMA( vecStart, -flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecStart, flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecEnd, flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, fOffset ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecEnd, -flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, fOffset ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + flScale = flScale * 2.0f; + color = (int) 64.0f * flAlpha; + + // Soft outline + VectorMA( vecStart, -flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecStart, flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecEnd, flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, fOffset ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecEnd, -flScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, fOffset ); + meshBuilder.Color4ub( color, color, color, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + } + else + { + //Bind the material + pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_pMaterial ); + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + //FIXME: for now no coloration + VectorMA( vecStart, -m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecStart, m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecEnd, m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, fOffset ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecEnd, -m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, fOffset ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End(); + pMesh->Draw(); +} + +/* +================================================== +IsActive +================================================== +*/ + +bool CFXDiscreetLine::IsActive( void ) +{ + return ( m_fLife > 0.0 ) ? true : false; +} + +/* +================================================== +Destroy +================================================== +*/ + +void CFXDiscreetLine::Destroy( void ) +{ + //Release the material + if ( m_pMaterial != NULL ) + { + m_pMaterial->DecrementReferenceCount(); + m_pMaterial = NULL; + } +} + +/* +================================================== +Update +================================================== +*/ + +void CFXDiscreetLine::Update( double frametime ) +{ + m_fStartTime += frametime; + m_fLife -= frametime; + + //Move our end points + //VectorMA( m_vecStart, frametime, m_vecStartVelocity, m_vecStart ); + //VectorMA( m_vecEnd, frametime, m_vecStartVelocity, m_vecEnd ); +} + diff --git a/game/client/fx_discreetline.h b/game/client/fx_discreetline.h new file mode 100644 index 00000000..06fd1361 --- /dev/null +++ b/game/client/fx_discreetline.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#if !defined( FXDISCREETLINE_H ) +#define FXDISCREETLINE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mathlib/vector.h" +#include "clientsideeffects.h" + +class IMaterial; + +class CFXDiscreetLine : public CClientSideEffect +{ +public: + + CFXDiscreetLine ( const char *name, const Vector& start, const Vector& direction, float velocity, + float length, float clipLength, float scale, float life, const char *shader ); + ~CFXDiscreetLine ( void ); + + virtual void Draw( double frametime ); + virtual bool IsActive( void ); + virtual void Destroy( void ); + virtual void Update( double frametime ); + +protected: + + IMaterial *m_pMaterial; + float m_fLife; + Vector m_vecOrigin, m_vecDirection; + float m_fVelocity; + float m_fStartTime; + float m_fClipLength; + float m_fScale; + float m_fLength; +}; + +#endif //FXDISCREETLINE_H \ No newline at end of file diff --git a/game/client/fx_envelope.cpp b/game/client/fx_envelope.cpp new file mode 100644 index 00000000..54f73525 --- /dev/null +++ b/game/client/fx_envelope.cpp @@ -0,0 +1,82 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "fx_envelope.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +C_EnvelopeFX::C_EnvelopeFX( void ) +{ + m_active = false; +} + +C_EnvelopeFX::~C_EnvelopeFX() +{ + RemoveRenderable(); +} + +const matrix3x4_t & C_EnvelopeFX::RenderableToWorldTransform() +{ + static matrix3x4_t mat; + SetIdentityMatrix( mat ); + PositionMatrix( GetRenderOrigin(), mat ); + return mat; +} + +void C_EnvelopeFX::RemoveRenderable() +{ + ClientLeafSystem()->RemoveRenderable( m_hRenderHandle ); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the envelope being in the client's known entity list +//----------------------------------------------------------------------------- +void C_EnvelopeFX::Update( void ) +{ + if ( m_active ) + { + if ( m_hRenderHandle == INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->AddRenderable( this, RENDER_GROUP_TRANSLUCENT_ENTITY ); + } + else + { + ClientLeafSystem()->RenderableChanged( m_hRenderHandle ); + } + } + else + { + RemoveRenderable(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Starts up the effect +// Input : entityIndex - entity to be attached to +// attachment - attachment point (if any) to be attached to +//----------------------------------------------------------------------------- +void C_EnvelopeFX::EffectInit( int entityIndex, int attachment ) +{ + m_entityIndex = entityIndex; + m_attachment = attachment; + + m_active = 1; + m_t = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Shuts down the effect +//----------------------------------------------------------------------------- +void C_EnvelopeFX::EffectShutdown( void ) +{ + m_active = 0; + m_t = 0; +} diff --git a/game/client/fx_envelope.h b/game/client/fx_envelope.h new file mode 100644 index 00000000..cd1ef6fe --- /dev/null +++ b/game/client/fx_envelope.h @@ -0,0 +1,59 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FX_ENVELOPE_H +#define FX_ENVELOPE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cbase.h" +#include "fx.h" +#include "view.h" +#include "view_scene.h" +#include "materialsystem/imaterialvar.h" + +class C_EnvelopeFX : public CDefaultClientRenderable +{ +public: + typedef CDefaultClientRenderable BaseClass; + + C_EnvelopeFX(); + virtual ~C_EnvelopeFX(); + + virtual void Update( void ); + + // IClientRenderable + virtual const Vector& GetRenderOrigin( void ) { return m_worldPosition; } + virtual void SetRenderOrigin( const Vector &origin ) { m_worldPosition = origin; } + virtual const QAngle& GetRenderAngles( void ) { return vec3_angle; } + virtual const matrix3x4_t & RenderableToWorldTransform(); + virtual bool ShouldDraw( void ) { return true; } + virtual bool IsTransparent( void ) { return true; } + virtual bool ShouldReceiveProjectedTextures( int flags ) { return false; } + + void SetTime( float t ) { m_t = t; } + void LimitTime( float tmax ) { m_tMax = tmax; } + void SetActive( bool state = true ) { m_active = state; } + bool IsActive( void ) const { return m_active; } + + virtual void EffectInit( int entityIndex, int attachment ); + virtual void EffectShutdown( void ); + +protected: + + void RemoveRenderable(); + + + int m_entityIndex; + int m_attachment; + bool m_active; + float m_t; + float m_tMax; + Vector m_worldPosition; +}; + +#endif // FX_ENVELOPE_H diff --git a/game/client/fx_explosion.cpp b/game/client/fx_explosion.cpp new file mode 100644 index 00000000..ed0658a5 --- /dev/null +++ b/game/client/fx_explosion.cpp @@ -0,0 +1,1418 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Base explosion effect +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "fx_explosion.h" +#include "ClientEffectPrecacheSystem.h" +#include "FX_Sparks.h" +#include "dlight.h" +#include "tempentity.h" +#include "IEfx.h" +#include "engine/IEngineSound.h" +#include "engine/IVDebugOverlay.h" +#include "c_te_effect_dispatch.h" +#include "fx.h" +#include "fx_quad.h" +#include "fx_line.h" +#include "fx_water.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define __EXPLOSION_DEBUG 0 + +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectExplosion ) +CLIENTEFFECT_MATERIAL( "effects/fire_cloud1" ) +CLIENTEFFECT_MATERIAL( "effects/fire_cloud2" ) +CLIENTEFFECT_MATERIAL( "effects/fire_embers1" ) +CLIENTEFFECT_MATERIAL( "effects/fire_embers2" ) +CLIENTEFFECT_MATERIAL( "effects/fire_embers3" ) +CLIENTEFFECT_MATERIAL( "particle/particle_smokegrenade" ) +CLIENTEFFECT_MATERIAL( "particle/particle_smokegrenade1" ) +CLIENTEFFECT_MATERIAL( "effects/splash3" ) +CLIENTEFFECT_MATERIAL( "effects/splashwake1" ) +CLIENTEFFECT_REGISTER_END() + +// +// CExplosionParticle +// + +class CExplosionParticle : public CSimpleEmitter +{ +public: + + CExplosionParticle( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + //Create + static CExplosionParticle *Create( const char *pDebugName ) + { + return new CExplosionParticle( pDebugName ); + } + + //Roll + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -8.0f ); + + //Cap the minimum roll + if ( fabs( pParticle->m_flRollDelta ) < 0.5f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f; + } + + return pParticle->m_flRoll; + } + + //Velocity + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) + { + Vector saveVelocity = pParticle->m_vecVelocity; + + //Decellerate + //pParticle->m_vecVelocity += pParticle->m_vecVelocity * ( timeDelta * -20.0f ); + static float dtime; + static float decay; + + if ( dtime != timeDelta ) + { + dtime = timeDelta; + float expected = 0.5; + decay = exp( log( 0.0001f ) * dtime / expected ); + } + + pParticle->m_vecVelocity = pParticle->m_vecVelocity * decay; + + + //Cap the minimum speed + if ( pParticle->m_vecVelocity.LengthSqr() < (32.0f*32.0f) ) + { + VectorNormalize( saveVelocity ); + pParticle->m_vecVelocity = saveVelocity * 32.0f; + } + } + + //Alpha + virtual float UpdateAlpha( const SimpleParticle *pParticle ) + { + float tLifetime = pParticle->m_flLifetime / pParticle->m_flDieTime; + float ramp = 1.0f - tLifetime; + + return Bias( ramp, 0.25f ); + } + + //Color + virtual Vector UpdateColor( const SimpleParticle *pParticle ) + { + Vector color; + + float tLifetime = pParticle->m_flLifetime / pParticle->m_flDieTime; + float ramp = Bias( 1.0f - tLifetime, 0.25f ); + + color[0] = ( (float) pParticle->m_uchColor[0] * ramp ) / 255.0f; + color[1] = ( (float) pParticle->m_uchColor[1] * ramp ) / 255.0f; + color[2] = ( (float) pParticle->m_uchColor[2] * ramp ) / 255.0f; + + return color; + } + +private: + CExplosionParticle( const CExplosionParticle & ); +}; + +//Singleton static member definition +C_BaseExplosionEffect C_BaseExplosionEffect::m_instance; + +C_BaseExplosionEffect::C_BaseExplosionEffect( void ) : m_Material_Smoke( NULL ), m_Material_FireCloud( NULL ) +{ + m_Material_Embers[0] = NULL; + m_Material_Embers[1] = NULL; +} + +//Singleton accessor +C_BaseExplosionEffect &BaseExplosionEffect( void ) +{ + return C_BaseExplosionEffect::Instance(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &deviant - +// &source - +// Output : float +//----------------------------------------------------------------------------- +float C_BaseExplosionEffect::ScaleForceByDeviation( Vector &deviant, Vector &source, float spread, float *force ) +{ + if ( ( deviant == vec3_origin ) || ( source == vec3_origin ) ) + return 1.0f; + + float dot = source.Dot( deviant ); + + dot = spread * fabs( dot ); + + if ( force != NULL ) + { + (*force) *= dot; + } + + return dot; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : position - +// force - +// Output : virtual void +//----------------------------------------------------------------------------- +void C_BaseExplosionEffect::Create( const Vector &position, float force, float scale, int flags ) +{ + m_vecOrigin = position; + m_fFlags = flags; + + //Find the force of the explosion + GetForceDirection( m_vecOrigin, force, &m_vecDirection, &m_flForce ); + +#if __EXPLOSION_DEBUG + debugoverlay->AddBoxOverlay( m_vecOrigin, -Vector(32,32,32), Vector(32,32,32), vec3_angle, 255, 0, 0, 64, 5.0f ); + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin+(m_vecDirection*force*m_flForce), 0, 0, 255, false, 3 ); +#endif + + PlaySound(); + + if ( scale != 0 ) + { + // UNDONE: Make core size parametric to scale or remove scale? + CreateCore(); + } + + CreateDebris(); + //FIXME: CreateDynamicLight(); + CreateMisc(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseExplosionEffect::CreateCore( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NOFIREBALL ) + return; + + Vector offset; + int i; + + //Spread constricts as force rises + float force = m_flForce; + + //Cap our force + if ( force < EXPLOSION_FORCE_MIN ) + force = EXPLOSION_FORCE_MIN; + + if ( force > EXPLOSION_FORCE_MAX ) + force = EXPLOSION_FORCE_MAX; + + float spread = 1.0f - (0.15f*force); + + SimpleParticle *pParticle; + + CSmartPtr pSimple = CExplosionParticle::Create( "exp_smoke" ); + pSimple->SetSortOrigin( m_vecOrigin ); + pSimple->SetNearClip( 64, 128 ); + + pSimple->GetBinding().SetBBox( m_vecOrigin - Vector( 128, 128, 128 ), m_vecOrigin + Vector( 128, 128, 128 ) ); + + if ( m_Material_Smoke == NULL ) + { + m_Material_Smoke = g_Mat_DustPuff[1]; + } + + //FIXME: Better sampling area + offset = m_vecOrigin + ( m_vecDirection * 32.0f ); + + //Find area ambient light color and use it to tint smoke + Vector worldLight = WorldGetLightForPoint( offset, true ); + + Vector tint; + float luminosity; + if ( worldLight == vec3_origin ) + { + tint = vec3_origin; + luminosity = 0.0f; + } + else + { + UTIL_GetNormalizedColorTintAndLuminosity( worldLight, &tint, &luminosity ); + } + + // We only take a portion of the tint + tint = (tint * 0.25f)+(Vector(0.75f,0.75f,0.75f)); + + // Rescale to a character range + luminosity *= 255; + + if ( (m_fFlags & TE_EXPLFLAG_NOFIREBALLSMOKE) == 0 ) + { + // + // Smoke - basic internal filler + // + + for ( i = 0; i < 4; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_Smoke, m_vecOrigin ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + + #ifdef INVASION_CLIENT_DLL + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + #endif + #ifdef _XBOX + pParticle->m_flDieTime = 1.0f; + #else + pParticle->m_flDieTime = random->RandomFloat( 2.0f, 3.0f ); + #endif + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += ( m_vecDirection * random->RandomFloat( 1.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 1, 750 ) * force; + + //Scale the force down as we fall away from our main direction + ScaleForceByDeviation( pParticle->m_vecVelocity, m_vecDirection, spread, &fForce ); + + pParticle->m_vecVelocity *= fForce; + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + pParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + int nColor = random->RandomInt( luminosity*0.5f, luminosity ); + pParticle->m_uchColor[0] = ( worldLight[0] * nColor ); + pParticle->m_uchColor[1] = ( worldLight[1] * nColor ); + pParticle->m_uchColor[2] = ( worldLight[2] * nColor ); + + pParticle->m_uchStartSize = 72; + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + } + } + + + // + // Inner core + // + +#ifndef _XBOX + + for ( i = 0; i < 8; i++ ) + { + offset.Random( -16.0f, 16.0f ); + offset += m_vecOrigin; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_Smoke, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + + #ifdef INVASION_CLIENT_DLL + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + #else + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + #endif + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += ( m_vecDirection * random->RandomFloat( 1.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 1, 2000 ) * force; + + //Scale the force down as we fall away from our main direction + ScaleForceByDeviation( pParticle->m_vecVelocity, m_vecDirection, spread, &fForce ); + + pParticle->m_vecVelocity *= fForce; + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + pParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + int nColor = random->RandomInt( luminosity*0.5f, luminosity ); + pParticle->m_uchColor[0] = ( worldLight[0] * nColor ); + pParticle->m_uchColor[1] = ( worldLight[1] * nColor ); + pParticle->m_uchColor[2] = ( worldLight[2] * nColor ); + + pParticle->m_uchStartSize = random->RandomInt( 32, 64 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2; + + pParticle->m_uchStartAlpha = random->RandomFloat( 128, 255 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + } + } +#endif // !_XBOX + + // + // Ground ring + // + + Vector vRight, vUp; + VectorVectors( m_vecDirection, vRight, vUp ); + + Vector forward; + +#ifndef INVASION_CLIENT_DLL + +#ifndef _XBOX + int numRingSprites = 32; +#else + int numRingSprites = 8; +#endif + + float flIncr = (2*M_PI) / (float) numRingSprites; // Radians + float flYaw = 0.0f; + + for ( i = 0; i < numRingSprites; i++ ) + { + flYaw += flIncr; + SinCos( flYaw, &forward.y, &forward.x ); + forward.z = 0.0f; + + offset = ( RandomVector( -4.0f, 4.0f ) + m_vecOrigin ) + ( forward * random->RandomFloat( 8.0f, 16.0f ) ); + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_Smoke, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.5f ); + + pParticle->m_vecVelocity = forward; + + float fForce = random->RandomFloat( 500, 2000 ) * force; + + //Scale the force down as we fall away from our main direction + ScaleForceByDeviation( pParticle->m_vecVelocity, pParticle->m_vecVelocity, spread, &fForce ); + + pParticle->m_vecVelocity *= fForce; + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + pParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + int nColor = random->RandomInt( luminosity*0.5f, luminosity ); + pParticle->m_uchColor[0] = ( worldLight[0] * nColor ); + pParticle->m_uchColor[1] = ( worldLight[1] * nColor ); + pParticle->m_uchColor[2] = ( worldLight[2] * nColor ); + + pParticle->m_uchStartSize = random->RandomInt( 16, 32 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 4; + + pParticle->m_uchStartAlpha = random->RandomFloat( 16, 32 ); + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + } + } +#endif + } + +#ifndef _XBOX + + // + // Embers + // + + if ( m_Material_Embers[0] == NULL ) + { + m_Material_Embers[0] = pSimple->GetPMaterial( "effects/fire_embers1" ); + } + + if ( m_Material_Embers[1] == NULL ) + { + m_Material_Embers[1] = pSimple->GetPMaterial( "effects/fire_embers2" ); + } + + for ( i = 0; i < 16; i++ ) + { + offset.Random( -32.0f, 32.0f ); + offset += m_vecOrigin; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_Embers[random->RandomInt(0,1)], offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 2.0f, 3.0f ); + + pParticle->m_vecVelocity.Random( -spread*2, spread*2 ); + pParticle->m_vecVelocity += m_vecDirection; + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 1.0f, 400.0f ); + + //Scale the force down as we fall away from our main direction + float vDev = ScaleForceByDeviation( pParticle->m_vecVelocity, m_vecDirection, spread ); + + pParticle->m_vecVelocity *= fForce * ( 16.0f * (vDev*vDev*0.5f) ); + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + pParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + int nColor = random->RandomInt( 192, 255 ); + pParticle->m_uchColor[0] = pParticle->m_uchColor[1] = pParticle->m_uchColor[2] = nColor; + + pParticle->m_uchStartSize = random->RandomInt( 8, 16 ) * vDev; + + pParticle->m_uchStartSize = clamp( pParticle->m_uchStartSize, 4, 32 ); + + pParticle->m_uchEndSize = pParticle->m_uchStartSize; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + } + } +#endif // !_XBOX + + // + // Fireballs + // + + if ( m_Material_FireCloud == NULL ) + { + m_Material_FireCloud = pSimple->GetPMaterial( "effects/fire_cloud2" ); + } + +#ifndef _XBOX + int numFireballs = 32; +#else + int numFireballs = 16; +#endif + + for ( i = 0; i < numFireballs; i++ ) + { + offset.Random( -48.0f, 48.0f ); + offset += m_vecOrigin; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_FireCloud, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.2f, 0.4f ); + + pParticle->m_vecVelocity.Random( -spread*0.75f, spread*0.75f ); + pParticle->m_vecVelocity += m_vecDirection; + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 400.0f, 800.0f ); + + //Scale the force down as we fall away from our main direction + float vDev = ScaleForceByDeviation( pParticle->m_vecVelocity, m_vecDirection, spread ); + + pParticle->m_vecVelocity *= fForce * ( 16.0f * (vDev*vDev*0.5f) ); + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + pParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + int nColor = random->RandomInt( 128, 255 ); + pParticle->m_uchColor[0] = pParticle->m_uchColor[1] = pParticle->m_uchColor[2] = nColor; + + pParticle->m_uchStartSize = random->RandomInt( 32, 85 ) * vDev; + + pParticle->m_uchStartSize = clamp( pParticle->m_uchStartSize, 32, 85 ); + + pParticle->m_uchEndSize = (int)((float)pParticle->m_uchStartSize * 1.5f); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -16.0f, 16.0f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseExplosionEffect::CreateDebris( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NOPARTICLES ) + return; + + // + // Sparks + // + + CSmartPtr pSparkEmitter = CTrailParticles::Create( "CreateDebris 1" ); + if ( pSparkEmitter == NULL ) + { + assert(0); + return; + } + + if ( m_Material_FireCloud == NULL ) + { + m_Material_FireCloud = pSparkEmitter->GetPMaterial( "effects/fire_cloud2" ); + } + + pSparkEmitter->SetSortOrigin( m_vecOrigin ); + + pSparkEmitter->m_ParticleCollision.SetGravity( 200.0f ); + pSparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + pSparkEmitter->SetVelocityDampen( 8.0f ); + + // Set our bbox, don't auto-calculate it! + pSparkEmitter->GetBinding().SetBBox( m_vecOrigin - Vector( 128, 128, 128 ), m_vecOrigin + Vector( 128, 128, 128 ) ); + +#ifndef _XBOX + int numSparks = random->RandomInt( 8, 16 ); +#else + int numSparks = random->RandomInt( 2, 4 ); +#endif + + Vector dir; + float spread = 1.0f; + TrailParticle *tParticle; + + // Dump out sparks + int i; + for ( i = 0; i < numSparks; i++ ) + { + tParticle = (TrailParticle *) pSparkEmitter->AddParticle( sizeof(TrailParticle), m_Material_FireCloud, m_vecOrigin ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + tParticle->m_flDieTime = random->RandomFloat( 0.1f, 0.15f ); + + dir.Random( -spread, spread ); + dir += m_vecDirection; + VectorNormalize( dir ); + + tParticle->m_flWidth = random->RandomFloat( 2.0f, 16.0f ); + tParticle->m_flLength = random->RandomFloat( 0.05f, 0.1f ); + + tParticle->m_vecVelocity = dir * random->RandomFloat( 1500, 2500 ); + + Color32Init( tParticle->m_color, 255, 255, 255, 255 ); + } + +#ifndef _XBOX + // + // Chunks + // + + Vector offset; + CSmartPtr fleckEmitter = CFleckParticles::Create( "CreateDebris 2", m_vecOrigin, Vector(128,128,128) ); + if ( !fleckEmitter ) + return; + + // Setup our collision information + fleckEmitter->m_ParticleCollision.Setup( m_vecOrigin, &m_vecDirection, 0.9f, 512, 1024, 800, 0.5f ); + + +#ifdef _XBOX + int numFlecks = random->RandomInt( 8, 16 ); +#else + int numFlecks = random->RandomInt( 16, 32 ); +#endif // _XBOX + + + // Dump out flecks + for ( i = 0; i < numFlecks; i++ ) + { + offset = m_vecOrigin + ( m_vecDirection * 16.0f ); + + offset[0] += random->RandomFloat( -8.0f, 8.0f ); + offset[1] += random->RandomFloat( -8.0f, 8.0f ); + offset[2] += random->RandomFloat( -8.0f, 8.0f ); + + FleckParticle *pParticle = (FleckParticle *) fleckEmitter->AddParticle( sizeof(FleckParticle), g_Mat_Fleck_Cement[random->RandomInt(0,1)], offset ); + + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 3.0f; + + dir[0] = m_vecDirection[0] + random->RandomFloat( -1.0f, 1.0f ); + dir[1] = m_vecDirection[1] + random->RandomFloat( -1.0f, 1.0f ); + dir[2] = m_vecDirection[2] + random->RandomFloat( -1.0f, 1.0f ); + + pParticle->m_uchSize = random->RandomInt( 1, 3 ); + + VectorNormalize( dir ); + + float fForce = ( random->RandomFloat( 64, 256 ) * ( 4 - pParticle->m_uchSize ) ); + + float fDev = ScaleForceByDeviation( dir, m_vecDirection, 0.8f ); + + pParticle->m_vecVelocity = dir * ( fForce * ( 16.0f * (fDev*fDev*0.5f) ) ); + + pParticle->m_flRoll = random->RandomFloat( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( 0, 360 ); + + float colorRamp = random->RandomFloat( 0.5f, 1.5f ); + pParticle->m_uchColor[0] = min( 1.0f, 0.25f*colorRamp )*255.0f; + pParticle->m_uchColor[1] = min( 1.0f, 0.25f*colorRamp )*255.0f; + pParticle->m_uchColor[2] = min( 1.0f, 0.25f*colorRamp )*255.0f; + } +#endif // !_XBOX +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseExplosionEffect::CreateMisc( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseExplosionEffect::CreateDynamicLight( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NODLIGHTS ) + return; + + dlight_t *dl = effects->CL_AllocDlight( 0 ); + + VectorCopy (m_vecOrigin, dl->origin); + + dl->decay = 200; + dl->radius = 255; + dl->color.r = 255; + dl->color.g = 220; + dl->color.b = 128; + dl->die = gpGlobals->curtime + 0.1f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseExplosionEffect::PlaySound( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NOSOUND ) + return; + + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "BaseExplosionEffect.Sound", &m_vecOrigin ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : origin - +// &m_vecDirection - +// strength - +// Output : float +//----------------------------------------------------------------------------- +float C_BaseExplosionEffect::Probe( const Vector &origin, Vector *vecDirection, float strength ) +{ + //Press out + Vector endpos = origin + ( (*vecDirection) * strength ); + + //Trace into the world + trace_t tr; + UTIL_TraceLine( origin, endpos, CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &tr ); + + //Push back a proportional amount to the probe + (*vecDirection) = -(*vecDirection) * (1.0f-tr.fraction); + +#if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, endpos, (255*(1.0f-tr.fraction)), (255*tr.fraction), 0, false, 3 ); +#endif + + assert(( 1.0f - tr.fraction ) >= 0.0f ); + + //Return the impacted proportion of the probe + return (1.0f-tr.fraction); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : origin - +// &m_vecDirection - +// &m_flForce - +//----------------------------------------------------------------------------- +void C_BaseExplosionEffect::GetForceDirection( const Vector &origin, float magnitude, Vector *resultDirection, float *resultForce ) +{ + Vector d[6]; + + //All cardinal directions + d[0] = Vector( 1, 0, 0 ); + d[1] = Vector( -1, 0, 0 ); + d[2] = Vector( 0, 1, 0 ); + d[3] = Vector( 0, -1, 0 ); + d[4] = Vector( 0, 0, 1 ); + d[5] = Vector( 0, 0, -1 ); + + //Init the results + (*resultDirection).Init(); + (*resultForce) = 1.0f; + + //Get the aggregate force vector + for ( int i = 0; i < 6; i++ ) + { + (*resultForce) += Probe( origin, &d[i], magnitude ); + (*resultDirection) += d[i]; + } + + //If we've hit nothing, then point up + if ( (*resultDirection) == vec3_origin ) + { + (*resultDirection) = Vector( 0, 0, 1 ); + (*resultForce) = EXPLOSION_FORCE_MIN; + } + + //Just return the direction + VectorNormalize( (*resultDirection) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Intercepts the water explosion dispatch effect +//----------------------------------------------------------------------------- +void ExplosionCallback( const CEffectData &data ) +{ + BaseExplosionEffect().Create( data.m_vOrigin, data.m_flMagnitude, data.m_flScale, data.m_fFlags ); +} + +DECLARE_CLIENT_EFFECT( "Explosion", ExplosionCallback ); + + +//=============================================================================================================== +// Water Explosion +//=============================================================================================================== +// +// CExplosionParticle +// + +class CWaterExplosionParticle : public CSimpleEmitter +{ +public: + + CWaterExplosionParticle( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + //Create + static CWaterExplosionParticle *Create( const char *pDebugName ) + { + return new CWaterExplosionParticle( pDebugName ); + } + + //Roll + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ) + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -8.0f ); + + //Cap the minimum roll + if ( fabs( pParticle->m_flRollDelta ) < 0.25f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.25f : -0.25f; + } + + return pParticle->m_flRoll; + } + + //Velocity + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) + { + Vector saveVelocity = pParticle->m_vecVelocity; + + //Decellerate + //pParticle->m_vecVelocity += pParticle->m_vecVelocity * ( timeDelta * -20.0f ); + static float dtime; + static float decay; + + if ( dtime != timeDelta ) + { + dtime = timeDelta; + float expected = 0.5; + decay = exp( log( 0.0001f ) * dtime / expected ); + } + + pParticle->m_vecVelocity = pParticle->m_vecVelocity * decay; + + + //Cap the minimum speed + if ( pParticle->m_vecVelocity.LengthSqr() < (8.0f*8.0f) ) + { + VectorNormalize( saveVelocity ); + pParticle->m_vecVelocity = saveVelocity * 8.0f; + } + } + + //Alpha + virtual float UpdateAlpha( const SimpleParticle *pParticle ) + { + float tLifetime = pParticle->m_flLifetime / pParticle->m_flDieTime; + float ramp = 1.0f - tLifetime; + + //Non-linear fade + if ( ramp < 0.75f ) + ramp *= ramp; + + return ramp; + } + +private: + CWaterExplosionParticle( const CWaterExplosionParticle & ); +}; + +//Singleton static member definition +C_WaterExplosionEffect C_WaterExplosionEffect::m_waterinstance; + +//Singleton accessor +C_WaterExplosionEffect &WaterExplosionEffect( void ) +{ + return C_WaterExplosionEffect::Instance(); +} + +#define MAX_WATER_SURFACE_DISTANCE 512 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_WaterExplosionEffect::Create( const Vector &position, float force, float scale, int flags ) +{ + m_vecOrigin = position; + + // Find our water surface by tracing up till we're out of the water + trace_t tr; + Vector vecTrace( 0, 0, MAX_WATER_SURFACE_DISTANCE ); + UTIL_TraceLine( m_vecOrigin, m_vecOrigin + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); + + // If we didn't start in water, we're above it + if ( tr.startsolid == false ) + { + // Look downward to find the surface + vecTrace.Init( 0, 0, -MAX_WATER_SURFACE_DISTANCE ); + UTIL_TraceLine( m_vecOrigin, m_vecOrigin + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); + + // If we hit it, setup the explosion + if ( tr.fraction < 1.0f ) + { + m_vecWaterSurface = tr.endpos; + m_flDepth = 0.0f; + } + else + { + //NOTENOTE: We somehow got into a water explosion without being near water? + Assert( 0 ); + m_vecWaterSurface = m_vecOrigin; + m_flDepth = 0.0f; + } + } + else if ( tr.fractionleftsolid ) + { + // Otherwise we came out of the water at this point + m_vecWaterSurface = m_vecOrigin + (vecTrace * tr.fractionleftsolid); + m_flDepth = MAX_WATER_SURFACE_DISTANCE * tr.fractionleftsolid; + } + else + { + // Use default values, we're really deep + m_vecWaterSurface = m_vecOrigin; + m_flDepth = MAX_WATER_SURFACE_DISTANCE; + } + + // Get our lighting information + FX_GetSplashLighting( m_vecOrigin + Vector( 0, 0, 32 ), &m_vecColor, &m_flLuminosity ); + + BaseClass::Create( position, force, scale, flags ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_WaterExplosionEffect::CreateCore( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NOFIREBALL ) + return; + + // Get our lighting information for the water surface + Vector color; + float luminosity; + FX_GetSplashLighting( m_vecWaterSurface + Vector( 0, 0, 8 ), &color, &luminosity ); + + float lifetime = random->RandomFloat( 0.8f, 1.0f ); + + // Ground splash + FX_AddQuad( m_vecWaterSurface + Vector(0,0,2), + Vector(0,0,1), + 64, + 64 * 4.0f, + 0.85f, + luminosity, + 0.0f, + 0.25f, + random->RandomInt( 0, 360 ), + random->RandomFloat( -4, 4 ), + color, + 2.0f, + "effects/splashwake1", + (FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA) ); + + Vector vRight, vUp; + VectorVectors( Vector(0,0,1) , vRight, vUp ); + + Vector start, end; + + float radius = 50.0f; + + unsigned int flags; + + // Base vertical shaft + FXLineData_t lineData; + + start = m_vecWaterSurface; + end = start + ( Vector( 0, 0, 1 ) * random->RandomFloat( radius, radius*1.5f ) ); + + if ( random->RandomInt( 0, 1 ) ) + { + flags |= FXSTATICLINE_FLIP_HORIZONTAL; + } + else + { + flags = 0; + } + + lineData.m_flDieTime = lifetime * 0.5f; + + lineData.m_flStartAlpha= luminosity; + lineData.m_flEndAlpha = 0.0f; + + lineData.m_flStartScale = radius*0.5f; + lineData.m_flEndScale = radius*2; + + lineData.m_pMaterial = materials->FindMaterial( "effects/splash3", 0, 0 ); + + lineData.m_vecStart = start; + lineData.m_vecStartVelocity = vec3_origin; + + lineData.m_vecEnd = end; + lineData.m_vecEndVelocity = Vector(0,0,random->RandomFloat( 650, 750 )); + + FX_AddLine( lineData ); + + // Inner filler shaft + start = m_vecWaterSurface; + end = start + ( Vector(0,0,1) * random->RandomFloat( 32, 64 ) ); + + if ( random->RandomInt( 0, 1 ) ) + { + flags |= FXSTATICLINE_FLIP_HORIZONTAL; + } + else + { + flags = 0; + } + + lineData.m_flDieTime = lifetime * 0.5f; + + lineData.m_flStartAlpha= luminosity; + lineData.m_flEndAlpha = 0.0f; + + lineData.m_flStartScale = radius; + lineData.m_flEndScale = radius*2; + + lineData.m_pMaterial = materials->FindMaterial( "effects/splash3", 0, 0 ); + + lineData.m_vecStart = start; + lineData.m_vecStartVelocity = vec3_origin; + + lineData.m_vecEnd = end; + lineData.m_vecEndVelocity = Vector(0,0,1) * random->RandomFloat( 64, 128 ); + + FX_AddLine( lineData ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_WaterExplosionEffect::CreateDebris( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NOPARTICLES ) + return; + + // Must be in deep enough water + if ( m_flDepth <= 128 ) + return; + + Vector offset; + int i; + + //Spread constricts as force rises + float force = m_flForce; + + //Cap our force + if ( force < EXPLOSION_FORCE_MIN ) + force = EXPLOSION_FORCE_MIN; + + if ( force > EXPLOSION_FORCE_MAX ) + force = EXPLOSION_FORCE_MAX; + + float spread = 1.0f - (0.15f*force); + + SimpleParticle *pParticle; + + CSmartPtr pSimple = CWaterExplosionParticle::Create( "waterexp_bubbles" ); + pSimple->SetSortOrigin( m_vecOrigin ); + pSimple->SetNearClip( 64, 128 ); + + //FIXME: Better sampling area + offset = m_vecOrigin + ( m_vecDirection * 64.0f ); + + //Find area ambient light color and use it to tint bubbles + Vector worldLight; + FX_GetSplashLighting( offset, &worldLight, NULL ); + + // + // Smoke + // + + CParticleSubTexture *pMaterial[2]; + + pMaterial[0] = pSimple->GetPMaterial( "effects/splash1" ); + pMaterial[1] = pSimple->GetPMaterial( "effects/splash2" ); + + for ( i = 0; i < 16; i++ ) + { + offset.Random( -32.0f, 32.0f ); + offset += m_vecOrigin; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pMaterial[random->RandomInt(0,1)], offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + +#ifdef INVASION_CLIENT_DLL + pParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); +#else + pParticle->m_flDieTime = random->RandomFloat( 2.0f, 3.0f ); +#endif + + pParticle->m_vecVelocity.Random( -spread, spread ); + pParticle->m_vecVelocity += ( m_vecDirection * random->RandomFloat( 1.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = 1500 * force; + + //Scale the force down as we fall away from our main direction + ScaleForceByDeviation( pParticle->m_vecVelocity, m_vecDirection, spread, &fForce ); + + pParticle->m_vecVelocity *= fForce; + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + pParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + pParticle->m_uchColor[0] = m_vecColor.x * 255; + pParticle->m_uchColor[1] = m_vecColor.y * 255; + pParticle->m_uchColor[2] = m_vecColor.z * 255; + + pParticle->m_uchStartSize = random->RandomInt( 32, 64 ); + pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2; + + pParticle->m_uchStartAlpha = m_flLuminosity; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -8.0f, 8.0f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_WaterExplosionEffect::CreateMisc( void ) +{ + Vector offset; + float colorRamp; + + int i; + float flScale = 2.0f; + + PMaterialHandle hMaterial = ParticleMgr()->GetPMaterial( "effects/splash2" ); + +#ifndef _XBOX + + int numDrops = 32; + float length = 0.1f; + Vector vForward, vRight, vUp; + Vector offDir; + + TrailParticle *tParticle; + + CSmartPtr sparkEmitter = CTrailParticles::Create( "splash" ); + + if ( !sparkEmitter ) + return; + + sparkEmitter->SetSortOrigin( m_vecWaterSurface ); + sparkEmitter->m_ParticleCollision.SetGravity( 800.0f ); + sparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + sparkEmitter->SetVelocityDampen( 2.0f ); + + //Dump out drops + for ( i = 0; i < numDrops; i++ ) + { + offset = m_vecWaterSurface; + offset[0] += random->RandomFloat( -16.0f, 16.0f ) * flScale; + offset[1] += random->RandomFloat( -16.0f, 16.0f ) * flScale; + + tParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + tParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + offDir = Vector(0,0,1) + RandomVector( -1.0f, 1.0f ); + + tParticle->m_vecVelocity = offDir * random->RandomFloat( 50.0f * flScale * 2.0f, 100.0f * flScale * 2.0f ); + tParticle->m_vecVelocity[2] += random->RandomFloat( 32.0f, 128.0f ) * flScale; + + tParticle->m_flWidth = clamp( random->RandomFloat( 1.0f, 3.0f ) * flScale, 0.1f, 4.0f ); + tParticle->m_flLength = random->RandomFloat( length*0.25f, length )/* * flScale*/; + + colorRamp = random->RandomFloat( 1.5f, 2.0f ); + + FloatToColor32( tParticle->m_color, min( 1.0f, m_vecColor[0] * colorRamp ), min( 1.0f, m_vecColor[1] * colorRamp ), min( 1.0f, m_vecColor[2] * colorRamp ), m_flLuminosity ); + } + + //Dump out drops + for ( i = 0; i < 4; i++ ) + { + offset = m_vecWaterSurface; + offset[0] += random->RandomFloat( -16.0f, 16.0f ) * flScale; + offset[1] += random->RandomFloat( -16.0f, 16.0f ) * flScale; + + tParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + tParticle->m_flDieTime = random->RandomFloat( 0.5f, 1.0f ); + + offDir = Vector(0,0,1) + RandomVector( -0.2f, 0.2f ); + + tParticle->m_vecVelocity = offDir * random->RandomFloat( 50 * flScale * 3.0f, 100 * flScale * 3.0f ); + tParticle->m_vecVelocity[2] += random->RandomFloat( 32.0f, 128.0f ) * flScale; + + tParticle->m_flWidth = clamp( random->RandomFloat( 2.0f, 3.0f ) * flScale, 0.1f, 4.0f ); + tParticle->m_flLength = random->RandomFloat( length*0.25f, length )/* * flScale*/; + + colorRamp = random->RandomFloat( 1.5f, 2.0f ); + + FloatToColor32( tParticle->m_color, min( 1.0f, m_vecColor[0] * colorRamp ), min( 1.0f, m_vecColor[1] * colorRamp ), min( 1.0f, m_vecColor[2] * colorRamp ), m_flLuminosity ); + } + +#endif + + CSmartPtr pSimple = CSplashParticle::Create( "splish" ); + pSimple->SetSortOrigin( m_vecWaterSurface ); + pSimple->SetClipHeight( m_vecWaterSurface.z ); + pSimple->GetBinding().SetBBox( m_vecWaterSurface-(Vector(32.0f, 32.0f, 32.0f)*flScale), m_vecWaterSurface+(Vector(32.0f, 32.0f, 32.0f)*flScale) ); + + SimpleParticle *pParticle; + + for ( i = 0; i < 16; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, m_vecWaterSurface ); + + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 2.0f; //NOTENOTE: We use a clip plane to realistically control our lifespan + + pParticle->m_vecVelocity.Random( -0.2f, 0.2f ); + pParticle->m_vecVelocity += ( Vector( 0, 0, random->RandomFloat( 4.0f, 6.0f ) ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= 50 * flScale * (8-i); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, m_vecColor[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, m_vecColor[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, m_vecColor[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = 24 * flScale * RemapValClamped( i, 7, 0, 1, 0.5f ); + pParticle->m_uchEndSize = min( 255, pParticle->m_uchStartSize * 2 ); + + pParticle->m_uchStartAlpha = RemapValClamped( i, 7, 0, 255, 32 ) * m_flLuminosity; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_WaterExplosionEffect::PlaySound( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NOSOUND ) + return; + + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "Physics.WaterSplash", &m_vecWaterSurface ); + + if ( m_flDepth > 128 ) + { + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "WaterExplosionEffect.Sound", &m_vecOrigin ); + } + else + { + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "BaseExplosionEffect.Sound", &m_vecOrigin ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Intercepts the water explosion dispatch effect +//----------------------------------------------------------------------------- +void WaterSurfaceExplosionCallback( const CEffectData &data ) +{ + WaterExplosionEffect().Create( data.m_vOrigin, data.m_flMagnitude, data.m_flScale, data.m_fFlags ); +} + +DECLARE_CLIENT_EFFECT( "WaterSurfaceExplosion", WaterSurfaceExplosionCallback ); + +//Singleton static member definition +C_MegaBombExplosionEffect C_MegaBombExplosionEffect::m_megainstance; + +//Singleton accessor +C_MegaBombExplosionEffect &MegaBombExplosionEffect( void ) +{ + return C_MegaBombExplosionEffect::Instance(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_MegaBombExplosionEffect::CreateCore( void ) +{ + if ( m_fFlags & TE_EXPLFLAG_NOFIREBALL ) + return; + + Vector offset; + int i; + + //Spread constricts as force rises + float force = m_flForce; + + //Cap our force + if ( force < EXPLOSION_FORCE_MIN ) + force = EXPLOSION_FORCE_MIN; + + if ( force > EXPLOSION_FORCE_MAX ) + force = EXPLOSION_FORCE_MAX; + + float spread = 1.0f - (0.15f*force); + + CSmartPtr pSimple = CExplosionParticle::Create( "exp_smoke" ); + pSimple->SetSortOrigin( m_vecOrigin ); + pSimple->SetNearClip( 32, 64 ); + + SimpleParticle *pParticle; + + if ( m_Material_FireCloud == NULL ) + { + m_Material_FireCloud = pSimple->GetPMaterial( "effects/fire_cloud2" ); + } + + // + // Fireballs + // + + for ( i = 0; i < 32; i++ ) + { + offset.Random( -48.0f, 48.0f ); + offset += m_vecOrigin; + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), m_Material_FireCloud, offset ); + + if ( pParticle != NULL ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.2f, 0.4f ); + + pParticle->m_vecVelocity.Random( -spread*0.75f, spread*0.75f ); + pParticle->m_vecVelocity += m_vecDirection; + + VectorNormalize( pParticle->m_vecVelocity ); + + float fForce = random->RandomFloat( 400.0f, 800.0f ); + + //Scale the force down as we fall away from our main direction + float vDev = ScaleForceByDeviation( pParticle->m_vecVelocity, m_vecDirection, spread ); + + pParticle->m_vecVelocity *= fForce * ( 16.0f * (vDev*vDev*0.5f) ); + + #if __EXPLOSION_DEBUG + debugoverlay->AddLineOverlay( m_vecOrigin, m_vecOrigin + pParticle->m_vecVelocity, 255, 0, 0, false, 3 ); + #endif + + int nColor = random->RandomInt( 128, 255 ); + pParticle->m_uchColor[0] = pParticle->m_uchColor[1] = pParticle->m_uchColor[2] = nColor; + + pParticle->m_uchStartSize = random->RandomInt( 32, 85 ) * vDev; + + pParticle->m_uchStartSize = clamp( pParticle->m_uchStartSize, 32, 85 ); + + pParticle->m_uchEndSize = (int)((float)pParticle->m_uchStartSize * 1.5f); + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -16.0f, 16.0f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void HelicopterMegaBombCallback( const CEffectData &data ) +{ + C_MegaBombExplosionEffect().Create( data.m_vOrigin, 1.0f, 1.0f, 0 ); +} + +DECLARE_CLIENT_EFFECT( "HelicopterMegaBomb", HelicopterMegaBombCallback ); diff --git a/game/client/fx_explosion.h b/game/client/fx_explosion.h new file mode 100644 index 00000000..cbf05b7b --- /dev/null +++ b/game/client/fx_explosion.h @@ -0,0 +1,136 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FX_EXPLOSION_H +#define FX_EXPLOSION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "particle_collision.h" +#include "glow_overlay.h" + +//JDW: For now we're clamping this value +#define EXPLOSION_FORCE_MAX 2 +#define EXPLOSION_FORCE_MIN 2 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_BaseExplosionEffect +{ +private: + static C_BaseExplosionEffect m_instance; + +public: + ~C_BaseExplosionEffect( void ) {} + + static C_BaseExplosionEffect &Instance( void ) { return m_instance; } + + virtual void Create( const Vector &position, float force, float scale, int flags ); + +protected: + C_BaseExplosionEffect( void ); + + virtual void PlaySound( void ); + + virtual void CreateCore( void ); + virtual void CreateDebris( void ); + virtual void CreateMisc( void ); + virtual void CreateDynamicLight( void ); + + float ScaleForceByDeviation( Vector &deviant, Vector &source, float spread, float *force = NULL ); + + float Probe( const Vector &origin, Vector *direction, float strength ); + void GetForceDirection( const Vector &origin, float magnitude, Vector *resultDirection, float *resultForce ); + +protected: + + Vector m_vecOrigin; + Vector m_vecDirection; + float m_flForce; + int m_fFlags; + + PMaterialHandle m_Material_Smoke; + PMaterialHandle m_Material_Embers[2]; + PMaterialHandle m_Material_FireCloud; +}; + +//Singleton accessor +extern C_BaseExplosionEffect &BaseExplosionEffect( void ); + + +// +// CExplosionOverlay +// + +class CExplosionOverlay : public CWarpOverlay +{ +public: + + virtual bool Update( void ); + +public: + + float m_flLifetime; + Vector m_vBaseColors[MAX_SUN_LAYERS]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Water explosion +//----------------------------------------------------------------------------- +class C_WaterExplosionEffect : public C_BaseExplosionEffect +{ + typedef C_BaseExplosionEffect BaseClass; +public: + static C_WaterExplosionEffect &Instance( void ) { return m_waterinstance; } + + virtual void Create( const Vector &position, float force, float scale, int flags ); + +protected: + virtual void CreateCore( void ); + virtual void CreateDebris( void ); + virtual void CreateMisc( void ); + virtual void PlaySound( void ); + +private: + Vector m_vecWaterSurface; + float m_flDepth; // Depth below the water surface (used for surface effect) + Vector m_vecColor; // Lighting tint information + float m_flLuminosity; // Luminosity information + + static C_WaterExplosionEffect m_waterinstance; +}; + +//Singleton accessor +extern C_WaterExplosionEffect &WaterExplosionEffect( void ); + +//----------------------------------------------------------------------------- +// Purpose: Water explosion +//----------------------------------------------------------------------------- +class C_MegaBombExplosionEffect : public C_BaseExplosionEffect +{ + typedef C_BaseExplosionEffect BaseClass; +public: + static C_MegaBombExplosionEffect &Instance( void ) { return m_megainstance; } + +protected: + virtual void CreateCore( void ); + + virtual void CreateDebris( void ) { }; + virtual void CreateMisc( void ) { }; + virtual void PlaySound( void ) { }; + +private: + static C_MegaBombExplosionEffect m_megainstance; +}; + +//Singleton accessor +extern C_MegaBombExplosionEffect &MegaBombExplosionEffect( void ); + +#endif // FX_EXPLOSION_H diff --git a/game/client/fx_fleck.cpp b/game/client/fx_fleck.cpp new file mode 100644 index 00000000..c26d303a --- /dev/null +++ b/game/client/fx_fleck.cpp @@ -0,0 +1,239 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "FX_Fleck.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +// enable this to have the fleck_merge cvar as well as the current system count displayed as it changes (for profiling) +#define REPORT_MERGED_FLECKS 0 + +// +// class PARTICLE_MERGE +//{ +//public: +// bool MergeParticleSystems( CFleckParticles *pSystem, const char *pEffectName, const Vector ¢er, const Vector &extents ) +// { merge; return true; } +//}; + +// a singly linked list through all particle effects of a specific type +// with a specific rule for sharing them. +// Needs a hook to the particle effect's constructor/destructor and factory method +// The factory needs to support optionally merging the new particles into a previously built particle effect +// this cuts down on lots of scene management overhead as well as rendering/batch overhead +template< class PARTICLE_EFFECT, class PARTICLE_MERGE > +class CParticleMergeList +{ +public: + CParticleMergeList() : m_pHead(NULL) {} + void AddParticleSystem( PARTICLE_EFFECT *pSystem ); + void RemoveParticleSystem( PARTICLE_EFFECT *pRemove ); + PARTICLE_EFFECT *FindAndMergeParticleSystem( const char *pEffectName, const Vector ¢er, const Vector &extents ); + bool MergeParticleSystems( PARTICLE_EFFECT *pSystem, const char *pEffectName, const Vector ¢er, const Vector &extents ); +private: + PARTICLE_EFFECT *m_pHead; + PARTICLE_MERGE m_merge; +}; + +#if REPORT_MERGED_FLECKS +ConVar fleck_merge("fleck_merge","1"); +int g_PCount = 0; +#endif + +template< class PARTICLE_EFFECT, class PARTICLE_MERGE > +void CParticleMergeList::AddParticleSystem( PARTICLE_EFFECT *pSystem ) +{ +#if REPORT_MERGED_FLECKS + g_PCount++; + Msg("PS: %d\n", g_PCount); +#endif + pSystem->m_pNextParticleSystem = m_pHead; + m_pHead = pSystem; +} + +template< class PARTICLE_EFFECT, class PARTICLE_MERGE > +void CParticleMergeList::RemoveParticleSystem( PARTICLE_EFFECT *pRemove ) +{ +#if REPORT_MERGED_FLECKS + g_PCount--; + Msg("PS: %d\n", g_PCount); +#endif + PARTICLE_EFFECT **pPrev = &m_pHead; + PARTICLE_EFFECT *pCur = *pPrev; + while ( pCur ) + { + if ( pCur == pRemove ) + { + *pPrev = pCur->m_pNextParticleSystem; + return; + } + pPrev = &pCur->m_pNextParticleSystem; + pCur = *pPrev; + } +} + +template< class PARTICLE_EFFECT, class PARTICLE_MERGE > +PARTICLE_EFFECT *CParticleMergeList::FindAndMergeParticleSystem( const char *pEffectName, const Vector ¢er, const Vector &extents ) +{ +#if REPORT_MERGED_FLECKS + if ( !fleck_merge.GetBool() ) + return NULL; +#endif + + for ( PARTICLE_EFFECT *pMerge = m_pHead; pMerge != NULL; pMerge = pMerge->m_pNextParticleSystem ) + { + if ( m_merge.MergeParticleSystems( pMerge, pEffectName, center, extents ) ) + return pMerge; + } + return NULL; +} + +// merge anything within 10 feet +const float MAX_RADIUS_BBOX_MERGE = 120.0f; + +template< class PARTICLE_EFFECT > +class CMergeSameNameBbox +{ +public: + bool MergeParticleSystems( PARTICLE_EFFECT *pSystem, const char *pEffectName, const Vector ¢er, const Vector &extents ) + { + // by default, match names + if ( !Q_stricmp(pSystem->GetEffectName(), pEffectName) ) + { + Vector mins, maxs; + pSystem->GetBinding().GetWorldspaceBounds( &mins, &maxs ); + AddPointToBounds( center - extents, mins, maxs ); + AddPointToBounds( center + extents, mins, maxs ); + Vector size = maxs - mins; + float radius = size.Length(); + if ( radius < MAX_RADIUS_BBOX_MERGE ) + { + pSystem->GetBinding().SetBBox( mins, maxs ); + // put sort origin at center of the new box + Vector sortOrigin = 0.5f * (mins+maxs); + pSystem->SetSortOrigin(sortOrigin); + return true; + } + } + return false; + } +}; + +CParticleMergeList< CFleckParticles, CMergeSameNameBbox > g_FleckMergeList; + +// +// CFleckParticles +// +CSmartPtr CFleckParticles::Create( const char *pDebugName, const Vector &vCenter, const Vector &extents ) +{ + CFleckParticles *pMerge = g_FleckMergeList.FindAndMergeParticleSystem( pDebugName, vCenter, extents ); + if ( pMerge ) + return pMerge; + + CFleckParticles *pRet = new CFleckParticles( pDebugName ); + if ( pRet ) + { + pRet->GetBinding().SetBBox( vCenter - extents, vCenter + extents ); + pRet->SetSortOrigin(vCenter); + } + return pRet; +} + + +CFleckParticles::CFleckParticles( const char *pDebugName ) : CSimpleEmitter( pDebugName ), m_pNextParticleSystem(NULL) +{ + g_FleckMergeList.AddParticleSystem(this); +} + +CFleckParticles::~CFleckParticles() +{ + g_FleckMergeList.RemoveParticleSystem(this); +} + +//----------------------------------------------------------------------------- +// Purpose: Test for surrounding collision surfaces for quick collision testing for the particle system +// Input : &origin - starting position +// *dir - direction of movement (if NULL, will do a point emission test in four directions) +// angularSpread - looseness of the spread +// minSpeed - minimum speed +// maxSpeed - maximum speed +// gravity - particle gravity for the sytem +// dampen - dampening amount on collisions +// flags - extra information +//----------------------------------------------------------------------------- +void CFleckParticles::Setup( const Vector &origin, const Vector *direction, float angularSpread, float minSpeed, float maxSpeed, float gravity, float dampen, int flags ) +{ + //See if we've specified a direction + m_ParticleCollision.Setup( origin, direction, angularSpread, minSpeed, maxSpeed, gravity, dampen ); +} + + +void CFleckParticles::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const FleckParticle *pParticle = (const FleckParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + Vector tPos; + TransformParticle( ParticleMgr()->GetModelView(), pParticle->m_Pos, tPos ); + float sortKey = (int) tPos.z; + + Vector color; + color[0] = pParticle->m_uchColor[0] / 255.0f; + color[1] = pParticle->m_uchColor[1] / 255.0f; + color[2] = pParticle->m_uchColor[2] / 255.0f; + //Render it + RenderParticle_ColorSizeAngle( + pIterator->GetParticleDraw(), + tPos, + color, + 1.0f - (pParticle->m_flLifetime / pParticle->m_flDieTime), + pParticle->m_uchSize, + pParticle->m_flRoll ); + + pParticle = (const FleckParticle*)pIterator->GetNext( sortKey ); + } +} + + +void CFleckParticles::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + FleckParticle *pParticle = (FleckParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + const float timeDelta = pIterator->GetTimeDelta(); + + //Should this particle die? + pParticle->m_flLifetime += timeDelta; + + if ( pParticle->m_flLifetime >= pParticle->m_flDieTime ) + { + pIterator->RemoveParticle( pParticle ); + } + else + { + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + //Simulate the movement with collision + trace_t trace; + m_ParticleCollision.MoveParticle( pParticle->m_Pos, pParticle->m_vecVelocity, &pParticle->m_flRollDelta, timeDelta, &trace ); + + // If we're in solid, then stop moving + if ( trace.allsolid ) + { + pParticle->m_vecVelocity = vec3_origin; + pParticle->m_flRollDelta = 0.0f; + } + } + + pParticle = (FleckParticle*)pIterator->GetNext(); + } +} + + diff --git a/game/client/fx_fleck.h b/game/client/fx_fleck.h new file mode 100644 index 00000000..a800933c --- /dev/null +++ b/game/client/fx_fleck.h @@ -0,0 +1,63 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#if !defined( FXFLECKS_H ) +#define FXFLECKS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "particles_simple.h" +#include "particlemgr.h" +#include "particle_collision.h" + +// FleckParticle + +class FleckParticle : public Particle +{ +public: + Vector m_vecVelocity; + float m_flRoll; + float m_flRollDelta; + float m_flDieTime; // How long it lives for. + float m_flLifetime; // How long it has been alive for so far. + byte m_uchColor[3]; + byte m_uchSize; +}; + +// +// CFleckParticles +// + +class CFleckParticles : public CSimpleEmitter +{ +public: + + CFleckParticles( const char *pDebugName ); + ~CFleckParticles(); + static CSmartPtr Create( const char *pDebugName, const Vector &vCenter, const Vector &extents ); + + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + //Setup for point emission + virtual void Setup( const Vector &origin, const Vector *direction, float angularSpread, float minSpeed, float maxSpeed, float gravity, float dampen, int flags = 0 ); + + CParticleCollision m_ParticleCollision; + + CFleckParticles *m_pNextParticleSystem; +private: + CFleckParticles( const CFleckParticles & ); // not defined, not accessible +}; + +#endif //FXFLECKS_H \ No newline at end of file diff --git a/game/client/fx_impact.cpp b/game/client/fx_impact.cpp new file mode 100644 index 00000000..5fff4fe3 --- /dev/null +++ b/game/client/fx_impact.cpp @@ -0,0 +1,430 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// +#include "cbase.h" +#include "decals.h" +#include "materialsystem/IMaterialVar.h" +#include "ieffects.h" +#include "fx.h" +#include "fx_impact.h" +#include "view.h" +#include "engine/IStaticPropMgr.h" +#include "c_impact_effects.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar r_drawflecks( "r_drawflecks", "1" ); +extern ConVar r_drawmodeldecals; + +ImpactSoundRouteFn g_pImpactSoundRouteFn = NULL; + +//========================================================================================================================== +// RAGDOLL ENUMERATOR +//========================================================================================================================== +CRagdollEnumerator::CRagdollEnumerator( Ray_t& shot, int iDamageType ) +{ + m_rayShot = shot; + m_iDamageType = iDamageType; + m_bHit = false; +} + +IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity ) +{ + C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); + if ( pEnt == NULL ) + return ITERATION_CONTINUE; + + C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt ); + + // If the ragdoll was created on this tick, then the forces were already applied on the server + if ( pModel == NULL || WasRagdollCreatedOnCurrentTick( pEnt ) ) + return ITERATION_CONTINUE; + + IPhysicsObject *pPhysicsObject = pModel->VPhysicsGetObject(); + if ( pPhysicsObject == NULL ) + return ITERATION_CONTINUE; + + trace_t tr; + enginetrace->ClipRayToEntity( m_rayShot, MASK_SHOT, pModel, &tr ); + + if ( tr.fraction < 1.0 ) + { + pModel->ImpactTrace( &tr, m_iDamageType, NULL ); + m_bHit = true; + + //FIXME: Yes? No? + return ITERATION_STOP; + } + + return ITERATION_CONTINUE; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool FX_AffectRagdolls( Vector vecOrigin, Vector vecStart, int iDamageType ) +{ + // don't do this when lots of ragdolls are simulating + if ( s_RagdollLRU.CountRagdolls(true) > 1 ) + return false; + Ray_t shotRay; + shotRay.Init( vecStart, vecOrigin ); + + CRagdollEnumerator ragdollEnum( shotRay, iDamageType ); + partition->EnumerateElementsAlongRay( PARTITION_CLIENT_RESPONSIVE_EDICTS, shotRay, false, &ragdollEnum ); + + return ragdollEnum.Hit(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void RagdollImpactCallback( const CEffectData &data ) +{ + FX_AffectRagdolls( data.m_vOrigin, data.m_vStart, data.m_nDamageType ); +} + +DECLARE_CLIENT_EFFECT( "RagdollImpact", RagdollImpactCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType, int iHitbox, C_BaseEntity *pEntity, trace_t &tr, int nFlags, int maxLODToDecal ) +{ + VPROF( "Impact" ); + + Assert ( pEntity ); + + // Clear out the trace + memset( &tr, 0, sizeof(trace_t)); + tr.fraction = 1.0f; + + // Setup our shot information + Vector shotDir = vecOrigin - vecStart; + float flLength = VectorNormalize( shotDir ); + Vector traceExt; + VectorMA( vecStart, flLength + 8.0f, shotDir, traceExt ); + + // Attempt to hit ragdolls + + bool bHitRagdoll = false; + + if ( !pEntity->IsClientCreated() ) + { + bHitRagdoll = FX_AffectRagdolls( vecOrigin, vecStart, iDamageType ); + } + + if ( (nFlags & IMPACT_NODECAL) == 0 ) + { + int decalNumber = decalsystem->GetDecalIndexForName( GetImpactDecal( pEntity, iMaterial, iDamageType ) ); + if ( decalNumber == -1 ) + return false; + + if ( (pEntity->entindex() == 0) && (iHitbox != 0) ) + { + staticpropmgr->AddDecalToStaticProp( vecStart, traceExt, iHitbox - 1, decalNumber, true, tr ); + } + else if ( pEntity ) + { + // Here we deal with decals on entities. + pEntity->AddDecal( vecStart, traceExt, vecOrigin, iHitbox, decalNumber, true, tr, maxLODToDecal ); + } + } + else + { + // Perform the trace ourselves + Ray_t ray; + ray.Init( vecStart, traceExt ); + + if ( (pEntity->entindex() == 0) && (iHitbox != 0) ) + { + // Special case for world entity with hitbox (that's a static prop) + ICollideable *pCollideable = staticpropmgr->GetStaticPropByIndex( iHitbox - 1 ); + enginetrace->ClipRayToCollideable( ray, MASK_SHOT, pCollideable, &tr ); + } + else + { + if ( !pEntity ) + return false; + + enginetrace->ClipRayToEntity( ray, MASK_SHOT, pEntity, &tr ); + } + } + + // If we found the surface, emit debris flecks + bool bReportRagdollImpacts = (nFlags & IMPACT_REPORT_RAGDOLL_IMPACTS) != 0; + if ( ( tr.fraction == 1.0f ) || ( bHitRagdoll && !bReportRagdollImpacts ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +char const *GetImpactDecal( C_BaseEntity *pEntity, int iMaterial, int iDamageType ) +{ + char const *decalName; + if ( !pEntity ) + { + decalName = "Impact.Concrete"; + } + else + { + decalName = pEntity->DamageDecal( iDamageType, iMaterial ); + } + + // See if we need to offset the decal for material type + return decalsystem->TranslateDecalForGameMaterial( decalName, iMaterial ); +} + +//----------------------------------------------------------------------------- +// Purpose: Perform custom effects based on the Decal index +//----------------------------------------------------------------------------- +static ConVar cl_new_impact_effects( "cl_new_impact_effects", "0" ); + +struct ImpactEffect_t +{ + const char *m_pName; + const char *m_pNameNoFlecks; +}; + +static ImpactEffect_t s_pImpactEffect[26] = +{ + { "impact_antlion", NULL }, // CHAR_TEX_ANTLION + { NULL, NULL }, // CHAR_TEX_BLOODYFLESH + { "impact_concrete", "impact_concrete_noflecks" }, // CHAR_TEX_CONCRETE + { "impact_dirt", NULL }, // CHAR_TEX_DIRT + { NULL, NULL }, // CHAR_TEX_EGGSHELL + { NULL, NULL }, // CHAR_TEX_FLESH + { NULL, NULL }, // CHAR_TEX_GRATE + { NULL, NULL }, // CHAR_TEX_ALIENFLESH + { NULL, NULL }, // CHAR_TEX_CLIP + { NULL, NULL }, // CHAR_TEX_UNUSED + { NULL, NULL }, // CHAR_TEX_UNUSED + { NULL, NULL }, // CHAR_TEX_PLASTIC + { "impact_metal", NULL }, // CHAR_TEX_METAL + { "impact_dirt", NULL }, // CHAR_TEX_SAND + { NULL, NULL }, // CHAR_TEX_FOLIAGE + { "impact_computer", NULL }, // CHAR_TEX_COMPUTER + { NULL, NULL }, // CHAR_TEX_UNUSED + { NULL, NULL }, // CHAR_TEX_UNUSED + { NULL, NULL }, // CHAR_TEX_SLOSH + { "impact_concrete", "impact_concrete_noflecks" }, // CHAR_TEX_TILE + { NULL, NULL }, // CHAR_TEX_UNUSED + { "impact_metal", NULL }, // CHAR_TEX_VENT + { "impact_wood", "impact_wood_noflecks" }, // CHAR_TEX_WOOD + { NULL, NULL }, // CHAR_TEX_UNUSED + { "impact_glass", NULL }, // CHAR_TEX_GLASS + { "warp_shield_impact", NULL }, // CHAR_TEX_WARPSHIELD +}; + +static void SetImpactControlPoint( CNewParticleEffect *pEffect, int nPoint, const Vector &vecImpactPoint, const Vector &vecForward, C_BaseEntity *pEntity ) +{ + Vector vecImpactY, vecImpactZ; + VectorVectors( vecForward, vecImpactY, vecImpactZ ); + vecImpactY *= -1.0f; + + pEffect->SetControlPoint( nPoint, vecImpactPoint ); + pEffect->SetControlPointOrientation( nPoint, vecForward, vecImpactY, vecImpactZ ); + pEffect->SetControlPointEntity( nPoint, pEntity ); +} + +static void PerformNewCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags ) +{ + bool bNoFlecks = !r_drawflecks.GetBool(); + if ( !bNoFlecks ) + { + bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0 ); + } + + // Compute the impact effect name + const ImpactEffect_t &effect = s_pImpactEffect[ iMaterial - 'A' ]; + const char *pImpactName = effect.m_pName; + if ( bNoFlecks && effect.m_pNameNoFlecks ) + { + pImpactName = effect.m_pNameNoFlecks; + } + if ( !pImpactName ) + return; + + CSmartPtr pEffect = CNewParticleEffect::Create( NULL, pImpactName ); + if ( !pEffect->IsValid() ) + return; + + Vector vecReflect; + float flDot = DotProduct( shotDir, tr.plane.normal ); + VectorMA( shotDir, -2.0f * flDot, tr.plane.normal, vecReflect ); + + Vector vecShotBackward; + VectorMultiply( shotDir, -1.0f, vecShotBackward ); + + Vector vecImpactPoint = ( tr.fraction != 1.0f ) ? tr.endpos : vecOrigin; + Assert( VectorsAreEqual( vecOrigin, tr.endpos, 1e-1 ) ); + + SetImpactControlPoint( pEffect.GetObject(), 0, vecImpactPoint, tr.plane.normal, tr.m_pEnt ); + SetImpactControlPoint( pEffect.GetObject(), 1, vecImpactPoint, vecReflect, tr.m_pEnt ); + SetImpactControlPoint( pEffect.GetObject(), 2, vecImpactPoint, vecShotBackward, tr.m_pEnt ); + pEffect->SetControlPoint( 3, Vector( iScale, iScale, iScale ) ); + if ( pEffect->m_pDef->ReadsControlPoint( 4 ) ) + { + Vector vecColor; + GetColorForSurface( &tr, &vecColor ); + pEffect->SetControlPoint( 4, vecColor ); + } +} + +void PerformCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags ) +{ + // Throw out the effect if any of these are true + if ( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) ) + return; + + if ( cl_new_impact_effects.GetInt() ) + { + PerformNewCustomEffects( vecOrigin, tr, shotDir, iMaterial, iScale, nFlags ); + return; + } + + bool bNoFlecks = !r_drawflecks.GetBool(); + if ( !bNoFlecks ) + { + bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0 ); + } + + // Cement and wood have dust and flecks + if ( ( iMaterial == CHAR_TEX_CONCRETE ) || ( iMaterial == CHAR_TEX_TILE ) ) + { + FX_DebrisFlecks( vecOrigin, &tr, iMaterial, iScale, bNoFlecks ); + } + else if ( iMaterial == CHAR_TEX_WOOD ) + { + FX_DebrisFlecks( vecOrigin, &tr, iMaterial, iScale, bNoFlecks ); + } + else if ( ( iMaterial == CHAR_TEX_DIRT ) || ( iMaterial == CHAR_TEX_SAND ) ) + { + FX_DustImpact( vecOrigin, &tr, iScale ); + } + else if ( iMaterial == CHAR_TEX_ANTLION ) + { + FX_AntlionImpact( vecOrigin, &tr ); + } + else if ( ( iMaterial == CHAR_TEX_METAL ) || ( iMaterial == CHAR_TEX_VENT ) ) + { + Vector reflect; + float dot = shotDir.Dot( tr.plane.normal ); + reflect = shotDir + ( tr.plane.normal * ( dot*-2.0f ) ); + + reflect[0] += random->RandomFloat( -0.2f, 0.2f ); + reflect[1] += random->RandomFloat( -0.2f, 0.2f ); + reflect[2] += random->RandomFloat( -0.2f, 0.2f ); + + FX_MetalSpark( vecOrigin, reflect, tr.plane.normal, iScale ); + } + else if ( iMaterial == CHAR_TEX_COMPUTER ) + { + Vector offset = vecOrigin + ( tr.plane.normal * 1.0f ); + + g_pEffects->Sparks( offset ); + } + else if ( iMaterial == CHAR_TEX_WARPSHIELD ) + { + QAngle vecAngles; + VectorAngles( -shotDir, vecAngles ); + DispatchParticleEffect( "warp_shield_impact", vecOrigin, vecAngles ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play a sound for an impact. If tr contains a valid hit, use that. +// If not, use the passed in origin & surface. +//----------------------------------------------------------------------------- +void PlayImpactSound( CBaseEntity *pEntity, trace_t &tr, Vector &vecServerOrigin, int nServerSurfaceProp ) +{ + VPROF( "PlayImpactSound" ); + surfacedata_t *pdata; + Vector vecOrigin; + + // If the client-side trace hit a different entity than the server, or + // the server didn't specify a surfaceprop, then use the client-side trace + // material if it's valid. + if ( tr.DidHit() && (pEntity != tr.m_pEnt || nServerSurfaceProp == 0) ) + { + nServerSurfaceProp = tr.surface.surfaceProps; + } + pdata = physprops->GetSurfaceData( nServerSurfaceProp ); + if ( tr.fraction < 1.0 ) + { + vecOrigin = tr.endpos; + } + else + { + vecOrigin = vecServerOrigin; + } + + // Now play the esound + if ( pdata->sounds.bulletImpact ) + { + const char *pbulletImpactSoundName = physprops->GetString( pdata->sounds.bulletImpact ); + + if ( g_pImpactSoundRouteFn ) + { + g_pImpactSoundRouteFn( pbulletImpactSoundName, vecOrigin ); + } + else + { + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, NULL, pbulletImpactSoundName, pdata->soundhandles.bulletImpact, &vecOrigin ); + } + + return; + } + +#ifdef _DEBUG + Msg("***ERROR: PlayImpactSound() on a surface with 0 bulletImpactCount!\n"); +#endif //_DEBUG +} + + +void SetImpactSoundRoute( ImpactSoundRouteFn fn ) +{ + g_pImpactSoundRouteFn = fn; +} + + +//----------------------------------------------------------------------------- +// Purpose: Pull the impact data out +// Input : &data - +// *vecOrigin - +// *vecAngles - +// *iMaterial - +// *iDamageType - +// *iHitbox - +// *iEntIndex - +//----------------------------------------------------------------------------- +C_BaseEntity *ParseImpactData( const CEffectData &data, Vector *vecOrigin, Vector *vecStart, + Vector *vecShotDir, short &nSurfaceProp, int &iMaterial, int &iDamageType, int &iHitbox ) +{ + C_BaseEntity *pEntity = data.GetEntity( ); + *vecOrigin = data.m_vOrigin; + *vecStart = data.m_vStart; + nSurfaceProp = data.m_nSurfaceProp; + iDamageType = data.m_nDamageType; + iHitbox = data.m_nHitBox; + + *vecShotDir = (*vecOrigin - *vecStart); + VectorNormalize( *vecShotDir ); + + // Get the material from the surfaceprop + surfacedata_t *psurfaceData = physprops->GetSurfaceData( data.m_nSurfaceProp ); + iMaterial = psurfaceData->game.material; + + return pEntity; +} + diff --git a/game/client/fx_impact.h b/game/client/fx_impact.h new file mode 100644 index 00000000..24859efa --- /dev/null +++ b/game/client/fx_impact.h @@ -0,0 +1,69 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FX_IMPACT_H +#define FX_IMPACT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_te_effect_dispatch.h" +#include "istudiorender.h" + +// Parse the impact data from the server's data block +C_BaseEntity *ParseImpactData( const CEffectData &data, Vector *vecOrigin, Vector *vecStart, Vector *vecShotDir, short &nSurfaceProp, int &iMaterial, int &iDamageType, int &iHitbox ); + +// Get the decal name to use based on an impact with the specified entity, surface material, and damage type +char const *GetImpactDecal( C_BaseEntity *pEntity, int iMaterial, int iDamageType ); + +// Basic decal handling +// Returns true if it hit something +enum +{ + IMPACT_NODECAL = 0x1, + IMPACT_REPORT_RAGDOLL_IMPACTS = 0x2, +}; + +bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType, int iHitbox, C_BaseEntity *pEntity, trace_t &tr, int nFlags = 0, int maxLODToDecal = ADDDECAL_TO_ALL_LODS ); + +// Flags for PerformCustomEffects +enum +{ + FLAGS_CUSTIOM_EFFECTS_NOFLECKS = 0x1, +}; + +// Do spiffy things according to the material hit +void PerformCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags = 0 ); + +// Play the correct impact sound according to the material hit +void PlayImpactSound( C_BaseEntity *pServerEntity, trace_t &tr, Vector &vecServerOrigin, int nServerSurfaceProp ); + +// This can be used to hook impact sounds and play them at a later time. +// Shotguns do this so it doesn't play 10 identical sounds in the same spot. +typedef void (*ImpactSoundRouteFn)( const char *pSoundName, const Vector &vEndPos ); +void SetImpactSoundRoute( ImpactSoundRouteFn fn ); + +//----------------------------------------------------------------------------- +// Purpose: Enumerator class for ragdolls being affected by bullet forces +//----------------------------------------------------------------------------- +class CRagdollEnumerator : public IPartitionEnumerator +{ +public: + // Forced constructor + CRagdollEnumerator( Ray_t& shot, int iDamageType ); + + // Actual work code + virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); + + bool Hit( void ) const { return m_bHit; } + +private: + Ray_t m_rayShot; + int m_iDamageType; + bool m_bHit; +}; + +#endif // FX_IMPACT_H diff --git a/game/client/fx_interpvalue.cpp b/game/client/fx_interpvalue.cpp new file mode 100644 index 00000000..3c08685a --- /dev/null +++ b/game/client/fx_interpvalue.cpp @@ -0,0 +1,93 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "fx_interpvalue.h" + +CInterpolatedValue::CInterpolatedValue( void ) : m_flStartTime( 0.0f ), m_flEndTime( 0.0f ), m_flStartValue( 0.0f ), m_flEndValue( 0.0f ), m_nInterpType( INTERP_LINEAR ) +{ +} + +CInterpolatedValue::CInterpolatedValue( float startTime, float endTime, float startValue, float endValue, InterpType_t type ) : + m_flStartTime( startTime ), m_flEndTime( endTime ), m_flStartValue( startValue ), m_flEndValue( endValue ), m_nInterpType( type ) +{ +} + +void CInterpolatedValue::SetTime( float start, float end ) +{ + m_flStartTime = start; m_flEndTime = end; +} + +void CInterpolatedValue::SetRange( float start, float end ) +{ + m_flStartValue = start; m_flEndValue = end; +} + +void CInterpolatedValue::SetType( InterpType_t type ) +{ + m_nInterpType = type; +} + +// Set the value with no range +void CInterpolatedValue::SetAbsolute( float value ) +{ + m_flStartValue = m_flEndValue = value; + m_flStartTime = m_flEndTime = gpGlobals->curtime; + m_nInterpType = INTERP_LINEAR; +} + +// Set the value with range and time supplied +void CInterpolatedValue::Init( float startValue, float endValue, float dt, InterpType_t type /*= INTERP_LINEAR*/ ) +{ + if ( dt <= 0.0f ) + { + SetAbsolute( endValue ); + return; + } + + SetTime( gpGlobals->curtime, gpGlobals->curtime + dt ); + SetRange( startValue, endValue ); + SetType( type ); +} + +// Start from the current value and move towards the end value +void CInterpolatedValue::InitFromCurrent( float endValue, float dt, InterpType_t type /*= INTERP_LINEAR*/ ) +{ + Init( Interp( gpGlobals->curtime ), endValue, dt, type ); +} + +// Find our interpolated value at the given point in time +float CInterpolatedValue::Interp( float curTime ) +{ + switch( m_nInterpType ) + { + case INTERP_LINEAR: + { + if ( curTime >= m_flEndTime ) + return m_flEndValue; + + if ( curTime <= m_flStartTime ) + return m_flStartValue; + + return RemapVal( curTime, m_flStartTime, m_flEndTime, m_flStartValue, m_flEndValue ); + } + + case INTERP_SPLINE: + { + if ( curTime >= m_flEndTime ) + return m_flEndValue; + + if ( curTime <= m_flStartTime ) + return m_flStartValue; + + return SimpleSplineRemapVal( curTime, m_flStartTime, m_flEndTime, m_flStartValue, m_flEndValue ); + } + } + + // NOTENOTE: You managed to pass in a bogus interpolation type! + Assert(0); + return -1.0f; +} diff --git a/game/client/fx_interpvalue.h b/game/client/fx_interpvalue.h new file mode 100644 index 00000000..0cfd9479 --- /dev/null +++ b/game/client/fx_interpvalue.h @@ -0,0 +1,52 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef FX_INTERPVALUE_H +#define FX_INTERPVALUE_H +#ifdef _WIN32 +#pragma once +#endif + +// Types of supported interpolation +enum InterpType_t +{ + INTERP_LINEAR = 0, + INTERP_SPLINE, +}; + +class CInterpolatedValue +{ +public: + CInterpolatedValue( void ); + CInterpolatedValue( float startTime, float endTime, float startValue, float endValue, InterpType_t type ); + + void SetTime( float start, float end ); + void SetRange( float start, float end ); + void SetType( InterpType_t type ); + + // Set the value with no range + void SetAbsolute( float value ); + + // Set the value with range and time supplied + void Init( float startValue, float endValue, float dt, InterpType_t type = INTERP_LINEAR ); + + // Start from the current value and move towards the end value + void InitFromCurrent( float endValue, float dt, InterpType_t type = INTERP_LINEAR ); + + // Find our interpolated value at the given point in time + float Interp( float curTime ); + +private: + + float m_flStartTime; + float m_flEndTime; + float m_flStartValue; + float m_flEndValue; + + int m_nInterpType; +}; + +#endif // FX_INTERPVALUE_H \ No newline at end of file diff --git a/game/client/fx_line.cpp b/game/client/fx_line.cpp new file mode 100644 index 00000000..f04e59d4 --- /dev/null +++ b/game/client/fx_line.cpp @@ -0,0 +1,305 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "materialsystem/IMaterial.h" +#include "clientsideeffects.h" +#include "FX_Line.h" +#include "materialsystem/IMesh.h" +#include "view.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +/* +================================================== +CFXLine +================================================== +*/ + +CFXLine::CFXLine( const char *name, const FXLineData_t &data ) +: CClientSideEffect( name ) +{ + m_FXData = data; + + m_FXData.m_flLifeTime = 0.0f; +} + +CFXLine::~CFXLine( void ) +{ + Destroy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : frametime - +//----------------------------------------------------------------------------- +void CFXLine::Draw( double frametime ) +{ + // Update the effect + Update( frametime ); + + Vector lineDir, viewDir; + + //Get the proper orientation for the line + VectorSubtract( m_FXData.m_vecStart, m_FXData.m_vecEnd, lineDir ); + VectorSubtract( m_FXData.m_vecEnd, CurrentViewOrigin(), viewDir ); + + Vector cross = lineDir.Cross( viewDir ); + + VectorNormalize( cross ); + + CMatRenderContextPtr pRenderContext( materials ); + + //Bind the material + IMesh* pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_FXData.m_pMaterial ); + + CMeshBuilder meshBuilder; + + Vector tmp; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + float scaleTimePerc = ( m_FXData.m_flLifeTime / m_FXData.m_flDieTime ); + float scale = m_FXData.m_flStartScale + ( ( m_FXData.m_flEndScale - m_FXData.m_flStartScale ) * scaleTimePerc ); + + color32 color = {255,255,255,255}; + + float alpha = m_FXData.m_flStartAlpha + ( ( m_FXData.m_flEndAlpha - m_FXData.m_flStartAlpha ) * scaleTimePerc ); + alpha = clamp( alpha, 0.0f, 1.0f ); + + color.a *= alpha; + + // Start + VectorMA( m_FXData.m_vecStart, -scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( m_FXData.m_vecStart, scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + // End + VectorMA( m_FXData.m_vecEnd, scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( m_FXData.m_vecEnd, -scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFXLine::IsActive( void ) +{ + return ( m_FXData.m_flLifeTime < m_FXData.m_flDieTime ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFXLine::Destroy( void ) +{ + //Release the material + if ( m_FXData.m_pMaterial != NULL ) + { + m_FXData.m_pMaterial->DecrementReferenceCount(); + m_FXData.m_pMaterial = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : frametime - +//----------------------------------------------------------------------------- +void CFXLine::Update( double frametime ) +{ + m_FXData.m_flLifeTime += frametime; + + //Move our end points + VectorMA( m_FXData.m_vecStart, frametime, m_FXData.m_vecStartVelocity, m_FXData.m_vecStart ); + VectorMA( m_FXData.m_vecEnd, frametime, m_FXData.m_vecEndVelocity, m_FXData.m_vecEnd ); +} + +void FX_DrawLine( const Vector &start, const Vector &end, float scale, IMaterial *pMaterial, const color32 &color ) +{ + Vector lineDir, viewDir; + //Get the proper orientation for the line + VectorSubtract( end, start, lineDir ); + VectorSubtract( end, CurrentViewOrigin(), viewDir ); + + Vector cross = lineDir.Cross( viewDir ); + + VectorNormalize( cross ); + + CMatRenderContextPtr pRenderContext( materials ); + + //Bind the material + IMesh* pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + CMeshBuilder meshBuilder; + + Vector tmp; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + VectorMA( start, -scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( start, scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( end, scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + VectorMA( end, -scale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + + +void FX_DrawLineFade( const Vector &start, const Vector &end, float scale, IMaterial *pMaterial, const color32 &color, float fadeDist ) +{ + Vector lineDir, viewDir; + //Get the proper orientation for the line + VectorSubtract( end, start, lineDir ); + VectorSubtract( end, CurrentViewOrigin(), viewDir ); + + float lineLength = lineDir.Length(); + float t0 = 0.25f; + float t1 = 0.75f; + if ( lineLength > 0 ) + { + t0 = fadeDist / lineLength; + t0 = clamp( t0, 0.0f, 0.25f ); + t1 = 1.0f - t0; + } + + Vector cross = lineDir.Cross( viewDir ); + + VectorNormalize( cross ); + + CMatRenderContextPtr pRenderContext( materials ); + + //Bind the material + IMesh* pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + CMeshBuilder meshBuilder; + + Vector tmp; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 8, 24 ); + + // 2 5 + // 0 1 4 7 + // 3 6 + + // 0,2,1 - 0,1,3 - 7,4,5 - 7,6,4 - 1,4,6, 1,6,3 - 1,5,4 - 1,2,5 + + // v0 + meshBuilder.Position3fv( start.Base() ); + meshBuilder.TexCoord2f( 0, 0.5f, 0.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + // v1 + Vector v1 = start + t0 * lineDir; + meshBuilder.Position3fv( v1.Base() ); + meshBuilder.TexCoord2f( 0, 0.5f, t0 ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + // v2 + tmp = v1 - scale*cross; + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, t0 ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + // v3 + tmp = v1 + scale*cross; + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, t0 ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + // v4 + Vector v4 = start + t1 * lineDir; + meshBuilder.Position3fv( v4.Base() ); + meshBuilder.TexCoord2f( 0, 0.5f, t1 ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + // v5 + tmp = v4 - scale*cross; + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, t1 ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + // v6 + tmp = v4 + scale*cross; + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, t1 ); + meshBuilder.Color4ub( color.r, color.g, color.b, color.a ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + // v7 + meshBuilder.Position3fv( end.Base() ); + meshBuilder.TexCoord2f( 0, 0.5f, 1.0f ); + meshBuilder.Color4ub( 0, 0, 0, 0 ); + meshBuilder.Normal3fv( cross.Base() ); + meshBuilder.AdvanceVertex(); + + + // triangles - 0,2,1 - 0,1,3 - 7,4,5 - 7,6,4 - 1,4,6, 1,6,3 - 1,5,4 - 1,2,5 + meshBuilder.FastIndex( 0 ); meshBuilder.FastIndex( 2 ); meshBuilder.FastIndex( 1 ); + meshBuilder.FastIndex( 0 ); meshBuilder.FastIndex( 1 ); meshBuilder.FastIndex( 3 ); + + meshBuilder.FastIndex( 7 ); meshBuilder.FastIndex( 4 ); meshBuilder.FastIndex( 5 ); + meshBuilder.FastIndex( 7 ); meshBuilder.FastIndex( 6 ); meshBuilder.FastIndex( 4 ); + + meshBuilder.FastIndex( 1 ); meshBuilder.FastIndex( 4 ); meshBuilder.FastIndex( 6 ); + meshBuilder.FastIndex( 1 ); meshBuilder.FastIndex( 6 ); meshBuilder.FastIndex( 3 ); + meshBuilder.FastIndex( 1 ); meshBuilder.FastIndex( 5 ); meshBuilder.FastIndex( 4 ); + meshBuilder.FastIndex( 1 ); meshBuilder.FastIndex( 2 ); meshBuilder.FastIndex( 5 ); + + meshBuilder.End(); + pMesh->Draw(); +} diff --git a/game/client/fx_line.h b/game/client/fx_line.h new file mode 100644 index 00000000..ed0b410b --- /dev/null +++ b/game/client/fx_line.h @@ -0,0 +1,59 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#if !defined( FXLINE_H ) +#define FXLINE_H +#ifdef _WIN32 +#pragma once +#endif + +struct FXLineData_t +{ + Vector m_vecStart; + Vector m_vecEnd; + Vector m_vecStartVelocity; + Vector m_vecEndVelocity; + float m_flStartAlpha; + float m_flEndAlpha; + float m_flStartScale; + float m_flEndScale; + float m_flDieTime; + float m_flLifeTime; + + IMaterial *m_pMaterial; +}; + +#include "FX_StaticLine.h" + +class CFXLine : public CClientSideEffect +{ +public: + + CFXLine( const char *name, const FXLineData_t &data ); + + ~CFXLine( void ); + + virtual void Draw( double frametime ); + virtual bool IsActive( void ); + virtual void Destroy( void ); + virtual void Update( double frametime ); + +protected: + + FXLineData_t m_FXData; +}; + +void FX_DrawLine( const Vector &start, const Vector &end, float scale, IMaterial *pMaterial, const color32 &color ); +void FX_DrawLineFade( const Vector &start, const Vector &end, float scale, IMaterial *pMaterial, const color32 &color, float fadeDist ); + +#endif //FXLINE_H \ No newline at end of file diff --git a/game/client/fx_quad.cpp b/game/client/fx_quad.cpp new file mode 100644 index 00000000..2932f8f5 --- /dev/null +++ b/game/client/fx_quad.cpp @@ -0,0 +1,170 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "view.h" +#include "materialsystem/IMesh.h" +#include "fx_quad.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CFXQuad::CFXQuad( const FXQuadData_t &data ) + +: CClientSideEffect( "Quad" ) +{ + m_FXData = data; +} + +CFXQuad::~CFXQuad( void ) +{ + Destroy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : frametime - +//----------------------------------------------------------------------------- +void CFXQuad::Draw( double frametime ) +{ + VPROF_BUDGET( "FX_Quad::Draw", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + // Update the effect + Update( frametime ); + + float scaleTimePerc, alphaTimePerc; + + //Determine the scale + if ( m_FXData.m_uiFlags & FXQUAD_BIAS_SCALE ) + { + scaleTimePerc = Bias( ( m_FXData.m_flLifeTime / m_FXData.m_flDieTime ), m_FXData.m_flScaleBias ); + } + else + { + scaleTimePerc = ( m_FXData.m_flLifeTime / m_FXData.m_flDieTime ); + } + + float scale = m_FXData.m_flStartScale + ( ( m_FXData.m_flEndScale - m_FXData.m_flStartScale ) * scaleTimePerc ); + + //Determine the alpha + if ( m_FXData.m_uiFlags & FXQUAD_BIAS_ALPHA ) + { + alphaTimePerc = Bias( ( m_FXData.m_flLifeTime / m_FXData.m_flDieTime ), m_FXData.m_flAlphaBias ); + } + else + { + alphaTimePerc = ( m_FXData.m_flLifeTime / m_FXData.m_flDieTime ); + } + + float alpha = m_FXData.m_flStartAlpha + ( ( m_FXData.m_flEndAlpha - m_FXData.m_flStartAlpha ) * alphaTimePerc ); + alpha = clamp( alpha, 0.0f, 1.0f ); + + CMatRenderContextPtr pRenderContext( materials ); + + //Bind the material + IMesh* pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_FXData.m_pMaterial ); + CMeshBuilder meshBuilder; + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + //Update our roll + m_FXData.m_flYaw = anglemod( m_FXData.m_flYaw + ( m_FXData.m_flDeltaYaw * frametime ) ); + + Vector pos; + Vector vRight, vUp; + + float color[4]; + + color[0] = m_FXData.m_Color[0]; + color[1] = m_FXData.m_Color[1]; + color[2] = m_FXData.m_Color[2]; + + if ( m_FXData.m_uiFlags & FXQUAD_COLOR_FADE ) + { + color[0] *= alpha; + color[1] *= alpha; + color[2] *= alpha; + } + + color[3] = alpha; + + VectorVectors( m_FXData.m_vecNormal, vRight, vUp ); + + Vector rRight, rUp; + + rRight = ( vRight * cos( DEG2RAD( m_FXData.m_flYaw ) ) ) - ( vUp * sin( DEG2RAD( m_FXData.m_flYaw ) ) ); + rUp = ( vRight * cos( DEG2RAD( m_FXData.m_flYaw+90.0f ) ) ) - ( vUp * sin( DEG2RAD( m_FXData.m_flYaw+90.0f ) ) ); + + vRight = rRight * ( scale * 0.5f ); + vUp = rUp * ( scale * 0.5f ); + + pos = m_FXData.m_vecOrigin + vRight - vUp; + + meshBuilder.Position3fv( pos.Base() ); + meshBuilder.Normal3fv( m_FXData.m_vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.Color4fv( color ); + meshBuilder.AdvanceVertex(); + + pos = m_FXData.m_vecOrigin - vRight - vUp; + + meshBuilder.Position3fv( pos.Base() ); + meshBuilder.Normal3fv( m_FXData.m_vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.Color4fv( color ); + meshBuilder.AdvanceVertex(); + + pos = m_FXData.m_vecOrigin - vRight + vUp; + + meshBuilder.Position3fv( pos.Base() ); + meshBuilder.Normal3fv( m_FXData.m_vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4fv( color ); + meshBuilder.AdvanceVertex(); + + pos = m_FXData.m_vecOrigin + vRight + vUp; + + meshBuilder.Position3fv( pos.Base() ); + meshBuilder.Normal3fv( m_FXData.m_vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4fv( color ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFXQuad::IsActive( void ) +{ + return ( m_FXData.m_flLifeTime < m_FXData.m_flDieTime ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFXQuad::Destroy( void ) +{ + //Release the material + if ( m_FXData.m_pMaterial != NULL ) + { + m_FXData.m_pMaterial->DecrementReferenceCount(); + m_FXData.m_pMaterial = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : frametime - +//----------------------------------------------------------------------------- +void CFXQuad::Update( double frametime ) +{ + m_FXData.m_flLifeTime += frametime; +} diff --git a/game/client/fx_quad.h b/game/client/fx_quad.h new file mode 100644 index 00000000..38b927db --- /dev/null +++ b/game/client/fx_quad.h @@ -0,0 +1,90 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "clientsideeffects.h" + +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMaterialSystem.h" + +#ifndef FX_QUAD_H +#define FX_QUAD_H +#ifdef _WIN32 +#pragma once +#endif + +// Flags +#define FXQUAD_BIAS_SCALE 0x0001 //Bias the scale's interpolation function +#define FXQUAD_BIAS_ALPHA 0x0002 //Bias the alpha's interpolation function +#define FXQUAD_COLOR_FADE 0x0004 //Blend the color towards black via the alpha (overcomes additive ignoring alpha) + +struct FXQuadData_t +{ + FXQuadData_t( void ) + { + m_flLifeTime = 0.0f; + m_flDieTime = 0.0f; + m_uiFlags = 0; + } + + void SetFlags( unsigned int flags ) { m_uiFlags |= flags; } + void SetOrigin( const Vector &origin ) { m_vecOrigin = origin; } + void SetNormal( const Vector &normal ) { m_vecNormal = normal; } + void SetScale( float start, float end ) { m_flStartScale = start; m_flEndScale = end; } + void SetAlpha( float start, float end ) { m_flStartAlpha = start; m_flEndAlpha = end; } + void SetLifeTime( float lifetime ) { m_flDieTime = lifetime; } + void SetColor( float r, float g, float b ) { m_Color = Vector( r, g, b ); } + void SetAlphaBias( float bias ) { m_flAlphaBias = bias; } + void SetScaleBias( float bias ) { m_flScaleBias = bias; } + void SetYaw( float yaw, float delta = 0.0f ){ m_flYaw = yaw; m_flDeltaYaw = delta; } + + void SetMaterial( const char *shader ) + { + m_pMaterial = materials->FindMaterial( shader, TEXTURE_GROUP_CLIENT_EFFECTS ); + + if ( m_pMaterial != NULL ) + { + m_pMaterial->IncrementReferenceCount(); + } + } + + unsigned int m_uiFlags; + IMaterial *m_pMaterial; + Vector m_vecOrigin; + Vector m_vecNormal; + float m_flStartScale; + float m_flEndScale; + float m_flDieTime; + float m_flLifeTime; + float m_flStartAlpha; + float m_flEndAlpha; + Vector m_Color; + float m_flYaw; + float m_flDeltaYaw; + + // Only used with FXQUAD_BIAS_ALPHA and FXQUAD_BIAS_SCALE + float m_flScaleBias; + float m_flAlphaBias; +}; + +class CFXQuad : public CClientSideEffect +{ +public: + + CFXQuad( const FXQuadData_t &data ); + + ~CFXQuad( void ); + + virtual void Draw( double frametime ); + virtual bool IsActive( void ); + virtual void Destroy( void ); + virtual void Update( double frametime ); + +protected: + + FXQuadData_t m_FXData; +}; + +#endif // FX_QUAD_H diff --git a/game/client/fx_shelleject.cpp b/game/client/fx_shelleject.cpp new file mode 100644 index 00000000..5a0542ea --- /dev/null +++ b/game/client/fx_shelleject.cpp @@ -0,0 +1,59 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "fx.h" +#include "c_te_effect_dispatch.h" +#include "c_te_legacytempents.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ShellEjectCallback( const CEffectData &data ) +{ + // Use the gun angles to orient the shell + IClientRenderable *pRenderable = data.GetRenderable(); + if ( pRenderable ) + { + tempents->EjectBrass( data.m_vOrigin, data.m_vAngles, pRenderable->GetRenderAngles(), 0 ); + } +} + +DECLARE_CLIENT_EFFECT( "ShellEject", ShellEjectCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RifleShellEjectCallback( const CEffectData &data ) +{ + // Use the gun angles to orient the shell + IClientRenderable *pRenderable = data.GetRenderable(); + if ( pRenderable ) + { + tempents->EjectBrass( data.m_vOrigin, data.m_vAngles, pRenderable->GetRenderAngles(), 1 ); + } +} + +DECLARE_CLIENT_EFFECT( "RifleShellEject", RifleShellEjectCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ShotgunShellEjectCallback( const CEffectData &data ) +{ + // Use the gun angles to orient the shell + IClientRenderable *pRenderable = data.GetRenderable(); + if ( pRenderable ) + { + tempents->EjectBrass( data.m_vOrigin, data.m_vAngles, pRenderable->GetRenderAngles(), 2 ); + } +} + +DECLARE_CLIENT_EFFECT( "ShotgunShellEject", ShotgunShellEjectCallback ); + + diff --git a/game/client/fx_sparks.cpp b/game/client/fx_sparks.cpp new file mode 100644 index 00000000..ebc373a9 --- /dev/null +++ b/game/client/fx_sparks.cpp @@ -0,0 +1,1534 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "view.h" +#include "c_tracer.h" +#include "dlight.h" +#include "ClientEffectPrecacheSystem.h" +#include "FX_Sparks.h" +#include "iefx.h" +#include "c_te_effect_dispatch.h" +#include "tier0/vprof.h" +#include "fx_quad.h" +#include "fx.h" +#include "c_pixel_visibility.h" +#include "particles_ez.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//Precahce the effects +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectSparks ) +CLIENTEFFECT_MATERIAL( "effects/spark" ) +CLIENTEFFECT_MATERIAL( "effects/energysplash" ) +CLIENTEFFECT_MATERIAL( "effects/energyball" ) +CLIENTEFFECT_MATERIAL( "sprites/rico1" ) +CLIENTEFFECT_MATERIAL( "sprites/rico1_noz" ) +CLIENTEFFECT_MATERIAL( "sprites/blueflare1" ) +CLIENTEFFECT_MATERIAL( "effects/yellowflare" ) +CLIENTEFFECT_MATERIAL( "effects/combinemuzzle1_nocull" ) +CLIENTEFFECT_MATERIAL( "effects/combinemuzzle2_nocull" ) +CLIENTEFFECT_MATERIAL( "effects/yellowflare_noz" ) +CLIENTEFFECT_REGISTER_END() + +PMaterialHandle g_Material_Spark = NULL; + +static ConVar fx_drawmetalspark( "fx_drawmetalspark", "1", FCVAR_DEVELOPMENTONLY, "Draw metal spark effects." ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &pos - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool EffectOccluded( const Vector &pos, pixelvis_handle_t *queryHandle ) +{ + if ( !queryHandle ) + { + // NOTE: This is called by networking code before the current view is set up. + // so use the main view instead + trace_t tr; + UTIL_TraceLine( pos, MainViewOrigin(), MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr ); + + return ( tr.fraction < 1.0f ) ? true : false; + } + pixelvis_queryparams_t params; + params.Init(pos); + + return PixelVisibility_FractionVisible( params, queryHandle ) > 0.0f ? false : true; +} + + +CSimpleGlowEmitter::CSimpleGlowEmitter( const char *pDebugName, const Vector &sortOrigin, float flDeathTime ) + : CSimpleEmitter( pDebugName ) +{ + SetSortOrigin( sortOrigin ); + m_queryHandle = 0; + m_wasTested = 0; + m_isVisible = 0; + m_startTime = gpGlobals->curtime; + m_flDeathTime = flDeathTime; +} + +CSimpleGlowEmitter *CSimpleGlowEmitter::Create( const char *pDebugName, const Vector &sortOrigin, float flDeathTime ) +{ + return new CSimpleGlowEmitter( pDebugName, sortOrigin, flDeathTime ); +} + + +void CSimpleGlowEmitter::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + if ( gpGlobals->curtime > m_flDeathTime ) + { + pIterator->RemoveAllParticles(); + return; + } + + if ( !WasTestedInView(1<<0) ) + return; + + BaseClass::SimulateParticles( pIterator ); +} + +bool CSimpleGlowEmitter::WasTestedInView( unsigned char viewMask ) +{ + return (m_wasTested & viewMask) ? true : false; +} + +bool CSimpleGlowEmitter::IsVisibleInView( unsigned char viewMask ) +{ + return (m_isVisible & viewMask) ? true : false; +} + +void CSimpleGlowEmitter::SetTestedInView( unsigned char viewMask, bool bTested ) +{ + m_wasTested &= ~viewMask; + if ( bTested ) + { + m_wasTested |= viewMask; + } +} + +void CSimpleGlowEmitter::SetVisibleInView( unsigned char viewMask, bool bVisible ) +{ + m_isVisible &= ~viewMask; + if ( bVisible ) + { + m_isVisible |= viewMask; + } +} + +unsigned char CSimpleGlowEmitter::CurrentViewMask() const +{ + int viewId = (int)CurrentViewID(); + viewId = clamp(viewId, 0, 7); + return 1<curtime - m_startTime) <= 0.1f ) + return; + SetVisibleInView(viewMask, false); + } + else + { + SetVisibleInView(viewMask, true); + } + + SetTestedInView(viewMask, true); + } + if ( !IsVisibleInView(viewMask) ) + return; + + BaseClass::RenderParticles( pIterator ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CTrailParticles::CTrailParticles( const char *pDebugName ) : CSimpleEmitter( pDebugName ) +{ + m_fFlags = 0; + m_flVelocityDampen = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Test for surrounding collision surfaces for quick collision testing for the particle system +// Input : &origin - starting position +// *dir - direction of movement (if NULL, will do a point emission test in four directions) +// angularSpread - looseness of the spread +// minSpeed - minimum speed +// maxSpeed - maximum speed +// gravity - particle gravity for the sytem +// dampen - dampening amount on collisions +// flags - extra information +//----------------------------------------------------------------------------- +void CTrailParticles::Setup( const Vector &origin, const Vector *direction, float angularSpread, float minSpeed, float maxSpeed, float gravity, float dampen, int flags, bool bNotCollideable ) +{ + //Take the flags + if ( !bNotCollideable ) + { + SetFlag( (flags|bitsPARTICLE_TRAIL_COLLIDE) ); //Force this if they've called this function + } + else + { + SetFlag( flags ); + } + + //See if we've specified a direction + m_ParticleCollision.Setup( origin, direction, angularSpread, minSpeed, maxSpeed, gravity, dampen ); +} + + +void CTrailParticles::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const TrailParticle *pParticle = (const TrailParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + //Get our remaining time + float lifePerc = 1.0f - ( pParticle->m_flLifetime / pParticle->m_flDieTime ); + float scale = (pParticle->m_flLength*lifePerc); + + if ( scale < 0.01f ) + scale = 0.01f; + + Vector start, delta; + + //NOTE: We need to do everything in screen space + TransformParticle( ParticleMgr()->GetModelView(), pParticle->m_Pos, start ); + float sortKey = start.z; + + Vector3DMultiply( ParticleMgr()->GetModelView(), pParticle->m_vecVelocity, delta ); + + float color[4]; + float ramp = 1.0; + + // Fade in for the first few frames + if ( pParticle->m_flLifetime <= 0.3 && m_fFlags & bitsPARTICLE_TRAIL_FADE_IN ) + { + ramp = pParticle->m_flLifetime; + } + else if ( m_fFlags & bitsPARTICLE_TRAIL_FADE ) + { + ramp = ( 1.0f - ( pParticle->m_flLifetime / pParticle->m_flDieTime ) ); + } + + color[0] = pParticle->m_color.r * ramp * (1.0f / 255.0f); + color[1] = pParticle->m_color.g * ramp * (1.0f / 255.0f); + color[2] = pParticle->m_color.b * ramp * (1.0f / 255.0f); + color[3] = pParticle->m_color.a * ramp * (1.0f / 255.0f); + + float flLength = (pParticle->m_vecVelocity * scale).Length();//( delta - pos ).Length(); + float flWidth = ( flLength < pParticle->m_flWidth ) ? flLength : pParticle->m_flWidth; + + //See if we should fade + Tracer_Draw( pIterator->GetParticleDraw(), start, (delta*scale), flWidth, color ); + + pParticle = (const TrailParticle*)pIterator->GetNext( sortKey ); + } +} + + +void CTrailParticles::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + //Turn off collision if we're not told to do it + if (( m_fFlags & bitsPARTICLE_TRAIL_COLLIDE )==false) + { + m_ParticleCollision.ClearActivePlanes(); + } + + TrailParticle *pParticle = (TrailParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + const float timeDelta = pIterator->GetTimeDelta(); + + //Simulate the movement with collision + trace_t trace; + m_ParticleCollision.MoveParticle( pParticle->m_Pos, pParticle->m_vecVelocity, NULL, timeDelta, &trace ); + + //Laterally dampen if asked to do so + if ( m_fFlags & bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ) + { + float attenuation = 1.0f - (timeDelta * m_flVelocityDampen); + + if ( attenuation < 0.0f ) + attenuation = 0.0f; + + //Laterally dampen + pParticle->m_vecVelocity[0] *= attenuation; + pParticle->m_vecVelocity[1] *= attenuation; + pParticle->m_vecVelocity[2] *= attenuation; + } + + //Should this particle die? + pParticle->m_flLifetime += timeDelta; + + if ( pParticle->m_flLifetime >= pParticle->m_flDieTime ) + pIterator->RemoveParticle( pParticle ); + + pParticle = (TrailParticle*)pIterator->GetNext(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Electric spark +// Input : &pos - origin point of effect +//----------------------------------------------------------------------------- +#define SPARK_ELECTRIC_SPREAD 0.0f +#define SPARK_ELECTRIC_MINSPEED 64.0f +#define SPARK_ELECTRIC_MAXSPEED 300.0f +#define SPARK_ELECTRIC_GRAVITY 800.0f +#define SPARK_ELECTRIC_DAMPEN 0.3f + +void FX_ElectricSpark( const Vector &pos, int nMagnitude, int nTrailLength, const Vector *vecDir ) +{ + VPROF_BUDGET( "FX_ElectricSpark", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSparkEmitter = CTrailParticles::Create( "FX_ElectricSpark 1" ); + + if ( !pSparkEmitter ) + { + Assert(0); + return; + } + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = pSparkEmitter->GetPMaterial( "effects/spark" ); + } + + //Setup our collision information + pSparkEmitter->Setup( (Vector &) pos, + NULL, + SPARK_ELECTRIC_SPREAD, + SPARK_ELECTRIC_MINSPEED, + SPARK_ELECTRIC_MAXSPEED, + SPARK_ELECTRIC_GRAVITY, + SPARK_ELECTRIC_DAMPEN, + bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + pSparkEmitter->SetSortOrigin( pos ); + + // + // Big sparks. + // + Vector dir; + int numSparks = nMagnitude * nMagnitude * random->RandomFloat( 2, 4 ); + + int i; + TrailParticle *pParticle; + for ( i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) pSparkEmitter->AddParticle( sizeof(TrailParticle), g_Material_Spark, pos ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = nMagnitude * random->RandomFloat( 1.0f, 2.0f ); + + dir.Random( -1.0f, 1.0f ); + dir[2] = random->RandomFloat( 0.5f, 1.0f ); + + if ( vecDir ) + { + dir += 2 * (*vecDir); + VectorNormalize( dir ); + } + + pParticle->m_flWidth = random->RandomFloat( 2.0f, 5.0f ); + pParticle->m_flLength = nTrailLength * random->RandomFloat( 0.02, 0.05f ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( SPARK_ELECTRIC_MINSPEED, SPARK_ELECTRIC_MAXSPEED ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + +#ifdef _XBOX + + // + // Cap + // + + SimpleParticle sParticle; + + sParticle.m_Pos = pos; + sParticle.m_flLifetime = 0.0f; + sParticle.m_flDieTime = 0.2f; + + sParticle.m_vecVelocity.Init(); + + sParticle.m_uchColor[0] = 255; + sParticle.m_uchColor[1] = 255; + sParticle.m_uchColor[2] = 255; + sParticle.m_uchStartAlpha = 255; + sParticle.m_uchEndAlpha = 255; + sParticle.m_uchStartSize = nMagnitude * random->RandomInt( 4, 8 ); + sParticle.m_uchEndSize = 0; + sParticle.m_flRoll = random->RandomInt( 0, 360 ); + sParticle.m_flRollDelta = 0.0f; + + AddSimpleParticle( &sParticle, ParticleMgr()->GetPMaterial( "effects/yellowflare" ) ); + +#else + + // + // Little sparks + // + + CSmartPtr pSparkEmitter2 = CTrailParticles::Create( "FX_ElectricSpark 2" ); + + if ( !pSparkEmitter2 ) + { + Assert(0); + return; + } + + pSparkEmitter2->SetSortOrigin( pos ); + + pSparkEmitter2->m_ParticleCollision.SetGravity( 400.0f ); + pSparkEmitter2->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + numSparks = nMagnitude * random->RandomInt( 16, 32 ); + + // Dump out sparks + for ( i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) pSparkEmitter2->AddParticle( sizeof(TrailParticle), g_Material_Spark, pos ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + + dir.Random( -1.0f, 1.0f ); + if ( vecDir ) + { + dir += *vecDir; + VectorNormalize( dir ); + } + + pParticle->m_flWidth = random->RandomFloat( 2.0f, 4.0f ); + pParticle->m_flLength = nTrailLength * random->RandomFloat( 0.02f, 0.03f ); + pParticle->m_flDieTime = nMagnitude * random->RandomFloat( 0.1f, 0.2f ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( 128, 256 ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + + // + // Caps + // + CSmartPtr pSimple = CSimpleGlowEmitter::Create( "FX_ElectricSpark 3", pos, gpGlobals->curtime + 0.2 ); + + // NOTE: None of these will render unless the effect is visible! + // + // Inner glow + // + SimpleParticle *sParticle; + + sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/yellowflare_noz" ), pos ); + + if ( sParticle == NULL ) + return; + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 0.2f; + + sParticle->m_vecVelocity.Init(); + + sParticle->m_uchColor[0] = 255; + sParticle->m_uchColor[1] = 255; + sParticle->m_uchColor[2] = 255; + sParticle->m_uchStartAlpha = 255; + sParticle->m_uchEndAlpha = 255; + sParticle->m_uchStartSize = nMagnitude * random->RandomInt( 4, 8 ); + sParticle->m_uchEndSize = 0; + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = 0.0f; + + sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/yellowflare_noz" ), pos ); + + if ( sParticle == NULL ) + return; + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 0.2f; + + sParticle->m_vecVelocity.Init(); + + float fColor = random->RandomInt( 32, 64 ); + sParticle->m_uchColor[0] = fColor; + sParticle->m_uchColor[1] = fColor; + sParticle->m_uchColor[2] = fColor; + sParticle->m_uchStartAlpha = fColor; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = nMagnitude * random->RandomInt( 32, 64 ); + sParticle->m_uchEndSize = 0; + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -1.0f, 1.0f ); + + // + // Smoke + // + Vector sOffs; + + sOffs[0] = pos[0] + random->RandomFloat( -4.0f, 4.0f ); + sOffs[1] = pos[1] + random->RandomFloat( -4.0f, 4.0f ); + sOffs[2] = pos[2]; + + sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), g_Mat_DustPuff[1], sOffs ); + + if ( sParticle == NULL ) + return; + + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 1.0f; + + sParticle->m_vecVelocity.Init(); + + sParticle->m_vecVelocity[2] = 16.0f; + + sParticle->m_vecVelocity[0] = random->RandomFloat( -16.0f, 16.0f ); + sParticle->m_vecVelocity[1] = random->RandomFloat( -16.0f, 16.0f ); + + sParticle->m_uchColor[0] = 255; + sParticle->m_uchColor[1] = 255; + sParticle->m_uchColor[2] = 200; + sParticle->m_uchStartAlpha = random->RandomInt( 16, 32 ); + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = random->RandomInt( 4, 8 ); + sParticle->m_uchEndSize = sParticle->m_uchStartSize*4.0f; + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = random->RandomFloat( -2.0f, 2.0f ); + + // + // Dlight + // + + /* + dlight_t *dl= effects->CL_AllocDlight ( 0 ); + + dl->origin = pos; + dl->color.r = dl->color.g = dl->color.b = 250; + dl->radius = random->RandomFloat(16,32); + dl->die = gpGlobals->curtime + 0.001; + */ + +#endif // !_XBOX +} + +//----------------------------------------------------------------------------- +// Purpose: Sparks created by scraping metal +// Input : &position - start +// &normal - direction of spark travel +//----------------------------------------------------------------------------- + +#define METAL_SCRAPE_MINSPEED 128.0f +#define METAL_SCRAPE_MAXSPEED 512.0f +#define METAL_SCRAPE_SPREAD 0.3f +#define METAL_SCRAPE_GRAVITY 800.0f +#define METAL_SCRAPE_DAMPEN 0.4f + +void FX_MetalScrape( Vector &position, Vector &normal ) +{ + VPROF_BUDGET( "FX_MetalScrape", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector offset = position + ( normal * 1.0f ); + + CSmartPtr sparkEmitter = CTrailParticles::Create( "FX_MetalScrape 1" ); + + if ( !sparkEmitter ) + return; + + sparkEmitter->SetSortOrigin( offset ); + + //Setup our collision information + sparkEmitter->Setup( offset, + &normal, + METAL_SCRAPE_SPREAD, + METAL_SCRAPE_MINSPEED, + METAL_SCRAPE_MAXSPEED, + METAL_SCRAPE_GRAVITY, + METAL_SCRAPE_DAMPEN, + bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + int numSparks = random->RandomInt( 4, 8 ); + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = sparkEmitter->GetPMaterial( "effects/spark" ); + } + + Vector dir; + TrailParticle *pParticle; + float length = 0.06f; + + //Dump out sparks + for ( int i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), g_Material_Spark, offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + + float spreadOfs = random->RandomFloat( 0.0f, 2.0f ); + + dir[0] = normal[0] + random->RandomFloat( -(METAL_SCRAPE_SPREAD*spreadOfs), (METAL_SCRAPE_SPREAD*spreadOfs) ); + dir[1] = normal[1] + random->RandomFloat( -(METAL_SCRAPE_SPREAD*spreadOfs), (METAL_SCRAPE_SPREAD*spreadOfs) ); + dir[2] = normal[2] + random->RandomFloat( -(METAL_SCRAPE_SPREAD*spreadOfs), (METAL_SCRAPE_SPREAD*spreadOfs) ); + + pParticle->m_flWidth = random->RandomFloat( 2.0f, 5.0f ); + pParticle->m_flLength = random->RandomFloat( length*0.25f, length ); + pParticle->m_flDieTime = random->RandomFloat( 2.0f, 2.0f ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( (METAL_SCRAPE_MINSPEED*(2.0f-spreadOfs)), (METAL_SCRAPE_MAXSPEED*(2.0f-spreadOfs)) ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Ricochet spark on metal +// Input : &position - origin of effect +// &normal - normal of the surface struck +//----------------------------------------------------------------------------- +#define METAL_SPARK_SPREAD 0.5f +#define METAL_SPARK_MINSPEED 128.0f +#define METAL_SPARK_MAXSPEED 512.0f +#define METAL_SPARK_GRAVITY 400.0f +#define METAL_SPARK_DAMPEN 0.25f + +void FX_MetalSpark( const Vector &position, const Vector &direction, const Vector &surfaceNormal, int iScale ) +{ + VPROF_BUDGET( "FX_MetalSpark", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + if ( !fx_drawmetalspark.GetBool() ) + return; + + // + // Emitted particles + // + + Vector offset = position + ( surfaceNormal * 1.0f ); + + CSmartPtr sparkEmitter = CTrailParticles::Create( "FX_MetalSpark 1" ); + + if ( sparkEmitter == NULL ) + return; + + //Setup our information + sparkEmitter->SetSortOrigin( offset ); + sparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + sparkEmitter->SetVelocityDampen( 8.0f ); + sparkEmitter->SetGravity( METAL_SPARK_GRAVITY ); + sparkEmitter->SetCollisionDamped( METAL_SPARK_DAMPEN ); + sparkEmitter->GetBinding().SetBBox( offset - Vector( 32, 32, 32 ), offset + Vector( 32, 32, 32 ) ); + + int numSparks = random->RandomInt( 4, 8 ) * ( iScale * 2 ); + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = sparkEmitter->GetPMaterial( "effects/spark" ); + } + + TrailParticle *pParticle; + Vector dir; + float length = 0.1f; + + //Dump out sparks + for ( int i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), g_Material_Spark, offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + + if( iScale > 1 && i%3 == 0 ) + { + // Every third spark goes flying far if we're having a big batch of sparks. + pParticle->m_flDieTime = random->RandomFloat( 0.15f, 0.25f ); + } + else + { + pParticle->m_flDieTime = random->RandomFloat( 0.05f, 0.1f ); + } + + float spreadOfs = random->RandomFloat( 0.0f, 2.0f ); + + dir[0] = direction[0] + random->RandomFloat( -(METAL_SPARK_SPREAD*spreadOfs), (METAL_SPARK_SPREAD*spreadOfs) ); + dir[1] = direction[1] + random->RandomFloat( -(METAL_SPARK_SPREAD*spreadOfs), (METAL_SPARK_SPREAD*spreadOfs) ); + dir[2] = direction[2] + random->RandomFloat( -(METAL_SPARK_SPREAD*spreadOfs), (METAL_SPARK_SPREAD*spreadOfs) ); + + VectorNormalize( dir ); + + pParticle->m_flWidth = random->RandomFloat( 1.0f, 4.0f ); + pParticle->m_flLength = random->RandomFloat( length*0.25f, length ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( (METAL_SPARK_MINSPEED*(2.0f-spreadOfs)), (METAL_SPARK_MAXSPEED*(2.0f-spreadOfs)) ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + + // + // Impact point glow + // + + FXQuadData_t data; + + data.SetMaterial( "effects/yellowflare" ); + data.SetColor( 1.0f, 1.0f, 1.0f ); + data.SetOrigin( offset ); + data.SetNormal( surfaceNormal ); + data.SetAlpha( 1.0f, 0.0f ); + data.SetLifeTime( 0.1f ); + data.SetYaw( random->RandomInt( 0, 360 ) ); + + int scale = random->RandomInt( 24, 28 ); + data.SetScale( scale, 0 ); + + FX_AddQuad( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: Spark effect. Nothing but sparks. +// Input : &pos - origin point of effect +//----------------------------------------------------------------------------- +#define SPARK_SPREAD 3.0f +#define SPARK_GRAVITY 800.0f +#define SPARK_DAMPEN 0.3f + +void FX_Sparks( const Vector &pos, int nMagnitude, int nTrailLength, const Vector &vecDir, float flWidth, float flMinSpeed, float flMaxSpeed, char *pSparkMaterial ) +{ + VPROF_BUDGET( "FX_Sparks", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + CSmartPtr pSparkEmitter = CTrailParticles::Create( "FX_Sparks 1" ); + + if ( !pSparkEmitter ) + { + Assert(0); + return; + } + + PMaterialHandle hMaterial; + if ( pSparkMaterial ) + { + hMaterial = pSparkEmitter->GetPMaterial( pSparkMaterial ); + } + else + { + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = pSparkEmitter->GetPMaterial( "effects/spark" ); + } + + hMaterial = g_Material_Spark; + } + + //Setup our collision information + pSparkEmitter->Setup( (Vector &) pos, + NULL, + SPARK_SPREAD, + flMinSpeed, + flMaxSpeed, + SPARK_GRAVITY, + SPARK_DAMPEN, + bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + pSparkEmitter->SetSortOrigin( pos ); + + // + // Big sparks. + // + Vector dir; + int numSparks = nMagnitude * nMagnitude * random->RandomFloat( 2, 4 ); + + int i; + TrailParticle *pParticle; + for ( i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) pSparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, pos ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = nMagnitude * random->RandomFloat( 1.0f, 2.0f ); + + float spreadOfs = random->RandomFloat( 0.0f, 2.0f ); + dir[0] = vecDir[0] + random->RandomFloat( -(SPARK_SPREAD*spreadOfs), (SPARK_SPREAD*spreadOfs) ); + dir[1] = vecDir[1] + random->RandomFloat( -(SPARK_SPREAD*spreadOfs), (SPARK_SPREAD*spreadOfs) ); + dir[2] = vecDir[2] + random->RandomFloat( -(SPARK_SPREAD*spreadOfs), (SPARK_SPREAD*spreadOfs) ); + pParticle->m_vecVelocity = dir * random->RandomFloat( (flMinSpeed*(2.0f-spreadOfs)), (flMaxSpeed*(2.0f-spreadOfs)) ); + + pParticle->m_flWidth = flWidth + random->RandomFloat( 0.0f, 0.5f ); + pParticle->m_flLength = nTrailLength * random->RandomFloat( 0.02, 0.05f ); + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + + // + // Little sparks + // + CSmartPtr pSparkEmitter2 = CTrailParticles::Create( "FX_ElectricSpark 2" ); + + if ( !pSparkEmitter2 ) + { + Assert(0); + return; + } + + if ( pSparkMaterial ) + { + hMaterial = pSparkEmitter->GetPMaterial( pSparkMaterial ); + } + else + { + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = pSparkEmitter2->GetPMaterial( "effects/spark" ); + } + + hMaterial = g_Material_Spark; + } + + pSparkEmitter2->SetSortOrigin( pos ); + + pSparkEmitter2->m_ParticleCollision.SetGravity( 400.0f ); + pSparkEmitter2->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + numSparks = nMagnitude * random->RandomInt( 4, 8 ); + + // Dump out sparks + for ( i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) pSparkEmitter2->AddParticle( sizeof(TrailParticle), hMaterial, pos ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + + dir.Random( -1.0f, 1.0f ); + dir += vecDir; + VectorNormalize( dir ); + + pParticle->m_flWidth = (flWidth * 0.25) + random->RandomFloat( 0.0f, 0.5f ); + pParticle->m_flLength = nTrailLength * random->RandomFloat( 0.02f, 0.03f ); + pParticle->m_flDieTime = nMagnitude * random->RandomFloat( 0.3f, 0.5f ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( flMinSpeed, flMaxSpeed ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Energy splash for plasma/beam weapon impacts +// Input : &pos - origin point of effect +//----------------------------------------------------------------------------- +#define ENERGY_SPLASH_SPREAD 0.7f +#define ENERGY_SPLASH_MINSPEED 128.0f +#define ENERGY_SPLASH_MAXSPEED 160.0f +#define ENERGY_SPLASH_GRAVITY 800.0f +#define ENERGY_SPLASH_DAMPEN 0.3f + +void FX_EnergySplash( const Vector &pos, const Vector &normal, int nFlags ) +{ + VPROF_BUDGET( "FX_EnergySplash", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector offset = pos + ( normal * 2.0f ); + + // Quick flash + FX_AddQuad( pos, + normal, + 64.0f, + 0, + 0.75f, + 1.0f, + 0.0f, + 0.4f, + random->RandomInt( 0, 360 ), + 0, + Vector( 1.0f, 1.0f, 1.0f ), + 0.25f, + "effects/combinemuzzle1_nocull", + (FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA) ); + + // Lingering burn + FX_AddQuad( pos, + normal, + 16, + 32, + 0.75f, + 1.0f, + 0.0f, + 0.4f, + random->RandomInt( 0, 360 ), + 0, + Vector( 1.0f, 1.0f, 1.0f ), + 0.5f, + "effects/combinemuzzle2_nocull", + (FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA) ); + + SimpleParticle *sParticle; + + CSmartPtr pEmitter; + + pEmitter = CSimpleEmitter::Create( "C_EntityDissolve" ); + pEmitter->SetSortOrigin( pos ); + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = pEmitter->GetPMaterial( "effects/spark" ); + } + + // Anime ground effects + for ( int j = 0; j < 8; j++ ) + { + offset.x = random->RandomFloat( -8.0f, 8.0f ); + offset.y = random->RandomFloat( -8.0f, 8.0f ); + offset.z = random->RandomFloat( 0.0f, 4.0f ); + + offset += pos; + + sParticle = (SimpleParticle *) pEmitter->AddParticle( sizeof(SimpleParticle), g_Material_Spark, offset ); + + if ( sParticle == NULL ) + return; + + sParticle->m_vecVelocity = Vector( Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( 16.0f, 64.0f ) ); + + sParticle->m_uchStartSize = random->RandomFloat( 2, 4 ); + + sParticle->m_flDieTime = random->RandomFloat( 0.4f, 0.6f ); + + sParticle->m_flLifetime = 0.0f; + + sParticle->m_flRoll = Helper_RandomInt( 0, 360 ); + + float alpha = 255; + + sParticle->m_flRollDelta = Helper_RandomFloat( -4.0f, 4.0f ); + sParticle->m_uchColor[0] = alpha; + sParticle->m_uchColor[1] = alpha; + sParticle->m_uchColor[2] = alpha; + sParticle->m_uchStartAlpha = alpha; + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchEndSize = 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Micro-Explosion effect +// Input : &position - origin of effect +// &normal - normal of the surface struck +//----------------------------------------------------------------------------- + +#define MICRO_EXPLOSION_MINSPEED 100.0f +#define MICRO_EXPLOSION_MAXSPEED 150.0f +#define MICRO_EXPLOSION_SPREAD 1.0f +#define MICRO_EXPLOSION_GRAVITY 0.0f +#define MICRO_EXPLOSION_DAMPEN 0.4f + +void FX_MicroExplosion( Vector &position, Vector &normal ) +{ + VPROF_BUDGET( "FX_MicroExplosion", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector offset = position + ( normal * 2.0f ); + + CSmartPtr sparkEmitter = CTrailParticles::Create( "FX_MicroExplosion 1" ); + + if ( !sparkEmitter ) + return; + + sparkEmitter->SetSortOrigin( offset ); + + //Setup our collision information + sparkEmitter->Setup( offset, + &normal, + MICRO_EXPLOSION_SPREAD, + MICRO_EXPLOSION_MINSPEED, + MICRO_EXPLOSION_MAXSPEED, + MICRO_EXPLOSION_GRAVITY, + MICRO_EXPLOSION_DAMPEN, + bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + int numSparks = random->RandomInt( 8, 16 ); + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = sparkEmitter->GetPMaterial( "effects/spark" ); + } + + TrailParticle *pParticle; + Vector dir, vOfs; + float length = 0.2f; + + //Fast lines + for ( int i = 0; i < numSparks; i++ ) + { + pParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), g_Material_Spark, offset ); + + if ( pParticle ) + { + pParticle->m_flLifetime = 0.0f; + + float ramp = ( (float) i / (float)numSparks ); + + dir[0] = normal[0] + random->RandomFloat( -MICRO_EXPLOSION_SPREAD*ramp, MICRO_EXPLOSION_SPREAD*ramp ); + dir[1] = normal[1] + random->RandomFloat( -MICRO_EXPLOSION_SPREAD*ramp, MICRO_EXPLOSION_SPREAD*ramp ); + dir[2] = normal[2] + random->RandomFloat( -MICRO_EXPLOSION_SPREAD*ramp, MICRO_EXPLOSION_SPREAD*ramp ); + + pParticle->m_flWidth = random->RandomFloat( 5.0f, 10.0f ); + pParticle->m_flLength = (length*((1.0f-ramp)*(1.0f-ramp)*0.5f)); + pParticle->m_flDieTime = 0.2f; + pParticle->m_vecVelocity = dir * random->RandomFloat( MICRO_EXPLOSION_MINSPEED*(1.5f-ramp), MICRO_EXPLOSION_MAXSPEED*(1.5f-ramp) ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + } + + // + // Filler + // + + CSmartPtr pSimple = CSimpleEmitter::Create( "FX_MicroExplosion 2" ); + pSimple->SetSortOrigin( offset ); + + SimpleParticle *sParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "sprites/rico1" ), offset ); + + if ( sParticle ) + { + sParticle->m_flLifetime = 0.0f; + sParticle->m_flDieTime = 0.3f; + + sParticle->m_vecVelocity.Init(); + + sParticle->m_uchColor[0] = 255; + sParticle->m_uchColor[1] = 255; + sParticle->m_uchColor[2] = 255; + sParticle->m_uchStartAlpha = random->RandomInt( 128, 255 ); + sParticle->m_uchEndAlpha = 0; + sParticle->m_uchStartSize = random->RandomInt( 12, 16 ); + sParticle->m_uchEndSize = sParticle->m_uchStartSize; + sParticle->m_flRoll = random->RandomInt( 0, 360 ); + sParticle->m_flRollDelta = 0.0f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Ugly prototype explosion effect +//----------------------------------------------------------------------------- +#define EXPLOSION_MINSPEED 300.0f +#define EXPLOSION_MAXSPEED 300.0f +#define EXPLOSION_SPREAD 0.8f +#define EXPLOSION_GRAVITY 800.0f +#define EXPLOSION_DAMPEN 0.4f + +#define EXPLOSION_FLECK_MIN_SPEED 150.0f +#define EXPLOSION_FLECK_MAX_SPEED 350.0f +#define EXPLOSION_FLECK_GRAVITY 800.0f +#define EXPLOSION_FLECK_DAMPEN 0.3f +#define EXPLOSION_FLECK_ANGULAR_SPRAY 0.8f + +void FX_Explosion( Vector& origin, Vector& normal, char materialType ) +{ + VPROF_BUDGET( "FX_Explosion", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector offset = origin + ( normal * 2.0f ); + + CSmartPtr pSparkEmitter = CTrailParticles::Create( "FX_Explosion 1" ); + if ( !pSparkEmitter ) + return; + + // Get color data from our hit point + IMaterial *pTraceMaterial; + Vector diffuseColor, baseColor; + pTraceMaterial = engine->TraceLineMaterialAndLighting( origin, normal * -16.0f, diffuseColor, baseColor ); + // Get final light value + float r = pow( diffuseColor[0], 1.0f/2.2f ) * baseColor[0]; + float g = pow( diffuseColor[1], 1.0f/2.2f ) * baseColor[1]; + float b = pow( diffuseColor[2], 1.0f/2.2f ) * baseColor[2]; + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = pSparkEmitter->GetPMaterial( "effects/spark" ); + } + + // Setup our collision information + pSparkEmitter->Setup( offset, + &normal, + EXPLOSION_SPREAD, + EXPLOSION_MINSPEED, + EXPLOSION_MAXSPEED, + EXPLOSION_GRAVITY, + EXPLOSION_DAMPEN, + bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + pSparkEmitter->SetSortOrigin( offset ); + + Vector dir; + int numSparks = random->RandomInt( 8,16 ); + + // Dump out sparks + int i; + for ( i = 0; i < numSparks; i++ ) + { + TrailParticle *pParticle = (TrailParticle *) pSparkEmitter->AddParticle( sizeof(TrailParticle), g_Material_Spark, offset ); + + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + + pParticle->m_flWidth = random->RandomFloat( 5.0f, 10.0f ); + pParticle->m_flLength = random->RandomFloat( 0.05, 0.1f ); + pParticle->m_flDieTime = random->RandomFloat( 1.0f, 2.0f ); + + dir[0] = normal[0] + random->RandomFloat( -EXPLOSION_SPREAD, EXPLOSION_SPREAD ); + dir[1] = normal[1] + random->RandomFloat( -EXPLOSION_SPREAD, EXPLOSION_SPREAD ); + dir[2] = normal[2] + random->RandomFloat( -EXPLOSION_SPREAD, EXPLOSION_SPREAD ); + pParticle->m_vecVelocity = dir * random->RandomFloat( EXPLOSION_MINSPEED, EXPLOSION_MAXSPEED ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } + + + // Chunks o'dirt + // Only create dirt chunks on concrete/world + if ( materialType == 'C' || materialType == 'W' ) + { + CSmartPtr fleckEmitter = CFleckParticles::Create( "FX_Explosion 10", offset, Vector(5,5,5) ); + if ( !fleckEmitter ) + return; + + // Setup our collision information + fleckEmitter->m_ParticleCollision.Setup( offset, &normal, EXPLOSION_FLECK_ANGULAR_SPRAY, EXPLOSION_FLECK_MIN_SPEED, EXPLOSION_FLECK_MAX_SPEED, EXPLOSION_FLECK_GRAVITY, EXPLOSION_FLECK_DAMPEN ); + + PMaterialHandle *hMaterialArray; + + switch ( materialType ) + { + case 'C': + case 'c': + default: + hMaterialArray = g_Mat_Fleck_Cement; + break; + } + + int numFlecks = random->RandomInt( 48, 64 ); + // Dump out flecks + for ( i = 0; i < numFlecks; i++ ) + { + FleckParticle *pParticle = (FleckParticle *) fleckEmitter->AddParticle( sizeof(FleckParticle), hMaterialArray[random->RandomInt(0,1)], offset ); + + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 3.0f; + dir[0] = normal[0] + random->RandomFloat( -EXPLOSION_FLECK_ANGULAR_SPRAY, EXPLOSION_FLECK_ANGULAR_SPRAY ); + dir[1] = normal[1] + random->RandomFloat( -EXPLOSION_FLECK_ANGULAR_SPRAY, EXPLOSION_FLECK_ANGULAR_SPRAY ); + dir[2] = normal[2] + random->RandomFloat( -EXPLOSION_FLECK_ANGULAR_SPRAY, EXPLOSION_FLECK_ANGULAR_SPRAY ); + pParticle->m_uchSize = random->RandomInt( 2, 6 ); + pParticle->m_vecVelocity = dir * ( random->RandomFloat( EXPLOSION_FLECK_MIN_SPEED, EXPLOSION_FLECK_MAX_SPEED ) * ( 7 - pParticle->m_uchSize ) ); + pParticle->m_flRoll = random->RandomFloat( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( 0, 360 ); + + float colorRamp = random->RandomFloat( 0.75f, 1.5f ); + pParticle->m_uchColor[0] = min( 1.0f, r*colorRamp )*255.0f; + pParticle->m_uchColor[1] = min( 1.0f, g*colorRamp )*255.0f; + pParticle->m_uchColor[2] = min( 1.0f, b*colorRamp )*255.0f; + } + } + + // Large sphere bursts + CSmartPtr pSimpleEmitter = CSimpleEmitter::Create( "FX_Explosion 1" ); + PMaterialHandle hSphereMaterial = g_Mat_DustPuff[1]; + Vector vecBurstOrigin = offset + normal * 8.0; + pSimpleEmitter->SetSortOrigin( vecBurstOrigin ); + SimpleParticle *pSphereParticle = (SimpleParticle *) pSimpleEmitter->AddParticle( sizeof(SimpleParticle), hSphereMaterial, vecBurstOrigin ); + if ( pSphereParticle ) + { + pSphereParticle->m_flLifetime = 0.0f; + pSphereParticle->m_flDieTime = 0.3f; + pSphereParticle->m_uchStartAlpha = 150.0; + pSphereParticle->m_uchEndAlpha = 64.0; + pSphereParticle->m_uchStartSize = 0.0; + pSphereParticle->m_uchEndSize = 255.0; + pSphereParticle->m_vecVelocity = Vector(0,0,0); + + float colorRamp = random->RandomFloat( 0.75f, 1.5f ); + pSphereParticle->m_uchColor[0] = min( 1.0f, r*colorRamp )*255.0f; + pSphereParticle->m_uchColor[1] = min( 1.0f, g*colorRamp )*255.0f; + pSphereParticle->m_uchColor[2] = min( 1.0f, b*colorRamp )*255.0f; + } + + // Throw some smoke balls out around the normal + int numBalls = 12; + Vector vecRight, vecForward, vecUp; + QAngle vecAngles; + VectorAngles( normal, vecAngles ); + AngleVectors( vecAngles, NULL, &vecRight, &vecUp ); + for ( i = 0; i < numBalls; i++ ) + { + SimpleParticle *pParticle = (SimpleParticle *) pSimpleEmitter->AddParticle( sizeof(SimpleParticle), hSphereMaterial, vecBurstOrigin ); + if ( pParticle ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.25f; + pParticle->m_uchStartAlpha = 128.0; + pParticle->m_uchEndAlpha = 64.0; + pParticle->m_uchStartSize = 16.0; + pParticle->m_uchEndSize = 64.0; + + float flAngle = ((float)i * M_PI * 2) / numBalls; + float x = cos( flAngle ); + float y = sin( flAngle ); + pParticle->m_vecVelocity = (vecRight*x + vecUp*y) * 1024.0; + + float colorRamp = random->RandomFloat( 0.75f, 1.5f ); + pParticle->m_uchColor[0] = min( 1.0f, r*colorRamp )*255.0f; + pParticle->m_uchColor[1] = min( 1.0f, g*colorRamp )*255.0f; + pParticle->m_uchColor[2] = min( 1.0f, b*colorRamp )*255.0f; + } + } + + // Create a couple of big, floating smoke clouds + CSmartPtr pSmokeEmitter = CSimpleEmitter::Create( "FX_Explosion 2" ); + pSmokeEmitter->SetSortOrigin( offset ); + for ( i = 0; i < 2; i++ ) + { + SimpleParticle *pParticle = (SimpleParticle *) pSmokeEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[1], offset ); + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 2.0f, 3.0f ); + pParticle->m_uchStartSize = 64; + pParticle->m_uchEndSize = 255; + dir[0] = normal[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = normal[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = normal[2] + random->RandomFloat( -0.8f, 0.8f ); + pParticle->m_vecVelocity = dir * random->RandomFloat( 2.0f, 24.0f )*(i+1); + pParticle->m_uchStartAlpha = 160; + pParticle->m_uchEndAlpha = 0; + pParticle->m_flRoll = random->RandomFloat( 180, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1, 1 ); + + float colorRamp = random->RandomFloat( 0.5f, 1.25f ); + pParticle->m_uchColor[0] = min( 1.0f, r*colorRamp )*255.0f; + pParticle->m_uchColor[1] = min( 1.0f, g*colorRamp )*255.0f; + pParticle->m_uchColor[2] = min( 1.0f, b*colorRamp )*255.0f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : origin - +// normal - +//----------------------------------------------------------------------------- +void FX_ConcussiveExplosion( Vector &origin, Vector &normal ) +{ + VPROF_BUDGET( "FX_ConcussiveExplosion", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + Vector offset = origin + ( normal * 2.0f ); + Vector dir; + int i; + + // + // Smoke + // + + CSmartPtr pSmokeEmitter = CSimpleEmitter::Create( "FX_ConcussiveExplosion 1" ); + pSmokeEmitter->SetSortOrigin( offset ); + + //Quick moving sprites + for ( i = 0; i < 16; i++ ) + { + SimpleParticle *pParticle = (SimpleParticle *) pSmokeEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[1], offset ); + + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 0.2f, 0.4f ); + pParticle->m_uchStartSize = random->RandomInt( 4, 8 ); + pParticle->m_uchEndSize = random->RandomInt( 32, 64 ); + + dir[0] = random->RandomFloat( -1.0f, 1.0f ); + dir[1] = random->RandomFloat( -1.0f, 1.0f ); + dir[2] = random->RandomFloat( -1.0f, 1.0f ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( 64.0f, 128.0f ); + pParticle->m_uchStartAlpha = random->RandomInt( 64, 128 ); + pParticle->m_uchEndAlpha = 0; + pParticle->m_flRoll = random->RandomFloat( 180, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -4, 4 ); + + int colorRamp = random->RandomFloat( 235, 255 ); + pParticle->m_uchColor[0] = colorRamp; + pParticle->m_uchColor[1] = colorRamp; + pParticle->m_uchColor[2] = colorRamp; + } + + //Slow lingering sprites + for ( i = 0; i < 2; i++ ) + { + SimpleParticle *pParticle = (SimpleParticle *) pSmokeEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[1], offset ); + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = random->RandomFloat( 1.0f, 2.0f ); + pParticle->m_uchStartSize = random->RandomInt( 32, 64 ); + pParticle->m_uchEndSize = random->RandomInt( 100, 128 ); + + dir[0] = normal[0] + random->RandomFloat( -0.8f, 0.8f ); + dir[1] = normal[1] + random->RandomFloat( -0.8f, 0.8f ); + dir[2] = normal[2] + random->RandomFloat( -0.8f, 0.8f ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( 16.0f, 32.0f ); + + pParticle->m_uchStartAlpha = random->RandomInt( 32, 64 ); + pParticle->m_uchEndAlpha = 0; + pParticle->m_flRoll = random->RandomFloat( 180, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1, 1 ); + + int colorRamp = random->RandomFloat( 235, 255 ); + pParticle->m_uchColor[0] = colorRamp; + pParticle->m_uchColor[1] = colorRamp; + pParticle->m_uchColor[2] = colorRamp; + } + + + // + // Quick sphere + // + + CSmartPtr pSimple = CSimpleEmitter::Create( "FX_ConcussiveExplosion 2" ); + + pSimple->SetSortOrigin( offset ); + + SimpleParticle *pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof(SimpleParticle), pSimple->GetPMaterial( "effects/blueflare1" ), offset ); + + if ( pParticle ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.1f; + pParticle->m_vecVelocity.Init(); + pParticle->m_flRoll = random->RandomFloat( 180, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1, 1 ); + + pParticle->m_uchColor[0] = pParticle->m_uchColor[1] = pParticle->m_uchColor[2] = 128; + + pParticle->m_uchStartAlpha = 255; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = 16; + pParticle->m_uchEndSize = 64; + } + + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof(SimpleParticle), pSimple->GetPMaterial( "effects/blueflare1" ), offset ); + + if ( pParticle ) + { + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 0.2f; + pParticle->m_vecVelocity.Init(); + pParticle->m_flRoll = random->RandomFloat( 180, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -1, 1 ); + pParticle->m_uchColor[0] = pParticle->m_uchColor[1] = pParticle->m_uchColor[2] = 32; + + pParticle->m_uchStartAlpha = 64; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_uchStartSize = 64; + pParticle->m_uchEndSize = 128; + } + + + // + // Dlight + // + + dlight_t *dl= effects->CL_AllocDlight ( 0 ); + + dl->origin = offset; + dl->color.r = dl->color.g = dl->color.b = 64; + dl->radius = random->RandomFloat(128,256); + dl->die = gpGlobals->curtime + 0.1; + + + // + // Moving lines + // + + TrailParticle *pTrailParticle; + CSmartPtr pSparkEmitter = CTrailParticles::Create( "FX_ConcussiveExplosion 3" ); + PMaterialHandle hMaterial; + int numSparks; + + if ( pSparkEmitter.IsValid() ) + { + hMaterial= pSparkEmitter->GetPMaterial( "effects/blueflare1" ); + + pSparkEmitter->SetSortOrigin( offset ); + pSparkEmitter->m_ParticleCollision.SetGravity( 0.0f ); + + numSparks = random->RandomInt( 16, 32 ); + + //Dump out sparks + for ( i = 0; i < numSparks; i++ ) + { + pTrailParticle = (TrailParticle *) pSparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( pTrailParticle == NULL ) + return; + + pTrailParticle->m_flLifetime = 0.0f; + + dir.Random( -1.0f, 1.0f ); + + pTrailParticle->m_flWidth = random->RandomFloat( 1.0f, 2.0f ); + pTrailParticle->m_flLength = random->RandomFloat( 0.01f, 0.1f ); + pTrailParticle->m_flDieTime = random->RandomFloat( 0.1f, 0.2f ); + + pTrailParticle->m_vecVelocity = dir * random->RandomFloat( 800, 1000 ); + + float colorRamp = random->RandomFloat( 0.75f, 1.0f ); + FloatToColor32( pTrailParticle->m_color, colorRamp, colorRamp, 1.0f, 1.0f ); + } + } + + //Moving particles + CSmartPtr pCollisionEmitter = CTrailParticles::Create( "FX_ConcussiveExplosion 4" ); + + if ( pCollisionEmitter.IsValid() ) + { + //Setup our collision information + pCollisionEmitter->Setup( (Vector &) offset, + NULL, + SPARK_ELECTRIC_SPREAD, + SPARK_ELECTRIC_MINSPEED*6, + SPARK_ELECTRIC_MAXSPEED*6, + -400, + SPARK_ELECTRIC_DAMPEN, + bitsPARTICLE_TRAIL_FADE ); + + pCollisionEmitter->SetSortOrigin( offset ); + + numSparks = random->RandomInt( 8, 16 ); + hMaterial = pCollisionEmitter->GetPMaterial( "effects/blueflare1" ); + + //Dump out sparks + for ( i = 0; i < numSparks; i++ ) + { + pTrailParticle = (TrailParticle *) pCollisionEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( pTrailParticle == NULL ) + return; + + pTrailParticle->m_flLifetime = 0.0f; + + dir.Random( -1.0f, 1.0f ); + dir[2] = random->RandomFloat( 0.0f, 0.75f ); + + pTrailParticle->m_flWidth = random->RandomFloat( 1.0f, 2.0f ); + pTrailParticle->m_flLength = random->RandomFloat( 0.01f, 0.1f ); + pTrailParticle->m_flDieTime = random->RandomFloat( 0.2f, 1.0f ); + + pTrailParticle->m_vecVelocity = dir * random->RandomFloat( 128, 512 ); + + float colorRamp = random->RandomFloat( 0.75f, 1.0f ); + FloatToColor32( pTrailParticle->m_color, colorRamp, colorRamp, 1.0f, 1.0f ); + } + } +} + + +void FX_SparkFan( Vector &position, Vector &normal ) +{ + Vector offset = position + ( normal * 1.0f ); + + CSmartPtr sparkEmitter = CTrailParticles::Create( "FX_MetalScrape 1" ); + + if ( !sparkEmitter ) + return; + + sparkEmitter->SetSortOrigin( offset ); + + //Setup our collision information + sparkEmitter->Setup( offset, + &normal, + METAL_SCRAPE_SPREAD, + METAL_SCRAPE_MINSPEED, + METAL_SCRAPE_MAXSPEED, + METAL_SCRAPE_GRAVITY, + METAL_SCRAPE_DAMPEN, + bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + + if ( g_Material_Spark == NULL ) + { + g_Material_Spark = sparkEmitter->GetPMaterial( "effects/spark" ); + } + + TrailParticle *pParticle; + Vector dir; + + float length = 0.06f; + + //Dump out sparks + for ( int i = 0; i < 35; i++ ) + { + pParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), g_Material_Spark, offset ); + if ( pParticle == NULL ) + return; + + pParticle->m_flLifetime = 0.0f; + + float spreadOfs = random->RandomFloat( 0.0f, 2.0f ); + + dir[0] = normal[0] + random->RandomFloat( -(METAL_SCRAPE_SPREAD*spreadOfs), (METAL_SCRAPE_SPREAD*spreadOfs) ); + dir[1] = normal[1] + random->RandomFloat( -(METAL_SCRAPE_SPREAD*spreadOfs), (METAL_SCRAPE_SPREAD*spreadOfs) ); + dir[2] = normal[2] + random->RandomFloat( -(METAL_SCRAPE_SPREAD*spreadOfs), (METAL_SCRAPE_SPREAD*spreadOfs) ); + + pParticle->m_flWidth = random->RandomFloat( 2.0f, 5.0f ); + pParticle->m_flLength = random->RandomFloat( length*0.25f, length ); + pParticle->m_flDieTime = random->RandomFloat( 2.0f, 2.0f ); + + pParticle->m_vecVelocity = dir * random->RandomFloat( (METAL_SCRAPE_MINSPEED*(2.0f-spreadOfs)), (METAL_SCRAPE_MAXSPEED*(2.0f-spreadOfs)) ); + + Color32Init( pParticle->m_color, 255, 255, 255, 255 ); + } +} + + +void ManhackSparkCallback( const CEffectData & data ) +{ + Vector vecNormal; + Vector vecPosition; + QAngle angles; + + vecPosition = data.m_vOrigin; + vecNormal = data.m_vNormal; + angles = data.m_vAngles; + + FX_SparkFan( vecPosition, vecNormal ); +} + +DECLARE_CLIENT_EFFECT( "ManhackSparks", ManhackSparkCallback ); diff --git a/game/client/fx_sparks.h b/game/client/fx_sparks.h new file mode 100644 index 00000000..bc200a9d --- /dev/null +++ b/game/client/fx_sparks.h @@ -0,0 +1,168 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "particles_simple.h" +#include "particlemgr.h" +#include "c_pixel_visibility.h" +#include "fx_fleck.h" + +#include "tier0/memdbgon.h" + +#define bitsPARTICLE_TRAIL_VELOCITY_DAMPEN 0x00000001 //Dampen the velocity as the particles move +#define bitsPARTICLE_TRAIL_COLLIDE 0x00000002 //Do collision with simulation +#define bitsPARTICLE_TRAIL_FADE 0x00000004 //Fade away +#define bitsPARTICLE_TRAIL_FADE_IN 0x00000008 //Fade in + +class TrailParticle : public Particle +{ +public: + + Vector m_vecVelocity; + color32 m_color; // Particle color + float m_flDieTime; // How long it lives for. + float m_flLifetime; // How long it has been alive for so far. + float m_flLength; // Length of the tail (in seconds!) + float m_flWidth; // Width of the spark +}; + +inline void Color32ToFloat4( float colorOut[4], const color32 & colorIn ) +{ + colorOut[0] = colorIn.r * (1.0f/255.0f); + colorOut[1] = colorIn.g * (1.0f/255.0f); + colorOut[2] = colorIn.b * (1.0f/255.0f); + colorOut[3] = colorIn.a * (1.0f/255.0f); +} + +inline void FloatToColor32( color32 &out, float r, float g, float b, float a ) +{ + out.r = r * 255; + out.g = g * 255; + out.b = b * 255; + out.a = a * 255; +} + +inline void Float4ToColor32( color32 &out, float colorIn[4] ) +{ + out.r = colorIn[0] * 255; + out.g = colorIn[1] * 255; + out.b = colorIn[2] * 255; + out.a = colorIn[3] * 255; +} + +inline void Color32Init( color32 &out, int r, int g, int b, int a ) +{ + out.r = r; + out.g = g; + out.b = b; + out.a = a; +} +// +// CTrailParticles +// + +class CTrailParticles : public CSimpleEmitter +{ + DECLARE_CLASS( CTrailParticles, CSimpleEmitter ); +public: + CTrailParticles( const char *pDebugName ); + + static CTrailParticles *Create( const char *pDebugName ) { return new CTrailParticles( pDebugName ); } + + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + + //Setup for point emission + virtual void Setup( const Vector &origin, const Vector *direction, float angularSpread, float minSpeed, float maxSpeed, float gravity, float dampen, int flags, bool bNotCollideable = false ); + + void SetFlag( int flags ) { m_fFlags |= flags; } + void SetVelocityDampen( float dampen ) { m_flVelocityDampen = dampen; } + void SetGravity( float gravity ) { m_ParticleCollision.SetGravity( gravity ); } + void SetCollisionDamped( float dampen ) { m_ParticleCollision.SetCollisionDampen( dampen ); } + void SetAngularCollisionDampen( float dampen ) { m_ParticleCollision.SetAngularCollisionDampen( dampen ); } + + CParticleCollision m_ParticleCollision; + +protected: + + int m_fFlags; + float m_flVelocityDampen; + +private: + CTrailParticles( const CTrailParticles & ); // not defined, not accessible +}; + +// +// Sphere trails +// + +class CSphereTrails : public CSimpleEmitter +{ + DECLARE_CLASS( CSphereTrails, CSimpleEmitter ); +public: + CSphereTrails( const char *pDebugName, const Vector &origin, float innerRadius, float outerRadius, float speed, int entityIndex, int attachment ); + + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void Update( float flTimeDelta ); + virtual void StartRender(); + + void AddStreaks( float count ); + Vector m_particleOrigin; + float m_life; + float m_outerRadius; + float m_innerRadius; + float m_effectSpeed; + float m_streakSpeed; + float m_count; + float m_growth; + int m_entityIndex; + int m_attachment; + PMaterialHandle m_hMaterial; + Vector m_boneOrigin; + float m_dieTime; + +private: + CSphereTrails( const CSphereTrails & ); // not defined, not accessible +}; + + +// Simple glow emitter won't draw any of it's particles until/unless the pixel visibility test succeeds +class CSimpleGlowEmitter : public CSimpleEmitter +{ + DECLARE_CLASS( CSimpleGlowEmitter, CSimpleEmitter ); +public: + CSimpleGlowEmitter( const char *pDebugName, const Vector &sortOrigin, float flDeathTime ); + static CSimpleGlowEmitter *Create( const char *pDebugName, const Vector &sortOrigin, float flDeathTime ); + + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); +protected: + + bool WasTestedInView( unsigned char viewMask ); + bool IsVisibleInView( unsigned char viewMask ); + void SetTestedInView( unsigned char viewMask, bool bTested ); + void SetVisibleInView( unsigned char viewMask, bool bVisible ); + unsigned char CurrentViewMask() const; + + float m_flDeathTime; // How long it has been alive for so far. + float m_startTime; + pixelvis_handle_t m_queryHandle; +private: + unsigned char m_wasTested; + unsigned char m_isVisible; + +private: + CSimpleGlowEmitter( const CSimpleGlowEmitter & ); // not defined, not accessible +}; + +#include "tier0/memdbgoff.h" diff --git a/game/client/fx_staticline.cpp b/game/client/fx_staticline.cpp new file mode 100644 index 00000000..c2b9e573 --- /dev/null +++ b/game/client/fx_staticline.cpp @@ -0,0 +1,166 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "clientsideeffects.h" +#include "FX_StaticLine.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/IMesh.h" +#include "view.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +/* +================================================== +CFXStaticLine +================================================== +*/ + +CFXStaticLine::CFXStaticLine( const char *name, const Vector& start, const Vector& end, float scale, float life, const char *shader, unsigned int flags ) +: CClientSideEffect( name ) +{ + assert( materials ); + if ( materials == NULL ) + return; + + // Create a material... + m_pMaterial = materials->FindMaterial( shader, TEXTURE_GROUP_CLIENT_EFFECTS ); + m_pMaterial->IncrementReferenceCount(); + + //Fill in the rest of the fields + m_vecStart = start; + m_vecEnd = end; + m_uiFlags = flags; + m_fLife = life; + m_fScale = scale * 0.5f; +} + +CFXStaticLine::~CFXStaticLine( void ) +{ + Destroy(); +} + +//================================================== +// Purpose: Draw the primitive +// Input: frametime - the time, this frame +//================================================== + +void CFXStaticLine::Draw( double frametime ) +{ + Vector lineDir, viewDir, cross; + Vector tmp; + + // Update the effect + Update( frametime ); + + // Get the proper orientation for the line + VectorSubtract( m_vecEnd, m_vecStart, lineDir ); + VectorSubtract( m_vecEnd, CurrentViewOrigin(), viewDir ); + + cross = lineDir.Cross( viewDir ); + + VectorNormalize( cross ); + + CMatRenderContextPtr pRenderContext( materials ); + + //Bind the material + IMesh* pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_pMaterial ); + CMeshBuilder meshBuilder; + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + bool flipVertical = (m_uiFlags & FXSTATICLINE_FLIP_VERTICAL) != 0; + bool flipHorizontal = (m_uiFlags & FXSTATICLINE_FLIP_HORIZONTAL ) != 0; + + //Setup our points + VectorMA( m_vecStart, -m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.Normal3fv( cross.Base() ); + if (flipHorizontal) + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + else if (flipVertical) + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + else + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( m_vecStart, m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.Normal3fv( cross.Base() ); + if (flipHorizontal) + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + else if (flipVertical) + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + else + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( m_vecEnd, m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.Normal3fv( cross.Base() ); + if (flipHorizontal) + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + else if (flipVertical) + meshBuilder.TexCoord2f( 0, 1.0f, 1.0f ); + else + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( m_vecEnd, -m_fScale, cross, tmp ); + meshBuilder.Position3fv( tmp.Base() ); + meshBuilder.Normal3fv( cross.Base() ); + if (flipHorizontal) + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + else if (flipVertical) + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + else + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + +//================================================== +// Purpose: Returns whether or not the effect is still active +// Output: true if active +//================================================== + +bool CFXStaticLine::IsActive( void ) +{ + return ( m_fLife > 0.0 ) ? true : false; +} + +//================================================== +// Purpose: Destroy the effect and its local resources +//================================================== + +void CFXStaticLine::Destroy( void ) +{ + //Release the material + if ( m_pMaterial != NULL ) + { + m_pMaterial->DecrementReferenceCount(); + m_pMaterial = NULL; + } +} + +//================================================== +// Purpose: Perform any necessary updates +// Input: frametime - the time, this frame +//================================================== + +void CFXStaticLine::Update( double frametime ) +{ + m_fLife -= frametime; +} diff --git a/game/client/fx_staticline.h b/game/client/fx_staticline.h new file mode 100644 index 00000000..0640cb42 --- /dev/null +++ b/game/client/fx_staticline.h @@ -0,0 +1,46 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "clientsideeffects.h" + +#if !defined( FXSTATICLINE_H ) +#define FXSTATICLINE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mathlib/vector.h" + +class IMaterial; + +#define FXSTATICLINE_FLIP_HORIZONTAL 0x00000001 //Swaps the TC's so the texture is flipped horizontally +#define FXSTATICLINE_FLIP_VERTICAL 0x00000002 //Swaps the TC's so the texture is flipped vertically + +class CFXStaticLine : public CClientSideEffect +{ +public: + + CFXStaticLine( const char *name, const Vector& start, const Vector& end, float scale, float life, const char *shader, unsigned int flags ); + ~CFXStaticLine( void ); + + virtual void Draw( double frametime ); + virtual bool IsActive( void ); + virtual void Destroy( void ); + virtual void Update( double frametime ); + +protected: + + IMaterial *m_pMaterial; + Vector m_vecStart, m_vecEnd; + unsigned int m_uiFlags; + float m_fLife; + float m_fScale; +}; + +#endif diff --git a/game/client/fx_tracer.cpp b/game/client/fx_tracer.cpp new file mode 100644 index 00000000..5d37bbad --- /dev/null +++ b/game/client/fx_tracer.cpp @@ -0,0 +1,168 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "fx.h" +#include "c_te_effect_dispatch.h" +#include "basecombatweapon_shared.h" +#include "baseviewmodel_shared.h" +#include "particles_new.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define TRACER_SPEED 5000 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector GetTracerOrigin( const CEffectData &data ) +{ + Vector vecStart = data.m_vStart; + QAngle vecAngles; + + int iAttachment = data.m_nAttachmentIndex;; + + // Attachment? + if ( data.m_fFlags & TRACER_FLAG_USEATTACHMENT ) + { + C_BaseViewModel *pViewModel = NULL; + + // If the entity specified is a weapon being carried by this player, use the viewmodel instead + IClientRenderable *pRenderable = data.GetRenderable(); + if ( !pRenderable ) + return vecStart; + + C_BaseEntity *pEnt = data.GetEntity(); + +// This check should probably be for all multiplayer games, investigate later +#if defined( HL2MP ) || defined( TF_CLIENT_DLL ) + if ( pEnt && pEnt->IsDormant() ) + return vecStart; +#endif + + C_BaseCombatWeapon *pWpn = dynamic_cast( pEnt ); + if ( pWpn && pWpn->IsCarriedByLocalPlayer() ) + { + C_BasePlayer *player = ToBasePlayer( pWpn->GetOwner() ); + + pViewModel = player ? player->GetViewModel( 0 ) : NULL; + if ( pViewModel ) + { + // Get the viewmodel and use it instead + pRenderable = pViewModel; + } + } + + // Get the attachment origin + if ( !pRenderable->GetAttachment( iAttachment, vecStart, vecAngles ) ) + { + DevMsg( "GetTracerOrigin: Couldn't find attachment %d on model %s\n", iAttachment, + modelinfo->GetModelName( pRenderable->GetModel() ) ); + } + } + + return vecStart; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TracerCallback( const CEffectData &data ) +{ + C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); + if ( !player ) + return; + + // Grab the data + Vector vecStart = GetTracerOrigin( data ); + float flVelocity = data.m_flScale; + bool bWhiz = (data.m_fFlags & TRACER_FLAG_WHIZ); + int iEntIndex = data.entindex(); + + if ( iEntIndex && iEntIndex == player->index ) + { + Vector foo = data.m_vStart; + QAngle vangles; + Vector vforward, vright, vup; + + engine->GetViewAngles( vangles ); + AngleVectors( vangles, &vforward, &vright, &vup ); + + VectorMA( data.m_vStart, 4, vright, foo ); + foo[2] -= 0.5f; + + FX_PlayerTracer( foo, (Vector&)data.m_vOrigin ); + return; + } + + // Use default velocity if none specified + if ( !flVelocity ) + { + flVelocity = TRACER_SPEED; + } + + // Do tracer effect + FX_Tracer( (Vector&)vecStart, (Vector&)data.m_vOrigin, flVelocity, bWhiz ); +} + +DECLARE_CLIENT_EFFECT( "Tracer", TracerCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ParticleTracerCallback( const CEffectData &data ) +{ + C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); + if ( !player ) + return; + + // Grab the data + Vector vecStart = GetTracerOrigin( data ); + Vector vecEnd = data.m_vOrigin; + + // Adjust view model tracers + C_BaseEntity *pEntity = data.GetEntity(); + if ( data.entindex() && data.entindex() == player->index ) + { + QAngle vangles; + Vector vforward, vright, vup; + + engine->GetViewAngles( vangles ); + AngleVectors( vangles, &vforward, &vright, &vup ); + + VectorMA( data.m_vStart, 4, vright, vecStart ); + vecStart[2] -= 0.5f; + } + + // Create the particle effect + QAngle vecAngles; + Vector vecToEnd = vecEnd - vecStart; + VectorNormalize(vecToEnd); + VectorAngles( vecToEnd, vecAngles ); + DispatchParticleEffect( data.m_nHitBox, vecStart, vecEnd, vecAngles, pEntity ); + + if ( data.m_fFlags & TRACER_FLAG_WHIZ ) + { + FX_TracerSound( vecStart, vecEnd, TRACER_TYPE_DEFAULT ); + } +} + +DECLARE_CLIENT_EFFECT( "ParticleTracer", ParticleTracerCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TracerSoundCallback( const CEffectData &data ) +{ + // Grab the data + Vector vecStart = GetTracerOrigin( data ); + + // Do tracer effect + FX_TracerSound( vecStart, (Vector&)data.m_vOrigin, data.m_fFlags ); +} + +DECLARE_CLIENT_EFFECT( "TracerSound", TracerSoundCallback ); + diff --git a/game/client/fx_trail.cpp b/game/client/fx_trail.cpp new file mode 100644 index 00000000..e3490a8d --- /dev/null +++ b/game/client/fx_trail.cpp @@ -0,0 +1,78 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "fx_trail.h" + +C_ParticleTrail::C_ParticleTrail( void ) +{ +} + +C_ParticleTrail::~C_ParticleTrail( void ) +{ + if ( m_pParticleMgr ) + { + m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the attachment point to spawn at +//----------------------------------------------------------------------------- +void C_ParticleTrail::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + C_BaseEntity *pEnt = pAttachedTo->GetBaseEntity(); + + if ( pEnt && (m_nAttachment > 0) ) + { + pEnt->GetAttachment( m_nAttachment, *pAbsOrigin, *pAbsAngles ); + return; + } + + BaseClass::GetAimEntOrigin( pAttachedTo, pAbsOrigin, pAbsAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: Turn on the emission of particles +//----------------------------------------------------------------------------- +void C_ParticleTrail::SetEmit( bool bEmit ) +{ + m_bEmit = bEmit; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the spawn rate of the effect +//----------------------------------------------------------------------------- +void C_ParticleTrail::SetSpawnRate( float rate ) +{ + m_SpawnRate = rate; + m_ParticleSpawn.Init( rate ); +} + +//----------------------------------------------------------------------------- +// Purpose: First sent down from the server +//----------------------------------------------------------------------------- +void C_ParticleTrail::OnDataChanged(DataUpdateType_t updateType) +{ + C_BaseEntity::OnDataChanged(updateType); + + if ( updateType == DATA_UPDATE_CREATED ) + { + Start( ParticleMgr(), NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_ParticleTrail::Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ) +{ + if( pParticleMgr->AddEffect( &m_ParticleEffect, this ) == false ) + return; + + m_pParticleMgr = pParticleMgr; +} + diff --git a/game/client/fx_trail.h b/game/client/fx_trail.h new file mode 100644 index 00000000..87e42511 --- /dev/null +++ b/game/client/fx_trail.h @@ -0,0 +1,56 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FX_TRAIL_H +#define FX_TRAIL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "particle_util.h" +#include "baseparticleentity.h" +#include "particle_prototype.h" + +class C_ParticleTrail : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + + DECLARE_CLASS( C_ParticleTrail, C_BaseParticleEntity ); + + C_ParticleTrail( void ); + virtual ~C_ParticleTrail( void ); + + void GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles ); + + void SetEmit( bool bEmit ); + bool ShouldEmit( void ) { return m_bEmit; } + + void SetSpawnRate( float rate ); + + +// C_BaseEntity. +public: + virtual void OnDataChanged(DataUpdateType_t updateType); + + virtual void Start( CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs ); + + int m_nAttachment; + float m_flLifetime; // How long this effect will last + +private: + + float m_SpawnRate; // How many particles per second. + bool m_bEmit; // Keep emitting particles? + + TimedEvent m_ParticleSpawn; + CParticleMgr *m_pParticleMgr; + +private: + + C_ParticleTrail( const C_ParticleTrail & ); +}; + +#endif // FX_TRAIL_H diff --git a/game/client/fx_water.cpp b/game/client/fx_water.cpp new file mode 100644 index 00000000..72a27dfd --- /dev/null +++ b/game/client/fx_water.cpp @@ -0,0 +1,635 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "ClientEffectPrecacheSystem.h" +#include "FX_Sparks.h" +#include "iefx.h" +#include "c_te_effect_dispatch.h" +#include "particles_ez.h" +#include "decals.h" +#include "engine/IEngineSound.h" +#include "fx_quad.h" +#include "tier0/vprof.h" +#include "fx.h" +#include "fx_water.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectSplash ) +CLIENTEFFECT_MATERIAL( "effects/splash1" ) +CLIENTEFFECT_MATERIAL( "effects/splash2" ) +CLIENTEFFECT_MATERIAL( "effects/splash4" ) +CLIENTEFFECT_MATERIAL( "effects/slime1" ) +CLIENTEFFECT_REGISTER_END() + + +#define SPLASH_MIN_SPEED 50.0f +#define SPLASH_MAX_SPEED 100.0f + +ConVar cl_show_splashes( "cl_show_splashes", "1" ); + +static Vector s_vecSlimeColor( 46.0f/255.0f, 90.0f/255.0f, 36.0f/255.0f ); + +// Each channel does not contribute to the luminosity equally, as represented here +#define RED_CHANNEL_CONTRIBUTION 0.30f +#define GREEN_CHANNEL_CONTRIBUTION 0.59f +#define BLUE_CHANNEL_CONTRIBUTION 0.11f + +//----------------------------------------------------------------------------- +// Purpose: Returns a normalized tint and luminosity for a specified color +// Input : &color - normalized input color to extract information from +// *tint - normalized tint of that color +// *luminosity - normalized luminosity of that color +//----------------------------------------------------------------------------- +void UTIL_GetNormalizedColorTintAndLuminosity( const Vector &color, Vector *tint, float *luminosity ) +{ + // Give luminosity if requested + if ( luminosity != NULL ) + { + // Each channel contributes differently than the others + *luminosity = ( color.x * RED_CHANNEL_CONTRIBUTION ) + + ( color.y * GREEN_CHANNEL_CONTRIBUTION ) + + ( color.z * BLUE_CHANNEL_CONTRIBUTION ); + } + + // Give tint if requested + if ( tint != NULL ) + { + if ( color == vec3_origin ) + { + *tint = vec3_origin; + } + else + { + float maxComponent = max( color.x, max( color.y, color.z ) ); + *tint = color / maxComponent; + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Retrieve and alter lighting for splashes +// Input : position - point to check +// *color - tint of the lighting at this point +// *luminosity - adjusted luminosity at this point +//----------------------------------------------------------------------------- +inline void FX_GetSplashLighting( Vector position, Vector *color, float *luminosity ) +{ + // Compute our lighting at our position + Vector totalColor = engine->GetLightForPoint( position, true ); + + // Get our lighting information + UTIL_GetNormalizedColorTintAndLuminosity( totalColor, color, luminosity ); + + // Fake a specular highlight (too dim otherwise) + if ( luminosity != NULL ) + { + *luminosity = min( 1.0f, (*luminosity) * 4.0f ); + + // Clamp so that we never go completely translucent + if ( *luminosity < 0.25f ) + { + *luminosity = 0.25f; + } + } + + // Only take a quarter of the tint, mostly we want to be white + if ( color != NULL ) + { + (*color) = ( (*color) * 0.25f ) + Vector( 0.75f, 0.75f, 0.75f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &normal - +// scale - +//----------------------------------------------------------------------------- +void FX_WaterRipple( const Vector &origin, float scale, Vector *pColor, float flLifetime, float flAlpha ) +{ + VPROF_BUDGET( "FX_WaterRipple", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + trace_t tr; + + Vector color = pColor ? *pColor : Vector( 0.8f, 0.8f, 0.75f ); + + Vector startPos = origin + Vector(0,0,8); + Vector endPos = origin + Vector(0,0,-64); + + UTIL_TraceLine( startPos, endPos, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 1.0f ) + { + //Add a ripple quad to the surface + FX_AddQuad( tr.endpos + ( tr.plane.normal * 0.5f ), + tr.plane.normal, + 16.0f*scale, + 128.0f*scale, + 0.7f, + flAlpha, // start alpha + 0.0f, // end alpha + 0.25f, + random->RandomFloat( 0, 360 ), + random->RandomFloat( -16.0f, 16.0f ), + color, + flLifetime, + "effects/splashwake1", + (FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &normal - +//----------------------------------------------------------------------------- +void FX_GunshotSplash( const Vector &origin, const Vector &normal, float scale ) +{ + VPROF_BUDGET( "FX_GunshotSplash", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + if ( cl_show_splashes.GetBool() == false ) + return; + + Vector color; + float luminosity; + + // Get our lighting information + FX_GetSplashLighting( origin + ( normal * scale ), &color, &luminosity ); + + float flScale = scale / 8.0f; + + if ( flScale > 4.0f ) + { + flScale = 4.0f; + } + + // Setup our trail emitter + CSmartPtr sparkEmitter = CTrailParticles::Create( "splash" ); + + if ( !sparkEmitter ) + return; + + sparkEmitter->SetSortOrigin( origin ); + sparkEmitter->m_ParticleCollision.SetGravity( 800.0f ); + sparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + sparkEmitter->SetVelocityDampen( 2.0f ); + sparkEmitter->GetBinding().SetBBox( origin - Vector( 32, 32, 32 ), origin + Vector( 32, 32, 32 ) ); + + PMaterialHandle hMaterial = ParticleMgr()->GetPMaterial( "effects/splash2" ); + + TrailParticle *tParticle; + + Vector offDir; + Vector offset; + float colorRamp; + + //Dump out drops + for ( int i = 0; i < 16; i++ ) + { + offset = origin; + offset[0] += random->RandomFloat( -8.0f, 8.0f ) * flScale; + offset[1] += random->RandomFloat( -8.0f, 8.0f ) * flScale; + + tParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + tParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + offDir = normal + RandomVector( -0.8f, 0.8f ); + + tParticle->m_vecVelocity = offDir * random->RandomFloat( SPLASH_MIN_SPEED * flScale * 3.0f, SPLASH_MAX_SPEED * flScale * 3.0f ); + tParticle->m_vecVelocity[2] += random->RandomFloat( 32.0f, 64.0f ) * flScale; + + tParticle->m_flWidth = random->RandomFloat( 1.0f, 3.0f ); + tParticle->m_flLength = random->RandomFloat( 0.025f, 0.05f ); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + tParticle->m_color.r = min( 1.0f, color[0] * colorRamp ) * 255; + tParticle->m_color.g = min( 1.0f, color[1] * colorRamp ) * 255; + tParticle->m_color.b = min( 1.0f, color[2] * colorRamp ) * 255; + tParticle->m_color.a = luminosity * 255; + } + + // Setup the particle emitter + CSmartPtr pSimple = CSplashParticle::Create( "splish" ); + pSimple->SetSortOrigin( origin ); + pSimple->SetClipHeight( origin.z ); + pSimple->SetParticleCullRadius( scale * 2.0f ); + pSimple->GetBinding().SetBBox( origin - Vector( 32, 32, 32 ), origin + Vector( 32, 32, 32 ) ); + + SimpleParticle *pParticle; + + //Main gout + for ( int i = 0; i < 8; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, origin ); + + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 2.0f; //NOTENOTE: We use a clip plane to realistically control our lifespan + + pParticle->m_vecVelocity.Random( -0.2f, 0.2f ); + pParticle->m_vecVelocity += ( normal * random->RandomFloat( 4.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= 50 * flScale * (8-i); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = 24 * flScale * RemapValClamped( i, 7, 0, 1, 0.5f ); + pParticle->m_uchEndSize = min( 255, pParticle->m_uchStartSize * 2 ); + + pParticle->m_uchStartAlpha = RemapValClamped( i, 7, 0, 255, 32 ) * luminosity; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f ); + } + + // Do a ripple + FX_WaterRipple( origin, flScale, &color, 1.5f, luminosity ); + + //Play a sound + CLocalPlayerFilter filter; + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = "Physics.WaterSplash"; + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NORM; + ep.m_pOrigin = &origin; + + + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &normal - +// scale - +// *pColor - +//----------------------------------------------------------------------------- +void FX_GunshotSlimeSplash( const Vector &origin, const Vector &normal, float scale ) +{ + if ( cl_show_splashes.GetBool() == false ) + return; + + VPROF_BUDGET( "FX_GunshotSlimeSplash", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + +#if 0 + + float colorRamp; + float flScale = min( 1.0f, scale / 8.0f ); + + PMaterialHandle hMaterial = ParticleMgr()->GetPMaterial( "effects/slime1" ); + PMaterialHandle hMaterial2 = ParticleMgr()->GetPMaterial( "effects/splash4" ); + + Vector color; + float luminosity; + + // Get our lighting information + FX_GetSplashLighting( origin + ( normal * scale ), &color, &luminosity ); + + Vector offDir; + Vector offset; + + TrailParticle *tParticle; + + CSmartPtr sparkEmitter = CTrailParticles::Create( "splash" ); + + if ( !sparkEmitter ) + return; + + sparkEmitter->SetSortOrigin( origin ); + sparkEmitter->m_ParticleCollision.SetGravity( 800.0f ); + sparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); + sparkEmitter->SetVelocityDampen( 2.0f ); + if ( IsXbox() ) + { + sparkEmitter->GetBinding().SetBBox( origin - Vector( 32, 32, 64 ), origin + Vector( 32, 32, 64 ) ); + } + + //Dump out drops + for ( int i = 0; i < 24; i++ ) + { + offset = origin; + offset[0] += random->RandomFloat( -16.0f, 16.0f ) * flScale; + offset[1] += random->RandomFloat( -16.0f, 16.0f ) * flScale; + + tParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); + + if ( tParticle == NULL ) + break; + + tParticle->m_flLifetime = 0.0f; + tParticle->m_flDieTime = random->RandomFloat( 0.25f, 0.5f ); + + offDir = normal + RandomVector( -0.6f, 0.6f ); + + tParticle->m_vecVelocity = offDir * random->RandomFloat( SPLASH_MIN_SPEED * flScale * 3.0f, SPLASH_MAX_SPEED * flScale * 3.0f ); + tParticle->m_vecVelocity[2] += random->RandomFloat( 32.0f, 64.0f ) * flScale; + + tParticle->m_flWidth = random->RandomFloat( 3.0f, 6.0f ) * flScale; + tParticle->m_flLength = random->RandomFloat( 0.025f, 0.05f ) * flScale; + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + tParticle->m_color.r = min( 1.0f, color.x * colorRamp ) * 255; + tParticle->m_color.g = min( 1.0f, color.y * colorRamp ) * 255; + tParticle->m_color.b = min( 1.0f, color.z * colorRamp ) * 255; + tParticle->m_color.a = 255 * luminosity; + } + + // Setup splash emitter + CSmartPtr pSimple = CSplashParticle::Create( "splish" ); + pSimple->SetSortOrigin( origin ); + pSimple->SetClipHeight( origin.z ); + pSimple->SetParticleCullRadius( scale * 2.0f ); + + if ( IsXbox() ) + { + pSimple->GetBinding().SetBBox( origin - Vector( 32, 32, 64 ), origin + Vector( 32, 32, 64 ) ); + } + + SimpleParticle *pParticle; + + // Tint + colorRamp = random->RandomFloat( 0.75f, 1.0f ); + color = Vector( 1.0f, 0.8f, 0.0f ) * color * colorRamp; + + //Main gout + for ( int i = 0; i < 8; i++ ) + { + pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial2, origin ); + + if ( pParticle == NULL ) + break; + + pParticle->m_flLifetime = 0.0f; + pParticle->m_flDieTime = 2.0f; //NOTENOTE: We use a clip plane to realistically control our lifespan + + pParticle->m_vecVelocity.Random( -0.2f, 0.2f ); + pParticle->m_vecVelocity += ( normal * random->RandomFloat( 4.0f, 6.0f ) ); + + VectorNormalize( pParticle->m_vecVelocity ); + + pParticle->m_vecVelocity *= 50 * flScale * (8-i); + + colorRamp = random->RandomFloat( 0.75f, 1.25f ); + + pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f; + pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f; + pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f; + + pParticle->m_uchStartSize = 24 * flScale * RemapValClamped( i, 7, 0, 1, 0.5f ); + pParticle->m_uchEndSize = min( 255, pParticle->m_uchStartSize * 2 ); + + pParticle->m_uchStartAlpha = RemapValClamped( i, 7, 0, 255, 32 ) * luminosity; + pParticle->m_uchEndAlpha = 0; + + pParticle->m_flRoll = random->RandomInt( 0, 360 ); + pParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f ); + } + +#else + + QAngle vecAngles; + VectorAngles( normal, vecAngles ); + if ( scale < 2.0f ) + { + DispatchParticleEffect( "slime_splash_01", origin, vecAngles ); + } + else if ( scale < 4.0f ) + { + DispatchParticleEffect( "slime_splash_02", origin, vecAngles ); + } + else + { + DispatchParticleEffect( "slime_splash_03", origin, vecAngles ); + } + +#endif + + //Play a sound + CLocalPlayerFilter filter; + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = "Physics.WaterSplash"; + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NORM; + ep.m_pOrigin = &origin; + + C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SplashCallback( const CEffectData &data ) +{ + Vector normal; + + AngleVectors( data.m_vAngles, &normal ); + + if ( data.m_fFlags & FX_WATER_IN_SLIME ) + { + FX_GunshotSlimeSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); + } + else + { + FX_GunshotSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); + } +} + +DECLARE_CLIENT_EFFECT( "watersplash", SplashCallback ); + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void GunshotSplashCallback( const CEffectData &data ) +{ + if ( data.m_fFlags & FX_WATER_IN_SLIME ) + { + FX_GunshotSlimeSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); + } + else + { + FX_GunshotSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); + } +} + +DECLARE_CLIENT_EFFECT( "gunshotsplash", GunshotSplashCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void RippleCallback( const CEffectData &data ) +{ + float flScale = data.m_flScale / 8.0f; + + Vector color; + float luminosity; + + // Get our lighting information + FX_GetSplashLighting( data.m_vOrigin + ( Vector(0,0,1) * 4.0f ), &color, &luminosity ); + + FX_WaterRipple( data.m_vOrigin, flScale, &color, 1.5f, luminosity ); +} + +DECLARE_CLIENT_EFFECT( "waterripple", RippleCallback ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pDebugName - +// Output : WaterDebrisEffect* +//----------------------------------------------------------------------------- +WaterDebrisEffect* WaterDebrisEffect::Create( const char *pDebugName ) +{ + return new WaterDebrisEffect( pDebugName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// timeDelta - +// Output : float +//----------------------------------------------------------------------------- +float WaterDebrisEffect::UpdateAlpha( const SimpleParticle *pParticle ) +{ + return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// timeDelta - +// Output : float +//----------------------------------------------------------------------------- +float CSplashParticle::UpdateRoll( SimpleParticle *pParticle, float timeDelta ) +{ + pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; + + pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -4.0f ); + + //Cap the minimum roll + if ( fabs( pParticle->m_flRollDelta ) < 0.5f ) + { + pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f; + } + + return pParticle->m_flRoll; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// timeDelta - +//----------------------------------------------------------------------------- +void CSplashParticle::UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) +{ + //Decellerate + static float dtime; + static float decay; + + if ( dtime != timeDelta ) + { + dtime = timeDelta; + float expected = 3.0f; + decay = exp( log( 0.0001f ) * dtime / expected ); + } + + pParticle->m_vecVelocity *= decay; + pParticle->m_vecVelocity[2] -= ( 800.0f * timeDelta ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pParticle - +// Output : float +//----------------------------------------------------------------------------- +float CSplashParticle::UpdateAlpha( const SimpleParticle *pParticle ) +{ + if ( m_bUseClipHeight ) + { + float flAlpha = pParticle->m_uchStartAlpha / 255.0f; + + return flAlpha * RemapValClamped(pParticle->m_Pos.z, + m_flClipHeight, + m_flClipHeight - ( UpdateScale( pParticle ) * 0.5f ), + 1.0f, + 0.0f ); + } + + return (pParticle->m_uchStartAlpha/255.0f) + ( (float)(pParticle->m_uchEndAlpha/255.0f) - (float)(pParticle->m_uchStartAlpha/255.0f) ) * (pParticle->m_flLifetime / pParticle->m_flDieTime); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &clipPlane - +//----------------------------------------------------------------------------- +void CSplashParticle::SetClipHeight( float flClipHeight ) +{ + m_bUseClipHeight = true; + m_flClipHeight = flClipHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pIterator - +//----------------------------------------------------------------------------- +void CSplashParticle::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + float timeDelta = pIterator->GetTimeDelta(); + + SimpleParticle *pParticle = (SimpleParticle*)pIterator->GetFirst(); + + while ( pParticle ) + { + //Update velocity + UpdateVelocity( pParticle, timeDelta ); + pParticle->m_Pos += pParticle->m_vecVelocity * timeDelta; + + // Clip by height if requested + if ( m_bUseClipHeight ) + { + // See if we're below, and therefore need to clip + if ( pParticle->m_Pos.z + UpdateScale( pParticle ) < m_flClipHeight ) + { + pIterator->RemoveParticle( pParticle ); + pParticle = (SimpleParticle*)pIterator->GetNext(); + continue; + } + } + + //Should this particle die? + pParticle->m_flLifetime += timeDelta; + UpdateRoll( pParticle, timeDelta ); + + if ( pParticle->m_flLifetime >= pParticle->m_flDieTime ) + pIterator->RemoveParticle( pParticle ); + + pParticle = (SimpleParticle*)pIterator->GetNext(); + } +} diff --git a/game/client/fx_water.h b/game/client/fx_water.h new file mode 100644 index 00000000..ae1847b0 --- /dev/null +++ b/game/client/fx_water.h @@ -0,0 +1,71 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FX_WATER_H +#define FX_WATER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "particles_simple.h" + +#include "tier0/memdbgon.h" + +class CSplashParticle : public CSimpleEmitter +{ +public: + + CSplashParticle( const char *pDebugName ) : CSimpleEmitter( pDebugName ), m_bUseClipHeight( false ) {} + + // Create + static CSplashParticle *Create( const char *pDebugName ) + { + return new CSplashParticle( pDebugName ); + } + + // Roll + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ); + + // Velocity + virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta ); + + // Alpha + virtual float UpdateAlpha( const SimpleParticle *pParticle ); + + void SetClipHeight( float flClipHeight ); + + // Simulation + void SimulateParticles( CParticleSimulateIterator *pIterator ); + +private: + CSplashParticle( const CSplashParticle & ); + + float m_flClipHeight; + bool m_bUseClipHeight; +}; + +class WaterDebrisEffect : public CSimpleEmitter +{ +public: + WaterDebrisEffect( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + static WaterDebrisEffect* Create( const char *pDebugName ); + + virtual float UpdateAlpha( const SimpleParticle *pParticle ); + +private: + WaterDebrisEffect( const WaterDebrisEffect & ); +}; + +extern void FX_WaterRipple( const Vector &origin, float scale, Vector *pColor, float flLifetime=1.5, float flAlpha=1 ); +extern void FX_GunshotSplash( const Vector &origin, const Vector &normal, float scale ); +extern void FX_GunshotSlimeSplash( const Vector &origin, const Vector &normal, float scale ); + +extern inline void FX_GetSplashLighting( Vector position, Vector *color, float *luminosity ); + +#include "tier0/memdbgoff.h" + +#endif // FX_WATER_H diff --git a/game/client/game_controls/ClientScoreBoardDialog.cpp b/game/client/game_controls/ClientScoreBoardDialog.cpp new file mode 100644 index 00000000..9fb9950d --- /dev/null +++ b/game/client/game_controls/ClientScoreBoardDialog.cpp @@ -0,0 +1,586 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include + +#include +#include +#include +#include +#include "IGameUIFuncs.h" // for key bindings +#include "inputsystem/iinputsystem.h" +#include "clientscoreboarddialog.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "vgui_avatarimage.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +bool AvatarIndexLessFunc( const int &lhs, const int &rhs ) +{ + return lhs < rhs; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CClientScoreBoardDialog::CClientScoreBoardDialog(IViewPort *pViewPort) : EditablePanel( NULL, PANEL_SCOREBOARD ) +{ + m_iPlayerIndexSymbol = KeyValuesSystem()->GetSymbolForString("playerIndex"); + m_nCloseKey = BUTTON_CODE_INVALID; + + //memset(s_VoiceImage, 0x0, sizeof( s_VoiceImage )); + TrackerImage = 0; + m_pViewPort = pViewPort; + + // initialize dialog + SetProportional(true); + SetKeyBoardInputEnabled(false); + SetMouseInputEnabled(false); + + // set the scheme before any child control is created + SetScheme("ClientScheme"); + + m_pPlayerList = new SectionedListPanel(this, "PlayerList"); + m_pPlayerList->SetVerticalScrollbar(false); + + LoadControlSettings("Resource/UI/ScoreBoard.res"); + m_iDesiredHeight = GetTall(); + m_pPlayerList->SetVisible( false ); // hide this until we load the images in applyschemesettings + + m_HLTVSpectators = 0; + + // update scoreboard instantly if on of these events occure + ListenForGameEvent( "hltv_status" ); + ListenForGameEvent( "server_spawn" ); + + m_pImageList = NULL; + + m_mapAvatarsToImageList.SetLessFunc( AvatarIndexLessFunc ); + m_mapAvatarsToImageList.RemoveAll(); + memset( &m_iImageAvatars, 0, sizeof(int) * (MAX_PLAYERS+1) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CClientScoreBoardDialog::~CClientScoreBoardDialog() +{ + if ( NULL != m_pImageList ) + { + delete m_pImageList; + m_pImageList = NULL; + } +} + +//----------------------------------------------------------------------------- +// Call every frame +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::OnThink() +{ + BaseClass::OnThink(); + + // NOTE: this is necessary because of the way input works. + // If a key down message is sent to vgui, then it will get the key up message + // Sometimes the scoreboard is activated by other vgui menus, + // sometimes by console commands. In the case where it's activated by + // other vgui menus, we lose the key up message because this panel + // doesn't accept keyboard input. It *can't* accept keyboard input + // because another feature of the dialog is that if it's triggered + // from within the game, you should be able to still run around while + // the scoreboard is up. That feature is impossible if this panel accepts input. + // because if a vgui panel is up that accepts input, it prevents the engine from + // receiving that input. So, I'm stuck with a polling solution. + // + // Close key is set to non-invalid when something other than a keybind + // brings the scoreboard up, and it's set to invalid as soon as the + // dialog becomes hidden. + if ( m_nCloseKey != BUTTON_CODE_INVALID ) + { + if ( !g_pInputSystem->IsButtonDown( m_nCloseKey ) ) + { + m_nCloseKey = BUTTON_CODE_INVALID; + gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, false ); + GetClientVoiceMgr()->StopSquelchMode(); + } + } +} + +//----------------------------------------------------------------------------- +// Called by vgui panels that activate the client scoreboard +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::OnPollHideCode( int code ) +{ + m_nCloseKey = (ButtonCode_t)code; +} + +//----------------------------------------------------------------------------- +// Purpose: clears everything in the scoreboard and all it's state +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::Reset() +{ + // clear + m_pPlayerList->DeleteAllItems(); + m_pPlayerList->RemoveAllSections(); + + m_iSectionId = 0; + m_fNextUpdateTime = 0; + // add all the sections + InitScoreboardSections(); +} + +//----------------------------------------------------------------------------- +// Purpose: adds all the team sections to the scoreboard +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::InitScoreboardSections() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: sets up screen +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_pImageList ) + delete m_pImageList; + m_pImageList = new ImageList( false ); + + m_mapAvatarsToImageList.RemoveAll(); + memset( &m_iImageAvatars, 0, sizeof(int) * (MAX_PLAYERS+1) ); + + PostApplySchemeSettings( pScheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: Does dialog-specific customization after applying scheme settings. +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::PostApplySchemeSettings( vgui::IScheme *pScheme ) +{ + // resize the images to our resolution + for (int i = 0; i < m_pImageList->GetImageCount(); i++ ) + { + int wide, tall; + m_pImageList->GetImage(i)->GetSize(wide, tall); + m_pImageList->GetImage(i)->SetSize(scheme()->GetProportionalScaledValueEx( GetScheme(),wide), scheme()->GetProportionalScaledValueEx( GetScheme(),tall)); + } + + m_pPlayerList->SetImageList( m_pImageList, false ); + m_pPlayerList->SetVisible( true ); + + // light up scoreboard a bit + SetBgColor( Color( 0,0,0,0) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::ShowPanel(bool bShow) +{ + // Catch the case where we call ShowPanel before ApplySchemeSettings, eg when + // going from windowed <-> fullscreen + if ( m_pImageList == NULL ) + { + InvalidateLayout( true, true ); + } + + if ( !bShow ) + { + m_nCloseKey = BUTTON_CODE_INVALID; + } + + if ( BaseClass::IsVisible() == bShow ) + return; + + if ( bShow ) + { + Reset(); + Update(); + SetVisible( true ); + MoveToFront(); + } + else + { + BaseClass::SetVisible( false ); + SetMouseInputEnabled( false ); + SetKeyBoardInputEnabled( false ); + } +} + +void CClientScoreBoardDialog::FireGameEvent( IGameEvent *event ) +{ + const char * type = event->GetName(); + + if ( Q_strcmp(type, "hltv_status") == 0 ) + { + // spectators = clients - proxies + m_HLTVSpectators = event->GetInt( "clients" ); + m_HLTVSpectators -= event->GetInt( "proxies" ); + } + + else if ( Q_strcmp(type, "server_spawn") == 0 ) + { + // We'll post the message ourselves instead of using SetControlString() + // so we don't try to translate the hostname. + const char *hostname = event->GetString( "hostname" ); + Panel *control = FindChildByName( "ServerName" ); + if ( control ) + { + PostMessage( control, new KeyValues( "SetText", "text", hostname ) ); + control->MoveToFront(); + } + } + + if( IsVisible() ) + Update(); + +} + +bool CClientScoreBoardDialog::NeedsUpdate( void ) +{ + return (m_fNextUpdateTime < gpGlobals->curtime); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::Update( void ) +{ + // Set the title + + // Reset(); + m_pPlayerList->DeleteAllItems(); + + FillScoreBoard(); + + // grow the scoreboard to fit all the players + int wide, tall; + m_pPlayerList->GetContentSize(wide, tall); + tall += GetAdditionalHeight(); + wide = GetWide(); + if (m_iDesiredHeight < tall) + { + SetSize(wide, tall); + m_pPlayerList->SetSize(wide, tall); + } + else + { + SetSize(wide, m_iDesiredHeight); + m_pPlayerList->SetSize(wide, m_iDesiredHeight); + } + + MoveToCenterOfScreen(); + + // update every second + m_fNextUpdateTime = gpGlobals->curtime + 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::UpdateTeamInfo() +{ +// TODO: work out a sorting algorithm for team display for TF2 +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::UpdatePlayerInfo() +{ + m_iSectionId = 0; // 0'th row is a header + int selectedRow = -1; + + // walk all the players and make sure they're in the scoreboard + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + IGameResources *gr = GameResources(); + + if ( gr && gr->IsConnected( i ) ) + { + // add the player to the list + KeyValues *playerData = new KeyValues("data"); + GetPlayerScoreInfo( i, playerData ); + UpdatePlayerAvatar( i, playerData ); + + const char *oldName = playerData->GetString("name",""); + char newName[MAX_PLAYER_NAME_LENGTH]; + + UTIL_MakeSafeName( oldName, newName, MAX_PLAYER_NAME_LENGTH ); + + playerData->SetString("name", newName); + + int itemID = FindItemIDForPlayerIndex( i ); + int sectionID = gr->GetTeam( i ); + + if ( gr->IsLocalPlayer( i ) ) + { + selectedRow = itemID; + } + if (itemID == -1) + { + // add a new row + itemID = m_pPlayerList->AddItem( sectionID, playerData ); + } + else + { + // modify the current row + m_pPlayerList->ModifyItem( itemID, sectionID, playerData ); + } + + // set the row color based on the players team + m_pPlayerList->SetItemFgColor( itemID, gr->GetTeamColor( sectionID ) ); + + playerData->deleteThis(); + } + else + { + // remove the player + int itemID = FindItemIDForPlayerIndex( i ); + if (itemID != -1) + { + m_pPlayerList->RemoveItem(itemID); + } + } + } + + if ( selectedRow != -1 ) + { + m_pPlayerList->SetSelectedItem(selectedRow); + } +} + +//----------------------------------------------------------------------------- +// Purpose: adds the top header of the scoreboars +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::AddHeader() +{ + // add the top header + m_pPlayerList->AddSection(m_iSectionId, ""); + m_pPlayerList->SetSectionAlwaysVisible(m_iSectionId); + m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "#PlayerName", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),NAME_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "frags", "#PlayerScore", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),SCORE_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "deaths", "#PlayerDeath", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),DEATH_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "#PlayerPing", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),PING_WIDTH) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a new section to the scoreboard (i.e the team header) +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::AddSection(int teamType, int teamNumber) +{ + if ( teamType == TYPE_TEAM ) + { + IGameResources *gr = GameResources(); + + if ( !gr ) + return; + + // setup the team name + wchar_t *teamName = g_pVGuiLocalize->Find( gr->GetTeamName(teamNumber) ); + wchar_t name[64]; + wchar_t string1[1024]; + + if (!teamName) + { + g_pVGuiLocalize->ConvertANSIToUnicode(gr->GetTeamName(teamNumber), name, sizeof(name)); + teamName = name; + } + + g_pVGuiLocalize->ConstructString( string1, sizeof( string1 ), g_pVGuiLocalize->Find("#Player"), 2, teamName ); + + m_pPlayerList->AddSection(m_iSectionId, "", StaticPlayerSortFunc); + + // Avatars are always displayed at 32x32 regardless of resolution + if ( ShowAvatars() ) + { + m_pPlayerList->AddColumnToSection( m_iSectionId, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iAvatarWidth ); + } + + m_pPlayerList->AddColumnToSection(m_iSectionId, "name", string1, 0, scheme()->GetProportionalScaledValueEx( GetScheme(),NAME_WIDTH) - m_iAvatarWidth ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "frags", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),SCORE_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "deaths", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),DEATH_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),PING_WIDTH) ); + } + else if ( teamType == TYPE_SPECTATORS ) + { + m_pPlayerList->AddSection(m_iSectionId, ""); + + // Avatars are always displayed at 32x32 regardless of resolution + if ( ShowAvatars() ) + { + m_pPlayerList->AddColumnToSection( m_iSectionId, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iAvatarWidth ); + } + m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "#Spectators", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),NAME_WIDTH) - m_iAvatarWidth ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "frags", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),SCORE_WIDTH) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Used for sorting players +//----------------------------------------------------------------------------- +bool CClientScoreBoardDialog::StaticPlayerSortFunc(vgui::SectionedListPanel *list, int itemID1, int itemID2) +{ + KeyValues *it1 = list->GetItemData(itemID1); + KeyValues *it2 = list->GetItemData(itemID2); + Assert(it1 && it2); + + // first compare frags + int v1 = it1->GetInt("frags"); + int v2 = it2->GetInt("frags"); + if (v1 > v2) + return true; + else if (v1 < v2) + return false; + + // next compare deaths + v1 = it1->GetInt("deaths"); + v2 = it2->GetInt("deaths"); + if (v1 > v2) + return false; + else if (v1 < v2) + return true; + + // the same, so compare itemID's (as a sentinel value to get deterministic sorts) + return itemID1 < itemID2; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a new row to the scoreboard, from the playerinfo structure +//----------------------------------------------------------------------------- +bool CClientScoreBoardDialog::GetPlayerScoreInfo(int playerIndex, KeyValues *kv) +{ + IGameResources *gr = GameResources(); + + if (!gr ) + return false; + + kv->SetInt("deaths", gr->GetDeaths( playerIndex ) ); + kv->SetInt("frags", gr->GetFrags( playerIndex ) ); + kv->SetInt("ping", gr->GetPing( playerIndex ) ) ; + kv->SetString("name", gr->GetPlayerName( playerIndex ) ); + kv->SetInt("playerIndex", playerIndex); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::UpdatePlayerAvatar( int playerIndex, KeyValues *kv ) +{ + // Update their avatar + if ( kv && ShowAvatars() && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() ) + { + player_info_t pi; + if ( engine->GetPlayerInfo( playerIndex, &pi ) ) + { + if ( pi.friendsID ) + { + CSteamID steamIDForPlayer( pi.friendsID, 1, steamapicontext->SteamUtils()->GetConnectedUniverse(), k_EAccountTypeIndividual ); + + // See if the avatar's changed + int iAvatar = steamapicontext->SteamFriends()->GetFriendAvatar( steamIDForPlayer ); + if ( m_iImageAvatars[playerIndex] != iAvatar ) + { + m_iImageAvatars[playerIndex] = iAvatar; + + // Now see if we already have that avatar in our list + int iIndex = m_mapAvatarsToImageList.Find( iAvatar ); + if ( iIndex == m_mapAvatarsToImageList.InvalidIndex() ) + { + CAvatarImage *pImage = new CAvatarImage(); + pImage->SetAvatarSteamID( steamIDForPlayer ); + pImage->SetSize( 32, 32 ); // Deliberately non scaling + int iImageIndex = m_pImageList->AddImage( pImage ); + + m_mapAvatarsToImageList.Insert( iAvatar, iImageIndex ); + } + } + + int iIndex = m_mapAvatarsToImageList.Find( iAvatar ); + + if ( iIndex != m_mapAvatarsToImageList.InvalidIndex() ) + { + kv->SetInt( "avatar", m_mapAvatarsToImageList[iIndex] ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: reload the player list on the scoreboard +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::FillScoreBoard() +{ + // update totals information + UpdateTeamInfo(); + + // update player info + UpdatePlayerInfo(); +} + +//----------------------------------------------------------------------------- +// Purpose: searches for the player in the scoreboard +//----------------------------------------------------------------------------- +int CClientScoreBoardDialog::FindItemIDForPlayerIndex(int playerIndex) +{ + for (int i = 0; i <= m_pPlayerList->GetHighestItemID(); i++) + { + if (m_pPlayerList->IsItemIDValid(i)) + { + KeyValues *kv = m_pPlayerList->GetItemData(i); + kv = kv->FindKey(m_iPlayerIndexSymbol); + if (kv && kv->GetInt() == playerIndex) + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the text of a control by name +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::MoveLabelToFront(const char *textEntryName) +{ + Label *entry = dynamic_cast