Item Placement

This is a tutorial on how to dynamically place items into maps without changing the map file. All the credit for this tut goes to eLiTe (Tom McIntyre) a coder for Annihilation. The only modification is in the loaditems function, due to my compiler not recognizing soume of the utility functions used. I also changed the filename extension. Everything else is the same. Check out the Annihilation homepage, The mod seems very worthwhile. If you have a problem with me having this tutorial up here, then mail me and I'll take it down. The only reason I have it here, is to make it more accessable to people. It's a great idea


Down to the coding. Create a new c file. Call it something relevant. Add it to your makefile/project. Paste the code below into it.

#include "g_local.h"
 
newitem_t   *newitem_head;
qboolean    erasing;
 
qboolean spawnitembyclassname(vec3_t spot, char *classname, qboolean real)
{
    edict_t     *ent;
    gitem_t     *item;
    vec3_t      forward, right;
    vec3_t      angles;
    newitem_t   *old_newitem_head;
 
    item = FindItemByClassname(classname);
    if (!item)
        return 0;
 
    ent = G_Spawn();
 
    ent->classname=gi.TagMalloc(64*sizeof(char), TAG_LEVEL);
    strcpy(ent->classname,classname);
    
    ent->item = item;
    if (real)
    {
        ent->s.effects = ent->item->world_model_flags;
        ent->s.renderfx = RF_GLOW;
        ent->touch = Touch_Item;
    }
    else
    {
        ent->s.effects = ent->item->world_model_flags|EF_COLOR_SHELL;
        ent->s.renderfx = RF_GLOW|RF_SHELL_RED;
        ent->touch = Touch_NewItem;
    }
    VectorSet (ent->mins, -15, -15, -15);
    VectorSet (ent->maxs, 15, 15, 15);
    gi.setmodel (ent, ent->item->world_model);
    ent->solid = SOLID_TRIGGER;
    ent->movetype = MOVETYPE_BOUNCE;  
    ent->owner = ent;
    
    angles[0] = 0;
    angles[1] = rand() % 360;
    angles[2] = 0;
    
    VectorCopy (spot, ent->s.origin);
    ent->s.origin[2] += 16;
 
    gi.linkentity (ent);
    
    if (!real)
    {
        old_newitem_head = newitem_head;
        newitem_head = gi.TagMalloc(sizeof(newitem_t), TAG_LEVEL);
        memset(newitem_head, 0, sizeof(newitem_t));
        newitem_head->next = old_newitem_head;
        newitem_head->ent = ent;
    }
 
    return 1;
}
 
void Cmd_Clearitems_f(edict_t *ent)
{
    newitem_t   *old_newitem_head;
    
    if (newitem_head)
    {
        while (newitem_head)
        {
            G_FreeEdict(newitem_head->ent);
            old_newitem_head=newitem_head;
            newitem_head=newitem_head->next;
            gi.TagFree(old_newitem_head);
        }
        
        gi.cprintf(ent, PRINT_HIGH, "All new item data cleared\n");
    }
    else
    {
        gi.cprintf(ent, PRINT_HIGH, "There is no new item data stored\n");
    }
}
 
void Cmd_Undoitem_f(edict_t *ent)
{
    newitem_t   *old_newitem_head;
    
    if (newitem_head)
    {
        gi.cprintf(ent, PRINT_HIGH, "%s was removed\n",newitem_head->ent->classname);
        G_FreeEdict(newitem_head->ent);
        old_newitem_head=newitem_head;
        newitem_head=newitem_head->next;
        gi.TagFree(old_newitem_head);
    }
    else
    {
        gi.cprintf(ent, PRINT_HIGH, "There is no new item data stored\n");
    }
}
 
