How To Add or Modify Items - Subject9x/battleMETAL GitHub Wiki

This article covers how to add or modify items in battleMETAL. ‘Items’ are a catch-all for describing both Weapons and Equipment because there is a code distinction between the two, however both are used by the hardpoint system. Items are attached to units via hardpoints, the hardpoints are defined in the unit’s data_ file.

[INFO] This article is in-depth, laying out how exactly all the pieces of getting weapons to work in battleMETAL are arranged.

[INFO] take notice of the #ifdef CSQC - these are compiler markers to pick what code is compile to CSQC and what is compiled for the Server.

Examples Non-projectile weapons - Light Laser

Projectile weapons - Ion Coil

Upgrade modules - Recharge Capacitor

Item Data Setup

Data for items are kept in the following folder: (code root)/common/data/items/

Every item has a definition file that looks something like this: Data_emp.qc

Inside the definition file, the data for a item is contained in a ini function: data_ini_weapon_emp_(){ ... };

This function is where all the item data is loaded onto the item being spawned by the game code. Data is organized by use, and labelled with comments for clarity.

Just Modifications

[WARN] back up your /id1/progs.dat and /id1/csqc.dat files before compiling any changes you make in Quake C!

[WARN] back up a copy of any .qc files you work on.

[INFO] if you are using the git source control tools, you can just keep your code in a side branch and you won’t have to backup files anywhere.

If you are just modifying existing item data, then simply changing values here will suffice. To test your changes, you must recompile progs.dat and csqc.dat because the data is stored in Quake C. Once you compile, your changes will be seen when you fire up battleMETAL.exe.

Adding New Items

[WARN] There is a maximum limit of 256 unique items that can be in the game, this limit is found in /common/data/data_values_game.qc

This next process will take you through the steps of adding a brand new item to the game. This will involve doing some level of coding but I’ve tried to keep the amount as low as possible.

  • Let’s start by going to the following folder: <code root/common/data/items/weapons/>
  • Then copy the file data_emp.qc, this is the quick way of standing up a new items file.
  • Rename this copy to data(your new item).qc_
  • Open this new file.
  • Change the function names from
    • te_muzzle_emp to te_muzzle_(your new item)
    • data_ini_emp_ to data_ini_(your new item)_
    • data_attack_emp_ to data_attack_(your new item)_
    • data_update_emp_ to data_update_(your new item)_

Next there’s 1 major variable that needs to be updated for new items:

  • self._data_idx This is used by the game as a unique identifier (UID) for the item, this must be unique. The easiest way to keep track is to look at /common/data/items/uid_items.qc to find the next unique number to assign to your new item.

Initialize the Item Data

First we’ll take a look at the main ini function where most of the Item data is kept. Go to the function named data_ini_(your new item). You must have the following variables set with your desired values in order for the item to work properly. This article uses /common/data/items/weapons/data_emp.qc as the example values.

Shared Values

  • .data_idx = 2;

    • The main unique ID for the item. All ITEMS share the same number sequence.
  • .w_clipsize = 1;

    • How many shots are in the clip of the item.
  • .reloadRate = 0.1;

    • Reload speed, in game-time, of each shot.
  • .reloadMax = 6;

    • Full Reload time, how many seconds it takes for item to reload back to full w_clipsize.
  • .energyRate = 266.7;

    • The Energy cost to fire a single shot of the item, this is deducted from the unit’s energy variable.
  • .w_range = 1700;

    • Range, in game units, of the item. This is more than just the max range of an item, this is also used by the menu system to tell the player the range of the item. Weapons with projectiles still use this to tell the player the range of those projectiles.
  • .damageValue = 400;

    • Amount of damage a single shot of the item does when hitting a target.
    • Projectiles : this value will be sent to any new projectiles this weapon uses for attacks.
  • .damageType = (DMG_ENE | EFF_STX | EFF_ENE);

    • Every item has a Type value for balancing and game purposes.The following are the defined types of weapon.

      • DMG_BAL = ballistic damage.
      • DMG_ENE = energy damage.
      • DMG_EXP = explosive damage.
      • DMG_MSC = designates weapon as 'equipment'
      • EFF_ARC = item has an arcing projectile, used by AI
      • EFF_ENE = item draws energy (client-side flag, really)
      • EFF_ENG = damage only affects energy
    • The desired types are arranged like the following when being used in the function: ( type | type | )

      • [WARN] Items can only have 1 DMG value, but can have multiple EFF_ values._
    • note Beta 2 v1.0.0 deprecates most of the DMG_ types in favor of .equipSize

  • .techLevel = 2;

    • A sort of meta-data element, currently not implemented. Originally this was part of the multiplayer code designed to limit equipment access.
  • .equipSize = ;

    • The size of the item when attempting to mount in a hardpoint. Primarily player-focused value.
    • REFERENCE: SizeType Bitflag
  • .spreadDefault = '0.009 0.008 0';

    • Defines the randomize spread of each shot of the item. The smaller the number, the tighter the shot grouping. A value of ‘1 1 0’ is massive, and equal to Quake’s original shotgun spread.
  • .wepFireOffset = '0 0.1 7';

    • This is a vector which defines where the muzzle flash effects, and any projectiles, will be when the item is ‘fired’.
    • this is used after a makevectors(wepAngles) call plus weapon.origin

