Enemies


Enemies are created just like regular sprites, but with extra enemy information and enemy attacks.

  • Enemies are created by adding a .txt script file in the /Script/Sprites folder of a mod.
  • The name of the .txt file will be how this enemy will be referred to in other script files.
  • To replace a base game enemy, you must name the .txt file using the right enemy ID.
  • Using the same name as a previous mod's enemy will overwrite it.
  • Making an enemy can be complicated. It's recommended that you use a similar existing script example to build your own enemy from it.
  • Just like sprites, enemies are composed of a capsule and a flat mesh. The mesh is used to display the sprite and is either static or faces the player's orientation. The capsule is used for collisions all around the mesh.

    Inserting enemies into levels

    Since there's no level or room editor, you can instead replace enemy types with other enemy types.
    By doing that, a base game enemy could randomly change to one of many different enemies of similar type or difficulty.
    To do this you must use the /Script/EnemyReplacing.txt file.

    EnemyReplacing.txt content example

    // Comments start with a double slash and end with a semicolon. They're ignored by the game.;
    // Every script operation must end with a semicolon. Spaces or line returns are ignored.;

    // Definition: SetEnemyWeight(EnemyID, weight);
    // The weight of an enemy decides how likely it'll be the chosen when randomly choosing an enemy.;
    // The default weight of all enemies is 100 and setting a weight of 0 means that the enemy will never appear.;
    SetEnemyWeight(71, 50);

    // Definition: ReplaceEnemy(EnemyID, newEnemyID, weight);
    // This gives a chance for the new enemy to replace another enemy depending on the weight.;
    // You can call ReplaceEnemy on the same EnemyID multiple times to give a chance for multiple enemy types to replace the base enemy.;
    // Setting a weight of 50 for this replacement means that both it and the base enemy have the same weight, meaning that the chance of either enemy appearing is 50%.;
    ReplaceEnemy(71, ProjectileShotgunZombie, 50);

    Enemy script file content example

    // Every script operation must end with a semicolon. Spaces or line returns are ignored.;
    // Base game sprite file names are a number, so only use non-number names to add new sprite. Ex: ProjectileShotgunZombie.txt;
    // Don't put spaces in file names.;
    // This file contains only a small subset of all possible sprite and enemy commands. To make your own enemy, take a similar existing one and copy its script file.;

    // This sprite script is for a reskin of the shotgun zombie that shoots slow projectiles.;

    // If true, it renders the sprite with a different animation depending on the angle the player is looking at it.;
    enemyAnimations = true;

    // Enemies have a second set of animation frames where it looks more damaged. This is the offset to those in the sprite sheet.;
    damagedMaterialOffset = 64;

    // Enemies will begin showing their damaged animations when reacing this life.;
    damagedMaterialLife = 10;

    // If true, enables sprites to have a specific animation when being destroyed.;
    dying2 = true;

    // Setting the STANDING animation.;
    // anim is a temporary animation structure that setups animations for future use.;
    // If true, m_clamp will stop the animation after playing it once. false will loop the animation forever.;
    anim.m_clamp = false;
    // m_animationSpeed is how fast the animation will play.;
    anim.m_animationSpeed = 0.5;
    // m_frameAmount is the amount of frames in the animation.;
    anim.m_frameAmount = 4;
    // m_frameOffset is the sprite sheet offset of the start of the animations.;
    anim.m_frameOffset = 0;
    // This adds the animation structure to animations which can be accessed in the future.;
    animations.Push(anim);
    // We don't need to set every single value of anim for every new animation. anim will keep its previous values until they're set again.;
    // We need to add 4 different anim to animations for each animation type. One for each face the player might be looking at.;
    anim.m_frameOffset = 4;
    animations.Push(anim);
    anim.m_frameOffset = 8;
    animations.Push(anim);
    anim.m_frameOffset = 12;
    animations.Push(anim);

    // Setting the RUNNING animation.;
    anim.m_animationSpeed = 1.0;
    anim.m_frameOffset = 16;
    animations.Push(anim);
    anim.m_frameOffset = 20;
    animations.Push(anim);
    anim.m_frameOffset = 24;
    animations.Push(anim);
    anim.m_frameOffset = 28;
    animations.Push(anim);

    // Setting the ATTACKING animation.;
    anim.m_animationSpeed = 0.5;
    anim.m_clamp = true;
    anim.m_frameOffset = 32;
    animations.Push(anim);
    anim.m_frameOffset = 36;
    animations.Push(anim);
    anim.m_frameOffset = 40;
    animations.Push(anim);
    anim.m_frameOffset = 44;
    animations.Push(anim);

    // Setting the DYING animation. This is when enemies fall like paper. It only has one face, regardless of where the player is looking at.;
    anim.m_animationSpeed = 1.0;
    anim.m_frameOffset = 48;
    animations.Push(anim);

    // Setting the PAIN animation.;
    anim.m_frameAmount = 1;
    anim.m_frameOffset = 52;
    animations.Push(anim);
    anim.m_frameOffset = 53;
    animations.Push(anim);
    anim.m_frameOffset = 54;
    animations.Push(anim);
    anim.m_frameOffset = 55;
    animations.Push(anim);

    // Setting the YELL animation.;
    anim.m_frameOffset = 56;
    animations.Push(anim);
    anim.m_frameOffset = 57;
    animations.Push(anim);
    anim.m_frameOffset = 58;
    animations.Push(anim);
    anim.m_frameOffset = 59;
    animations.Push(anim);

    // Setting the ATTACKING2 animation. This one isn't used for this enemy, but it must still be set.;
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);

    // Setting the ATTACKING3 animation. This one isn't used for this enemy, but it must still be set.;
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);

    // Setting the ATTACKING4 animation. This one isn't used for this enemy, but it must still be set.;
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);

    // Setting the ATTACKING5 animation. This one isn't used for this enemy, but it must still be set.;
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);

    // Setting the DYING2 animation. This is the normal dying animation. It supports different animations for each face, but setting the same one for all of them is fine.;
    anim.m_animationSpeed = 2.5;
    anim.m_frameAmount = 4;
    anim.m_frameOffset = 60;
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);

    // There are many more possible attacks that an enemy can have, but since we don't need them, we can skip them. We must still define them.;
    // the for command will call the commands between the {} a number of times. 15 in this case, to skip all the extra attack animations.;
    for (15) {;
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    };

    // Setting the NO_HEAD animation. This is an animation for the death of an enemy that lost its head and falls like paper.;
    anim.m_frameOffset = 64;
    anim.m_frameAmount = 1;
    anim.m_clamp = true;
    animations.Push(anim);
    anim.m_frameOffset = 65;
    animations.Push(anim);
    anim.m_frameOffset = 66;
    animations.Push(anim);
    anim.m_frameOffset = 67;
    animations.Push(anim);

    // Setting the NO_HEAD_DYING2 animation. This is an animation for the death of an enemy that lost its head and has a normal death animation.;
    // It supports different animations for each face, but setting the same one for all of them is fine.;
    anim.m_frameOffset = 68;
    anim.m_frameAmount = 4;
    anim.m_clamp = true;
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);
    animations.Push(anim);

    // Setting the SLICED_TOP animation. This is the top of a sliced in half enemy that falls like paper.;
    anim.m_frameAmount = 1;
    anim.m_clamp = true;
    anim.m_frameOffset = 264;
    animations.Push(anim);
    anim.m_frameOffset = 265;
    animations.Push(anim);
    anim.m_frameOffset = 266;
    animations.Push(anim);
    anim.m_frameOffset = 267;
    animations.Push(anim);

    // Setting the SLICED_BOTTOM animation. This is the bottom of a sliced in half enemy that falls like paper.;
    anim.m_frameAmount = 1;
    anim.m_clamp = true;
    anim.m_frameOffset = 296;
    animations.Push(anim);
    anim.m_frameOffset = 297;
    animations.Push(anim);
    anim.m_frameOffset = 298;
    animations.Push(anim);
    anim.m_frameOffset = 299;
    animations.Push(anim);

    // Setting the FEET animation. This is the feet, standing on their own, for an enemy that got cut or exploded.;
    anim.m_frameOffset = 76;
    anim.m_frameAmount = 1;
    anim.m_clamp = true;
    animations.Push(anim);
    anim.m_frameOffset = 77;
    animations.Push(anim);
    anim.m_frameOffset = 78;
    animations.Push(anim);
    anim.m_frameOffset = 79;
    animations.Push(anim);

    // Setting the NO_FEET animation. This is the enemy without feet, falling like a piece of paper.;
    anim.m_frameOffset = 80;
    anim.m_frameAmount = 1;
    anim.m_clamp = true;
    animations.Push(anim);
    anim.m_frameOffset = 81;
    animations.Push(anim);
    anim.m_frameOffset = 82;
    animations.Push(anim);
    anim.m_frameOffset = 83;
    animations.Push(anim);

    // True if the enemy can take damage.;
    canBeDamaged = true;

    // The time before the enemy sprite is actually destroyed after death. 0.025 is the minimum for enemies because it ensures the players can overkill them and gib them.;
    delayKill = 0.025;

    // True if the enemy can catch on fire.;
    flammable = true;

    // Init is the main sprite-creation command. It requires some commands to be called before it and others after it.;
    // definition: Init(Sprite scale vector(scale x, scale y, scale z), material, movement type, collision type, destructible type, activation type);
    // It's possible to randomly choose a material by adding multiple ones separated by ':'. Example: 15:BlueChickenMaterial:33:PotatoMaterial;
    // Movement types;
    // 0 : No movement (but can be moved);
    // 1 : Projectile;
    // 2 : Enemy;
    // 3 : Static (cannot be moved at all);
    // 4 : Physics (bouncing);
    // Collision types;
    // 0 : No collisions;
    // 1 : The flat mesh has collisions;
    // 2 : The capsule shape around the sprite has collisions;
    // 3 : Overlap (don't use);
    // Destruction types;
    // 0 : Can't be destroyed;
    // 1 : Disappears on destruction;
    // 2 : Explodes (not used, becomes 1);
    // 3 : Slice (not used, becomes 1);
    // 4 : Falls down like a flat mesh;
    // 5 : Context (depends on how the destruction happened. Mostly for enemies);
    // Activation types;
    // 0 : Can't be activated;
    // 1 : Can only be activated once;
    // 2 : Can be activated infinitely;
    // 3 : This is a switch;
    Init(vector(0.95, 1.9, 0.95), ProjectileShotgunZombieMaterial, 2, 2, 5, 0);

    // How much damage the enemy can take before being destroyed;
    life = 25;

    // AI is a subject on its own. Please check the AI section in the modding documentation at https://blazingbitgames.com/mods ;
    // This line will call the REGULAR_ENEMY_LOOT AI command when an enemy dies.;
    ai.AddDeathNode(9, 0);

    // This line will call the ADD_TOTAL_LEVEL_KILL AI command when an enemy spawns.;
    ai.AddNode(17, -1.0, -1.0, 0);

    // This will make sure that the enemy will be added to the total enemies on clients too.;
    ai.SetLastNodeToRunOnClients();

    // This line will call the ADD_LEVEL_KILL AI command when an enemy dies.;
    ai.AddDeathNode(16, 1, 0.0);

    // parStruct is a temporary particle structure that can be used to spawnn particles in various ways.;
    // If true, the particle will be attached to this sprite and move with it. It'll also be destroyed if the sprite is destroyed.;
    partStruct.m_managed = false;
    // If true, if the other sprite in the collision bleeds, only play the other sprite's bleed particles instead of our own. Useful to not spawn generic bullet hit spark on fleshy enemies that can bleed instead.;
    partStruct.m_removeIfTargetBleeds = false;
    // The normal points outward from the impact at the impact angle. You can offset the particle towards that direction, so that big particles don't clip with the sprite.;
    partStruct.m_normalOffset = 0.0;
    // If true, will rotate the particle to face the impact.;
    partStruct.m_rotate = true;
    // The actual type of particle to spawn.;
    partStruct.m_type = 1;
    // If true, overwrite the impact location and use the current's sprite location instead.;
    partStruct.m_useActorCoordinates = false;
    // You can specify a x, y, z offset to the particle if it should not come out of the center of the sprite.;
    partStruct.m_offset = vector(0.0,0.0,0.0);
    // This adds the particle structure to bleedParticles which will spawn these particles every time the sprite takes damage.;
    bleedParticles.Push(partStruct);

    partStruct.m_rotate = false;
    partStruct.m_type = 0;
    partStruct.m_useActorCoordinates = true;
    // This adds the particle structure to killParticles which will spawn these particles when the sprite dies.;
    killParticles.Push(partStruct);

    // This tag specifies that this sprite is an enemy and that the next commands are enemy commands.;
    [enemy]

    // Gibs are sprites that are spawned when an enemy is overkilled. You can add as many as you want.;
    addGib(ProjectileShotgunZombieGib);
    addGib(ProjectileShotgunZombieGib2);
    addGib(ProjectileShotgunZombieGib3);
    addGib(ProjectileShotgunZombieGib4);
    // Every enemy needs a specific head gib in order to support decapitation animations. The head gib can also be set as a normal gib like in this example.;
    headGib = ProjectileShotgunZombieGib;

    // attackStruct is a structure that stores attack information. An enemy will choose an attack at random from the list of possible ones.;
    // The amount of time the attack is executed when chosen.;
    attackStruct.m_attackAmount = 1;
    // A backup of m_attackAmount.;
    attackStruct.m_originalAttackAmount = 1;
    // Prioritize this attack if the target is in range of it.;
    attackStruct.m_distancePriority = false;
    // Prevent using other attacks for this amount of seconds after using this attack.;
    attackStruct.m_pauseTimerAfter = 1.0;
    // Always set to 0.0.;
    attackStruct.m_reuseTimer = 0.0;
    // Always set to 0.0.;
    attackStruct.m_reuseTimerOriginal = 0.0;
    // The minimum range the target must be at to be able to use this attack.
    // cubeSize is a magic value that represents the length of a wall/floor/ceiling cube in the game. "6.0 * cubeSize" means that the target of the attack must be less than 6 cubes away.;
    attackStruct.m_attackRange = 6.0 * cubeSize;
    // A delay before the attack is executed after being chosen.;
    attackStruct.m_attackDelay = 1.65;
    // If the attack has several steps, this delay will be used for every step that isn't the last.;
    attackStruct.m_attackDelayAfterInitial = 1.65;
    // The sound ID of the attack.;
    attackStruct.m_attackSound.m_sound = RubberChickenSound;
    attackStruct.m_attackSound.m_pitchMin = 0.95;
    attackStruct.m_attackSound.m_pitchMax = 1.05;
    // The type of the attack. 50 is GENERIC_PROJECTILE which lets you customize a simple projectile attack.;
    attackStruct.m_enemyAttack = 50;
    // The animation ID for the attack. 2 is ATTACKING.;
    attackStruct.m_attackAnim = 2;
    // If the attack would have multiple steps, the secondary steps would have this animation ID. 255 is an invalid animation ID.;
    attackStruct.m_extraAttackAnim = 255;
    // If true, the attack can be stopped if the enemy loses line of sight.;
    attackStruct.m_canBeCancelled = true;
    // If true, the enemy must have line of sight with the target before being able to choose this attack.;
    attackStruct.m_lineOfSight = true;
    // If the enemy takes too much damage or if he loses line of sight, only cancel the attack if there are this amount of steps left or less.;
    attackStruct.m_attacksLeftCancel = 1;
    // If the attack displays a beam(ex: sniper trail), this is the amount of time it should be shown.;
    attackStruct.m_attackBeamTime = 0.0;
    // Always set to 0.0.;
    attackStruct.m_attackBeamTimer = 0.0;
    // If using the GENERIC_PROJECTILE or GENERIC_BEAM attack types, this is the sprite ID to use for the projectile/beam segment.;
    attackStruct.m_genProjType = SlowBullet;
    // If using the GENERIC_PROJECTILE, the velocity of the projectile.;
    attackStruct.m_genProjVel = 3000.0;
    // If using the GENERIC_PROJECTILE, GENERIC_BEAM or GENERIC_BLADE attack types, the damage of the attack.;
    attackStruct.m_genProjDamage = 2;
    // If using the GENERIC_PROJECTILE, GENERIC_BEAM or GENERIC_BLADE attack types, the recoil of the attack.;
    attackStruct.m_genProjRecoil = 6.0;
    // If using the GENERIC_PROJECTILE, GENERIC_BEAM or GENERIC_BLADE attack types, the amount of times the attack happens or how many steps to the attack there are.;
    attackStruct.m_genProjAmount = 12;
    // This adds the attackStruct to the list of potential attacks.;
    attacks.Push(attackStruct);

    // The time the enemies yells for after spotting a target.;
    yellTime = 0.3;

    // The speed at which an enemy runs or flies.;
    runSpeed = 300.0;

    // The delay between the sound of enemy steps.;
    stepDelay = 0.48125;

    // The amount of damage an enemy must get to trigger the pain state.;
    painTreshold = 15;

    // True if the enemy can jump.;
    jumps = true;

    // True if the enemy can fly.;
    flies = false;

    // The min height in percent at which damage to the enemy counts as headshot.;
    headshotMinZPercent = 0.75;

    // The max height in percent at which damage to the enemy counts as headshot.;
    headshotMaxZPercent = 1.0;

    // True if the enemy's head is located in front of its body.;
    headshotHeadInFront = false;

    // The speed at which the enemy can turn.;
    turnSpeed = 270.0;

    // The sound ID to use for steps.;
    stepSound.m_sound = 19;

    // The sound ID to use for idle moans.;
    idleSound.m_sound = 82;
    idleSound.m_volumeMin = 1.0;
    idleSound.m_volumeMax = 1.0;
    idleSound.m_pitchMin = 0.9;
    idleSound.m_pitchMin = 1.1;
    // The sound ID to use when the enemy spots a target.;
    yellSound.m_sound = 83;
    yellSound.m_volumeMin = 1.0;
    yellSound.m_volumeMax = 1.0;
    yellSound.m_pitchMin = 0.9;
    yellSound.m_pitchMin = 1.1;
    // The sound ID to use when the enemy takes damage.;
    painSound.m_sound = 84;
    painSound.m_volumeMin = 1.0;
    painSound.m_volumeMax = 1.0;
    painSound.m_pitchMin = 0.9;
    painSound.m_pitchMin = 1.1;
    // The sound ID to use when the enemy dies.;
    dieSound.m_sound = RubberChickenSound;
    dieSound.m_volumeMin = 1.0;
    dieSound.m_volumeMax = 1.0;
    dieSound.m_pitchMin = 0.9;
    dieSound.m_pitchMin = 1.1;