1 #include "pch.h"
2 #include "options.h"
3 
4 #include "fullscrn.h"
5 #include "midi.h"
6 #include "render.h"
7 #include "Sound.h"
8 #include "winmain.h"
9 
10 constexpr int options::MaxUps, options::MaxFps, options::MinUps, options::MinFps, options::DefUps, options::DefFps;
11 constexpr int options::MaxSoundChannels, options::MinSoundChannels, options::DefSoundChannels;
12 
13 optionsStruct options::Options{};
14 std::map<std::string, std::string> options::settings{};
15 ControlsStruct options::RebindControls{};
16 bool options::ShowDialog = false;
17 GameInput* options::ControlWaitingForInput = nullptr;
18 const ControlRef options::Controls[6]
19 {
20 	{"Left Flipper", RebindControls.LeftFlipper},
21 	{"Right Flipper", RebindControls.RightFlipper},
22 	{"Left Table Bump", RebindControls.LeftTableBump},
23 	{"Right Table Bump", RebindControls.RightTableBump},
24 	{"Bottom Table Bump", RebindControls.BottomTableBump},
25 	{"Plunger", RebindControls.Plunger},
26 };
27 
28 
init()29 void options::init()
30 {
31 	auto imContext = ImGui::GetCurrentContext();
32 	ImGuiSettingsHandler ini_handler;
33 	ini_handler.TypeName = "Pinball";
34 	ini_handler.TypeHash = ImHashStr(ini_handler.TypeName);
35 	ini_handler.ReadOpenFn = MyUserData_ReadOpen;
36 	ini_handler.ReadLineFn = MyUserData_ReadLine;
37 	ini_handler.WriteAllFn = MyUserData_WriteAll;
38 	imContext->SettingsHandlers.push_back(ini_handler);
39 
40 	// Settings are loaded from disk on the first frame
41 	if (!imContext->SettingsLoaded)
42 	{
43 		ImGui::NewFrame();
44 		ImGui::EndFrame();
45 	}
46 
47 	Options.Key = Options.KeyDft =
48 	{
49 		{
50 			{InputTypes::Keyboard, SDLK_z},
51 			{InputTypes::Mouse, SDL_BUTTON_LEFT},
52 			{InputTypes::GameController, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
53 		},
54 		{
55 			{InputTypes::Keyboard, SDLK_SLASH},
56 			{InputTypes::Mouse, SDL_BUTTON_RIGHT},
57 			{InputTypes::GameController, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
58 		},
59 		{
60 			{InputTypes::Keyboard, SDLK_SPACE},
61 			{InputTypes::Mouse, SDL_BUTTON_MIDDLE},
62 			{InputTypes::GameController, SDL_CONTROLLER_BUTTON_A},
63 		},
64 		{
65 			{InputTypes::Keyboard, SDLK_x},
66 			{InputTypes::Mouse, SDL_BUTTON_X1},
67 			{InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
68 		},
69 		{
70 			{InputTypes::Keyboard, SDLK_PERIOD},
71 			{InputTypes::Mouse, SDL_BUTTON_X2},
72 			{InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
73 		},
74 		{
75 			{InputTypes::Keyboard, SDLK_UP},
76 			{InputTypes::Mouse, SDL_BUTTON_X2 + 1},
77 			{InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_UP},
78 		},
79 	};
80 	GetInput("Left Flipper key", Options.Key.LeftFlipper);
81 	GetInput("Right Flipper key", Options.Key.RightFlipper);
82 	GetInput("Plunger key", Options.Key.Plunger);
83 	GetInput("Left Table Bump key", Options.Key.LeftTableBump);
84 	GetInput("Right Table Bump key", Options.Key.RightTableBump);
85 	GetInput("Bottom Table Bump key", Options.Key.BottomTableBump);
86 
87 	Options.Sounds = get_int("Sounds", true);
88 	Options.Music = get_int("Music", false);
89 	Options.FullScreen = get_int("FullScreen", false);
90 	Options.Players = get_int("Players", 1);
91 	Options.UniformScaling = get_int("Uniform scaling", true);
92 	ImGui::GetIO().FontGlobalScale = get_float("UI Scale", 1.0f);
93 	Options.Resolution = get_int("Screen Resolution", -1);
94 	Options.LinearFiltering = get_int("Linear Filtering", true);
95 	Options.FramesPerSecond = std::min(MaxFps, std::max(MinUps, get_int("Frames Per Second", DefFps)));
96 	Options.UpdatesPerSecond = std::min(MaxUps, std::max(MinUps, get_int("Updates Per Second", DefUps)));
97 	Options.UpdatesPerSecond = std::max(Options.UpdatesPerSecond, Options.FramesPerSecond);
98 	Options.ShowMenu = get_int("ShowMenu", true);
99 	Options.UncappedUpdatesPerSecond = get_int("Uncapped Updates Per Second", false);
100 	Options.SoundChannels = get_int("Sound Channels", DefSoundChannels);
101 	Options.SoundChannels = std::min(MaxSoundChannels, std::max(MinSoundChannels, Options.SoundChannels));
102 
103 	winmain::UpdateFrameRate();
104 
105 	auto maxRes = fullscrn::GetMaxResolution();
106 	if (Options.Resolution >= 0 && Options.Resolution > maxRes)
107 		Options.Resolution = maxRes;
108 	fullscrn::SetResolution(Options.Resolution == -1 ? maxRes : Options.Resolution);
109 }
110 
uninit()111 void options::uninit()
112 {
113 	SetInput("Left Flipper key", Options.Key.LeftFlipper);
114 	SetInput("Right Flipper key", Options.Key.RightFlipper);
115 	SetInput("Plunger key", Options.Key.Plunger);
116 	SetInput("Left Table Bump key", Options.Key.LeftTableBump);
117 	SetInput("Right Table Bump key", Options.Key.RightTableBump);
118 	SetInput("Bottom Table Bump key", Options.Key.BottomTableBump);
119 
120 	set_int("Sounds", Options.Sounds);
121 	set_int("Music", Options.Music);
122 	set_int("FullScreen", Options.FullScreen);
123 	set_int("Players", Options.Players);
124 	set_int("Screen Resolution", Options.Resolution);
125 	set_int("Uniform scaling", Options.UniformScaling);
126 	set_float("UI Scale", ImGui::GetIO().FontGlobalScale);
127 	set_int("Linear Filtering", Options.LinearFiltering);
128 	set_int("Frames Per Second", Options.FramesPerSecond);
129 	set_int("Updates Per Second", Options.UpdatesPerSecond);
130 	set_int("ShowMenu", Options.ShowMenu);
131 	set_int("Uncapped Updates Per Second", Options.UncappedUpdatesPerSecond);
132 	set_int("Sound Channels", Options.SoundChannels);
133 }
134 
135 
get_int(LPCSTR lpValueName,int defaultValue)136 int options::get_int(LPCSTR lpValueName, int defaultValue)
137 {
138 	auto value = GetSetting(lpValueName, std::to_string(defaultValue));
139 	return std::stoi(value);
140 }
141 
set_int(LPCSTR lpValueName,int data)142 void options::set_int(LPCSTR lpValueName, int data)
143 {
144 	SetSetting(lpValueName, std::to_string(data));
145 }
146 
get_string(LPCSTR lpValueName,LPCSTR defaultValue)147 std::string options::get_string(LPCSTR lpValueName, LPCSTR defaultValue)
148 {
149 	return GetSetting(lpValueName, defaultValue);
150 }
151 
set_string(LPCSTR lpValueName,LPCSTR value)152 void options::set_string(LPCSTR lpValueName, LPCSTR value)
153 {
154 	SetSetting(lpValueName, value);
155 }
156 
get_float(LPCSTR lpValueName,float defaultValue)157 float options::get_float(LPCSTR lpValueName, float defaultValue)
158 {
159 	auto value = GetSetting(lpValueName, std::to_string(defaultValue));
160 	return std::stof(value);
161 }
162 
set_float(LPCSTR lpValueName,float data)163 void options::set_float(LPCSTR lpValueName, float data)
164 {
165 	SetSetting(lpValueName, std::to_string(data));
166 }
167 
GetInput(const std::string & rowName,GameInput (& defaultValues)[3])168 void options::GetInput(const std::string& rowName, GameInput (&defaultValues)[3])
169 {
170 	for (auto i = 0u; i <= 2; i++)
171 	{
172 		auto name = rowName + " " + std::to_string(i);
173 		auto inputType = static_cast<InputTypes>(get_int((name + " type").c_str(), -1));
174 		auto input = get_int((name + " input").c_str(), -1);
175 		if (inputType <= InputTypes::GameController && input != -1)
176 			defaultValues[i] = {inputType, input};
177 	}
178 }
179 
SetInput(const std::string & rowName,GameInput (& values)[3])180 void options::SetInput(const std::string& rowName, GameInput (&values)[3])
181 {
182 	for (auto i = 0u; i <= 2; i++)
183 	{
184 		auto input = values[i];
185 		auto name = rowName + " " + std::to_string(i);
186 		set_int((name + " type").c_str(), static_cast<int>(input.Type));
187 		set_int((name + " input").c_str(), input.Value);
188 	}
189 }
190 
toggle(Menu1 uIDCheckItem)191 void options::toggle(Menu1 uIDCheckItem)
192 {
193 	switch (uIDCheckItem)
194 	{
195 	case Menu1::Sounds:
196 		Options.Sounds ^= true;
197 		Sound::Enable(Options.Sounds);
198 		return;
199 	case Menu1::Music:
200 		Options.Music ^= true;
201 		if (!Options.Music)
202 			midi::music_stop();
203 		else
204 			midi::play_pb_theme();
205 		return;
206 	case Menu1::Show_Menu:
207 		Options.ShowMenu = Options.ShowMenu == 0;
208 		fullscrn::window_size_changed();
209 		return;
210 	case Menu1::Full_Screen:
211 		Options.FullScreen ^= true;
212 		fullscrn::set_screen_mode(Options.FullScreen);
213 		return;
214 	case Menu1::OnePlayer:
215 	case Menu1::TwoPlayers:
216 	case Menu1::ThreePlayers:
217 	case Menu1::FourPlayers:
218 		Options.Players = static_cast<int>(uIDCheckItem) - static_cast<int>(Menu1::OnePlayer) + 1;
219 		break;
220 	case Menu1::MaximumResolution:
221 	case Menu1::R640x480:
222 	case Menu1::R800x600:
223 	case Menu1::R1024x768:
224 		{
225 			auto restart = false;
226 			int newResolution = static_cast<int>(uIDCheckItem) - static_cast<int>(Menu1::R640x480);
227 			if (uIDCheckItem == Menu1::MaximumResolution)
228 			{
229 				restart = fullscrn::GetResolution() != fullscrn::GetMaxResolution();
230 				Options.Resolution = -1;
231 			}
232 			else if (newResolution <= fullscrn::GetMaxResolution())
233 			{
234 				restart = newResolution != (Options.Resolution == -1
235 					                            ? fullscrn::GetMaxResolution()
236 					                            : fullscrn::GetResolution());
237 				Options.Resolution = newResolution;
238 			}
239 
240 			if (restart)
241 				winmain::Restart();
242 			break;
243 		}
244 	case Menu1::WindowUniformScale:
245 		Options.UniformScaling ^= true;
246 		fullscrn::window_size_changed();
247 		break;
248 	case Menu1::WindowLinearFilter:
249 		Options.LinearFiltering ^= true;
250 		render::recreate_screen_texture();
251 		break;
252 	default:
253 		break;
254 	}
255 }
256 
InputDown(GameInput input)257 void options::InputDown(GameInput input)
258 {
259 	if (ControlWaitingForInput)
260 	{
261 		// Skip function keys, just in case.
262 		if (input.Type == InputTypes::Keyboard && input.Value >= SDLK_F1 && input.Value <= SDLK_F12)
263 			return;
264 
265 		// Start is reserved for pause
266 		if (input.Type == InputTypes::GameController && input.Value == SDL_CONTROLLER_BUTTON_START)
267 			return;
268 
269 		*ControlWaitingForInput = input;
270 		ControlWaitingForInput = nullptr;
271 	}
272 }
273 
ShowControlDialog()274 void options::ShowControlDialog()
275 {
276 	if (!ShowDialog)
277 	{
278 		ControlWaitingForInput = nullptr;
279 		RebindControls = Options.Key;
280 		ShowDialog = true;
281 	}
282 }
283 
RenderControlDialog()284 void options::RenderControlDialog()
285 {
286 	static const char* mouseButtons[]
287 	{
288 		nullptr,
289 		"Mouse Left",
290 		"Mouse Middle",
291 		"Mouse Right",
292 		"Mouse X1",
293 		"Mouse X2",
294 	};
295 
296 	if (!ShowDialog)
297 		return;
298 
299 	ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{550, 450});
300 	if (ImGui::Begin("3D Pinball: Player Controls", &ShowDialog))
301 	{
302 		ImGui::TextUnformatted("Instructions");
303 		ImGui::Separator();
304 
305 		ImGui::TextWrapped(
306 			"To change game controls, click the control button, press the new key, and then choose OK.");
307 		ImGui::TextWrapped(
308 			"To restore 3D Pinball to its original settings, choose Default, and then choose OK.");
309 		ImGui::Spacing();
310 
311 		ImGui::TextUnformatted("Control Options");
312 
313 		ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{5, 10});
314 		if (ImGui::BeginTable("Controls", 4, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))
315 		{
316 			ImGui::TableSetupColumn("Control");
317 			ImGui::TableSetupColumn("Binding 1");
318 			ImGui::TableSetupColumn("Binding 2");
319 			ImGui::TableSetupColumn("Binding 3");
320 			ImGui::TableHeadersRow();
321 
322 			int index = 0;
323 			for (auto& row : Controls)
324 			{
325 				ImGui::TableNextColumn();
326 				ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5, 0, 0, 1});
327 				if (ImGui::Button(row.Name))
328 				{
329 					for (auto i = 0u; i <= 2; i++)
330 						row.Option[i] = {};
331 				}
332 				ImGui::PopStyleColor(1);
333 
334 				for (auto i = 0u; i <= 2; i++)
335 				{
336 					auto& ctrl = row.Option[i];
337 					ImGui::TableNextColumn();
338 					if (ControlWaitingForInput == &ctrl)
339 					{
340 						ImGui::Button("Press the key", ImVec2(-1, 0));
341 					}
342 					else
343 					{
344 						std::string tmp;
345 						const char* keyName;
346 						switch (ctrl.Type)
347 						{
348 						case InputTypes::Keyboard:
349 							keyName = SDL_GetKeyName(ctrl.Value);
350 							break;
351 						case InputTypes::Mouse:
352 							if (ctrl.Value >= SDL_BUTTON_LEFT && ctrl.Value <= SDL_BUTTON_X2)
353 								keyName = mouseButtons[ctrl.Value];
354 							else
355 								keyName = (tmp += "Mouse " + std::to_string(ctrl.Value)).c_str();
356 							break;
357 						case InputTypes::GameController:
358 							keyName = SDL_GameControllerGetStringForButton(
359 								static_cast<SDL_GameControllerButton>(ctrl.Value));
360 							break;
361 						case InputTypes::None:
362 						default:
363 							keyName = "Unused";
364 						}
365 						if (!keyName || !keyName[0])
366 							keyName = "Unknown key";
367 						if (ImGui::Button((std::string{keyName} + "##" + std::to_string(index++)).c_str(),
368 						                  ImVec2(-1, 0)))
369 						{
370 							ControlWaitingForInput = &ctrl;
371 						}
372 					}
373 				}
374 			}
375 			ImGui::EndTable();
376 		}
377 		ImGui::PopStyleVar();
378 		ImGui::Spacing();
379 
380 		if (ImGui::Button("OK"))
381 		{
382 			Options.Key = RebindControls;
383 			ShowDialog = false;
384 		}
385 
386 		ImGui::SameLine();
387 		if (ImGui::Button("Cancel"))
388 		{
389 			ShowDialog = false;
390 		}
391 
392 		ImGui::SameLine();
393 		if (ImGui::Button("Default"))
394 		{
395 			RebindControls = Options.KeyDft;
396 			ControlWaitingForInput = nullptr;
397 		}
398 	}
399 	ImGui::End();
400 	ImGui::PopStyleVar();
401 
402 	if (!ShowDialog)
403 		ControlWaitingForInput = nullptr;
404 }
405 
MyUserData_ReadLine(ImGuiContext * ctx,ImGuiSettingsHandler * handler,void * entry,const char * line)406 void options::MyUserData_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line)
407 {
408 	auto& keyValueStore = *static_cast<std::map<std::string, std::string>*>(entry);
409 	std::string keyValue = line;
410 	auto separatorPos = keyValue.find('=');
411 	if (separatorPos != std::string::npos)
412 	{
413 		auto key = keyValue.substr(0, separatorPos);
414 		auto value = keyValue.substr(separatorPos + 1, keyValue.size());
415 		keyValueStore[key] = value;
416 	}
417 }
418 
MyUserData_ReadOpen(ImGuiContext * ctx,ImGuiSettingsHandler * handler,const char * name)419 void* options::MyUserData_ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name)
420 {
421 	// There is only one custom entry
422 	return strcmp(name, "Settings") == 0 ? &settings : nullptr;
423 }
424 
MyUserData_WriteAll(ImGuiContext * ctx,ImGuiSettingsHandler * handler,ImGuiTextBuffer * buf)425 void options::MyUserData_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
426 {
427 	buf->appendf("[%s][%s]\n", handler->TypeName, "Settings");
428 	for (const auto& setting : settings)
429 	{
430 		buf->appendf("%s=%s\n", setting.first.c_str(), setting.second.c_str());
431 	}
432 	buf->append("\n");
433 }
434 
GetSetting(const std::string & key,const std::string & value)435 const std::string& options::GetSetting(const std::string& key, const std::string& value)
436 {
437 	auto setting = settings.find(key);
438 	if (setting == settings.end())
439 	{
440 		settings[key] = value;
441 		if (ImGui::GetCurrentContext())
442 			ImGui::MarkIniSettingsDirty();
443 		return value;
444 	}
445 	return setting->second;
446 }
447 
SetSetting(const std::string & key,const std::string & value)448 void options::SetSetting(const std::string& key, const std::string& value)
449 {
450 	settings[key] = value;
451 	if (ImGui::GetCurrentContext())
452 		ImGui::MarkIniSettingsDirty();
453 }
454