3-D
Tankwars:Team Members:

3-D Tankwars (name to be decided) is a multiplayer first person tank fighting game in the spirit of the old Macintosh game Spectre. We anticipate implementing simple wire-frame 3-d graphics of a game world consisting of tanks and obstacles. Multiplay will use UDP to allow 32 players to compete at once. The game will track kills and games will be won when a player reaches a pre-set kill limit and a ranking will be calculated. Our major goals were to implement fun game-play (most of all), wire-frame based 3-d graphics, and UDP network support. Secondary goals are to incorporate some sort of sound.
The primary difference between this game and the others is that it uses a completely original 3-d renderer which creates the wireframe models of the tanks and displays them on the map. Furthermore the multiplayer aspect of the game allows up to 32 players to compete at once which is many more than many other groups have attempted. The game also required the creation of a completely original data structure for holding the data for each of the 32 tanks that will allow the information to be communicated among all 32 players quickly so that all computers will have relatively identical world maps. Finally, the sound implementation had to be relatively optimized, as the rest of the game uses up a considerable amount of system resources, in order for sound to play smoothly. Playing background music and sound effects added to the challenge.
Asynchronus Keyboard I/O:
Both the UDP networking and the keyboard data is handled asynchronously. The goal is to make the tank respond instantly to any changes in keyboard input. The standard interrupt 9 handler is sufficient to provide this support. Installing and removing the callback is the same as in previous mp’s but in order for movement changes to be instantaneous we must record key-presses as well as the key releases. The scan codes for key releases are the same as for key presses except the 7 bit of the scan code is 1 instead of zero. We then use a byte long bit-string to store the information about the status of the 5 key’s that require this sort of implementation in [_keyFlags] in our KeyboardISR. This information is then translated to movement information in _keyUpdate and is stored in [_movementFlags]. [_movementFlags] is defined as follows:
_movementFlags : flags which are used in the local machine and
will be set by the keyboard callback and _keyUpdate.
bits 7 : quit game
bits 6,5: unused for now
bits 4 : shoot
bits 3 : move
bits 2 : dir {0=back, 1=forward}
bits 1 : turn
bits 0 : turn dir { 0=right, 1=left }
_keyFlags is defined as follows:
bit 7,6,5 : unused
bit 4 : Space
bit 3 : LeftArrow
bit 2 : RightArrow
bit 1 : UpArrow
bit 0 : DownArrow
Storing Tank
Information:
Every piece of information intrinsic to each of the thirty two tanks is stored in the _TankArray. It holds information such as the tank’s location, the direction its facing, the amount of damage it can take, whether its shooting or not, it’s color, and which tank the local tank is aiming at. The x and y position are simply stored as integers in the x-y plane. The rotation vector (_rot_vector_x, _rot_vector_y ) contains a direction vector which points from the center of the tank out in the direction it is facing with a magnitude of it’s speed. Damage is simply an integer which indicates how much damage the tank has taken. IsShooting is a bit-string which was left slightly large to allow for addition of other weapons in the future. Bit 0 indicates whether to draw the laser as firing, bit 1 indicates whether to record damage from that tank to the tank indicated in target. If target is 0FFh then that indicates a missed shot. Color is simply the color the tank should be drawn in which is represented in the standard format alpha-red-green-blue format. This model of storing tank information is very useful for a multiple player game because each local computer only applies changes to the local tanks entry in the tankArray and then broadcasts it over the network to all the other tanks which copy that updated information into their own tanksArray. That way, each local tank can see the location and action information necessary to draw and implement game play. The _tankArray is coded as follows.
_tankArray resb 32*16
;word _x_pos
;word _y_pos
;word _rot_vector_x
;word _rot_vector_y
;word _damage
;byte _isShooting
;dword _color
;byte _target
Furthermore
there are several global pointers into the tankArray
which have numerous uses throughout the game.
[_playerNumber] which indicates which number
the local player is in the array and is indicated {1-32}. [_playerOffset]
which is the offset for the start of the entry indicated by [_playerNumber].
Moving the Tank on
the Plane:
The local tank must have some initial position. This can be accomplish by assigning each tank a unique start position on the board that will be the same for every computer in the game, regardless of which _playerNumber is the local tank. _initializeTankArray is our function for accomplishing this, here the initial start position is set, the start direction, the damage is set to max, it is marked as not shooting, the color is set so each tank is uniquely colored. Movement of the local tank is accomplished with _moveTank which moves the tank forward and backwards according to the settings in [_movementFlags]. This is simply done by either adding or subtracting the rotation vector from the position vector. This way the tank will translate along it’s direction. The rotation is slightly more complicated and is accomplish in function _rotate. _rotate calculates an angle from the rotation vector in radians using the FPU and adds or subtracts an angle from that angle according to [_movementFlags] and then converts that radian angle back into the direction vector by multiplying it’s sine and cosigne by the tank velocity constant.
Network overview:
Joining
The game has 3 different packet structures in use; Join, AckJoin, and Info. The first client to attempt to join by broadcasting out a join packet. If it does not receive a response after [_WaitCount] gets to TIMEOUT ticks, it assumes it is the first player and assigns itself player1. The next player to join will broadcast out a join packet. Player 1 will receive the packet and respond with an AckJoin packet containing the new total number of players and all their IPs (which is not needed in the present mode). The new client will make its player number the new total number of players in the packet and copy the list of IPs to its local list. All other clients will join similarly to the second player.
Gameplay
During the game, each player broadcasts its player info to everyone else whenever the data changes. The other players receive the data and copy it into their local cache of player data, which is used to display the game map and player stats.
3-D Drawing Algorithms:
;--------------------------------------------------------------
;-- RotateArray() --
;--------------------------------------------------------------
;;; void _RotateArray(long
*offset, short length, short angle)
;;; PURPOSE: rotates all points
to camera angle
;;; INPUTS: array @ offset (of
words), angle
;;; RETURNS: updated array with
x and y coords offset
;;; CALLS: None
;;[cos(ang) sin(ang)] => new
x = cos(ang)*x+sin(ang)*y
;;[-sin(ang) cos(ang)] => new y =-sin(ang)*y+cos(ang)*x
;; author : Allan Rudwick
takes an array of x,y,z, and rotates it angle degrees about the origin
;--------------------------------------------------------------
;-- Translate() --
;--------------------------------------------------------------
;;; void _Translate(word X,
word Y, long *offset, short length, short dir)
;;; PURPOSE: changes origin to
where camera is located
;;; INPUTS: array @ offset (of
words), x, y to translate to/from, direction (0=add/1 =sub <- sub mainly
camera)
;;; RETURNS: updated array with
x and y coords offset
;;;
CALLS: None
;; author : Allan Rudwick
adds a position offset to an array
of length length and x,y,z
values.
;--------------------------------------------------------------
;-- FlagPoints() --
;--------------------------------------------------------------
;;; void _FlagPoints(long
*offset, short length)
;;; PURPOSE: flag points that
are behind the camera or far away from the camera
;;; INPUTS: array @ offset (of
words)
;;; RETURNS: updated array
;;;
CALLS: None
;; author : Allan
Rudwick
changes the x-coordinate of points behind the camera to FFFF to prevent
them from being drawn
;--------------------------------------------------------------
;-- ArrayToPixels() --
;--------------------------------------------------------------
;;; _ArrayToPixels(dword *offset, word length, dword
*pixelOffset)
;;; PURPOSE: convert a point in
3D to 2D for display, uses FPU if X value = FFFF, leave as FFFF
;;; INPUTS: offset of array (of
words), length
;;; RETURNS: array of x,y screen locations in pixelOffset
;;;
CALLS: None
;; author : Allan
Rudwick
converts 3D array at offset of length to a 2D array for output to the
screen
;--------------------------------------------------------------
;-- DrawLine() --
;--------------------------------------------------------------
;;; void _DrawLine(short
X1, short Y1, short X2, short Y2, long pixel)
;;; PURPOSE: Implements Bresenham's Algorithm.
If either input X is FFFFh, dont draw line
;;; INPUTS: X1,Y1,X2,Y2,long
Pixel (color values)
;;; RETURNS: updated screen
buffer
;;;
CALLS: DrawPoint()
;; author : Allan
Rudwick
draws a line between 2 x,y points using the
algorithm described in class
;--------------------------------------------------------------
;-- SetupCamera() --
;--------------------------------------------------------------
;;; void _ParseArray(word
playernum)
;;; PURPOSE: Sets CamX, CamY, CamAng
to apply to tank
;;; INPUTS: [tankArray]
;;; RETURNS: CamX, CamY, CamAng
;;;
CALLS:
;; author : Allan
Rudwick
initializes the camera so that it is behind the local tank
Network Algorithms:
;--------------------------------------------------------------
;-- SetupNetwork() --
;--------------------------------------------------------------
;;; void SetupNetwork()
;;; PURPOSE: initialize the network
;;; INPUTS: _SocketHandler
;;; OUTPUTS: [_packoff], [_socket], _address structure, [_localIP]
;;; [_gotdatagram], [_hostname]
;;; RETURNS: -1 if failed
;;; CALLS: _AllocMem, _InitSocket, _Socket_create,
;;; _Socket_gethostname, _Socket_gethostbyname,
;;; _Socket_inet_ntoa, _Socket_htons, _Socket_bind
;;; _LockArea, _Socket_SetCallback, _Socket_AddCallback
;;; author: David Schmitz
Cleanup and return -1 if any call fails
Allocate memory for constructing packets
Initialize socket library by calling _Socket_create
Create a socket and store handle in [_socket]
-- not vital code, for future functionality
Get local hostname and store in [_hostname]
Use local hostname to get IP and store IP in [_localIP]
Write port to _address structure in network byte order
Bind socket to _address
Lock _gotdatagram and _SocketHandler to make them safe for
the interrupt to handle
Set callback for network to _SocketHandler function
Add callbacks for SOCKEVENT_READ event
To cleanup
Clear socket callback if it was set
close the socket if it was open
call _ExitSocket to cleanup after Socket libraries
return -1
;--------------------------------------------------------------
;-- SocketHandler() --
;--------------------------------------------------------------
;;; void _SocketHandler(int Socket, int Event)
;;; PURPOSE: sets [_gotdatagram] to 1 if interested packet recieved
;;; INPUTS: Socket and Event
;;; RETURNS: None
;;; CALLS: None
;;; author: David Schmitz
Checks if our socket caused event and if it is SOCKEVENT_READ
if so, set [_gotdatagram] to 1
otherwise just return
;--------------------------------------------------------------
;-- gotData() --
;--------------------------------------------------------------
;;; void gotData()
;;; PURPOSE: Handles when data is received
;;; INPUTS: [_socket]
;;; OUTPUTS: [_gotdatagram], [_buf]
;;; RETURNS: None
;;; CALLS: _Socket_recvfrom, CheckPacketType
;;; author: David Schmitz
clears [_gotdatagram]
calls _Socket_recvfrom to put received data in [_buf]
call CheckPacketType to interpret received data
;--------------------------------------------------------------
;-- CheckPacketType() --
;--------------------------------------------------------------
;;; void CheckPacketType()
;;; PURPOSE: Handle responses to received packets
;;; INPUTS: packet in [_buf], [_totalNumPlayers], [_playerNumber]
;;; OUTPUTS: [_playerNumber], [_IPArray], [_playerOffset]
;;; RETURNS: None
;;; CALLS: _SendPack
;;; author: David Schmitz
check first dword of [_buf] and see if it corresponds to any
of the defined packet headers
If Join Packet type
if player 1
if [_totalNumPlayers] < MAXPLAYERS send valid join packet
else send invalid join packet
else ignore packet
If ACK Join Packet type
if for valid player number
copy new player number to [_totalNumPlayers]
copy packet body to _IPArray
if [_playerNumber] is 0, make it new player number
also set new [_playerOffset]
If Info Packet type
ignore if packet from local player
if packet contains damage for local player
subtract from local player's health in _tankArray
if local health becomes less than 0, set exit in [_GameFlags]
set flag to transmit player info inform others of change in health
copy body of packet to appropriate player entry in _tankArray
--Quit and Game packets not implemented, for future functionality
return
;--------------------------------------------------------------
;-- BuildJoinPacket() --
;--------------------------------------------------------------
;;; void BuildJoinPacket()
;;; PURPOSE: Set up buffer to send Join Packet
;;; INPUTS: [_packoff], [_packlen], [_localIP]
;;; OUTPUTS: packet at [_packoff]
;;; RETURNS: None
;;; CALLS: None
;;; author: David Schmitz
Make Join packet - 8 bytes
Header is JOINHEAD (4 bytes, all zero's)
Body is the [_localIP] of joining player
;--------------------------------------------------------------
;-- BuildAckJPacket() --
;--------------------------------------------------------------
;;; void BuildAckJPacket()
;;; PURPOSE: Set up buffer to send Ack Join Packet
;;; INPUTS: [_packoff], [_packlen], [_IPArray], [_totalNumPlayers]
;;; OUTPUTS: packet at [_packoff]
;;; RETURNS: None
;;; CALLS: None
;;; author: David Schmitz
copy packet header to start of packet buffer, last byte of
packet header is [_totalNumPlayers]
uses string instructions to copy [_IPArray] to packet buffer
;--------------------------------------------------------------
;-- BuildInfoPacket() --
;--------------------------------------------------------------
;;; void BuildInfoPacket()
;;; PURPOSE: Set up buffer to send Info Packet
;;; INPUTS: [_packoff], [_packlen], [_playerNumber], _tankArray
;;; RETURNS: None
;;; CALLS: None
;;; author: David Schmitz
copies packet header to start of packet buffer, last byte of
packet header is [_playerNumber]
uses string instructions to copy player entry of _tankArray to buffer
;--------------------------------------------------------------
;-- BuildQuitPacket() --
;--------------------------------------------------------------
;;; void BuildQuitPacket()
;;; PURPOSE: Set up buffer to send Quit Packet
;;; INPUTS: [_packoff], [_packlen], [_playerNumber]
;;; RETURNS: None
;;; CALLS: None
;;; author: David Schmitz
copies QUIT packet header and packet length to [_packlen]
last byte of packet header is [_playerNumber]
;--------------------------------------------------------------
;-- BuildGamePacket() --
;--------------------------------------------------------------
;;; void BuildGamePacket()
;;; PURPOSE: Set up buffer to send Game Packet
;;; INPUTS: [_packoff], [_packlen]
;;; RETURNS: None
;;; CALLS: None
;;; author: David Schmitz
copies Game header to head of packet and length to [_packlen]
packet not used so no further spec
;--------------------------------------------------------------
;-- _SendPack() --
;--------------------------------------------------------------
;;; int _SendPack(int .PacketType)
;;; PURPOSE: Sends packet of given entry in _packetArray
;;; INPUTS: PacketType, [_socket], [_packoff], _address array
;;; RETURNS: -1 if failed
;;; CALLS: Build*Packet, _Socket_htonl, _Socket_sendto
;;; author: David Schmitz
takes in an argument which corresponds to a packet type to send
calls entry in array corresponding to call of build function for
respective packet
set _address structure address to BCASTADDR
call _Socket_sendto to send packet in packet buffer
return -1 on error
reset address to 0's (INADDR_ANY)
return
;--------------------------------------------------------------
;-- CleanupNet() --
;--------------------------------------------------------------
;;; void CleanupNet()
;;; PURPOSE: Cleans up after net code and prepares for closing
;;; INPUTS: None
;;; RETURNS: None
;;; CALLS: _Socket_SetCallback, _Socket_close, _ExitSocket
;;; author: David Schmitz
call _Socket_SetCallback to remove callback
call _Socket_close to close socket
call _ExitSocket to cleanup after socket library
Attribution Notes on Network Algorithms:
parts of SocketHandler and SetupNetwork were modified code from UDP
examples by Peter Johnson. _Socket* calls are all part of winsock
wrappers present in pmodelib. _ExitSocket and _InitSocket are also
part of pmodelib to initialize libraries and network data structures
_address is a structure defined as part of pmodelib
;; ------------------------------------------------------
;; void _rotate()
;; ------------------------------------------------------
;; inputs : _movementFlags
- stores which movements are to occur this tic
;; outputs : _tankArray -
updates the rotation of the local tank.
;; author :
Peter F. Klemperer
This
function calculates rotates the local tank according to _movementFlags. When a turn is indicated, the rotation vector
is converted into a radian angle using the FPU arctangent function, a constant
turn amount is added or subtracted from the angle, and the angle is converted
back into a vector using the FPU sincos function and
then multiplied by the velocity constant.
;; ----------------------------------------------
;; -- CheckNewLocation() --
;; ----------------------------------------------
;; int _CheckNewLocation(word _newX, word
_newY )
;; inputs : _newX - the new x postion to be tested.
;; _newY
- the new y postion to be tested.
;; _tankArray
- holds all the tank information
;; _playerOffset
- holds the local tank offset in tankArray.
;; _lastPlayerOffset
- holds the last legal tank offsetin tankArray
;; outputs : return 0 if legal, 1 if not legal.
;; notes
: bx contians _playerOffset
;; cx
contains the offset to the index of tankArray being
checked.
;; author : Peter Klemperer
This
function checks whether the newPosition is too close
to all the other tanks in _tankArray. NOTE!
If you check every tank entry you will always fail because the new
vector will be inside the collision distance of it’s
own tank’s original location, so that entry must be skipped.
;;
-----------------------------------------------------------
;; -- void _moveTank() --
;;
-----------------------------------------------------------
;; input : _movementFlags -
indicates the movement for this tic.
;; _tankArray - holds the
information for all the tanks.
;; _playerOffset - holds the offset
for the
;; local tank
in _tankArray
;;
;; output : _tankArray - will
hold the updated movement for the local tank
;; eax holds 1 if tank moved, 0 if
not
;; calls : _CheckNewLocation -
checks legality of chosen movement.
;; notes : ebx holds the _playerOffset.
;;
author : Peter Klemperer
This
function moves the tank forwards and backwards according to _movementFlags. The
movement is accomplished by simply adding or subtracting the rotation vector
from the position vector in the local tanks entry in _tankArray. It then calls CheckNewLocation
to vertify that the new position (indicated in _newX and _newY ) is not within the collision distance of any of the other
tanks in the _tankArray. If new position is legal, then the new values
replace the old ones in the local tank entry.
;--------------------------------------------------------------
;-- shootFlags() --
;--------------------------------------------------------------
;;; void shootFlags()
;;; PURPOSE: Cleans up after net
code and prepares for closing
;;; INPUTS: [_movementFlags] - flags whether shoot button was pressed
;;; OUTPUTS: [_TankArray] - set the ISSHOOTING flag for the local tank
;;; [_movementFLags]
- resets the flags for another shot.
;;; CALLS: none
;;; AUTHOR: Peter Klemperer
This
function check
whether each tank is firing and if it is, whether it’s firing at the local
tank. If so, the local tank has been
damaged and damage is substracted from the local
tanks Damage entry.
;; ---------------------------------------------------
;; _initializeTankArray - initializes the tank array
;; --------------------------------------------------
;; inputs : _tankArray
;; outputs : _tankArray - updates the values in the array.
;; note :
the tanks are all initially on the y-axis with position dictated by playerNumber.
;; initial damage amount is set by
STARTDAMAGE and color is all green for now.
;; author : Peter Klemperer
Initiliazes the tankArray so that all tanks have a unique start position. Care must be used that the start locations
are far enough apart that they are not within one anothers
collision distance. This function is
covered more heavily earlier in the notes.
void _InitializeSound(void) Robert Tao
- Initializes sound to get ready to play making use of DMA and SB16 libraries.
- Loads the required wav files into memory so they can be played.
Inputs: None
Outputs: Updated DMA Buffer
Calls: _AllocMem, _OpenFile, _ReadFile, _LockArea, _CloseFile, _SB16_Init, _SB16_GetChannel, _SB16_SetFormat, _SB16_SetMixers, _DMA_Allocate_Mem, _DMA_Lock_Mem
Notes: Make sure all the necessary variables are set when gathering information from the called functions.
void _PlaySound(void) Robert Tao
- Starts constantly transferring to DMA Buffer.
- Starts constantly playing sound in DMA Buffer.
Inputs: None
Outputs: None
Calls: _DMA_Start, _SB16_Start
Notes: Make sure the interrupt times are set after the intended portion of buffer has been played.
void _RefillDMABuffer(void) Robert Tao
- Refills Appropriate half of DMA Buffer
Inputs: None
Outputs: Updated DMA Buffer
Calls: _SB16_Start, _DMA_Stop, _SB16_Stop, _InitializeSound, _PlaySound
Notes: While one half of the buffer is being played, update the other half. This way, there will not be any awkward pauses or stuttering in the song. This is where adding up sounds to play two sounds can be implemented. MMX commands can be used to add up the sounds to play multiple sounds. Use flags set in different parts of the program to tell whether a sound should be played or what part of the buffer to refill. If the song is about to end, switch to a single cycle so that it can be ended. Afterwards, loop the song so that it plays constantly.
void _SoundISR(void) Robert Tao
-Increments variables to check progress of DMA buffer
Inputs: None
Outputs: None
Calls: None
Notes: Update [_LoadBuffer] and [_ISRCalls]
;--------------------------------------------------------------
;-- SetupNetwork() --
;--------------------------------------------------------------
;;; void SetupNetwork()
;;; PURPOSE: initialize the
network
;;; INPUTS: None
;;; RETURNS: -1 if failed
;;; CALLS: _AllocMem,
_InitSocket, _Socket_create,
;;; _Socket_gethostname, _Socket_gethostbyname,
;;; _Socket_inet_ntoa, _Socket_htons,
_Socket_bind
;;; _LockArea, _Socket_SetCallback,
_Socket_AddCallback
;;; author: David Schmitz
;--------------------------------------------------------------
;-- SocketHandler() --
;--------------------------------------------------------------
;;; void _SocketHandler(int Socket, int Event)
;;; PURPOSE: sets [_gotdatagram] to 1 if interested packet recieved
;;; INPUTS: Socket and Event
;;; RETURNS: None
;;; CALLS: None
;;; author: David Schmitz
;--------------------------------------------------------------
;-- gotData() --
;--------------------------------------------------------------
;;; void gotData()
;;; PURPOSE: Handles when data
is received
;;; INPUTS: [_gotdatagram]
;;; RETURNS: None
;;; CALLS: _Socket_recvfrom,
CheckPacketType
;;; author: David Schmitz
;--------------------------------------------------------------
;-- DrawTank() --
;--------------------------------------------------------------
;;; void _DrawTank(short X, short Y, short width, short length, short
height1, short height2, long color, word angle, word damage, word tankid)
;;; PURPOSE: call PointToPixel, Drawline multiple
times, draw a pyramid
;;; INPUTS: X,Y, halfwidth, halflength, height1,
height2, color, angle (w/ respect to xy plane)
;;; RETURNS: updated screen
buffer
;;; CALLS: ArrayToPixels, DrawLine, CenterArray, RotateArray, TranslateArray
;;; author: David Schmitz
;--------------------------------------------------------------
;-- CheckPacketType() --
;--------------------------------------------------------------
;;; void CheckPacketType()
;;; PURPOSE: Handle responses
to received packets
;;; INPUTS: packet in [_buf]
;;; RETURNS: None
;;;
CALLS: _SendPack
;;; author: David Schmitz
;--------------------------------------------------------------
;-- BuildInfoPacket() --
;--------------------------------------------------------------
;;; void BuildInfoPacket()
;;; PURPOSE: Set up buffer to
send Info Packet
;;; INPUTS: None
;;; RETURNS: None
;;;
CALLS: None
;;; author: David Schmitz
;--------------------------------------------------------------
;-- _SendPack() --
;--------------------------------------------------------------
;;; int
_SendPack(int .PacketType)
;;; PURPOSE: Sends packet of
given entry in _packetArray
;;; INPUTS: PacketType
;;; RETURNS: -1 if failed
;;;
CALLS: Build*Packet, _Socket_htonl, _Socket_sendto
;;; author: David Schmitz
;--------------------------------------------------------------
;-- CleanupNet() --
;--------------------------------------------------------------
;;; void CleanupNet()
;;; PURPOSE: Cleans up after
net code and prepares for closing
;;; INPUTS: None
;;; RETURNS: None
;;;
CALLS: _Socket_SetCallback, _Socket_close,
_ExitSocket
;;; author: David Schmitz
;--------------------------------------------------------------
;-- MP5Main() --
;--------------------------------------------------------------
_MP5Main
call _Initialize ;
mov byte[_movementFlags],
0
call _initializeTankArray
;; Call to join game
invoke _SendPack, dword 0
test eax, eax
js near .EndGameLoop
.MainGameLoop:
cmp byte [_gotdatagram],
1
je .getdata
test byte
[_GameFlags], 00000001b ; IF ESC BYTE INDICATED QUIT
jnz near .EndGameLoop