void Touch_NewItem(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
    newitem_t   *old_newitem_head;
    newitem_t   *old_old_newitem_head;
    
    if (!erasing)
        return;
 
    if (!other->client)
        return;
 
    if (other->health<=0)
        return;
 
    // flash the screen
    other->client->bonus_alpha = 0.25;  
    
    old_newitem_head=newitem_head;
    old_old_newitem_head=old_newitem_head;
    
    while (old_newitem_head)
    {
        if (old_newitem_head->ent==ent)
        {
            if (old_newitem_head==newitem_head)
                newitem_head=newitem_head->next;
            
            old_old_newitem_head->next=old_newitem_head->next;
            
            gi.cprintf(other, PRINT_HIGH, "%s was removed\n",old_newitem_head->ent->classname);
            G_FreeEdict(old_newitem_head->ent);
            gi.TagFree(old_newitem_head);
            return;
        }
        else
        {
            old_old_newitem_head=old_newitem_head;
            old_newitem_head=old_newitem_head->next;
        }
    }
}
 
void Cmd_Listitems_f(edict_t *ent)
{
    newitem_t   *other_newitem_head;
    
    if (newitem_head)
    {
        gi.cprintf(ent, PRINT_HIGH, "New item list, starting with the most recent...\n");
        
        other_newitem_head=newitem_head;
        
        while (other_newitem_head)
        {
            gi.cprintf(ent, PRINT_HIGH, "(%d,%d,%d): %s\n",(int)other_newitem_head->ent->s.origin[0],(int)other_newitem_head->ent->s.origin[1],(int)other_newitem_head->ent->s.origin[2],other_newitem_head->ent->classname);
            other_newitem_head=other_newitem_head->next;
        }
        
    }
    else
    {
        gi.cprintf(ent, PRINT_HIGH, "There is no new item data stored\n");
    }
}
 
void Cmd_Saveitems_f(edict_t *ent)
{
    gi.cprintf(ent, PRINT_HIGH, "Saving new item data for this level... ");
    if (saveitemdata(ent))
        gi.cprintf(ent, PRINT_HIGH, "OK\n");
    else
        gi.cprintf(ent, PRINT_HIGH, "ERROR! No new items?\n");
}
 
qboolean saveitemdata(edict_t *ent)
{
    FILE        *fp;
    int         i;
    char        filename[256];
    cvar_t      *game_dir,*mapname;
    newitem_t   *other_newitem_head;
 
    game_dir = gi.cvar ("game", "", 0);
    mapname = gi.cvar ("mapname", "", 0);
 
#ifdef  _WIN32
    i =  sprintf(filename, ".\\");
    i += sprintf(filename + i, game_dir->string);
    i += sprintf(filename + i, "\\config\\");
    i += sprintf(filename + i, level.mapname);
    i += sprintf(filename + i, ".ent"); // change .ent to the file extension you want
#else
    strcpy(filename, "./");
    strcat(filename, game_dir->string);
    strcat(filename, "/config/");
    strcat(filename, level.mapname);
    strcat(filename, ".ent"); // change .ent to the file extension you want
#endif
 
    if (!newitem_head)
    {
        if (fp = fopen (filename, "rt")) // checks to see if the file exists
        {
            fclose(fp);
            gi.cprintf(ent,PRINT_HIGH,"ERASING NEW ITEMS DATA... ");
            if (remove(filename)==0)
                return 1;
        }
        
        return 0;
    }
 
    fp = fopen (filename, "wt");
    if (!fp)
        return 0;
    
    fprintf(fp,"# ADDED ITEMS DATA FILE\n");
    fprintf(fp,"# Warning: Modifying this file may cause errors in the game\n");
    fprintf(fp,"\n");
    fprintf(fp,"[ItemData]\n");
    
    // Surf the linked list :)
    other_newitem_head=newitem_head;
 
    while (other_newitem_head)
    {
        fprintf(fp,"%d %d %d %s\n",(int)other_newitem_head->ent->s.origin[0],(int)other_newitem_head->ent->s.origin[1],(int)other_newitem_head->ent->s.origin[2],other_newitem_head->ent->classname);
        other_newitem_head=other_newitem_head->next;
    }
 
    fclose(fp);
    
    return 1;
}
 
// addition to get the data from the file without needing trimstring which was used in the original.
 
