1 ////////////////////////////////////////////////////////////////////////////////
2 //    Scorched3D (c) 2000-2011
3 //
4 //    This file is part of Scorched3D.
5 //
6 //    Scorched3D is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    Scorched3D is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License along
17 //    with this program; if not, write to the Free Software Foundation, Inc.,
18 //    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 ////////////////////////////////////////////////////////////////////////////////
20 
21 #include <stdlib.h>
22 #include <wx/wx.h>
23 #include <wx/image.h>
24 #include <wx/process.h>
25 #include <wx/txtstrm.h>
26 #include <wx/msgdlg.h>
27 #include <wx/dcbuffer.h>
28 #include <wx/splash.h>
29 #define WIN32_LEAN_AND_MEAN
30 #include <windows.h>
31 #include <SDL/SDL.h>
32 #include <wxdialogs/MainDialog.h>
33 #include <wxdialogs/DisplayDialog.h>
34 #include <wxdialogs/ServerSDialog.h>
35 #include <wxdialogs/TrueTypeFont.h>
36 #include <scorched/ScorchedParams.h>
37 #include <graph/OptionsDisplay.h>
38 #include <common/Defines.h>
39 
40 extern char scorched3dAppName[128];
41 static wxFrame *mainDialog = 0;
42 bool wxWindowExit = false;
43 
44 enum
45 {
46 	ID_MAIN_TIMER
47 };
48 
convertString(const std::string & input)49 wxString convertString(const std::string &input)
50 {
51 	wxString result(input.c_str(), wxConvUTF8);
52 	return result;
53 }
54 
addTitleToWindow(wxWindow * parent,wxSizer * sizer,const char * fileName,int buttonId)55 void addTitleToWindow(
56 	wxWindow *parent,
57 	wxSizer *sizer,
58 	const char *fileName,
59 	int buttonId)
60 {
61 	wxBitmap scorchedBitmap;
62 	if (scorchedBitmap.LoadFile(wxString(fileName, wxConvUTF8),
63 		wxBITMAP_TYPE_BMP) &&
64 		scorchedBitmap.Ok())
65 	{
66 		wxBitmapButton *button = new wxBitmapButton(
67 			parent, buttonId, scorchedBitmap);
68 		wxBoxSizer *boxSizer = new wxBoxSizer(wxHORIZONTAL);
69 		boxSizer->Add(button, 0, wxALL, 5);
70 		sizer->Add(boxSizer, 0, wxALIGN_CENTER | wxALL, 5);
71 	}
72 }
73 
74 static SDL_mutex *messageMutex_ = 0;
75 static std::string messageString_;
76 static int exitCode_ = 0;
77 
78 class ScorchedProcess : public wxProcess
79 {
80 public:
ScorchedProcess(bool server)81 	ScorchedProcess(bool server) :
82 		wxProcess(!server?wxPROCESS_REDIRECT:0),
83 		server_(server)
84 	{
85 		if (server_) serverProcessesRunning_++;
86 		else clientProcessesRunning_++;
87 	}
88 
OnTerminate(int pid,int status)89 	virtual void OnTerminate(int pid, int status)
90 	{
91 		if (server_) serverProcessesRunning_--;
92 		else clientProcessesRunning_--;
93 
94 		if (status != 0)
95 		{
96 			SDL_LockMutex(messageMutex_);
97 			if (server_) exitCode_ = 64; // So it doesn't say to load failsafe
98 			else exitCode_ = status;
99 
100 			if (status != 64)
101 			{
102 				messageString_ = "The Scorched3d process "
103 					"terminated unexpectedly.\n";
104 			}
105 			else
106 			{
107 				messageString_ = "The Scorched3d process "
108 					"terminated due to configuration errors.\n";
109 			}
110 			while (IsInputAvailable())
111 			{
112 				wxTextInputStream tis(*GetInputStream());
113 				wxString line = tis.ReadLine();
114 				messageString_.append((const char *) line.mb_str(wxConvUTF8));
115 				messageString_.append("\n");
116 			}
117 			SDL_UnlockMutex(messageMutex_);
118 		}
119 		Detach();
120 		wxProcess::OnTerminate(pid, status);
121 	}
122 
getClientProcessesRunning()123 	static unsigned int getClientProcessesRunning()
124 	{
125 		return clientProcessesRunning_;
126 	}
127 
getServerProcessesRunning()128 	static unsigned int getServerProcessesRunning()
129 	{
130 		return serverProcessesRunning_;
131 	}
132 
133 protected:
134 	bool server_;
135 	static unsigned int clientProcessesRunning_;
136 	static unsigned int serverProcessesRunning_;
137 };
138 
139 unsigned int ScorchedProcess::clientProcessesRunning_(0);
140 unsigned int ScorchedProcess::serverProcessesRunning_(0);
141 
runScorched3D(const char * text,bool server)142 void runScorched3D(const char *text, bool server)
143 {
144 	if (!server &&
145 		ScorchedProcess::getClientProcessesRunning() > 0)
146 	{
147 		if (::wxMessageBox(
148 			wxT("You are already running the game, do you wish to run another copy?"),
149 			wxT("Scorched3D"),
150 			wxCANCEL | wxOK | wxICON_EXCLAMATION) == wxCANCEL)
151 		{
152 			return;
153 		}
154 	}
155 
156 	std::string exeName = S3D::getExeName();
157 	const char *exePart = strstr(exeName.c_str(), ".exe");
158 	if (exePart) ((char *)exePart)[0] = '\0';
159 	exePart = strstr(exeName.c_str(), ".EXE");
160 	if (exePart) ((char *)exePart)[0] = '\0';
161 
162 	char path[1024];
163 	snprintf(path, 1024, "\"%s%s%s\" %s -settingsdir %s %s",
164 		exeName.c_str(),
165 		(server?"s":"c"),
166 		(exePart?".exe":""),
167 		(ScorchedParams::instance()->getAllowExceptions()?" -allowexceptions":""),
168 		ScorchedParams::instance()->getSettingsDir(),
169 		text);
170 
171 	ScorchedProcess *process = new ScorchedProcess(server);
172 	long result = ::wxExecute(wxString(path, wxConvUTF8), wxEXEC_ASYNC, process);
173 	if (result == 0)
174 	{
175 		delete process;
176 		S3D::dialogMessage(scorched3dAppName, S3D::formatStringBuffer(
177 			"Error: Failed to execute scorched3d using commandline :-\n"
178 			"%s",
179 			path));
180 	}
181 }
182 
addButtonToWindow(int id,const char * text,const char * bitmapName,wxWindow * parent,wxSizer * sizer,wxObjectRefData * data)183 wxButton *addButtonToWindow(
184 	int id,
185 	const char *text,
186 	const char *bitmapName,
187 	wxWindow *parent,
188 	wxSizer *sizer,
189 	wxObjectRefData *data)
190 {
191 	wxButton *button = 0;
192 	wxBitmap bitmap;
193 	if (bitmap.LoadFile(wxString(bitmapName, wxConvUTF8), wxBITMAP_TYPE_BMP) &&
194 		bitmap.Ok())
195 	{
196 		button = new wxBitmapButton(parent, id, bitmap);
197 	}
198 	else
199 	{
200 		button = new wxButton(parent, id, wxT("Select"));
201 	}
202 	if (data) button->SetRefData(data);
203 
204 	wxStaticText *staticText = new wxStaticText(
205 		parent, -1,
206 		wxString(text, wxConvUTF8));
207 
208 	sizer->Add(button, 0, wxRIGHT, 5);
209 	sizer->Add(staticText, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
210 
211 	return button;
212 }
213 
214 class MainFrame: public wxFrame
215 {
216 public:
217 	MainFrame();
218 
219 	void onTimer(wxTimerEvent &event);
220 	void onPaint(wxPaintEvent& event);
221 	void onMotion(wxMouseEvent &event);
222 	void onEraseBackground(wxEraseEvent &event);
223 
224 	void onDisplayButton();
225 	void onQuitButton();
226 	void onSingleButton();
227 	void onServerButton();
228 	void onDonateClick();
229 	void onHelpButton();
230 
231 private:
232 	DECLARE_EVENT_TABLE()
233 
234 	struct ImageData
235 	{
236 		int x, y;
237 		wxImage loadedImage;
238 		wxImage descriptionImage;
239 		wxBitmap cachedBitmap1;
240 		wxBitmap cachedBitmap2;
241 		wxBitmap cachedDescription;
242 	};
243 
244 	wxTimer timer_;
245 	wxBitmap backdropBitmap_;
246 	wxImage backdropImage_;
247 	std::list<ImageData *> images_;
248 	long mouseX_, mouseY_;
249 	int lastPos_;
250 
251 	void generateCachedImage(int x, int y,
252 		wxImage &src, wxBitmap &destBitamp,
253 		bool highlight = false);
254 };
255 
BEGIN_EVENT_TABLE(MainFrame,wxFrame)256 BEGIN_EVENT_TABLE(MainFrame, wxFrame)
257 	EVT_TIMER(ID_MAIN_TIMER, MainFrame::onTimer)
258 	EVT_PAINT(MainFrame::onPaint)
259 	EVT_MOUSE_EVENTS(MainFrame::onMotion)
260 	EVT_ERASE_BACKGROUND(MainFrame::onEraseBackground)
261 END_EVENT_TABLE()
262 
263 MainFrame::MainFrame() :
264 	wxFrame((wxFrame *)NULL, -1, wxString(scorched3dAppName, wxConvUTF8),
265 		wxDefaultPosition, wxDefaultSize,
266 		wxMINIMIZE_BOX | wxCAPTION | wxSYSTEM_MENU),
267 	mouseX_(0), mouseY_(0), lastPos_(-1)
268 {
269 	if (!messageMutex_) messageMutex_ = SDL_CreateMutex();
270 
271 	// Set the frame's icon
272 	wxIcon icon(convertString(S3D::getDataFile("data/images/tank2.ico")), wxBITMAP_TYPE_ICO);
273 	SetIcon(icon);
274 
275 #if wxCHECK_VERSION(2, 6, 0)
276 	SetBackgroundStyle(wxBG_STYLE_CUSTOM);
277 #endif
278 
279 	// Load the backdrop bitmaps
280 	if (!backdropImage_.LoadFile(
281 		convertString(S3D::getDataFile("data/images/backdrop.gif")),
282 		wxBITMAP_TYPE_GIF))
283 	{
284 		S3D::dialogMessage("Scorched", "Failed to load backdrop");
285 	}
286 	backdropBitmap_ = wxBitmap(backdropImage_, -1);
287 
288 	// Load all of the button bitmaps
289 	struct ImageDefinition
290 	{
291 		const char *name;
292 		const char *description;
293 		int x, y;
294 	} imageDefinitions[] = {
295 		"Play", "- Play a game.", 30, 150,
296 		"Start Server", "- Start a LAN or internet server.", 30, 180,
297 		"Settings", "- Change the display, sound or other settings.", 30, 210,
298 		"Help", "- View the online help.", 30, 260,
299 		"Donate", "- Show support for Scorched3D.", 30, 290,
300 		"Quit", "- Exit the game.", 30, 340
301 	};
302 
303 	TrueTypeFont largeImageFont(S3D::getDataFile("data/fonts/dejavusans.ttf"), 14);
304 	TrueTypeFont smallImageFont(S3D::getDataFile("data/fonts/dejavusans.ttf"), 12);
305 
306 	for (int i=0; i<sizeof(imageDefinitions) / sizeof(ImageDefinition); i++)
307 	{
308 		ImageData *image = new ImageData();
309 		image->x = imageDefinitions[i].x;
310 		image->y = imageDefinitions[i].y;
311 
312 		if (!largeImageFont.getImageForText(imageDefinitions[i].name, image->loadedImage) ||
313 			!smallImageFont.getImageForText(imageDefinitions[i].description, image->descriptionImage))
314 		{
315 			S3D::dialogMessage("Scorched",
316 				S3D::formatStringBuffer("Failed to load button %s", imageDefinitions[i].name));
317 		}
318 		else
319 		{
320 			images_.push_back(image);
321 		}
322 	}
323 
324 	// Setup timer
325 	timer_.SetOwner(this, ID_MAIN_TIMER);
326 	timer_.Start(1000, false);
327 
328 	// use the sizer for layout
329 	// Create the positioning sizer
330 	wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
331 	topsizer->SetMinSize(533, 400);
332 	SetSizer(topsizer);
333 	topsizer->SetSizeHints(this); // set size hints to honour minimum size
334 
335 	CentreOnScreen();
336 }
337 
onMotion(wxMouseEvent & event)338 void MainFrame::onMotion(wxMouseEvent &event)
339 {
340 	mouseX_ = event.m_x;
341 	mouseY_ = event.m_y;
342 
343 	int foundPos = -1;
344 	int pos = 0;
345 	std::list<ImageData *>::iterator itor;
346 	for (itor = images_.begin();
347 		itor != images_.end();
348 		++itor, pos++)
349 	{
350 		ImageData *imageData = (*itor);
351 		if (mouseX_ > imageData->x &&
352 			mouseY_ > imageData->y &&
353 			mouseX_ < imageData->x + imageData->cachedBitmap1.GetWidth() &&
354 			mouseY_ < imageData->y + imageData->cachedBitmap1.GetHeight())
355 		{
356 			foundPos = pos;
357 		}
358 	}
359 
360 	if (event.ButtonDown() &&
361 		foundPos != -1)
362 	{
363 		switch (foundPos)
364 		{
365 		case 0:
366 			onSingleButton();
367 			break;
368 		case 1:
369 			onServerButton();
370 			break;
371 		case 2:
372 			onDisplayButton();
373 			break;
374 		case 3:
375 			onHelpButton();
376 			break;
377 		case 4:
378 			onDonateClick();
379 			break;
380 		case 5:
381 			onQuitButton();
382 			break;
383 		}
384 	}
385 
386 	if (lastPos_ != foundPos)
387 	{
388 		lastPos_ = foundPos;
389 		Refresh();
390 	}
391 }
392 
generateCachedImage(int x,int y,wxImage & src,wxBitmap & destBitamp,bool highlight)393 void MainFrame::generateCachedImage(int x, int y,
394 	wxImage &src, wxBitmap &destBitamp,
395 	bool highlight)
396 {
397 	wxImage dest(
398 		src.GetWidth(),
399 		src.GetHeight());
400 
401 	unsigned char *backdropdata = backdropImage_.GetData();
402 	backdropdata +=
403 		3 * x +
404 		3 * y * backdropImage_.GetWidth();
405 
406 	unsigned char *srcdata = src.GetData();
407 	unsigned char *destdata = dest.GetData();
408 	for (int y=0; y<src.GetHeight(); y++)
409 	{
410 		unsigned char *backdropstart = backdropdata;
411 		for (int x=0; x<src.GetWidth(); x++)
412 		{
413 			float alpha = srcdata[0] + srcdata[1] + srcdata[2];
414 			alpha /= 255.0f + 255.0f + 255.0f;
415 
416 			float mult = (highlight?1.5f:1.0f);
417 			float src0 = float(srcdata[0]) * mult; if (src0 > 255.0f) src0 = 255.0f;
418 			float src1 = float(srcdata[1]) * mult; if (src1 > 255.0f) src1 = 255.0f;
419 			float src2 = float(srcdata[2]) * mult; if (src2 > 255.0f) src2 = 255.0f;
420 			destdata[0] = (unsigned char) ((1.0f - alpha) * float(backdropdata[0]) + alpha * float(src0));
421 			destdata[1] = (unsigned char) ((1.0f - alpha) * float(backdropdata[1]) + alpha * float(src1));
422 			destdata[2] = (unsigned char) ((1.0f - alpha) * float(backdropdata[2]) + alpha * float(src2));
423 
424 			srcdata += 3;
425 			destdata += 3;
426 			backdropdata += 3;
427 		}
428 		backdropdata = backdropstart + 3 * backdropImage_.GetWidth();
429 
430 	}
431 
432 	destBitamp = wxBitmap(dest, -1);
433 }
434 
onPaint(wxPaintEvent & event)435 void MainFrame::onPaint(wxPaintEvent& event)
436 {
437 	wxBufferedPaintDC dc(this);
438 
439 	dc.DrawBitmap(backdropBitmap_, 0, 0, false);
440 
441 	// So its easy to display an alpha blended bitmap in wxWindows then!!!
442 	std::list<ImageData *>::iterator itor;
443 	for (itor = images_.begin();
444 		itor != images_.end();
445 		++itor)
446 	{
447 		ImageData *imageData = (*itor);
448 
449 		if (!imageData->cachedBitmap1.Ok())
450 		{
451 			generateCachedImage(imageData->x, imageData->y,
452 				imageData->loadedImage, imageData->cachedBitmap1, false);
453 			generateCachedImage(imageData->x, imageData->y,
454 				imageData->loadedImage, imageData->cachedBitmap2, true);
455 			generateCachedImage(imageData->x + 135, imageData->y + 2,
456 				imageData->descriptionImage, imageData->cachedDescription, true);
457 		}
458 
459 		if (mouseX_ > imageData->x &&
460 			mouseY_ > imageData->y &&
461 			mouseX_ < imageData->x + imageData->cachedBitmap1.GetWidth() &&
462 			mouseY_ < imageData->y + imageData->cachedBitmap1.GetHeight())
463 		{
464 			dc.DrawBitmap(imageData->cachedBitmap2, imageData->x, imageData->y, false);
465 			dc.DrawBitmap(imageData->cachedDescription, imageData->x + 135, imageData->y + 2, false);
466 		}
467 		else
468 		{
469 			dc.DrawBitmap(imageData->cachedBitmap1, imageData->x, imageData->y, false);
470 		}
471 	}
472 }
473 
onEraseBackground(wxEraseEvent & event)474 void MainFrame::onEraseBackground(wxEraseEvent &event)
475 {
476 }
477 
onTimer(wxTimerEvent & event)478 void MainFrame::onTimer(wxTimerEvent &event)
479 {
480 	std::string newString;
481 	SDL_LockMutex(messageMutex_);
482 	if (!messageString_.empty())
483 	{
484 		newString = messageString_;
485 		messageString_ = "";
486 	}
487 	SDL_UnlockMutex(messageMutex_);
488 
489 	if (!newString.empty())
490 	{
491 		if (exitCode_ != 64)
492 		{
493 			newString.append("\n"
494 				"Would you like to load the failsafe "
495 				"scorched3d settings?\n"
496 				"This gives the best chance of working but "
497 				"at the cost of graphical detail.\n"
498 				"You can adjust this later in the Scorched3D "
499 				"display settings dialog.\n"
500 				"Note: Most problems can be fixed by using "
501 				"the very latest drivers\n"
502 				"for your graphics card.");
503 			int answer = ::wxMessageBox(
504 				wxString(newString.c_str(), wxConvUTF8),
505 				wxT("Scorched3D Abnormal Termination"),
506 				wxYES_NO | wxICON_ERROR);
507 			if (answer == wxYES)
508 			{
509 				OptionsDisplay::instance()->loadSafeValues();
510 				OptionsDisplay::instance()->writeOptionsToFile(ScorchedParams::instance()->getWriteFullOptions());
511 			}
512 		}
513 		else
514 		{
515 			::wxMessageBox(
516 				wxString(newString.c_str(), wxConvUTF8),
517 				wxT("Scorched3D Termination"),
518 				wxICON_ERROR);
519 		}
520 	}
521 }
522 
onDonateClick()523 void MainFrame::onDonateClick()
524 {
525 	const char *exec =
526 		"\"https://www.paypal.com/xclick/business=donations%40"
527 		"scorched3d.co.uk&item_name=Scorched3D&no_note=1&tax=0&currency_code=GBP\"";
528 	S3D::showURL(exec);
529 }
530 
onDisplayButton()531 void MainFrame::onDisplayButton()
532 {
533 	showDisplayDialog();
534 }
535 
onSingleButton()536 void MainFrame::onSingleButton()
537 {
538 	runScorched3D("", false);
539 }
540 
onServerButton()541 void MainFrame::onServerButton()
542 {
543 	showServerSDialog();
544 }
545 
onQuitButton()546 void MainFrame::onQuitButton()
547 {
548 	wxWindowExit = true;
549 	Close();
550 }
551 
onHelpButton()552 void MainFrame::onHelpButton()
553 {
554 	S3D::showURL("http://www.scorched3d.co.uk/wiki");
555 }
556 
557 extern bool newVersion;
showMainDialog()558 void showMainDialog()
559 {
560 	mainDialog = new MainFrame;
561 	mainDialog->Show(TRUE);
562 
563 	if (newVersion)
564 	{
565 		wxBitmap bitmap;
566 		if (bitmap.LoadFile(convertString(S3D::getDataFile("data/images/splash.gif")),
567 			wxBITMAP_TYPE_GIF))
568 		{
569 			new wxSplashScreen(bitmap,
570 				wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT,
571 				9000, NULL, -1, wxDefaultPosition, wxDefaultSize,
572 				wxSIMPLE_BORDER | wxSTAY_ON_TOP);
573 		}
574 	}
575 }
576 
getMainDialog()577 wxFrame *getMainDialog()
578 {
579 	return mainDialog;
580 }
581