Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | /* |
2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
3 | * described in COPYING.txt. |
||
4 | * Portions of this file are copyright Parallax Software and licensed |
||
5 | * according to the Parallax license below. |
||
6 | * See COPYING.txt for license details. |
||
7 | |||
8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
18 | */ |
||
19 | |||
20 | /* |
||
21 | * |
||
22 | * Functions to load & save player's settings (*.plr file) |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | #include <stdexcept> |
||
27 | #include <stdio.h> |
||
28 | #include <string.h> |
||
29 | #if !defined(_MSC_VER) && !defined(macintosh) |
||
30 | #include <unistd.h> |
||
31 | #endif |
||
32 | #include <errno.h> |
||
33 | #include <ctype.h> |
||
34 | #include <inttypes.h> |
||
35 | |||
36 | #include "dxxerror.h" |
||
37 | #include "strutil.h" |
||
38 | #include "game.h" |
||
39 | #include "gameseq.h" |
||
40 | #include "player.h" |
||
41 | #include "playsave.h" |
||
42 | #include "joy.h" |
||
43 | #include "digi.h" |
||
44 | #include "newmenu.h" |
||
45 | #include "palette.h" |
||
46 | #include "menu.h" |
||
47 | #include "config.h" |
||
48 | #include "text.h" |
||
49 | #include "state.h" |
||
50 | #include "gauges.h" |
||
51 | #include "screens.h" |
||
52 | #include "powerup.h" |
||
53 | #include "makesig.h" |
||
54 | #include "u_mem.h" |
||
55 | #include "args.h" |
||
56 | #include "vers_id.h" |
||
57 | #include "newdemo.h" |
||
58 | #include "gauges.h" |
||
59 | #include "nvparse.h" |
||
60 | |||
61 | #include "compiler-range_for.h" |
||
62 | #include "d_range.h" |
||
63 | #include "partial_range.h" |
||
64 | |||
65 | #define PLAYER_EFFECTIVENESS_FILENAME_FORMAT PLAYER_DIRECTORY_STRING("%s.eff") |
||
66 | |||
67 | #define GameNameStr "game_name" |
||
68 | #define GameModeStr "gamemode" |
||
69 | #define RefusePlayersStr "RefusePlayers" |
||
70 | #define DifficultyStr "difficulty" |
||
71 | #define GameFlagsStr "game_flags" |
||
72 | #define AllowedItemsStr "AllowedItems" |
||
73 | #define SpawnGrantedItemsStr "SpawnGrantedItems" |
||
74 | #define DuplicatePrimariesStr "DuplicatePrimaries" |
||
75 | #define DuplicateSecondariesStr "DuplicateSecondaries" |
||
76 | #define DuplicateAccessoriesStr "DuplicateAccessories" |
||
77 | #define ShufflePowerupsStr "ShufflePowerups" |
||
78 | #define AlwaysLightingStr "AlwaysLighting" |
||
79 | #define ShowEnemyNamesStr "ShowEnemyNames" |
||
80 | #define BrightPlayersStr "BrightPlayers" |
||
81 | #define InvulAppearStr "InvulAppear" |
||
82 | #define KillGoalStr "KillGoal" |
||
83 | #define PlayTimeAllowedStr "PlayTimeAllowed" |
||
84 | #define ControlInvulTimeStr "control_invul_time" |
||
85 | #define PacketsPerSecStr "PacketsPerSec" |
||
86 | #define NoFriendlyFireStr "NoFriendlyFire" |
||
87 | #define MouselookFlagsStr "Mouselook" |
||
88 | #define AutosaveIntervalStr "AutosaveInterval" |
||
89 | #define TrackerStr "Tracker" |
||
90 | #define TrackerNATHPStr "trackernat" |
||
91 | #define NGPVersionStr "ngp version" |
||
92 | |||
93 | #if defined(DXX_BUILD_DESCENT_I) |
||
94 | #define PLX_OPTION_HEADER_TEXT "[D1X Options]" |
||
95 | #define WEAPON_REORDER_HEADER_TEXT "[weapon reorder]" |
||
96 | #define WEAPON_REORDER_PRIMARY_NAME_TEXT "primary" |
||
97 | #define WEAPON_REORDER_PRIMARY_VALUE_TEXT "0x%x,0x%x,0x%x,0x%x,0x%x,0x%x" |
||
98 | #define WEAPON_REORDER_SECONDARY_NAME_TEXT "secondary" |
||
99 | #define WEAPON_REORDER_SECONDARY_VALUE_TEXT "0x%x,0x%x,0x%x,0x%x,0x%x,0x%x" |
||
100 | //version 5 -> 6: added new highest level information |
||
101 | //version 6 -> 7: stripped out the old saved_game array. |
||
102 | //version 7 -> 8: readded the old saved_game array since this is needed |
||
103 | // for shareware saved games |
||
104 | //the shareware is level 4 |
||
105 | |||
106 | #define SAVED_GAME_VERSION 8 //increment this every time saved_game struct changes |
||
107 | #define COMPATIBLE_SAVED_GAME_VERSION 4 |
||
108 | #define COMPATIBLE_PLAYER_STRUCT_VERSION 16 |
||
109 | #elif defined(DXX_BUILD_DESCENT_II) |
||
110 | #define PLX_OPTION_HEADER_TEXT "[D2X OPTIONS]" |
||
111 | //version 5 -> 6: added new highest level information |
||
112 | //version 6 -> 7: stripped out the old saved_game array. |
||
113 | //version 7 -> 8: added reticle flag, & window size |
||
114 | //version 8 -> 9: removed player_struct_version |
||
115 | //version 9 -> 10: added default display mode |
||
116 | //version 10 -> 11: added all toggles in toggle menu |
||
117 | //version 11 -> 12: added weapon ordering |
||
118 | //version 12 -> 13: added more keys |
||
119 | //version 13 -> 14: took out marker key |
||
120 | //version 14 -> 15: added guided in big window |
||
121 | //version 15 -> 16: added small windows in cockpit |
||
122 | //version 16 -> 17: ?? |
||
123 | //version 17 -> 18: save guidebot name |
||
124 | //version 18 -> 19: added automap-highres flag |
||
125 | //version 19 -> 20: added kconfig data for windows joysticks |
||
126 | //version 20 -> 21: save seperate config types for DOS & Windows |
||
127 | //version 21 -> 22: save lifetime netstats |
||
128 | //version 22 -> 23: ?? |
||
129 | //version 23 -> 24: add name of joystick for windows version. |
||
130 | |||
131 | #define PLAYER_FILE_VERSION 24 //increment this every time the player file changes |
||
132 | #define COMPATIBLE_PLAYER_FILE_VERSION 17 |
||
133 | #define AllowMarkerViewStr "Allow_marker_view" |
||
134 | #define ThiefAbsenceFlagStr "ThiefAbsent" |
||
135 | #define ThiefNoEnergyWeaponsFlagStr "ThiefNoEnergyWeapons" |
||
136 | #define AllowGuidebotStr "AllowGuidebot" |
||
137 | #endif |
||
138 | #define KEYBOARD_HEADER_TEXT "[keyboard]" |
||
139 | #define SENSITIVITY_NAME_TEXT "sensitivity" |
||
140 | #define SENSITIVITY_VALUE_TEXT "%d" |
||
141 | #define LINEAR_NAME_TEXT "linearity" |
||
142 | #define LINEAR_VALUE_TEXT "%d" |
||
143 | #define SPEED_NAME_TEXT "speed" |
||
144 | #define SPEED_VALUE_TEXT "%d" |
||
145 | #define DEADZONE_NAME_TEXT "deadzone" |
||
146 | #define DEADZONE_VALUE_TEXT "%d" |
||
147 | #define JOYSTICK_HEADER_TEXT "[joystick]" |
||
148 | #define MOUSE_HEADER_TEXT "[mouse]" |
||
149 | #define MOUSE_FLIGHTSIM_NAME_TEXT "flightsim" |
||
150 | #define MOUSE_FLIGHTSIM_VALUE_TEXT "%d" |
||
151 | #define MOUSE_FSDEAD_NAME_TEXT "fsdead" |
||
152 | #define MOUSE_FSDEAD_VALUE_TEXT "%d" |
||
153 | #define MOUSE_FSINDICATOR_NAME_TEXT "fsindi" |
||
154 | #define MOUSE_FSINDICATOR_VALUE_TEXT "%d" |
||
155 | #define MOUSE_OVERRUN_NAME_TEXT "overrun" |
||
156 | #define WEAPON_KEYv2_HEADER_TEXT "[weapon keys v2]" |
||
157 | #define WEAPON_KEYv2_VALUE_TEXT "0x%x,0x%x,0x%x" |
||
158 | #define COCKPIT_HEADER_TEXT "[cockpit]" |
||
159 | #define COCKPIT_MODE_NAME_TEXT "mode" |
||
160 | #define COCKPIT_HUD_NAME_TEXT "hud" |
||
161 | #define COCKPIT_RETICLE_TYPE_NAME_TEXT "rettype" |
||
162 | #define COCKPIT_RETICLE_COLOR_NAME_TEXT "retrgba" |
||
163 | #define COCKPIT_RETICLE_SIZE_NAME_TEXT "retsize" |
||
164 | #define TOGGLES_HEADER_TEXT "[toggles]" |
||
165 | #define TOGGLES_BOMBGAUGE_NAME_TEXT "bombgauge" |
||
166 | #define TOGGLES_ESCORTHOTKEYS_NAME_TEXT "escorthotkeys" |
||
167 | #define TOGGLES_PERSISTENTDEBRIS_NAME_TEXT "persistentdebris" |
||
168 | #define TOGGLES_PRSHOT_NAME_TEXT "prshot" |
||
169 | #define TOGGLES_NOREDUNDANCY_NAME_TEXT "noredundancy" |
||
170 | #define TOGGLES_MULTIMESSAGES_NAME_TEXT "multimessages" |
||
171 | #define TOGGLES_MULTIPINGHUD_NAME_TEXT "multipinghud" |
||
172 | #define TOGGLES_NORANKINGS_NAME_TEXT "norankings" |
||
173 | #define TOGGLES_AUTOMAPFREEFLIGHT_NAME_TEXT "automapfreeflight" |
||
174 | #define TOGGLES_NOFIREAUTOSELECT_NAME_TEXT "nofireautoselect" |
||
175 | #define TOGGLES_CYCLEAUTOSELECTONLY_NAME_TEXT "cycleautoselectonly" |
||
176 | #define TOGGLES_FRIENDMISSILEVIEW_NAME_TEXT "friendmissileview" |
||
177 | #define TOGGLES_CLOAKINVULTIMER_NAME_TEXT "cloakinvultimer" |
||
178 | #define TOGGLES_RESPAWN_ANY_KEY "respawnkey" |
||
179 | #define TOGGLES_MOUSELOOK "mouselook" |
||
180 | #define TOGGLES_THIEF_ABSENCE_SP "thiefabsent" |
||
181 | #define TOGGLES_THIEF_NO_ENERGY_WEAPONS_SP "thiefnoenergyweapons" |
||
182 | #define TOGGLES_AUTOSAVE_INTERVAL_SP "autosaveinterval" |
||
183 | #define GRAPHICS_HEADER_TEXT "[graphics]" |
||
184 | #define GRAPHICS_ALPHAEFFECTS_NAME_TEXT "alphaeffects" |
||
185 | #define GRAPHICS_DYNLIGHTCOLOR_NAME_TEXT "dynlightcolor" |
||
186 | #define PLX_VERSION_HEADER_TEXT "[plx version]" |
||
187 | #define END_TEXT "[end]" |
||
188 | |||
189 | #define SAVE_FILE_ID MAKE_SIG('D','P','L','R') |
||
190 | |||
191 | struct player_config PlayerCfg; |
||
192 | namespace dsx { |
||
193 | #if defined(DXX_BUILD_DESCENT_I) |
||
194 | static void plyr_read_stats(); |
||
195 | static std::array<saved_game_sw, N_SAVE_SLOTS> saved_games; |
||
196 | #elif defined(DXX_BUILD_DESCENT_II) |
||
197 | static inline void plyr_read_stats() {} |
||
198 | static int get_lifetime_checksum (int a,int b); |
||
199 | #endif |
||
200 | } |
||
201 | |||
202 | template <std::size_t N> |
||
203 | static void check_weapon_reorder(std::array<ubyte, N> &w) |
||
204 | { |
||
205 | uint_fast32_t m = 0; |
||
206 | range_for (const auto i, w) |
||
207 | if (i == 255) |
||
208 | m |= 1 << N; |
||
209 | else if (i < N - 1) |
||
210 | m |= 1 << i; |
||
211 | else |
||
212 | break; |
||
213 | if (m != ((1 << N) | ((1 << (N - 1)) - 1))) |
||
214 | { |
||
215 | w[0] = 255; |
||
216 | range_for (const uint_fast32_t i, xrange(1u, N)) |
||
217 | w[i] = i - 1; |
||
218 | } |
||
219 | } |
||
220 | |||
221 | namespace dsx { |
||
222 | int new_player_config() |
||
223 | { |
||
224 | #if defined(DXX_BUILD_DESCENT_I) |
||
225 | range_for (auto &i, saved_games) |
||
226 | i.name[0] = 0; |
||
227 | #endif |
||
228 | InitWeaponOrdering (); //setup default weapon priorities |
||
229 | PlayerCfg.ControlType=0; // Assume keyboard |
||
230 | PlayerCfg.RespawnMode = RespawnPress::Any; |
||
231 | PlayerCfg.MouselookFlags = 0; |
||
232 | PlayerCfg.KeySettings = DefaultKeySettings; |
||
233 | PlayerCfg.KeySettingsRebirth = DefaultKeySettingsRebirth; |
||
234 | kc_set_controls(); |
||
235 | |||
236 | PlayerCfg.DefaultDifficulty = DEFAULT_DIFFICULTY; |
||
237 | PlayerCfg.AutoLeveling = 1; |
||
238 | PlayerCfg.NHighestLevels = 1; |
||
239 | PlayerCfg.HighestLevels[0].Shortname[0] = 0; //no name for mission 0 |
||
240 | PlayerCfg.HighestLevels[0].LevelNum = 1; //was highest level in old struct |
||
241 | PlayerCfg.KeyboardSens[0] = PlayerCfg.KeyboardSens[1] = PlayerCfg.KeyboardSens[2] = PlayerCfg.KeyboardSens[3] = PlayerCfg.KeyboardSens[4] = 16; |
||
242 | PlayerCfg.JoystickSens[0] = PlayerCfg.JoystickSens[1] = PlayerCfg.JoystickSens[2] = PlayerCfg.JoystickSens[3] = PlayerCfg.JoystickSens[4] = PlayerCfg.JoystickSens[5] = 8; |
||
243 | PlayerCfg.JoystickDead[0] = PlayerCfg.JoystickDead[1] = PlayerCfg.JoystickDead[2] = PlayerCfg.JoystickDead[3] = PlayerCfg.JoystickDead[4] = PlayerCfg.JoystickDead[5] = 0; |
||
244 | PlayerCfg.JoystickLinear[0] = PlayerCfg.JoystickLinear[1] = PlayerCfg.JoystickLinear[2] = PlayerCfg.JoystickLinear[3] = PlayerCfg.JoystickLinear[4] = PlayerCfg.JoystickLinear[5] = 0; |
||
245 | PlayerCfg.JoystickSpeed[0] = PlayerCfg.JoystickSpeed[1] = PlayerCfg.JoystickSpeed[2] = PlayerCfg.JoystickSpeed[3] = PlayerCfg.JoystickSpeed[4] = PlayerCfg.JoystickSpeed[5] = 16; |
||
246 | PlayerCfg.MouseFlightSim = 0; |
||
247 | PlayerCfg.MouseSens[0] = PlayerCfg.MouseSens[1] = PlayerCfg.MouseSens[2] = PlayerCfg.MouseSens[3] = PlayerCfg.MouseSens[4] = PlayerCfg.MouseSens[5] = 8; |
||
248 | PlayerCfg.MouseOverrun[0] = PlayerCfg.MouseOverrun[1] = PlayerCfg.MouseOverrun[2] = PlayerCfg.MouseOverrun[3] = PlayerCfg.MouseOverrun[4] = PlayerCfg.MouseOverrun[5] = 0; |
||
249 | PlayerCfg.MouseFSDead = 0; |
||
250 | PlayerCfg.MouseFSIndicator = 1; |
||
251 | PlayerCfg.CockpitMode[0] = PlayerCfg.CockpitMode[1] = CM_FULL_COCKPIT; |
||
252 | PlayerCfg.ReticleType = RET_TYPE_CLASSIC; |
||
253 | PlayerCfg.ReticleRGBA[0] = RET_COLOR_DEFAULT_R; PlayerCfg.ReticleRGBA[1] = RET_COLOR_DEFAULT_G; PlayerCfg.ReticleRGBA[2] = RET_COLOR_DEFAULT_B; PlayerCfg.ReticleRGBA[3] = RET_COLOR_DEFAULT_A; |
||
254 | PlayerCfg.ReticleSize = 0; |
||
255 | PlayerCfg.HudMode = HudType::Standard; |
||
256 | #if defined(DXX_BUILD_DESCENT_I) |
||
257 | PlayerCfg.BombGauge = 1; |
||
258 | #elif defined(DXX_BUILD_DESCENT_II) |
||
259 | PlayerCfg.Cockpit3DView[0]=CV_NONE; |
||
260 | PlayerCfg.Cockpit3DView[1]=CV_NONE; |
||
261 | PlayerCfg.ThiefModifierFlags = 0; |
||
262 | PlayerCfg.MissileViewEnabled = MissileViewMode::EnabledSelfOnly; |
||
263 | PlayerCfg.HeadlightActiveDefault = 1; |
||
264 | PlayerCfg.GuidedInBigWindow = 0; |
||
265 | PlayerCfg.GuidebotName = "GUIDE-BOT"; |
||
266 | PlayerCfg.GuidebotNameReal = PlayerCfg.GuidebotName; |
||
267 | PlayerCfg.EscortHotKeys = 1; |
||
268 | #endif |
||
269 | PlayerCfg.PersistentDebris = 0; |
||
270 | PlayerCfg.PRShot = 0; |
||
271 | PlayerCfg.NoRedundancy = 0; |
||
272 | PlayerCfg.MultiMessages = 0; |
||
273 | PlayerCfg.MultiPingHud = 0; |
||
274 | PlayerCfg.NoRankings = 0; |
||
275 | PlayerCfg.AutomapFreeFlight = 0; |
||
276 | PlayerCfg.NoFireAutoselect = FiringAutoselectMode::Immediate; |
||
277 | PlayerCfg.CycleAutoselectOnly = 0; |
||
278 | PlayerCfg.CloakInvulTimer = 0; |
||
279 | PlayerCfg.AlphaEffects = 0; |
||
280 | PlayerCfg.DynLightColor = 0; |
||
281 | |||
282 | // Default taunt macros |
||
283 | #if defined(DXX_BUILD_DESCENT_I) |
||
284 | PlayerCfg.NetworkMessageMacro[0].copy_if(TXT_DEF_MACRO_1); |
||
285 | PlayerCfg.NetworkMessageMacro[1].copy_if(TXT_DEF_MACRO_2); |
||
286 | PlayerCfg.NetworkMessageMacro[2].copy_if(TXT_DEF_MACRO_3); |
||
287 | PlayerCfg.NetworkMessageMacro[3].copy_if(TXT_DEF_MACRO_4); |
||
288 | #elif defined(DXX_BUILD_DESCENT_II) |
||
289 | PlayerCfg.NetworkMessageMacro[0] = "Why can't we all just get along?"; |
||
290 | PlayerCfg.NetworkMessageMacro[1] = "Hey, I got a present for ya"; |
||
291 | PlayerCfg.NetworkMessageMacro[2] = "I got a hankerin' for a spankerin'"; |
||
292 | PlayerCfg.NetworkMessageMacro[3] = "This one's headed for Uranus"; |
||
293 | #endif |
||
294 | PlayerCfg.NetlifeKills=0; PlayerCfg.NetlifeKilled=0; |
||
295 | |||
296 | return 1; |
||
297 | } |
||
298 | } |
||
299 | |||
300 | static int convert_pattern_array(const char *name, std::size_t namelen, int *array, std::size_t arraylen, const char *word, const char *line) |
||
301 | { |
||
302 | if (memcmp(word, name, namelen - 1)) |
||
303 | return 0; |
||
304 | char *p; |
||
305 | unsigned long which = strtoul(word + namelen - 1, &p, 10); |
||
306 | if (*p || which >= arraylen) |
||
307 | return 0; |
||
308 | array[which] = strtol(line, NULL, 10); |
||
309 | return 1; |
||
310 | } |
||
311 | |||
312 | template <std::size_t namelen, std::size_t arraylen> |
||
313 | static int convert_pattern_array(const char (&name)[namelen], std::array<int, arraylen> &array, const char *word, const char *line) |
||
314 | { |
||
315 | return convert_pattern_array(name, namelen, &array[0], arraylen, word, line); |
||
316 | } |
||
317 | |||
318 | static void print_pattern_array(PHYSFS_File *fout, const char *name, const int *array, std::size_t arraylen) |
||
319 | { |
||
320 | for (std::size_t i = 0; i < arraylen; ++i) |
||
321 | PHYSFSX_printf(fout,"%s%" DXX_PRI_size_type "=%d\n", name, i, array[i]); |
||
322 | } |
||
323 | |||
324 | template <std::size_t arraylen> |
||
325 | static void print_pattern_array(PHYSFS_File *fout, const char *name, const std::array<int, arraylen> &array) |
||
326 | { |
||
327 | print_pattern_array(fout, name, &array[0], arraylen); |
||
328 | } |
||
329 | |||
330 | static const char *splitword(char *line, char c) |
||
331 | { |
||
332 | char *p = strchr(line, c); |
||
333 | if (p) |
||
334 | { |
||
335 | *p = 0; |
||
336 | return p + 1; |
||
337 | } |
||
338 | return p; |
||
339 | } |
||
340 | |||
341 | namespace dsx { |
||
342 | static void read_player_dxx(const char *filename) |
||
343 | { |
||
344 | plyr_read_stats(); |
||
345 | |||
346 | auto f = PHYSFSX_openReadBuffered(filename); |
||
347 | if (!f) |
||
348 | return; |
||
349 | |||
350 | PlayerCfg.SPGameplayOptions.AutosaveInterval = std::chrono::minutes(10); |
||
351 | for (PHYSFSX_gets_line_t<50> line; PHYSFSX_fgets(line,f);) |
||
352 | { |
||
353 | #if defined(DXX_BUILD_DESCENT_I) |
||
354 | if (!strcmp(line, WEAPON_REORDER_HEADER_TEXT)) |
||
355 | { |
||
356 | while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT)) |
||
357 | { |
||
358 | const char *value=splitword(line,'='); |
||
359 | if (!value) |
||
360 | continue; |
||
361 | #define CONVERT_WEAPON_REORDER_VALUE(A,F) \ |
||
362 | unsigned int wo0=0,wo1=0,wo2=0,wo3=0,wo4=0,wo5=0; \ |
||
363 | if (sscanf(value,F,&wo0, &wo1, &wo2, &wo3, &wo4, &wo5) == 6) \ |
||
364 | A[0]=wo0, A[1]=wo1, A[2]=wo2, A[3]=wo3, A[4]=wo4, A[5]=wo5, check_weapon_reorder(A); |
||
365 | if(!strcmp(line,WEAPON_REORDER_PRIMARY_NAME_TEXT)) |
||
366 | { |
||
367 | CONVERT_WEAPON_REORDER_VALUE(PlayerCfg.PrimaryOrder, WEAPON_REORDER_PRIMARY_VALUE_TEXT); |
||
368 | } |
||
369 | else if(!strcmp(line,WEAPON_REORDER_SECONDARY_NAME_TEXT)) |
||
370 | { |
||
371 | CONVERT_WEAPON_REORDER_VALUE(PlayerCfg.SecondaryOrder, WEAPON_REORDER_SECONDARY_VALUE_TEXT); |
||
372 | } |
||
373 | } |
||
374 | } |
||
375 | else |
||
376 | #endif |
||
377 | if (!strcmp(line,KEYBOARD_HEADER_TEXT)) |
||
378 | { |
||
379 | while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT)) |
||
380 | { |
||
381 | const char *value=splitword(line,'='); |
||
382 | if (!value) |
||
383 | continue; |
||
384 | convert_pattern_array(SENSITIVITY_NAME_TEXT, PlayerCfg.KeyboardSens, line, value); |
||
385 | } |
||
386 | } |
||
387 | else if (!strcmp(line,JOYSTICK_HEADER_TEXT)) |
||
388 | { |
||
389 | while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT)) |
||
390 | { |
||
391 | const char *value=splitword(line,'='); |
||
392 | if (!value) |
||
393 | continue; |
||
394 | convert_pattern_array(SENSITIVITY_NAME_TEXT, PlayerCfg.JoystickSens, line, value) || |
||
395 | convert_pattern_array(LINEAR_NAME_TEXT, PlayerCfg.JoystickLinear, line, value) || |
||
396 | convert_pattern_array(SPEED_NAME_TEXT, PlayerCfg.JoystickSpeed, line, value) || |
||
397 | convert_pattern_array(DEADZONE_NAME_TEXT, PlayerCfg.JoystickDead, line, value); |
||
398 | } |
||
399 | } |
||
400 | else if (!strcmp(line,MOUSE_HEADER_TEXT)) |
||
401 | { |
||
402 | while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT)) |
||
403 | { |
||
404 | const char *value=splitword(line,'='); |
||
405 | if (!value) |
||
406 | continue; |
||
407 | if(!strcmp(line,MOUSE_FLIGHTSIM_NAME_TEXT)) |
||
408 | PlayerCfg.MouseFlightSim = atoi(value); |
||
409 | else if (convert_pattern_array(SENSITIVITY_NAME_TEXT, PlayerCfg.MouseSens, line, value)) |
||
410 | ; |
||
411 | else if (convert_pattern_array(MOUSE_OVERRUN_NAME_TEXT, PlayerCfg.MouseOverrun, line, value)) |
||
412 | ; |
||
413 | else if(!strcmp(line,MOUSE_FSDEAD_NAME_TEXT)) |
||
414 | PlayerCfg.MouseFSDead = atoi(value); |
||
415 | else if(!strcmp(line,MOUSE_FSINDICATOR_NAME_TEXT)) |
||
416 | PlayerCfg.MouseFSIndicator = atoi(value); |
||
417 | } |
||
418 | } |
||
419 | else if (!strcmp(line,WEAPON_KEYv2_HEADER_TEXT)) |
||
420 | { |
||
421 | while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT)) |
||
422 | { |
||
423 | const char *value=splitword(line,'='); |
||
424 | if (!value) |
||
425 | continue; |
||
426 | unsigned int kc1=0,kc2=0,kc3=0; |
||
427 | int i=atoi(line); |
||
428 | |||
429 | if(i==0) i=10; |
||
430 | i=(i-1)*3; |
||
431 | |||
432 | if (sscanf(value,WEAPON_KEYv2_VALUE_TEXT,&kc1,&kc2,&kc3) != 3) |
||
433 | continue; |
||
434 | PlayerCfg.KeySettingsRebirth[i] = kc1; |
||
435 | PlayerCfg.KeySettingsRebirth[i+1] = kc2; |
||
436 | PlayerCfg.KeySettingsRebirth[i+2] = kc3; |
||
437 | } |
||
438 | } |
||
439 | else if (!strcmp(line,COCKPIT_HEADER_TEXT)) |
||
440 | { |
||
441 | while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT)) |
||
442 | { |
||
443 | const char *value=splitword(line,'='); |
||
444 | if (!value) |
||
445 | continue; |
||
446 | #if defined(DXX_BUILD_DESCENT_I) |
||
447 | if(!strcmp(line,COCKPIT_MODE_NAME_TEXT)) |
||
448 | PlayerCfg.CockpitMode[0] = PlayerCfg.CockpitMode[1] = static_cast<cockpit_mode_t>(atoi(value)); |
||
449 | else |
||
450 | #endif |
||
451 | if(!strcmp(line,COCKPIT_HUD_NAME_TEXT)) |
||
452 | PlayerCfg.HudMode = static_cast<HudType>(atoi(value)); |
||
453 | else if(!strcmp(line,COCKPIT_RETICLE_TYPE_NAME_TEXT)) |
||
454 | PlayerCfg.ReticleType = atoi(value); |
||
455 | else if(!strcmp(line,COCKPIT_RETICLE_COLOR_NAME_TEXT)) |
||
456 | sscanf(value,"%i,%i,%i,%i",&PlayerCfg.ReticleRGBA[0],&PlayerCfg.ReticleRGBA[1],&PlayerCfg.ReticleRGBA[2],&PlayerCfg.ReticleRGBA[3]); |
||
457 | else if(!strcmp(line,COCKPIT_RETICLE_SIZE_NAME_TEXT)) |
||
458 | PlayerCfg.ReticleSize = atoi(value); |
||
459 | } |
||
460 | } |
||
461 | else if (!strcmp(line,TOGGLES_HEADER_TEXT)) |
||
462 | { |
||
463 | PlayerCfg.MouselookFlags = 0; |
||
464 | #if defined(DXX_BUILD_DESCENT_II) |
||
465 | PlayerCfg.ThiefModifierFlags = 0; |
||
466 | #endif |
||
467 | while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT)) |
||
468 | { |
||
469 | const char *value=splitword(line,'='); |
||
470 | if (!value) |
||
471 | continue; |
||
472 | #if defined(DXX_BUILD_DESCENT_I) |
||
473 | if(!strcmp(line,TOGGLES_BOMBGAUGE_NAME_TEXT)) |
||
474 | PlayerCfg.BombGauge = atoi(value); |
||
475 | #elif defined(DXX_BUILD_DESCENT_II) |
||
476 | if(!strcmp(line,TOGGLES_ESCORTHOTKEYS_NAME_TEXT)) |
||
477 | PlayerCfg.EscortHotKeys = atoi(value); |
||
478 | else if (!strcmp(line, TOGGLES_THIEF_ABSENCE_SP)) |
||
479 | { |
||
480 | if (strtoul(value, 0, 10)) |
||
481 | PlayerCfg.ThiefModifierFlags |= ThiefModifier::Absent; |
||
482 | } |
||
483 | else if (!strcmp(line, TOGGLES_THIEF_NO_ENERGY_WEAPONS_SP)) |
||
484 | { |
||
485 | if (strtoul(value, 0, 10)) |
||
486 | PlayerCfg.ThiefModifierFlags |= ThiefModifier::NoEnergyWeapons; |
||
487 | } |
||
488 | #endif |
||
489 | else if (!strcmp(line, TOGGLES_AUTOSAVE_INTERVAL_SP)) |
||
490 | { |
||
491 | const auto l = strtoul(value, 0, 10); |
||
492 | PlayerCfg.SPGameplayOptions.AutosaveInterval = std::chrono::seconds(l); |
||
493 | } |
||
494 | if(!strcmp(line,TOGGLES_PERSISTENTDEBRIS_NAME_TEXT)) |
||
495 | PlayerCfg.PersistentDebris = atoi(value); |
||
496 | if(!strcmp(line,TOGGLES_PRSHOT_NAME_TEXT)) |
||
497 | PlayerCfg.PRShot = atoi(value); |
||
498 | if(!strcmp(line,TOGGLES_NOREDUNDANCY_NAME_TEXT)) |
||
499 | PlayerCfg.NoRedundancy = atoi(value); |
||
500 | if(!strcmp(line,TOGGLES_MULTIMESSAGES_NAME_TEXT)) |
||
501 | PlayerCfg.MultiMessages = atoi(value); |
||
502 | if(!strcmp(line,TOGGLES_MULTIPINGHUD_NAME_TEXT)) |
||
503 | PlayerCfg.MultiPingHud = atoi(value); |
||
504 | if(!strcmp(line,TOGGLES_NORANKINGS_NAME_TEXT)) |
||
505 | PlayerCfg.NoRankings = atoi(value); |
||
506 | if(!strcmp(line,TOGGLES_AUTOMAPFREEFLIGHT_NAME_TEXT)) |
||
507 | PlayerCfg.AutomapFreeFlight = atoi(value); |
||
508 | if(!strcmp(line,TOGGLES_NOFIREAUTOSELECT_NAME_TEXT)) |
||
509 | PlayerCfg.NoFireAutoselect = static_cast<FiringAutoselectMode>(atoi(value)); |
||
510 | if(!strcmp(line,TOGGLES_CYCLEAUTOSELECTONLY_NAME_TEXT)) |
||
511 | PlayerCfg.CycleAutoselectOnly = atoi(value); |
||
512 | if(!strcmp(line,TOGGLES_CLOAKINVULTIMER_NAME_TEXT)) |
||
513 | PlayerCfg.CloakInvulTimer = atoi(value); |
||
514 | else if (!strcmp(line, TOGGLES_RESPAWN_ANY_KEY)) |
||
515 | PlayerCfg.RespawnMode = static_cast<RespawnPress>(atoi(value)); |
||
516 | else if (!strcmp(line, TOGGLES_MOUSELOOK)) |
||
517 | PlayerCfg.MouselookFlags = strtoul(value, 0, 10); |
||
518 | } |
||
519 | } |
||
520 | else if (!strcmp(line,GRAPHICS_HEADER_TEXT)) |
||
521 | { |
||
522 | while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT)) |
||
523 | { |
||
524 | const char *value=splitword(line,'='); |
||
525 | if (!value) |
||
526 | continue; |
||
527 | if(!strcmp(line,GRAPHICS_ALPHAEFFECTS_NAME_TEXT)) |
||
528 | PlayerCfg.AlphaEffects = atoi(value); |
||
529 | if(!strcmp(line,GRAPHICS_DYNLIGHTCOLOR_NAME_TEXT)) |
||
530 | PlayerCfg.DynLightColor = atoi(value); |
||
531 | } |
||
532 | } |
||
533 | else if (!strcmp(line,PLX_VERSION_HEADER_TEXT)) // know the version this pilot was used last with - allow modifications |
||
534 | { |
||
535 | #if 0 |
||
536 | int v1=0,v2=0,v3=0; |
||
537 | #endif |
||
538 | while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT)) |
||
539 | { |
||
540 | #if 0 |
||
541 | const char *value=splitword(line,'='); |
||
542 | if (!value) |
||
543 | continue; |
||
544 | sscanf(value,"%i.%i.%i",&v1,&v2,&v3); |
||
545 | #endif |
||
546 | } |
||
547 | } |
||
548 | else if (!strcmp(line,END_TEXT)) |
||
549 | { |
||
550 | break; |
||
551 | } |
||
552 | else if (!strcmp(line,PLX_OPTION_HEADER_TEXT)) |
||
553 | { |
||
554 | // No action needed |
||
555 | } |
||
556 | else |
||
557 | { |
||
558 | while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT)) |
||
559 | { |
||
560 | } |
||
561 | } |
||
562 | } |
||
563 | } |
||
564 | |||
565 | #if defined(DXX_BUILD_DESCENT_I) |
||
566 | constexpr char effcode1[]="d1xrocks_SKCORX!D"; |
||
567 | constexpr char effcode2[]="AObe)7Rn1 -+/zZ'0"; |
||
568 | constexpr char effcode3[]="aoeuidhtnAOEUIDH6"; |
||
569 | constexpr char effcode4[]="'/.;]<{=,+?|}->[3"; |
||
570 | |||
571 | static const uint8_t *decode_stat(const uint8_t *p,int *v,const char *effcode) |
||
572 | { |
||
573 | unsigned char c; |
||
574 | int neg,i; |
||
575 | |||
576 | if (p[0]==0) |
||
577 | return NULL; |
||
578 | else if (p[0]>='a'){ |
||
579 | neg=1;/*I=p[0]-'a';*/ |
||
580 | }else{ |
||
581 | neg=0;/*I=p[0]-'A';*/ |
||
582 | } |
||
583 | i=0;*v=0; |
||
584 | p++; |
||
585 | while (p[i*2] && p[i*2+1] && p[i*2]!=' '){ |
||
586 | c=(p[i*2]-33)+((p[i*2+1]-33)<<4); |
||
587 | c^=effcode[i+neg]; |
||
588 | *v+=c << (i*8); |
||
589 | i++; |
||
590 | } |
||
591 | if (neg) |
||
592 | *v *= -1; |
||
593 | if (!p[i*2]) |
||
594 | return NULL; |
||
595 | return p+(i*2); |
||
596 | } |
||
597 | |||
598 | static void plyr_read_stats_v(int *k, int *d) |
||
599 | { |
||
600 | char filename[PATH_MAX]; |
||
601 | int k1=-1,k2=0,d1=-1,d2=0; |
||
602 | *k=0;*d=0;//in case the file doesn't exist. |
||
603 | memset(filename, '\0', PATH_MAX); |
||
604 | snprintf(filename,sizeof(filename),PLAYER_EFFECTIVENESS_FILENAME_FORMAT,static_cast<const char *>(get_local_player().callsign)); |
||
605 | if (auto f = PHYSFSX_openReadBuffered(filename)) |
||
606 | { |
||
607 | PHYSFSX_gets_line_t<256> line; |
||
608 | if(!PHYSFS_eof(f)) |
||
609 | { |
||
610 | PHYSFSX_fgets(line,f); |
||
611 | const char *value=splitword(line,':'); |
||
612 | if(!strcmp(line,"kills") && value) |
||
613 | *k=atoi(value); |
||
614 | } |
||
615 | if(!PHYSFS_eof(f)) |
||
616 | { |
||
617 | PHYSFSX_fgets(line,f); |
||
618 | const char *value=splitword(line,':'); |
||
619 | if(!strcmp(line,"deaths") && value) |
||
620 | *d=atoi(value); |
||
621 | } |
||
622 | if(!PHYSFS_eof(f)) |
||
623 | { |
||
624 | PHYSFSX_fgets(line,f); |
||
625 | const char *value=splitword(line,':'); |
||
626 | if(value && !strcmp(line,"key") && strlen(value)>10){ |
||
627 | if (value[0]=='0' && value[1]=='1'){ |
||
628 | const uint8_t *p; |
||
629 | if ((p=decode_stat(reinterpret_cast<const unsigned char *>(value + 3), &k1, effcode1))&& |
||
630 | (p=decode_stat(p+1,&k2,effcode2))&& |
||
631 | (p=decode_stat(p+1,&d1,effcode3))){ |
||
632 | decode_stat(p+1,&d2,effcode4); |
||
633 | } |
||
634 | } |
||
635 | } |
||
636 | } |
||
637 | if (k1!=k2 || k1!=*k || d1!=d2 || d1!=*d) |
||
638 | { |
||
639 | *k=0;*d=0; |
||
640 | } |
||
641 | } |
||
642 | } |
||
643 | |||
644 | static void plyr_read_stats() |
||
645 | { |
||
646 | plyr_read_stats_v(&PlayerCfg.NetlifeKills,&PlayerCfg.NetlifeKilled); |
||
647 | } |
||
648 | |||
649 | void plyr_save_stats() |
||
650 | { |
||
651 | int kills = PlayerCfg.NetlifeKills,deaths = PlayerCfg.NetlifeKilled, neg, i; |
||
652 | char filename[PATH_MAX]; |
||
653 | std::array<uint8_t, 16> buf, buf2; |
||
654 | uint8_t a; |
||
655 | memset(filename, '\0', PATH_MAX); |
||
656 | snprintf(filename,sizeof(filename),PLAYER_EFFECTIVENESS_FILENAME_FORMAT,static_cast<const char *>(get_local_player().callsign)); |
||
657 | auto f = PHYSFSX_openWriteBuffered(filename); |
||
658 | if(!f) |
||
659 | return; //broken! |
||
660 | |||
661 | PHYSFSX_printf(f,"kills:%i\n",kills); |
||
662 | PHYSFSX_printf(f,"deaths:%i\n",deaths); |
||
663 | PHYSFSX_printf(f,"key:01 "); |
||
664 | |||
665 | if (kills < 0) |
||
666 | { |
||
667 | neg=1; |
||
668 | kills*=-1; |
||
669 | } |
||
670 | else |
||
671 | neg=0; |
||
672 | |||
673 | for (i=0;kills;i++) |
||
674 | { |
||
675 | a=(kills & 0xFF) ^ effcode1[i+neg]; |
||
676 | buf[i*2]=(a&0xF)+33; |
||
677 | buf[i*2+1]=(a>>4)+33; |
||
678 | a=(kills & 0xFF) ^ effcode2[i+neg]; |
||
679 | buf2[i*2]=(a&0xF)+33; |
||
680 | buf2[i*2+1]=(a>>4)+33; |
||
681 | kills>>=8; |
||
682 | } |
||
683 | |||
684 | buf[i*2]=0; |
||
685 | buf2[i*2]=0; |
||
686 | |||
687 | if (neg) |
||
688 | i+='a'; |
||
689 | else |
||
690 | i+='A'; |
||
691 | |||
692 | PHYSFSX_printf(f,"%c%s %c%s ",i,buf.data(),i,buf2.data()); |
||
693 | |||
694 | if (deaths < 0) |
||
695 | { |
||
696 | neg=1; |
||
697 | deaths*=-1; |
||
698 | }else |
||
699 | neg=0; |
||
700 | |||
701 | for (i=0;deaths;i++) |
||
702 | { |
||
703 | a=(deaths & 0xFF) ^ effcode3[i+neg]; |
||
704 | buf[i*2]=(a&0xF)+33; |
||
705 | buf[i*2+1]=(a>>4)+33; |
||
706 | a=(deaths & 0xFF) ^ effcode4[i+neg]; |
||
707 | buf2[i*2]=(a&0xF)+33; |
||
708 | buf2[i*2+1]=(a>>4)+33; |
||
709 | deaths>>=8; |
||
710 | } |
||
711 | |||
712 | buf[i*2]=0; |
||
713 | buf2[i*2]=0; |
||
714 | |||
715 | if (neg) |
||
716 | i+='a'; |
||
717 | else |
||
718 | i+='A'; |
||
719 | |||
720 | PHYSFSX_printf(f, "%c%s %c%s\n", i, buf.data(), i, buf2.data()); |
||
721 | } |
||
722 | #endif |
||
723 | |||
724 | static int write_player_dxx(const char *filename) |
||
725 | { |
||
726 | int rc=0; |
||
727 | char tempfile[PATH_MAX]; |
||
728 | |||
729 | strcpy(tempfile,filename); |
||
730 | tempfile[strlen(tempfile)-4]=0; |
||
731 | strcat(tempfile,".pl$"); |
||
732 | auto fout = PHYSFSX_openWriteBuffered(tempfile); |
||
733 | if (!fout && CGameArg.SysUsePlayersDir) |
||
734 | { |
||
735 | PHYSFS_mkdir(PLAYER_DIRECTORY_STRING("")); //try making directory |
||
736 | fout=PHYSFSX_openWriteBuffered(tempfile); |
||
737 | } |
||
738 | |||
739 | if(fout) |
||
740 | { |
||
741 | PHYSFSX_printf(fout,PLX_OPTION_HEADER_TEXT "\n"); |
||
742 | #if defined(DXX_BUILD_DESCENT_I) |
||
743 | PHYSFSX_printf(fout,WEAPON_REORDER_HEADER_TEXT "\n"); |
||
744 | PHYSFSX_printf(fout,WEAPON_REORDER_PRIMARY_NAME_TEXT "=" WEAPON_REORDER_PRIMARY_VALUE_TEXT "\n",PlayerCfg.PrimaryOrder[0], PlayerCfg.PrimaryOrder[1], PlayerCfg.PrimaryOrder[2],PlayerCfg.PrimaryOrder[3], PlayerCfg.PrimaryOrder[4], PlayerCfg.PrimaryOrder[5]); |
||
745 | PHYSFSX_printf(fout,WEAPON_REORDER_SECONDARY_NAME_TEXT "=" WEAPON_REORDER_SECONDARY_VALUE_TEXT "\n",PlayerCfg.SecondaryOrder[0], PlayerCfg.SecondaryOrder[1], PlayerCfg.SecondaryOrder[2],PlayerCfg.SecondaryOrder[3], PlayerCfg.SecondaryOrder[4], PlayerCfg.SecondaryOrder[5]); |
||
746 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
747 | #endif |
||
748 | PHYSFSX_printf(fout,KEYBOARD_HEADER_TEXT "\n"); |
||
749 | print_pattern_array(fout, SENSITIVITY_NAME_TEXT, PlayerCfg.KeyboardSens); |
||
750 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
751 | PHYSFSX_printf(fout,JOYSTICK_HEADER_TEXT "\n"); |
||
752 | print_pattern_array(fout, SENSITIVITY_NAME_TEXT, PlayerCfg.JoystickSens); |
||
753 | print_pattern_array(fout, LINEAR_NAME_TEXT, PlayerCfg.JoystickLinear); |
||
754 | print_pattern_array(fout, SPEED_NAME_TEXT, PlayerCfg.JoystickSpeed); |
||
755 | print_pattern_array(fout, DEADZONE_NAME_TEXT, PlayerCfg.JoystickDead); |
||
756 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
757 | PHYSFSX_printf(fout,MOUSE_HEADER_TEXT "\n"); |
||
758 | PHYSFSX_printf(fout,MOUSE_FLIGHTSIM_NAME_TEXT "=" MOUSE_FLIGHTSIM_VALUE_TEXT "\n",PlayerCfg.MouseFlightSim); |
||
759 | print_pattern_array(fout, SENSITIVITY_NAME_TEXT, PlayerCfg.MouseSens); |
||
760 | print_pattern_array(fout, MOUSE_OVERRUN_NAME_TEXT, PlayerCfg.MouseOverrun); |
||
761 | PHYSFSX_printf(fout,MOUSE_FSDEAD_NAME_TEXT "=" MOUSE_FSDEAD_VALUE_TEXT "\n",PlayerCfg.MouseFSDead); |
||
762 | PHYSFSX_printf(fout,MOUSE_FSINDICATOR_NAME_TEXT "=" MOUSE_FSINDICATOR_VALUE_TEXT "\n",PlayerCfg.MouseFSIndicator); |
||
763 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
764 | PHYSFSX_printf(fout,WEAPON_KEYv2_HEADER_TEXT "\n"); |
||
765 | PHYSFSX_printf(fout,"1=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[0],PlayerCfg.KeySettingsRebirth[1],PlayerCfg.KeySettingsRebirth[2]); |
||
766 | PHYSFSX_printf(fout,"2=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[3],PlayerCfg.KeySettingsRebirth[4],PlayerCfg.KeySettingsRebirth[5]); |
||
767 | PHYSFSX_printf(fout,"3=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[6],PlayerCfg.KeySettingsRebirth[7],PlayerCfg.KeySettingsRebirth[8]); |
||
768 | PHYSFSX_printf(fout,"4=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[9],PlayerCfg.KeySettingsRebirth[10],PlayerCfg.KeySettingsRebirth[11]); |
||
769 | PHYSFSX_printf(fout,"5=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[12],PlayerCfg.KeySettingsRebirth[13],PlayerCfg.KeySettingsRebirth[14]); |
||
770 | PHYSFSX_printf(fout,"6=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[15],PlayerCfg.KeySettingsRebirth[16],PlayerCfg.KeySettingsRebirth[17]); |
||
771 | PHYSFSX_printf(fout,"7=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[18],PlayerCfg.KeySettingsRebirth[19],PlayerCfg.KeySettingsRebirth[20]); |
||
772 | PHYSFSX_printf(fout,"8=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[21],PlayerCfg.KeySettingsRebirth[22],PlayerCfg.KeySettingsRebirth[23]); |
||
773 | PHYSFSX_printf(fout,"9=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[24],PlayerCfg.KeySettingsRebirth[25],PlayerCfg.KeySettingsRebirth[26]); |
||
774 | PHYSFSX_printf(fout,"0=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[27],PlayerCfg.KeySettingsRebirth[28],PlayerCfg.KeySettingsRebirth[29]); |
||
775 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
776 | PHYSFSX_printf(fout,COCKPIT_HEADER_TEXT "\n"); |
||
777 | #if defined(DXX_BUILD_DESCENT_I) |
||
778 | PHYSFSX_printf(fout,COCKPIT_MODE_NAME_TEXT "=%i\n",PlayerCfg.CockpitMode[0]); |
||
779 | #endif |
||
780 | PHYSFSX_printf(fout,COCKPIT_HUD_NAME_TEXT "=%u\n", static_cast<unsigned>(PlayerCfg.HudMode)); |
||
781 | PHYSFSX_printf(fout,COCKPIT_RETICLE_TYPE_NAME_TEXT "=%i\n",PlayerCfg.ReticleType); |
||
782 | PHYSFSX_printf(fout,COCKPIT_RETICLE_COLOR_NAME_TEXT "=%i,%i,%i,%i\n",PlayerCfg.ReticleRGBA[0],PlayerCfg.ReticleRGBA[1],PlayerCfg.ReticleRGBA[2],PlayerCfg.ReticleRGBA[3]); |
||
783 | PHYSFSX_printf(fout,COCKPIT_RETICLE_SIZE_NAME_TEXT "=%i\n",PlayerCfg.ReticleSize); |
||
784 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
785 | PHYSFSX_printf(fout,TOGGLES_HEADER_TEXT "\n"); |
||
786 | #if defined(DXX_BUILD_DESCENT_I) |
||
787 | PHYSFSX_printf(fout,TOGGLES_BOMBGAUGE_NAME_TEXT "=%i\n",PlayerCfg.BombGauge); |
||
788 | #elif defined(DXX_BUILD_DESCENT_II) |
||
789 | PHYSFSX_printf(fout,TOGGLES_ESCORTHOTKEYS_NAME_TEXT "=%i\n",PlayerCfg.EscortHotKeys); |
||
790 | PHYSFSX_printf(fout, TOGGLES_THIEF_ABSENCE_SP "=%i\n", PlayerCfg.ThiefModifierFlags & ThiefModifier::Absent); |
||
791 | PHYSFSX_printf(fout, TOGGLES_THIEF_NO_ENERGY_WEAPONS_SP "=%i\n", PlayerCfg.ThiefModifierFlags & ThiefModifier::NoEnergyWeapons); |
||
792 | #endif |
||
793 | PHYSFSX_printf(fout, TOGGLES_AUTOSAVE_INTERVAL_SP "=%i\n", PlayerCfg.SPGameplayOptions.AutosaveInterval.count()); |
||
794 | PHYSFSX_printf(fout,TOGGLES_PERSISTENTDEBRIS_NAME_TEXT "=%i\n",PlayerCfg.PersistentDebris); |
||
795 | PHYSFSX_printf(fout,TOGGLES_PRSHOT_NAME_TEXT "=%i\n",PlayerCfg.PRShot); |
||
796 | PHYSFSX_printf(fout,TOGGLES_NOREDUNDANCY_NAME_TEXT "=%i\n",PlayerCfg.NoRedundancy); |
||
797 | PHYSFSX_printf(fout,TOGGLES_MULTIMESSAGES_NAME_TEXT "=%i\n",PlayerCfg.MultiMessages); |
||
798 | PHYSFSX_printf(fout,TOGGLES_MULTIPINGHUD_NAME_TEXT "=%i\n",PlayerCfg.MultiPingHud); |
||
799 | PHYSFSX_printf(fout,TOGGLES_NORANKINGS_NAME_TEXT "=%i\n",PlayerCfg.NoRankings); |
||
800 | PHYSFSX_printf(fout,TOGGLES_AUTOMAPFREEFLIGHT_NAME_TEXT "=%i\n",PlayerCfg.AutomapFreeFlight); |
||
801 | PHYSFSX_printf(fout,TOGGLES_NOFIREAUTOSELECT_NAME_TEXT "=%i\n",static_cast<unsigned>(PlayerCfg.NoFireAutoselect)); |
||
802 | PHYSFSX_printf(fout,TOGGLES_CYCLEAUTOSELECTONLY_NAME_TEXT "=%i\n",PlayerCfg.CycleAutoselectOnly); |
||
803 | PHYSFSX_printf(fout,TOGGLES_CLOAKINVULTIMER_NAME_TEXT "=%i\n",PlayerCfg.CloakInvulTimer); |
||
804 | PHYSFSX_printf(fout,TOGGLES_RESPAWN_ANY_KEY "=%i\n",static_cast<unsigned>(PlayerCfg.RespawnMode)); |
||
805 | PHYSFSX_printf(fout, TOGGLES_MOUSELOOK "=%i\n", PlayerCfg.MouselookFlags); |
||
806 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
807 | PHYSFSX_printf(fout,GRAPHICS_HEADER_TEXT "\n"); |
||
808 | PHYSFSX_printf(fout,GRAPHICS_ALPHAEFFECTS_NAME_TEXT "=%i\n",PlayerCfg.AlphaEffects); |
||
809 | PHYSFSX_printf(fout,GRAPHICS_DYNLIGHTCOLOR_NAME_TEXT "=%i\n",PlayerCfg.DynLightColor); |
||
810 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
811 | PHYSFSX_printf(fout,PLX_VERSION_HEADER_TEXT "\n"); |
||
812 | PHYSFSX_printf(fout,"plx version=" DXX_VERSION_STR "\n"); |
||
813 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
814 | PHYSFSX_printf(fout,END_TEXT "\n"); |
||
815 | fout.reset(); |
||
816 | if(rc==0) |
||
817 | { |
||
818 | PHYSFS_delete(filename); |
||
819 | rc = PHYSFSX_rename(tempfile,filename); |
||
820 | } |
||
821 | return rc; |
||
822 | } |
||
823 | else |
||
824 | return errno; |
||
825 | } |
||
826 | |||
827 | //read in the player's saved games. returns errno (0 == no error) |
||
828 | int read_player_file() |
||
829 | { |
||
830 | char filename[PATH_MAX]; |
||
831 | #if defined(DXX_BUILD_DESCENT_I) |
||
832 | int shareware_file = -1; |
||
833 | int player_file_size; |
||
834 | #elif defined(DXX_BUILD_DESCENT_II) |
||
835 | int rewrite_it=0; |
||
836 | int swap = 0; |
||
837 | short player_file_version; |
||
838 | #endif |
||
839 | |||
840 | Assert(Player_num < MAX_PLAYERS); |
||
841 | |||
842 | memset(filename, '\0', PATH_MAX); |
||
843 | snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.plr"), static_cast<const char *>(InterfaceUniqueState.PilotName)); |
||
844 | if (!PHYSFSX_exists(filename,0)) |
||
845 | return ENOENT; |
||
846 | auto file = PHYSFSX_openReadBuffered(filename); |
||
847 | if (!file) |
||
848 | goto read_player_file_failed; |
||
849 | |||
850 | new_player_config(); // Set defaults! |
||
851 | |||
852 | #if defined(DXX_BUILD_DESCENT_I) |
||
853 | // Unfortunatly d1x has been writing both shareware and registered |
||
854 | // player files with a saved_game_version of 7 and 8, whereas the |
||
855 | // original decent used 4 for shareware games and 7 for registered |
||
856 | // games. Because of this the player files didn't get properly read |
||
857 | // when reading d1x shareware player files in d1x registered or |
||
858 | // vica versa. The problem is that the sizeof of the taunt macros |
||
859 | // differ between the share and registered versions, causing the |
||
860 | // reading of the player file to go wrong. Thus we now determine the |
||
861 | // sizeof the player file to determine what kinda player file we are |
||
862 | // dealing with so that we can do the right thing |
||
863 | PHYSFS_seek(file, 0); |
||
864 | player_file_size = PHYSFS_fileLength(file); |
||
865 | #endif |
||
866 | int id; |
||
867 | PHYSFS_readSLE32(file, &id); |
||
868 | #if defined(DXX_BUILD_DESCENT_I) |
||
869 | short saved_game_version, player_struct_version; |
||
870 | saved_game_version = PHYSFSX_readShort(file); |
||
871 | player_struct_version = PHYSFSX_readShort(file); |
||
872 | PlayerCfg.NHighestLevels = PHYSFSX_readInt(file); |
||
873 | { |
||
874 | const unsigned u = PHYSFSX_readInt(file); |
||
875 | PlayerCfg.DefaultDifficulty = cast_clamp_difficulty(u); |
||
876 | } |
||
877 | PlayerCfg.AutoLeveling = PHYSFSX_readInt(file); |
||
878 | #elif defined(DXX_BUILD_DESCENT_II) |
||
879 | player_file_version = PHYSFSX_readShort(file); |
||
880 | #endif |
||
881 | |||
882 | if (id!=SAVE_FILE_ID) { |
||
883 | nm_messagebox(TXT_ERROR, 1, TXT_OK, "Invalid player file"); |
||
884 | return -1; |
||
885 | } |
||
886 | |||
887 | #if defined(DXX_BUILD_DESCENT_I) |
||
888 | if (saved_game_version < COMPATIBLE_SAVED_GAME_VERSION || player_struct_version < COMPATIBLE_PLAYER_STRUCT_VERSION) { |
||
889 | nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_ERROR_PLR_VERSION); |
||
890 | return -1; |
||
891 | } |
||
892 | |||
893 | /* determine if we're dealing with a shareware or registered playerfile */ |
||
894 | switch (saved_game_version) |
||
895 | { |
||
896 | case 4: |
||
897 | shareware_file = 1; |
||
898 | break; |
||
899 | case 5: |
||
900 | case 6: |
||
901 | shareware_file = 0; |
||
902 | break; |
||
903 | case 7: |
||
904 | /* version 7 doesn't have the saved games array */ |
||
905 | if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2212 - sizeof(saved_games))) |
||
906 | shareware_file = 1; |
||
907 | if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2252 - sizeof(saved_games))) |
||
908 | shareware_file = 0; |
||
909 | break; |
||
910 | case 8: |
||
911 | if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == 2212) |
||
912 | shareware_file = 1; |
||
913 | if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == 2252) |
||
914 | shareware_file = 0; |
||
915 | /* d1x-rebirth v0.31 to v0.42 broke things by adding stuff to the |
||
916 | player struct without thinking (sigh) */ |
||
917 | if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2212 + 2*sizeof(int))) |
||
918 | { |
||
919 | |||
920 | shareware_file = 1; |
||
921 | /* skip the cruft added to the player_info struct */ |
||
922 | PHYSFS_seek(file, PHYSFS_tell(file)+2*sizeof(int)); |
||
923 | } |
||
924 | if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2252 + 2*sizeof(int))) |
||
925 | { |
||
926 | shareware_file = 0; |
||
927 | /* skip the cruft added to the player_info struct */ |
||
928 | PHYSFS_seek(file, PHYSFS_tell(file)+2*sizeof(int)); |
||
929 | } |
||
930 | } |
||
931 | |||
932 | if (shareware_file == -1) { |
||
933 | nm_messagebox(TXT_ERROR, 1, TXT_OK, "Error invalid or unknown\nplayerfile-size"); |
||
934 | return -1; |
||
935 | } |
||
936 | |||
937 | if (saved_game_version <= 5) { |
||
938 | |||
939 | //deal with old-style highest level info |
||
940 | |||
941 | PlayerCfg.HighestLevels[0].Shortname[0] = 0; //no name for mission 0 |
||
942 | PlayerCfg.HighestLevels[0].LevelNum = PlayerCfg.NHighestLevels; //was highest level in old struct |
||
943 | |||
944 | //This hack allows the player to start on level 8 if he's made it to |
||
945 | //level 7 on the shareware. We do this because the shareware didn't |
||
946 | //save the information that the player finished level 7, so the most |
||
947 | //we know is that he made it to level 7. |
||
948 | if (PlayerCfg.NHighestLevels==7) |
||
949 | PlayerCfg.HighestLevels[0].LevelNum = 8; |
||
950 | |||
951 | } |
||
952 | else { //read new highest level info |
||
953 | if (PHYSFS_read(file,PlayerCfg.HighestLevels,sizeof(hli),PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels) |
||
954 | goto read_player_file_failed; |
||
955 | } |
||
956 | |||
957 | if ( saved_game_version != 7 ) { // Read old & SW saved games. |
||
958 | if (PHYSFS_read(file,saved_games,sizeof(saved_games),1) != 1) |
||
959 | goto read_player_file_failed; |
||
960 | } |
||
961 | |||
962 | #elif defined(DXX_BUILD_DESCENT_II) |
||
963 | if (player_file_version > 255) // bigendian file? |
||
964 | swap = 1; |
||
965 | |||
966 | if (swap) |
||
967 | player_file_version = SWAPSHORT(player_file_version); |
||
968 | |||
969 | if (player_file_version < COMPATIBLE_PLAYER_FILE_VERSION) { |
||
970 | nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_ERROR_PLR_VERSION); |
||
971 | return -1; |
||
972 | } |
||
973 | |||
974 | PHYSFS_seek(file,PHYSFS_tell(file)+2*sizeof(short)); //skip Game_window_w,Game_window_h |
||
975 | PlayerCfg.DefaultDifficulty = cast_clamp_difficulty(PHYSFSX_readByte(file)); |
||
976 | PlayerCfg.AutoLeveling = PHYSFSX_readByte(file); |
||
977 | PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(sbyte)); // skip ReticleOn |
||
978 | PlayerCfg.CockpitMode[0] = PlayerCfg.CockpitMode[1] = static_cast<cockpit_mode_t>(PHYSFSX_readByte(file)); |
||
979 | PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(sbyte)); //skip Default_display_mode |
||
980 | { |
||
981 | auto i = PHYSFSX_readByte(file); |
||
982 | switch (i) |
||
983 | { |
||
984 | case static_cast<unsigned>(MissileViewMode::None): |
||
985 | case static_cast<unsigned>(MissileViewMode::EnabledSelfOnly): |
||
986 | case static_cast<unsigned>(MissileViewMode::EnabledSelfAndAllies): |
||
987 | break; |
||
988 | default: |
||
989 | i = 0; |
||
990 | break; |
||
991 | } |
||
992 | PlayerCfg.MissileViewEnabled = static_cast<MissileViewMode>(i); |
||
993 | } |
||
994 | PlayerCfg.HeadlightActiveDefault = PHYSFSX_readByte(file); |
||
995 | PlayerCfg.GuidedInBigWindow = PHYSFSX_readByte(file); |
||
996 | if (player_file_version >= 19) |
||
997 | PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(sbyte)); //skip Automap_always_hires |
||
998 | |||
999 | //read new highest level info |
||
1000 | |||
1001 | PlayerCfg.NHighestLevels = PHYSFSX_readShort(file); |
||
1002 | if (swap) |
||
1003 | PlayerCfg.NHighestLevels = SWAPSHORT(PlayerCfg.NHighestLevels); |
||
1004 | Assert(PlayerCfg.NHighestLevels <= MAX_MISSIONS); |
||
1005 | |||
1006 | if (PHYSFS_read(file, PlayerCfg.HighestLevels, sizeof(hli), PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels) |
||
1007 | goto read_player_file_failed; |
||
1008 | #endif |
||
1009 | |||
1010 | //read taunt macros |
||
1011 | { |
||
1012 | int len; |
||
1013 | |||
1014 | #if defined(DXX_BUILD_DESCENT_I) |
||
1015 | len = shareware_file? 25:35; |
||
1016 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1017 | len = MAX_MESSAGE_LEN; |
||
1018 | #endif |
||
1019 | |||
1020 | for (auto &i : PlayerCfg.NetworkMessageMacro) |
||
1021 | if (PHYSFS_read(file, i, len, 1) != 1) |
||
1022 | goto read_player_file_failed; |
||
1023 | } |
||
1024 | |||
1025 | //read kconfig data |
||
1026 | { |
||
1027 | ubyte dummy_joy_sens; |
||
1028 | |||
1029 | if (PHYSFS_read(file, &PlayerCfg.KeySettings.Keyboard, sizeof(PlayerCfg.KeySettings.Keyboard), 1) != 1) |
||
1030 | goto read_player_file_failed; |
||
1031 | #if DXX_MAX_JOYSTICKS |
||
1032 | auto &KeySettingsJoystick = PlayerCfg.KeySettings.Joystick; |
||
1033 | #else |
||
1034 | std::array<uint8_t, MAX_CONTROLS> KeySettingsJoystick; |
||
1035 | #endif |
||
1036 | if (PHYSFS_read(file, &KeySettingsJoystick, sizeof(KeySettingsJoystick), 1) != 1) |
||
1037 | goto read_player_file_failed; |
||
1038 | PHYSFS_seek( file, PHYSFS_tell(file)+(sizeof(ubyte)*MAX_CONTROLS*3) ); // Skip obsolete Flightstick/Thrustmaster/Gravis map fields |
||
1039 | if (PHYSFS_read(file, &PlayerCfg.KeySettings.Mouse, sizeof(PlayerCfg.KeySettings.Mouse), 1) != 1) |
||
1040 | goto read_player_file_failed; |
||
1041 | PHYSFS_seek( file, PHYSFS_tell(file)+(sizeof(ubyte)*MAX_CONTROLS) ); // Skip obsolete Cyberman map field |
||
1042 | #if defined(DXX_BUILD_DESCENT_I) |
||
1043 | if (PHYSFS_read(file, &PlayerCfg.ControlType, sizeof(ubyte), 1 )!=1) |
||
1044 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1045 | if (player_file_version>=20) |
||
1046 | PHYSFS_seek( file, PHYSFS_tell(file)+(sizeof(ubyte)*MAX_CONTROLS) ); // Skip obsolete Winjoy map field |
||
1047 | uint8_t control_type_dos, control_type_win; |
||
1048 | if (PHYSFS_read(file, &control_type_dos, sizeof(ubyte), 1) != 1) |
||
1049 | goto read_player_file_failed; |
||
1050 | else if (player_file_version >= 21 && PHYSFS_read(file, &control_type_win, sizeof(ubyte), 1) != 1) |
||
1051 | #endif |
||
1052 | goto read_player_file_failed; |
||
1053 | else if (PHYSFS_read(file, &dummy_joy_sens, sizeof(ubyte), 1) !=1 ) |
||
1054 | goto read_player_file_failed; |
||
1055 | |||
1056 | #if defined(DXX_BUILD_DESCENT_II) |
||
1057 | PlayerCfg.ControlType = control_type_dos; |
||
1058 | |||
1059 | range_for (const unsigned i, xrange(11u)) |
||
1060 | { |
||
1061 | PlayerCfg.PrimaryOrder[i] = PHYSFSX_readByte(file); |
||
1062 | PlayerCfg.SecondaryOrder[i] = PHYSFSX_readByte(file); |
||
1063 | } |
||
1064 | check_weapon_reorder(PlayerCfg.PrimaryOrder); |
||
1065 | check_weapon_reorder(PlayerCfg.SecondaryOrder); |
||
1066 | |||
1067 | if (player_file_version>=16) |
||
1068 | { |
||
1069 | PHYSFS_readSLE32(file, &PlayerCfg.Cockpit3DView[0]); |
||
1070 | PHYSFS_readSLE32(file, &PlayerCfg.Cockpit3DView[1]); |
||
1071 | if (swap) |
||
1072 | { |
||
1073 | PlayerCfg.Cockpit3DView[0] = SWAPINT(PlayerCfg.Cockpit3DView[0]); |
||
1074 | PlayerCfg.Cockpit3DView[1] = SWAPINT(PlayerCfg.Cockpit3DView[1]); |
||
1075 | } |
||
1076 | } |
||
1077 | #endif |
||
1078 | } |
||
1079 | |||
1080 | #if defined(DXX_BUILD_DESCENT_I) |
||
1081 | if ( saved_game_version != 7 ) { |
||
1082 | int i, found=0; |
||
1083 | |||
1084 | Assert( N_SAVE_SLOTS == 10 ); |
||
1085 | |||
1086 | for (i=0; i < N_SAVE_SLOTS; i++ ) { |
||
1087 | if ( saved_games[i].name[0] ) { |
||
1088 | throw std::runtime_error("old save game not supported"); |
||
1089 | // make sure we do not do this again, which would possibly overwrite |
||
1090 | // a new newstyle savegame |
||
1091 | saved_games[i].name[0] = 0; |
||
1092 | found++; |
||
1093 | } |
||
1094 | } |
||
1095 | if (found) |
||
1096 | write_player_file(); |
||
1097 | } |
||
1098 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1099 | if (player_file_version>=22) |
||
1100 | { |
||
1101 | PHYSFS_readSLE32(file, &PlayerCfg.NetlifeKills); |
||
1102 | PHYSFS_readSLE32(file, &PlayerCfg.NetlifeKilled); |
||
1103 | if (swap) { |
||
1104 | PlayerCfg.NetlifeKills = SWAPINT(PlayerCfg.NetlifeKills); |
||
1105 | PlayerCfg.NetlifeKilled = SWAPINT(PlayerCfg.NetlifeKilled); |
||
1106 | } |
||
1107 | } |
||
1108 | else |
||
1109 | { |
||
1110 | PlayerCfg.NetlifeKills=0; PlayerCfg.NetlifeKilled=0; |
||
1111 | } |
||
1112 | |||
1113 | if (player_file_version>=23) |
||
1114 | { |
||
1115 | int i; |
||
1116 | PHYSFS_readSLE32(file, &i); |
||
1117 | if (swap) |
||
1118 | i = SWAPINT(i); |
||
1119 | if (i!=get_lifetime_checksum (PlayerCfg.NetlifeKills,PlayerCfg.NetlifeKilled)) |
||
1120 | { |
||
1121 | PlayerCfg.NetlifeKills=0; PlayerCfg.NetlifeKilled=0; |
||
1122 | nm_messagebox(NULL, 1, "Shame on me", "Trying to cheat eh?"); |
||
1123 | rewrite_it=1; |
||
1124 | } |
||
1125 | } |
||
1126 | |||
1127 | //read guidebot name |
||
1128 | if (player_file_version >= 18) |
||
1129 | PHYSFSX_fgets(PlayerCfg.GuidebotName, file); |
||
1130 | else |
||
1131 | PlayerCfg.GuidebotName = "GUIDE-BOT"; |
||
1132 | PlayerCfg.GuidebotNameReal = PlayerCfg.GuidebotName; |
||
1133 | { |
||
1134 | if (player_file_version >= 24) |
||
1135 | { |
||
1136 | PHYSFSX_gets_line_t<128> buf; |
||
1137 | PHYSFSX_fgets(buf, file); // Just read it in fpr DPS. |
||
1138 | } |
||
1139 | } |
||
1140 | #endif |
||
1141 | |||
1142 | #if defined(DXX_BUILD_DESCENT_II) |
||
1143 | if (rewrite_it) |
||
1144 | write_player_file(); |
||
1145 | #endif |
||
1146 | |||
1147 | filename[strlen(filename) - 4] = 0; |
||
1148 | strcat(filename, ".plx"); |
||
1149 | read_player_dxx(filename); |
||
1150 | kc_set_controls(); |
||
1151 | |||
1152 | return EZERO; |
||
1153 | |||
1154 | read_player_file_failed: |
||
1155 | nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s\n\n%s", "Error reading PLR file", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); // Pierre-Marie Baty -- work around PHYSFS_getLastError() deprecation |
||
1156 | return -1; |
||
1157 | } |
||
1158 | } |
||
1159 | |||
1160 | /* Given a Mission_path, return a pair of pointers. |
||
1161 | * - If the mission cannot be saved, both pointers are nullptr. |
||
1162 | * - If the mission name was previously used, return a pointer to that |
||
1163 | * entry and a pointer to end. |
||
1164 | * - If the mission name is not recorded, return a pointer to the first |
||
1165 | * unused element (which may be end() if all elements are used) and a |
||
1166 | * pointer to end(). The caller must check that the first unused |
||
1167 | * element is not end(). |
||
1168 | */ |
||
1169 | static std::array<std::array<hli, MAX_MISSIONS>::pointer, 2> find_hli_entry(const partial_range_t<hli *> &r, const Mission_path &m) |
||
1170 | { |
||
1171 | const auto mission_filename = m.filename; |
||
1172 | const auto mission_length = std::distance(mission_filename, m.path.end()); |
||
1173 | if (mission_length >= sizeof(hli::Shortname)) |
||
1174 | { |
||
1175 | /* Name is too long to store, so there will never be a match |
||
1176 | * and it is never stored. |
||
1177 | */ |
||
1178 | con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "warning: highest level information cannot be tracked because the mission name is too long to store (should be at most 8 bytes, but is \"%s\")."), &*mission_filename); |
||
1179 | return {{nullptr, nullptr}}; |
||
1180 | } |
||
1181 | const auto &&a = [p = &*mission_filename](const hli &h) { |
||
1182 | return !d_stricmp(h.Shortname.data(), p); |
||
1183 | }; |
||
1184 | const auto i = std::find_if(r.begin(), r.end(), a); |
||
1185 | return {{&*i, r.end()}}; |
||
1186 | } |
||
1187 | |||
1188 | //set a new highest level for player for this mission |
||
1189 | void set_highest_level(const uint8_t best_levelnum_this_game) |
||
1190 | { |
||
1191 | int ret; |
||
1192 | |||
1193 | if ((ret=read_player_file()) != EZERO) |
||
1194 | if (ret != ENOENT) //if file doesn't exist, that's ok |
||
1195 | return; |
||
1196 | |||
1197 | const auto &&r = partial_range(PlayerCfg.HighestLevels, PlayerCfg.NHighestLevels); |
||
1198 | const auto &&i = find_hli_entry(r, *Current_mission); |
||
1199 | const auto ie = i[1]; |
||
1200 | if (!ie) |
||
1201 | return; |
||
1202 | |||
1203 | auto ii = i[0]; |
||
1204 | const hli *d1_preferred = nullptr; |
||
1205 | #if defined(DXX_BUILD_DESCENT_II) |
||
1206 | const hli *d2_preferred = nullptr, *d2x_preferred = nullptr; |
||
1207 | #endif |
||
1208 | range_for (auto &m, r) |
||
1209 | { |
||
1210 | hli *preferred; |
||
1211 | /* Swapping instead of rotating will perturb the order a bit, |
||
1212 | * but this is a one-time fix to get builtin missions to |
||
1213 | * reserved storage. |
||
1214 | */ |
||
1215 | const auto ms = m.Shortname.data(); |
||
1216 | if (!d1_preferred && !strcmp(D1_MISSION_FILENAME, ms)) |
||
1217 | d1_preferred = preferred = &PlayerCfg.HighestLevels[0]; |
||
1218 | #if defined(DXX_BUILD_DESCENT_II) |
||
1219 | else if (!d2_preferred && !strcmp(FULL_MISSION_FILENAME, ms)) |
||
1220 | d2_preferred = preferred = &PlayerCfg.HighestLevels[1]; |
||
1221 | else if (!d2x_preferred && !strcmp("d2x", ms)) |
||
1222 | d2x_preferred = preferred = &PlayerCfg.HighestLevels[2]; |
||
1223 | #endif |
||
1224 | else |
||
1225 | continue; |
||
1226 | if (preferred == &m) |
||
1227 | continue; |
||
1228 | std::swap(*preferred, m); |
||
1229 | } |
||
1230 | const unsigned reserved_slots = !!d1_preferred |
||
1231 | #if defined(DXX_BUILD_DESCENT_II) |
||
1232 | + !!d2_preferred + !!d2x_preferred |
||
1233 | #endif |
||
1234 | ; |
||
1235 | auto &hl = PlayerCfg.HighestLevels; |
||
1236 | const auto irs = &hl[reserved_slots]; |
||
1237 | |||
1238 | uint8_t previous_best_levelnum = 0; |
||
1239 | if (ii == ie) |
||
1240 | { |
||
1241 | /* |
||
1242 | * If ii == ie, the mission is unknown. If there is no free |
||
1243 | * space available, move everything, so that the |
||
1244 | * least-recently-used element (at *irs) is discarded to make |
||
1245 | * room to add this mission as most recently used. |
||
1246 | * |
||
1247 | * Leave previous_best_levelnum set to 0, so that any progress |
||
1248 | * at all qualifies for a save. |
||
1249 | */ |
||
1250 | if (ie == PlayerCfg.HighestLevels.end()) |
||
1251 | { |
||
1252 | std::move(std::next(irs), ie, irs); |
||
1253 | --ii; |
||
1254 | } |
||
1255 | else |
||
1256 | PlayerCfg.NHighestLevels++; |
||
1257 | ii->Shortname.back() = 0; |
||
1258 | strncpy(ii->Shortname.data(), &*Current_mission->filename, ii->Shortname.size() - 1); |
||
1259 | } |
||
1260 | else if (ii != ie - 1) |
||
1261 | { |
||
1262 | /* If this mission is not the most recently used, reorder the |
||
1263 | * list so that it becomes the most recently used. |
||
1264 | * |
||
1265 | * Leave previous_best_levelnum set to 0, so that a save is |
||
1266 | * required, even if the player has not set a new record. |
||
1267 | */ |
||
1268 | std::rotate(ii, std::next(ii), ie); |
||
1269 | ii = ie - 1; |
||
1270 | } |
||
1271 | else |
||
1272 | /* Update previous_best_levelnum so that progress is only saved |
||
1273 | * if this is a new best. |
||
1274 | */ |
||
1275 | previous_best_levelnum = ii->LevelNum; |
||
1276 | |||
1277 | if (previous_best_levelnum < best_levelnum_this_game) |
||
1278 | { |
||
1279 | auto &best_levelnum_recorded = ii->LevelNum; |
||
1280 | if (best_levelnum_recorded < best_levelnum_this_game) |
||
1281 | best_levelnum_recorded = best_levelnum_this_game; |
||
1282 | write_player_file(); |
||
1283 | } |
||
1284 | } |
||
1285 | |||
1286 | //gets the player's highest level from the file for this mission |
||
1287 | int get_highest_level(void) |
||
1288 | { |
||
1289 | read_player_file(); |
||
1290 | const Mission_path &mission = *Current_mission; |
||
1291 | // Destination Saturn. |
||
1292 | const auto &&r = partial_range(PlayerCfg.HighestLevels, PlayerCfg.NHighestLevels); |
||
1293 | #if defined(DXX_BUILD_DESCENT_I) |
||
1294 | unsigned highest_saturn_level = 0; |
||
1295 | if (!*mission.filename) |
||
1296 | range_for (auto &m, r) |
||
1297 | if (!d_stricmp("DESTSAT", m.Shortname.data())) |
||
1298 | highest_saturn_level = m.LevelNum; |
||
1299 | #endif |
||
1300 | const auto &&i = find_hli_entry(r, mission); |
||
1301 | const auto ie = i[1]; |
||
1302 | const auto ii = i[0]; |
||
1303 | if (ii == ie) |
||
1304 | return 0; |
||
1305 | auto LevelNum = ii->LevelNum; |
||
1306 | #if defined(DXX_BUILD_DESCENT_I) |
||
1307 | /* Override `LevelNum` of the full campaign with the player's |
||
1308 | * progress in the OEM mini-campaign, so that users who switch can |
||
1309 | * keep their progress. |
||
1310 | * |
||
1311 | * If the player never played Destination Saturn, or if this is not |
||
1312 | * the built-in campaign, highest_saturn_level will be 0 and this |
||
1313 | * will be skipped. |
||
1314 | */ |
||
1315 | if (LevelNum < highest_saturn_level) |
||
1316 | LevelNum = highest_saturn_level; |
||
1317 | #endif |
||
1318 | return LevelNum; |
||
1319 | } |
||
1320 | |||
1321 | //write out player's saved games. returns errno (0 == no error) |
||
1322 | namespace dsx { |
||
1323 | void write_player_file() |
||
1324 | { |
||
1325 | char filename[PATH_MAX]; |
||
1326 | int errno_ret; |
||
1327 | |||
1328 | if ( Newdemo_state == ND_STATE_PLAYBACK ) |
||
1329 | return; |
||
1330 | |||
1331 | errno_ret = WriteConfigFile(); |
||
1332 | |||
1333 | snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.plx"), static_cast<const char *>(InterfaceUniqueState.PilotName)); |
||
1334 | write_player_dxx(filename); |
||
1335 | snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.plr"), static_cast<const char *>(InterfaceUniqueState.PilotName)); |
||
1336 | auto file = PHYSFSX_openWriteBuffered(filename); |
||
1337 | if (!file) |
||
1338 | return; |
||
1339 | |||
1340 | //Write out player's info |
||
1341 | PHYSFS_writeULE32(file, SAVE_FILE_ID); |
||
1342 | #if defined(DXX_BUILD_DESCENT_I) |
||
1343 | PHYSFS_writeULE16(file, SAVED_GAME_VERSION); |
||
1344 | PHYSFS_writeULE16(file, PLAYER_STRUCT_VERSION); |
||
1345 | PHYSFS_writeSLE32(file, PlayerCfg.NHighestLevels); |
||
1346 | PHYSFS_writeSLE32(file, PlayerCfg.DefaultDifficulty); |
||
1347 | PHYSFS_writeSLE32(file, PlayerCfg.AutoLeveling); |
||
1348 | errno_ret = EZERO; |
||
1349 | |||
1350 | //write higest level info |
||
1351 | if ((PHYSFS_write( file, PlayerCfg.HighestLevels, sizeof(hli), PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels)) { |
||
1352 | errno_ret = errno; |
||
1353 | return; |
||
1354 | } |
||
1355 | |||
1356 | if (PHYSFS_write( file, saved_games,sizeof(saved_games),1) != 1) { |
||
1357 | errno_ret = errno; |
||
1358 | return; |
||
1359 | } |
||
1360 | |||
1361 | range_for (auto &i, PlayerCfg.NetworkMessageMacro) |
||
1362 | if (PHYSFS_write(file, i.data(), i.size(), 1) != 1) { |
||
1363 | errno_ret = errno; |
||
1364 | return; |
||
1365 | } |
||
1366 | |||
1367 | //write kconfig info |
||
1368 | { |
||
1369 | if (PHYSFS_write(file, PlayerCfg.KeySettings.Keyboard, sizeof(PlayerCfg.KeySettings.Keyboard), 1) != 1) |
||
1370 | errno_ret=errno; |
||
1371 | #if DXX_MAX_JOYSTICKS |
||
1372 | auto &KeySettingsJoystick = PlayerCfg.KeySettings.Joystick; |
||
1373 | #else |
||
1374 | const std::array<uint8_t, MAX_CONTROLS> KeySettingsJoystick{}; |
||
1375 | #endif |
||
1376 | if (PHYSFS_write(file, KeySettingsJoystick, sizeof(KeySettingsJoystick), 1) != 1) |
||
1377 | errno_ret=errno; |
||
1378 | for (unsigned i = 0; i < MAX_CONTROLS*3; i++) |
||
1379 | if (PHYSFS_write(file, "0", sizeof(ubyte), 1) != 1) // Skip obsolete Flightstick/Thrustmaster/Gravis map fields |
||
1380 | errno_ret=errno; |
||
1381 | if (PHYSFS_write(file, PlayerCfg.KeySettings.Mouse, sizeof(PlayerCfg.KeySettings.Mouse), 1) != 1) |
||
1382 | errno_ret=errno; |
||
1383 | { |
||
1384 | std::array<uint8_t, MAX_CONTROLS> cyberman{}; |
||
1385 | if (PHYSFS_write(file, cyberman.data(), cyberman.size(), 1) != 1) // Skip obsolete Cyberman map field |
||
1386 | errno_ret=errno; |
||
1387 | } |
||
1388 | |||
1389 | if(errno_ret == EZERO) |
||
1390 | { |
||
1391 | ubyte old_avg_joy_sensitivity = 8; |
||
1392 | if (PHYSFS_write( file, &PlayerCfg.ControlType, sizeof(ubyte), 1 )!=1) |
||
1393 | errno_ret=errno; |
||
1394 | else if (PHYSFS_write( file, &old_avg_joy_sensitivity, sizeof(ubyte), 1 )!=1) |
||
1395 | errno_ret=errno; |
||
1396 | } |
||
1397 | } |
||
1398 | |||
1399 | if (!file.close()) |
||
1400 | errno_ret = errno; |
||
1401 | |||
1402 | if (errno_ret != EZERO) { |
||
1403 | PHYSFS_delete(filename); //delete bogus file |
||
1404 | nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s\n\n%s",TXT_ERROR_WRITING_PLR, strerror(errno_ret)); |
||
1405 | } |
||
1406 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1407 | (void)errno_ret; |
||
1408 | PHYSFS_writeULE16(file, PLAYER_FILE_VERSION); |
||
1409 | |||
1410 | |||
1411 | PHYSFS_seek(file,PHYSFS_tell(file)+2*(sizeof(PHYSFS_uint16))); // skip Game_window_w, Game_window_h |
||
1412 | PHYSFSX_writeU8(file, PlayerCfg.DefaultDifficulty); |
||
1413 | PHYSFSX_writeU8(file, PlayerCfg.AutoLeveling); |
||
1414 | PHYSFSX_writeU8(file, PlayerCfg.ReticleType==RET_TYPE_NONE?0:1); |
||
1415 | PHYSFSX_writeU8(file, PlayerCfg.CockpitMode[0]); |
||
1416 | PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(PHYSFS_uint8)); // skip Default_display_mode |
||
1417 | PHYSFSX_writeU8(file, static_cast<uint8_t>(PlayerCfg.MissileViewEnabled)); |
||
1418 | PHYSFSX_writeU8(file, PlayerCfg.HeadlightActiveDefault); |
||
1419 | PHYSFSX_writeU8(file, PlayerCfg.GuidedInBigWindow); |
||
1420 | PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(PHYSFS_uint8)); // skip Automap_always_hires |
||
1421 | |||
1422 | //write higest level info |
||
1423 | PHYSFS_writeULE16(file, PlayerCfg.NHighestLevels); |
||
1424 | if ((PHYSFS_write(file, PlayerCfg.HighestLevels, sizeof(hli), PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels)) |
||
1425 | goto write_player_file_failed; |
||
1426 | |||
1427 | range_for (auto &i, PlayerCfg.NetworkMessageMacro) |
||
1428 | if (PHYSFS_write(file, i.data(), i.size(), 1) != 1) |
||
1429 | goto write_player_file_failed; |
||
1430 | |||
1431 | //write kconfig info |
||
1432 | { |
||
1433 | |||
1434 | ubyte old_avg_joy_sensitivity = 8; |
||
1435 | ubyte control_type_dos = PlayerCfg.ControlType; |
||
1436 | |||
1437 | if (PHYSFS_write(file, PlayerCfg.KeySettings.Keyboard, sizeof(PlayerCfg.KeySettings.Keyboard), 1) != 1) |
||
1438 | goto write_player_file_failed; |
||
1439 | #if DXX_MAX_JOYSTICKS |
||
1440 | auto &KeySettingsJoystick = PlayerCfg.KeySettings.Joystick; |
||
1441 | #else |
||
1442 | const std::array<uint8_t, MAX_CONTROLS> KeySettingsJoystick{}; |
||
1443 | #endif |
||
1444 | if (PHYSFS_write(file, KeySettingsJoystick, sizeof(KeySettingsJoystick), 1) != 1) |
||
1445 | goto write_player_file_failed; |
||
1446 | for (unsigned i = 0; i < MAX_CONTROLS*3; i++) |
||
1447 | if (PHYSFS_write(file, "0", sizeof(ubyte), 1) != 1) // Skip obsolete Flightstick/Thrustmaster/Gravis map fields |
||
1448 | goto write_player_file_failed; |
||
1449 | if (PHYSFS_write(file, PlayerCfg.KeySettings.Mouse, sizeof(PlayerCfg.KeySettings.Mouse), 1) != 1) |
||
1450 | goto write_player_file_failed; |
||
1451 | for (unsigned i = 0; i < MAX_CONTROLS*2; i++) |
||
1452 | if (PHYSFS_write(file, "0", sizeof(ubyte), 1) != 1) // Skip obsolete Cyberman/Winjoy map fields |
||
1453 | goto write_player_file_failed; |
||
1454 | if (PHYSFS_write(file, &control_type_dos, sizeof(ubyte), 1) != 1) |
||
1455 | goto write_player_file_failed; |
||
1456 | ubyte control_type_win = 0; |
||
1457 | if (PHYSFS_write(file, &control_type_win, sizeof(ubyte), 1) != 1) |
||
1458 | goto write_player_file_failed; |
||
1459 | if (PHYSFS_write(file, &old_avg_joy_sensitivity, sizeof(ubyte), 1) != 1) |
||
1460 | goto write_player_file_failed; |
||
1461 | |||
1462 | range_for (const unsigned i, xrange(11u)) |
||
1463 | { |
||
1464 | PHYSFS_write(file, &PlayerCfg.PrimaryOrder[i], sizeof(ubyte), 1); |
||
1465 | PHYSFS_write(file, &PlayerCfg.SecondaryOrder[i], sizeof(ubyte), 1); |
||
1466 | } |
||
1467 | |||
1468 | PHYSFS_writeULE32(file, PlayerCfg.Cockpit3DView[0]); |
||
1469 | PHYSFS_writeULE32(file, PlayerCfg.Cockpit3DView[1]); |
||
1470 | |||
1471 | PHYSFS_writeULE32(file, PlayerCfg.NetlifeKills); |
||
1472 | PHYSFS_writeULE32(file, PlayerCfg.NetlifeKilled); |
||
1473 | int i=get_lifetime_checksum (PlayerCfg.NetlifeKills,PlayerCfg.NetlifeKilled); |
||
1474 | PHYSFS_writeULE32(file, i); |
||
1475 | } |
||
1476 | |||
1477 | //write guidebot name |
||
1478 | PHYSFSX_writeString(file, PlayerCfg.GuidebotNameReal); |
||
1479 | |||
1480 | { |
||
1481 | char buf[128]; |
||
1482 | strcpy(buf, "DOS joystick"); |
||
1483 | PHYSFSX_writeString(file, buf); // Write out current joystick for player. |
||
1484 | } |
||
1485 | |||
1486 | if (!file.close()) |
||
1487 | goto write_player_file_failed; |
||
1488 | |||
1489 | return; |
||
1490 | |||
1491 | write_player_file_failed: |
||
1492 | nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s\n\n%s", TXT_ERROR_WRITING_PLR, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); |
||
1493 | if (file) |
||
1494 | { |
||
1495 | file.reset(); |
||
1496 | PHYSFS_delete(filename); //delete bogus file |
||
1497 | } |
||
1498 | #endif |
||
1499 | } |
||
1500 | |||
1501 | #if defined(DXX_BUILD_DESCENT_II) |
||
1502 | static int get_lifetime_checksum (int a,int b) |
||
1503 | { |
||
1504 | int num; |
||
1505 | |||
1506 | // confusing enough to beat amateur disassemblers? Lets hope so |
||
1507 | |||
1508 | num=(a<<8 ^ b); |
||
1509 | num^=(a | b); |
||
1510 | num*=num>>2; |
||
1511 | return (num); |
||
1512 | } |
||
1513 | #endif |
||
1514 | |||
1515 | template <uint_fast32_t shift, uint_fast32_t width> |
||
1516 | static void convert_duplicate_powerup_integer(packed_netduplicate_items &d, const char *value) |
||
1517 | { |
||
1518 | /* Initialize 'i' to avoid bogus -Wmaybe-uninitialized at -Og on |
||
1519 | * gcc-4.9 */ |
||
1520 | unsigned i = 0; |
||
1521 | if (convert_integer(i, value) && !(i & ~((1 << width) - 1))) |
||
1522 | d.set_sub_field<shift, width>(i); |
||
1523 | } |
||
1524 | |||
1525 | // read stored values from ngp file to netgame_info |
||
1526 | void read_netgame_profile(netgame_info *ng) |
||
1527 | { |
||
1528 | char filename[PATH_MAX]; |
||
1529 | #if DXX_USE_TRACKER |
||
1530 | ng->TrackerNATWarned = TrackerNATHolePunchWarn::Unset; |
||
1531 | #endif |
||
1532 | |||
1533 | snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.ngp"), static_cast<const char *>(InterfaceUniqueState.PilotName)); |
||
1534 | auto file = PHYSFSX_openReadBuffered(filename); |
||
1535 | if (!file) |
||
1536 | return; |
||
1537 | |||
1538 | ng->MPGameplayOptions.AutosaveInterval = std::chrono::minutes(10); |
||
1539 | // NOTE that we do not set any defaults here or even initialize netgame_info. For flexibility, leave that to the function calling this. |
||
1540 | for (PHYSFSX_gets_line_t<50> line; const char *const eol = PHYSFSX_fgets(line, file);) |
||
1541 | { |
||
1542 | const auto lb = line.begin(); |
||
1543 | if (eol == line.end()) |
||
1544 | continue; |
||
1545 | auto eq = std::find(lb, eol, '='); |
||
1546 | if (eq == eol) |
||
1547 | continue; |
||
1548 | auto value = std::next(eq); |
||
1549 | if (cmp(lb, eq, GameNameStr)) |
||
1550 | convert_string(ng->game_name, value, eol); |
||
1551 | else if (cmp(lb, eq, GameModeStr)) |
||
1552 | convert_integer(ng->gamemode, value); |
||
1553 | else if (cmp(lb, eq, RefusePlayersStr)) |
||
1554 | convert_integer(ng->RefusePlayers, value); |
||
1555 | else if (cmp(lb, eq, DifficultyStr)) |
||
1556 | { |
||
1557 | uint8_t difficulty; |
||
1558 | if (convert_integer(difficulty, value)) |
||
1559 | ng->difficulty = cast_clamp_difficulty(difficulty); |
||
1560 | } |
||
1561 | else if (cmp(lb, eq, GameFlagsStr)) |
||
1562 | { |
||
1563 | packed_game_flags p; |
||
1564 | if (convert_integer(p.value, value)) |
||
1565 | ng->game_flag = unpack_game_flags(&p); |
||
1566 | } |
||
1567 | else if (cmp(lb, eq, AllowedItemsStr)) |
||
1568 | convert_integer(ng->AllowedItems, value); |
||
1569 | else if (cmp(lb, eq, SpawnGrantedItemsStr)) |
||
1570 | convert_integer(ng->SpawnGrantedItems.mask, value); |
||
1571 | else if (cmp(lb, eq, DuplicatePrimariesStr)) |
||
1572 | convert_duplicate_powerup_integer<packed_netduplicate_items::primary_shift, packed_netduplicate_items::primary_width>(ng->DuplicatePowerups, value); |
||
1573 | else if (cmp(lb, eq, DuplicateSecondariesStr)) |
||
1574 | convert_duplicate_powerup_integer<packed_netduplicate_items::secondary_shift, packed_netduplicate_items::secondary_width>(ng->DuplicatePowerups, value); |
||
1575 | #if defined(DXX_BUILD_DESCENT_II) |
||
1576 | else if (cmp(lb, eq, DuplicateAccessoriesStr)) |
||
1577 | convert_duplicate_powerup_integer<packed_netduplicate_items::accessory_shift, packed_netduplicate_items::accessory_width>(ng->DuplicatePowerups, value); |
||
1578 | else if (cmp(lb, eq, AllowMarkerViewStr)) |
||
1579 | convert_integer(ng->Allow_marker_view, value); |
||
1580 | else if (cmp(lb, eq, AlwaysLightingStr)) |
||
1581 | convert_integer(ng->AlwaysLighting, value); |
||
1582 | else if (cmp(lb, eq, ThiefAbsenceFlagStr)) |
||
1583 | { |
||
1584 | if (strtoul(value, 0, 10)) |
||
1585 | ng->ThiefModifierFlags |= ThiefModifier::Absent; |
||
1586 | } |
||
1587 | else if (cmp(lb, eq, ThiefNoEnergyWeaponsFlagStr)) |
||
1588 | { |
||
1589 | if (strtoul(value, 0, 10)) |
||
1590 | ng->ThiefModifierFlags |= ThiefModifier::NoEnergyWeapons; |
||
1591 | } |
||
1592 | else if (cmp(lb, eq, AllowGuidebotStr)) |
||
1593 | convert_integer(ng->AllowGuidebot, value); |
||
1594 | #endif |
||
1595 | else if (cmp(lb, eq, ShufflePowerupsStr)) |
||
1596 | convert_integer(ng->ShufflePowerupSeed, value); |
||
1597 | else if (cmp(lb, eq, ShowEnemyNamesStr)) |
||
1598 | convert_integer(ng->ShowEnemyNames, value); |
||
1599 | else if (cmp(lb, eq, BrightPlayersStr)) |
||
1600 | convert_integer(ng->BrightPlayers, value); |
||
1601 | else if (cmp(lb, eq, InvulAppearStr)) |
||
1602 | convert_integer(ng->InvulAppear, value); |
||
1603 | else if (cmp(lb, eq, KillGoalStr)) |
||
1604 | convert_integer(ng->KillGoal, value); |
||
1605 | else if (cmp(lb, eq, PlayTimeAllowedStr)) |
||
1606 | { |
||
1607 | int PlayTimeAllowed; |
||
1608 | if (convert_integer(PlayTimeAllowed, value)) |
||
1609 | ng->PlayTimeAllowed = std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>(PlayTimeAllowed); |
||
1610 | } |
||
1611 | else if (cmp(lb, eq, ControlInvulTimeStr)) |
||
1612 | convert_integer(ng->control_invul_time, value); |
||
1613 | else if (cmp(lb, eq, PacketsPerSecStr)) |
||
1614 | convert_integer(ng->PacketsPerSec, value); |
||
1615 | else if (cmp(lb, eq, NoFriendlyFireStr)) |
||
1616 | convert_integer(ng->NoFriendlyFire, value); |
||
1617 | else if (cmp(lb, eq, MouselookFlagsStr)) |
||
1618 | convert_integer(ng->MouselookFlags, value); |
||
1619 | else if (cmp(lb, eq, AutosaveIntervalStr)) |
||
1620 | { |
||
1621 | uint16_t AutosaveInterval; |
||
1622 | if (convert_integer(AutosaveInterval, value)) |
||
1623 | ng->MPGameplayOptions.AutosaveInterval = std::chrono::seconds(AutosaveInterval); |
||
1624 | } |
||
1625 | #if DXX_USE_TRACKER |
||
1626 | else if (cmp(lb, eq, TrackerStr)) |
||
1627 | convert_integer(ng->Tracker, value); |
||
1628 | else if (cmp(lb, eq, TrackerNATHPStr)) |
||
1629 | ng->TrackerNATWarned = static_cast<TrackerNATHolePunchWarn>(strtoul(value, 0, 10)); |
||
1630 | #endif |
||
1631 | } |
||
1632 | } |
||
1633 | |||
1634 | // write values from netgame_info to ngp file |
||
1635 | void write_netgame_profile(netgame_info *ng) |
||
1636 | { |
||
1637 | char filename[PATH_MAX]; |
||
1638 | snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.ngp"), static_cast<const char *>(InterfaceUniqueState.PilotName)); |
||
1639 | auto file = PHYSFSX_openWriteBuffered(filename); |
||
1640 | if (!file) |
||
1641 | return; |
||
1642 | |||
1643 | PHYSFSX_printf(file, GameNameStr "=%s\n", ng->game_name.data()); |
||
1644 | PHYSFSX_printf(file, GameModeStr "=%i\n", ng->gamemode); |
||
1645 | PHYSFSX_printf(file, RefusePlayersStr "=%i\n", ng->RefusePlayers); |
||
1646 | PHYSFSX_printf(file, DifficultyStr "=%i\n", ng->difficulty); |
||
1647 | PHYSFSX_printf(file, GameFlagsStr "=%i\n", pack_game_flags(&ng->game_flag).value); |
||
1648 | PHYSFSX_printf(file, AllowedItemsStr "=%i\n", ng->AllowedItems); |
||
1649 | PHYSFSX_printf(file, SpawnGrantedItemsStr "=%i\n", ng->SpawnGrantedItems.mask); |
||
1650 | PHYSFSX_printf(file, DuplicatePrimariesStr "=%" PRIuFAST32 "\n", ng->DuplicatePowerups.get_primary_count()); |
||
1651 | PHYSFSX_printf(file, DuplicateSecondariesStr "=%" PRIuFAST32 "\n", ng->DuplicatePowerups.get_secondary_count()); |
||
1652 | #if defined(DXX_BUILD_DESCENT_II) |
||
1653 | PHYSFSX_printf(file, DuplicateAccessoriesStr "=%" PRIuFAST32 "\n", ng->DuplicatePowerups.get_accessory_count()); |
||
1654 | PHYSFSX_printf(file, AllowMarkerViewStr "=%i\n", ng->Allow_marker_view); |
||
1655 | PHYSFSX_printf(file, AlwaysLightingStr "=%i\n", ng->AlwaysLighting); |
||
1656 | PHYSFSX_printf(file, ThiefAbsenceFlagStr "=%i\n", ng->ThiefModifierFlags & ThiefModifier::Absent); |
||
1657 | PHYSFSX_printf(file, ThiefNoEnergyWeaponsFlagStr "=%i\n", ng->ThiefModifierFlags & ThiefModifier::NoEnergyWeapons); |
||
1658 | PHYSFSX_printf(file, AllowGuidebotStr "=%i\n", ng->AllowGuidebot); |
||
1659 | #endif |
||
1660 | PHYSFSX_printf(file, ShufflePowerupsStr "=%i\n", !!ng->ShufflePowerupSeed); |
||
1661 | PHYSFSX_printf(file, ShowEnemyNamesStr "=%i\n", ng->ShowEnemyNames); |
||
1662 | PHYSFSX_printf(file, BrightPlayersStr "=%i\n", ng->BrightPlayers); |
||
1663 | PHYSFSX_printf(file, InvulAppearStr "=%i\n", ng->InvulAppear); |
||
1664 | PHYSFSX_printf(file, KillGoalStr "=%i\n", ng->KillGoal); |
||
1665 | PHYSFSX_printf(file, PlayTimeAllowedStr "=%i\n", std::chrono::duration_cast<std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>>(ng->PlayTimeAllowed).count()); |
||
1666 | PHYSFSX_printf(file, ControlInvulTimeStr "=%i\n", ng->control_invul_time); |
||
1667 | PHYSFSX_printf(file, PacketsPerSecStr "=%i\n", ng->PacketsPerSec); |
||
1668 | PHYSFSX_printf(file, NoFriendlyFireStr "=%i\n", ng->NoFriendlyFire); |
||
1669 | PHYSFSX_printf(file, MouselookFlagsStr "=%i\n", ng->MouselookFlags); |
||
1670 | PHYSFSX_printf(file, AutosaveIntervalStr "=%i\n", ng->MPGameplayOptions.AutosaveInterval.count()); |
||
1671 | #if DXX_USE_TRACKER |
||
1672 | PHYSFSX_printf(file, TrackerStr "=%i\n", ng->Tracker); |
||
1673 | PHYSFSX_printf(file, TrackerNATHPStr "=%i\n", ng->TrackerNATWarned); |
||
1674 | #else |
||
1675 | PHYSFSX_puts_literal(file, TrackerStr "=0\n" TrackerNATHPStr "=0\n"); |
||
1676 | #endif |
||
1677 | PHYSFSX_printf(file, NGPVersionStr "=" DXX_VERSION_STR "\n"); |
||
1678 | } |
||
1679 | |||
1680 | } |