-
Notifications
You must be signed in to change notification settings - Fork 55
Enemy Attacks
Relevant files: wave.lua, arena.lua, bullet.lua, solid.lua, soul.lua
A wave is an attack that an enemy uses. Wave files are responsible for containing information about a wave and spawning the bullets and handling all behavior for the attack. Wave files are made by extending the Wave class, and should go in scripts/battle/waves
. A basic wave file looks something like this:
local WaveName, super = Class(Wave)
function WaveName:init()
super:init(self)
self.time = 5
end
function WaveName:onStart()
-- do stuff here
end
return WaveName
As per most classes, waves need certain information to be defined in their init()
. Variables and functions that should be used for initiation are:
time
: How long the wave will last, in seconds. If it's set to -1, the wave will last forever.
setArenaPosition(x, y)
: Sets the initial position of the arena, relative to the topleft of the screen.
setArenaOffset(x, y)
: Sets the initial position of the arena, relative to its default position.
setArenaSize(width, height)
: Sets the initial size of the arena. If only one argument is provided, it will set the arena to a square with the side length specified. The default size of the arena is 142x142.
setArenaShape(...)
: Sets the initial shape of the arena, taking in a series of tables with 2 values each, referring to the x and y position of each point of a polygon. For example, the default shape of the arena is {0, 0}, {142, 0}, {142, 142}, {0, 142}
.
setSoulPosition(x, y)
: Sets the soul's starting position, relative to the topleft of the screen.
setSoulOffset(x, y)
: Sets the soul's starting position, relative to its default starting position.
Waves also have the following variables and functions for usage:
timer
: An instance of a timer from the hump libraries. It is recommended to do actions based on this timer instead of Game.battle.timer
, as the wave's timer will only update while the wave is active.
bullets
: A table of all bullets added to the wave through spawnBullet()
.
objects
: A table of all objects added to the wave through either spawnBullet()
, spawnSprite()
, or spawnObject()
.
encounter
: Refers to the instance of the encounter for the battle. Gets defined after init()
, and thus cannot be used within that function.
finished
: Whether the wave is finished or not. Can be set to true to manually end a wave.
spawnBullet(bullet, ...)
: Spawns a bullet parented to Game.battle
. bullet
can be either an existing instance of a Bullet class, a string referring to a bullet ID (see Bullets below), or a path to a sprite for the new bullet to use. If bullet
refers to a bullet ID, ...
will be passed into the init()
function for the new bullet class; if bullet
refers to a sprite path, the first two arguments of ...
refer to the coordinates of the bullet, relative to the topleft of the screen. After successfully spawning a bullet, it adds the bullet to the wave's bullets
table. Returns the bullet if it succeeds in making one.
spawnBulletTo(parent, bullet, ...)
: Spawns a bullet and parents it to the specified object. Follows the same rules as spawnBullet()
.
spawnSprite(texture, x, y, layer)
: Spawns a sprite parented to Game.battle
that will be automatically removed when the wave ends. texture
is the path to the sprite image, x
and y
are coordinates relative to the topleft of the screen, and layer
is the layer of the sprite. Automatically sets the sprite's origin to 0.5, 0.5 (meaning its position refers to its center), and its scale to 2. If layer
is not specified, it defaults to LAYERS.above_arena (see Global Variables for a list of layer constants). After successfully making a sprite, it adds the sprite to the wave's objects
table. Returns the sprite.
spawnSpriteTo(parent, texture, x, y, layer)
: Spawns a sprite and parents it to the specified object. Follows the same rules as spawnSprite()
.
spawnObject(object, x, y)
: Parents the specified object to Game.battle
, and automatically removes the object when the wave ends. object
is any Object instance, and x
and y
are optional coordinates of where the object should spawn. Adds the object to the wave's objects
table. Returns the object.
spawnObjectTo(parent, object, x, y)
: Parents the object to the specified parent. Follows the same rules as spawnObject()
.
getAttackers()
: Returns a table of enemies that the attack belongs to.
Overriding functions is necessary for waves to work. Wave extends Object, meaning it has the usual overridable functions like update()
and draw()
. The following functions can be overridden:
onStart()
: Called when the attack starts, after the arena finishes transitioning. Any objects that need to be set up at the beginning (eg. bullets, sprites, timer loops, etc) should be done here, not in init(), since the wave will not update until after onStart()
is called.
onEnd()
: Called when the attack ends, before the arena transitions out.
canEnd()
: Returns whether the wave is allowed to end. Returns true by default. Can be overridden to prevent a wave from ending.
onArenaEnter()
: Called when the arena is first created for a wave, before it transitions. If this returns true, the wave will begin updating immediately, instead of waiting for the arena to finish transitioning.
onArenaExit()
: Called when the arena finishing transitioning after an attack ends.
There are many, many valid ways to code a wave. To give some pointers, a few examples of how you could code a wave include:
- Using
self.timer
, create a repeating function that occurs every x seconds to spawn an instance of a bullet extension. An example of this is the Basic:onStart() function in the example mod. - Similar to above, use
self.timer
to create instances of the bullet class, and update the bullets inupdate()
, or set theirphysics
table to have them move automatically. - Using
self.timer
, create a coroutine that handles creating and moving bullets. Kristal implements extra functionality for thewait(delay)
function: ifdelay
is not provided, it will wait for a single frame.
The arena refers to the shape that the player is confined to during a wave. You can get the current instance of the encounter's Arena by getting Game.battle.arena
. The following variables and functions are available for usage during a wave:
color
: Color of the arena border.
bg_color
: Color of the background of the arena.
x
, y
: Refers to the center of the arena. May not be accurate when transformations occur, see getCenter()
below.
left
, right
: Refers to the horizontal position of the furthest points in each direction. May not be accurate when transformations occur, see getLeft()
and getRight()
below.
top
, bottom
: Refers to the vertical position of the furthest points in each direction. May not be accurate when transformations occur, see getTop()
and getBottom()
below.
shape
: A table containing a list of tables with 2 values each, referring to the x and y position of each point of a polygon.
line_width
: A number referring to the thickness of the arena borders. If changed, you must call setShape()
to update the arena.
mask
: An Object that masks its children to the arena. Its position is at the center of the arena.
collider
: The ColliderGroup used to check collision. Usually unnecessary to check, as Game.battle:checkSolidCollision(collider)
exists.
getCenter()
: Returns x and y, properly accounting for transformations.
getLeft()
, getRight()
, getTop()
, getBottom()
: Same as left
, etc, but properly accounts for transformations.
getTopLeft()
, getTopRight()
, getBottomLeft()
, getBottomRight()
: Returns x and y coordinates similarly to combining the above functions.
getBackgroundColor()
: Returns bg_color
.
setSize(width, height)
: Changes the size of the arena if it's rectangular.
setShape(shape)
: Sets the shape of the arena to any polygon. shape
is a table, containing a list of tables with 2 values each, referring to the x and y position of each point of the polygon.
setBackgroundColor(r, g, b, a)
: Sets bg_color
to the color specified. a
is an optional argument, defaulting to 1.
If an object is parented to either Game.battle.mask
or Arena.mask
, then the object will only render inside the arena. The layer of both mask objects is equivalent to the arena's layer, and thus object layers will be relative to that.
Bullets are the most important part of any attack, and as such, they can be complicated to set up. This section will first explain creating generic bullets without making new files for them, then detail how to extend them.
By default, all bullets have a scale of 2, and an origin of 0.5, 0.5. This means its collider, sprite, and any other children will be relative to the topleft of the bullet's width
and height
, which are automatically set to the dimensions of its sprite if the bullet is initiated with a path specified (in other words, making all children relative to the topleft of the bullet's sprite). When making any bullet's collider, remember that it will be scaled 2x because of this.
Generic bullets can be spawned through Wave:spawnBullet(sprite, x, y)
. The Bullet class has the following variables and functions which can be changed during initiation or after spawning:
wave
: A reference to the current Wave class that is active. Gets defined after init()
, but only if spawned through spawnBullet()
; otherwise, it is never defined.
tp
: The amount of TP (in percentage) the player gains from grazing the bullet. Defaults to 1.6 (1/10th of a defend).
time_bonus
: The number of frames, based on 30fps, that the wave's length will be reduced by when grazing the bullet. Apparently this is a mechanic in Deltarune.
damage
: Amount of damage the bullet does. If not provided, the game will calculate damage based on the enemy's attack
.
inv_timer
: How long the player will be invulnerable after being hit by the bullet.
destroy_on_hit
: Whether the bullet will be removed when it collides with the player. True by default.
remove_offscreen
: Whether the bullet will be removed when it goes offscreen. True by default.
setSprite(texture, speed, loop, on_finished)
: Sets the sprite of the bullet to the specified path, and changes the bullet's width
and height
variables to the dimensions of the sprite. speed
, loop
, and on_finished
will be passed into the sprite's play()
function.
getDirection()
: Returns direction
if it's defined, otherwise returns rotation
.
isBullet(id)
: Returns whether the bullet either is the bullet with the specified ID, or extends it.
All files in scripts/battle/bullets
will be loaded as bullet files. By default, each bullet's ID is its file path after that point (eg. scripts/battle/bullets/sub/attack.lua
will have the ID sub/attack
). You can change this ID by passing in an extra table to the Class()
function, setting id
to the string you'd prefer, though this is not necessary. A basic bullet file doing this looks something like this:
local BulletName, super = Class(Bullet, {id="bulletID"})
function BulletName:init(x, y)
-- the original Bullet:init takes x and y as arguments,
-- with an optional texture argument
super:init(self, x, y)
self:setHitbox(-8,-8,16,16)
end
return BulletName
When extending a bullet class, some extra functions are available for overriding:
onCollide(soul)
: Called when the player collides with the bullet, regardless of invincibility frames. By default, calls onDamage()
if the player does not have active invincibility frames, and removes the bullet if destroy_on_hit
is true.
onDamage(soul)
: Called when the player collides with the bullet without invincibility frames. By default, damages the player and sets their invincibility frames.
A reminder: Since global variables aren't created for bullet classes, you'll need to use the alternate extension method mentioned in the extra notes for Objects if you want to extend an existing extension of a bullet. Another important thing to note is that wave
for bullets is set after initialization, and thus init()
will not be able to reference it. If you need to refer to wave
for setting up the bullet, you can override onAdd()
(see Objects for more detail).
Solids can also be added during waves, though the processes for spawning and extending them are different. Solids are simply Objects, and must be instantiated by calling the global Solid
class, in the form of Solid(filled, x, y, width, height)
. filled
is a boolean determining whether the solid should draw its collider with the arena's color, x
and y
are the coordinates of the topleft of the solid, and width
and height
are optional arguments determining the size of the solid if it's rectangular. If you want the solid to be a shape other than rectangular, you can set its collider
directly (see Colliders for more detail). Once you instantiate a solid, you can add it to the wave via Wave:spawnObject()
.
If you want to extend a Solid, you'll need to create a new Object for it (see Creating new Objects for more detail). Solids have a function called onSquished(soul)
that can be overridden if extended, which is called when the solid crushes the soul against another solid or the arena. By default, this function takes the value of the solid's squish_damage
variable (which defaults to 80), damages a random party member by that amount, explodes the soul, and ends the wave immediately. This behavior may be temporary, and solids moving is not yet fully functional, so expect potential bugs if you try to do so.
The soul is the object that the player controls during waves. You can retrieve the current instance of the soul through Game.battle.soul
. The Soul class has a few variables and functions available:
speed
: The speed the soul moves in pixels per frame at 30 fps. Defaults to 4 normally, and 2 when cancel is held.
transitioning
: True while the soul is moving to and from the arena between turns.
last_collided_x
, last_collided_y
: Numbers referring to whether the soul collided with a solid the last time it moved. 0 means it didn't collide, 1 means it collided to the right or down respectively, and -1 means it collided to the left or up respectively.
moving_x
, moving_y
: Numbers referring to how far the soul moved last time it moved.
noclip
: Whether the soul ignores solid collision.
slope_correction
: Whether the soul will be moved along slopes when it moves into them.
isMoving()
: Returns whether the soul is moving.
move(x, y, speed)
: Moves the soul x
pixels horizontally and y
pixels vertically, multiplied by speed
if provided. Returns whether the soul successfully moved and whether the soul collided with something.
The primary reason a coder may need to know about souls is to create new soul modes. Unlike most objects that extend classes, new soul classes don't get their own folder; instead, soul extensions should go in scripts/objects
, creating a global variable for them (see Creating new objects for more detail). When extending Soul, the following functions can be overridden:
doMovement()
: Called in update. Handles moving the player.
onCollide(bullet)
: Called when the player collides with a bullet. Handles calling the bullet's onCollide()
.
onDamage(bullet, amount)
: Called when the soul takes damage from a bullet. Does not handle actually hurting party members.
onSquished(solid)
: Called when the soul is squished by a solid. Handles calling the solid's onSquished()
.
There are two primary ways to use a custom soul mode. The first is by overriding Encounter:createSoul()
to set soul mode at the start of a wave; the Soul object returned by that function will be used for the wave. The other method is by calling Game.battle:swapSoul()
with a new soul instance, if the player soul already exists, which can be used to change soul mode in the middle of a wave. For example, if you have a custom soul mode at scripts/objects/BlueSoul.lua
, you would call Game.battle:swapSoul(BlueSoul())
to set the battle's soul mode to that.
Downloading Kristal
Installing and playing mods
Game options
File structure
mod.json and mod.lua
Libraries
Save data
Game and Kristal variables and functions
Important tips
Using objects
Creating new objects
Extra notes
Collision
DrawFX
Sprites and the Sprite object
Custom fonts
Sounds and music
External libraries
Utility functions
Global variables
Debug shortcuts
Debug menus
Making an actor
ActorSprite
Default animations
Creating a party member
Using and overriding functions
Types of Items
Default items
Inventory
Defining a spell
Making rooms
Using rooms in your mod
The Map class
Game.world functions
Events and Controllers
Existing events
Characters
Types of Characters
Battle areas and World bullets
Making a cutscene
Starting cutscenes
The Text object
In-text modifiers
Creating a shop
Overridable functions
Shop states
Shopkeepers
Making an encounter
Game.battle functions
Battle and PartyBattler states
PartyBattlers and EnemyBattlers
Creating an enemy
Overridable functions
Misc. functions and variables