Author: by Victor Jimenez (aka Weektor) Part IV - Adding Items and Monsters
This is probably the piece of the tutorial that most people have been
wanting. You have also probably been asking yourself "Why is this guy going
through all this?" Granted that simple things like changing the way that
something works is pretty easy and doesn't require a lot of code
changes. However, to do that in a way that allows multiple non-cooperating
developers to integrate their modifications is something else.
Now let me explain something first. This modification is not about "how" to
make a new weapon or item. That involves game design and how that would effect
the game balance. What I am going to do here is describe how to implement
the weapon that you designed so that it can be used.
Remember, all this is being done dynamically, on the fly as it were. And
when I say dynamically, I mean 'DYNAMICALLY'. For debugging purposes,
you could create the 'LoadDLLModule' that adds commands that you can use on
the console to load and manipulate particular DLLs. Or imagine a mod where two
contestants face off and a weapon spawns randomly into the map. It can
spawn anywhere and you could even have the spectators vote on what
weapon. The contestant that gets to the weapon first gets to keep it and
use it. Or instead of a weapon, you could spawn a monster into the arena
and the last one left wins. In theory, you could even define a 'General
Monster Behavior Engine!(tm)' and have the monster's behavior, looks,
actions, etc. all defined by external data that is read in from an outside
file. That is truly cool and ultimately what I want to do with all this
stuff. These are all the things that are capable of being done from this mod.
As a matter of fact, the only thing that keeps Quake from being 100%
dynamic is the very real need to know the visibility of walls ahead of
time. Otherwise, you could configure a "Quake Server" that fed maps to
people as they explore their environment.
In this tutorial, we are going to discuss how to create and add an item and
do the same for a monster. We will make them similar in what you, the mod
author, need to do but are handled somewhat differently under the
hood.
We'll handle items first. This is where we can see how object-oriented the
present id code really is. It also is where a real object oriented language
would have had the greatest impact.
Items are described by a simple structure, found in g_local.h at line #200:
These structures are maintained in a globally defined array in the g_item.c
file around line # 1100. The array is called, appropriately enough,
itemlist. This array is statically defined to hold the items that
immediately follow. Thus the array is no bigger or smaller that it needs to
be. Since this is the case, you always see mod authors appending data to
the end of this structure. Interestingly enough, there is a constant
defined in the q_shared.h file on line 65 that's called MAX_ITEM. It
doesn't control the size of the itemlist array, but rather the size of the
inventory that the player can carry. The game dll code uses the inventory
array in a one to one mapping between the items in inventory and the
itemlist array. Furthermore, it will place the amount of the item in the
inventory array.
If we had an object oriented language, we could redefine the access method
for the itemlist array and the have the object do whatever pointer
manipulation, list walking etc. to provide an index access
method. Similarly, the inventory could be changed for the player so it
still behaved the same way and allowed expanded lists, etc. This way, none
of the code would change. Of course, you could do the obvious thing, which
I like even better.
Go to line 1100 of g_items.c and change the line from
to:
This makes the itemlist array able to contain up to MAX_ITEM number of new
items. Now all we need to do is manage the items and the itemlist. If we
provide routines to do this and exclusively use these functions to access
and manipulate the itemlist array, all the mod authors will be able to use
this mechanism.
We're not quite done with the g_items.c file however. If you search for a
function called InitItems, you will see that it is calculating the number
of items in the game based on the size of the itemlist array. Well, we just
made that MAX_ITEM in size. What we really want is a count of items that
have been placed in the array. So make the function look like this:
Ideally, this function would set num_items to zero (0!) and the game code
would add it's items using the provided functions. That's what I intended
to do for a TC construction kit anyway.
Well, let's add the functions to manipulate the items. Open a file called
u_entmgr.h and put the following into it:
We are implementing a set of functions that will let us insert and remove
items at will.
So now open a file called u_itemmgr.c and insert the following code:
The InsertItem function scans through the list, looking for an empty slot
to insert the new item. If it finds an empty slot, it will set the internal
game dll pointers to your information. Remember to maintain the copy of the
data that you inserted into the game, at least until you remove it. If you
don't, the game will more that likely crash. Big Time. Trust me.
I was a little leery of putting a remove function into the game. If you
look at the current u_loaddll.h, you will see no mention of a remove
function in the user import structure. However, a friend of mine wanted to
make weapons that were confined to a particular area of a map and if you
went out of the area, the weapon would go away. I humored him. The
RemoveItem is the result. It attempts to set the variables to safe values.
You may be wondering about how do you find an item in the itemlist
array. Well, id kindly provided a FindItem function that you can request a
pointer from the FindFunction stuff in Part 3 of the tutorial.
Now go into the u_loaddll.h and change the userdll_import_t structure by
adding the following lines right after the FindFunction declaration:
Next, go into the u_loaddll.c file and add the include for the new header
that we created under the "u_findfunc.h" like this:
and go to around line 168, inside the InitializeUserDLLs() function, right
after the line where you are setting the FindFunction variable and insert
the following two lines:
You are now ready to start adding items to the game.
We need to explain some of the underlying structure of the item structure,
especially about the functions that you have to supply.
The first function is the pickup function. The pickup function is what gets
called when you run over an item. It determines if you can pick the item up
or not and configures the player's variables if it was pickup-able. If the
player picked up the weapon, the function is to return true. If not, then
you return false. Health is handled by this function.
The next function is the use function. The use function is for whenever you
want to use the item. If the item is a weapon, this means that whenever you
switch to this weapon, it will be called. If the item is a Powerup,
whatever effect that power up has is now invoked.
The drop function is what is called to allow the item to be dropped and
removed from the player's inventory. I suppose that you could also use this
function to do something like drop an item off to activate an entity or set
a marker for territory.
The final function is the weaponthink function. This function is called
whenever you activate the item that you are using. This does all the magic
and it is not really intuitive of how it works. The problem is that this
function has to handle all the animations for your item. If you are a
weapon, you will call a special function named Weapon_Generic with one of
the parameters being the function that does the actual firing. You will
have to obtain a pointer to the Weapon_Generic function, create a wrapper
function and pass the function that you created to the Weapon_Generic
function. As an example, we'll convert the Blaster to fire three shots
instead of the usually wimpy one shot. This was originally published on
QDevelS by Sum.
We start with the dll template that we developed and remove all the
unnecessary functions and variables and add the desired functionality:
Don't forget the .def file to export the UserDLLGetAPI function, or
whatever you have to do to get your dll to have the UserDLLGetAPI function
symbol defined. And finally, don't forget to add your new dll to the
quserdll.ini file.
Pretty neat. Kinda makes the wimpy ole blaster a Gib-O-Matic!(TM) You can get
access to any item in Quake II this way and modify it by finding it's entry
and placing your own functions in the pointer variables.
There is only one problem. If you play deathmatch with this mod, everyone
has a 3-shot blaster. This points out a characteristic of items that
inhabit Quake: If you change one item, they all change. What we want to do
is make an item spawn into the world so we can run over to it and pick it
up. To do this properly requires a model that's skin, placed in the world,
etc. Since this is a tutorial about programming (and I'm a terrible
artist. Trust me.), I decided to replace the shotgun in the first episode
with the Gib-0-Matic!(TM). To do this, we need to investigate how things
come into existance in Quake, or are spawned.
When an item gets spawned into the world, the item is passed the instance
variables by a generic function called SpawnItem, found in g_items.c. This
is fine except that this function has some very convoluted code to deal
specifically with the id supplied items in Quake. What if we want to have
or need our own convoluted spawning code to handle our items? If we look at
g_spawn.c, which is where the SpawnItem function gets called from the
ED_CallSpawn function, we notice that the items are handled very
differently from the other things that spawn. There are a couple of
possible solutions to this problem. The one that I chose basically makes
the items spawn like everything else. In other words, a spawn function gets
created for each of the items and placed in the in a spawn_t structure that
is inserted into spawns[], found in g_spawn.c. This approach allows mods to
configure the spawning of their items according to whatever flags are set
without modifying the original code every time a new item is added.
So go to the bottom of g_items.c and add the following functions for the id
created items:
We now need to make a few changes to g_spawn.c to accomodate the items
being spawned as entities. Namely, we need to create references to the
functions that we just created in g_item.c so that the functions in
g_spawn.c can access them. Open g_spawn.c and add the following lines at
the very top of the file:
Notice that I move the definition of the spawn_t structure out of g_spawn.c
and put it into g_local.h. Don't forget to do this! We will need this
structure later on for our own mods, when we want to add our own entities
and items now.
Right after the functions that we added are a bunch of function prototypes
for the other spawn items. If you go past the end of those, around line
190, you will see the spawns array. Change that line to the following:
While MAX_EDICTS is the absolute maximum that the game will handle, there
is actually a another limit. There is a CVAR called maxentities that the
game dll uses to set the sizes of arrays. This CVAR is not set from the
MAX_EDICTS value. It is set in the g_save.c file as a latched CVAR from a
constant value of "1024". Since id's stuff is already set up to handle a
large number of edicts and all the game functions expect this, we are home
free in the entity management department. However, using MAX_EDICTS here is
not strictly correct. Oh well.
Of course, I feel fairly safe in using that number as a limit though, but
not because it seems large enough. Rather, if you look up where MAX_EDICTS
is defined, in order to change it to a larger value, you will see some very
interesting comments. Basically, these values are used in the network
protocol and if you change them, you will have to redo the protocol. So
don't even think about it. Period.
Anyway, we were making room in the array for future items that need to
be spawned into a level. Now go to the bottom of the array and add the
following lines, before the {NULL, NULL} :
It is very important that the new additions come before the {NULL,NULL}
entry. That entry serves as a marker to the id functions to know when the
end of the spawn functions have been reached.
We are almost done with the changes to g_spawn. Just below the spawns[] is
a function called ED_CallSpawn. We need to make a few changes to it so that
the items and the entities get spawned the same way. Change that function
so it looks like this:
Compile everything again. If everything is working the right way, you will
see items in your level, such as the Adrenaline Packs at the bottom of the
ramp, or the combat armor through the area where you crouch. Believe me, I
was real glad when I saw those things!
We have pretty well covered items in Quake II. Except that we still don't
have a way of inserting things into the spawns[].
If you examine the u_entmgr.c code, you will notice that the opening
comment says that it contains the code to insert things into the
spawns[]. However, upon looking through the code, you will realize that it
doesn't. Time to add it in.
Open the u_entmgr.h file and add the following lines at the bottom:
Now open the u_entmgr.c file and add the following five lines of code to
the top of file, before any of the functions:
Now go to the bottom of the u_entmgr.c file and add the following code:
Ok, to insert make inserting an item easier, go to the InsertItem function
and replace it with the following function. It just takes a spawning
function as one of the parameters and adds it to the spawns[] for
you in a special way. Otherwise, whenever you add an item, you'd have to
first add it to the item list and then the spawns array and have them
possibly get out of sync by succeeding in one but not the other. You would
then have to delete the one that succeeded. Not real difficult, but
eventually, someone would leave out one of the steps and spend many an hour
puzzled by what was happening.
To make them available for our mods to use them, we need to go into the
u_loaddll.c and the u_loaddll.h files and modify the structures and
functions there. Pull up the u_loaddll.h file and place the following two
lines under the lines that you added earlier for the item functions, around
line 47:
Now open the u_loaddll.c file and on line 170, below where the InsertItem
and RemoveItem functions were filled in, add the following two lines:
Whew!!! We can now finally insert items with impunity and feel reasonably
sure that Quake will spawn them and make them available for use. However,
we need to discuss one last thing before fixing up the Gib-O-Matic!(tm)
tutorial. Along the way of setting all this up, we bridged the difference
between items and entities and we now have a toolset that will let
us create entities in Quake and have them show up. Entities are things like
monsters, triggers, players, etc. Which leads us to a little discussion on
the type of objects in Quake.
From our experience, you can see that there are two types of objects that
you run across in Quake, items and entities. Actually, there are three. But
we can't do anything to the third, the temporary entities. We will discuss
these types of entities in Part V, because it has important ramifications
for security and user created mods.
There are fundamental differences between the two. Even though we have now
changed items so that they spawn into the world the same way as entities,
the Quake engine still handles them very differently. For instance, in the
g_spawn.c file, the ED_ParseEdict function is parsing the edict data from
the map and placing it in various fields for each and every entity. Items,
on the other hand, all share a single set of variables, as we already
know. If you change the properties of one, like a blaster to fire three
shots instead of one, suddenly all your friends have a triple-shot blaster.
There is only one last thing to check to make sure that our newly created
items are properly place into a map. We want to make sure that our dll sets
up our items first, before the gamex86.dll loads the maps and processes
the entities.
The function that performs the spawning of entities is, interestingly
enough, called SpawnEntities. It is found in g_spawn.c, around line 500. If
we look at the function, we see that it actually outputs a line to the
console, just before it returns. The line says something to the effect of
some number of entities inhibited. If we start up Quake II and look at the
output, sure enough, one of the last lines is the entities inhibited
line. Before that line, if you haven't taken out the gi.dprintf's from the
module loading code, you will see all the usual junk about loading our user
dll's. That's Great! We can be sure that our items and monsters and
whatnots get put into the game before the map gets processed. This means
that all our stuff will be available and indistinguishable from all the
built in stuff.
Essentially, get rid of the first 221 lines in hyper_3.c and replaced them
with this:
Compile the dll and place it in the Quake2 directory. Now, using your
favorite level editor, (I use PakExplorer and emacs), place your newly
created item into the map. Leaving out the messy details, go to where you
placed your item and pick it up. You should see a BFG looking thing.
Don't just stand there! Fire your weapon, soldier!
Hopefully, you will see triple bolts of goodness shooting out. Tada -
Gib-O-Matic!(tm)
I know. Quake complains about missing frames and such. That's work for the
Modellers and for you when you are doing the weapon design.
You may be wondering what is in store for Part V of the MultiDLL
tutorial. As you read, we now have the tools to insert entities into
the world. You may be wondering why I didn't provide an example of an
entity being created and used. Well, that's because I didn't cover
everything that you need to make a new entity, only how to insert it into
Quake. In the next and last part of the tutorial, I will cover that topic
and Security and what it means to you. In there, I will talk a little
(little, I promise) about what it means to be secure and some of the theory
and algorithms that we'll be using. I'll also explain what I meant about
the third type of entities and give you some future directions to go with
this. You've heard some of them and I'll recap and give some pointers.
Until next time, Keep On Fraggin!
Tutorial by by Victor Jimenez (aka Weektor) . This site, and all content and graphics displayed on it,
Difficulty: Moderate to Understand, Moderate to implement
typedef struct gitem_s
{
char *classname; // spawning name
qboolean (*pickup)(struct edict_s *ent, struct edict_s *other);
void (*use)(struct edict_s *ent, struct gitem_s *item);
void (*drop)(struct edict_s *ent, struct gitem_s *item);
void (*weaponthink)(struct edict_s *ent);
char *pickup_sound;
char *world_model;
int world_model_flags;
char *view_model;
// client side info
char *icon;
char *pickup_name; // for printing on pickup
int count_width; // number of digits to display by icon
int quantity; // for ammo how much, for weapons how much is used per shot
char *ammo; // for weapons
int flags; // IT_* flags
void *info;
int tag;
char *precaches; // string of all models, sounds, and images this item will use
} gitem_t;
gitem_t itemlist[] =
gitem_t itemlist[MAX_ITEM] =
void InitItems (void)
{
//VJJ - old way, we need to walk the list until we find the end.
//game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1;
//hackus maximus
game.num_items = 42;
}
/*
u_entmgr.h
vjj
This file declares the functions that a user would have at their disposal to
manipulate items and entities.
*/
//returns a pointer to the gitem_t allocated in the itemlist, null if failed
gitem_t *InsertItem(gitem_t *it);
//finds and removes the items. returns the old index, zero if not found
int RemoveItem(char *name);
/*
u_entmgr.c
vjj
This file contains the functions that a user would call to insert their
items into the itemlist[] and entities into the spawns[].
*/
#include "g_local.h"
#include "u_itemmgr.h"
#define EMPTY_NAME "*none*"
//add an item to the itemlist array. Note that once you request a spot, it
//forever increases the number of items in the list, even if you remove the
//weapon.
//Note that most of the items in the item struct are pointers. No copy of the
//data is performed. You must maintain a copy of the data in your dll!
gitem_t *
InsertItem(gitem_t *it)
{
int i;
gitem_t *spot;
spot = NULL;
//first, we want to find a place for the item.
for(i=1;i< game.num_items && !spot;i++)
if(!Q_stricmp(itemlist[i].classname,EMPTY_NAME))
spot = &itemlist[i];
//if we didn't find an empty slot, see if we can create one
if(!spot && game.num_items < MAX_ITEMS)
spot = &itemlist[++game.num_items];
//OK, fill spot in with the stuff the user sent in
if(spot)
*spot = *it;
return spot;
}
//remove an item. This is tricky. We just can't get rid of it since this
//would leave a hole in the array. Also, if someone has a pointer to the
//item and called one of the functions, the game would crash if there wasn't
//something there. So we need to supply our own dummy functions.
qboolean booldummy(struct edict_s *i, struct edict_s *ii)
{
return false;
}
void dummy1(struct edict_s *i, struct gitem_s *ii)
{}
void dummy2(struct edict_s *i)
{}
int
RemoveItem(gitem_t *it)
{
int i;
//first, find the index for the item
i = ITEM_INDEX(it);
//we want to check to make sure we don't break anything
if( i > MAX_ITEMS || i < 0)
return 0;
//we are OK, fix all the pointers and value w/ safe stuff
it->classname = EMPTY_NAME;
it->pickup = booldummy;
it->use = dummy1;
it->drop = dummy1;
it->weaponthink = dummy2;
it->pickup_sound = "items/pkup.wav";
it->world_model = NULL;
it->world_model_flags = 0;
it->view_model = NULL;
it->icon = "i_fixme";
it->pickup_name = EMPTY_NAME;
it->count_width = 0;
it->quantity = 0;
it->ammo = NULL;
it->flags = 0;
it->info = NULL;
it->tag = 0;
it->precaches = "";
return i;
}
gitem_t *(*InsertItem)(gitem_t *it);
void (*RemoveItem)(gitem_t *it);
#include "u_entmgr.h"
UserDLLImports.InsertItem = InsertItem;
UserDLLImports.RemoveItem = RemoveItem;
/*
hyper_3.c
vjj 03/29/98
We are using the dll template that was created for the multiple dll
modification.
*/
#define USER_EXCLUDE_FUNCTIONS 1
#include "g_local.h"
#include "g_cmds.h"
#include "u_loaddll.h"
/* place the name of your dll here */
#define DLL_NAME "Hyper3"
/*
first, we need to set up a number of variables that will be needed while
running. This would be where we set up the references to the Quake2 things
like the gi structure, the game structure, etc.
for our example, we need the InsertCommand function and the gi for the
commands to work.
*/
static game_import_t *ptrgi;
static game_export_t *ptrGlobals;
static level_locals_t *ptrLevel;
static game_locals_t *ptrGame;
static void (*PlayerInsertCommands)(struct g_cmds_t *, int, char *);
static void (*(*PlayerFindFunction)(char *t));
/* commented out - future functionality
static gitem_t *(*InsertItem)(gitem_t *it);
static void (*RemoveItem)(gitem_t *it);
static void (*InsertMonster)();
static void (*InsertClient)(); // for bots?
*/
static int AlreadyInit = 0;
static int AlreadyLoad = 0;
/*
user code goes here
*/
void (*Com_Printf)(char *msg, ...); //pretty much always need this one
void (*Blaster_Fire)(edict_t *, vec3_t, int, qboolean, int);
void (*NoAmmoWeaponChange) (edict_t *);
void (*Weapon_Generic) (edict_t *, int, int, int, int, int *, int *, void (*fire)(edict_t *ent));
gitem_t *(*FindItem)(char *);
//originally created by Sumfuka, published on QDevelS
void Weapon_Blaster_Fire (edict_t *ent)
{
int damage;
// STEVE
vec3_t tempvec;
if (deathmatch->value)
damage = 15;
else
damage = 10;
Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER);
// STEVE : add 2 new bolts below
VectorSet(tempvec, 0, 8, 0);
VectorAdd(tempvec, vec3_origin, tempvec);
Blaster_Fire (ent, tempvec, damage, false, EF_BLASTER);
VectorSet(tempvec, 0, -8, 0);
VectorAdd(tempvec, vec3_origin, tempvec);
Blaster_Fire (ent, tempvec, damage, false, EF_BLASTER);
ent->client->ps.gunframe++;
}
void New_Weapon_Blaster (edict_t *ent)
{
static int pause_frames[] = {19, 32, 0};
static int fire_frames[] = {5, 0};
Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);
}
void Weapon_HyperBlaster_Fire (edict_t *ent)
{
float rotation;
vec3_t offset;
int effect;
ent->client->weapon_sound = ptrgi->soundindex("weapons/hyprbl1a.wav");
if (!(ent->client->buttons & BUTTON_ATTACK))
{
ent->client->ps.gunframe++;
}
else
{
if (! ent->client->pers.inventory[ent->client->ammo_index] )
{
if (ptrLevel->time >= ent->pain_debounce_time)
{
ptrgi->sound(ent, CHAN_VOICE,ptrgi->soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
ent->pain_debounce_time = ptrLevel->time + 1;
}
NoAmmoWeaponChange (ent);
}
else
{
// STEVE .... the lines below are new !
// ...........TRIPLE HYPER BLASTER !!!
if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9))
effect = EF_HYPERBLASTER;
else
effect = 0;
// change the offset radius to 6 (from 4), spread the bolts out a little
rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6;
offset[0] = 0;
offset[1] = -8 * sin(rotation);
offset[2] = 8 * cos(rotation);
Blaster_Fire (ent, offset, 20, true, effect);
// fire a second blast at a different rotation
rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6 + M_PI*2.0/3.0;
offset[0] = 0;
offset[1] = -8 * sin(rotation);
offset[2] = 8 * cos(rotation);
Blaster_Fire (ent, offset, 20, true, effect);
// fire a third blast at a different rotation
rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6 + M_PI*4.0/3.0;
offset[0] = 0;
offset[1] = -8 * sin(rotation);
offset[2] = 8 * cos(rotation);
Blaster_Fire (ent, offset, 20, true, effect);
// deduct 3 times the amount of ammo as before ( the *3 on end)
ent->client->pers.inventory[ent->client->ammo_index] -=
ent->client->pers.weapon->quantity * 3;
}
ent->client->ps.gunframe++;
if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index])
ent->client->ps.gunframe = 6;
}
if (ent->client->ps.gunframe == 12)
{
ptrgi->sound(ent, CHAN_AUTO, ptrgi->soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
ent->client->weapon_sound = 0;
}
}
void New_Weapon_HyperBlaster (edict_t *ent)
{
static int pause_frames[] = {0};
static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0};
Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire);
}
/*
End user code section
*/
/*
okay, that was the end of the original code. we need to provide the
framework. This part should be fairly boilerplate.
*/
/*
there are five functions that we need to provide.
*/
/*this is a security function and supposed to return an MD5 hash of the code in radix64*/
void
UserDLLMD5(char *buf)
{
buf[0]='\0'; /*do nothing for now*/
}
/* initialization function - called to set up the dll. This is usually
called to set up mod specific global data*/
void
UserDLLInit()
{
gitem_t *it;
if (AlreadyInit) return;
AlreadyInit = 1;
ptrgi->dprintf("In UserDLLInit for DLL %s\n",DLL_NAME);
//this is where you would acquire any needed function pointers
Com_Printf = (void (*)(char *msg, ...)) PlayerFindFunction("Com_Printf");
Blaster_Fire = (void (*)(edict_t *, vec3_t , int , qboolean , int ))
PlayerFindFunction("Blaster_Fire");
NoAmmoWeaponChange = (void (*)(edict_t *ent))
PlayerFindFunction("NoAmmoWeaponChange");
Weapon_Generic = (void (*)(edict_t *, int, int, int, int, int *, int *,void (*fire)(edict_t *ent))
PlayerFindFunction("Weapon_Generic");
FindItem = ((gitem_t *)(*)(char *))
PlayerFindFunction("FindItem");
//Ok, now we are ready to change old weapon to new
it = FindItem("Blaster");
it->weaponthink = New_Weapon_Blaster;
it = FindItem("Hyperblaster");
it->weaponthink = New_Weapon_HyperBlaster;
}
/* this is the clean up function - if there were global data structures
that you had allocated, this is where you get rid of them */
void
UserDLLStop()
{}
/* called at the start of each level. The player is in the game. Level
specific variables can be placed here */
void
UserDLLStartLevel(edict_t *ent)
{}
/* called when the user exits the level. Used to clear out variable so
that a user ends in a pre-configured state */
void
UserDLLEndLevel(void)
{}
/* called when the player respawns in a level. */
void
UserDLLPlayerRespawns(edict_t *self)
{}
/* called when a player dies */
void
UserDLLPlayerDies(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{}
/*
we need to initialize the structure that we will pass back, the same
way that id does it.
*/
static userdll_export_t userdll_export =
{
1, //version of the library
"default", //creator - put up to 31 chars of your name here
UserDLLMD5, //this is supposed to return an MD5 hash of the code
UserDLLInit, //initialization function - adds the command in
UserDLLStop, //this is the clean up function
UserDLLStartLevel, //supposed to be called at the start of each level
UserDLLEndLevel, //called when the user exits the level
UserDLLPlayerRespawns, //called when user respawns
UserDLLPlayerDies //called when the user dies
};
/*
finally, at long last, we define the entry point that is called by
the external loader. In our example, we only care about
*/
userdll_export_t
UserDLLGetAPI(userdll_import_t udit)
{
PlayerInsertCommands = udit.InsertCommands;
PlayerFindFunction = udit.FindFunction;
ptrgi = udit.gi;
ptrGlobals = udit.globals;
ptrLevel = udit.level;
ptrGame = udit.game;
ptrgi->dprintf("Inside GetAPI for %s\n",DLL_NAME);
return userdll_export;
}
//added by vjj
//These are the functions needed to spawn the builtin Quake items.
//This problem is compounded by the fact that we are trying to build
//two things at once - one is the general solution to allow us to make
//things in the general way we want and two, fixes to the code so that
//it'll work properly. Of course, we wish to minimize the changes.
//To simplify our effort, we will place these in the spawns[] list.
//A developer would have to create these functions for the new items
//that would be developed and then add tha appropriate entries into
//both the spawns[] and itemlist[]. We will create an interface to
//the spawns[] list and that will be the low level interface since
//everything ends up in the spawns[] list from items to monsters
//to triggers, etc.
//These are simply the old calls to SpawnItem with the entity filled in.
void SP_item_armor_body(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_armor_body"));
}
void SP_item_armor_combat(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_armor_combat"));
}
void SP_item_armor_jacket(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_armor_jacket"));
}
void SP_item_armor_shard(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_armor_shard"));
}
void SP_item_power_screen(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_power_screen"));
}
void SP_item_power_shield(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_power_shield"));
}
void SP_item_weapon_blaster(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_blaster"));
}
void SP_item_weapon_shotgun(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_shotgun"));
}
void SP_item_weapon_supershotgun(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_supershotgun"));
}
void SP_item_weapon_machinegun(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_machinegun"));
}
void SP_item_weapon_chaingun(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_chaingun"));
}
void SP_item_ammo_grenades(edict_t *self)
{
SpawnItem(self,FindItemByClassname("ammo_grenades"));
}
void SP_item_weapon_grenadelauncher(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_grenadelauncher"));
}
void SP_item_weapon_rocketlauncher(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_rocketlauncher"));
}
void SP_item_weapon_hyperblaster(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_hyperblaster"));
}
void SP_item_weapon_railgun(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_railgun"));
}
void SP_item_weapon_bfg(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_bfg"));
}
void SP_item_ammo_shells(edict_t *self)
{
SpawnItem(self,FindItemByClassname("ammo_shells"));
}
void SP_item_ammo_bullets(edict_t *self)
{
SpawnItem(self,FindItemByClassname("ammo_bullets"));
}
void SP_item_ammo_cells(edict_t *self)
{
SpawnItem(self,FindItemByClassname("ammo_cells"));
}
void SP_item_ammo_rockets(edict_t *self)
{
SpawnItem(self,FindItemByClassname("ammo_rockets"));
}
void SP_item_ammo_slugs(edict_t *self)
{
SpawnItem(self,FindItemByClassname("ammo_slugs"));
}
void SP_item_powerup_quad(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_quad"));
}
void SP_item_powerup_invulnerability(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_invulnerability"));
}
void SP_item_powerup_silencer(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_silencer"));
}
void SP_item_powerup_breather(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_breather"));
}
void SP_item_powerup_enviro(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_enviro"));
}
void SP_item_powerup_ancient_head(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_ancient_head"));
}
void SP_item_powerup_adrenaline(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_adrenaline"));
}
void SP_item_powerup_bandolier(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_bandolier"));
}
void SP_item_powerup_pack(edict_t *self)
{
SpawnItem(self,FindItemByClassname("item_pack"));
}
void SP_item_key_data_cd(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_data_cd"));
}
void SP_item_key_power_cube(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_power_cube"));
}
void SP_item_key_pyramid(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_pyramid"));
}
void SP_item_key_data_spinner(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_data_spinner"));
}
void SP_item_key_pass(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_pass"));
}
void SP_item_key_blue_key(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_blue_key"));
}
void SP_item_key_red_key(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_red_key"));
}
void SP_item_key_commander_head(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_commander_head"));
}
void SP_item_key_airstrike_target(edict_t *self)
{
SpawnItem(self,FindItemByClassname("key_airstrike_target"));
}
//changes by VJJ
//commented out - placed in g_locals.h
//typedef struct
//{
// char *name;
// void (*spawn)(edict_t *ent);
//} spawn_t;
//Ok, since we are modifying this file, we might as well declare the
//item spawning functions here. These are the functions that actually
//cause the item to be spawned. By convention, the actual spawning
//function is defined wherever the entity (or item in our case) is
//defined.
//We are actually having to do two things. We need to manage the itemlist[]
//and we need to manage the spawns[] lists. When a level is brought up, the
//first thing that is does is run through the list of things in it and spawns
//them.
void SP_item_armor_body(edict_t *self);
void SP_item_armor_combat(edict_t *self);
void SP_item_armor_jacket(edict_t *self);
void SP_item_armor_shard(edict_t *self);
void SP_item_power_screen(edict_t *self);
void SP_item_power_shield(edict_t *self);
void SP_item_weapon_blaster(edict_t *self);
void SP_item_weapon_shotgun(edict_t *self);
void SP_item_weapon_supershotgun(edict_t *self);
void SP_item_weapon_machinegun(edict_t *self);
void SP_item_weapon_chaingun(edict_t *self);
void SP_item_ammo_grenades(edict_t *self);
void SP_item_weapon_grenadelauncher(edict_t *self);
void SP_item_weapon_rocketlauncher(edict_t *self);
void SP_item_weapon_hyperblaster(edict_t *self);
void SP_item_weapon_railgun(edict_t *self);
void SP_item_weapon_bfg(edict_t *self);
void SP_item_ammo_shells(edict_t *self);
void SP_item_ammo_bullets(edict_t *self);
void SP_item_ammo_cells(edict_t *self);
void SP_item_ammo_rockets(edict_t *self);
void SP_item_ammo_slugs(edict_t *self);
void SP_item_powerup_quad(edict_t *self);
void SP_item_powerup_invulnerability(edict_t *self);
void SP_item_powerup_silencer(edict_t *self);
void SP_item_powerup_breather(edict_t *self);
void SP_item_powerup_enviro(edict_t *self);
void SP_item_powerup_ancient_head(edict_t *self);
void SP_item_powerup_adrenaline(edict_t *self);
void SP_item_powerup_bandolier(edict_t *self);
void SP_item_powerup_pack(edict_t *self);
void SP_item_key_data_cd(edict_t *self);
void SP_item_key_power_cube(edict_t *self);
void SP_item_key_pyramid(edict_t *self);
void SP_item_key_data_spinner(edict_t *self);
void SP_item_key_pass(edict_t *self);
void SP_item_key_blue_key(edict_t *self);
void SP_item_key_red_key(edict_t *self);
void SP_item_key_commander_head(edict_t *self);
void SP_item_key_airstrike_target(edict_t *self);
//end of added item spawn functions
spawn_t spawns[MAX_EDICTS] = {
//vjj - added all the item spawn functions.
//I know, I should have used the built in functions but
//these items are part of the standard game, just like
//the monsters. I really should have used the functions
//that I provided to add to the spawn array the items,
//monsters, triggers, everything. Oh well. It's left
//as an exercise to the student. I suppose that it even
//could be tailored on a level by level so that only
//the things that will be used are placed in the spawns
//array. One final improvement would be to sort the entries
//as they are placed in the spawns[] so we can use some of
//the more efficient searches.
{"item_armor_body", SP_item_armor_body},
{"item_armor_combat", SP_item_armor_combat},
{"item_armor_jacket", SP_item_armor_jacket},
{"item_armor_shard", SP_item_armor_shard},
{"item_power_screen", SP_item_power_screen},
{"item_power_shield", SP_item_power_shield},
{"weapon_blaster", SP_item_weapon_blaster},
{"weapon_shotgun", SP_item_weapon_shotgun},
{"weapon_supershotgun", SP_item_weapon_supershotgun},
{"weapon_machinegun", SP_item_weapon_machinegun},
{"weapon_chaingun", SP_item_weapon_chaingun},
{"ammo_grenades", SP_item_ammo_grenades},
{"weapon_grenadelauncher", SP_item_weapon_grenadelauncher},
{"weapon_rocketlauncher", SP_item_weapon_rocketlauncher},
{"weapon_hyperblaster", SP_item_weapon_hyperblaster},
{"weapon_railgun", SP_item_weapon_railgun},
{"weapon_bfg", SP_item_weapon_bfg},
{"ammo_shells", SP_item_ammo_shells},
{"ammo_bullets", SP_item_ammo_bullets},
{"ammo_cells", SP_item_ammo_cells},
{"ammo_rockets", SP_item_ammo_rockets},
{"ammo_slugs", SP_item_ammo_slugs},
{"item_quad", SP_item_powerup_quad},
{"item_invulnerability", SP_item_powerup_invulnerability},
{"item_silencer", SP_item_powerup_silencer},
{"item_breather", SP_item_powerup_breather},
{"item_enviro", SP_item_powerup_enviro},
{"item_ancient_head", SP_item_powerup_ancient_head},
{"item_adrenaline", SP_item_powerup_adrenaline},
{"item_bandolier", SP_item_powerup_bandolier},
{"item_pack", SP_item_powerup_pack},
{"key_data_cd", SP_item_key_data_cd},
{"key_power_cube", SP_item_key_power_cube},
{"key_pyramid", SP_item_key_pyramid},
{"key_data_spinner", SP_item_key_data_spinner},
{"key_pass", SP_item_key_pass},
{"key_blue_key", SP_item_key_blue_key},
{"key_red_key", SP_item_key_red_key},
{"key_commander_head", SP_item_key_commander_head},
{"key_airstrike_target", SP_item_key_airstrike_target},
//end of item modifications.
/*
===============
ED_CallSpawn
Finds the spawn function for the entity and calls it
===============
*/
void ED_CallSpawn (edict_t *ent)
{
spawn_t *s;
//vjj - don't need these since items are now spawned the
//same way as entities.
//gitem_t *item;
//int i;
if (!ent->classname)
{
gi.dprintf ("ED_CallSpawn: NULL classname\n");
return;
}
// check item spawn functions
/* vjj - removed special item spawn stuff
for (i=0,item=itemlist ; i
//returns a pointer to the spawn_t structure in the spawns[], null if failed
spawn_t *InsertEntity(spawn_t *spawnInfo);
//finds and removes the entity. returns the old index, -1 if not found
int RemoveEntity(char *name);
//these are the Entity functions
//These functions allow the user to insert and remove types of entities from
//the spawns[].
//we need to be able to access the spawns[]
extern spawn_t spawns[];
//This works in a similar way to the way that the InsertItem works. It attempts
//to find an empty spot for your entity, recyling whatever open entry it finds.
//If it can't find a spot, it then appends to the end of the list, assuming
//that there is enough room.
//Note that the InsertItem function does not call this function to perform it's
//insertion of the spawn_t into Spawns.
spawn_t *
InsertEntity(spawn_t *spawnInfo)
{
int i;
spawn_t *spot, *s;
spot = NULL;
//first, we want to find a place for the entity.
for(s=spawns, i=0; i
//add an item to the itemlist array. Note that once you request a spot, it
//forever increases the number of items in the list, even if you remove the
//weapon.
//Note that most of the items in the item struct are pointers. No copy of the
//data is performed. You must maintain a copy of the data in your dll!
//You also have to pass in a spawn_t struct that tells how to spawn your
//item.
gitem_t *
InsertItem(gitem_t *it, spawn_t *spawnInfo)
{
int i, inc_items;
gitem_t *spot;
spawn_t *spspot, *s;
inc_items = 0;
spot = NULL;
//first, we want to find a place for the item.
for(i=1;i
void (*InsertEntity)(spawn_t *t);
void (*RemoveEntity)(char *name);
UserDLLImports.InsertEntity = InsertEntity;
UserDLLImports.RemoveEntity = RemoveEntity;
/*
hyper_3.c
vjj 03/29/98
We are using the dll template that was created for the multiple dll
modification.
*/
#define USER_EXCLUDE_FUNCTIONS 1
#include "../game3.14/g_local.h"
#include "../game3.14/g_cmds.h"
#include "../game3.14/u_loaddll.h"
/* place the name of your dll here */
#define DLL_NAME "Hyper3"
/*
first, we need to set up a number of variables that will be needed while
running. This would be where we set up the references to the Quake2 things
like the gi structure, the game structure, etc.
for our example, we need the InsertCommand function and the gi for the
commands to work.
*/
static game_import_t *ptrgi;
static game_export_t *ptrGlobals;
static level_locals_t *ptrLevel;
static game_locals_t *ptrGame;
static void (*PlayerInsertCommands)(struct g_cmds_t *, int, char *);
static void (*(*PlayerFindFunction)(char *t));
static gitem_t *(*PlayerInsertItem)(gitem_t *it, spawn_t *spawn);
//int (*RemoveItem)(char *name);
/* commented out - future functionality
static void (*InsertMonster)();
static void (*InsertClient)(); // for bots?
*/
static int AlreadyInit = 0;
static int AlreadyLoad = 0;
/*
user code goes here
*/
//we don't really need to allocate memory on a permanent basis since it is copied into
//the item array. However, I'm lazy :-p
gitem_t gib_o_matic;
vec3_t vec3_origin = {0,0,0};
cvar_t *deathmatch;
void (*Com_Printf)(char *msg, ...); //pretty much always need this one
void (*Blaster_Fire)(edict_t *, vec3_t, int, qboolean, int);
//we never run out of ammo for the Gib-O-Matic
//void (*NoAmmoWeaponChange) (edict_t *);
void (*Weapon_Generic) (edict_t *, int, int, int, int, int *, int *, void (*fire)(edict_t *ent));
gitem_t *(*FindItem)(char *);
void (*SpawnItem)(edict_t *, gitem_t *);
gitem_t *(*FindItemByClassname)(char *);
//originally created by Sumfuka, published on QDevelS
//we are just doing the hand blaster
//GOM = gib-o-matic
void GOM_Weapon_Fire (edict_t *ent)
{
int damage;
// STEVE
vec3_t tempvec;
if (deathmatch->value)
damage = 15;
else
damage = 10;
Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER);
// STEVE : add 2 new bolts below
VectorSet(tempvec, 0, 8, 0);
VectorAdd(tempvec, vec3_origin, tempvec);
Blaster_Fire (ent, tempvec, damage, false, EF_BLASTER);
VectorSet(tempvec, 0, -8, 0);
VectorAdd(tempvec, vec3_origin, tempvec);
Blaster_Fire (ent, tempvec, damage, false, EF_BLASTER);
ent->client->ps.gunframe++;
}
void GOM_Weapon_Blaster (edict_t *ent)
{
static int pause_frames[] = {19, 32, 0};
static int fire_frames[] = {5, 0};
Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, GOM_Weapon_Fire);
}
//no fancy spawning stuff here, just nice and easy
void SP_GOM_Weapon_Blaster(edict_t *self)
{
SpawnItem(self,FindItemByClassname("weapon_gibomatic"));
}
//need this to be able to spawn
spawn_t sp_gom =
{
"weapon_gibomatic", SP_GOM_Weapon_Blaster
};
/*
End user code section
*/
/*
okay, that was the end of the original code. we need to provide the
framework. This part should be fairly boilerplate.
*/
/*
there are five functions that we need to provide.
*/
/*this is a security function and supposed to return an MD5 hash of the code in radix64*/
void
UserDLLMD5(char *buf)
{
buf[0]='\0'; /*do nothing for now*/
}
/* initialization function - called to set up the dll. This is usually
called to set up mod specific global data*/
void
UserDLLInit(void)
{
gitem_t *it;
if (AlreadyInit) return;
AlreadyInit = 1;
ptrgi->dprintf("In UserDLLInit for DLL %s\n",DLL_NAME);
//this is where you would acquire any needed function pointers
Com_Printf = (void (*)(char *msg, ...)) PlayerFindFunction("Com_Printf");
Blaster_Fire = (void (*)(edict_t *, vec3_t , int , qboolean , int ))
PlayerFindFunction("Blaster_Fire");
//NoAmmoWeaponChange = (void (*)(edict_t *ent))
// PlayerFindFunction("NoAmmoWeaponChange");
Weapon_Generic = (void (*)(edict_t *, int, int, int, int, int *, int *,void (*fire)(edict_t *ent)))
PlayerFindFunction("Weapon_Generic");
FindItem = (gitem_t * (*)(char *))
PlayerFindFunction("FindItem");
SpawnItem = (void (*)(edict_t *, gitem_t *))PlayerFindFunction("SpawnItem");
FindItemByClassname = (gitem_t *(*)(char *))PlayerFindFunction("FindItemByClassname");
//Ok, now we are ready to create new weapon from old weapon
//we actually are not going to build it from the blaster but rather
//the shotgun because the blaster expects you to always carry it around
//and is missing a bunch of functions that we would either have to
//write or get a reference.
it = FindItem("Shotgun");
gib_o_matic = *it;
//Ok, we copied over the default fields, fill in the ones we need
gib_o_matic.classname = "weapon_gibomatic";
gib_o_matic.weaponthink = GOM_Weapon_Blaster;
gib_o_matic.world_model = "models/weapons/g_disint/tris.md2";
gib_o_matic.view_model = "models/weapons/v_disint/tris.md2";
gib_o_matic.icon = "w_blaster";
gib_o_matic.pickup_name = "Gib-0-Matic";
gib_o_matic.count_width = 0;
gib_o_matic.quantity = 0;
gib_o_matic.ammo = "Cells";
gib_o_matic.precaches = "weapons/blastf1a.wav misc/lasfly.wav";
//I didn't fill in a few. Namely the gen weapon functions and a few
//default values that don't change from weapon to weapon. At this
//point, the Gib-O-Matic mostly behaves like a blaster.
//need to register the weapon.
PlayerInsertItem(&gib_o_matic,&sp_gom);
deathmatch = ptrgi->cvar ("deathmatch", "4", CVAR_SERVERINFO | CVAR_LATCH);
}
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for their great help and support with hosting.
Best viewed with Netscape 4