Serious Sam Fusion 2017 (beta)

Serious Sam Fusion 2017 (beta)

Not enough ratings
Guide to Weapon Engine mod
By NSKuber
This guide describes how to utilize Weapon Engine mod in order to create weapons with creative functionality that cannot be achieved through usual means given us by CT.
   
Award
Favorite
Favorited
Unfavorite
So what is this?
Weapon Engine mod is essentially a re-creation (with its own flaws and improvements) of the firing system in Serious Sam Fusion. Weapon functionality in Sam can be considered rather poor, and the Weapon Engine mod aims to improve that. At least until Croteam finishes their new weapon system, but that is not likely to happen until SS4 arrives.

The Engine's basic function idea is the following:
1) You create a special "Throw macro event" event in weapon's firing animation;
2) Whenever the firing animation is played, script picks up that event and then launches a number of fireable objects which are specified beforehand in a script table.

The great things are:
1) You can add one or more firing events into one animation, and add them into different frames, making the weapon not necessarily shoot immediately, and possibly not once.
2) Each firing event can have an arbitrary number of fireable objects, each of which has its own parameters, like type (hitscan, projectile or beam (YES, BEAM!)), spread, origin of fire, etc.

You can also add alternative firing modes now!

There are, of course, a number of disadvantages of this system when compared to default one.
The main is, because any scripted thing is inherently less stable and reliable than native coded by CT. Especially in MP (this mod *does* work in Multiplayer). So unless the weapon functionality that you have in mind cannot be recreated using default system, I don't recommend using this mod.
A simple example
I assume that the reader of this guide is familiar with SED, and knows how to handle weapons, models and animations.

Ok, so what exactly do you have to do in order to make your weapon function through this script?

DOUBLE SHOTGUN RECREATION
Let's consider a simple example: recreating BFE Double Shotgun functionality using Weapon Engine.

First of all, I find Double Shotgun weapon parameters and set "Bullets", "Bullet damage" and "Bullet range" to 0. We won't be needing those Croteam's hitscan pellets!

After that, I add a Script entity onto the level and open its text. This script will contain all the information that Weapon Engine needs to hande firing of my weapon.
First line in this script should always be
Wait(CustomEvent("OnStep"))
Then I create a table which will hold all scripted firing events of our weapon for Weapon Engine to process using the following code. All such tables are stored in global worldGlobals.WeaponScriptedFiringParams table:
local path = "Content/SeriousSam3/Databases/Weapons/DoubleShotgunWeapon.ep" worldGlobals.WeaponScriptedFiringParams[path] = {}
Here, for convenience, I created a local variable "path" which contains the path to the weapon params file of my weapon, because it will be used quite often.

