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 WinXFlush(aboutWindow);
437 }
438
439
About_callback(int widget_desc,void * data,const char ** str)440 int About_callback(int widget_desc, void *data, const char **str)
441 {
442 UNUSED_PARAM(widget_desc); UNUSED_PARAM(data); UNUSED_PARAM(str);
443 if (about_created == false) {
444 About_create_window();
445 about_created = true;
446 }
447 XMapWindow(dpy, aboutWindow);
448 return 0;
449 }
450
451 /*****************************************************************************/
452 int keys_viewer = NO_WIDGET;
453
Keys_callback(int widget_desc,void * data,const char ** unused)454 int Keys_callback(int widget_desc, void *data, const char **unused)
455 {
456 unsigned bufsize = (num_keydefs * 64);
457 char *buf = calloc(bufsize, 1), *end = buf, *str;
458 const char *help;
459 int i, len, maxkeylen = 0;
460
461 UNUSED_PARAM(widget_desc); UNUSED_PARAM(data); UNUSED_PARAM(unused);
462
463 for (i = 0; i < num_keydefs; i++) {
464 if ((str = XKeysymToString((KeySym)keydefs[i].keysym)) != NULL
465 && (len = strlen(str)) > maxkeylen) {
466 maxkeylen = len;
467 }
468 }
469 for (i = 0; i < num_keydefs; i++) {
470 if (!(str = XKeysymToString((KeySym)keydefs[i].keysym))
471 || !(help = Get_keyHelpString(keydefs[i].key)))
472 continue;
473
474 if ((end - buf) + (maxkeylen + strlen(help) + 4) >= bufsize) {
475 bufsize += 4096;
476 xpprintf("realloc: %d\n", bufsize);
477 if (!(buf = realloc(buf, bufsize))) {
478 error("No memory for key list");
479 return 0;
480 }
481 }
482 sprintf(end, "%-*s %s\n", maxkeylen, str, help);
483 end += strlen(end);
484 }
485 keys_viewer =
486 Widget_create_viewer(buf,
487 end - buf,
488 2*DisplayWidth(dpy, DefaultScreen(dpy))/3,
489 4*DisplayHeight(dpy, DefaultScreen(dpy))/5,
490 2,
491 "XPilot - key reference", "XPilot:keys",
492 motdFont);
493 if (keys_viewer == NO_WIDGET) {
494 warn("Can't create key viewer");
495 return 0;
496 }
497 #if 0
498 else if (keys_viewer != NO_WIDGET)
499 Widget_map(keys_viewer);
500 #endif
501 return 0;
502 }
503
Keys_destroy(void)504 void Keys_destroy(void)
505 {
506 Widget_destroy(keys_viewer);
507 keys_viewer = NO_WIDGET;
508 /*keys_created = false;*/
509 }
510
511
512 #define MAX_MOTD_SIZE (30*1024)
513
514 static char *motd_buf = NULL;
515 static size_t motd_size;
516 int motd_viewer = NO_WIDGET;
517 static bool motd_auto_popup;
518
Motd_callback(int widget_desc,void * data,const char ** str)519 int Motd_callback(int widget_desc, void *data, const char **str)
520 {
521 UNUSED_PARAM(widget_desc); UNUSED_PARAM(data); UNUSED_PARAM(str);
522
523 /* always refresh motd */
524 motd_auto_popup = false;
525 Net_ask_for_motd(0, MAX_MOTD_SIZE);
526 Net_flush();
527
528 if (motd_viewer != NO_WIDGET)
529 Widget_map(motd_viewer);
530 return 0;
531 }
532
Motd_destroy(void)533 void Motd_destroy(void)
534 {
535 Widget_destroy(motd_viewer);
536 motd_viewer = NO_WIDGET;
537 XFREE(motd_buf);
538 }
539
Handle_motd(long off,char * buf,int len,long filesize)540 int Handle_motd(long off, char *buf, int len, long filesize)
541 {
542 int i;
543 static char no_motd_msg[] = "\nThis server has no MOTD.\n\n";
544
545 if (!motd_buf) {
546 motd_size = MIN(filesize, MAX_MOTD_SIZE);
547 i = MAX(motd_size, (long)(sizeof no_motd_msg)) + 1;
548 if (!(motd_buf = malloc((size_t)i))) {
549 error("No memory for MOTD");
550 return -1;
551 }
552 memset(motd_buf, ' ', motd_size);
553 for (i = 39; i < (int)motd_size; i += 40)
554 motd_buf[i] = '\n';
555 }
556 else if (filesize < (long)motd_size) {
557 motd_size = filesize;
558 motd_buf[motd_size] = '\0';
559 }
560 if (off < (long)motd_size && len > 0) {
561 if (off + len > (long)motd_size)
562 len = motd_size - off;
563 memcpy(motd_buf + off, buf, (size_t)len);
564 }
565 else if (len == 0 && off > 0)
566 return 0;
567
568 if (motd_size == 0) {
569 if (motd_auto_popup) {
570 XFREE(motd_buf);
571 return 0;
572 }
573 strcpy(motd_buf, no_motd_msg);
574 motd_size = strlen(motd_buf);
575 }
576 if (motd_viewer == NO_WIDGET) {
577 char title[100];
578
579 snprintf(title, sizeof(title), "XPilot motd from %s", servername);
580 motd_viewer = Widget_create_viewer(
581 motd_buf,
582 (off || len) ? (off + len) : (int)strlen(motd_buf),
583 2*DisplayWidth(dpy, DefaultScreen(dpy))/3,
584 4*DisplayHeight(dpy, DefaultScreen(dpy))/8,
585 2,
586 title, "XPilot:motd",
587 motdFont);
588 if (motd_viewer == NO_WIDGET)
589 warn("Can't create MOTD viewer");
590 }
591 else if (len > 0)
592 Widget_update_viewer(motd_viewer, motd_buf, off + len);
593
594 return 0;
595 }
596
aboutCleanup(void)597 void aboutCleanup(void)
598 {
599 XFREE(motd_buf);
600 }
601
602 #ifdef _WINDOWS
Credits_callback(int widget_desc,void * data,const char ** unused)603 int Credits_callback(int widget_desc, void *data, const char **unused)
604 {
605 extern void DoWinAboutBox();
606 DoWinAboutBox();
607 return(0);
608 }
609 #endif
610
611