Client-side Values

  • .w_name = "";

    • Game name for the item, this is for the Player.
  • .fire_sound = "sound/weapons/emp_fire.ogg";

    • The sound effect that will be played when the item is ‘fired.’
  • .model = "q3mdl/weapons/w_emp.md3";

    • The model object that will be rendered when the item is created.
  • .abbrev = "";

    • Deprecated, going to be removed.
  • .description = "";

    • Line-wrapped text description of weapon that is displayed in the small box in Weapon Info panel of the Arming Menu .
  • .icon = "gfx/ui/wep_icons/wi_mlgd.png";

    • file path to the item's image file, this is displayed in UI and HUD. Recommend a smaller png, something 64x64.
  • .itemMuzzleFlash = te_muzzle_lgm();

    • pointer to the client-side muzzle flash function.

Server-side Values

  • .w_isburst = FALSE

    • TRUE/FALSE for if the weapon is a burst-firing gun or not.
  • .w_attack = data_ini_attack_emp;

    • Very important, this value should be the item’s fire function. This is called whenever the item is ‘fired.’
  • .think = default_weapon_think;

    • Defines the update function. This code is called every frame, and generally covers reload checks. If you want custom reload behavior, you can create your own think() function and put its name here.
  • .data_ini_projectile = data_projectile_ini_emp_;

    • [Optional] This is defined if you want the item to launch a projectile when the item is ‘fired.’ Projectile functions are found in /common/data/projectiles/. We will go over projectiles later in this article.
  • .item_run_upgrade = <pointer to function>();

    • run when Unit spawns or when item has been repaired by a Repair Bay
  • item_remove_upgrade = <pointer to function>();

    • run when Unit loses this item.

Define the Attack function behavior

The next step is to define the ‘fire’ or ‘attack’ behavior for the item. This function is called every time the unit or Player makes the ‘fire’ command.

Above your item’s ini function, write the following function definition: void() data_ini_attack_(your item name)_;

This allows the main item ini function to point to the attack function, and then you can define the attack function later, mostly for readability.

Now, below your ini function, create a function block that has the same name as the attack function like so: void() data_ini_attack_(your item name)_ ={...};

This is where you will put all the behavior for when the item is ‘fired.’

[INFO] using other existing weapons as examples will assist in writing the code for your own weapons.

There are few major sections to the attack function that are important too almost every item.

Does an item require energy to fire?

As seen with most of the original Energy weapons in battleMETAL, some weapons require the Unit have a required amount of energy stored before the weapon can be fired. The EMP is a good example, it requires the Unit to have 266.7 Energy available before it can fire. After during the fire function, this amount of Energy is also subtracted from the Unit’s total Energy. If the Unit does not have enough Energy to fire, the EMP cannot be fired.

This effect is achieved using the following function you should put at the top of the attack function block: if( !_ctrl_weapon_checkEnergy()_ ){ return;}

This will prevent the item from firing if the Unit does not have the energy amount prescribed in the weapon’s ini value of self.energyRate.

Ammo in the magazine

Next we have the ammo-tracking function. Most items have a form of ammo and a time it takes to reload. To enable this functionality in your new weapon, you simply invoke the following function:

ctrl_weapon_updateAmmo_( **FALSE**, 1.75 );

This takes 2 main arguments - burstFire_, and burstRate.

  • burstFire is a TRUE / FALSE.

    • If TRUE, when the item has > 0 shots remaining in the clip, this setting will make the weapon automatically fire the entire clip in sequence after the Unit has made the first fire call.
  • burstRate is divided by self.reloadRate to produce the ‘Recoil’ wait time between shots.

    • If the item only has 1 shot maximum in a clip, this value is ignored.

Other than these 2 values, the function takes care of the rest of the item state-management. Now that we have item management out of the way, we can move to actually executing the shot code itself.

Setting up the shot

The last step for defining the attack function for your new item is filling out any damage-dealing behavior, projectile spawning, or special effects.

Taking Aim

battleMETAL takes care of the heavy lifting for you with a specific function called: ctrl_weapon_fireOffset();

This function calculates the required angle, positions, and the engine tracelinefunction call for this shot.

ctrl_weapon_fireOffset() populates the following globals after being run:

In Quake based on calling traceline()

  • entity trace_ent

    • the entity hit by the traceline().
  • vector trace_endpos

    • the end position in 3d space of the traceline().
  • float trace_fraction

    • the percentage (0.01 - 1.0) of the trace that was completed before it stopped.
  • float trace_plane_dist

    • the length, in game units, of the trace.

(there are a few more, but these aren’t necessary for understanding. I do recommend you look them up if you want to get deeper into modding Quake C.)

