
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