After that I add into the newly created table for my weapon a firing event with some name, let it be "Fired" (again, it's an empty table for now):
worldGlobals.WeaponScriptedFiringParams[path]["Fired"] = {}

Now I need to make so that the weapon signals that event when firing. To do that, I open my Double Shotgun's First Person model and go to Animation editor, select firing animation and add a "Throw macro event" by pressing yellow plus on the left in "Events" ("Animation" tab). In "Event" field I put my event name "Fired", and in "Frame" I put first frame of the animation (0 in this case)


Next I need to specify fireable objects which will be fired when the event is signaled.
Let's return to the script. I create one pellet by adding the following code:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1] = {} worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["type"] = "hitscan" worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["source"] = "Barrel01" worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["direction"] = "crosshair" worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["damage"] = {10} worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["impulse"] = 200 worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["randomSpread"] = {1,1} worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["tracerProbability"] = 0.5 worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["noBulletTracersOnViewer"] = true

What do these mean?

The first line creates a fireable object with index 1 (which is, again, a table) - this is our pellet.

The second defines the type of the fireable object, in this case it's hitscan.

The third specifies the origin of the fireable object, in this case it's a "Barrel01" attachment on weapon.

Fourth line specifies the direction at which the object will be initially aiming. Here it is "crosshair", meaning the point at which your crosshair is poinitng.

Fifth line specifies the damage of the pellet (don't think about braces right now).

Sixth specifies the force impulse generated by one pellet.

Seventh sets random spread (at 100 meters) of a pellet - here it will be a circle of 1 meter radius.

Eighth sets the probability of visual tracers.

The last line specifies that the tracers will not be shown for the player who shot the weapon, but only for other players in the game.

And that's almost it for a single pellet!

As you may know, default shotgun weapons in Sam use fixed pellet patterns - we will add that later.

Now, Double shotgun fires 14 pellets, so I need to add 13 more fireable objects. But you don't have to add that many lines of code for each new fireable object! You can copy it via a special function worldGlobals.WeaponEngineCopyFireableObject(pathFrom,strEventFrom,iFrom,pathTo,strEventTo,iTo)!
As you can see, it has 6 arguments - first three specify path of the weapon, event and fireable object index which will be copied, and last three specify where will it be copied to. Please note that the event table with name strEventTo for weapon with path pathTo should already exist when using this function! In our case, the pellet multiplying proccess will look as follows:

for k=2,14,1 do worldGlobals.WeaponEngineCopyFireableObject(path,"Fired",1,path,"Fired",k) end
Here we copy first pellet into pellets 2-14 of the same weapon and same firing event.

We're almost done here. Right now, out shotgun will shoot 14 pellets directly down the crosshair with a small random spread (1 meter circle at 100 meters distance). We want to add pattern to our shotgun. Here's how we do it:
local DoubleShotgunPattern = {{-8.5,4.2},{0,4.2},{8.5,4.2},{-11.5,1.4},{-3,1.4},{3,1.4},{11.5,1.4},{-8.5,-1.4},{0,-1.4},{8.5,-1.4},{-11.5,-4.2},{-3,-4.2},{3,-4.2},{11.5,-4.2},} for k=1,14,1 do worldGlobals.WeaponScriptedFiringParams[path]["Fired"][k]["offset"] = DoubleShotgunPattern[k] end

First, I create a table DoubleShotgunPattern, which contains pairs of numbers representing {x,y} offset from center of each pellet. Then I add these offsets to each of our 14 pellets.

We're done with specifying the parameters of our weapon!

Now I go to "Entity" tab while the script entity is selected, press on (local) -> Save As and save this script into each of the three folders under some name of my preference:

/Content/SeriousSamHD/Scripts/CustomWorldScripts/
/Content/SeriousSamHD_TSE/Scripts/CustomWorldScripts/
/Content/SeriousSam3/Scripts/CustomWorldScripts/

(saving it in each folder makes it work in the levels of the game you saved it in).

To rehash, this is how the full script, setting up out shotgun, will look like:
Wait(CustomEvent("OnStep")) local path = "Content/SeriousSam3/Databases/Weapons/DoubleShotgunWeapon.ep" worldGlobals.WeaponScriptedFiringParams[path] = {} worldGlobals.WeaponScriptedFiringParams[path]["Fired"] = {} worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1] = {} worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["type"] = "hitscan" worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["source"] = "Barrel01" worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["direction"] = "crosshair" worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["damage"] = {10} worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["impulse"] = 200 worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["randomSpread"] = {1,1} worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["tracerProbability"] = 0.5 worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["noBulletTracersOnViewer"] = true for i=2,14,1 do worldGlobals.WeaponEngineCopyFireableObject(path,"Fired",1,path,"Fired",i) end local DoubleShotgunPattern = {{-8.5,4.2},{0,4.2},{8.5,4.2},{-11.5,1.4},{-3,1.4},{3,1.4},{11.5,1.4},{-8.5,-1.4},{0,-1.4},{8.5,-1.4},{-11.5,-4.2},{-3,-4.2},{3,-4.2},{11.5,-4.2},} for k=1,14,1 do worldGlobals.WeaponScriptedFiringParams[path]["Fired"][k]["offset"] = DoubleShotgunPattern[k] end

And whenever a "Fired" macro event will be signaled by our shotgun (which will happen right at the start of firing animation, so each time you shoot), all those 14 pellets will be "fired" by the script!
General algorithm
Let's reiterate how exactly you create a proper firing event:

  1. Add a script entity, put Wait(CustomEvent("OnStep")) line into it first;
  2. Create a table holding all firing events for your weapon by its params path;
  3. Add a firing event (table) into that table;
  4. Add a "Throw macro event" with the name of your firing event into your weapon's animation;
  5. Fill up your firing event with fireable objects (each is a table);
  6. Fill up each fireable object with its params;

After all is done and your script is set up, you save that script into "/Scripts/CustomWorldScripts/" folder of each gametitle you want that script to run in.
Pros and Cons of Weapon Engine
As mentioned above, Weapon Engine has a number of disadvantages over default system. But it also has a great deal of advantages! Let us list some of them:
  • The main advantage is that you can set up firing events at any point of the animation, and have an arbitrary number of firing events per animation!
  • You can also specify any number of fireable objects per firing event!
  • And each fireable object can have its own unique type, spread, range, damage, projectile, launch speed, effects, you name it!
  • BEAMS
  • You can make chargeable/randomized damage hitscan/beams!
  • You can make randomized projectile speed!
  • You can customize hitscan/beam effects, including hit effects and sounds!
  • Probably more, it's just an amazing tool for making crazy silly/creative weapons!

Ok, let's get down to disadvantages:
  • The main disadvantage is, because any scripted thing is inherently less stable and reliable than native coded by CT, this may be less stable than default system, especially in MP (the Weapon Engine *does* work in Multiplayer).
  • Projectiles have some issues. Because it is currently impossible to specify a weapon when a projectile is launched via scripts, projectiles sometimes will not gib enemies which they should, they will not be affected by scripted damage multipliers, and their damage will not be affected by charge in case of chargeable weapons;
  • In Multiplayer, base hit effects of hitscan/beams can be inaccurate, especially on solids;
  • Because scripts work at the end of each frame, if a weapon has high firing rate but your framerate is low, the firing rate of the Weapon Engine will be inconsistent between consecutive shots;
  • Probably more here too, will add if I remember other issues;

Now let's move onto the list of all possible things you can customize with Weapon Engine.
Meta params: if your weapon is chargeable
If you take a chargeable weapon (Grenade Launcher, Cannon) as the base of your weapon, you should add a special entry into its firing events table. Assume that the table for your weapon is already created, and the path to your weapon params is in variable path, then you need to add the following there:
worldGlobals.WeaponScriptedFiringParams[path]["charging"] = {fMaxChargeTime, bAutoFire}
where fMaxChargeTime is a number, equal to the max charging time of your weapon (field "Charging time" in weapon params), while bAutoFire is a boolean variable (true or false), denoting whether your weapon has "Auto fire" flag enabled or disabled. It is important to properly set this for proper functioning of chargeable weapons!

For example, for the Cannon it looks like this:
worldGlobals.WeaponScriptedFiringParams[path]["charging"] = {1.25, false}
Firing event params
Now assume you have created your firing event (e.g. with the name "Fired" again) for your weapon with path path. You have couple options here too!

firingSounds
You can add a sound to your firing event! This can come in handy if the firing sound should be played not at the start of your firing aniamtion. One can argue "but wait, why do we need script for this when we can just add a "Sound event" to the animation?" Well, you can, but there is an issue.

Each sound term in the sound scheme of the model has flags.

If your term has flags "3D, volumetric", then it will sound "volumetric" for everyone, which is good for the player firing, while it's a bit worse for all other players - the sound won't be directional and others won't be able to determine where it's coming from.

If you remove "volumetric" flag and leave only "3D", then the sound will be directional, but it will be directional for everyone, including the player firing, which usually sounds worse for him as compared to having "volumetric" too.

The firing sound added by Weapon Engine combines the best of both, spawning "3D, volumetric" sound for the player who fires, and just a "3D" sound for everybody else.

(also you can modify range of such scripted firing sound)

So here is how you set it up: first you add a table with a name "firingSounds" which will contain all firing sounds variations:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"]["firingSounds"] = {}
Then you add all variations of sounds, like this:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"]["firingSounds"][1] = {strSoundPath, fVolume, fPitch, fPitchRandom, fHotspotRange, fFalloffRange}
Here, strSoundPath is the path to the sound file, fVolume is the volume of the sound (number from 0 to 1), fPitch is the base pitch of the sound (number from 0 to 1), fPitchRandom is a random added pitch (again, number from 0 to 1), fHotspotRange is the hotspot range of the sound (any positive number) while fFalloffRange if the falloff range.

All parameters except for strSoundPath are optional, you can leave only the path.

You can add any number of sounds, just make sure they're properly numbered from 1 and up to the last one. The sound to be played on each signaling of the event will be chosen at random.

Example for the Laser sound from my Alt-Fire mod ("FiredBaseRU" is a firing event for RU barrel):

worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseRU"]["firingSounds"] = {} worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseRU"]["firingSounds"][1] = {"Content/SeriousSam3/Sounds/Weapons/Laser/_Fire.wav",1,1,0.02}