battleMETAL custom globals

  • vector FIRE_OFFSET

    • The complete offset from unit origin used for shot calculation.
  • entity WEAPON_PARENT

    • The direct parent entity of the item.
  • entity FIRE_ENT

    • The unit that is making the shot, usually the player.
  • vector FIRE_ORIGIN

    • The origin of the shot - based on unit.origin + FIRE_OFFSET.
  • vector FIRE_ENDPOINT

    • The endpoint of the shot itself after applying spread/convergence. These values are then accessible till the end of the function call, after that they will have values from the next fire request.

[WARN] do not rely on these globals to hold their value at all after you use them in the current function.

The Item spawns a projectile

To have the item spawn a projectile, the following steps need to be taken:

  • Check for projectile function if( self.data_ini_projectile )
    • This is a safety check to make sure you wired this up correctly.
  • Next call the ctrl_weapon_make_projectile( (timer) ) function -
    • This does the majority of the standup of making a new projectile
    • Executes self.data_ini_projectile()
    • Takes a timer for its argument, indicating the update delay before its update function is then run.
      • Setting the timer to -1 will set NEWSHOT’s lifetime to .p_lifetime.
      • Populates the global _NEWSHOT _as the new projectile.
    • The new projectile can be further customized by calling _NEWSHOT after the ini function.

Wait, item doesn’t need a projectile!

That’s cool too; this is what’s called a ‘hitscan’ shot then, where the shot instantly hits whatever is at the FIRE_ENDPOINT of the shot. To set this up, we just call t_damage directly -

t_damage ( entity trg, entity inflictor, entity attacker, float dmg_amt, vector dmg_point, vector force );

  • Trg

    • The target entity being struck by the damage. In fire functions, this usually is trace_ent which gets set by ctrl_weapon_fireOffset()
  • Inflictor

    • This is the entity that is dealing the damage, usually the weapon itself.
  • Attacker

    • The unit dealing the damage, ie the Weapon’s owner.
  • Dmg_amt

    • The amount of damage being dealt to the Trg entity.
  • Dmg_point

    • This is the 3d game-coordinate of where the impact on the Unit’s bounding box occurred. The damage function uses this point to find the closest subcomponent when determining which piece gets damaged.
  • Force

    • Not used very much, this is a vector of direction * magnitude to impart a crude version of impact force to the target.

[INFO] best example of the above code is either the data_light_laser.qc or data_light_atc.qc weapons.

How about special effects?

Weapon special effects are defined in the weapon's te_muzzle_<weapon> function. These muzzleflash functions are run in CSQC whenever a unit fires a weapon.

  • short 'how?' - Whenever a unit fires, they populate a field variable .attackFlag with their hardpointId bitflag.
  • this value is then passed via SendEntity to each client.
  • CSQC then finds the Weapon on the client attached to this ID, and runs its te_muzzle_<item> function.

Using other weapon files as examples, you can also have sound and particle effects triggered in this code.

Both systems use different function sets which are outside the scope of this article. There are numerous examples within the existing weapon files to understand the basics of doing special effects.

Define the Update function behavior

Remember this variable in your item’s ini function? self.think = default_weapon_think_; This defines the update function for the item during gameplay. The controlling code calls this function for every item the unit has, and on every frame (meaning very frequently).

The default function, default_weapon_think, basically handles reloading the item. You can find this function over in the file common/data/data_header.qc .

Majority of items in vanilla battleMETAL don’t adjust the think() behavior, for the off-hand that you want to change some bits, this section details how to at least setup the alternate behavior.

Because this new update function is referenced in the _ini_ you will need to define it before the _ini_. You can either write the whole function above the _ini_ or just the reference, and then define it later. For readability, I usually just define the reference and then write the function below.

Now that you’ve defined the function, make sure to assign this function to the item’s .think value, replacing default_weapon_think. self .think = (your new think function);

If you still want the default reload code in your custom think, you can just copy the code in default_weapon_think and past it into your new function. Other than that, the sky's the limit for adding behavior. To get the player entity in this function you can just call self.owner.

Wiring the Item into Place

The last step for creating your item data is to get this wired into the game data system. Remember the data_idx variable from the top of the file? This is why it needed to be unique. To make your new item available for play, you must complete the following steps:

  1. common/data/item/uid_items.qc

  2. Locate the function:

    • void() initialize_data_item_={....}
  3. You will see a bunch of SWITCH: CASE blocks, each one calling a specific data_ini(item)_ function.

    • To get your item on the list, simply add a new block above the DEFAULT case like so

    • Case : data_ini_(your item)_(); Break;

    • The break; is very important, this should always be at the end of any of your new Case statements. This allows the code to leave the function early, without having to check any other cases.

Now post it online, and celebrate! And that’s it for this article. I hope this clears up how to create your own weapons and equipment for battleMETAL.

[INFO] there is another article for adding your weapon to the menu system.

⚠️ **GitHub.com Fallback** ⚠️