├── LICENSE ├── README.md └── src ├── maps └── mp │ └── gametypes │ ├── _class.gsc │ ├── _globallogic.gsc │ ├── _menus.gsc │ ├── _rank.gsc │ └── war.gsc └── settings └── level55.gsc /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cod4-level-hack-mod 2 | Level Hack Server Mod for Call of Duty 4 3 | - You can see the mod in action: https://www.youtube.com/watch?v=JR1TWOGiYuE 4 | 5 | # Installation 6 | You need a CoD4X server to run this mod: 7 | http://www.cod4x.me/ 8 | 9 | - Set up your server to run without mod 10 | - Add these additional gsc (in src/ folder) files to override the server-side scripts 11 | - Set up your server to run on TDM (g_gametype "war") and only on Crash (map mp_crash) 12 | - Enjoy your own level hack server! 13 | 14 | Optional: 15 | - You can modify the parameter of these functions: 'self tickprint("TEXT");' in "settings/level55.gsc" to place your own messages/ads/etc. 16 | - You **should NOT** remove parts where original author name is present! e.g. 'self iprintln("^3Level 55 mod by Shuffni!");'! 17 | - You **CAN** add yourself to authors if you want! 18 | -------------------------------------------------------------------------------- /src/maps/mp/gametypes/_class.gsc: -------------------------------------------------------------------------------- 1 | // _____ _ __ __ _ 2 | // / ____| | / _|/ _| (_) 3 | // | (___ | |__ _ _| |_| |_ _ __ _ 4 | // \___ \| '_ \| | | | _| _| '_ \| | 5 | // ____) | | | | |_| | | | | | | | | | 6 | // |_____/|_| |_|\__,_|_| |_| |_| |_|_| 7 | // Level Hack Server Mod 8 | // 9 | // This file is part of Shuffni's level hack server mod. 10 | // 11 | // This is free software: you can redistribute it and/or modify 12 | // it under the terms of the GNU General Public License as published by 13 | // the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | // GNU General Public License for more details. 20 | // 21 | // You should have received a copy of the GNU General Public License 22 | // along with this software. If not, see . 23 | 24 | 25 | #include common_scripts\utility; 26 | // check if below includes are removable 27 | #include maps\mp\_utility; 28 | #include maps\mp\gametypes\_hud_util; 29 | 30 | init() 31 | { 32 | level.classMap["assault_mp"] = "CLASS_ASSAULT"; 33 | level.classMap["specops_mp"] = "CLASS_SPECOPS"; 34 | level.classMap["heavygunner_mp"] = "CLASS_HEAVYGUNNER"; 35 | level.classMap["demolitions_mp"] = "CLASS_DEMOLITIONS"; 36 | level.classMap["sniper_mp"] = "CLASS_SNIPER"; 37 | 38 | level.classMap["offline_class1_mp"] = "OFFLINE_CLASS1"; 39 | level.classMap["offline_class2_mp"] = "OFFLINE_CLASS2"; 40 | level.classMap["offline_class3_mp"] = "OFFLINE_CLASS3"; 41 | level.classMap["offline_class4_mp"] = "OFFLINE_CLASS4"; 42 | level.classMap["offline_class5_mp"] = "OFFLINE_CLASS5"; 43 | level.classMap["offline_class6_mp"] = "OFFLINE_CLASS6"; 44 | level.classMap["offline_class7_mp"] = "OFFLINE_CLASS7"; 45 | level.classMap["offline_class8_mp"] = "OFFLINE_CLASS8"; 46 | level.classMap["offline_class9_mp"] = "OFFLINE_CLASS9"; 47 | level.classMap["offline_class10_mp"] = "OFFLINE_CLASS10"; 48 | 49 | level.classMap["custom1"] = "CLASS_CUSTOM1"; 50 | level.classMap["custom2"] = "CLASS_CUSTOM2"; 51 | level.classMap["custom3"] = "CLASS_CUSTOM3"; 52 | level.classMap["custom4"] = "CLASS_CUSTOM4"; 53 | level.classMap["custom5"] = "CLASS_CUSTOM5"; 54 | 55 | if ( level.onlineGame ) 56 | level.defaultClass = "CLASS_ASSAULT"; 57 | else 58 | level.defaultClass = "OFFLINE_CLASS1"; 59 | 60 | level.weapons["frag"] = "frag_grenade_mp"; 61 | level.weapons["smoke"] = "smoke_grenade_mp"; 62 | level.weapons["flash"] = "flash_grenade_mp"; 63 | level.weapons["concussion"] = "concussion_grenade_mp"; 64 | level.weapons["c4"] = "c4_mp"; 65 | level.weapons["claymore"] = "claymore_mp"; 66 | level.weapons["rpg"] = "rpg_mp"; 67 | 68 | // initializes create a class settings 69 | cac_init(); 70 | 71 | // default class weapon loadout for offline mode 72 | // param( team, class, stat number, inventory string, inventory count ) 73 | 74 | offline_class_datatable = "mp/offline_classTable.csv"; 75 | 76 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS1", 200 ); 77 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS2", 210 ); 78 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS3", 220 ); 79 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS4", 230 ); 80 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS5", 240 ); 81 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS6", 250 ); 82 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS7", 260 ); 83 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS8", 270 ); 84 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS9", 280 ); 85 | load_default_loadout( offline_class_datatable, "both", "OFFLINE_CLASS10", 290 ); 86 | 87 | online_class_datatable = "mp/classTable.csv"; 88 | 89 | load_default_loadout( online_class_datatable, "both", "CLASS_ASSAULT", 200 ); // assault 90 | load_default_loadout( online_class_datatable, "both", "CLASS_SPECOPS", 210 ); // spec ops 91 | load_default_loadout( online_class_datatable, "both", "CLASS_HEAVYGUNNER", 220 ); // heavy gunner 92 | load_default_loadout( online_class_datatable, "both", "CLASS_DEMOLITIONS", 230 ); // demolitions 93 | load_default_loadout( online_class_datatable, "both", "CLASS_SNIPER", 240 ); // sniper 94 | 95 | // generating weapon type arrays which classifies the weapon as primary (back stow), pistol, or inventory (side pack stow) 96 | // using mp/statstable.csv's weapon grouping data ( numbering 0 - 149 ) 97 | level.primary_weapon_array = []; 98 | level.side_arm_array = []; 99 | level.grenade_array = []; 100 | level.inventory_array = []; 101 | max_weapon_num = 149; 102 | for( i = 0; i < max_weapon_num; i++ ) 103 | { 104 | if( !isdefined( level.tbl_weaponIDs[i] ) || level.tbl_weaponIDs[i]["group"] == "" ) 105 | continue; 106 | if( !isdefined( level.tbl_weaponIDs[i] ) || level.tbl_weaponIDs[i]["reference"] == "" ) 107 | continue; 108 | 109 | //statstablelookup( get_col, with_col, with_data ) 110 | weapon_type = level.tbl_weaponIDs[i]["group"]; //statstablelookup( level.cac_cgroup, level.cac_cstat, i ); 111 | weapon = level.tbl_weaponIDs[i]["reference"]; //statstablelookup( level.cac_creference, level.cac_cstat, i ); 112 | attachment = level.tbl_weaponIDs[i]["attachment"]; //statstablelookup( level.cac_cstring, level.cac_cstat, i ); 113 | 114 | weapon_class_register( weapon+"_mp", weapon_type ); 115 | 116 | if( isdefined( attachment ) && attachment != "" ) 117 | { 118 | attachment_tokens = strtok( attachment, " " ); 119 | if( isdefined( attachment_tokens ) ) 120 | { 121 | if( attachment_tokens.size == 0 ) 122 | weapon_class_register( weapon+"_"+attachment+"_mp", weapon_type ); 123 | else 124 | { 125 | // multiple attachment options 126 | for( k = 0; k < attachment_tokens.size; k++ ) 127 | weapon_class_register( weapon+"_"+attachment_tokens[k]+"_mp", weapon_type ); 128 | } 129 | } 130 | } 131 | } 132 | 133 | precacheShader( "waypoint_bombsquad" ); 134 | 135 | level thread onPlayerConnecting(); 136 | } 137 | 138 | // assigns default class loadout to team from datatable 139 | load_default_loadout( datatable, team, class, stat_num ) 140 | { 141 | if( team == "both" ) 142 | { 143 | // do not thread, tablelookup is demanding 144 | load_default_loadout_raw( datatable, "allies", class, stat_num ); 145 | load_default_loadout_raw( datatable, "axis", class, stat_num ); 146 | } 147 | else 148 | load_default_loadout_raw( datatable, team, class, stat_num ); 149 | } 150 | 151 | load_default_loadout_raw( class_dataTable, team, class, stat_num ) 152 | { 153 | // give primary weapon and attachment 154 | primary_attachment = tablelookup( class_dataTable, 1, stat_num + 2, 4 ); 155 | if( primary_attachment != "" && primary_attachment != "none" ) 156 | level.classWeapons[team][class][0] = tablelookup( class_dataTable, 1, stat_num + 1, 4 ) + "_" + primary_attachment + "_mp"; 157 | else 158 | level.classWeapons[team][class][0] = tablelookup( class_dataTable, 1, stat_num + 1, 4 ) + "_mp"; 159 | 160 | // give secondary weapon and attachment 161 | secondary_attachment = tablelookup( class_dataTable, 1, stat_num + 4, 4 ); 162 | if( secondary_attachment != "" && secondary_attachment != "none" ) 163 | level.classSidearm[team][class] = tablelookup( class_dataTable, 1, stat_num + 3, 4 ) + "_" + secondary_attachment + "_mp"; 164 | else 165 | level.classSidearm[team][class] = tablelookup( class_dataTable, 1, stat_num + 3, 4 ) + "_mp"; 166 | 167 | // give frag and special grenades 168 | level.classGrenades[class]["primary"]["type"] = tablelookup( class_dataTable, 1, stat_num, 4 ) + "_mp"; 169 | level.classGrenades[class]["primary"]["count"] = int( tablelookup( class_dataTable, 1, stat_num, 6 ) ); 170 | level.classGrenades[class]["secondary"]["type"] = tablelookup( class_dataTable, 1, stat_num + 8, 4 ) + "_mp"; 171 | level.classGrenades[class]["secondary"]["count"] = int( tablelookup( class_dataTable, 1, stat_num + 8, 6 ) ); 172 | 173 | // give default class perks 174 | level.default_perk[class] = []; 175 | level.default_perk[class][0] = tablelookup( class_dataTable, 1, stat_num + 5, 4 ); 176 | level.default_perk[class][1] = tablelookup( class_dataTable, 1, stat_num + 6, 4 ); 177 | level.default_perk[class][2] = tablelookup( class_dataTable, 1, stat_num + 7, 4 ); 178 | 179 | // give all inventory 180 | inventory_ref = tablelookup( class_dataTable, 1, stat_num + 5, 4 ); 181 | if( isdefined( inventory_ref ) && tablelookup( "mp/statsTable.csv", 6, inventory_ref, 2 ) == "inventory" ) 182 | { 183 | inventory_count = int( tablelookup( "mp/statsTable.csv", 6, inventory_ref, 5 ) ); 184 | inventory_item_ref = tablelookup( "mp/statsTable.csv", 6, inventory_ref, 4 ); 185 | assertex( isdefined( inventory_count ) && inventory_count != 0 && isdefined( inventory_item_ref ) && inventory_item_ref != "" , "Inventory in statsTable.csv not specified correctly" ); 186 | 187 | level.classItem[team][class]["type"] = inventory_item_ref; 188 | level.classItem[team][class]["count"] = inventory_count; 189 | } 190 | else 191 | { 192 | level.classItem[team][class]["type"] = ""; 193 | level.classItem[team][class]["count"] = 0; 194 | } 195 | // give all inventory 196 | //level.classItem[team][class]["type"] = inventory; 197 | //level.classItem[team][class]["count"] = inv_count; 198 | } 199 | 200 | weapon_class_register( weapon, weapon_type ) 201 | { 202 | if( isSubstr( "weapon_smg weapon_assault weapon_projectile weapon_sniper weapon_shotgun weapon_lmg", weapon_type ) ) 203 | level.primary_weapon_array[weapon] = 1; 204 | else if( weapon_type == "weapon_pistol" ) 205 | level.side_arm_array[weapon] = 1; 206 | else if( weapon_type == "weapon_grenade" ) 207 | level.grenade_array[weapon] = 1; 208 | else if( weapon_type == "weapon_explosive" ) 209 | level.inventory_array[weapon] = 1; 210 | else 211 | assertex( false, "Weapon group info is missing from statsTable for: " + weapon_type ); 212 | } 213 | 214 | // create a class init 215 | cac_init() 216 | { 217 | // max create a class "class" allowed 218 | level.cac_size = 5; 219 | 220 | // init cac data table column definitions 221 | level.cac_numbering = 0; // unique unsigned int - general numbering of all items 222 | level.cac_cstat = 1; // unique unsigned int - stat number assigned 223 | level.cac_cgroup = 2; // string - item group name, "primary" "secondary" "inventory" "specialty" "grenades" "special grenades" "stow back" "stow side" "attachment" 224 | level.cac_cname = 3; // string - name of the item, "Extreme Conditioning" 225 | level.cac_creference = 4; // string - reference string of the item, "m203" "m16" "bulletdamage" "c4" 226 | level.cac_ccount = 5; // signed int - item count, if exists, -1 = has no count 227 | level.cac_cimage = 6; // string - item's image file name 228 | level.cac_cdesc = 7; // long string - item's description 229 | level.cac_cstring = 8; // long string - item's other string data, reserved 230 | level.cac_cint = 9; // signed int - item's other number data, used for attachment number representations 231 | level.cac_cunlock = 10; // unsigned int - represents if item is unlocked by default 232 | level.cac_cint2 = 11; // signed int - item's other number data, used for primary weapon camo skin number representations 233 | 234 | // generating camo/attachment data vars collected from attachmentTable.csv 235 | level.tbl_CamoSkin = []; 236 | for( i=0; i<8; i++ ) 237 | { 238 | // this for-loop is shared because there are equal number of attachments and camo skins. 239 | level.tbl_CamoSkin[i]["bitmask"] = int( tableLookup( "mp/attachmentTable.csv", 11, i, 10 ) ); 240 | 241 | level.tbl_WeaponAttachment[i]["bitmask"] = int( tableLookup( "mp/attachmentTable.csv", 9, i, 10 ) ); 242 | level.tbl_WeaponAttachment[i]["reference"] = tableLookup( "mp/attachmentTable.csv", 9, i, 4 ); 243 | } 244 | 245 | level.tbl_weaponIDs = []; 246 | for( i=0; i<150; i++ ) 247 | { 248 | reference_s = tableLookup( "mp/statsTable.csv", 0, i, 4 ); 249 | if( reference_s != "" ) 250 | { 251 | level.tbl_weaponIDs[i]["reference"] = reference_s; 252 | level.tbl_weaponIDs[i]["group"] = tablelookup( "mp/statstable.csv", 0, i, 2 ); 253 | level.tbl_weaponIDs[i]["count"] = int( tablelookup( "mp/statstable.csv", 0, i, 5 ) ); 254 | level.tbl_weaponIDs[i]["attachment"] = tablelookup( "mp/statstable.csv", 0, i, 8 ); 255 | } 256 | else 257 | continue; 258 | } 259 | 260 | perkReferenceToIndex = []; 261 | 262 | level.perkNames = []; 263 | level.perkIcons = []; 264 | level.PerkData = []; 265 | // generating perk data vars collected form statsTable.csv 266 | for( i=150; i<194; i++ ) 267 | { 268 | reference_s = tableLookup( "mp/statsTable.csv", 0, i, 4 ); 269 | if( reference_s != "" ) 270 | { 271 | level.tbl_PerkData[i]["reference"] = reference_s; 272 | level.tbl_PerkData[i]["reference_full"] = tableLookup( "mp/statsTable.csv", 0, i, 6 ); 273 | level.tbl_PerkData[i]["count"] = int( tableLookup( "mp/statsTable.csv", 0, i, 5 ) ); 274 | level.tbl_PerkData[i]["group"] = tableLookup( "mp/statsTable.csv", 0, i, 2 ); 275 | level.tbl_PerkData[i]["name"] = tableLookupIString( "mp/statsTable.csv", 0, i, 3 ); 276 | precacheString( level.tbl_PerkData[i]["name"] ); 277 | level.tbl_PerkData[i]["perk_num"] = tableLookup( "mp/statsTable.csv", 0, i, 8 ); 278 | 279 | perkReferenceToIndex[ level.tbl_PerkData[i]["reference_full"] ] = i; 280 | 281 | level.perkNames[level.tbl_PerkData[i]["reference_full"]] = level.tbl_PerkData[i]["name"]; 282 | level.perkIcons[level.tbl_PerkData[i]["reference_full"]] = level.tbl_PerkData[i]["reference_full"]; 283 | precacheShader( level.perkIcons[level.tbl_PerkData[i]["reference_full"]] ); 284 | } 285 | else 286 | continue; 287 | } 288 | 289 | // allowed perks in each slot, for validation. 290 | level.allowedPerks[0] = []; 291 | level.allowedPerks[1] = []; 292 | level.allowedPerks[2] = []; 293 | 294 | level.allowedPerks[0][ 0] = 190; // 190 through 193 are attachments and "none" 295 | level.allowedPerks[0][ 1] = 191; 296 | level.allowedPerks[0][ 2] = 192; 297 | level.allowedPerks[0][ 3] = 193; 298 | level.allowedPerks[0][ 4] = perkReferenceToIndex[ "specialty_weapon_c4" ]; 299 | level.allowedPerks[0][ 5] = perkReferenceToIndex[ "specialty_specialgrenade" ]; 300 | level.allowedPerks[0][ 6] = perkReferenceToIndex[ "specialty_weapon_rpg" ]; 301 | level.allowedPerks[0][ 7] = perkReferenceToIndex[ "specialty_weapon_claymore" ]; 302 | level.allowedPerks[0][ 8] = perkReferenceToIndex[ "specialty_fraggrenade" ]; 303 | level.allowedPerks[0][ 9] = perkReferenceToIndex[ "specialty_extraammo" ]; 304 | level.allowedPerks[0][10] = perkReferenceToIndex[ "specialty_detectexplosive" ]; 305 | 306 | level.allowedPerks[1][ 0] = 190; 307 | level.allowedPerks[1][ 1] = perkReferenceToIndex[ "specialty_bulletdamage" ]; 308 | level.allowedPerks[1][ 2] = perkReferenceToIndex[ "specialty_armorvest" ]; 309 | level.allowedPerks[1][ 3] = perkReferenceToIndex[ "specialty_fastreload" ]; 310 | level.allowedPerks[1][ 4] = perkReferenceToIndex[ "specialty_rof" ]; 311 | level.allowedPerks[1][ 5] = perkReferenceToIndex[ "specialty_twoprimaries" ]; 312 | level.allowedPerks[1][ 6] = perkReferenceToIndex[ "specialty_gpsjammer" ]; 313 | level.allowedPerks[1][ 7] = perkReferenceToIndex[ "specialty_explosivedamage" ]; 314 | 315 | level.allowedPerks[2][ 0] = 190; 316 | level.allowedPerks[2][ 1] = perkReferenceToIndex[ "specialty_longersprint" ]; 317 | level.allowedPerks[2][ 2] = perkReferenceToIndex[ "specialty_bulletaccuracy" ]; 318 | level.allowedPerks[2][ 3] = perkReferenceToIndex[ "specialty_pistoldeath" ]; 319 | level.allowedPerks[2][ 4] = perkReferenceToIndex[ "specialty_grenadepulldeath" ]; 320 | level.allowedPerks[2][ 5] = perkReferenceToIndex[ "specialty_bulletpenetration" ]; 321 | level.allowedPerks[2][ 6] = perkReferenceToIndex[ "specialty_holdbreath" ]; 322 | level.allowedPerks[2][ 7] = perkReferenceToIndex[ "specialty_quieter" ]; 323 | level.allowedPerks[2][ 8] = perkReferenceToIndex[ "specialty_parabolic" ]; 324 | } 325 | 326 | getClassChoice( response ) 327 | { 328 | tokens = strtok( response, "," ); 329 | 330 | assert( isDefined( level.classMap[tokens[0]] ) ); 331 | 332 | return ( level.classMap[tokens[0]] ); 333 | } 334 | 335 | getWeaponChoice( response ) 336 | { 337 | tokens = strtok( response, "," ); 338 | if ( tokens.size > 1 ) 339 | return int(tokens[1]); 340 | else 341 | return 0; 342 | } 343 | 344 | // ============================================================================ 345 | // obtains custom class setup from stat values 346 | cac_getdata() 347 | { 348 | if ( isDefined( self.cac_initialized ) ) 349 | return; 350 | 351 | /* custom class stat allocation order, example of custom class slot 1 352 | 201 weapon_primary 353 | 202 weapon_primary attachment 354 | 203 weapon_secondary 355 | 204 weapon_secondary attachment 356 | 205 weapon_specialty1 357 | 206 weapon_specialty2 358 | 207 weapon_specialty3 359 | 208 weapon_special_grenade_type 360 | 209 weapon_primary_camo_style 361 | */ 362 | 363 | for( i = 0; i < 5; i ++ ) 364 | { 365 | //assertex( self getstat ( i*10+200 ) == 1, "Custom class not initialized!" ); 366 | 367 | // do not change the allocation and assignment of 0-299 stat bytes, or data will be misinterpreted by this function! 368 | primary_num = self getstat ( 200+(i*10)+1 ); // returns weapon number (also the unlock stat number from data table) 369 | primary_attachment_flag = self getstat ( 200+(i*10)+2 ); // returns attachment number (from data table) 370 | if ( !isDefined( level.tbl_WeaponAttachment[primary_attachment_flag] ) ) // handle bad attachment stat 371 | primary_attachment_flag = 0; 372 | primary_attachment_mask = level.tbl_WeaponAttachment[primary_attachment_flag]["bitmask"]; 373 | secondary_num = self getstat ( 200+(i*10)+3 ); // returns weapon number (also the unlock stat number from data table) 374 | secondary_attachment_flag = self getstat ( 200+(i*10)+4 ); // returns attachment number (from data table) 375 | if ( !isDefined( level.tbl_WeaponAttachment[secondary_attachment_flag] ) ) // handle bad attachment stat 376 | secondary_attachment_flag = 0; 377 | secondary_attachment_mask = level.tbl_WeaponAttachment[secondary_attachment_flag]["bitmask"]; 378 | specialty1 = self getstat ( 200+(i*10)+5 ); // returns specialty number (from data table) 379 | specialty2 = self getstat ( 200+(i*10)+6 ); // returns specialty number (from data table) 380 | specialty3 = self getstat ( 200+(i*10)+7 ); // returns specialty number (from data table) 381 | special_grenade = self getstat ( 200+(i*10)+8 ); // returns special grenade type as single special grenade items (from data table) 382 | camo_num = self getstat ( 200+(i*10)+9 ); // returns camo number (from data table) 383 | 384 | if ( camo_num < 0 || camo_num >= level.tbl_CamoSkin.size ) 385 | { 386 | println( "^1Warning: (" + self.name + ") camo " + camo_num + " is invalid. Setting to none." ); 387 | camo_num = 0; 388 | } 389 | 390 | camo_mask = level.tbl_CamoSkin[camo_num]["bitmask"]; 391 | 392 | m16WeaponIndex = 25; 393 | assert( level.tbl_weaponIDs[m16WeaponIndex]["reference"] == "m16" ); 394 | if ( primary_num < 0 || !isDefined( level.tbl_weaponIDs[ primary_num ] ) ) 395 | { 396 | primary_num = m16WeaponIndex; 397 | primary_attachment_flag = 0; 398 | } 399 | if ( secondary_num < 0 || !isDefined( level.tbl_weaponIDs[ secondary_num ] ) ) 400 | { 401 | secondary_num = 0; 402 | secondary_attachment_flag = 0; 403 | } 404 | 405 | specialty1 = validatePerk( specialty1, 0 ); 406 | specialty2 = validatePerk( specialty2, 1 ); 407 | specialty3 = validatePerk( specialty3, 2 ); 408 | 409 | // if specialty2 is not Overkill, disallow anything besides pistols for secondary weapon 410 | if ( level.tbl_PerkData[specialty2]["reference_full"] != "specialty_twoprimaries" ) 411 | { 412 | if ( level.tbl_weaponIDs[secondary_num]["group"] != "weapon_pistol" ) 413 | { 414 | println( "^1Warning: (" + self.name + ") secondary weapon is not a pistol but perk 2 is not Overkill. Setting secondary weapon to pistol." ); 415 | secondary_num = 0; 416 | secondary_attachment_flag = 0; 417 | } 418 | } 419 | // if certain attachments are used, make sure specialty1 is set right 420 | primary_attachment_ref = level.tbl_WeaponAttachment[primary_attachment_flag]["reference"]; 421 | secondary_attachment_ref = level.tbl_WeaponAttachment[secondary_attachment_flag]["reference"]; 422 | if ( primary_attachment_ref == "grip" || primary_attachment_ref == "gl" || secondary_attachment_ref == "grip" || secondary_attachment_ref == "gl" ) 423 | { 424 | if ( specialty1 != 190 && specialty1 != 191 && specialty1 != 192 && specialty1 != 193 ) 425 | { 426 | println( "^1Warning: (" + self.name + ") grip or grenade launcher is used but perk 1 was index " + specialty1 + ". Setting perk 1 to none." ); 427 | specialty1 = 193; // 193 = there's an attachment, so no perk 428 | } 429 | } 430 | 431 | // validate weapon attachments, if faulty attachement found, reset to no attachments 432 | primary_ref = level.tbl_WeaponIDs[primary_num]["reference"]; 433 | primary_attachment_set = level.tbl_weaponIDs[primary_num]["attachment"]; 434 | secondary_ref = level.tbl_WeaponIDs[secondary_num]["reference"]; 435 | secondary_attachment_set = level.tbl_weaponIDs[secondary_num]["attachment"]; 436 | if ( !issubstr( primary_attachment_set, primary_attachment_ref ) ) 437 | { 438 | println( "^1Warning: (" + self.name + ") attachment [" + primary_attachment_ref + "] is not valid for [" + primary_ref + "]. Removing attachment." ); 439 | primary_attachment_flag = 0; 440 | } 441 | if ( !issubstr( secondary_attachment_set, secondary_attachment_ref ) ) 442 | { 443 | println( "^1Warning: (" + self.name + ") attachment [" + secondary_attachment_ref + "] is not valid for [" + secondary_ref + "]. Removing attachment." ); 444 | secondary_attachment_flag = 0; 445 | } 446 | 447 | // validate special grenade type 448 | flashGrenadeIndex = 101; 449 | assert( level.tbl_weaponIDs[flashGrenadeIndex]["reference"] == "flash_grenade" ); // if this fails we need to change flashGrenadeIndex 450 | if ( !isDefined( level.tbl_weaponIDs[special_grenade] ) ) 451 | special_grenade = flashGrenadeIndex; 452 | specialGrenadeType = level.tbl_weaponIDs[special_grenade]["reference"]; 453 | if ( specialGrenadeType != "flash_grenade" && specialGrenadeType != "smoke_grenade" && specialGrenadeType != "concussion_grenade" ) 454 | { 455 | println( "^1Warning: (" + self.name + ") special grenade " + special_grenade + " is invalid. Setting to flash grenade." ); 456 | special_grenade = flashGrenadeIndex; 457 | } 458 | 459 | if ( specialGrenadeType == "smoke_grenade" && level.tbl_PerkData[specialty1]["reference_full"] == "specialty_specialgrenade" ) 460 | { 461 | println( "^1Warning: (" + self.name + ") smoke grenade may not be used with extra special grenades. Setting to flash grenade." ); 462 | special_grenade = flashGrenadeIndex; 463 | } 464 | 465 | // apply attachment to primary weapon, getting weapon reference strings 466 | attachment_string = level.tbl_WeaponAttachment[primary_attachment_flag]["reference"]; 467 | if( primary_attachment_flag != 0 && attachment_string != "" ) 468 | self.custom_class[i]["primary"] = level.tbl_weaponIDs[primary_num]["reference"]+"_"+attachment_string+"_mp"; 469 | else 470 | self.custom_class[i]["primary"] = level.tbl_weaponIDs[primary_num]["reference"]+"_mp"; 471 | 472 | // apply attachment to secondary weapon, getting weapon reference strings 473 | attachment_string = level.tbl_WeaponAttachment[secondary_attachment_flag]["reference"]; 474 | if( secondary_attachment_flag != 0 && attachment_string != "" ) 475 | self.custom_class[i]["secondary"] = level.tbl_weaponIDs[secondary_num]["reference"]+"_"+attachment_string+"_mp"; 476 | else 477 | self.custom_class[i]["secondary"] = level.tbl_weaponIDs[secondary_num]["reference"]+"_mp"; 478 | 479 | // obtaining specialties, getting specialty reference strings 480 | assertex( isdefined( level.tbl_PerkData[specialty1] ), "Specialty #:"+specialty1+"'s data is undefined" ); 481 | self.custom_class[i]["specialty1"] = level.tbl_PerkData[specialty1]["reference_full"]; //tablelookup( "mp/statstable.csv", level.cac_cstat, specialty1, level.cac_cimage ); 482 | self.custom_class[i]["specialty1_weaponref"] = level.tbl_PerkData[specialty1]["reference"]; //tablelookup( "mp/statstable.csv", level.cac_cstat, specialty1, level.cac_creference ); 483 | self.custom_class[i]["specialty1_count"] = level.tbl_PerkData[specialty1]["count"]; //int( tablelookup( "mp/statstable.csv", level.cac_cstat, specialty1, level.cac_ccount ) ); 484 | self.custom_class[i]["specialty1_group"] = level.tbl_PerkData[specialty1]["group"]; //tablelookup( "mp/statstable.csv", level.cac_cstat, specialty1, level.cac_cgroup ); 485 | 486 | self.custom_class[i]["specialty2"] = level.tbl_PerkData[specialty2]["reference"]; //tablelookup( "mp/statstable.csv", level.cac_cstat, specialty2, level.cac_creference ); 487 | self.custom_class[i]["specialty2_weaponref"] = self.custom_class[i]["specialty2"]; 488 | self.custom_class[i]["specialty2_count"] = level.tbl_PerkData[specialty2]["count"]; //int( tablelookup( "mp/statstable.csv", level.cac_cstat, specialty2, level.cac_ccount ) ); 489 | self.custom_class[i]["specialty2_group"] = level.tbl_PerkData[specialty2]["group"]; //tablelookup( "mp/statstable.csv", level.cac_cstat, specialty2, level.cac_cgroup ); 490 | 491 | self.custom_class[i]["specialty3"] = level.tbl_PerkData[specialty3]["reference"]; //tablelookup( "mp/statstable.csv", level.cac_cstat, specialty3, level.cac_creference ); 492 | self.custom_class[i]["specialty3_weaponref"] = self.custom_class[i]["specialty3"]; 493 | self.custom_class[i]["specialty3_count"] = level.tbl_PerkData[specialty3]["count"]; //int( tablelookup( "mp/statstable.csv", level.cac_cstat, specialty3, level.cac_ccount ) ); 494 | self.custom_class[i]["specialty3_group"] = level.tbl_PerkData[specialty3]["group"]; //tablelookup( "mp/statstable.csv", level.cac_cstat, specialty3, level.cac_cgroup ); 495 | 496 | // builds the full special grenade reference string 497 | self.custom_class[i]["special_grenade"] = level.tbl_weaponIDs[special_grenade]["reference"]+"_mp"; //tablelookup( "mp/statstable.csv", level.cac_numbering, special_grenade, level.cac_creference ) + "_mp"; 498 | self.custom_class[i]["special_grenade_count"] = level.tbl_weaponIDs[special_grenade]["count"]; //int( tablelookup( "mp/statstable.csv", level.cac_numbering, special_grenade, level.cac_ccount ) ); 499 | 500 | // camo selection, default 0 = no camo skin 501 | self.custom_class[i]["camo_num"] = camo_num; 502 | self.cac_initialized = true; 503 | 504 | /* debug 505 | println( "\n ========== CLASS DEBUG INFO ========== \n" ); 506 | println( "Primary: "+self.custom_class[i]["primary"] ); 507 | println( "Secondary: "+self.custom_class[i]["secondary"] ); 508 | println( "Specialty1: "+self.custom_class[i]["specialty1"]+" - Group: "+self.custom_class[i]["specialty1_group"]+" - Count: "+self.custom_class[i]["specialty1_count"] ); 509 | println( "Specialty2: "+self.custom_class[i]["specialty2"] ); 510 | println( "Specialty3: "+self.custom_class[i]["specialty3"] ); 511 | println( "Special Grenade: "+self.custom_class[i]["special_grenade"]+" - Count: "+self.custom_class[i]["special_grenade_count"] ); 512 | println( "Primary Camo: "+attachmenttablelookup( level.cac_cname, level.cac_cint2, camo_num ) ); 513 | */ 514 | } 515 | } 516 | 517 | validatePerk( perkIndex, perkSlotIndex ) 518 | { 519 | for ( i = 0; i < level.allowedPerks[ perkSlotIndex ].size; i++ ) 520 | { 521 | if ( perkIndex == level.allowedPerks[ perkSlotIndex ][i] ) 522 | return perkIndex; 523 | } 524 | println( "^1Warning: (" + self.name + ") Perk " + level.tbl_PerkData[perkIndex]["reference_full"] + " is not allowed for perk slot index " + perkSlotIndex + "; replacing with no perk" ); 525 | return 190; 526 | } 527 | 528 | 529 | logClassChoice( class, primaryWeapon, specialType, perks ) 530 | { 531 | if ( class == self.lastClass ) 532 | return; 533 | 534 | self logstring( "choseclass: " + class + " weapon: " + primaryWeapon + " special: " + specialType ); 535 | for( i=0; i 0, "Default class: " + self.pers["class"] + " is missing specialties " ); 786 | 787 | // re-registering perks to code since perks are cleared after respawn in case if players switch classes 788 | self register_perks(); 789 | 790 | // weapon override for round based gametypes 791 | // TODO: if they switched to a sidearm, we shouldn't give them that as their primary! 792 | if ( isDefined( self.pers["weapon"] ) && self.pers["weapon"] != "none" ) 793 | weapon = self.pers["weapon"]; 794 | else 795 | weapon = level.classWeapons[team][class][primaryIndex]; 796 | 797 | sidearm = level.classSidearm[team][class]; 798 | 799 | self GiveWeapon( sidearm ); 800 | if ( self cac_hasSpecialty( "specialty_extraammo" ) ) 801 | self giveMaxAmmo( sidearm ); 802 | 803 | // give primary weapon 804 | primaryWeapon = weapon; 805 | 806 | primaryTokens = strtok( primaryWeapon, "_" ); 807 | self.pers["primaryWeapon"] = primaryTokens[0]; 808 | 809 | if ( self.pers["primaryWeapon"] == "m14" ) 810 | self.pers["primaryWeapon"] = "m21"; 811 | 812 | self maps\mp\gametypes\_teams::playerModelForWeapon( self.pers["primaryWeapon"] ); 813 | 814 | self GiveWeapon( weapon ); 815 | if( self cac_hasSpecialty( "specialty_extraammo" ) ) 816 | self giveMaxAmmo( weapon ); 817 | self setSpawnWeapon( weapon ); 818 | 819 | // give secondary weapon 820 | self SetActionSlot( 1, "nightvision" ); 821 | 822 | secondaryWeapon = level.classItem[team][class]["type"]; 823 | if ( secondaryWeapon != "" ) 824 | { 825 | self GiveWeapon( secondaryWeapon ); 826 | 827 | self setWeaponAmmoOverall( secondaryWeapon, level.classItem[team][class]["count"] ); 828 | 829 | self SetActionSlot( 3, "weapon", secondaryWeapon ); 830 | self SetActionSlot( 4, "" ); 831 | } 832 | else 833 | { 834 | self SetActionSlot( 3, "altMode" ); 835 | self SetActionSlot( 4, "" ); 836 | } 837 | 838 | grenadeTypePrimary = level.classGrenades[class]["primary"]["type"]; 839 | if ( grenadeTypePrimary != "" ) 840 | { 841 | grenadeCount = level.classGrenades[class]["primary"]["count"]; 842 | 843 | self GiveWeapon( grenadeTypePrimary ); 844 | self SetWeaponAmmoClip( grenadeTypePrimary, grenadeCount ); 845 | self SwitchToOffhand( grenadeTypePrimary ); 846 | } 847 | 848 | grenadeTypeSecondary = level.classGrenades[class]["secondary"]["type"]; 849 | if ( grenadeTypeSecondary != "" ) 850 | { 851 | grenadeCount = level.classGrenades[class]["secondary"]["count"]; 852 | 853 | if ( grenadeTypeSecondary == level.weapons["flash"]) 854 | self setOffhandSecondaryClass("flash"); 855 | else 856 | self setOffhandSecondaryClass("smoke"); 857 | 858 | self giveWeapon( grenadeTypeSecondary ); 859 | self SetWeaponAmmoClip( grenadeTypeSecondary, grenadeCount ); 860 | } 861 | 862 | self thread logClassChoice( class, primaryWeapon, grenadeTypeSecondary, self.specialty ); 863 | } 864 | 865 | switch ( weaponClass( primaryWeapon ) ) 866 | { 867 | case "rifle": 868 | self setMoveSpeedScale( 0.95 ); 869 | break; 870 | case "pistol": 871 | self setMoveSpeedScale( 1.0 ); 872 | break; 873 | case "mg": 874 | self setMoveSpeedScale( 0.875 ); 875 | break; 876 | case "smg": 877 | self setMoveSpeedScale( 1.0 ); 878 | break; 879 | case "spread": 880 | self setMoveSpeedScale( 1.0 ); 881 | break; 882 | default: 883 | self setMoveSpeedScale( 1.0 ); 884 | break; 885 | } 886 | 887 | // cac specialties that require loop threads 888 | self cac_selector(); 889 | } 890 | 891 | // sets the amount of ammo in the gun. 892 | // if the clip maxs out, the rest goes into the stock. 893 | setWeaponAmmoOverall( weaponname, amount ) 894 | { 895 | if ( isWeaponClipOnly( weaponname ) ) 896 | { 897 | self setWeaponAmmoClip( weaponname, amount ); 898 | } 899 | else 900 | { 901 | self setWeaponAmmoClip( weaponname, amount ); 902 | diff = amount - self getWeaponAmmoClip( weaponname ); 903 | assert( diff >= 0 ); 904 | self setWeaponAmmoStock( weaponname, diff ); 905 | } 906 | } 907 | 908 | replenishLoadout() // used by ammo hardpoint. 909 | { 910 | team = self.pers["team"]; 911 | class = self.pers["class"]; 912 | 913 | weaponsList = self GetWeaponsList(); 914 | for( idx = 0; idx < weaponsList.size; idx++ ) 915 | { 916 | weapon = weaponsList[idx]; 917 | 918 | self giveMaxAmmo( weapon ); 919 | self SetWeaponAmmoClip( weapon, 9999 ); 920 | 921 | if ( weapon == "claymore_mp" || weapon == "claymore_detonator_mp" ) 922 | self setWeaponAmmoStock( weapon, 2 ); 923 | } 924 | 925 | if ( self getAmmoCount( level.classGrenades[class]["primary"]["type"] ) < level.classGrenades[class]["primary"]["count"] ) 926 | self SetWeaponAmmoClip( level.classGrenades[class]["primary"]["type"], level.classGrenades[class]["primary"]["count"] ); 927 | 928 | if ( self getAmmoCount( level.classGrenades[class]["secondary"]["type"] ) < level.classGrenades[class]["secondary"]["count"] ) 929 | self SetWeaponAmmoClip( level.classGrenades[class]["secondary"]["type"], level.classGrenades[class]["secondary"]["count"] ); 930 | } 931 | 932 | onPlayerConnecting() 933 | { 934 | for(;;) 935 | { 936 | level waittill( "connecting", player ); 937 | 938 | if ( !level.oldschool ) 939 | { 940 | if ( !isDefined( player.pers["class"] ) ) 941 | { 942 | player.pers["class"] = ""; 943 | } 944 | player.class = player.pers["class"]; 945 | player.lastClass = ""; 946 | } 947 | player.detectExplosives = false; 948 | player.bombSquadIcons = []; 949 | player.bombSquadIds = []; 950 | } 951 | } 952 | 953 | 954 | fadeAway( waitDelay, fadeDelay ) 955 | { 956 | wait waitDelay; 957 | 958 | self fadeOverTime( fadeDelay ); 959 | self.alpha = 0; 960 | } 961 | 962 | 963 | setClass( newClass ) 964 | { 965 | self.curClass = newClass; 966 | } 967 | 968 | 969 | // ============================================================================================ 970 | // ======= ======= 971 | // ======= Create a Class Specialties ======= 972 | // ======= ======= 973 | // ============================================================================================ 974 | 975 | initPerkDvars() 976 | { 977 | level.cac_bulletdamage_data = cac_get_dvar_int( "perk_bulletDamage", "40" ); // increased bullet damage by this % 978 | level.cac_armorvest_data = cac_get_dvar_int( "perk_armorVest", "75" ); // increased health by this % 979 | level.cac_explosivedamage_data = cac_get_dvar_int( "perk_explosiveDamage", "25" ); // increased explosive damage by this % 980 | } 981 | 982 | // CAC: Selector function, calls the individual cac features according to player's class settings 983 | // Info: Called every time player spawns during loadout stage 984 | cac_selector() 985 | { 986 | perks = self.specialty; 987 | 988 | self.detectExplosives = false; 989 | for( i=0; i " + victim.name + "'s armor countered " + attacker.name + "'s increased bullet damage" ); 1097 | #/ 1098 | } 1099 | else 1100 | { 1101 | final_damage = damage*(100+level.cac_bulletdamage_data)/100; 1102 | /# 1103 | if ( getdvarint("scr_perkdebug") ) 1104 | println( "Perk/> " + attacker.name + "'s bullet damage did extra damage to " + victim.name ); 1105 | #/ 1106 | } 1107 | } 1108 | else if( attacker cac_hasSpecialty( "specialty_explosivedamage" ) && isExplosiveDamage( meansofdeath ) ) 1109 | { 1110 | // if victim has armor then do not change damage, it is cancelled out, else damage is increased 1111 | 1112 | if( isdefined( victim ) && isPlayer( victim ) && victim cac_hasSpecialty( "specialty_armorvest" ) ) 1113 | { 1114 | final_damage = old_damage; 1115 | /# 1116 | if ( getdvarint("scr_perkdebug") ) 1117 | println( "Perk/> " + victim.name + "'s armor countered " + attacker.name + "'s increased explosive damage" ); 1118 | #/ 1119 | } 1120 | else 1121 | { 1122 | final_damage = damage*(100+level.cac_explosivedamage_data)/100; 1123 | /# 1124 | if ( getdvarint("scr_perkdebug") ) 1125 | println( "Perk/> " + attacker.name + "'s explosive damage did extra damage to " + victim.name ); 1126 | #/ 1127 | } 1128 | } 1129 | else 1130 | { 1131 | // if attacker has no bullet damage then check if victim has armor 1132 | // if victim has armor then less damage is taken, else damage unchanged 1133 | 1134 | if( isdefined( victim ) && isPlayer( victim ) && victim cac_hasSpecialty( "specialty_armorvest" ) ) 1135 | { 1136 | final_damage = old_damage*(level.cac_armorvest_data/100); 1137 | /# 1138 | if ( getdvarint("scr_perkdebug") ) 1139 | println( "Perk/> " + victim.name + "'s armor decreased " + attacker.name + "'s damage" ); 1140 | #/ 1141 | } 1142 | else 1143 | { 1144 | final_damage = old_damage; 1145 | } 1146 | } 1147 | 1148 | // debug 1149 | /# 1150 | if ( getdvarint("scr_perkdebug") ) 1151 | println( "Perk/> Damage Factor: " + final_damage/old_damage + " - Pre Damage: " + old_damage + " - Post Damage: " + final_damage ); 1152 | #/ 1153 | 1154 | // return unchanged damage 1155 | return int( final_damage ); 1156 | } 1157 | 1158 | // including grenade launcher, grenade, RPG, C4, claymore 1159 | isExplosiveDamage( meansofdeath ) 1160 | { 1161 | explosivedamage = "MOD_GRENADE MOD_GRENADE_SPLASH MOD_PROJECTILE MOD_PROJECTILE_SPLASH MOD_EXPLOSIVE"; 1162 | if( isSubstr( explosivedamage, meansofdeath ) ) 1163 | return true; 1164 | return false; 1165 | } 1166 | 1167 | // if primary weapon damage 1168 | isPrimaryDamage( meansofdeath ) 1169 | { 1170 | // including pistols as well since sometimes they share ammo 1171 | if( meansofdeath == "MOD_RIFLE_BULLET" || meansofdeath == "MOD_PISTOL_BULLET" ) 1172 | return true; 1173 | return false; 1174 | } 1175 | -------------------------------------------------------------------------------- /src/maps/mp/gametypes/_menus.gsc: -------------------------------------------------------------------------------- 1 | // _____ _ __ __ _ 2 | // / ____| | / _|/ _| (_) 3 | // | (___ | |__ _ _| |_| |_ _ __ _ 4 | // \___ \| '_ \| | | | _| _| '_ \| | 5 | // ____) | | | | |_| | | | | | | | | | 6 | // |_____/|_| |_|\__,_|_| |_| |_| |_|_| 7 | // Level Hack Server Mod 8 | // 9 | // This file is part of Shuffni's level hack server mod. 10 | // 11 | // This is free software: you can redistribute it and/or modify 12 | // it under the terms of the GNU General Public License as published by 13 | // the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | // GNU General Public License for more details. 20 | // 21 | // You should have received a copy of the GNU General Public License 22 | // along with this software. If not, see . 23 | 24 | init() 25 | { 26 | game["menu_team"] = "team_marinesopfor"; 27 | game["menu_class_allies"] = "class_marines"; 28 | game["menu_changeclass_allies"] = "changeclass_marines"; 29 | game["menu_initteam_allies"] = "initteam_marines"; 30 | game["menu_class_axis"] = "class_opfor"; 31 | game["menu_changeclass_axis"] = "changeclass_opfor"; 32 | game["menu_initteam_axis"] = "initteam_opfor"; 33 | game["menu_class"] = "class"; 34 | game["menu_changeclass"] = "changeclass"; 35 | game["menu_changeclass_offline"] = "changeclass_offline"; 36 | 37 | if ( !level.console ) 38 | { 39 | game["menu_callvote"] = "callvote"; 40 | game["menu_muteplayer"] = "muteplayer"; 41 | precacheMenu(game["menu_callvote"]); 42 | precacheMenu(game["menu_muteplayer"]); 43 | 44 | // ---- back up one folder to access game_summary.menu ---- 45 | // game summary menu file precache 46 | game["menu_eog_main"] = "endofgame"; 47 | 48 | // menu names (do not precache since they are in game_summary_ingame which should be precached 49 | game["menu_eog_unlock"] = "popup_unlock"; 50 | game["menu_eog_summary"] = "popup_summary"; 51 | game["menu_eog_unlock_page1"] = "popup_unlock_page1"; 52 | game["menu_eog_unlock_page2"] = "popup_unlock_page2"; 53 | 54 | precacheMenu(game["menu_eog_main"]); 55 | precacheMenu(game["menu_eog_unlock"]); 56 | precacheMenu(game["menu_eog_summary"]); 57 | precacheMenu(game["menu_eog_unlock_page1"]); 58 | precacheMenu(game["menu_eog_unlock_page2"]); 59 | 60 | } 61 | else 62 | { 63 | game["menu_controls"] = "ingame_controls"; 64 | game["menu_options"] = "ingame_options"; 65 | game["menu_leavegame"] = "popup_leavegame"; 66 | 67 | if(level.splitscreen) 68 | { 69 | game["menu_team"] += "_splitscreen"; 70 | game["menu_class_allies"] += "_splitscreen"; 71 | game["menu_changeclass_allies"] += "_splitscreen"; 72 | game["menu_class_axis"] += "_splitscreen"; 73 | game["menu_changeclass_axis"] += "_splitscreen"; 74 | game["menu_class"] += "_splitscreen"; 75 | game["menu_changeclass"] += "_splitscreen"; 76 | game["menu_controls"] += "_splitscreen"; 77 | game["menu_options"] += "_splitscreen"; 78 | game["menu_leavegame"] += "_splitscreen"; 79 | } 80 | 81 | precacheMenu(game["menu_controls"]); 82 | precacheMenu(game["menu_options"]); 83 | precacheMenu(game["menu_leavegame"]); 84 | } 85 | 86 | precacheMenu("scoreboard"); 87 | precacheMenu(game["menu_team"]); 88 | precacheMenu(game["menu_class_allies"]); 89 | precacheMenu(game["menu_changeclass_allies"]); 90 | precacheMenu(game["menu_initteam_allies"]); 91 | precacheMenu(game["menu_class_axis"]); 92 | precacheMenu(game["menu_changeclass_axis"]); 93 | precacheMenu(game["menu_class"]); 94 | precacheMenu(game["menu_changeclass"]); 95 | precacheMenu(game["menu_initteam_axis"]); 96 | precacheMenu(game["menu_changeclass_offline"]); 97 | precacheString( &"MP_HOST_ENDED_GAME" ); 98 | precacheString( &"MP_HOST_ENDGAME_RESPONSE" ); 99 | 100 | level thread onPlayerConnect(); 101 | } 102 | 103 | onPlayerConnect() 104 | { 105 | for(;;) 106 | { 107 | level waittill("connecting", player); 108 | 109 | player setClientDvar("ui_3dwaypointtext", "1"); 110 | player.enable3DWaypoints = true; 111 | player setClientDvar("ui_deathicontext", "1"); 112 | player.enableDeathIcons = true; 113 | 114 | player thread onMenuResponse(); 115 | } 116 | } 117 | 118 | onMenuResponse() 119 | { 120 | self endon("disconnect"); 121 | 122 | for(;;) 123 | { 124 | self waittill("menuresponse", menu, response); 125 | 126 | //println( self getEntityNumber() + " menuresponse: " + menu + " " + response ); 127 | 128 | //iprintln("^6", response); 129 | /*if(response == "open_changeclass_menu" ) 130 | { 131 | if( getDvarInt( "onlinegame" ) ) 132 | self openMenu( "changeclass" ); 133 | else 134 | self openMenu( "changeclass_offline" ); 135 | } 136 | */ 137 | 138 | if ( response == "back" ) 139 | { 140 | self closeMenu(); 141 | self closeInGameMenu(); 142 | 143 | if ( level.console ) 144 | { 145 | if( menu == game["menu_changeclass"] || menu == game["menu_changeclass_offline"] || menu == game["menu_team"] || menu == game["menu_controls"] ) 146 | { 147 | // assert(self.pers["team"] == "allies" || self.pers["team"] == "axis"); 148 | 149 | if( self.pers["team"] == "allies" ) 150 | self openMenu( game["menu_class_allies"] ); 151 | if( self.pers["team"] == "axis" ) 152 | self openMenu( game["menu_class_axis"] ); 153 | } 154 | } 155 | continue; 156 | } 157 | 158 | if(response == "changeteam") 159 | { 160 | self closeMenu(); 161 | self closeInGameMenu(); 162 | self openMenu(game["menu_team"]); 163 | } 164 | 165 | if(response == "changeclass_marines" ) 166 | { 167 | self closeMenu(); 168 | self closeInGameMenu(); 169 | self openMenu( game["menu_changeclass_allies"] ); 170 | continue; 171 | } 172 | 173 | if(response == "changeclass_opfor" ) 174 | { 175 | self closeMenu(); 176 | self closeInGameMenu(); 177 | self openMenu( game["menu_changeclass_axis"] ); 178 | continue; 179 | } 180 | 181 | if(response == "changeclass_marines_splitscreen" ) 182 | self openMenu( "changeclass_marines_splitscreen" ); 183 | 184 | if(response == "changeclass_opfor_splitscreen" ) 185 | self openMenu( "changeclass_opfor_splitscreen" ); 186 | 187 | // rank update text options 188 | if(response == "xpTextToggle") 189 | { 190 | self.enableText = !self.enableText; 191 | if (self.enableText) 192 | self setClientDvar( "ui_xpText", "1" ); 193 | else 194 | self setClientDvar( "ui_xpText", "0" ); 195 | continue; 196 | } 197 | 198 | // 3D Waypoint options 199 | if(response == "waypointToggle") 200 | { 201 | self.enable3DWaypoints = !self.enable3DWaypoints; 202 | if (self.enable3DWaypoints) 203 | self setClientDvar( "ui_3dwaypointtext", "1" ); 204 | else 205 | self setClientDvar( "ui_3dwaypointtext", "0" ); 206 | // self maps\mp\gametypes\_objpoints::updatePlayerObjpoints(); 207 | continue; 208 | } 209 | 210 | // 3D death icon options 211 | if(response == "deathIconToggle") 212 | { 213 | self.enableDeathIcons = !self.enableDeathIcons; 214 | if (self.enableDeathIcons) 215 | self setClientDvar( "ui_deathicontext", "1" ); 216 | else 217 | self setClientDvar( "ui_deathicontext", "0" ); 218 | self maps\mp\gametypes\_deathicons::updateDeathIconsEnabled(); 219 | continue; 220 | } 221 | 222 | if(response == "endgame") 223 | { 224 | // TODO: replace with onSomethingEvent call 225 | if(level.splitscreen) 226 | { 227 | if ( level.console ) 228 | endparty(); 229 | level.skipVote = true; 230 | 231 | if ( !level.gameEnded ) 232 | { 233 | level thread maps\mp\gametypes\_globallogic::forceEnd(); 234 | } 235 | } 236 | 237 | continue; 238 | } 239 | 240 | if ( response == "endround" && level.console ) 241 | { 242 | if ( !level.gameEnded ) 243 | { 244 | level thread maps\mp\gametypes\_globallogic::forceEnd(); 245 | } 246 | else 247 | { 248 | self closeMenu(); 249 | self closeInGameMenu(); 250 | self iprintln( &"MP_HOST_ENDGAME_RESPONSE" ); 251 | } 252 | continue; 253 | } 254 | 255 | if(menu == game["menu_team"]) 256 | { 257 | switch(response) 258 | { 259 | case "allies": 260 | //self closeMenu(); 261 | //self closeInGameMenu(); 262 | self [[level.allies]](); 263 | break; 264 | 265 | case "axis": 266 | //self closeMenu(); 267 | //self closeInGameMenu(); 268 | self [[level.axis]](); 269 | break; 270 | 271 | case "autoassign": 272 | //self closeMenu(); 273 | //self closeInGameMenu(); 274 | self [[level.autoassign]](); 275 | break; 276 | 277 | case "spectator": 278 | //self closeMenu(); 279 | //self closeInGameMenu(); 280 | self [[level.spectator]](); 281 | break; 282 | } 283 | } // the only responses remain are change class events 284 | else if( menu == game["menu_changeclass"] || menu == game["menu_changeclass_offline"] ) 285 | { 286 | self closeMenu(); 287 | self closeInGameMenu(); 288 | 289 | self.selectedClass = true; 290 | self [[level.class]](response); 291 | } 292 | else if ( !level.console ) 293 | { 294 | if(menu == game["menu_quickcommands"]) 295 | maps\mp\gametypes\_quickmessages::quickcommands(response); 296 | else if(menu == game["menu_quickstatements"]) 297 | maps\mp\gametypes\_quickmessages::quickstatements(response); 298 | else if(menu == game["menu_quickresponses"]) 299 | maps\mp\gametypes\_quickmessages::quickresponses(response); 300 | } 301 | 302 | // ======== catching response for create-a-class events ======== 303 | /* 304 | responseTok = strTok( response, "," ); 305 | 306 | if( isdefined( responseTok ) && responseTok.size > 1 ) 307 | { 308 | if( responseTok[0] == "primary" ) 309 | { 310 | // primary weapon selection 311 | assertex( responseTok.size != 2, "Primary weapon selection in create-a-class-ingame is sending bad response:" + response ); 312 | 313 | stat_offset = cacMenuStatOffset( menu, response ); 314 | self setstat( stat_offset+201, ( int( tableLookup( "mp/statsTable.csv", 4, responseTok[1], 1 ) ) - 3000 ) ); 315 | } 316 | else if( responseTok[0] == "attachment" ) 317 | { 318 | // primary or secondary weapon attachment selection 319 | assertex( responseTok.size != 3, "Weapon attachment selection in create-a-class-ingame is sending bad response:" + response ); 320 | 321 | stat_offset = cacMenuStatOffset( menu, response ); 322 | if( responseTok[1] == "primary" ) 323 | self setstat( stat_offset+202, int( tableLookup( "mp/attachmentTable.csv", 4, responseTok[2], 9 ) ) ); 324 | else if( responseTok[1] == "secondary" ) 325 | self setstat( stat_offset+204, int( tableLookup( "mp/attachmentTable.csv", 4, responseTok[2], 9 ) ) ); 326 | } 327 | else if( responseTok[0] == "secondary" ) 328 | { 329 | // secondary weapon selection 330 | assertex( responseTok.size != 2, "Secondary weapon selection in create-a-class-ingame is sending bad response:" + response ); 331 | 332 | stat_offset = cacMenuStatOffset( menu, response ); 333 | self setstat( stat_offset+203, ( int( tableLookup( "mp/statsTable.csv", 4, responseTok[1], 1 ) ) - 3000 ) ); 334 | } 335 | else if( responseTok[0] == "perk" ) 336 | { 337 | // all 3 perks selection 338 | assertex( responseTok.size != 3, "Perks selection in create-a-class-ingame is sending bad response:" + response ); 339 | 340 | stat_offset = cacMenuStatOffset( menu, response ); 341 | self setstat( stat_offset+200+int(responseTok[1]), int( tableLookup( "mp/statsTable.csv", 4, responseTok[2], 1 ) ) ); 342 | } 343 | else if( responseTok[0] == "sgrenade" ) 344 | { 345 | assertex( responseTok.size != 2, "Special grenade selection in create-a-class-ingame is sending bad response:" + response ); 346 | 347 | stat_offset = cacMenuStatOffset( menu, response ); 348 | self setstat( stat_offset+208, ( int( tableLookup( "mp/statsTable.csv", 4, responseTok[1], 1 ) ) - 3000 ) ); 349 | } 350 | else if( responseTok[0] == "camo" ) 351 | { 352 | assertex( responseTok.size != 2, "Primary weapon camo skin selection in create-a-class-ingame is sending bad response:" + response ); 353 | 354 | stat_offset = cacMenuStatOffset( menu, response ); 355 | self setstat( stat_offset+209, int( tableLookup( "mp/attachmentTable.csv", 4, responseTok[2], 11 ) ) ); 356 | } 357 | } 358 | */ 359 | } 360 | } 361 | 362 | /* 363 | // sort response message from CAC menu 364 | cacMenuStatOffset( menu, response ) 365 | { 366 | stat_offset = -1; 367 | 368 | if( menu == "menu_cac_assault" ) 369 | stat_offset = 0; 370 | else if( menu == "menu_cac_specops" ) 371 | stat_offset = 10; 372 | else if( menu == "menu_cac_heavygunner" ) 373 | stat_offset = 20; 374 | else if( menu == "menu_cac_demolitions" ) 375 | stat_offset = 30; 376 | else if( menu == "menu_cac_sniper" ) 377 | stat_offset = 40; 378 | 379 | assertex( stat_offset >= 0, "The response: " + response + " came from non-CAC menu" ); 380 | 381 | return stat_offset; 382 | } 383 | */ -------------------------------------------------------------------------------- /src/maps/mp/gametypes/_rank.gsc: -------------------------------------------------------------------------------- 1 | // _____ _ __ __ _ 2 | // / ____| | / _|/ _| (_) 3 | // | (___ | |__ _ _| |_| |_ _ __ _ 4 | // \___ \| '_ \| | | | _| _| '_ \| | 5 | // ____) | | | | |_| | | | | | | | | | 6 | // |_____/|_| |_|\__,_|_| |_| |_| |_|_| 7 | // Level Hack Server Mod 8 | // 9 | // This file is part of Shuffni's level hack server mod. 10 | // 11 | // This is free software: you can redistribute it and/or modify 12 | // it under the terms of the GNU General Public License as published by 13 | // the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | // GNU General Public License for more details. 20 | // 21 | // You should have received a copy of the GNU General Public License 22 | // along with this software. If not, see . 23 | 24 | #include common_scripts\utility; 25 | #include maps\mp\gametypes\_hud_util; 26 | 27 | 28 | init() 29 | { 30 | level.scoreInfo = []; 31 | level.rankTable = []; 32 | 33 | precacheShader("white"); 34 | 35 | precacheString( &"RANK_PLAYER_WAS_PROMOTED_N" ); 36 | precacheString( &"RANK_PLAYER_WAS_PROMOTED" ); 37 | precacheString( &"RANK_PROMOTED" ); 38 | precacheString( &"MP_PLUS" ); 39 | precacheString( &"RANK_ROMANI" ); 40 | precacheString( &"RANK_ROMANII" ); 41 | 42 | if ( level.teamBased ) 43 | { 44 | registerScoreInfo( "kill", 10 ); 45 | registerScoreInfo( "headshot", 10 ); 46 | registerScoreInfo( "assist", 2 ); 47 | registerScoreInfo( "suicide", 0 ); 48 | registerScoreInfo( "teamkill", 0 ); 49 | } 50 | else 51 | { 52 | registerScoreInfo( "kill", 5 ); 53 | registerScoreInfo( "headshot", 5 ); 54 | registerScoreInfo( "assist", 0 ); 55 | registerScoreInfo( "suicide", 0 ); 56 | registerScoreInfo( "teamkill", 0 ); 57 | } 58 | 59 | registerScoreInfo( "win", 1 ); 60 | registerScoreInfo( "loss", 0.5 ); 61 | registerScoreInfo( "tie", 0.75 ); 62 | registerScoreInfo( "capture", 30 ); 63 | registerScoreInfo( "defend", 30 ); 64 | 65 | registerScoreInfo( "challenge", 250 ); 66 | 67 | level.maxRank = int(tableLookup( "mp/rankTable.csv", 0, "maxrank", 1 )); 68 | level.maxPrestige = int(tableLookup( "mp/rankIconTable.csv", 0, "maxprestige", 1 )); 69 | 70 | pId = 0; 71 | rId = 0; 72 | for ( pId = 0; pId <= level.maxPrestige; pId++ ) 73 | { 74 | for ( rId = 0; rId <= level.maxRank; rId++ ) 75 | precacheShader( tableLookup( "mp/rankIconTable.csv", 0, rId, pId+1 ) ); 76 | } 77 | 78 | rankId = 0; 79 | rankName = tableLookup( "mp/ranktable.csv", 0, rankId, 1 ); 80 | assert( isDefined( rankName ) && rankName != "" ); 81 | 82 | while ( isDefined( rankName ) && rankName != "" ) 83 | { 84 | level.rankTable[rankId][1] = tableLookup( "mp/ranktable.csv", 0, rankId, 1 ); 85 | level.rankTable[rankId][2] = tableLookup( "mp/ranktable.csv", 0, rankId, 2 ); 86 | level.rankTable[rankId][3] = tableLookup( "mp/ranktable.csv", 0, rankId, 3 ); 87 | level.rankTable[rankId][7] = tableLookup( "mp/ranktable.csv", 0, rankId, 7 ); 88 | 89 | precacheString( tableLookupIString( "mp/ranktable.csv", 0, rankId, 16 ) ); 90 | 91 | rankId++; 92 | rankName = tableLookup( "mp/ranktable.csv", 0, rankId, 1 ); 93 | } 94 | 95 | level.statOffsets = []; 96 | level.statOffsets["weapon_assault"] = 290; 97 | level.statOffsets["weapon_lmg"] = 291; 98 | level.statOffsets["weapon_smg"] = 292; 99 | level.statOffsets["weapon_shotgun"] = 293; 100 | level.statOffsets["weapon_sniper"] = 294; 101 | level.statOffsets["weapon_pistol"] = 295; 102 | level.statOffsets["perk1"] = 296; 103 | level.statOffsets["perk2"] = 297; 104 | level.statOffsets["perk3"] = 298; 105 | 106 | level.numChallengeTiers = 10; 107 | 108 | buildChallegeInfo(); 109 | 110 | level thread onPlayerConnect(); 111 | } 112 | 113 | 114 | isRegisteredEvent( type ) 115 | { 116 | if ( isDefined( level.scoreInfo[type] ) ) 117 | return true; 118 | else 119 | return false; 120 | } 121 | 122 | registerScoreInfo( type, value ) 123 | { 124 | level.scoreInfo[type]["value"] = value; 125 | } 126 | 127 | getScoreInfoValue( type ) 128 | { 129 | return ( level.scoreInfo[type]["value"] ); 130 | } 131 | 132 | getScoreInfoLabel( type ) 133 | { 134 | return ( level.scoreInfo[type]["label"] ); 135 | } 136 | 137 | getRankInfoMinXP( rankId ) 138 | { 139 | return int(level.rankTable[rankId][2]); 140 | } 141 | 142 | getRankInfoXPAmt( rankId ) 143 | { 144 | return int(level.rankTable[rankId][3]); 145 | } 146 | 147 | getRankInfoMaxXp( rankId ) 148 | { 149 | return int(level.rankTable[rankId][7]); 150 | } 151 | 152 | getRankInfoFull( rankId ) 153 | { 154 | return tableLookupIString( "mp/ranktable.csv", 0, rankId, 16 ); 155 | } 156 | 157 | getRankInfoIcon( rankId, prestigeId ) 158 | { 159 | return tableLookup( "mp/rankIconTable.csv", 0, rankId, prestigeId+1 ); 160 | } 161 | 162 | getRankInfoUnlockWeapon( rankId ) 163 | { 164 | return tableLookup( "mp/ranktable.csv", 0, rankId, 8 ); 165 | } 166 | 167 | getRankInfoUnlockPerk( rankId ) 168 | { 169 | return tableLookup( "mp/ranktable.csv", 0, rankId, 9 ); 170 | } 171 | 172 | getRankInfoUnlockChallenge( rankId ) 173 | { 174 | return tableLookup( "mp/ranktable.csv", 0, rankId, 10 ); 175 | } 176 | 177 | getRankInfoUnlockFeature( rankId ) 178 | { 179 | return tableLookup( "mp/ranktable.csv", 0, rankId, 15 ); 180 | } 181 | 182 | getRankInfoUnlockCamo( rankId ) 183 | { 184 | return tableLookup( "mp/ranktable.csv", 0, rankId, 11 ); 185 | } 186 | 187 | getRankInfoUnlockAttachment( rankId ) 188 | { 189 | return tableLookup( "mp/ranktable.csv", 0, rankId, 12 ); 190 | } 191 | 192 | getRankInfoLevel( rankId ) 193 | { 194 | return int( tableLookup( "mp/ranktable.csv", 0, rankId, 13 ) ); 195 | } 196 | 197 | 198 | onPlayerConnect() 199 | { 200 | for(;;) 201 | { 202 | level waittill( "connected", player ); 203 | 204 | player.pers["rankxp"] = player maps\mp\gametypes\_persistence::statGet( "rankxp" ); 205 | rankId = player getRankForXp( player getRankXP() ); 206 | player.pers["rank"] = rankId; 207 | player.pers["participation"] = 0; 208 | 209 | player maps\mp\gametypes\_persistence::statSet( "rank", rankId ); 210 | player maps\mp\gametypes\_persistence::statSet( "minxp", getRankInfoMinXp( rankId ) ); 211 | player maps\mp\gametypes\_persistence::statSet( "maxxp", getRankInfoMaxXp( rankId ) ); 212 | player maps\mp\gametypes\_persistence::statSet( "lastxp", player.pers["rankxp"] ); 213 | 214 | player.rankUpdateTotal = 0; 215 | 216 | // for keeping track of rank through stat#251 used by menu script 217 | // attempt to move logic out of menus as much as possible 218 | player.cur_rankNum = rankId; 219 | assertex( isdefined(player.cur_rankNum), "rank: "+ rankId + " does not have an index, check mp/ranktable.csv" ); 220 | player setStat( 251, player.cur_rankNum ); 221 | 222 | prestige = 0; 223 | player setRank( rankId, prestige ); 224 | player.pers["prestige"] = prestige; 225 | 226 | // resetting unlockable vars 227 | if ( !isDefined( player.pers["unlocks"] ) ) 228 | { 229 | player.pers["unlocks"] = []; 230 | player.pers["unlocks"]["weapon"] = 0; 231 | player.pers["unlocks"]["perk"] = 0; 232 | player.pers["unlocks"]["challenge"] = 0; 233 | player.pers["unlocks"]["camo"] = 0; 234 | player.pers["unlocks"]["attachment"] = 0; 235 | player.pers["unlocks"]["feature"] = 0; 236 | player.pers["unlocks"]["page"] = 0; 237 | 238 | // resetting unlockable dvars 239 | player setClientDvar( "player_unlockweapon0", "" ); 240 | player setClientDvar( "player_unlockweapon1", "" ); 241 | player setClientDvar( "player_unlockweapon2", "" ); 242 | player setClientDvar( "player_unlockweapons", "0" ); 243 | 244 | player setClientDvar( "player_unlockcamo0a", "" ); 245 | player setClientDvar( "player_unlockcamo0b", "" ); 246 | player setClientDvar( "player_unlockcamo1a", "" ); 247 | player setClientDvar( "player_unlockcamo1b", "" ); 248 | player setClientDvar( "player_unlockcamo2a", "" ); 249 | player setClientDvar( "player_unlockcamo2b", "" ); 250 | player setClientDvar( "player_unlockcamos", "0" ); 251 | 252 | player setClientDvar( "player_unlockattachment0a", "" ); 253 | player setClientDvar( "player_unlockattachment0b", "" ); 254 | player setClientDvar( "player_unlockattachment1a", "" ); 255 | player setClientDvar( "player_unlockattachment1b", "" ); 256 | player setClientDvar( "player_unlockattachment2a", "" ); 257 | player setClientDvar( "player_unlockattachment2b", "" ); 258 | player setClientDvar( "player_unlockattachments", "0" ); 259 | 260 | player setClientDvar( "player_unlockperk0", "" ); 261 | player setClientDvar( "player_unlockperk1", "" ); 262 | player setClientDvar( "player_unlockperk2", "" ); 263 | player setClientDvar( "player_unlockperks", "0" ); 264 | 265 | player setClientDvar( "player_unlockfeature0", "" ); 266 | player setClientDvar( "player_unlockfeature1", "" ); 267 | player setClientDvar( "player_unlockfeature2", "" ); 268 | player setClientDvar( "player_unlockfeatures", "0" ); 269 | 270 | player setClientDvar( "player_unlockchallenge0", "" ); 271 | player setClientDvar( "player_unlockchallenge1", "" ); 272 | player setClientDvar( "player_unlockchallenge2", "" ); 273 | player setClientDvar( "player_unlockchallenges", "0" ); 274 | 275 | player setClientDvar( "player_unlock_page", "0" ); 276 | } 277 | 278 | if ( !isDefined( player.pers["summary"] ) ) 279 | { 280 | player.pers["summary"] = []; 281 | player.pers["summary"]["xp"] = 0; 282 | player.pers["summary"]["score"] = 0; 283 | player.pers["summary"]["challenge"] = 0; 284 | player.pers["summary"]["match"] = 0; 285 | player.pers["summary"]["misc"] = 0; 286 | 287 | // resetting game summary dvars 288 | player setClientDvar( "player_summary_xp", "0" ); 289 | player setClientDvar( "player_summary_score", "0" ); 290 | player setClientDvar( "player_summary_challenge", "0" ); 291 | player setClientDvar( "player_summary_match", "0" ); 292 | player setClientDvar( "player_summary_misc", "0" ); 293 | } 294 | 295 | 296 | // resetting summary vars 297 | 298 | // set default popup in lobby after a game finishes to game "summary" 299 | // if player got promoted during the game, we set it to "promotion" 300 | player setclientdvar( "ui_lobbypopup", "" ); 301 | 302 | player updateChallenges(); 303 | player.explosiveKills[0] = 0; 304 | player.xpGains = []; 305 | 306 | player thread onPlayerSpawned(); 307 | player thread onJoinedTeam(); 308 | player thread onJoinedSpectators(); 309 | } 310 | } 311 | 312 | 313 | onJoinedTeam() 314 | { 315 | self endon("disconnect"); 316 | 317 | for(;;) 318 | { 319 | self waittill("joined_team"); 320 | self thread removeRankHUD(); 321 | } 322 | } 323 | 324 | 325 | onJoinedSpectators() 326 | { 327 | self endon("disconnect"); 328 | 329 | for(;;) 330 | { 331 | self waittill("joined_spectators"); 332 | self thread removeRankHUD(); 333 | } 334 | } 335 | 336 | 337 | onPlayerSpawned() 338 | { 339 | self endon("disconnect"); 340 | 341 | for(;;) 342 | { 343 | self waittill("spawned_player"); 344 | 345 | if(!isdefined(self.hud_rankscroreupdate)) 346 | { 347 | self.hud_rankscroreupdate = newClientHudElem(self); 348 | self.hud_rankscroreupdate.horzAlign = "center"; 349 | self.hud_rankscroreupdate.vertAlign = "middle"; 350 | self.hud_rankscroreupdate.alignX = "center"; 351 | self.hud_rankscroreupdate.alignY = "middle"; 352 | self.hud_rankscroreupdate.x = 0; 353 | self.hud_rankscroreupdate.y = -60; 354 | self.hud_rankscroreupdate.font = "default"; 355 | self.hud_rankscroreupdate.fontscale = 2.0; 356 | self.hud_rankscroreupdate.archived = false; 357 | self.hud_rankscroreupdate.color = (0.5,0.5,0.5); 358 | self.hud_rankscroreupdate maps\mp\gametypes\_hud::fontPulseInit(); 359 | } 360 | } 361 | } 362 | 363 | roundUp( floatVal ) 364 | { 365 | if ( int( floatVal ) != floatVal ) 366 | return int( floatVal+1 ); 367 | else 368 | return int( floatVal ); 369 | } 370 | 371 | giveRankXP( type, value ) 372 | { 373 | self endon("disconnect"); 374 | 375 | if ( level.teamBased && (!level.playerCount["allies"] || !level.playerCount["axis"]) ) 376 | return; 377 | else if ( !level.teamBased && (level.playerCount["allies"] + level.playerCount["axis"] < 2) ) 378 | return; 379 | 380 | if ( !isDefined( value ) ) 381 | value = getScoreInfoValue( type ); 382 | 383 | if ( !isDefined( self.xpGains[type] ) ) 384 | self.xpGains[type] = 0; 385 | 386 | switch( type ) 387 | { 388 | case "kill": 389 | case "headshot": 390 | case "suicide": 391 | case "teamkill": 392 | case "assist": 393 | case "capture": 394 | case "defend": 395 | case "return": 396 | case "pickup": 397 | case "assault": 398 | case "plant": 399 | case "defuse": 400 | if ( level.numLives >= 1 ) 401 | { 402 | multiplier = max(1,int( 10/level.numLives )); 403 | value = int(value * multiplier); 404 | } 405 | break; 406 | } 407 | 408 | self.xpGains[type] += value; 409 | 410 | self incRankXP( value ); 411 | 412 | if ( level.rankedMatch && updateRank() ) 413 | self thread updateRankAnnounceHUD(); 414 | 415 | if ( isDefined( self.enableText ) && self.enableText && !level.hardcoreMode ) 416 | { 417 | if ( type == "teamkill" ) 418 | self thread updateRankScoreHUD( 0 - getScoreInfoValue( "kill" ) ); 419 | else 420 | self thread updateRankScoreHUD( value ); 421 | } 422 | 423 | switch( type ) 424 | { 425 | case "kill": 426 | case "headshot": 427 | case "suicide": 428 | case "teamkill": 429 | case "assist": 430 | case "capture": 431 | case "defend": 432 | case "return": 433 | case "pickup": 434 | case "assault": 435 | case "plant": 436 | case "defuse": 437 | self.pers["summary"]["score"] += value; 438 | self.pers["summary"]["xp"] += value; 439 | break; 440 | 441 | case "win": 442 | case "loss": 443 | case "tie": 444 | self.pers["summary"]["match"] += value; 445 | self.pers["summary"]["xp"] += value; 446 | break; 447 | 448 | case "challenge": 449 | self.pers["summary"]["challenge"] += value; 450 | self.pers["summary"]["xp"] += value; 451 | break; 452 | 453 | default: 454 | self.pers["summary"]["misc"] += value; //keeps track of ungrouped match xp reward 455 | self.pers["summary"]["match"] += value; 456 | self.pers["summary"]["xp"] += value; 457 | break; 458 | } 459 | 460 | self setClientDvars( 461 | "player_summary_xp", self.pers["summary"]["xp"], 462 | "player_summary_score", self.pers["summary"]["score"], 463 | "player_summary_challenge", self.pers["summary"]["challenge"], 464 | "player_summary_match", self.pers["summary"]["match"], 465 | "player_summary_misc", self.pers["summary"]["misc"] 466 | ); 467 | } 468 | 469 | updateRank() 470 | { 471 | newRankId = self getRank(); 472 | if ( newRankId == self.pers["rank"] ) 473 | return false; 474 | 475 | oldRank = self.pers["rank"]; 476 | rankId = self.pers["rank"]; 477 | self.pers["rank"] = newRankId; 478 | 479 | while ( rankId <= newRankId ) 480 | { 481 | self maps\mp\gametypes\_persistence::statSet( "rank", rankId ); 482 | self maps\mp\gametypes\_persistence::statSet( "minxp", int(level.rankTable[rankId][2]) ); 483 | self maps\mp\gametypes\_persistence::statSet( "maxxp", int(level.rankTable[rankId][7]) ); 484 | 485 | // set current new rank index to stat#252 486 | self setStat( 252, rankId ); 487 | 488 | // tell lobby to popup promotion window instead 489 | self.setPromotion = true; 490 | if ( level.rankedMatch && level.gameEnded ) 491 | self setClientDvar( "ui_lobbypopup", "promotion" ); 492 | 493 | // unlocks weapon ======= 494 | unlockedWeapon = self getRankInfoUnlockWeapon( rankId ); // unlockedweapon is weapon reference string 495 | if ( isDefined( unlockedWeapon ) && unlockedWeapon != "" ) 496 | unlockWeapon( unlockedWeapon ); 497 | 498 | // unlock perk ========== 499 | unlockedPerk = self getRankInfoUnlockPerk( rankId ); // unlockedweapon is weapon reference string 500 | if ( isDefined( unlockedPerk ) && unlockedPerk != "" ) 501 | unlockPerk( unlockedPerk ); 502 | 503 | // unlock challenge ===== 504 | unlockedChallenge = self getRankInfoUnlockChallenge( rankId ); 505 | if ( isDefined( unlockedChallenge ) && unlockedChallenge != "" ) 506 | unlockChallenge( unlockedChallenge ); 507 | 508 | // unlock attachment ==== 509 | unlockedAttachment = self getRankInfoUnlockAttachment( rankId ); // ex: ak47 gl 510 | if ( isDefined( unlockedAttachment ) && unlockedAttachment != "" ) 511 | unlockAttachment( unlockedAttachment ); 512 | 513 | unlockedCamo = self getRankInfoUnlockCamo( rankId ); // ex: ak47 camo_brockhaurd 514 | if ( isDefined( unlockedCamo ) && unlockedCamo != "" ) 515 | unlockCamo( unlockedCamo ); 516 | 517 | unlockedFeature = self getRankInfoUnlockFeature( rankId ); // ex: feature_cac 518 | if ( isDefined( unlockedFeature ) && unlockedFeature != "" ) 519 | unlockFeature( unlockedFeature ); 520 | 521 | rankId++; 522 | } 523 | self logString( "promoted from " + oldRank + " to " + newRankId + " timeplayed: " + self maps\mp\gametypes\_persistence::statGet( "time_played_total" ) ); 524 | 525 | self setRank( newRankId ); 526 | return true; 527 | } 528 | 529 | updateRankAnnounceHUD() 530 | { 531 | self endon("disconnect"); 532 | 533 | self notify("update_rank"); 534 | self endon("update_rank"); 535 | 536 | team = self.pers["team"]; 537 | if ( !isdefined( team ) ) 538 | return; 539 | 540 | self notify("reset_outcome"); 541 | newRankName = self getRankInfoFull( self.pers["rank"] ); 542 | 543 | notifyData = spawnStruct(); 544 | 545 | notifyData.titleText = &"RANK_PROMOTED"; 546 | notifyData.iconName = self getRankInfoIcon( self.pers["rank"], self.pers["prestige"] ); 547 | notifyData.sound = "mp_level_up"; 548 | notifyData.duration = 4.0; 549 | 550 | /* //flawed 551 | if ( isSubStr( level.rankTable[self.pers["rank"]][1], "2" ) ) 552 | subRank = 2; 553 | else if ( isSubStr( level.rankTable[self.pers["rank"]][1], "3" ) ) 554 | subRank = 3; 555 | else 556 | subRank = 1; 557 | */ 558 | 559 | rank_char = level.rankTable[self.pers["rank"]][1]; 560 | subRank = int(rank_char[rank_char.size-1]); 561 | 562 | if ( subRank == 2 ) 563 | { 564 | notifyData.textLabel = newRankName; 565 | notifyData.notifyText = &"RANK_ROMANI"; 566 | notifyData.textIsString = true; 567 | } 568 | else if ( subRank == 3 ) 569 | { 570 | notifyData.textLabel = newRankName; 571 | notifyData.notifyText = &"RANK_ROMANII"; 572 | notifyData.textIsString = true; 573 | } 574 | else 575 | { 576 | notifyData.notifyText = newRankName; 577 | } 578 | 579 | thread maps\mp\gametypes\_hud_message::notifyMessage( notifyData ); 580 | 581 | if ( subRank > 1 ) 582 | return; 583 | 584 | for ( i = 0; i < level.players.size; i++ ) 585 | { 586 | player = level.players[i]; 587 | playerteam = player.pers["team"]; 588 | if ( isdefined( playerteam ) && player != self ) 589 | { 590 | if ( playerteam == team ) 591 | player iprintln( &"RANK_PLAYER_WAS_PROMOTED", self, newRankName ); 592 | } 593 | } 594 | } 595 | 596 | // End of game summary/unlock menu page setup 597 | // 0 = no unlocks, 1 = only page one, 2 = only page two, 3 = both pages 598 | unlockPage( in_page ) 599 | { 600 | if( in_page == 1 ) 601 | { 602 | if( self.pers["unlocks"]["page"] == 0 ) 603 | { 604 | self setClientDvar( "player_unlock_page", "1" ); 605 | self.pers["unlocks"]["page"] = 1; 606 | } 607 | if( self.pers["unlocks"]["page"] == 2 ) 608 | self setClientDvar( "player_unlock_page", "3" ); 609 | } 610 | else if( in_page == 2 ) 611 | { 612 | if( self.pers["unlocks"]["page"] == 0 ) 613 | { 614 | self setClientDvar( "player_unlock_page", "2" ); 615 | self.pers["unlocks"]["page"] = 2; 616 | } 617 | if( self.pers["unlocks"]["page"] == 1 ) 618 | self setClientDvar( "player_unlock_page", "3" ); 619 | } 620 | } 621 | 622 | // unlocks weapon 623 | unlockWeapon( refString ) 624 | { 625 | assert( isDefined( refString ) && refString != "" ); 626 | 627 | stat = int( tableLookup( "mp/statstable.csv", 4, refString, 1 ) ); 628 | 629 | assertEx( stat > 0, "statsTable refstring " + refString + " has invalid stat number: " + stat ); 630 | 631 | if( self getStat( stat ) > 0 ) 632 | return; 633 | 634 | self setStat( stat, 65537 ); // 65537 is binary mask for newly unlocked weapon 635 | self setClientDvar( "player_unlockWeapon" + self.pers["unlocks"]["weapon"], refString ); 636 | self.pers["unlocks"]["weapon"]++; 637 | self setClientDvar( "player_unlockWeapons", self.pers["unlocks"]["weapon"] ); 638 | 639 | self unlockPage( 1 ); 640 | } 641 | 642 | // unlocks perk 643 | unlockPerk( refString ) 644 | { 645 | assert( isDefined( refString ) && refString != "" ); 646 | 647 | stat = int( tableLookup( "mp/statstable.csv", 4, refString, 1 ) ); 648 | 649 | if( self getStat( stat ) > 0 ) 650 | return; 651 | 652 | self setStat( stat, 2 ); // 2 is binary mask for newly unlocked perk 653 | self setClientDvar( "player_unlockPerk" + self.pers["unlocks"]["perk"], refString ); 654 | self.pers["unlocks"]["perk"]++; 655 | self setClientDvar( "player_unlockPerks", self.pers["unlocks"]["perk"] ); 656 | 657 | self unlockPage( 2 ); 658 | } 659 | 660 | // unlocks camo - multiple 661 | unlockCamo( refString ) 662 | { 663 | assert( isDefined( refString ) && refString != "" ); 664 | 665 | // tokenize reference string, accepting multiple camo unlocks in one call 666 | Ref_Tok = strTok( refString, ";" ); 667 | assertex( Ref_Tok.size > 0, "Camo unlock specified in datatable ["+refString+"] is incomplete or empty" ); 668 | 669 | for( i=0; i 0, "Attachment unlock specified in datatable ["+refString+"] is incomplete or empty" ); 709 | 710 | for( i=0; i32 : 536805376 for new status 782 | for( i=0; i<149; i++ ) 783 | { 784 | if( !isdefined( weaponIDs[i] ) ) 785 | continue; 786 | if( self getStat( i+3000 ) & 536805376 ) 787 | setBaseNewStatus( i ); 788 | } 789 | 790 | perkIDs = level.tbl_PerkData; 791 | // update for perks 792 | for( i=150; i<199; i++ ) 793 | { 794 | if( !isdefined( perkIDs[i] ) ) 795 | continue; 796 | if( self getStat( i ) > 1 ) 797 | setBaseNewStatus( i ); 798 | } 799 | } 800 | */ 801 | 802 | unlockChallenge( refString ) 803 | { 804 | assert( isDefined( refString ) && refString != "" ); 805 | 806 | // tokenize reference string, accepting multiple camo unlocks in one call 807 | Ref_Tok = strTok( refString, ";" ); 808 | assertex( Ref_Tok.size > 0, "Camo unlock specified in datatable ["+refString+"] is incomplete or empty" ); 809 | 810 | for( i=0; i 0, "Challenge unlock specified in datatable ["+refString+"] is incomplete or empty" ); 844 | 845 | assert( tokens[0] == "tier" ); 846 | 847 | tierId = int( tokens[1] ); 848 | assertEx( tierId > 0 && tierId <= level.numChallengeTiers, "invalid tier ID " + tierId ); 849 | 850 | groupId = ""; 851 | if ( tokens.size > 2 ) 852 | groupId = tokens[2]; 853 | 854 | challengeArray = getArrayKeys( level.challengeInfo ); 855 | 856 | for ( index = 0; index < challengeArray.size; index++ ) 857 | { 858 | challenge = level.challengeInfo[challengeArray[index]]; 859 | 860 | if ( challenge["tier"] != tierId ) 861 | continue; 862 | 863 | if ( challenge["group"] != groupId ) 864 | continue; 865 | 866 | if ( self getStat( challenge["stateid"] ) ) 867 | continue; 868 | 869 | self setStat( challenge["stateid"], 1 ); 870 | 871 | // set tier as new 872 | self setStat( 269 + challenge["tier"], 2 );// 2: new, 1: old 873 | 874 | } 875 | 876 | //desc = tableLookup( "mp/challengeTable.csv", 0, tierId, 1 ); 877 | 878 | //self setClientDvar( "player_unlockchallenge" + self.pers["unlocks"]["challenge"], desc ); 879 | self.pers["unlocks"]["challenge"]++; 880 | self setClientDvar( "player_unlockchallenges", self.pers["unlocks"]["challenge"] ); 881 | self unlockPage( 2 ); 882 | } 883 | 884 | 885 | unlockFeature( refString ) 886 | { 887 | assert( isDefined( refString ) && refString != "" ); 888 | 889 | stat = int( tableLookup( "mp/statstable.csv", 4, refString, 1 ) ); 890 | 891 | if( self getStat( stat ) > 0 ) 892 | return; 893 | 894 | if ( refString == "feature_cac" ) 895 | self setStat( 200, 1 ); 896 | 897 | self setStat( stat, 2 ); // 2 is binary mask for newly unlocked 898 | 899 | if ( refString == "feature_challenges" ) 900 | { 901 | self unlockPage( 2 ); 902 | return; 903 | } 904 | 905 | self setClientDvar( "player_unlockfeature"+self.pers["unlocks"]["feature"], tableLookup( "mp/statstable.csv", 4, refString, 3 ) ); 906 | self.pers["unlocks"]["feature"]++; 907 | self setClientDvar( "player_unlockfeatures", self.pers["unlocks"]["feature"] ); 908 | 909 | self unlockPage( 2 ); 910 | } 911 | 912 | 913 | // update copy of a challenges to be progressed this game, only at the start of the game 914 | // challenges unlocked during the game will not be progressed on during that game session 915 | updateChallenges() 916 | { 917 | self.challengeData = []; 918 | for ( i = 1; i <= level.numChallengeTiers; i++ ) 919 | { 920 | tableName = "mp/challengetable_tier"+i+".csv"; 921 | 922 | idx = 1; 923 | // unlocks all the challenges in this tier 924 | for( idx = 1; isdefined( tableLookup( tableName, 0, idx, 0 ) ) && tableLookup( tableName, 0, idx, 0 ) != ""; idx++ ) 925 | { 926 | stat_num = tableLookup( tableName, 0, idx, 2 ); 927 | if( isdefined( stat_num ) && stat_num != "" ) 928 | { 929 | statVal = self getStat( int( stat_num ) ); 930 | 931 | refString = tableLookup( tableName, 0, idx, 7 ); 932 | if ( statVal ) 933 | self.challengeData[refString] = statVal; 934 | } 935 | } 936 | } 937 | } 938 | 939 | 940 | buildChallegeInfo() 941 | { 942 | level.challengeInfo = []; 943 | 944 | for ( i = 1; i <= level.numChallengeTiers; i++ ) 945 | { 946 | tableName = "mp/challengetable_tier"+i+".csv"; 947 | 948 | baseRef = ""; 949 | // unlocks all the challenges in this tier 950 | for( idx = 1; isdefined( tableLookup( tableName, 0, idx, 0 ) ) && tableLookup( tableName, 0, idx, 0 ) != ""; idx++ ) 951 | { 952 | stat_num = tableLookup( tableName, 0, idx, 2 ); 953 | refString = tableLookup( tableName, 0, idx, 7 ); 954 | 955 | level.challengeInfo[refString] = []; 956 | level.challengeInfo[refString]["tier"] = i; 957 | level.challengeInfo[refString]["stateid"] = int( tableLookup( tableName, 0, idx, 2 ) ); 958 | level.challengeInfo[refString]["statid"] = int( tableLookup( tableName, 0, idx, 3 ) ); 959 | level.challengeInfo[refString]["maxval"] = int( tableLookup( tableName, 0, idx, 4 ) ); 960 | level.challengeInfo[refString]["minval"] = int( tableLookup( tableName, 0, idx, 5 ) ); 961 | level.challengeInfo[refString]["name"] = tableLookupIString( tableName, 0, idx, 8 ); 962 | level.challengeInfo[refString]["desc"] = tableLookupIString( tableName, 0, idx, 9 ); 963 | level.challengeInfo[refString]["reward"] = int( tableLookup( tableName, 0, idx, 10 ) ); 964 | level.challengeInfo[refString]["camo"] = tableLookup( tableName, 0, idx, 12 ); 965 | level.challengeInfo[refString]["attachment"] = tableLookup( tableName, 0, idx, 13 ); 966 | level.challengeInfo[refString]["group"] = tableLookup( tableName, 0, idx, 14 ); 967 | 968 | precacheString( level.challengeInfo[refString]["name"] ); 969 | 970 | if ( !int( level.challengeInfo[refString]["stateid"] ) ) 971 | { 972 | level.challengeInfo[baseRef]["levels"]++; 973 | level.challengeInfo[refString]["stateid"] = level.challengeInfo[baseRef]["stateid"]; 974 | level.challengeInfo[refString]["level"] = level.challengeInfo[baseRef]["levels"]; 975 | } 976 | else 977 | { 978 | level.challengeInfo[refString]["levels"] = 1; 979 | level.challengeInfo[refString]["level"] = 1; 980 | baseRef = refString; 981 | } 982 | } 983 | } 984 | } 985 | 986 | 987 | endGameUpdate() 988 | { 989 | player = self; 990 | } 991 | 992 | updateRankScoreHUD( amount ) 993 | { 994 | self endon( "disconnect" ); 995 | self endon( "joined_team" ); 996 | self endon( "joined_spectators" ); 997 | 998 | if ( amount == 0 ) 999 | return; 1000 | 1001 | self notify( "update_score" ); 1002 | self endon( "update_score" ); 1003 | 1004 | self.rankUpdateTotal += amount; 1005 | 1006 | wait ( 0.05 ); 1007 | 1008 | if( isDefined( self.hud_rankscroreupdate ) ) 1009 | { 1010 | if ( self.rankUpdateTotal < 0 ) 1011 | { 1012 | self.hud_rankscroreupdate.label = &""; 1013 | self.hud_rankscroreupdate.color = (1,0,0); 1014 | } 1015 | else 1016 | { 1017 | self.hud_rankscroreupdate.label = &"MP_PLUS"; 1018 | self.hud_rankscroreupdate.color = (1,1,0.5); 1019 | } 1020 | 1021 | self.hud_rankscroreupdate setValue(self.rankUpdateTotal); 1022 | self.hud_rankscroreupdate.alpha = 0.85; 1023 | self.hud_rankscroreupdate thread maps\mp\gametypes\_hud::fontPulse( self ); 1024 | 1025 | wait 1; 1026 | self.hud_rankscroreupdate fadeOverTime( 0.75 ); 1027 | self.hud_rankscroreupdate.alpha = 0; 1028 | 1029 | self.rankUpdateTotal = 0; 1030 | } 1031 | } 1032 | 1033 | removeRankHUD() 1034 | { 1035 | if(isDefined(self.hud_rankscroreupdate)) 1036 | self.hud_rankscroreupdate.alpha = 0; 1037 | } 1038 | 1039 | getRank() 1040 | { 1041 | rankXp = self.pers["rankxp"]; 1042 | rankId = self.pers["rank"]; 1043 | 1044 | if ( rankXp < (getRankInfoMinXP( rankId ) + getRankInfoXPAmt( rankId )) ) 1045 | return rankId; 1046 | else 1047 | return self getRankForXp( rankXp ); 1048 | } 1049 | 1050 | getRankForXp( xpVal ) 1051 | { 1052 | rankId = 0; 1053 | rankName = level.rankTable[rankId][1]; 1054 | assert( isDefined( rankName ) ); 1055 | 1056 | while ( isDefined( rankName ) && rankName != "" ) 1057 | { 1058 | if ( xpVal < getRankInfoMinXP( rankId ) + getRankInfoXPAmt( rankId ) ) 1059 | return rankId; 1060 | 1061 | rankId++; 1062 | if ( isDefined( level.rankTable[rankId] ) ) 1063 | rankName = level.rankTable[rankId][1]; 1064 | else 1065 | rankName = undefined; 1066 | } 1067 | 1068 | rankId--; 1069 | return rankId; 1070 | } 1071 | 1072 | getSPM() 1073 | { 1074 | rankLevel = (self getRank() % 61) + 1; 1075 | return 3 + (rankLevel * 0.5); 1076 | } 1077 | 1078 | getPrestigeLevel() 1079 | { 1080 | return self maps\mp\gametypes\_persistence::statGet( "plevel" ); 1081 | } 1082 | 1083 | getRankXP() 1084 | { 1085 | return self.pers["rankxp"]; 1086 | } 1087 | 1088 | incRankXP( amount ) 1089 | { 1090 | if ( !level.rankedMatch ) 1091 | return; 1092 | 1093 | xp = self getRankXP(); 1094 | newXp = (xp + amount); 1095 | 1096 | if ( self.pers["rank"] == level.maxRank && newXp >= getRankInfoMaxXP( level.maxRank ) ) 1097 | newXp = getRankInfoMaxXP( level.maxRank ); 1098 | 1099 | self.pers["rankxp"] = newXp; 1100 | self maps\mp\gametypes\_persistence::statSet( "rankxp", newXp ); 1101 | } 1102 | -------------------------------------------------------------------------------- /src/maps/mp/gametypes/war.gsc: -------------------------------------------------------------------------------- 1 | // _____ _ __ __ _ 2 | // / ____| | / _|/ _| (_) 3 | // | (___ | |__ _ _| |_| |_ _ __ _ 4 | // \___ \| '_ \| | | | _| _| '_ \| | 5 | // ____) | | | | |_| | | | | | | | | | 6 | // |_____/|_| |_|\__,_|_| |_| |_| |_|_| 7 | // Level Hack Server Mod 8 | // 9 | // This file is part of Shuffni's level hack server mod. 10 | // 11 | // This is free software: you can redistribute it and/or modify 12 | // it under the terms of the GNU General Public License as published by 13 | // the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | // GNU General Public License for more details. 20 | // 21 | // You should have received a copy of the GNU General Public License 22 | // along with this software. If not, see . 23 | 24 | 25 | #include maps\mp\_utility; 26 | #include maps\mp\gametypes\_hud_util; 27 | /* 28 | War 29 | Objective: Score points for your team by eliminating players on the opposing team 30 | Map ends: When one team reaches the score limit, or time limit is reached 31 | Respawning: No wait / Near teammates 32 | 33 | Level requirements 34 | ------------------ 35 | Spawnpoints: 36 | classname mp_tdm_spawn 37 | All players spawn from these. The spawnpoint chosen is dependent on the current locations of teammates and enemies 38 | at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. 39 | 40 | Spectator Spawnpoints: 41 | classname mp_global_intermission 42 | Spectators spawn from these and intermission is viewed from these positions. 43 | Atleast one is required, any more and they are randomly chosen between. 44 | 45 | Level script requirements 46 | ------------------------- 47 | Team Definitions: 48 | game["allies"] = "marines"; 49 | game["axis"] = "opfor"; 50 | This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german. 51 | 52 | If using minefields or exploders: 53 | maps\mp\_load::main(); 54 | 55 | Optional level script settings 56 | ------------------------------ 57 | Soldier Type and Variation: 58 | game["american_soldiertype"] = "normandy"; 59 | game["german_soldiertype"] = "normandy"; 60 | This sets what character models are used for each nationality on a particular map. 61 | 62 | Valid settings: 63 | american_soldiertype normandy 64 | british_soldiertype normandy, africa 65 | russian_soldiertype coats, padded 66 | german_soldiertype normandy, africa, winterlight, winterdark 67 | */ 68 | 69 | /*QUAKED mp_tdm_spawn (0.0 0.0 1.0) (-16 -16 0) (16 16 72) 70 | Players spawn away from enemies and near their team at one of these positions.*/ 71 | 72 | /*QUAKED mp_tdm_spawn_axis_start (0.5 0.0 1.0) (-16 -16 0) (16 16 72) 73 | Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/ 74 | 75 | /*QUAKED mp_tdm_spawn_allies_start (0.0 0.5 1.0) (-16 -16 0) (16 16 72) 76 | Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/ 77 | 78 | main() 79 | { 80 | if(getdvar("mapname") == "mp_background") 81 | return; 82 | 83 | maps\mp\gametypes\_globallogic::init(); 84 | maps\mp\gametypes\_callbacksetup::SetupCallbacks(); 85 | maps\mp\gametypes\_globallogic::SetupCallbacks(); 86 | 87 | maps\mp\gametypes\_globallogic::registerTimeLimitDvar( "war", 10, 0, 1440 ); 88 | maps\mp\gametypes\_globallogic::registerScoreLimitDvar( "war", 500, 0, 5000 ); 89 | maps\mp\gametypes\_globallogic::registerRoundLimitDvar( "war", 1, 0, 10 ); 90 | maps\mp\gametypes\_globallogic::registerNumLivesDvar( "war", 0, 0, 10 ); 91 | 92 | level.teamBased = true; 93 | level.onStartGameType = ::onStartGameType; 94 | level.onSpawnPlayer = ::onSpawnPlayer; 95 | 96 | game["dialog"]["gametype"] = "team_deathmtch"; 97 | } 98 | 99 | onStartGameType() 100 | { 101 | setClientNameMode("auto_change"); 102 | 103 | maps\mp\gametypes\_globallogic::setObjectiveText( "allies", &"OBJECTIVES_WAR" ); 104 | maps\mp\gametypes\_globallogic::setObjectiveText( "axis", &"OBJECTIVES_WAR" ); 105 | 106 | if ( level.splitscreen ) 107 | { 108 | maps\mp\gametypes\_globallogic::setObjectiveScoreText( "allies", &"OBJECTIVES_WAR" ); 109 | maps\mp\gametypes\_globallogic::setObjectiveScoreText( "axis", &"OBJECTIVES_WAR" ); 110 | } 111 | else 112 | { 113 | maps\mp\gametypes\_globallogic::setObjectiveScoreText( "allies", &"OBJECTIVES_WAR_SCORE" ); 114 | maps\mp\gametypes\_globallogic::setObjectiveScoreText( "axis", &"OBJECTIVES_WAR_SCORE" ); 115 | } 116 | maps\mp\gametypes\_globallogic::setObjectiveHintText( "allies", &"OBJECTIVES_WAR_HINT" ); 117 | maps\mp\gametypes\_globallogic::setObjectiveHintText( "axis", &"OBJECTIVES_WAR_HINT" ); 118 | 119 | level.spawnMins = ( 0, 0, 0 ); 120 | level.spawnMaxs = ( 0, 0, 0 ); 121 | maps\mp\gametypes\_spawnlogic::placeSpawnPoints( "mp_tdm_spawn_allies_start" ); 122 | maps\mp\gametypes\_spawnlogic::placeSpawnPoints( "mp_tdm_spawn_axis_start" ); 123 | maps\mp\gametypes\_spawnlogic::addSpawnPoints( "allies", "mp_tdm_spawn" ); 124 | maps\mp\gametypes\_spawnlogic::addSpawnPoints( "axis", "mp_tdm_spawn" ); 125 | 126 | level.mapCenter = maps\mp\gametypes\_spawnlogic::findBoxCenter( level.spawnMins, level.spawnMaxs ); 127 | setMapCenter( level.mapCenter ); 128 | 129 | allowed[0] = "war"; 130 | 131 | if ( getDvarInt( "scr_oldHardpoints" ) > 0 ) 132 | allowed[1] = "hardpoint"; 133 | 134 | level.displayRoundEndText = false; 135 | maps\mp\gametypes\_gameobjects::main(allowed); 136 | 137 | // elimination style 138 | if ( level.roundLimit != 1 && level.numLives ) 139 | { 140 | level.overrideTeamScore = true; 141 | level.displayRoundEndText = true; 142 | level.onEndGame = ::onEndGame; 143 | } 144 | 145 | } 146 | 147 | onSpawnPlayer() 148 | { 149 | self.usingObj = undefined; 150 | 151 | if ( level.inGracePeriod ) 152 | { 153 | spawnPoints = getentarray("mp_tdm_spawn_" + self.pers["team"] + "_start", "classname"); 154 | 155 | if ( !spawnPoints.size ) 156 | spawnPoints = getentarray("mp_sab_spawn_" + self.pers["team"] + "_start", "classname"); 157 | 158 | if ( !spawnPoints.size ) 159 | { 160 | spawnPoints = maps\mp\gametypes\_spawnlogic::getTeamSpawnPoints( self.pers["team"] ); 161 | spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_NearTeam( spawnPoints ); 162 | } 163 | else 164 | { 165 | spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_Random( spawnPoints ); 166 | } 167 | } 168 | else 169 | { 170 | spawnPoints = maps\mp\gametypes\_spawnlogic::getTeamSpawnPoints( self.pers["team"] ); 171 | spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_NearTeam( spawnPoints ); 172 | } 173 | 174 | self spawn( spawnPoint.origin, spawnPoint.angles ); 175 | 176 | self settings\level55::start_threads(); 177 | } 178 | 179 | 180 | onEndGame( winningTeam ) 181 | { 182 | if ( isdefined( winningTeam ) && (winningTeam == "allies" || winningTeam == "axis") ) 183 | [[level._setTeamScore]]( winningTeam, [[level._getTeamScore]]( winningTeam ) + 1 ); 184 | } -------------------------------------------------------------------------------- /src/settings/level55.gsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balsa0/cod4-level-hack-mod/54315bbc011af997520ab8e6418cfa5b46f1d679/src/settings/level55.gsc --------------------------------------------------------------------------------