ammoSpent

This one is simple - it's a number of ammo units which will be subtracted from your total ammo count when a firing event is signaled. Set up like this:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"]["ammoSpent"] = iAmount
where iAmount is any integer (can even be negative, resulting in awarding ammo lol).

Example for the Rocket Launcher laser-guided rocket from my Alt-Fire mod ("FiredAlt" is a firing event for that rocket):
worldGlobals.WeaponScriptedFiringParams[path]["FiredAlt"]["ammoSpent"] = 1

fireWithoutAmmo

This is an addition to the previous one - it's a Boolean variable (can be set to true or false).
If set to false, then nothing will happen if the player doesn't have enough ammo required by previous parameter.
If set to true, then the script will handle a firing event in any case, even if ammo requirements aren't met (consuming all remaining ammo in the process).
Fireable object params: type
Assume now you have a weapon with path path and a firing event "Fired" in it, and you also have created a fireable object with index 1 using the code
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1] = {}
Now we got a lot of possible params to add here. But first, let me take a selfie you need to specify the most important parameter - type of the fireable object!

There are 3 types at the moment: "hitscan", "projectile" and "beam".

Hitscan works, well, like hitscan. It casts a ray towards specified direction and applies damage and effects to the first thing it hits (or doesn't hit anything if max range is reached) and that's it.

Projectile is straightforward too - launches a projectile specified in another parameter.

Beam is pretty similar to hitscan, but if the beam hits an enemy (alive or dead), or a destructible object which it destroys, then it travels further and damages other enemies/objects behind it, up until it hits something too hard for it, or reaches max range.

So you set up your type by filling out "type" field of your fireable object, like this:

worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["type"] = "hitscan"
Fireable object params: source, direction, offset and spread
After you specified the type of your fireable object, you may want to specify where it will be fired from and to. The following 4 parameters are responsible for this.

source
MANDATORY
Applicable: all types


This should be a string, which can be either lookOrigin, or the name of an attachment from which the object should be fired (default weapons, except laser, are hardcoded to use "Barrel01", for example).
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["source"] = "Barrel01"

direction
MANDATORY
Applicable: all types


This should be a string, which can be either crosshair or look. This defines the base direction at which the fireable object is aimed before adding offset or random spread.
crosshair sets this direction so that it points exactly to what your crosshair is aiming at. This is how current Croteam aiming system works.
look sets this direction exactly same as your First-Person camera has. This is how CT aiming system works in all Sam games prior to Fusion and used to work in Fusion until a few patches ago.
The difference can be noticed when the source is not lookOrigin, but some attachment.
I highly recomment using "look" direction for beams and piercing projectiles lest your damage go in unintended direction after the first enemy.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["direction"] = "crosshair"

offset
OPTIONAL
Applicable: all types


This is a pair of numbers (in meters), which define the offset of the point which fireable object will hit at 100 meters distance. First number is X coordinate (positive - shift to the right, negative - to the left), while the second is Y coordinate (positive - shift upwards, negative - downwards).
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["offset"] = {100,100}
In this example, the fireable object direction will now be shifted 45 degrees upwards and 45 degrees to the right, because it is shifted by 100 meters up and to the right at 100 meters distance.

randomSpread
OPTIONAL
Applicable: all types


This is a pair of numbers (in meters), which define the elliptic random spread at 100 meters, which will be added after the offset. First number is the width of the ellipse, while the second is the height.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["randomSpread"] = {2,2}

In this example, a random spread in the circle of up to 2 meters at 100 meters will be added to the fireable object direction.



Fireable object params: projectile params
Params listed in this section are applicable only if the "type" is set to "projectile".

projectile
MANDATORY
Applicable: projectile


This is a string path to the params file of the projectile to be launched.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRU"][1]["projectile"] = "Content/SeriousSam3/Databases/Projectiles/Laser_Green.ep"

velocity
MANDATORY
Applicable: projectile


This is a table, containing one or two numbers, defining the launch velocity of the projectile. If there are two numbers, then
  1. if the weapon is chargeable, then the first number denotes velocity at min charge, while the second - at max charge;
  2. if the weapon is not chargeable, then the velocity will be chosen at random between the first and the second number;
If there is one number, it's the same as if you put both numbers but made them the same.
IMPORTANT: projectile damage is NOT affected by and weapon scripted damage multipliers and by charge in case of chargeable weapon! This is something out of my reach right now, unfortunately.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRU"][1]["velocity"] = {120}
Fireable object params: functional hitscan/beam params
The following are functional parameters applicable to either "hitscan" or "beam" fireable object, or both.

range
OPTIONAL
Applicable: hitscan, beam


This is a positive number (meters), defining the maximum range of the hitscan or beam. If not defined, will be set to 500 by default.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["range"] = 150

damage
MANDATORY
Applicable: hitscan, beam


This is a table, containing one or two positive numbers, defining the damage of the hitscan/beam. If there are two numbers, then
  1. if the weapon is chargeable, then the first number denotes damage at min charge, while the second - at max charge;
  2. if the weapon is not chargeable, then the damage will be chosen at random between the first and the second number;
If there is one number, it's the same as if you put both numbers but made them the same.
Damage is affected by Serious Damage and weapon scripted damage multipliers.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["damage"] = {10}

piercingPower
OPTIONAL
Applicable: beam


This is a table, containing one or two positive numbers, defining the piercing power of the beam.
The piercing power functions as follows: if the enemy hit by the beam has maximum health less than or equal to the piercing power of the beam, the beam will travel further. If its max health is greater than piercing power, the beam will end there.
If there are two numbers, then
  1. if the weapon is chargeable, then the first number denotes piercing power at min charge, while the second - at max charge;
  2. if the weapon is not chargeable, then the piercing power will be chosen at random between the first and the second number;
If there is one number, it's the same as if you put both numbers but made them the same.
If this is not defined, will be set to 1000000 (one million) by default.
Piercing power is affected by Serious Damage and weapon scripted damage multipliers.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["piercingPower"] = {140}

bulletRadius
OPTIONAL
Applicable: hitscan, beam


This is a positive number (meters), defining the radius of the beam/hitscan when tested against enemies. The width of the bullet when tested against solid objects is always 0. If not defined, will be set to 0.1 by default.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["bulletRadius"] = 0.4

damageType
OPTIONAL
Applicable: hitscan, beam


This is a string, containing the type of damage which will be applied to the object hit by hitscan/beam. If not defined, will be set to "Bullet" for hitscan and "Piercing" for beam by default.
You can look up which damage types exist in the game by, for example, opening any projectile params and expanding "Type of direct damage" list.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["damageType"] = "Plasma"

impulse
OPTIONAL
Applicable: hitscan, beam


This is a positive number, defining the amount of force impulse applied to the enemy hit by the fireable object.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["impulse"] = 200

explosionProjectile
OPTIONAL
Applicable: hitscan, beam


This parameter allows you to add explosions to your hitscan/beam!
It should be a path to the projectile which will be spawned at the end of the beam/hitscan (e.g. when hitscan hits the target or beam hits unpierceable object). This projectile will be spawned and immediately explode. It is purely used to apply explosion damage to things.
This projectile must have "Magic" type!
For best result, this projectile should also (1) have "empty" model - without mesh, without explosion effects and sounds (they will be set up below). But it must have collision! (2) it should also have just the explosion damage set up - it's the only one that will matter anyway.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["explosionProjectile"] = "Content/SeriousSam3/Databases/Projectiles/Rocket_Player.ep"

explosionEveryHit
OPTIONAL
Applicable: beam


This is a boolean variable. If set to true, then explosion (specified above) will happen on each target the beam damages instead of only in the end of the beam.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["explosionEveryHit"] = true

explosionEffects
OPTIONAL
Applicable: hitscan, beam


This is a table containing paths to particle effects to use as explosion effects for aforementioned hitscan/beam explosion. If the table exists, one of the particles will be spawned at random when the explosion is happening.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["explosionEffects"] = {"Content/SeriousSam3/Presets/Effects/Generic/Explosion_04.pfx"},

explosionSounds
OPTIONAL
Applicable: hitscan, beam


This is a table containing paths to custom sounds use as explosion sounds for aforementioned hitscan/beam explosion. If the table exists, one of the sounds will be spawned at random when the explosion is happening.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["explosionSounds"] = {"Content/SeriousSam3/Sounds/Explosions/Explosion11_mono.wav"},

signalHitEvents
OPTIONAL
Applicable: hitscan, beam


This should be a boolean variable. This is purely for me and other scripting modders. If set to true, then each time hitscan/beam hits an object, it will signal a custom event on the weapon entity which fired the object: "WeaponEngineHitscanHit" in case of hitscan and "WeaponEngineBeamHit" in case of beam.
The event has a payload with 3 members:
  1. entity - the entity which was hit;
  2. point - the point which was hit;
  3. normal - the hit normal;
Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["signalHitEvents"] = true
Fireable object params: visuals for hitscan/beam
The following are visual parameters applicable to either "hitscan" or "beam" fireable object, or both.

beamEffects
OPTIONAL
Applicable: beam


This is a table, describing visual effects of the beam.
First element of the table is a positive number, defining the duration (in seconds) of the beam effects. After this time, the particles will be deleted.

Second element of the table is a positive number, defining the maximum range at which particles of the beam will be spawned. This can be useful if your beam reach is very far, but your particles are too intense/small and you want to limit the number of them to improve performance.

After the second element, all consequent elements should be tables again, consisting of a number-path pair, defining the length of the particle and the path to it.
Each next pair should have length smaller than the previous one.
Different variants can be used to more efficiently and precisely visualize the length of the beam.

Example:
worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1]["beamEffects"] = {1,150, {3,"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Effects/LaserRay.pfx"}, {1,"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Effects/LaserRay_Med.pfx"}, {0.4,"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Effects/LaserRay_Small.pfx"},}