int GetLineFromFile(FILE *in, char s[])
{
        int i, c;
 
        // This reads characters from in into s until MAX_LINE_SIZE-1 is reached,
        // a newline character is reached, or an EOF is reached.
        for (i = 0; i < 254 && (c = fgetc(in)) != '\n' && c != EOF; i++)
               s[i] = c;
        // Add a '\0' to the end of s
        s[i] = '\0';
        return i;
}
// end addition
 
 
qboolean loaditemdata(void)
{
    FILE        *fp;
    char        filename[256],classname[64],buffer[256];
    cvar_t      *game_dir,*mapname;
    vec3_t      spot;
    long        line;
    int         i,a,b,c;
    qboolean    inkey=0;
    
    game_dir = gi.cvar ("game", "", 0);
 
    i =  sprintf(filename, ".\\");
    i += sprintf(filename + i, game_dir->string);
    i += sprintf(filename + i, "\\config\\");
    i += sprintf(filename + i, level.mapname);
    i += sprintf(filename + i, ".ent"); // change file extension here
 
 
    fp = fopen (filename, "rt");
    if (!fp){
        gi.dprintf ("No item file found\n");  
        return 0;
}
    
    line=0;
    while (!feof(fp))
    {
        line++;
        GetLineFromFile(fp,buffer);        // modification in this codeblock to not use trimstring
        if (buffer[0]=='\0' || buffer[0]=='#')
            continue;
        if (!inkey)
        {
            // Search for [ItemData] key
            if (Q_stricmp(buffer,"[ItemData]")!=0)
                continue;
            inkey=1;
        }
        else
        {
            classname[0]='\0';
            sscanf(buffer,"%d %d %d %63s",&a,&b,&c,classname);
            spot[0]=(float)a;
            spot[1]=(float)b;
            spot[2]=(float)c;
                    
            if (strlen(classname)==0 || !FindItemByClassname(classname))
            {
                gi.dprintf("Error in config/%s.ent on line %d!\n",level.mapname,line);
            }
            else if (deathmatch->value || coop->value)
            {
                spawnitembyclassname(spot,classname,1);
                gi.dprintf("Added %s at (%d,%d,%d)\n",classname,(int)spot[0],(int)spot[1],(int)spot[2]);
            }
             else
            {
                spawnitembyclassname(spot,classname,0);
                gi.dprintf("Loaded %s at (%d,%d,%d)\n",classname,(int)spot[0],(int)spot[1],(int)spot[2]);
            }
        }
    }
 
    fclose(fp);
    
    return 1;
}

I'm not going to explain the procedures, they should be self explaintry.


Add the code below to the bottom of g_local.h

qboolean spawnitembyclassname(vec3_t spot, char *classname, qboolean real);
void activateitem(edict_t *item);
void Cmd_Clearitems_f(edict_t *ent);
void Cmd_Undoitem_f(edict_t *ent);
void Touch_NewItem(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
void Cmd_Listitems_f(edict_t *ent);
void Cmd_Saveitems_f(edict_t *ent);
qboolean saveitemdata(edict_t *ent);
 
typedef struct newitem_s
{
     edict_t *ent;
     struct  newitem_s *next;
} newitem_t;
 
extern newitem_t    *newitem_head;
extern qboolean     erasing;

Add the line
loaditemdata();
to the end of the SpawnEntities function in g_spawn.c

and add
newitem_head=NULL;
to SP_worldspawn before the end.


Now to access the code. Add the code below to clientcommand, before the final else in g_cmds.c

   else if (Q_stricmp (cmd, "placeitem") == 0)
    {
        if (deathmatch->value)
        {
            gi.cprintf(ent, PRINT_HIGH, "This command is only available in single player mode\n");
            return;
        }
        
        cmd = gi.argv(1);
        if (strlen(cmd) == 0)
        {
            gi.cprintf(ent, PRINT_HIGH, "You must specify a classname\n");
            return;
        }
        
        if (spawnitembyclassname(ent->s.origin,cmd,0))      
            gi.cprintf(ent, PRINT_HIGH, "%s placed at (%d,%d,%d)\n", cmd, (int)ent->s.origin[0], (int)ent->s.origin[1], (int)ent->s.origin[2]);
        else
            gi.cprintf(ent, PRINT_HIGH, "Could not place %s - item classname unrecognised\n",cmd);
        
        return; // We messed up cmd
    }
    else if (Q_stricmp (cmd, "clearitems") == 0)
    {
        if (deathmatch->value)
        {
            gi.cprintf(ent, PRINT_HIGH, "This command is only available in single player mode\n");
            return;
        }
        
        Cmd_Clearitems_f(ent);
    }
    else if (Q_stricmp (cmd, "undoitem") == 0)
    {
        if (deathmatch->value)
        {
            gi.cprintf(ent, PRINT_HIGH, "This command is only available in single player mode\n");
            return;
        }
        
        Cmd_Undoitem_f(ent);
    }
    else if (Q_stricmp (cmd, "eraseitems") == 0)
    {
        if (deathmatch->value)
        {
            gi.cprintf(ent, PRINT_HIGH, "This command is only available in single player mode\n");
            return;
        }
        
        if (erasing)
        {
            erasing=0;
            gi.cprintf(ent, PRINT_HIGH, "New item eraser disabled!\n");
        }
        else
        {
            erasing=1;
            gi.cprintf(ent, PRINT_HIGH, "New item eraser enabled!\nCareful where you walk!\n");
        }
    }
    else if (Q_stricmp (cmd, "listitems") == 0)
    {
        if (deathmatch->value)
        {
            gi.cprintf(ent, PRINT_HIGH, "This command is only available in single player mode\n");
            return;
        }
 
        Cmd_Listitems_f (ent);
    }
    else if (Q_stricmp (cmd, "saveitems") == 0)
    {
        if (deathmatch->value)
        {
            gi.cprintf(ent, PRINT_HIGH, "This command is only available in single player mode\n");
            return;
        }
 
        Cmd_Saveitems_f (ent);
    }

Create a config directory within your mod dir. Compile the code, and try adding a few items in singleplayer. Start up the map in mp and try them out :).

