1 /*
2     C-Dogs SDL
3     A port of the legendary (and fun) action/arcade cdogs.
4 
5     Copyright (c) 2013-2014, 2016-2018 Cong Xu
6     All rights reserved.
7 
8     Redistribution and use in source and binary forms, with or without
9     modification, are permitted provided that the following conditions are met:
10 
11     Redistributions of source code must retain the above copyright notice, this
12     list of conditions and the following disclaimer.
13     Redistributions in binary form must reproduce the above copyright notice,
14     this list of conditions and the following disclaimer in the documentation
15     and/or other materials provided with the distribution.
16 
17     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20     ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22     CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27     POSSIBILITY OF SUCH DAMAGE.
28 */
29 #include "config.h"
30 
31 #include <limits.h>
32 #include <stdio.h>
33 
34 #include "blit.h"
35 #include "config_json.h"
36 #include "config_old.h"
37 #include "keyboard.h"
38 #include "music.h"
39 #include "sounds.h"
40 #include "utils.h"
41 
42 
DifficultyStr(int d)43 const char *DifficultyStr(int d)
44 {
45 	switch (d)
46 	{
47 		T2S(DIFFICULTY_VERYEASY, "Easiest");
48 		T2S(DIFFICULTY_EASY, "Easy");
49 		T2S(DIFFICULTY_NORMAL, "Normal");
50 		T2S(DIFFICULTY_HARD, "Hard");
51 		T2S(DIFFICULTY_VERYHARD, "Very hard");
52 	default:
53 		return "";
54 	}
55 }
StrDifficulty(const char * s)56 int StrDifficulty(const char *s)
57 {
58 	S2T(DIFFICULTY_VERYEASY, "Easiest");
59 	S2T(DIFFICULTY_EASY, "Easy");
60 	S2T(DIFFICULTY_NORMAL, "Normal");
61 	S2T(DIFFICULTY_HARD, "Hard");
62 	S2T(DIFFICULTY_VERYHARD, "Very hard");
63 	return DIFFICULTY_NORMAL;
64 }
FireMoveStyleStr(int s)65 const char *FireMoveStyleStr(int s)
66 {
67 	switch (s)
68 	{
69 		T2S(FIREMOVE_STOP, "Stop");
70 		T2S(FIREMOVE_NORMAL, "Normal");
71 		T2S(FIREMOVE_STRAFE, "Strafe");
72 	default:
73 		return "";
74 	}
75 }
StrFireMoveStyle(const char * s)76 int StrFireMoveStyle(const char *s)
77 {
78 	S2T(FIREMOVE_STOP, "Stop");
79 	S2T(FIREMOVE_NORMAL, "Normal");
80 	S2T(FIREMOVE_STRAFE, "Strafe");
81 	return FIREMOVE_STOP;
82 }
SwitchMoveStyleStr(int s)83 const char *SwitchMoveStyleStr(int s)
84 {
85 	switch (s)
86 	{
87 		T2S(SWITCHMOVE_SLIDE, "Slide");
88 		T2S(SWITCHMOVE_STRAFE, "Strafe");
89 		T2S(SWITCHMOVE_NONE, "None");
90 	default:
91 		return "";
92 	}
93 }
StrSwitchMoveStyle(const char * s)94 int StrSwitchMoveStyle(const char *s)
95 {
96 	S2T(SWITCHMOVE_SLIDE, "Slide");
97 	S2T(SWITCHMOVE_STRAFE, "Strafe");
98 	S2T(SWITCHMOVE_NONE, "None");
99 	return SWITCHMOVE_SLIDE;
100 }
ScaleModeStr(int s)101 const char *ScaleModeStr(int s)
102 {
103 	switch (s)
104 	{
105 		T2S(SCALE_MODE_NN, "Nearest neighbor");
106 		T2S(SCALE_MODE_BILINEAR, "Bilinear");
107 	default:
108 		return "";
109 	}
110 }
StrScaleMode(const char * s)111 int StrScaleMode(const char *s)
112 {
113 	S2T(SCALE_MODE_NN, "Nearest neighbor");
114 	S2T(SCALE_MODE_BILINEAR, "Bilinear");
115 	return SCALE_MODE_NN;
116 }
GoreAmountStr(int g)117 const char *GoreAmountStr(int g)
118 {
119 	switch (g)
120 	{
121 		T2S(GORE_NONE, "None");
122 		T2S(GORE_LOW, "Trickle");
123 		T2S(GORE_MEDIUM, "Buckets");
124 		T2S(GORE_HIGH, "Torrents");
125 	default:
126 		return "";
127 	}
128 }
StrGoreAmount(const char * s)129 int StrGoreAmount(const char *s)
130 {
131 	S2T(GORE_NONE, "None");
132 	S2T(GORE_LOW, "Trickle");
133 	S2T(GORE_MEDIUM, "Buckets");
134 	S2T(GORE_HIGH, "Torrents");
135 	return GORE_NONE;
136 }
LaserSightStr(int l)137 const char *LaserSightStr(int l)
138 {
139 	switch (l)
140 	{
141 		T2S(LASER_SIGHT_NONE, "None");
142 		T2S(LASER_SIGHT_PLAYERS, "Players only");
143 		T2S(LASER_SIGHT_ALL, "All");
144 	default:
145 		return "";
146 	}
147 }
StrLaserSight(const char * s)148 int StrLaserSight(const char *s)
149 {
150 	S2T(LASER_SIGHT_NONE, "None");
151 	S2T(LASER_SIGHT_PLAYERS, "Players only");
152 	S2T(LASER_SIGHT_ALL, "All");
153 	return LASER_SIGHT_NONE;
154 }
SplitscreenStyleStr(int s)155 const char *SplitscreenStyleStr(int s)
156 {
157 	switch (s)
158 	{
159 		T2S(SPLITSCREEN_NORMAL, "Normal");
160 		T2S(SPLITSCREEN_ALWAYS, "Always");
161 		T2S(SPLITSCREEN_NEVER, "Never");
162 		default:
163 			return "";
164 	}
165 }
StrSplitscreenStyle(const char * s)166 int StrSplitscreenStyle(const char *s)
167 {
168 	S2T(SPLITSCREEN_NORMAL, "Normal");
169 	S2T(SPLITSCREEN_ALWAYS, "Always");
170 	S2T(SPLITSCREEN_NEVER, "Never");
171 	return SPLITSCREEN_NORMAL;
172 }
AIChatterStr(int c)173 const char *AIChatterStr(int c)
174 {
175 	switch (c)
176 	{
177 		T2S(AICHATTER_NONE, "None");
178 		T2S(AICHATTER_SELDOM, "Seldom");
179 		T2S(AICHATTER_OFTEN, "Often");
180 		T2S(AICHATTER_ALWAYS, "Always");
181 	default:
182 		return "";
183 	}
184 }
StrAIChatter(const char * s)185 int StrAIChatter(const char *s)
186 {
187 	S2T(AICHATTER_NONE, "None");
188 	S2T(AICHATTER_SELDOM, "Seldom");
189 	S2T(AICHATTER_OFTEN, "Often");
190 	S2T(AICHATTER_ALWAYS, "Always");
191 	return AICHATTER_NONE;
192 }
QuickPlayQuantityStr(int q)193 const char *QuickPlayQuantityStr(int q)
194 {
195 	switch (q)
196 	{
197 		T2S(QUICKPLAY_QUANTITY_ANY, "Any");
198 		T2S(QUICKPLAY_QUANTITY_SMALL, "Small");
199 		T2S(QUICKPLAY_QUANTITY_MEDIUM, "Medium");
200 		T2S(QUICKPLAY_QUANTITY_LARGE, "Large");
201 	default:
202 		return "";
203 	}
204 }
StrQuickPlayQuantity(const char * s)205 int StrQuickPlayQuantity(const char *s)
206 {
207 	S2T(QUICKPLAY_QUANTITY_ANY, "Any");
208 	S2T(QUICKPLAY_QUANTITY_SMALL, "Small");
209 	S2T(QUICKPLAY_QUANTITY_MEDIUM, "Medium");
210 	S2T(QUICKPLAY_QUANTITY_LARGE, "Large");
211 	return QUICKPLAY_QUANTITY_ANY;
212 }
213 
214 
215 Config gConfig;
216 
217 static Config ConfigNew(const char *name, const ConfigType type);
ConfigNewString(const char * name,const char * defaultValue)218 Config ConfigNewString(const char *name, const char *defaultValue)
219 {
220 	Config c = ConfigNew(name, CONFIG_TYPE_STRING);
221 	UNUSED(defaultValue);
222 	CASSERT(false, "unimplemented");
223 	return c;
224 }
ConfigNewInt(const char * name,const int defaultValue,const int minValue,const int maxValue,const int increment,int (* strToInt)(const char *),char * (* intToStr)(int))225 Config ConfigNewInt(
226 	const char *name, const int defaultValue,
227 	const int minValue, const int maxValue, const int increment,
228 	int (*strToInt)(const char *), char *(*intToStr)(int))
229 {
230 	Config c = ConfigNew(name, CONFIG_TYPE_INT);
231 	c.u.Int.Default = c.u.Int.Value = c.u.Int.Last = defaultValue;
232 	c.u.Int.Min = minValue;
233 	c.u.Int.Max = maxValue;
234 	c.u.Int.Increment = increment;
235 	c.u.Int.StrToInt = strToInt ? strToInt : atoi;
236 	c.u.Int.IntToStr = intToStr ? intToStr : IntStr;
237 	return c;
238 }
ConfigNewFloat(const char * name,const double defaultValue,const double minValue,const double maxValue,const double increment)239 Config ConfigNewFloat(
240 	const char *name, const double defaultValue,
241 	const double minValue, const double maxValue, const double increment)
242 {
243 	Config c = ConfigNew(name, CONFIG_TYPE_FLOAT);
244 	c.u.Float.Default = c.u.Float.Value = c.u.Float.Last = defaultValue;
245 	c.u.Float.Min = minValue;
246 	c.u.Float.Max = maxValue;
247 	c.u.Float.Increment = increment;
248 	return c;
249 }
ConfigNewBool(const char * name,const bool defaultValue)250 Config ConfigNewBool(const char *name, const bool defaultValue)
251 {
252 	Config c = ConfigNew(name, CONFIG_TYPE_BOOL);
253 	c.u.Bool.Default = c.u.Bool.Value = c.u.Bool.Last = defaultValue;
254 	return c;
255 }
ConfigNewEnum(const char * name,const int defaultValue,const int minValue,const int maxValue,int (* strToEnum)(const char *),const char * (* enumToStr)(int))256 Config ConfigNewEnum(
257 	const char *name, const int defaultValue,
258 	const int minValue, const int maxValue,
259 	int (*strToEnum)(const char *), const char *(*enumToStr)(int))
260 {
261 	Config c = ConfigNew(name, CONFIG_TYPE_ENUM);
262 	c.u.Enum.Default = c.u.Enum.Value = c.u.Enum.Last = defaultValue;
263 	c.u.Enum.Min = minValue;
264 	c.u.Enum.Max = maxValue;
265 	c.u.Enum.StrToEnum = strToEnum;
266 	c.u.Enum.EnumToStr = enumToStr;
267 	return c;
268 }
ConfigNewGroup(const char * name)269 Config ConfigNewGroup(const char *name)
270 {
271 	Config c = ConfigNew(name, CONFIG_TYPE_GROUP);
272 	CArrayInit(&c.u.Group, sizeof(Config));
273 	return c;
274 }
ConfigNew(const char * name,const ConfigType type)275 static Config ConfigNew(const char *name, const ConfigType type)
276 {
277 	Config c;
278 	memset(&c, 0, sizeof c);
279 	if (name != NULL)
280 	{
281 		CSTRDUP(c.Name, name);
282 	}
283 	c.Type = type;
284 	return c;
285 }
286 
ConfigDestroy(Config * c)287 void ConfigDestroy(Config *c)
288 {
289 	CFREE(c->Name);
290 	if (c->Type == CONFIG_TYPE_GROUP)
291 	{
292 		CA_FOREACH(Config, child, c->u.Group)
293 			ConfigDestroy(child);
294 		CA_FOREACH_END()
295 		CArrayTerminate(&c->u.Group);
296 	}
297 }
298 
ConfigGroupAdd(Config * group,Config child)299 void ConfigGroupAdd(Config *group, Config child)
300 {
301 	CASSERT(group->Type == CONFIG_TYPE_GROUP, "Invalid config type");
302 	CArrayPushBack(&group->u.Group, &child);
303 }
304 
ConfigGetVersion(FILE * f)305 int ConfigGetVersion(FILE *f)
306 {
307 	if (ConfigIsOld(f))
308 	{
309 		return 0;
310 	}
311 	rewind(f);
312 	return ConfigGetJSONVersion(f);
313 }
314 
ConfigGet(Config * c,const char * name)315 Config *ConfigGet(Config *c, const char *name)
316 {
317 	char *nameCopy;
318 	CSTRDUP(nameCopy, name);
319 	char *pch = strtok(nameCopy, ".");
320 	while (pch != NULL)
321 	{
322 		if (c->Type != CONFIG_TYPE_GROUP)
323 		{
324 			CASSERT(false, "Invalid config type");
325 			goto bail;
326 		}
327 		bool found = false;
328 		CA_FOREACH(Config, child, c->u.Group)
329 			if (strcmp(child->Name, pch) == 0)
330 			{
331 				c = child;
332 				found = true;
333 				break;
334 			}
335 		CA_FOREACH_END()
336 		if (!found)
337 		{
338 			CASSERT(false, "Config not found");
339 			goto bail;
340 		}
341 		pch = strtok(NULL, ".");
342 	}
343 bail:
344 	CFREE(nameCopy);
345 	return c;
346 }
347 
ConfigChanged(const Config * c)348 bool ConfigChanged(const Config *c)
349 {
350 	switch (c->Type)
351 	{
352 	case CONFIG_TYPE_STRING:
353 		return strcmp(c->u.String.Value, c->u.String.Last) != 0;
354 	case CONFIG_TYPE_INT:
355 		return c->u.Int.Value != c->u.Int.Last;
356 	case CONFIG_TYPE_FLOAT:
357 		return c->u.Float.Value != c->u.Float.Last;
358 	case CONFIG_TYPE_BOOL:
359 		return c->u.Bool.Value != c->u.Bool.Last;
360 	case CONFIG_TYPE_ENUM:
361 		return c->u.Enum.Value != c->u.Enum.Last;
362 	case CONFIG_TYPE_GROUP:
363 		CA_FOREACH(Config, child, c->u.Group)
364 			if (ConfigChanged(child))
365 			{
366 				return true;
367 			}
368 		CA_FOREACH_END()
369 		return false;
370 	default:
371 		CASSERT(false, "Unknown config type");
372 		return false;
373 	}
374 }
375 
ConfigResetChanged(Config * c)376 void ConfigResetChanged(Config *c)
377 {
378 	switch (c->Type)
379 	{
380 	case CONFIG_TYPE_STRING:
381 		CFREE(c->u.String.Value);
382 		if (c->u.String.Last != NULL)
383 		{
384 			CSTRDUP(c->u.String.Value, c->u.String.Last);
385 		}
386 		break;
387 	case CONFIG_TYPE_INT:
388 		c->u.Int.Value = c->u.Int.Last;
389 		break;
390 	case CONFIG_TYPE_FLOAT:
391 		c->u.Float.Value = c->u.Float.Last;
392 		break;
393 	case CONFIG_TYPE_BOOL:
394 		c->u.Bool.Value = c->u.Bool.Last;
395 		break;
396 	case CONFIG_TYPE_ENUM:
397 		c->u.Enum.Value = c->u.Enum.Last;
398 		break;
399 	case CONFIG_TYPE_GROUP:
400 		CA_FOREACH(Config, child, c->u.Group)
401 			ConfigResetChanged(child);
402 		CA_FOREACH_END()
403 		break;
404 	default:
405 		CASSERT(false, "Unknown config type");
406 		break;
407 	}
408 }
409 
ConfigSetChanged(Config * c)410 void ConfigSetChanged(Config *c)
411 {
412 	switch (c->Type)
413 	{
414 	case CONFIG_TYPE_STRING:
415 		CFREE(c->u.String.Last);
416 		if (c->u.String.Value != NULL)
417 		{
418 			CSTRDUP(c->u.String.Last, c->u.String.Value);
419 		}
420 		break;
421 	case CONFIG_TYPE_INT:
422 		c->u.Int.Last = c->u.Int.Value;
423 		break;
424 	case CONFIG_TYPE_FLOAT:
425 		c->u.Float.Last = c->u.Float.Value;
426 		break;
427 	case CONFIG_TYPE_BOOL:
428 		c->u.Bool.Last = c->u.Bool.Value;
429 		break;
430 	case CONFIG_TYPE_ENUM:
431 		c->u.Enum.Last = c->u.Enum.Value;
432 		break;
433 	case CONFIG_TYPE_GROUP:
434 		CA_FOREACH(Config, child, c->u.Group)
435 			ConfigSetChanged(child);
436 		CA_FOREACH_END()
437 		break;
438 	default:
439 		CASSERT(false, "Unknown config type");
440 		break;
441 	}
442 }
443 
ConfigResetDefault(Config * c)444 void ConfigResetDefault(Config *c)
445 {
446 	switch (c->Type)
447 	{
448 	case CONFIG_TYPE_STRING:
449 		CFREE(c->u.String.Value);
450 		if (c->u.String.Default != NULL)
451 		{
452 			CSTRDUP(c->u.String.Value, c->u.String.Default);
453 		}
454 		break;
455 	case CONFIG_TYPE_INT:
456 		c->u.Int.Value = c->u.Int.Default;
457 		break;
458 	case CONFIG_TYPE_FLOAT:
459 		c->u.Float.Value = c->u.Float.Default;
460 		break;
461 	case CONFIG_TYPE_BOOL:
462 		c->u.Bool.Value = c->u.Bool.Default;
463 		break;
464 	case CONFIG_TYPE_ENUM:
465 		c->u.Enum.Value = c->u.Enum.Default;
466 		break;
467 	case CONFIG_TYPE_GROUP:
468 		CA_FOREACH(Config, child, c->u.Group)
469 			ConfigResetDefault(child);
470 		CA_FOREACH_END()
471 		break;
472 	default:
473 		CASSERT(false, "Unknown config type");
474 		break;
475 	}
476 }
477 
ConfigGetString(Config * c,const char * name)478 const char *ConfigGetString(Config *c, const char *name)
479 {
480 	c = ConfigGet(c, name);
481 	CASSERT(c->Type == CONFIG_TYPE_STRING, "wrong config type");
482 	return c->u.String.Value;
483 }
ConfigGetInt(Config * c,const char * name)484 int ConfigGetInt(Config *c, const char *name)
485 {
486 	c = ConfigGet(c, name);
487 	CASSERT(c->Type == CONFIG_TYPE_INT, "wrong config type");
488 	return c->u.Int.Value;
489 }
ConfigGetFloat(Config * c,const char * name)490 double ConfigGetFloat(Config *c, const char *name)
491 {
492 	c = ConfigGet(c, name);
493 	CASSERT(c->Type == CONFIG_TYPE_FLOAT, "wrong config type");
494 	return c->u.Float.Value;
495 }
ConfigGetBool(Config * c,const char * name)496 bool ConfigGetBool(Config *c, const char *name)
497 {
498 	c = ConfigGet(c, name);
499 	CASSERT(c->Type == CONFIG_TYPE_BOOL, "wrong config type");
500 	return c->u.Bool.Value;
501 }
ConfigGetEnum(Config * c,const char * name)502 int ConfigGetEnum(Config *c, const char *name)
503 {
504 	c = ConfigGet(c, name);
505 	CASSERT(c->Type == CONFIG_TYPE_ENUM, "wrong config type");
506 	return c->u.Enum.Value;
507 }
ConfigGetGroup(Config * c,const char * name)508 CArray *ConfigGetGroup(Config *c, const char *name)
509 {
510 	c = ConfigGet(c, name);
511 	CASSERT(c->Type == CONFIG_TYPE_GROUP, "wrong config type");
512 	return &c->u.Group;
513 }
514 
ConfigSetInt(Config * c,const char * name,const int value)515 void ConfigSetInt(Config *c, const char *name, const int value)
516 {
517 	c = ConfigGet(c, name);
518 	CASSERT(c->Type == CONFIG_TYPE_INT, "wrong config type");
519 	c->u.Int.Value = CLAMP(value, c->u.Int.Min, c->u.Int.Max);
520 }
521 
ConfigSetFloat(Config * c,const char * name,const double value)522 void ConfigSetFloat(Config *c, const char *name, const double value)
523 {
524 	c = ConfigGet(c, name);
525 	CASSERT(c->Type == CONFIG_TYPE_FLOAT, "wrong config type");
526 	c->u.Float.Value = CLAMP(value, c->u.Float.Min, c->u.Float.Max);
527 }
528 
ConfigTrySetFromString(Config * c,const char * name,const char * value)529 bool ConfigTrySetFromString(Config *c, const char *name, const char *value)
530 {
531 	Config *child = ConfigGet(c, name);
532 	switch (child->Type)
533 	{
534 	case CONFIG_TYPE_STRING:
535 		CASSERT(false, "unimplemented");
536 		return false;
537 	case CONFIG_TYPE_INT:
538 		ConfigSetInt(c, name, atoi(value));
539 		return true;
540 	case CONFIG_TYPE_FLOAT:
541 		ConfigSetFloat(c, name, atof(value));
542 		return true;
543 	case CONFIG_TYPE_BOOL:
544 		child->u.Bool.Value = strcmp(value, "true") == 0;
545 		return false;
546 	case CONFIG_TYPE_ENUM:
547 		CASSERT(false, "unimplemented");
548 		return false;
549 	case CONFIG_TYPE_GROUP:
550 		CASSERT(false, "Cannot set group config");
551 		return false;
552 	default:
553 		CASSERT(false, "Unknown config type");
554 		return false;
555 	}
556 }
557 
558 
ConfigDefault(void)559 Config ConfigDefault(void)
560 {
561 	Config root = ConfigNewGroup(NULL);
562 
563 	Config game = ConfigNewGroup("Game");
564 	ConfigGroupAdd(&game, ConfigNewBool("FriendlyFire", false));
565 	ConfigGroupAdd(&game,
566 		ConfigNewInt("RandomSeed", 0, 0, INT_MAX, 1, NULL, NULL));
567 	ConfigGroupAdd(&game, ConfigNewEnum(
568 		"Difficulty", DIFFICULTY_NORMAL,
569 		DIFFICULTY_VERYEASY, DIFFICULTY_VERYHARD,
570 		StrDifficulty, DifficultyStr));
571 	ConfigGroupAdd(&game, ConfigNewInt("FPS", 70, 10, 120, 10, NULL, NULL));
572 	ConfigGroupAdd(&game, ConfigNewBool("Superhot(tm)Mode", false));
573 	ConfigGroupAdd(&game,
574 		ConfigNewInt("EnemyDensity", 100, 25, 200, 25, NULL, PercentStr));
575 	ConfigGroupAdd(&game,
576 		ConfigNewInt("NonPlayerHP", 100, 25, 200, 25, NULL, PercentStr));
577 	ConfigGroupAdd(&game,
578 		ConfigNewInt("PlayerHP", 75, 25, 200, 25, NULL, PercentStr));
579 	ConfigGroupAdd(&game,
580 		ConfigNewInt("Lives", 2, 1, 5, 1, NULL, NULL));
581 	ConfigGroupAdd(&game, ConfigNewBool("HealthPickups", true));
582 	ConfigGroupAdd(&game, ConfigNewBool("Fog", true));
583 	ConfigGroupAdd(&game,
584 		ConfigNewInt("SightRange", 15, 8, 40, 1, NULL, NULL));
585 	ConfigGroupAdd(&game, ConfigNewEnum(
586 		"FireMoveStyle", FIREMOVE_STOP, FIREMOVE_STOP, FIREMOVE_STRAFE,
587 		StrFireMoveStyle, FireMoveStyleStr));
588 	ConfigGroupAdd(&game, ConfigNewEnum(
589 		"SwitchMoveStyle", SWITCHMOVE_SLIDE,
590 		SWITCHMOVE_SLIDE, SWITCHMOVE_NONE,
591 		StrSwitchMoveStyle, SwitchMoveStyleStr));
592 	ConfigGroupAdd(&game, ConfigNewEnum(
593 		"AllyCollision", ALLYCOLLISION_REPEL,
594 		ALLYCOLLISION_NORMAL, ALLYCOLLISION_NONE,
595 		StrAllyCollision, AllyCollisionStr));
596 	ConfigGroupAdd(&game, ConfigNewEnum(
597 		"LaserSight", LASER_SIGHT_NONE, LASER_SIGHT_NONE, LASER_SIGHT_ALL,
598 		StrLaserSight, LaserSightStr));
599 	ConfigGroupAdd(&root, game);
600 
601 	Config dm = ConfigNewGroup("Deathmatch");
602 	ConfigGroupAdd(&dm, ConfigNewInt("Lives", 10, 1, 20, 1, NULL, NULL));
603 	ConfigGroupAdd(&root, dm);
604 
605 	Config df = ConfigNewGroup("Dogfight");
606 	ConfigGroupAdd(&df,
607 		ConfigNewInt("PlayerHP", 100, 25, 200, 25, NULL, PercentStr));
608 	ConfigGroupAdd(&df, ConfigNewInt("FirstTo", 5, 1, 10, 1, NULL, NULL));
609 	ConfigGroupAdd(&root, df);
610 
611 	Config gfx = ConfigNewGroup("Graphics");
612 	ConfigGroupAdd(&gfx, ConfigNewInt(
613 		"Brightness", 0, BLIT_BRIGHTNESS_MIN, BLIT_BRIGHTNESS_MAX, 1,
614 		NULL, NULL));
615 
616 	ConfigGroupAdd(&gfx, ConfigNewBool("Fullscreen",
617 #ifdef __GCWZERO__
618 		true
619 #else
620 		false
621 #endif
622 		));
623 
624 	ConfigGroupAdd(&gfx,
625 		ConfigNewInt("WindowWidth", 640, 320, 4096, 0, NULL, NULL));
626 	ConfigGroupAdd(&gfx,
627 		ConfigNewInt("WindowHeight", 480, 200, 2160, 0, NULL, NULL));
628 	ConfigGroupAdd(&gfx, ConfigNewInt("ScaleFactor",
629 #ifdef __GCWZERO__
630 		1
631 #else
632 		2
633 #endif
634 		, 1, 16, 1, NULL, NULL));
635 	ConfigGroupAdd(&gfx,
636 		ConfigNewInt("ShakeMultiplier", 1, 0, 10, 1, NULL, NULL));
637 	ConfigGroupAdd(&gfx, ConfigNewBool("ShowHUD", true));
638 	ConfigGroupAdd(&gfx, ConfigNewEnum(
639 		"ScaleMode", SCALE_MODE_NN, SCALE_MODE_NN, SCALE_MODE_BILINEAR,
640 		StrScaleMode, ScaleModeStr));
641 	ConfigGroupAdd(&gfx, ConfigNewBool("Shadows", true));
642 	ConfigGroupAdd(&gfx, ConfigNewEnum(
643 		"Gore", GORE_LOW, GORE_NONE, GORE_HIGH, StrGoreAmount, GoreAmountStr));
644 	ConfigGroupAdd(&gfx, ConfigNewBool("Brass", true));
645 	ConfigGroupAdd(&gfx, ConfigNewBool("SecondWindow", false));
646 	ConfigGroupAdd(&root, gfx);
647 
648 	Config input = ConfigNewGroup("Input");
649 	Config pk0 = ConfigNewGroup("PlayerCodes0");
650 	ConfigGroupAdd(&pk0, ConfigNewInt("left", SDL_SCANCODE_LEFT, 0, 0, 0, NULL, NULL));
651 	ConfigGroupAdd(&pk0, ConfigNewInt("right", SDL_SCANCODE_RIGHT, 0, 0, 0, NULL, NULL));
652 	ConfigGroupAdd(&pk0, ConfigNewInt("up", SDL_SCANCODE_UP, 0, 0, 0, NULL, NULL));
653 	ConfigGroupAdd(&pk0, ConfigNewInt("down", SDL_SCANCODE_DOWN, 0, 0, 0, NULL, NULL));
654 #ifdef __GCWZERO__
655 	ConfigGroupAdd(&pk0, ConfigNewInt("button1", SDL_SCANCODE_LCTRL, 0, 0, 0, NULL, NULL));
656 	ConfigGroupAdd(&pk0, ConfigNewInt("button2", SDL_SCANCODE_LALT, 0, 0, 0, NULL, NULL));
657 	ConfigGroupAdd(&pk0, ConfigNewInt("grenade", SDL_SCANCODE_BACKSPACE, 0, 0, 0, NULL, NULL));
658 	ConfigGroupAdd(&pk0, ConfigNewInt("map", SDL_SCANCODE_TAB, 0, 0, 0, NULL, NULL));
659 #else
660 	ConfigGroupAdd(&pk0, ConfigNewInt("button1", SDL_SCANCODE_X, 0, 0, 0, NULL, NULL));
661 	ConfigGroupAdd(&pk0, ConfigNewInt("button2", SDL_SCANCODE_Z, 0, 0, 0, NULL, NULL));
662 	ConfigGroupAdd(&pk0, ConfigNewInt("grenade", SDL_SCANCODE_S, 0, 0, 0, NULL, NULL));
663 	ConfigGroupAdd(&pk0, ConfigNewInt("map", SDL_SCANCODE_A, 0, 0, 0, NULL, NULL));
664 #endif
665 	ConfigGroupAdd(&input, pk0);
666 	Config pk1 = ConfigNewGroup("PlayerCodes1");
667 	ConfigGroupAdd(&pk1, ConfigNewInt("left", SDL_SCANCODE_KP_4, 0, 0, 0, NULL, NULL));
668 	ConfigGroupAdd(&pk1, ConfigNewInt("right", SDL_SCANCODE_KP_6, 0, 0, 0, NULL, NULL));
669 	ConfigGroupAdd(&pk1, ConfigNewInt("up", SDL_SCANCODE_KP_8, 0, 0, 0, NULL, NULL));
670 	ConfigGroupAdd(&pk1, ConfigNewInt("down", SDL_SCANCODE_KP_2, 0, 0, 0, NULL, NULL));
671 	ConfigGroupAdd(&pk1, ConfigNewInt("button1", SDL_SCANCODE_KP_ENTER, 0, 0, 0, NULL, NULL));
672 	ConfigGroupAdd(&pk1, ConfigNewInt("button2", SDL_SCANCODE_KP_PERIOD, 0, 0, 0, NULL, NULL));
673 	ConfigGroupAdd(&pk1, ConfigNewInt("grenade", SDL_SCANCODE_KP_3, 0, 0, 0, NULL, NULL));
674 	ConfigGroupAdd(&pk1, ConfigNewInt("map", SDL_SCANCODE_KP_0, 0, 0, 0, NULL, NULL));
675 	ConfigGroupAdd(&input, pk1);
676 	ConfigGroupAdd(&root, input);
677 
678 	Config itf = ConfigNewGroup("Interface");
679 	ConfigGroupAdd(&itf, ConfigNewBool("ShowFPS", false));
680 	ConfigGroupAdd(&itf, ConfigNewBool("ShowTime", false));
681 	ConfigGroupAdd(&itf, ConfigNewBool("ShowHUDMap", true));
682 	ConfigGroupAdd(&itf, ConfigNewEnum(
683 		"AIChatter", AICHATTER_SELDOM, AICHATTER_NONE, AICHATTER_ALWAYS,
684 		StrAIChatter, AIChatterStr));
685 	ConfigGroupAdd(&itf, ConfigNewEnum(
686 		"Splitscreen", SPLITSCREEN_NEVER,
687 		SPLITSCREEN_NORMAL, SPLITSCREEN_NEVER,
688 		StrSplitscreenStyle, SplitscreenStyleStr));
689 	ConfigGroupAdd(&itf, ConfigNewBool("SplitscreenAI", false));
690 	ConfigGroupAdd(&root, itf);
691 
692 	Config snd = ConfigNewGroup("Sound");
693 	ConfigGroupAdd(&snd,
694 		ConfigNewInt("MusicVolume", 32, 0, 64, 8, NULL, Div8Str));
695 	ConfigGroupAdd(&snd,
696 		ConfigNewInt("SoundVolume", 64, 0, 64, 8, NULL, Div8Str));
697 	ConfigGroupAdd(&snd, ConfigNewBool("Footsteps", true));
698 	ConfigGroupAdd(&snd, ConfigNewBool("Reloads", true));
699 	ConfigGroupAdd(&root, snd);
700 
701 	Config qp = ConfigNewGroup("QuickPlay");
702 	ConfigGroupAdd(&qp, ConfigNewEnum(
703 		"MapSize", QUICKPLAY_QUANTITY_ANY,
704 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
705 		StrQuickPlayQuantity, QuickPlayQuantityStr));
706 	ConfigGroupAdd(&qp, ConfigNewEnum(
707 		"WallCount", QUICKPLAY_QUANTITY_ANY,
708 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
709 		StrQuickPlayQuantity, QuickPlayQuantityStr));
710 	ConfigGroupAdd(&qp, ConfigNewEnum(
711 		"WallLength", QUICKPLAY_QUANTITY_ANY,
712 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
713 		StrQuickPlayQuantity, QuickPlayQuantityStr));
714 	ConfigGroupAdd(&qp, ConfigNewEnum(
715 		"RoomCount", QUICKPLAY_QUANTITY_ANY,
716 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
717 		StrQuickPlayQuantity, QuickPlayQuantityStr));
718 	ConfigGroupAdd(&qp, ConfigNewEnum(
719 		"SquareCount", QUICKPLAY_QUANTITY_ANY,
720 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
721 		StrQuickPlayQuantity, QuickPlayQuantityStr));
722 	ConfigGroupAdd(&qp, ConfigNewEnum(
723 		"EnemyCount", QUICKPLAY_QUANTITY_ANY,
724 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
725 		StrQuickPlayQuantity, QuickPlayQuantityStr));
726 	ConfigGroupAdd(&qp, ConfigNewEnum(
727 		"EnemySpeed", QUICKPLAY_QUANTITY_ANY,
728 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
729 		StrQuickPlayQuantity, QuickPlayQuantityStr));
730 	ConfigGroupAdd(&qp, ConfigNewEnum(
731 		"EnemyHealth", QUICKPLAY_QUANTITY_ANY,
732 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
733 		StrQuickPlayQuantity, QuickPlayQuantityStr));
734 	ConfigGroupAdd(&qp, ConfigNewBool("EnemiesWithExplosives", true));
735 	ConfigGroupAdd(&qp, ConfigNewEnum(
736 		"ItemCount", QUICKPLAY_QUANTITY_ANY,
737 		QUICKPLAY_QUANTITY_ANY, QUICKPLAY_QUANTITY_LARGE,
738 		StrQuickPlayQuantity, QuickPlayQuantityStr));
739 	ConfigGroupAdd(&root, qp);
740 
741 	ConfigGroupAdd(&root, ConfigNewBool("StartServer", false));
742 
743 	return root;
744 }
745