Let me explain how these work:
Assume you have three particles set up: first of length 3, second of length 1 and third of length 0.4, like in the example above.
Assume that the beam to be visualized has length 100.5 meters.
  1. First, every 3 meters (length of first particle), the first particle will be spawned.
  2. When there will be less than 3 meters left to cover (in our case, 100.5 - 33*3 = 1.5 meters), script will switch onto next particle, which has length 1. 1 is less than 1.5, so one particle of length 1 will be spawned.
  3. The remaining 0.5 meters are less than the length of the second particle, so the script will switch to the third particle of length 0.4, and spawn one of it.
  4. Then, 0.1 meters will remain, which is less than 0.4, but there are no smaller particles left, so these 0.1 meters will be covered by a 0.4 meter particle again.

tracerProbability
OPTIONAL
Applicable: hitscan, beam


This is a number between 0 and 1, defining the probability of a bullet tracer appearing. Unfortunately, it is not possible to determine whether the player has first-person or third-person camera enabled, so by default these tracers will be visible even if you are firing in first person.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["tracerProbability"] = 0.5

noBulletTracersOnViewer
OPTIONAL
Applicable: hitscan, beam


This is a boolean var, true or false. If set to true, hitscan/beam tracers will not spawn for the player who is shooting the weapon (both in FP and TP view), but only for other players in the game.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["noBulletTracersOnViewer"] = true

