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