Command

Syntax

Example

purpose

placeitem

placeitem [itemclassname]

placeitem weapon_shotgun

To put the items into the map

clearitems

clearitems

clearitems

to clear all the items you have placed from the map

undoitem

undoitem

undoitem

removes the last item you placed

eraseitems

eraseitems

eraseitems

toggles eraseitems mode on and off. while it is on you will delete any new item you touch

listitems

listitems

listitems

Lists all the items you've placed plus coords

saveitems

saveitems

saveitems

saves the file containing all the locations of your newly place items. failure to use this command will mean no new items will show up

A list of the items you can place(not including any you have added)

ammo_shells

ammo_bullets

ammo_grenades

ammo_rockets

ammo_cells

ammo_slugs

item_armor_body

item_armor_combat

item_armor_jacket

item_power_screen

item_armor_shard

item_power_shield

item_adrenaline

item_ancient_head

item_bandolier

item_breather

item_enviro

item_silencer

item_health

item_health_large

item_quad

item_health_mega

item_health_small

item_invulnerability

item_pack

 

 

key_airstrike_target

key_blue_key

key_red_key

key_commander_head

key_data_cd

key_data_spinner

key_pass

key_power_cube

key_pyramid

weapon_bfg

weapon_shotgun

weapon_supershotgun

weapon_machinegun

weapon_chaingun

weapon_grenadelauncher

weapon_rocketlauncher

weapon_hyperblaster

weapon_railgun

If you want to disable the weapons that are there on the level before you do anything to it, then add the code below to ED_CallSpawn in g_spawn.c</p>

if (Q_strncasecmp("weapon_",ent->classname,7)==0)
        {
               return;
        }
</pre>
<p> just below</p>
<pre class=code>
        if (!ent->classname)
        {
               gi.dprintf ("ED_CallSpawn: NULL classname\n");
               return;
        }
</pre>
<p> If you want to disable items then modify the if to read</p>
<pre class=code>
        if (Q_strncasecmp("item_",ent->classname,5)==0)
</pre>
<p> And to disable ammo then modify it to read</p>
<pre class=code>
        if (Q_strncasecmp("ammo_",ent->classname,5)==0)
</pre>
<p> To disable all of them then change the if to read</p>
<pre class=code>
        if ((Q_strncasecmp("weapon_",ent->classname,7)==0)||(Q_strncasecmp("ammo_",ent->classname,5)==0)||(Q_strncasecmp("item_",ent->classname,5)==0))

This will not stop the items you have placed from showing up


 

Skunkworks Tutorial