customTracerEffects
OPTIONAL
Applicable: hitscan, beam


This is a table containing paths to custom particle effects to use instead of base bullet tracer particle. If the table exists, one of the particles will be chosen at random.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["customTracerEffects"] = {"Content/SeriousSam3/Presets/Effects/Generic/BloodTrail_02.pfx", "Content/SeriousSam3/Presets/Effects/Generic/BloodTrail_03.pfx"}

noDefaultBodyHitEffect
OPTIONAL
Applicable: hitscan, beam


This is a boolean var, true or false. If set to true, disables the default hit particles and sounds on bodies (enemies/corpses).
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["noDefaultBodyHitEffect"] = true

bodyHitEffects
OPTIONAL
Applicable: hitscan, beam


This is a table containing paths to custom particle effects to use instead/in addition to base body hit particle. If the table exists, one of the particles will be spawned at random when a body is hit.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["bodyHitEffects"] = {"Content/SeriousSam3/Presets/Effects/Generic/BloodTrail_02.pfx", "Content/SeriousSam3/Presets/Effects/Generic/BloodTrail_03.pfx"}

bodyHitSounds
OPTIONAL
Applicable: hitscan, beam


This is a table containing paths to custom sounds to use instead/in addition to base body hit sound. If the table exists, one of the sounds will be spawned at random when a body is hit.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["bodyHitSounds"] = {"Content/SeriousSam3/Sounds/Impacts/Stone/Impact_Stone01.wav", "Content/SeriousSam3/Sounds/Impacts/Stone/Impact_Stone02.wav"}

noDefaultSolidHitEffect
OPTIONAL
Applicable: hitscan, beam


This is a boolean var, true or false. If set to true, disables the default hit particles and sounds on solids.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["noDefaultSolidHitEffect"] = true

solidHitEffects
OPTIONAL
Applicable: hitscan, beam


This is a table containing paths to custom particle effects to use instead/in addition to base solid hit particle. If the table exists, one of the particles will be spawned at random when a solid is hit.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["solidHitEffects"] = {"Content/SeriousSam3/Presets/Effects/Generic/BloodTrail_02.pfx", "Content/SeriousSam3/Presets/Effects/Generic/BloodTrail_03.pfx"}

solidHitSounds
OPTIONAL
Applicable: hitscan, beam


This is a table containing paths to custom sounds to use instead/in addition to base solid hit sound. If the table exists, one of the sounds will be spawned at random when a solid is hit.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["solidHitSounds"] = {"Content/SeriousSam3/Sounds/Impacts/Stone/Impact_Stone01.wav", "Content/SeriousSam3/Sounds/Impacts/Stone/Impact_Stone02.wav"}

solidHitDecals
OPTIONAL
Applicable: hitscan, beam


This is a pair path-number, where the path leads to a templates file, containing custom decals, and the number denotes the number of different decals in that templates file.
These decals will use instead/in addition to base solid hit decals. If the table exists, one of the decals will be spawned at random when a solid is hit.
Example:
worldGlobals.WeaponScriptedFiringParams[path]["Fired"][1]["solidHitDecals"] = {"Content/MySolidHitDecals.rsc",3}

You can open "Content/Shared/Scripts/Templates/WeaponEngine/CustomDecalTemplate.rsc" file to see how to set up templates file (and resave it as base for your templates file).



Extra: looping firing sound
Sometimes you may have a weapon for which you have one long looping firing sound which you would like to play while the weapon is firing. The Weapon Engine provides a functionality for you!

Here is how you set it up: assume the path to your weapon's params is in variable path once again. Then you create a table:
worldGlobals.WeaponScriptedFiringSoundsParams[path] = {}

