1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 1991-2001 by
5  *
6  *      Bj�rn Stabell        <bjoern@xpilot.org>
7  *      Ken Ronny Schouten   <ken@xpilot.org>
8  *      Bert Gijsbers        <bert@xpilot.org>
9  *      Dick Balaska         <dick@xpilot.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25 
26 #include "xpclient_x11.h"
27 
28 /* How far away objects should be placed from each other etc... */
29 #define BORDER			10
30 #define BTN_BORDER		4
31 
32 /* Information window dimensions */
33 #define ABOUT_WINDOW_WIDTH	600
34 #define ABOUT_WINDOW_HEIGHT	700
35 
36 static bool		about_created = false;
37 
38 
39 #define NUM_ABOUT_PAGES		5
40 
41 /*
42  * This variable tells us what item comes last on page 0.  If -1 it hasn't
43  * been initialized yet (page 0 needs exposing to do this).  If it is
44  * NUM_ITEMS-1 then there is no need to split to page and the NEXT and PREV
45  * keys will automatically skip that page.
46  */
47 static int itemsplit = -1;
48 
49 /*
50  * General text formatting routine which does wrap around
51  * if necessary at whitespaces.  The function returns the
52  * vertical position it ended at.
53  */
DrawShadowText(Display * display,Window w,GC gc,int x_border,int y_start,const char * str,unsigned long fg,unsigned long bg)54 int DrawShadowText(Display *display, Window w, GC gc,
55 		   int x_border, int y_start, const char *str,
56 		   unsigned long fg, unsigned long bg)
57 {
58     XFontStruct		*font = XQueryFont(display, XGContextFromGC(gc));
59     int			y, x, tmp;
60     int                 count = 1;
61     XWindowAttributes	wattr;
62 
63     if (str == NULL || *str == '\0')
64 	return 0;
65 
66     /* Get width of window */
67     XGetWindowAttributes(display, w, &wattr);
68 
69     /* Start position */
70     x = x_border;
71     y = y_start + font->ascent;
72 
73     do {
74 	char word[LINE_MAX];
75 	int wordLen, i;
76 
77 	for (i = 0; *str && !isspace(*str) && i < LINE_MAX-1; str++, i++)
78 	    word[i] = *str;
79 	word[i] = '\0';
80 
81 	/* Word length in pixels */
82 	wordLen = XTextWidth(font, word, i);
83 
84 	/* We need a linebreak? */
85 	if (x + wordLen > wattr.width - BORDER) {
86 	    x = x_border;
87 	    y += font->ascent + font->descent + 1;
88 	}
89 
90 	/* Draw word and move cursor to point to after this word */
91 	ShadowDrawString(display, w, gc, x, y, word, fg, bg);
92 	x += wordLen;
93 
94 	/* Handle whitespace */
95 	for (; isspace(*str); str++)
96 	    switch (*str) {
97 		/* New paragraph */
98 	    case '\n':
99 		x = x_border;
100 		y += font->ascent + font->descent + 1;
101 		break;
102 
103 		/* Just a space */
104 	    default:
105 		x += XTextWidth(font, " ", 1);
106 		break;
107 	    }
108     } while (*str != '\0');
109 
110     tmp = font->descent+1;
111 
112     XFreeFontInfo(NULL, font, count);
113 
114     return y + tmp;
115 }
116 
117 
Expose_about_window(void)118 void Expose_about_window(void)
119 {
120     int	i, y, old_y, box_start, box_end, first, last;
121 
122     XClearWindow(dpy, aboutWindow);
123 
124     switch (about_page) {
125 
126     case 0:
127 	DrawShadowText(dpy, aboutWindow, textGC,
128 	BORDER, BORDER,
129 	"ABOUT XPILOT\n"
130 	"\n"
131 	"The game was conceived in its original form at the "
132 	"University of Troms� (Norway) by Ken Ronny Schouten and "
133 	"Bj�rn Stabell during the fall of 1991, but much of the game today "
134 	"is the result of hard efforts by Bert Gijsbers of the "
135 	"molecular cytology lab at the University of Amsterdam "
136 	"(The Netherlands).  "
137 	"Bert joined the team in the spring of 1993.\n"
138 	"\n"
139 	"Dick Balaska (Connecticut, USA) ported XPilot to Windows 95 and NT "
140 	"in the summer of 1996.\n"
141 	"\n"
142 	"A large number of features have been contributed by XPilot fans from "
143 	"all around the world.  See the CREDITS file for details.\n"
144 	"\n"
145 	"For more information, "
146 	"read the XPilot FAQ (Frequently Asked Questions).\n"
147 	"\n\n"
148 	"Good luck as a future xpilot,\n"
149 	"Bj�rn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska",
150 	colors[WHITE].pixel, colors[BLACK].pixel);
151 	break;
152 
153     case 1:
154 	DrawShadowText(dpy, aboutWindow, textGC,
155 	BORDER, BORDER,
156 	"ABOUT XPILOT NG\n"
157 	"\n"
158 	"XPilot NG is an improved version of XPilot.\n\n"
159 	"For more info visit http://xpilot.sourceforge.net/ or\n"
160 	"read the man pages xpilot-ng-x11(6) and xpilot-ng-server(6).\n\n"
161 	"You can report any bug you find to <" PACKAGE_BUGREPORT ">.",
162 	colors[WHITE].pixel, colors[BLACK].pixel);
163 	break;
164 
165     case 2:
166 	DrawShadowText(dpy, aboutWindow, textGC,
167 	BORDER, BORDER,
168 	"GAME OBJECTIVE\n"
169 	"\n"
170 	"XPilot is a multi-player 2D space game.  "
171 	"Some features are borrowed from classics like the Atari coin-ups "
172 	"Asteroids and Gravitar, and the home-computer games "
173 	"Thrust (Commdore 64) and Gravity Force, but XPilot has many "
174 	"new features as well.\n"
175 	"\n"
176 	"The primary goal of the game is to collect points and increase "
177 	"your rating by destroying enemy fighters and cannons.  "
178 	"You are equipped with a machine gun when you start the game, "
179 	"but after a while you should have managed to collect some other "
180 	"fancy equipment.\n"
181 	"\n"
182 	"Another important task is to refuel your ship.  This is "
183 	"vital because your engine, radar, weapons and shields all "
184 	"require fuel.  Some even work better the more fuel you "
185 	"have aboard (mainly the radar).\n"
186 	"\n"
187 	"Optional modes include variations on this game play: "
188 	"you can play together in teams, you can disable shields "
189 	"(and all other equipment if you like), "
190 	"you can race against time and fellow players, and much much more.",
191 	colors[WHITE].pixel, colors[BLACK].pixel);
192 	break;
193 
194     case 3:
195     case 4:
196 	if (about_page == 3) {
197 	    y = DrawShadowText(dpy, aboutWindow, textGC,
198 			   BORDER, BORDER,
199 			   "BONUS ITEMS\n"
200 			   "\n"
201 			   "Scattered around the world you might find some "
202 			   "of these red triangle objects.  They are "
203 			   "well worth picking up since they either improve "
204 			   "on the equipment you have, or they give you "
205 			   "new equipment.  If a fighter explodes, some of "
206 			   "its equipment might be found among the debris.",
207 			   colors[WHITE].pixel, colors[BLACK].pixel);
208 	    first = 0;
209 	    last = (itemsplit == -1) ? (NUM_ITEMS-1) : itemsplit;
210 	} else {
211 	    y = DrawShadowText(dpy, aboutWindow, textGC,
212 			   BORDER, BORDER,
213 			   "BONUS ITEMS CONTINUED\n",
214 			   colors[WHITE].pixel, colors[BLACK].pixel);
215 	    first = itemsplit+1;
216 	    last = (NUM_ITEMS-1);
217 	}
218 
219 	y += BORDER;
220 	box_start = y;
221 	y += BORDER / 2;
222 	for (i = first; i <= last; i++) {
223 
224 	    y += BORDER / 2;
225 
226 	    /* Draw description text */
227 	    old_y = y;
228 	    y = DrawShadowText(dpy, aboutWindow, textGC,
229 			       5*BORDER + 2*ITEM_SIZE, old_y,
230 			       Item_get_text(i),
231 			       colors[WHITE].pixel, colors[BLACK].pixel);
232 	    if (y - old_y < 2 * ITEM_TRIANGLE_SIZE)
233 		y = old_y + 2 * ITEM_TRIANGLE_SIZE;
234 	    box_end = y + BORDER / 2;
235 	    if (i == last)
236 		box_end += BORDER / 2;
237 
238 	    /* Paint the item on the left side */
239 	    XSetForeground(dpy, textGC, colors[BLACK].pixel);
240 	    XFillRectangle(dpy, aboutWindow, textGC,
241 			   BORDER, box_start,
242 			   2*ITEM_SIZE+2*BORDER,
243 			   (unsigned)box_end - box_start);
244 	    XSetForeground(dpy, textGC, colors[RED].pixel);
245 	    Gui_paint_item((u_byte)i, aboutWindow, textGC, 2*BORDER + ITEM_SIZE,
246 		       old_y + ITEM_TRIANGLE_SIZE);
247 	    XSetForeground(dpy, textGC, colors[WHITE].pixel);
248 
249 	    /*
250 	     * Check for items overlapping button window, if so then
251 	     * remove this item, set itemsplit to previous item and
252 	     * stop adding more items.
253 	     */
254 	    if (about_page == 3
255 		&& itemsplit == -1
256 		&& box_end >= (ABOUT_WINDOW_HEIGHT - BORDER * 2 - 4
257 			       - (2*BTN_BORDER + buttonFont->ascent
258 				  + buttonFont->descent))) {
259 		itemsplit = i-1;
260 		XSetForeground(dpy, textGC, colors[windowColor].pixel);
261 		XFillRectangle(dpy, aboutWindow, textGC,
262 			       BORDER, box_start,
263 			       ABOUT_WINDOW_WIDTH,
264 			       (unsigned)box_end - box_start);
265 		XSetForeground(dpy, textGC, colors[WHITE].pixel);
266 		break;
267 	    }
268 
269 	    y = box_end;
270 	    box_start = box_end;
271 
272 	}
273 	/*
274 	 * No page split, obviously font is small enough or not enough
275 	 * items.
276 	 */
277 	if (about_page == 3 && itemsplit == -1)
278 	    itemsplit = NUM_ITEMS-1;
279 	break;
280 
281     default:
282 	error("Unkown page number %d\n", about_page);
283 	break;
284     }
285 }
286 
287 
About_create_window(void)288 static void About_create_window(void)
289 {
290     const unsigned int		windowWidth = ABOUT_WINDOW_WIDTH,
291 				buttonWindowHeight = 2*BTN_BORDER
292 				    + buttonFont->ascent + buttonFont->descent,
293 				windowHeight = ABOUT_WINDOW_HEIGHT;
294     unsigned			textWidth;
295     XSetWindowAttributes	sattr;
296     unsigned long		mask;
297 
298 
299     /*
300      * Create the window and initialize window name.
301      */
302     mask = 0;
303     sattr.background_pixel = colors[windowColor].pixel;
304     mask |= CWBackPixel;
305     sattr.border_pixel = colors[borderColor].pixel;
306     mask |= CWBorderPixel;
307     if (colormap != 0) {
308 	sattr.colormap = colormap;
309 	mask |= CWColormap;
310     }
311     sattr.backing_store = Always;
312     mask |= CWBackingStore;
313 
314     aboutWindow
315 	= XCreateWindow(dpy,
316 			DefaultRootWindow(dpy),
317 			0, 0,
318 			windowWidth, windowHeight,
319 			2, (int)dispDepth,
320 			InputOutput, visual,
321 			mask, &sattr);
322     XStoreName(dpy, aboutWindow, "XPilot - information");
323     XSetIconName(dpy, aboutWindow, "XPilot/info");
324     XSetTransientForHint(dpy, aboutWindow, topWindow);
325 
326     textWidth = XTextWidth(buttonFont, "CLOSE", 5);
327     about_close_b
328 	= XCreateSimpleWindow(dpy, aboutWindow,
329 			      BORDER,
330 			      (int)(windowHeight - BORDER
331 				    - buttonWindowHeight - 4),
332 			      2*BTN_BORDER + textWidth,
333 			      buttonWindowHeight,
334 			      0, 0,
335 			      colors[buttonColor].pixel);
336 
337     /*
338      * Create 'buttons' in the window.
339      */
340     textWidth = XTextWidth(buttonFont, "PREV", 4);
341     about_prev_b
342 	= XCreateSimpleWindow(dpy, aboutWindow,
343 			      (int)(windowWidth / 2
344 				    - BTN_BORDER - textWidth / 2),
345 			      (int)(windowHeight
346 				    - BORDER - buttonWindowHeight - 4),
347 			      2*BTN_BORDER + textWidth, buttonWindowHeight,
348 			      0, 0,
349 			      colors[buttonColor].pixel);
350 
351     textWidth = XTextWidth(buttonFont, "NEXT", 4);
352     about_next_b
353 	= XCreateSimpleWindow(dpy, aboutWindow,
354 			      (int)(windowWidth - BORDER
355 				    - 2*BTN_BORDER - textWidth),
356 			      (int)(windowHeight - BORDER
357 				    - buttonWindowHeight - 4),
358 			      2*BTN_BORDER + textWidth, buttonWindowHeight,
359 			      0, 0,
360 			      colors[buttonColor].pixel);
361 
362     XSelectInput(dpy, about_close_b,
363 		 ExposureMask | ButtonPressMask | ButtonReleaseMask);
364     XSelectInput(dpy, about_next_b,
365 		 ExposureMask | ButtonPressMask | ButtonReleaseMask);
366     XSelectInput(dpy, about_prev_b,
367 		 ExposureMask | ButtonPressMask | ButtonReleaseMask);
368     XSelectInput(dpy, aboutWindow, ExposureMask);
369 
370     Expose_about_window();
371 
372     XMapSubwindows(dpy, aboutWindow);
373 }
374 
375 
Expose_button_window(int color,Window w)376 void Expose_button_window(int color, Window w)
377 {
378     if (w != about_close_b
379 	&& w != about_next_b
380 	&& w != about_prev_b) {
381 	return;
382     }
383 
384     {
385 	XWindowAttributes	wattr;		/* Get window height */
386 	XGetWindowAttributes(dpy, w, &wattr);	/* and width */
387 
388 	XSetForeground(dpy, buttonGC, colors[color].pixel);
389 	XFillRectangle(dpy, w, buttonGC, 0, 0,
390 		       (unsigned)wattr.width, (unsigned)wattr.height);
391 	XSetForeground(dpy, buttonGC, colors[WHITE].pixel);
392     }
393 
394     if (w == about_close_b)
395 	ShadowDrawString(dpy, w, buttonGC,
396 			 BTN_BORDER, buttonFont->ascent + BTN_BORDER,
397 			 "CLOSE",
398 			 colors[WHITE].pixel, colors[BLACK].pixel);
399     if (w == about_prev_b)
400 	ShadowDrawString(dpy, w, buttonGC,
401 			 BTN_BORDER, buttonFont->ascent + BTN_BORDER,
402 			 "PREV",
403 			 colors[WHITE].pixel, colors[BLACK].pixel);
404     if (w == about_next_b)
405 	ShadowDrawString(dpy, w, buttonGC,
406 			 BTN_BORDER, buttonFont->ascent + BTN_BORDER,
407 			 "NEXT",
408 			 colors[WHITE].pixel, colors[BLACK].pixel);
409 }
410 
411 
About(Window w)412 void About(Window w)
413 {
414     if (about_created == false) {
415 	About_create_window();
416 	about_created = true;
417     }
418     if (w == about_close_b) {
419 	about_page = 0;
420 	XUnmapWindow(dpy, aboutWindow);
421     } else if (w == about_next_b) {
422 	about_page++;
423 	if (about_page == 1 && itemsplit >= NUM_ITEMS-1)
424 	    about_page++;
425 	if (about_page >= NUM_ABOUT_PAGES)
426 	    about_page = 0;
427 	Expose_about_window();
428     } else if (w == about_prev_b) {
429 	about_page--;
430 	if (about_page == 1 && itemsplit >= NUM_ITEMS-1)
431 	    about_page--;
432 	if (about_page <= -1)
433 	    about_page = NUM_ABOUT_PAGES-1;
434 	Expose_about_window();
435     }
436 }
437 
438 
About_callback(int widget_desc,void * data,const char ** str)439 int About_callback(int widget_desc, void *data, const char **str)
440 {
441     UNUSED_PARAM(widget_desc); UNUSED_PARAM(data); UNUSED_PARAM(str);
442     if (about_created == false) {
443 	About_create_window();
444 	about_created = true;
445     }
446     XMapWindow(dpy, aboutWindow);
447     return 0;
448 }
449 
450 /*****************************************************************************/
451 int		keys_viewer = NO_WIDGET;
452 
Keys_callback(int widget_desc,void * data,const char ** unused)453 int Keys_callback(int widget_desc, void *data, const char **unused)
454 {
455     unsigned	bufsize = (num_keydefs * 64);
456     char	*buf = XCALLOC(char, bufsize), *end = buf, *str;
457     const char	*help;
458     int		i, len, maxkeylen = 0;
459 
460     UNUSED_PARAM(widget_desc); UNUSED_PARAM(data); UNUSED_PARAM(unused);
461 
462     for (i = 0; i < num_keydefs; i++) {
463 	if ((str = XKeysymToString((KeySym)keydefs[i].keysym)) != NULL
464 	    && (len = strlen(str)) > maxkeylen) {
465 	    maxkeylen = len;
466 	}
467     }
468     for (i = 0; i < num_keydefs; i++) {
469 	if (!(str = XKeysymToString((KeySym)keydefs[i].keysym))
470 	    || !(help = Get_keyHelpString(keydefs[i].key)))
471 	    continue;
472 
473 	if ((end - buf) + (maxkeylen + strlen(help) + 4) >= bufsize) {
474 	    bufsize += 4096;
475 	    xpprintf("realloc: %d\n", bufsize);
476 	    if (!(buf = XREALLOC(char, buf, bufsize))) {
477 		error("No memory for key list");
478 		return 0;
479 	    }
480 	}
481 	sprintf(end, "%-*s  %s\n", maxkeylen, str, help);
482 	end += strlen(end);
483     }
484     keys_viewer =
485 	Widget_create_viewer(buf,
486 			     end - buf,
487 			     2*DisplayWidth(dpy, DefaultScreen(dpy))/3,
488 			     4*DisplayHeight(dpy, DefaultScreen(dpy))/5,
489 			     2,
490 			     "XPilot - key reference", "XPilot:keys",
491 			     motdFont);
492     if (keys_viewer == NO_WIDGET) {
493 	warn("Can't create key viewer");
494 	return 0;
495     }
496 #if 0
497     else if (keys_viewer != NO_WIDGET)
498 	Widget_map(keys_viewer);
499 #endif
500     return 0;
501 }
502 
Keys_destroy(void)503 void Keys_destroy(void)
504 {
505     Widget_destroy(keys_viewer);
506     keys_viewer = NO_WIDGET;
507     /*keys_created = false;*/
508 }
509 
510 
511 #define MAX_MOTD_SIZE	(30*1024)
512 
513 static char		*motd_buf = NULL;
514 static size_t		motd_size;
515        int		motd_viewer = NO_WIDGET;
516 static bool		motd_auto_popup;
517 
Motd_callback(int widget_desc,void * data,const char ** str)518 int Motd_callback(int widget_desc, void *data, const char **str)
519 {
520     UNUSED_PARAM(widget_desc); UNUSED_PARAM(data); UNUSED_PARAM(str);
521 
522     /* always refresh motd */
523     motd_auto_popup = false;
524     Net_ask_for_motd(0, MAX_MOTD_SIZE);
525     Net_flush();
526 
527     if (motd_viewer != NO_WIDGET)
528 	Widget_map(motd_viewer);
529     return 0;
530 }
531 
Motd_destroy(void)532 void Motd_destroy(void)
533 {
534     Widget_destroy(motd_viewer);
535     motd_viewer = NO_WIDGET;
536     XFREE(motd_buf);
537 }
538 
Handle_motd(long off,char * buf,int len,long filesize)539 int Handle_motd(long off, char *buf, int len, long filesize)
540 {
541     int			i;
542     static char		no_motd_msg[] = "\nThis server has no MOTD.\n\n";
543 
544     if (!motd_buf) {
545 	motd_size = MIN(filesize, MAX_MOTD_SIZE);
546 	i = MAX(motd_size, (long)(sizeof no_motd_msg)) + 1;
547 	if (!(motd_buf = XMALLOC(char, (size_t)i))) {
548 	    error("No memory for MOTD");
549 	    return -1;
550 	}
551 	memset(motd_buf, ' ', motd_size);
552 	for (i = 39; i < (int)motd_size; i += 40)
553 	    motd_buf[i] = '\n';
554     }
555     else if (filesize < (long)motd_size) {
556 	motd_size = filesize;
557 	motd_buf[motd_size] = '\0';
558     }
559     if (off < (long)motd_size && len > 0) {
560 	if (off + len > (long)motd_size)
561 	    len = motd_size - off;
562 	memcpy(motd_buf + off, buf, (size_t)len);
563     }
564     else if (len == 0 && off > 0)
565 	return 0;
566 
567     if (motd_size == 0) {
568 	if (motd_auto_popup) {
569 	    XFREE(motd_buf);
570 	    return 0;
571 	}
572 	strcpy(motd_buf, no_motd_msg);
573 	motd_size = strlen(motd_buf);
574     }
575     if (motd_viewer == NO_WIDGET) {
576 	char title[100];
577 
578 	snprintf(title, sizeof(title), "XPilot motd from %s", servername);
579 	motd_viewer = Widget_create_viewer(
580 	    motd_buf,
581 	    (off || len) ? (off + len) : (int)strlen(motd_buf),
582 	    2*DisplayWidth(dpy, DefaultScreen(dpy))/3,
583 	    4*DisplayHeight(dpy, DefaultScreen(dpy))/8,
584 	    2,
585 	    title, "XPilot:motd",
586 	    motdFont);
587 	if (motd_viewer == NO_WIDGET)
588 	    warn("Can't create MOTD viewer");
589     }
590     else if (len > 0)
591 	Widget_update_viewer(motd_viewer, motd_buf, off + len);
592 
593     return 0;
594 }
595 
aboutCleanup(void)596 void aboutCleanup(void)
597 {
598     XFREE(motd_buf);
599 }
600