|
Welcome to
the first page o’ answers to all of those zany mod questions out there. First
off, allow me to introduce myself…I’m Keith Fuller, a programmer at Raven Software
and one of the few people on Earth lucky enough to have been on the Soldier
of Fortune development team. My role on the team was that of “game coder”, which
means I didn’t focus on the techy rendering stuff or the network pipeline but
rather on most of the stuff comprising the actual gameplay mechanics of SOF.
In other words, of all of the code in the game I’m most familiar with the game
dll and the player dll, which is the code we’ve released to the general public.
I’m going
to have to explain a few ground rules before I can continue. In various other
public forums I have made myself available for general questions and the occasional
support role, but here I’m only going to talk about code-related issues. That’s
going to be a letdown for the three dozen people who emailed me in the past
week asking me to give them a CD key for the game or tell them how to bypass
the violence lockout. Nonetheless, that’s the way it is. Also, this isn’t the
place for questions like, “How do I turn on the console” or “How do you get
past the second level” or anything like that. We have several wonderful forums
scattered about the Web that can help you with such issues. I’m only going to
discuss code here. And now, on with the show.
QUESTION 1:
Adding new weapons seems to be very
different to how it worked under Quake 2 or Kingpin, could you take us through
step by step how to add a new weapon with an alternate fire mode.
I’ll
also include as part (b) of Question 1 another piece of mail that asked something
very similar:
QUESTION 1b:
How can you make John woo style dual pistols??
May as well
start with the tricky ones, eh? OK, I’m going to give you the necessary information
to replace one of the existing weapons, let’s say the 9mm pistol, with another
weapon of your choosing, let’s say some twin pearl-handled .45’s that’d make
Chow Yun Fat drool. Allow me to start off with a bit of a warning, though.
If you think
you can just take the existing in-view weapon models for either of the pistols
that shipped with SOF and create a “dual pistols” mode with them, I’m sorry
to say that’s not going to happen. You’ll need someone to create some weapon
models and animations for you if you want to perform this particular modification.
If you need tips on how to do that, send me some mail and I’ll get one of our
artist-types to point you in the right direction. That having been said we can
get on with the tutorial.
A brief explanation
of the game’s basic architecture is in order. For the sake of our discussion,
there are five main parts to SOF:
1)
The client. That’s the code on the user’s end that handles stuff like
keyboard input and putting stuff on the screen. The client code is part of the
SOF.exe file you probably have hanging out in c:\program files\raven\sof.
2)
The renderer dll, also known as ref_gl.dll. This code is responsible
for actually drawing stuff on the screen.
3)
The server. Also part of SOF.exe, this code only runs on, you guessed
it, the server’s computer. It determines what actually happens in the game and
continually tells all of the player’s computers (or clients) what’s going on
by sending a whole bunch of data across the LAN or Internet.
4)
The game dll, gamex86.dll. This code is loaded by the server and contains
all of the game mechanics. Which body part gets hit by a bullet, which sound
should be player, which damage texture should be used…all of that’s determined
here.
5)
The player dll. This guy may be a little harder to understand, but here
are the basics. Both the server and the client load this exact same chunk of
code and call the exact same functions inside of that code so that certain information
such as your ammo count and your weapon inventory is kept properly synchronized
across the network during multiplayer games. Remember, the client takes your
keyboard input, like the fact that you just fired the rocket launcher, sends
it across the network to the server, the server calls some functions in the
game dll to figure out just what that rocket launch means, and then the server
sends some information back to the client, like “draw cloud of body parts at
location of rocket impact.” Because network connections can be pretty laggy
(No, Keith. Really? I didn’t know that.) there could be some big differences
at any point in time between what the server thinks about the player and what
the client thinks about the player. By managing inventory-related stuff in the
player dll, though, the game dll (which loads player.dll) can call the exact
same functions as the client (which also loads player.dll) and therefore the
server-side code can have a picture of the player’s inventory that’s very similar
to the client’s. So you’ll see a lot of code in the player dll that looks like:
if
(isClient)
{
//
I’ve been loaded by the client so perform some client-//related task here
}
else
{
//
I’ve been loaded by the server-side game dll so perform //some server-related
task here
}
So there you
have it. SOF in a nutshell. Hopefully that gives you an idea of the kinds of
things we’ll need to do in the player dll and why we’ll be monkeying with it
to produce our stylin’ dual .45’s.
Take a look
at the file player/w_types.h. You’ll find an enumerated type that looks like
this:
typedef
enum
{
SFW_EMPTYSLOT = 0,
SFW_KNIFE,
SFW_PISTOL2,
SFW_PISTOL1,
SFW_MACHINEPISTOL,
SFW_ASSAULTRIFLE,
SFW_SNIPER,
SFW_AUTOSHOTGUN,
SFW_SHOTGUN,
SFW_MACHINEGUN,
SFW_ROCKET,
SFW_MICROWAVEPULSE,
SFW_FLAMEGUN,
SFW_HURTHAND,
SFW_THROWHAND,
SFW_NUM_WEAPONS
}
weapons_t;
You’ll probably
note that this looks a lot like the list of weapons in the game. That’s because
this is the list of weapons in the game. Everywhere in the code where
you see the prefix SFW_ you know the code is referring to one of the available weapons’
model or sound or ammo count or something (with the exception of SFW_HURTHAND and SFW_THROWHAND, but I’ll leave it to you to figure out what those
are). An important point here is that this same list is also found in qcommon\w_types.h,
meaning the client code and the game dll will also refer to this enum, so you’ll
want to do something like a find-and-replace in all of the client, game, and
player files swapping SFW_PISTOL1 (which
is the 9mm pistol) for SFW_DUAL_PISTOLS.
A quick search
for SFW_PISTOL1 reveals a fairly long
list of occurrences – I’m just going to touch on the highlights.
Later on in
the list of SFW_PISTOL1 references
you’ll see several mentions in the player dll. First, let’s discuss the array
in player\w_weapons.cpp, attackInfoExtra_t
attacks[]. This array contains some useful information about the noise,
accuracy, and kick of each weapon, but it also contains another important item
– the attack ID. Every item in this array that begins with ATK_ refers to a specific attack. The rocket
launcher, for instance, is referred to in the code as SFW_ROCKET, while its attack is referred to as ATK_ROCKET and it’s alternate attack is ATK_ROCKET_ALT. So, to continue our replacement
of the 9mm with the dual pistols, you’ll want to hunt down references to ATK_PISTOL1
and replace them with something like ATK_DUAL_PISTOLS.
To give our dual pistols an alternate attack, look at what’s going on with the
ATK_ROCKET_ALT attack ID and add ATK_DUAL_PISTOLS_ALT. I advise starting with
the main list of attack ID’s (attacks_e)
found at the top of player\w_public.h.
One of the
most important places that the attack ID’s are mentioned is the CWeaponInfo
wInfo[] array, because it lists a bunch of important data for each attack
in the game. It’s pretty self-explanatory, just make sure you check it out.
There’s some
more important stuff to replace in player\w_weapons.cpp. You’ll want to change
the “Pistol1” entry in the weaponInfoExtra_t extraInfo[] array to reflect
the new “dual pistols” information. Incidentally, the name portion of the weaponInfoExtra_t struct is what tells the game where to look
for your new twin-pistols models. So if you put “JohnWoo” in there it’ll look
for your models in the ghoul\Weapon\Inview\JohnWoo folder.
Just below
the extraInfo array you’ll need something
like…
dualPistolsInfo
dual45s(&extraInfo[3]);
…to
replace the line…
pistol1Info
glock(&extraInfo[3]);
This means
you’ll have to create a new class, dualPistolsInfo. Take a look at what’s going
on in the existing class, pistol1Info,
and you should be able to copy that. Then, in the weaponInfo_c *weapInfo[SFW_NUM_WEAPONS] array, replace &glock with &dual45s and you’ve done a lot of the setup.
There are
many more places in the code where you’ll need to replace references to SFW_PISTOL1,
but most of those are pretty straightforward after this.
I hope that
helps. If you get yourself the models you need and you try all of the above
and still have more specific questions, send ‘em in.
QUESTION
2: I wonder if i can do somting, so i can
carry more ammo and weapons.I think the limit is little to lo.Is there any way
i can change it ?is there a file there i can change it in or ??
Actually,
you can do this by starting the game and, under “Custom Settings”, click “Cheats
Available”. But since that answer has nothing to do with code, allow me to give
you one that does. Look for the int ammoMaxes[]
array in player\w_inven.cpp. That lists, for each of the ammo types in the game,
the maximum amount of ammo the player can carry at any given time. If you up
those numbers, you can pick up ammo to your heart’s content and Mullins can
become even more of a walking arsenal than he already is.
QUESTION
3: Could you point me to an example in the
source or provide a snippett of code on how to detect if a projectile is hitting
a wall at an angle or perpendicular. And . . . how to determine the velocity
it is traveling at the time of impact.
I
want a grenade launched to explode on impact on touch ONLY if the grenade is
impacting at between a 30 and 90 degree angle to the normal and only if the
velocity is above a certain amount.
This
is a cool question, IMHO, cuz it involves a wee bit of vector mathematics, which
is something of a favorite of mine.
The
first part is pretty simple…finding the velocity. I’m going to assume you know
how to get your hands on the edict_t*
that points to the grenade in question. Once you have that, let’s call it edict_t *gren, the velocity of the object is
in gren->velocity.
You
want to make sure that when you set up your grenade object (when it first gets
thrown/launched) you give it a touch function, a line of code that’ll look something
like…
gren->touch = GrenadeTouch;
Then,
in the GrenadeTouch function you’ll
do all of the impact code testing for angles and such. If you look at existing
touch functions you’ll see that they’re defined much like the following…
void itemAmmoTouch(edict_t *self,
edict_t *other, cplane_t *plane, mtexinfo_t *surf)
In
the case of our grenade, self refers
to the grenade, other refers to whatever
it hits (that’d be the worldspawn if you’re talking about hitting walls and
floors), plane refers to the geometric
properties of the polygon that was touched, and surf contains the surface properties of that
polygon. If we’re talking about the angle of impact, we’re going to be concerned
with plane.
Here’s
the definition of the cplane_t structure…
typedef
struct cplane_s
{
vec3_t normal;
float dist;
byte type; // for fast side tests
byte signbits; // signx + (signy<<1) + (signz<<1)
}
cplane_t;
It’s called
cplane_t because any given polygon
is represented in the game as a plane, the geometric term for a flat surface.
What we care about here is the normal field which, oddly enough, refers to the normal vector
of the polygon. What is the normal vector, you might ask. That’s the vector
pointing out perpendicularly from the polygon’s surface. So if the polygon you
hit was a flat floor, the normal vector for that surface would point straight
up [0, 0, 1].
Now, a handy
little function you should become familiar with is DotProduct(),
which takes two unit vectors and basically returns the cosine of the angle between
them. That sentence may have been a little term-heavy, so I’ll break it down.
A unit vector is a vector pointing in any direction with a length of one. Like
the “up” vector, [0, 0, 1]. As for the cosine of an angle, I won’t bore you
with trigonometric definitions here, but suffice it to say that the cosine of
the angle between two unit vectors is a number anywhere between -1 and 1, -1
if the vectors are pointing exactly opposite each other, 0 if the vectors are
exactly perpendicular to each other, and 1 if the vectors point in exactly the
same direction.
You can get
a unit vector from any vector in the game by calling VectorNormalize()
and passing in the non-unit length vector. In the case of cplane_t’s
normal, that vector is always a unit vector so you don’t have to sweat it, but
for the purposes of this question we’ll consider the velocity vector of the
grenade, which undoubtedly is not a unit vector. First, you’d want to make a
copy of the velocity vector using VectorCopy(), then normalize it to get the
unit vector in the direction of the grenade’s velocity. The code inside your
grenade’s touch function might look like this:
vec3_t
grenVelocity;
VectorCopy(gren->velocity,
grenVelocity);
VectorNormalize(grenVelocity);
To bring this
all together, you’ll want to get the unit vector for the grenade’s velocity,
perform the dot product of this unit vector and the normal for the plane the
grenade hit. Since the grenade will be heading toward this surface and the normal
is pointing away from the surface, the result of the dot product will be negative,
so I’d say negate it (which will turn it into a positive number) to make things
easier on yourself. Then compare the result with the cosine of whatever angles
you’re after, in this case 0 and 60 degrees (because we’re looking at 90 and
30 degrees from the surface, not the normal). Mind you, before you do any of
this, you might want to make sure that what the grenade hit is, in fact, level
architecture rather than a player or something. So here’s what your grenade
touch function might look like…
void GrenadeTouch(edict_t *gren, edict_t
*other, cplane_t *plane, mtexinfo_t *surf)
{
vec3_t grenVelocity;
float dotResult = 0, cosine60 = 0.5, cosine0 = 1;
// did we hit the world?
if (other == &g_edicts[0])
{
// get the unit vector for grenade’s velocity
VectorCopy(gren->velocity, grenVelocity);
VectorNormalize(grenVelocity);
// get the dot product of the velocity and the normal of our impact
surface
dotResult = DotProduct(grenVelocity, surf->normal);
// negate it so it’s easier to read
dotResult *= -1;
// if we impact sharply enough, detonate
if ( (dotResult <= cosine0) && (dotResult >= cosine60)
)
{
// BLAMMO!!
}
}
else
{
// looks like I hit a player or something. maybe I should just
explode.
}
}
There you
go. One point I’ll make about the above function is that you really only need
to check if the dot product result is greater than or equal to the cosine of
60 degrees, because, by definition, it’ll always be less than or equal to the
cosine of 0 degrees. I just left that check in there for simplicity in understanding
the process.
That’s all
I’ve got time for this week. I’ll try to wade through the remaining questions
you folks sent me and provide more answers next time. If you need more clarification
on an issue, or if you come up with more puzzlers, send ‘em my way.
Keith Fuller
Programmer
Raven Software
kfuller@ravensoft.com
|