Next, you need to come up with a name for your custom looping firing sound event. Let it be "FiringSound":
worldGlobals.WeaponScriptedFiringSoundsParams[path]["FiringSound"] = {}
Then you want to go to the firing animation of your weapon, and add a bunch of macro events with name "FiringSound" there so that your weapon signals it not less frequently than once every 0.2 seconds! For example, if your weapon's firing animation has frame time 0.033333, then signal it each 3-5 frames for safety! See Double Shotgun example in the beginning of the guide to see how to add macro events.

Then this table should contain one or three entries.

If you have one looping sound which you want to play whenever your weapon is firing, then there should be just one entry, which is, like in singular firing sounds case (see "Firing event params section") should contain a path to a sound and a number of optional parameters:

worldGlobals.WeaponScriptedFiringSoundsParams[path]["FiringSound"][1] = {strSoundPath, fVolume, fPitch, fPitchRandom, fHotspotRange, fFalloffRange}
Here, strSoundPath is the path to the sound file, fVolume is the volume of the sound (number from 0 to 1), fPitch is the base pitch of the sound (number from 0 to 1), fPitchRandom is a random added pitch (again, number from 0 to 1), fHotspotRange is the hotspot range of the sound (any positive number) while fFalloffRange if the falloff range.

If you want to have one sound playing whenever the weapon is first fired and then you want it to switch to another sound after some time has passed while firing continuously, then you need to add 2 more entries.

The second entry should be like the first, but contain the information about that "starting sound".

The third entry should be a positive number, specifying for how long (in seconds) the starting sound will be played before it switches to the base looping sound.

Example:
worldGlobals.WeaponScriptedFiringSoundsParams[path]["FiringSound"] = {{"Content/SeriousSam3/Sounds/MyLoop.wav",1,1,0.02}, {"Content/SeriousSam3/Sounds/MyLoopBegin.wav",1,1,0.02}, 1}

Here, the sound "MyLoopBegin.wav" will be played for 1 second of continuous firing and then switched to "MyLoop.wav".
Extra for scripters
Obtaining projectile owner
Whenever projectile is spawned by Weapon Engine, its owner is written into the worldGlobals.WeaponEngineProjectileOwner table, like this:
worldGlobals.WeaponEngineProjectileOwner[projectile] = owner
where projectile is the projectile entity and owner is the player puppet which launched the projectile. This allows for much easier detection of the owner of the projectile.

Modifying firing event on the fly
Whenever a weapon which uses a Weapon Engine scripted firing is drawn, it's firing events table is fully duplicated to the temporary worldGlobals.CurrentWeaponScriptedParams table, which is actually used in the script, so whatever is contained in the worldGlobals.WeaponScriptedFiringParams[path] table, is now contained in the worldGlobals.CurrentWeaponScriptedParams[weapon], where weapon is the weapon entity.

You can modify this table on the fly to change how weapon functions, without affecting the initial table!

Note, however, that it is necessary that some info (projectile paths, barrel names) should be present in the table at the time the weapon is drawn, so it's better to place all info you will need into worldGlobals.WeaponScriptedFiringParams[path] table beforehand!
Adding firing modes
Seen that "Alternative Fire modes" mod?

Well, you can do similar stuff yourself now using Weapon Engine!

How? Same as with firing events, by filling out special table. Assume again, that the variable path contains the path to your weapon. First you create a table for all firing modes of your weapon:
worldGlobals.WeaponEngineFiringModes[path] = {}
Next, you can enable the ability for the player to switch firing modes using "Alternative fire" or "Reload" key (or both) and not only using dedicated custom keybind by adding either (or both) of these strings:
worldGlobals.WeaponEngineFiringModes[path]["allowAltFire"] = true worldGlobals.WeaponEngineFiringModes[path]["allowReload"] = true
First one enables switching using "Alt Fire", second - using "Reload".

