forked from smcameron/wordwarvi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
wordwarvi_hacking.html
767 lines (700 loc) · 35.4 KB
/
wordwarvi_hacking.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
<html>
<title>Hacking on Word War vi</tltle>
<body>
<p align=center><font size=+3>Hacking on Word War vi</font>
<hr>
<h2>Contents</h2>
<ul>
<li><a href="#code">Getting the code</a></li>
<li><a href="#organized">How the code is organized</a></li>
<li><a href="#main">A first look at the code: main() in wordwarvi.c</a></li>
<li><a href="#data">Some important data structures and variables</a></li>
<li><a href="#advance_game">advance_game(), called once per frame.</a></li>
<li><a href="#expose">main_da_expose(), called once per frame to redraw the screen.</a></li>
<li><a href="#enemy">How to add a new kind of enemy</a></li>
<li><a href="#sound">Adding new sounds</a></li>
<li><a href="#patches">Making and submitting patches</a></li>
</ul>
<hr>
<a name="code">
<h2>Getting the code</h2></a>
<p>The best way to get the code if you want to hack on it is to use git.
<p>Do the following at the shell prompt:
<pre>
$ mkdir wwvi
$ cd wwvi
$ git clone [email protected]:smcameron/wordwarvi.git
</pre>
<p>This will check the code out into a directory called wordwarvi
<a name="organized">
<h2>First a bit about how the code is organized.</h2></a>
<p>Mostly, the code is in one big file, wordwarvi.c, simply because this
all evolved from something which began as a way for me to kill time while
I was bored. I didn't exactly set out to make a game so much as a game
just kind of happened. You might think, "Oh jeez, the code's probably a mess,"
and you'd be partly right, but only partly. It's held up pretty well and has
needed no major reorganizations. I've been programming for a very long time
now, about 25 years, so I can sometimes get away with just winging it without
producing a mess, esp. if it's a fairly straightforward, mostly singlethreaded
app like this (yes the audio code is somewhat multithreaded, but, not in a
complicated way.) With computers being as fast and
flush with memory as they are these days, compiling and editing such a file
is not a big deal. There's no need to wonder too much about where something
is defined, it's likely in wordwarvi.c. Just hit slash and search what
you're looking for (you are using vi or vim for editing right? Surely
no heretics would dare to besmirch the source code by touching it with
Emacs.)
<p>The code is in C (definitely not C++, I'm not a big fan of C++ and the STL
at all -- have you ever profiled an STL-using C++ program? Try it sometime.).
<p>The style is very K&R-ish, it is pretty much along the lines of
<a href="http://kerneltrap.org/files/Jeremy/CodingStyle.txt">The
Linux kernel coding style</a>.
<p>Ok, enough of that, on to the code. Let's start with an overview of the
source files, and then take a look at main(), which is at the bottom of wordwarvi.c.
<pre>
[scameron@zuul wordwarvi]$ ls -l *.[ch]
-rw-rw-r-- 1 scameron scameron 3040 2008-12-03 18:13 joystick.c
-rw-rw-r-- 1 scameron scameron 1679 2008-07-19 15:53 joystick.h
-rw-rw-r-- 1 scameron scameron 19321 2008-12-13 07:13 levels.h
-rw-rw-r-- 1 scameron scameron 4023 2008-12-06 00:30 ogg_to_pcm.c
-rw-rw-r-- 1 scameron scameron 928 2008-12-03 17:55 ogg_to_pcm.h
-rw-rw-r-- 1 scameron scameron 8399 2008-12-15 19:11 rumble.c
-rw-rw-r-- 1 scameron scameron 1258 2008-07-19 17:48 rumble.h
-rw-rw-r-- 1 scameron scameron 305 2008-12-03 17:57 stamp.c
-rw-rw-r-- 1 scameron scameron 932 2008-12-11 20:22 version.h
-rw-rw-r-- 1 scameron scameron 386951 2008-12-17 17:40 wordwarvi.c
-rw-rw-r-- 1 scameron scameron 12267 2008-12-08 17:25 wwviaudio.c
-rw-rw-r-- 1 scameron scameron 5360 2008-11-22 11:44 wwviaudio.h
[scameron@zuul wordwarvi]$
</pre>
<p>joystick.c and joystick.h contain code for dealing with the linux
input layer to talk to the joysticks and get joystick motion and
button press events.
<p>levels.h defines many constants and data structures which are used to
control how each level in the game appears, how rough or smooth the
terrain is, what kind and how many of each enemy are present in the
level, how some of those enemies behave, etc.
<p>ogg_to_pcm.c and ogg_to_pcm.h are derived from the source to oggdec,
which is an ogg decoder. It's for decoding ogg data to PCM data that
the audio hardware is wanting. It just decodes, it doesn't playback.
<p>rumble.c and rumble.h deal with the xbox 360 game controller's
rumble feature.
<p>stamp.c is generated by the Makefile for a strange little joke
in the game (to make it give you a million points per level if you
build the code from source within the last hour. Hey, the first
thing the game tells you is "Uuuuuse the Soooource!!!" It's not
kidding.)
<p>version.h just contains the version number of the game.
<p>wwviaudio.c and wwviaudio.h define the audio playback engine
the game uses, as a layer on top of portaudio.
<p>Finally, wordwarvi.c contains the meat of the game.
<a name="main">
<h2>A first look at the code: main() in wordwarvi.c</h2></a>
(You should open up wordwarvi.c in another window, and
search for 'main(' while reading what's below. You
can <a href="https://github.com/smcameron/wordwarvi">
browse the word war vi source code via your
browser as well.</a>).
<p>At first, there's just a bunch of initializing of variables,
parsing commandline options and the .exrc config file, setting
upt portaudio, reading in audio data, a bunch of gtk/gdk stuff
to set up the main window, colors, callbacks, opening up
joystick devices, the rumble device, etc.k
An important line of code is this one:
<pre>
timer_tag = g_timeout_add(1000 / frame_rate_hz, advance_game, NULL);
</pre>
That sets up a timer which calls the function advance_game 30 times a
second (or at some other rate if frame_rate_hz is changed from its
default value.) The advance_game() function is the heart of Word War
vi, it is the main game loop.
Shortly after this timer is set up, this line of code occurs:
<pre>
gtk_main ();
</pre>
That is gtk's main loop. It gathers events from the keyboard and mouse,
and dispatches them to whatever callbacks are registered. It also
dispatches the timer to advance_game 30 times a second. It doesn't
return right away, but only when the program is exiting.
Next, we'd want to look at advance_game(). But before we can understand
that, we need to know about a few data structures in the game.
<a name="data">
<h2>Some important data structures and variables</h2></a>
<p>The most important data structure in the game is called
"go" (for game object) and it is an array of struct game_obj_t's.
<p>There is one struct game_obj_t, an element of "go", for every
single object in the game, every enemy, every bullet, every spark,
every hunk of debris, in the game. (Some exceptions: some text
displayed on the screen, the terrain, and the background stars).
Pretty much every other thing in the game has a game_obj_t associated
with it.
<p>So what's in a struct game_obj_t?
<pre>
struct game_obj_t {
int number; /* offset into the go[] (game object) array */
obj_move_func *move;
obj_draw_func *draw;
obj_destroy_func *destroy;
struct my_vect_obj *v; /* drawing instructions */
int x, y; /* current position, in game coords */
int vx, vy; /* velocity */
int above_target_y;
int below_target_y;
int color; /* initial color */
int alive; /* alive? Or dead? */
int otype; /* object type */
struct game_obj_t *bullseye; /* point to object this object is chasing */
int last_xi; /* the last x index into the terrain array which */
/* corresponds to the segment directly underneath */
/* this object -- used for detecting when an object */
/* smacks into the ground. */
int counter; /* a counter various object types used for var. purposes */
union type_specific_data tsd; /* the Type Specific Data for this object */
int missile_timer; /* to keep missiles from firing excessively rapidly */
int radar_image; /* Does this object show up on radar? */
int uses_health;
struct health_data health;
struct game_obj_t *next; /* These pointers, next, prev, are used to construct the */
struct game_obj_t *prev; /* target list, the list of things which may be hit by other things */
int ontargetlist; /* this list keeps of from having to scan the entire object list. */
get_coords_func gun_location; /* for attaching guns to objects */
struct game_obj_t *attached_gun;/* For attaching guns to objects */
struct my_vect_obj *debris_form;/* How to draw it when destroyed. */
};
</pre>
<p>Some of the more important of the above structure members are:
<table border=2>
<tr><td><b>Member</b></td>
<td><b>What it's for</b></td>
</tr>
<tr><td>move</td>
<td>function called every frame (30 times per second) to move the object.
These functions are, by convention, named xxxx_move, where xxxx is the
type of object being moved. So, for instance, if you want to know how
the octopus enemies in the game move, you would look for octopus_move().
</td></tr>
<tr>
<td>draw</td>
<td>function called to draw the object (when it's on screen) for each frame
(30 times per second.) These functions are, by convention, called xxxx_draw,
where xxxx is the type of object to be drawn. However, if the object is
simple, and can be draw just by a series of lines, it may use draw_generic().
and provide instructions for draw_generic in the "v" element. (non generic
draw functions may explicitly call draw_generic as a base function.)
</td>
<tr>
<td>x,y</td>
<td>Coordinates of the object in game world</td>
</tr>
<tr>
<td>vx, vy</td>
<td>Velocity of the object in the game world.</td>
</tr>
<tr>
<td>v</td>
<td>Pointer to instructions on how to draw an object (mostly this is just an array of relative
coordinates of connected vertices in a line drawing, with some line break and color change
instructions in the mix.)</td>
</tr>
<td>next, prev</td>
<td>pointers to the "next" and "previous" elements in the "target list", which is
the subset of "shootable" things in the game. The "go" array contains every object,
but when a laser or bomb is moving through the air it is nice not to have to check
its coordinates against every object in go[], as that is a lot of objects, and most
(e.g. sparks) cannot be hit by a laser or bomb. The "target list" concept lets us
only check against that subset of objects which may actually be hit.)
</td>
</tr>
</table>
That's not all of them, of course, but that is what I'd consider the most important subset.
<p>There are a couple of pointers, "player", and "player_target." Usually (except for
xmas mode) these point to the same thing, to go[0], which represents the player's ship.
(In xmas mode, player points to rudolph, and player_target points to the sleigh.)
<p>You will often see that in the "move" function of an enemy, it will refer to player_target
to find out how near to the player, and in what direction the player lies
in order to determine what action it should take.
<p>To control which elements of the go[] array are used and which are free, seeing
as how they dynamically are made active and inactive during the course of the game,
there is another variable, free_obj_bitmap[], which is a big array of 32 bit ints.
One bit in one of the ints in this array indicates whether the corresponding
element of go[] is free or allocated. There are a couple of functions to find a
free slot and allocate it, and to free an object. find_free_obj() allocates an
object, setting a bit in free_obj_bitmap, and free_obj() deallocates an object,
clearing the corresponding bit in free_obj_bitmap. Though I use the term allocate
and free, this is not malloc()/free(), nor new/delete or anything like that. The
go[] array is statically allocated. I am just doing my own internal allocation.
My allocator, knowing that all elements of go[] are the same size, can do some
optimizations which make it faster than allocating with malloc as objects are
needed. (This is the kind of place where C++ and the STL die a horrid death.)
Realize I'm doing my own "allocation" individually for every single spark in
an explosion, for example. It must be fast.
<p>There is a game_state variable, which is a structure that is a mish mash of various
global game state. Things like viewport coordinates and velocity
(arguably these should be separated out into their own structure), current score,
whether sound effects are on or off, whether music is on or off, some timer
variables to control the rates of the player firing lasers and bombs, and
various other global state. This is probably one of the messier areas of the
code, in terms of aesthetics.
<p>There is a "terrain" variable:
<pre>
struct terrain_t { /* x,y points of the ground, in game coords */
int npoints;
int x[TERRAIN_LENGTH];
int y[TERRAIN_LENGTH];
int slope[TERRAIN_LENGTH];
} terrain;
</pre>
This "terrain" variable contains the x,y coords of one endpoint of each line
segment making up the terrain, along with the slope of that line segment.
(The slope is used for figuring out how chunks of debris bounce.)
<p>Together, the game_state, terrain, and go[] arrays pretty much
encompass the state of the game (there are no doubt some minor
details not included in those three though.)
<p>So, how are these three things initialized at the beginning of a game?
The terrain array is filled in by a fairly simple fractal algorithm
at the beginngin of each level. The function which does this is called
generate_terrain(). Have a look at it if you're curious.
<p>The game_state is mostly initialized by initialize_game_state_new_level
and start_level. (Various things have to be initialized at various times,
so it's a bit scattered around.)
<p>The most interesting one is the game object array, go[]. This is also
initialized by start_level(). This start_level() function calls many
functions named add_xxxx, where xxxx is some kind of object, and it is
responsible for adding objects of that type into the go[] array.
(I use the word "type"
loosely here, for all elements of go[] are of the same type:
struct game_obj_t.)
<p>For example, here's a section of start_level():
<pre>
generate_terrain(&terrain);
add_buildings(&terrain);/* Some FreeBSD users report that */
/*add_buildings() causes crashes. */
/* Commenting this out on FreeBSD */
/* may help, but, no buildings. */
/* I've looked at the code, but */
/* don't see anything wrong with it. */
add_humanoids(&terrain);
add_bridges(&terrain);
</pre>
That obviously generates the terrain, adds buildings, humanoids (the guys you
pick up), and bridges to whatever level is about to start.
Then, there is an array defined in levels.h, called leveld[], which contains
a specification of what objects are in a level, and how they are
distributed in that level. This specification is examined and for each
kind of object another add_xxxx function does the work of adding that type
of object into the go[] array. Most of those add_xxxx functions loop through
however many of the type of object they are to create, and call another function,
add_generic_object, which adds an object with some things specified, and then
this returned object is further customized. add_generic_object() is worth
having a look at, It allocates an object with find_free_obj, then initializes
position, velocity, move and draw functions, color, vector of points (for drawing),
what type of object it is, whether it should be on the target list, whether
(and maybe how long) it should live, etc.
<p>Now we're ready to look at the advance_game() function, which gets called
by the timer we set up 30 times a second.
<a name="advance_game">
<h2>advance_game(), called once per frame.</h2></a>
<p>Have a look at the code.
<p>There are a few sections I'm going to ignore. There is a section for
handline when the game is paused. when the help screen is active, if the
user is in the process of quitting, and so on. Those are really detours
that don't come into play while the game is actively being played, and it's
a kind of messy state machine that flips between these modes, but these
special modes themselves do more or less the same thing as the regular
game-in-play mode: they check for user input, and draw the screen.
They skip the step of moving the objects, but I'm getting ahead of
myself. The gist of this advance_game function is as follows:
<ul>
<li>Check for joystick input
<li>Check for keyboard input (these need elaborating, but not just now.)
<li>For each object in the game, if it's alive, move it.
<li>Tell gtk to draw the screen (this triggers a call to
our function, main_da_expose(), which draws a frame. main_da_expose
is short for "main drawing area expose", and is the function which
gets called with the main drawing area (you can think of this as our
window) gets an "expose" event from gtk. The expose event happens
because a) some part of the window which was previously covered
(by another window) became uncovered, or more likely, b) some part
of the window has actively been changed, and the program has instructed
gtk to generate an expose event to get this change onscreen. The
calling of all the objects "move" functions was what is considered
"actively changing" the window, and we tell gtk to generate the
expose event by calling gtk_widget_queue_draw(main_da) in advance_game
after moving all the objects.
</ul>
<a name="expose">
<h2>main_da_expose(), called once per frame to redraw the screen.</h2></a>
<p>This function gets called whenever an "expose" event to the drawing
area in the main window comes in. That "expose" event gets triggered
30 times a second, because it's triggered at the end of advance_game
by an explicit call to gtk_widget_queue_draw().
<p>This function draws the terrain, and the "boundaries" on the
left, top, and right sides of the game area (if they're on screen)
then draws the star field (calls draw_stars), and then
calls <b>draw_objs</b> to draw all the objects. Then it calls draw_radar
(which only draws the borders of the radar). Then, it draws the
help screen or quit screen if either of those are active.
<p>The <b>draw_objs</b> function loops through all the objects in the game,
and if the object is alive, it draws it on the radar if it's the
type of thing that shows up on the radar, checks if it's on screen,
and if so, either draws it directly if the objects draw function
is NULL, or calls the object's draw function.
<a name="enemy"
<h2>How to add a new kind of enemy</h2></a>
<p>Ok, that may be enough exposition about how the game works in general.
Suppose you want to add a new type of enemy into the game, how would you
do it? What would be the steps?
<p>Let's take as an example the "big" rockets which were added to the game
rather lately. i'm taking this one as an example because it is rather simple,
and because I know the change went in as a single commit. Here is the diff
which represnets all the changes needed to add this new enemy type to the
game.
<ul>
<li><a href="https://github.com/smcameron/wordwarvi/commit/af21432c47b5c968584493e95ff1a80ab3d0fb44">levels.h and wordwarvi.c changes</a>
</ul>
<p>First let's have a look at the changes in levels.h, and see what's going on there.
<pre>
#define OBJ_TYPE_SHIP 'w' /* Bill Gates's state of the art warship. */
#define OBJ_TYPE_GUN 'g' /* ground based laser gun */
#define OBJ_TYPE_ROCKET 'r' /* ground based rockets */
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+#define OBJ_TYPE_BIG_ROCKET 'I' /* ground based rockets */</span>
#define OBJ_TYPE_SAM_STATION 'S' /* ground based missile launching station */
#define OBJ_TYPE_GDB 'd' /* GDB enemy */
#define OBJ_TYPE_OCTOPUS 'o' /* a big ol' octopus */
</pre>
Here, the line:
<pre>
#define OBJ_TYPE_BIG_ROCKET 'I' /* ground based rockets */
</pre>
get added to the code.
<p>For each kind of object there is a constant, OBJ_TYPE_XXXX for that object
which defines a unique character by which that type is recognized (crude, sure,
but it works.) So we add a new one for our new one in with the others.
<p>Next, there are a series of changes like this:
<pre>
{ OBJ_TYPE_SAM_STATION, 8, DO_IT_RANDOMLY, 0 },
{ OBJ_TYPE_GUN, 18, DO_IT_RANDOMLY, 0 },
{ OBJ_TYPE_KGUN, 25, DO_IT_RANDOMLY, 0 },
<SPAN style="BACKGROUND-COLOR: #ffd0d0">- { OBJ_TYPE_AIRSHIP, 1, 90, 0 },</span>
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+ { OBJ_TYPE_AIRSHIP, 4, 90, 0 },</span>
{ OBJ_TYPE_WORM, 1, DO_IT_RANDOMLY, 0 },
{ OBJ_TYPE_BALLOON, 1, DO_IT_RANDOMLY, 0 },
{ OBJ_TYPE_GDB, 9, DO_IT_RANDOMLY, 0 },
{ OBJ_TYPE_OCTOPUS, 1, 75, 1 },
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+ { OBJ_TYPE_BIG_ROCKET, 15, DO_IT_RANDOMLY, 0 },</span>
// { OBJ_TYPE_TENTACLE, 0, DO_IT_RANDOMLY, 0 },
</pre>
(It seems I increased the number of airships as well, but this is unrelated. Oops.)
These are just adding a specified number of the new object type OBJ_TYPE_BIG_ROCKET
into each level of the game. There are extensive comments in levels.h which explain
what those structures are.
<a href="https://github.com/smcameron/wordwarvi/blob/master/levels.h">Read them.</a>)
<p>Then we get into the changes to wordwarvi.c
<pre>
#define NROCKETS 20 /* Number of rockets sprinkled into the terrain */
#define NJETS 15 /* Number of jets sprinkled into the terrain */
#define LAUNCH_DIST 1200 /* How close player can get in x dimension before rocket launches */
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+#define BIG_ROCKET_LAUNCH_DIST 200 /* How close player can get in x dimension before rocket launches */</span>
#define MAX_ROCKET_SPEED -32 /* max vertical speed of rocket */
#define SAM_LAUNCH_DIST 400 /* How close player can get in x deminsion before SAM might launch */
</pre>
The above change is just defining a constant which gets used by the big rocket's
move function to know when to launch.
<pre>
@@ -381,6 +382,7 @@
score_table[OBJ_TYPE_MISSILE] = 50;
score_table[OBJ_TYPE_HARPOON] = 50;
score_table[OBJ_TYPE_ROCKET] = 100;
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+ score_table[OBJ_TYPE_BIG_ROCKET] = 400;</span>
score_table[OBJ_TYPE_SAM_STATION] = 400;
score_table[OBJ_TYPE_BRIDGE] = 10;
score_table[OBJ_TYPE_GDB] = 400;
</pre>
The above is adding to the score table the number of
points that are awarded for shooting down a big rocket.
If the type of object you're trying to add to the game
isn't shootable or bombable or destroyable,
(say, it's strictly scenery, like a rock,
or a tree or something) then you can skip this step.
<table>
<tr>
<td>
<pre>
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+struct my_point_t big_rocket_points[] = {
+ { 0, -35 },
+ { -5, -25 },
+ { -5, 5 },
+ { 0, 15 },
+ { 5, 5 },
+ { 5, -25 },
+ { 0, -35 },
+ { LINE_BREAK, LINE_BREAK },
+ { -5, 5 },
+ { -10, 15 },
+ { 10, 15 },
+ { 5, 5 },
+};
+</span>
</pre>
</td>
<td>
<img src="bigrocket.png"><br>
<p align=center>Image represented by code to the left.
<p align=center>Each square is 5 units
</td>
</tr>
</table>
The above change adds a list of instructions for drawing the big rockets.
This consists of a list of point coordinates to be connected by lines by
the drawing routine. These are a bit inconvient to set up, but... that's
just how it works. You can draw it out on graph paper, and type in the
coordinates, or if you have a good mind for spatial things, do it in your
head (it gets easier with practice.)
<pre>
struct my_vect_obj sleigh_vect;
struct my_vect_obj left_sleigh_vect;
struct my_vect_obj rocket_vect;
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+struct my_vect_obj big_rocket_vect;</span>
struct my_vect_obj jet_vect;
struct my_vect_obj spark_vect;
struct my_vect_obj right_laser_vect;
</pre>
Once you've made the points array, you've got to
make a vector thing to hold them. Just the way this thing works.
It's got a pointer to the array, and a count of the number of
elements. It gets initialized elsewhere.
<p>Now we get to some interesting code, the big rocket's move function:
<pre>
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+static void add_bullet(int x, int y, int vx, int vy,
+ int time, int color, struct game_obj_t *bullseye);
+void big_rocket_move(struct game_obj_t *o)
+{
+ int xdist, ydist, gl, i;
+ if (!o->alive)
+ return;
+
+ /* Should probably make a rocket-station, which launches rockets */
+ /* instead of just having bare rockets sitting on the ground which */
+ /* launch once, blow up, then that's the end of them. */
+
+ /* see if rocket should launch... */
+ xdist = abs(o->x - player_target->x);
+ if (xdist < BIG_ROCKET_LAUNCH_DIST && o->alive != 2 &&randomn(100) < 20) {
+ ydist = o->y - player_target->y;
+ // if (((xdist<<2) <= ydist && ydist > 0)) {
+ if (o->vy == 0) { /* only add the sound once. */
+ wwviaudio_add_sound(ROCKET_LAUNCH_SOUND);
+ o->alive = 2; /* launched. */
+ o->vy = -6; /* give them a little boost. */
+ }
+ // }
+ }
+ if (o->alive == 2) {
+ if (o->vy > MAX_ROCKET_SPEED - (o->number % 5))
+ o->vy--;
+
+ /* let the rockets veer slightly left or right. */
+ if (player_target->x < o->x && player_target->vx < 0)
+ o->vx = -2;
+ else if (player_target->x > o->x && player_target->vx > 0)
+ o->vx = 2;
+ else
+ o->vx = 0;
+
+ /* It's possible a gravity bomb smashes the rocket */
+ /* into the ground. */
+ gl = find_ground_level(o, NULL);
+ if (o->y > gl) {
+ wwviaudio_add_sound(ROCKET_EXPLOSION_SOUND);
+ explosion(o->x, o->y, o->vx, 1, 70, 150, 20);
+ remove_target(o);
+ kill_object(o);
+ return;
+ }
+
+ ydist = o->y - player_target->y;
+ if ((ydist*ydist + xdist*xdist) < 16000) { /* hit the player? */
+ wwviaudio_add_sound(ROCKET_EXPLOSION_SOUND);
+ do_strong_rumble();
+ explosion(o->x, o->y, o->vx, 1, 70, 150, 20);
+ // game_state.health -= 10;
+ remove_target(o);
+ kill_object(o);
+
+ for (i=0;i<15;i++) {
+ add_bullet(o->x, o->y,
+ randomn(40)-20, randomn(40)-20,
+ 30, YELLOW, player_target);
+ }
+
+ return;
+ }
+ }
+
+
+ /* move the rocket... */
+ o->x += o->vx;
+ o->y += o->vy;
+ if (o->vy != 0) {
+ explode(o->x, o->y+15, 0, 9, 8, 7, 13); /* spray out some exhaust */
+ /* a gravity bomb might pick up the rocket... this prevents */
+ /* it from being left stranded in space, not moving. */
+ if (o->alive == 1)
+ o->alive = 2;
+ }
+ if (o->y - player->y < -1000 && o->vy != 0) {
+ /* if the rocket is way off the top of the screen, just forget about it. */
+ remove_target(o);
+ kill_object(o);
+ o->destroy(o);
+ }
+}
+</span>
</pre>
Now, the above was largely copied from rocket_move, and modified for big_rocket_move.
(arguably the common code should be factored out of rocket_move and big_rocket_move).
<p>But, what's going on here? The move functions all get passed the game_obj_t pointer
of the object that's being moved. So typically they adjust o->x and o->y by
o->vx and o->vy in the trivial case. But they also do other things, like try
to figure ot where the player is, and what action to take, if any, based on this
information.
<p>Going through the above function step by step (keep in mind this gets
called 30x per second):
<ul>
<li>It calculates xdist, the horizontal distance between the rocket and
the player. If it's less than a certain amount, and we haven't already launched
and a roll of the dice comes up right (remember this gets called 30x a second),
then we launch. Launching consists of playing a sound, remembering that we've
launched, and setting our y velocity to -6.
<li>Then, if we've sometime previously launched,
<ul>
<li>we do some funny things to limit
the rockets speed based on it's position in the go[] array -- just to give the
rockets a bit of unpredictability.
<li>We veer the rocket slightly left or right depending on if the player is left
or right. This is done by adjusting vx slightly.
<li>Find the ground level at our current x position... if we're underground, we've
crashed... explode. (This can happen via gravity bomb). kill_object() frees
the object (marks it as available in the free_obj_bitmap array).
remove_target() removes ourself from the target list (since we're dead and all.)
<li>Find the ydist to the player, and calculate the square of the distance
to the player by pythagorean theorem. If this distance is less than a certain
amount, then we explode, and add a bunch of randomly moving shrapnel bullets
into the game.
</ul>
<li>Next, move the rocket by adjusting x,y by vx,vy respectively.
<li>Finally, spray out some exhaust (call to explode() which sprays sparks,
with some clever parameters to make them go in a particular direction),
and eliminate the rocket if it gets too far offscreen.
</ul>
<p>The next series of changes to the code which look like:
<pre>
@@ -4530,6 +4632,7 @@
case OBJ_TYPE_KGUN:
case OBJ_TYPE_TRUSS:
case OBJ_TYPE_ROCKET:
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+ case OBJ_TYPE_BIG_ROCKET:</span>
case OBJ_TYPE_JET:
case OBJ_TYPE_MISSILE:
case OBJ_TYPE_HARPOON:
</pre>
are adding the new object type into switch statements in laser_move()
and bomb_move() and gravity_bomb_move() to make them behave as they
do with other shootable objects.
<p>This change:
<pre>
@@ -7254,6 +7360,8 @@
rocket_vect.p = rocket_points;
rocket_vect.npoints = sizeof(rocket_points) / sizeof(rocket_points[0]);
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+ big_rocket_vect.p = big_rocket_points;
+ big_rocket_vect.npoints = sizeof(big_rocket_points) / sizeof(big_rocket_points[0]);</span>
jetpilot_vect_left.p = jetpilot_points_left;
jetpilot_vect_left.npoints = sizeof(jetpilot_points_left) / sizeof(jetpilot_points_left[0]);
jetpilot_vect_right.p = jetpilot_points_right;
</pre>
is setting up the points for the rocket drawing into the big_rocket_vect structure.
Basically just assigning a pointer to the beginning of the array containing the
points and a count of the number of elements in the array.
</p>Next is a function to add the big rockets into the levels at the beginning
of each level. This is probably largely copied from add_rockets().
<pre>
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+static void add_big_rockets(struct terrain_t *t, struct level_obj_descriptor_entry *entry)
+{
+ int i, xi;
+ struct game_obj_t *o;
+ for (i=0;i<entry->nobjs;i++) {
+ xi = initial_x_location(entry, i);
+ o = add_generic_object(t->x[xi], t->y[xi] - 15, 0, 0,
+ big_rocket_move, NULL, WHITE, &big_rocket_vect, 1, OBJ_TYPE_BIG_ROCKET, 1);
+ if (o != NULL) {
+ o->above_target_y = -35;
+ o->below_target_y = 15;
+ level.nbigrockets++;
+ }
+ }
+}
+</span>
</pre>
The initial_x_location function chooses an x location based on the specification
in entry (which is ultimately coming from levels.h). Notice the call to add_generic_object
to do most of the work, then the object is slightly customized. The above_target_y and
below_target_y adjust the vertical "hit zone' for the laser for this object.
<p>The remainder of the changes are added to start_level()
to make it interpret the instructions in levels.h for the new object type:
<pre>
@@ -10647,6 +10771,7 @@
add_socket(&terrain);
level.nrockets = 0;
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+ level.nbigrockets = 0;</span>
level.njets = 0;
level.nflak = 0;
level.nfueltanks = 0;
@@ -10669,6 +10794,9 @@
case OBJ_TYPE_ROCKET:
add_rockets(&terrain, &objdesc[i]);
break;
<SPAN style="BACKGROUND-COLOR: #d0ffd0">+ case OBJ_TYPE_BIG_ROCKET:
+ add_big_rockets(&terrain, &objdesc[i]);
+ break;</span>
case OBJ_TYPE_JET:
add_jets(&terrain, &objdesc[i]);
break;
</pre>
<a name="sound">
<h2>Adding new sounds</h2>
In adding the rocket, we used already existing sounds. What if you need a new sound?
This is quite easy. Consider <a href="https://github.com/smcameron/wordwarvi/commit/ecf301db2ea21338c18067f11e9af2b9d8f47251">these diffs</a>, which add new radar state transition sounds.
<p>For each sound you add, add 1 to the NCLIPS macro, and define a new number for your new sound.
In this case, two sounds were added, so NCLIPS was changed from 56 to 58, and the new new numbers were:
<pre>
#define RADAR_FAIL 55
#define RADAR_READY 56
</pre>
Then, in the function init_clips, add a call to read_ogg_clip for each sound
you want to add. For the two new radar sounds, these lines were added:
<pre>
wwviaudio_read_ogg_clip(RADAR_READY, "sounds/radar_ready.ogg");
wwviaudio_read_ogg_clip(RADAR_FAIL, "sounds/radar_fail.ogg");
</pre>
<p>Then, wherever in the code you want to play your new sound, just
add a line like:
<pre>
wwviaudio_add_sound(RADAR_FAIL);
</pre>
<p>That's it! Well, of course, you've got to have the sound, which should be 44100
samples per second, mono, encoded as an ogg, via oggenc, for example.
<a name="patches">
<h2>Making and submitting patches</h2></a>
<p>And here I'm being optimistic, and supposing someone will go to the
trouble to modify my game, and make something worthwhile and send it to
me to be included in the game. One can hope.
<p>If you've checked the code out as described at the top of this page,
and modified it to your liking, then to make a patch, all you've got to
do is execute the following command:
<pre>
git diff > mypatch.patch
</pre>
<p>You can then send me the patch. You can find my email address in
the file called AUTHORS.
</body>
</html>