(by the way, custom keybinds don't work in SED, but you can switch modes in SED using your "Use" key (E by default))

Next up you should fill up special firing modes ("firing mode" here is a set of actions to perform upon switching to that firing mode). Note that these modes should have numbers from 1 and up to the last mode you want to have.
worldGlobals.WeaponEngineFiringModes[path][1] = {}

Now you can fill special options for the first firing mode (and after first create more firing modes and fill them too).

eventsSwitch
This is the basic parameter. It is a table consisting of string pairs. Each pair represents which firing event params (first element in the pair) will be replaced by another firing event (second element in the pair). Let me show an example:
worldGlobals.WeaponEngineFiringModes[path][1]["eventsSwitch"] = { {"Fired","FiredBase"} }
This means, that whenever the script will switch to the first firing mode, contents of event "Fired" will be replaced by contents of event "FiredBase" (all this happens in a special dynamic table and doesn't affect default Weapon Engine table) in the following manner:
worldGlobals.CurrentWeaponScriptedParams[weapon]["Fired"] = worldGlobals.CurrentWeaponScriptedParams[weapon]["FiredBase"]

You can add more pairs to this table and switch firing events around as you wish. Just remember, that, e.g., in example above, when "Fired" event is overwritted, whatever table was in that event prior to switch, will be lost unless it's available from somewhere else.

speedMult
This is a number, which, if defined, will determine the speed multiplier applied to (base) weapon speed upon switching to the firing mode. Example:
worldGlobals.WeaponEngineFiringModes[path][1]["speedMult"] = 2

attachments
This is a table of strings, containing the names of attachments which will be shown upon switching to the given firing mode. All attachments specified in all other firing modes will be hidden before attachments for the current mode are shown.
It is important that all of these "mode"-related attachments be set to "Hidden" in the weapon's model! Otherwise the script won't be able to hide them.
Example:
worldGlobals.WeaponEngineFiringModes[path][1]["attachments"] = {"Mode02"}

extras
This one is for scripters. extras is a function with any arbitrary code, which will be executed upon switching to the given firing mode. This function is given two arguments, the first is the player wielding the weapon, and the second is the weapon entity itself.
Note that all operations listed above (including this additional extra code) are carried out on all machines, both host and clients.
Example:
worldGlobals.WeaponEngineFiringModes[path][1]["extras"] = function(player,weapon) HandleARBurstFiring(player,weapon,weapon:GetParams():GetFileName()) end

That is all. See more examples from real Alt-Fire weapons in the last section of the guide.

P.S. if you need, you can obtain the current firing mode set on a weapon from the table:
worldGlobals.AltFireCurrentFiringModes[weapon]
where weapon is the weapon entity.
Full examples of some weapons
Here are a couple more examples of Weapon Engine fills for my Alt-Fire weapons:

The events code for the Single Shotgun:
--SINGLE SHOTGUN path = "Content/SeriousSam3/Databases/Weapons/AltFire/SingleShotgunWeapon.ep" worldGlobals.WeaponScriptedFiringParams[path] = {["Fired"] = {},["FiredBase"] = {},["FiredAlt"] = {}} worldGlobals.WeaponScriptedFiringParams[path]["FiredBase"]["firingSounds"] = { {"Content/SeriousSam3/Models/Weapons/AltFire/SingleShotgun/Sounds/Fire01.wav",1,1,0.02} } worldGlobals.WeaponScriptedFiringParams[path]["FiredBase"][1] = { ["type"] = "hitscan", ["source"] = "Barrel01", ["direction"] = "crosshair", ["damage"] = {10}, ["impulse"] = 200, ["randomSpread"] = {0.5,0.5}, ["tracerProbability"] = 0.5, ["noBulletTracersOnViewer"] = true, } worldGlobals.WeaponScriptedFiringParams[path]["FiredAlt"]["firingSounds"] = { {"Content/SeriousSam3/Models/Weapons/AltFire/SingleShotgun/Sounds/Fire02.wav",1,1,0.02} } worldGlobals.WeaponScriptedFiringParams[path]["FiredAlt"][1] = { ["type"] = "hitscan", ["source"] = "Barrel01", ["direction"] = "crosshair", ["damage"] = {8}, ["impulse"] = 200, ["randomSpread"] = {0.5,0.5}, ["tracerProbability"] = 0.5, ["noBulletTracersOnViewer"] = true, } local SingleShotgunPattern = {{-5*0.4,1.9*0.1},{0*0.4,1.9*0.1},{5*0.4,1.9*0.1},{-7.5*0.4,-1.9*0.1},{-2.5*0.4,-1.9*0.1},{2.5*0.4,-1.9*0.1},{7.5*0.4,-1.9*0.1},} for j=2,7,1 do worldGlobals.WeaponEngineCopyFireableObject(path,"FiredBase",1,path,"FiredBase",j) worldGlobals.WeaponEngineCopyFireableObject(path,"FiredAlt",1,path,"FiredAlt",j) end for j=1,7,1 do worldGlobals.WeaponScriptedFiringParams[path]["FiredBase"][j]["offset"] = SingleShotgunPattern[j] worldGlobals.WeaponScriptedFiringParams[path]["FiredAlt"][j]["offset"] = {} worldGlobals.WeaponScriptedFiringParams[path]["FiredAlt"][j]["offset"][1] = SingleShotgunPattern[j][1] * 3 worldGlobals.WeaponScriptedFiringParams[path]["FiredAlt"][j]["offset"][2] = SingleShotgunPattern[j][2] * 10 end
The alternative firing modes code for Single Shotgun:
--SINGLE SHOTGUN path = "Content/SeriousSam3/Databases/Weapons/AltFire/SingleShotgunWeapon.ep" worldGlobals.WeaponEngineFiringModes[path] = {} worldGlobals.WeaponEngineFiringModes[path]["allowAltFire"] = true worldGlobals.WeaponEngineFiringModes[path][1] = { ["eventsSwitch"] = { {"Fired","FiredBase"} }, ["speedMult"] = 1, ["attachments"] = {"Mode01"}, ["extras"] = function(player,weapon) player:HideAttachmentOnWeapon("SirianBracelet") end, } worldGlobals.WeaponEngineFiringModes[path][2] = { ["eventsSwitch"] = { {"Fired","FiredAlt"} }, ["speedMult"] = 3, ["attachments"] = {"Mode02"}, ["extras"] = function(player,weapon) player:HideAttachmentOnWeapon("SirianBracelet") end, }

The events code for the Devastator:
--DEVASTATOR path = "Content/SeriousSam3/Databases/Weapons/AltFire/AutoShotgunWeapon.ep" worldGlobals.WeaponScriptedFiringParams[path] = {["Fired"] = {},["FiredBase"] = {},["FiredAlt"] = {}} worldGlobals.WeaponScriptedFiringParams[path]["FiredBase"][1] = { ["type"] = "projectile", ["projectile"] = "Content/SeriousSam3/Databases/Projectiles/AutoShotgunProjectile.ep", ["velocity"] = {300}, ["source"] = "Barrel01", ["direction"] = "look", } worldGlobals.WeaponScriptedFiringParams[path]["FiredAlt"][1] = { ["type"] = "projectile", ["projectile"] = "Content/SeriousSam3/Databases/Projectiles/AltFire/AutoShotgunGrenade_AltFire.ep", ["velocity"] = {80}, ["source"] = "Barrel01", ["direction"] = "crosshair", }

The events code for the Laser:
--LASER path = "Content/SeriousSam3/Databases/Weapons/AltFire/LaserWeapon.ep" worldGlobals.WeaponScriptedFiringParams[path] = {["Fired"] = {},["FiredBegin"] = {}, ["FiredRU"] = {},["FiredLU"] = {},["FiredRD"] = {},["FiredLD"] = {}, ["FiredBaseRU"] = {},["FiredBaseLU"] = {},["FiredBaseRD"] = {},["FiredBaseLD"] = {}, ["FiredRay"] = {},["FiredRayCharging"] = {},} worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseRU"][1] = { ["type"] = "projectile", ["projectile"] = "Content/SeriousSam3/Databases/Projectiles/Laser_Green.ep", ["velocity"] = {120}, ["source"] = "BarrelRU", ["direction"] = "crosshair", } worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseRU"]["firingSounds"] = {{"Content/SeriousSam3/Sounds/Weapons/Laser/_Fire.wav",1}} worldGlobals.WeaponEngineCopyFireableObject(path,"FiredBaseRU",1,path,"FiredBaseLU",1) worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseLU"][1]["source"] = "BarrelLU" worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseLU"]["firingSounds"] = {{"Content/SeriousSam3/Sounds/Weapons/Laser/_Fire.wav",1}} worldGlobals.WeaponEngineCopyFireableObject(path,"FiredBaseRU",1,path,"FiredBaseRD",1) worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseRD"][1]["source"] = "BarrelRD" worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseRD"]["firingSounds"] = {{"Content/SeriousSam3/Sounds/Weapons/Laser/_Fire.wav",1}} worldGlobals.WeaponEngineCopyFireableObject(path,"FiredBaseRU",1,path,"FiredBaseLD",1) worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseLD"][1]["source"] = "BarrelLD" worldGlobals.WeaponScriptedFiringParams[path]["FiredBaseLD"]["firingSounds"] = {{"Content/SeriousSam3/Sounds/Weapons/Laser/_Fire.wav",1}} worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"]["ammoSpent"] = 19 worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"]["fireWithoutAmmo"] = true worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"]["firingSounds"] = {{"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Sounds/ray_mono_louder.wav",2}} worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][1] = { ["type"] = "beam", ["range"] = 150, ["source"] = "BarrelRU", ["direction"] = "look", ["damage"] = {50}, ["piercingPower"] = {1000000}, ["bulletRadius"] = 0.4, ["beamEffects"] = {1,150, {3,"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Effects/LaserRay.pfx"}, {1,"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Effects/LaserRay_Med.pfx"}, {0.4,"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Effects/LaserRay_Small.pfx"},}, ["noDefaultBodyHitEffect"] = true, ["bodyHitEffects"] = {"Content/SeriousSam3/Presets/Effects/Matterial/GreenLaser_Hit.pfx",}, ["noDefaultSolidHitEffect"] = true, ["solidHitEffects"] = {"Content/SeriousSam3/Presets/Effects/Matterial/GreenLaser_Hit.pfx",}, ["solidHitDecals"] = {"Content/SeriousSam3/Models/Weapons/AltFire/Laser/LaserHitDecalTemplate.rsc",1}, } worldGlobals.WeaponEngineCopyFireableObject(path,"FiredRay",1,path,"FiredRay",2) worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][2]["source"] = "BarrelLD" worldGlobals.WeaponEngineCopyFireableObject(path,"FiredRay",1,path,"FiredRay",3) worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][3]["source"] = "BarrelLU" worldGlobals.WeaponEngineCopyFireableObject(path,"FiredRay",1,path,"FiredRay",4) worldGlobals.WeaponScriptedFiringParams[path]["FiredRay"][4]["source"] = "BarrelRD" worldGlobals.WeaponScriptedFiringParams[path]["FiredRayCharging"]["firingSounds"] = {{"Content/SeriousSam3/Models/Weapons/AltFire/Laser/Sounds/charge_med_mono.wav",1}}
Conclusion
Phew, writing this took waaaay longer than I expected.

The guide will be updated if I remember I forgot to add something or if functionality of the Weapon Engine will be expanded.

Good luck and have fun!

5 Comments
BuhBuhich 13 Apr, 2024 @ 1:08pm 
How to add aim ability for the SingleShotgunWeapon.ep?
Chrysalis 26 Nov, 2019 @ 5:44am 
Fixed.
I copied WeaponEngine.lua to Content\*MyTittle*\Scripts\CustomWorldScripts
Thank you <3
NSKuber  [author] 26 Nov, 2019 @ 2:35am 
Custom titles have their own "Custom world scripts" folders, so Weapon Engine and Alt Fire scripts don't work there automatically. There are several solutions here:
(1) you can ask the creator of the Automatic Script Parser mod to add your gtitle to the mod, and then all custom world scripts will work in your gtitle. If you're planning on releasing your mod with custom gtitle, I think that's the simplest and best option.
(2) you can copypaste the "\Content\SeriousSam3\Scripts\CustomWorldScripts\WeaponEngine.lua" into custom scripts folder for your gtitle. Works for testing and development, but not good solution when releasing the mod - it's better to use (1) then.
Chrysalis 25 Nov, 2019 @ 10:38am 
Maybe it's only my problem but when I tried to use my *Weapon Script* in custom Tittle, it doesn't work. In vanilla BFE - all fine. (Custom tittle is just resaved BFE title)
Pog man 13 Jun, 2019 @ 7:55am 
how to start it?