-
Notifications
You must be signed in to change notification settings - Fork 612
Battlefield Framework Documentation
A battlefield is any content where players are warped from an entrance point into a special area and receive a Battlefield effect. The goal with the Battlefield Framework is to simplify the creation of battlefield content. This new solution attempts to achieve this by reducing repeated code and unifying battlefield details into a single file. This document covers how to use the new battlefield framework as well as how to implement new and migrate existing battlefield content.
See the following for a Full Battlefield Example.
The Battlefield Framework is based on the Interaction Framework and consequently having an understanding of that system is beneficial but not strictly necessary.
A battlefield is defined by using Battlefield:new()
or one of the subclasses such as BattlefieldMission:new()
. The majority of Battlefield details are provided at this time. For a full list of options see Battlefield Parameters
local content = Battlefield:new({
zoneId = xi.zone.BEARCLAW_PINNACLE,
battlefieldId = xi.battlefield.id.BROTHERS,
maxPlayers = 18,
levelCap = 75,
timeLimit = utils.minutes(30),
index = 3,
entryNpc = "Wind_Pillar_4",
exitNpc = "Wind_Pillar_Exit",
requiredKeyItems = { xi.ki.ZEPHYR_FAN, message = ID.text.ZEPHYR_RIPS },
grantXP = 3500,
})
Once a Battlefield has been initially defined there are additional aspects that need to be provided.
Monsters in a battlefield are defined within groups. Each group is a set of monsters that share some commonality between them. A group can have some settings, modifiers and helpful callback handlers attached. For a full list of options see Battlefield Group Parameters.
content.groups =
{
{
mobs = { "Ghost_Clot" },
stationary = true,
mods =
{
[xi.mod.IMPACT_SDT] = 0,
[xi.mod.HTH_SDT] = 0,
[xi.mod.SLASH_SDT] = 1250,
},
death = function(battlefield, mob, count)
xi.limbus.openDoor(mob:getBattlefield(), ID.SE_APOLLYON.npc.PORTAL[1])
end,
}
}
Many Battlefields follow the same pattern with all monsters being spawned, stationary, superlink, and required to kill in order to spawn the Armoury Crate. This can easily be achieved with something such as the following:
content:addEssentialMobs({ "Eldertaur", "Mindertaur" })
Upon victory Battlefields spawn an Armoury Crate which will grant loot once opened. Loot is defined within groups where each group will only drop a single item unless a different quantity
is specified. For each item there is a droperate
set to the likelihood of it dropping. These numbers are used in weighted randomness and are consequently relative to each other.
content.loot =
{
{
{ itemid = xi.items.NONE, droprate = xi.battlefield.dropChance.VERY_LOW },
{ itemid = xi.items.SQUARE_OF_ELTORO_LEATHER, droprate = xi.battlefield.dropChance.NORMAL },
{ itemid = xi.items.PIECE_OF_CASSIA_LUMBER, droprate = xi.battlefield.dropChance.NORMAL },
{ itemid = xi.items.DRAGON_BONE, droprate = xi.battlefield.dropChance.NORMAL },
},
{
{ itemid = xi.items.NONE, droprate = xi.battlefield.dropChance.EXTREMELY_HIGH },
{ itemid = xi.items.CLOUD_EVOKER, droprate = xi.battlefield.dropChance.LOW },
},
{
quantity = 2,
{ itemid = xi.items.NONE, droprate = xi.battlefield.dropChance.HIGH },
{ itemid = xi.items.SCOUTERS_ROPE, droprate = xi.battlefield.dropChance.LOW },
{ itemid = xi.items.HEDGEHOG_BOMB, droprate = xi.battlefield.dropChance.LOW },
{ itemid = xi.items.MARTIAL_ANELACE, droprate = xi.battlefield.dropChance.LOW },
{ itemid = xi.items.MARTIAL_LANCE, droprate = xi.battlefield.dropChance.LOW },
{ itemid = xi.items.SCROLL_OF_RAISE_III, droprate = xi.battlefield.dropChance.HIGH },
},
}
Some battlefields such as Limbus will have monsters patrolling a specific path rather than being stationary. In order to simplify this paths
can be set which is a table of mob ID to a table of a path to patrol.
content.paths =
{
[ID.SE_APOLLYON.mob.TIEHOLTSODI] =
{
{ x = 151.0, y = 0.0, z = -528.0, wait = 10000 },
{ x = 147.0, y = 0.0, z = -468.05, wait = 10000 },
},
[ID.SE_APOLLYON.mob.ADAMANTSHELL[1]] =
{
{ x = 138.0, y = -2.0, z = -496.0, wait = 2500 },
{ x = 142.0, y = -1.11, z = -500.0, wait = 2500 },
},
-- ...
}
Nearly all of Battlefield logic can be overridden by providing a function override for the created content. For a full list of functions that can be overridden check out globals/battlefields.lua
.
function content:checkRequirements(player, npc, registrant, trade)
-- Always allow entry into the battlefield
return true
end
- zoneId: Which zone this battlefield exists within (required)
- battlefieldId: Battlefield ID used in the database (required)
- maxPlayers: Maximum number of players allowed to enter (required)
- timeLimit: Time in seconds allotted to complete the battlefield before being ejected. (required)
- index: The index of the battlefield within the zone. This is used to communicate with the client on which menu item this battlefield is. (required)
- levelCap: Level cap imposed upon the battlefield. Defaults to 0 - no level cap. (optional)
- area: Some battlefields have multiple areas (Burning Circles) while others have fixed areas (Apollyon). Set to have a fixed area. (optional)
- entryNpc: The name of the NPC used for entering (required)
- exitNpc: The name of the NPC used for exiting
- allowSubjob: Determines if character subjobs are enabled or disabled upon entry. Defaults to true. (optional)
- hasWipeGrace: Grants players a 3 minute grace period on a full wipe before ejecting them. Defaults to true. (optional)
- canLoseExp: Determines if a character loses experience points upon death while inside the battlefield. Defaults to true. (optional)
- delayToExit: Amount of time to wait before exiting the battlefield. Defaults to 5 seconds. (optional)
-
requiredItems: Items required to be traded to enter the battlefield. Needs to be in the format of
{ itemid, quantity, useMessage = ID.text.*, wearMessage = ID.text.*, wornMessage = ID.text.* }
. (optional) - requiredKeyItems: Key items required to be able to enter the battlefield - these are removed upon entry (optional)
- title: Title given to players upon victory (optional)
- grantXP: Amount of XP to grant upon victory (optional)
- lossEventParams: Parameters given to the loss event (32002). Defaults to none. (optional)
- missionArea: The mission area this battlefield is associated with (optional)
- mission: The mission this battlefield is associated with (optional)
- missionStatusArea: The mission area to retrieve the mission status from. Will default to using the player's nation (optional)
- missionStatus: The optional extra status information xi.mission.status (optional)
- requiredMissionStatus: The required mission status to enter
- skipMissionStatus: The required mission status to skip the cutscene. Defaults to the required mission status.
- mobs: A list of monster names to associate with the group.
- mobIds: A list of mob IDs to associate with the group. In multiple arena battlefields such as BCNMs this needs to be a list of lists of mob IDs for each arena which is typically 3.
- spawned: Determines if monsters in this group will be spawned. Defaults to true.
- superlink: Determines if monsters in this group will superlink with each other. Defaults to false.
- stationary: Determines if monsters in this group will not roam. Defaults to true.
-
mods: A table mapping
xi.mod.*
to a value which will be set for every monster in the group. -
mobMods: A table mapping
xi.mobMod.*
to a value which will be set for every monster in the group. -
setup: A callback
function(battlefield, mobs)
is called after initializing the group. -
death: A callback
function(battlefield, mob)
is called whenever a monster in the group dies. -
allDeath: A callback
function(battlefield, mob)
is called whenever all monsters in the group die. Themob
parameter is the last monster to have died. -
randomDeath: A callback
function(battlefield, mob)
called whenever a chosen random monster, determined during initialization, in the group dies.
Creating a new battlefield is much more streamlined than it was previously. Nearly all data pertaining to the battlefield is contained within the single battlefield script. New battlefields belong in scripts/battlefields/<zone>/<name>.lua
. The only other place battlefield data exists is within bcnm_info.sql
where the following values are needed: bcnmId
, zoneId
, name
, fastestName
, fastestPartySize
, fastestTime
. All other values are made redundant with this new solution.
Here is a checklist of things to do when creating a battlefield.
- Create a new script file within
scripts/battlefields/<zone>/<name>.lua
- A new
xi.battlefield.id
entry needs to be made for the battlefield. - Determine the proper Battlefield
index
which is used for communicating with the client. - Properly setup the battlefield with the appropriate parameters.
- Provide the monster groups. Often this can be solved with something such as
content:addEssentialMobs({ "Apis" })
. - Implement the monster scripts if they need any unique behavior/logic that cannot be handled within monster groups. (not covered)
- Implement any missing mobskills (not covered)
- Provide a loot table using
xi.items.*
andxi.battlefield.dropChance.*
- Be sure to include a
return content:register()
at the bottom of the script - Ensure the following
ID.text.*
values are available for the zoneIDs.lua
script
PARTY_MEMBERS_ARE_ENGAGED
NO_BATTLEFIELD_ENTRY
TIME_IN_THE_BATTLEFIELD_IS_UP
PARTY_MEMBERS_HAVE_FALLEN
ENTERING_THE_BATTLEFIELD_FOR
MEMBERS_OF_YOUR_ALLIANCE
MEMBERS_OF_YOUR_PARTY
TIME_LIMIT_FOR_THIS_BATTLE_IS
THE_PARTY_WILL_BE_REMOVED
MEMBERS_LEVELS_ARE_RESTRICTED
GIL_OBTAINED
In order to simplify mission battlefields there is a BattlefieldMission
subclass that has several additional parameters that it can take.
There are a significant number of existing battlefields that need to be migrated over to this new solution. This is a fairly straightforward process as it is primarily taking existing data and unifying it in a single script. Please read Battle Creation before continuing. The following covers where to find all of the different battlefield details.
The first thing to do is identify the battlefield ID that is associated with the battlefield being migrating.
Within bcnm_info.sql
the values timeLimit
, levelCap
, and partySize
can be found that need to be provided during battlefield creation.
Inside scripts/globals/bcnms.lua
the battlefield index can be found inside local battlefields
table as the first value for the battlefield table. In the same table the required entry item (if any) can be found.
Down below inside checkReqs()
the requirements for entry are specified. This can vary significantly from having to provide a requiredKeyItems
to missions using a BattlefieldMission
to having to override Battlefield:checkRequirements()
.
Continuing below, checkSkip()
contains details on the requirements to be able to skip the entry cutscene. This is generally for missions and handled automatically with BattlefieldMission
.
Within bcnm_battlefield.sql
the mob IDs for the battlefield can be found along with if they are spawned at the start and if they are required to spawn the Armoury Crate. This is used to set up content.groups
.
Sometimes there is logic within the mob scripts that should be brought over into the monster groups. Group callbacks can be used for many things and the setup
callback can be used to set up event handlers for other logic.
Within scripts/zones/<zone>/npc/Armoury_Crate.lua
the existing loot table can be found. Generally they use raw itemids and raw droprates that need to be converted to xi.items.*
and xi.battlefield.dropChance.*
respectively.
One important thing to note is that all Battlefields within a zone should be migrated at once otherwise there entrance triggers and other events will be handled twice - once by the Battlefield Framework and once by the entrance npc script.
Once all of the battlefields within a zone have been migrated then the old scripts in the zone can be cleaned up.
Once all of the battlefields have been migrated then the previous battlefield solution can be torn down. Here is a list of tasks to be done that is by no means exhaustive.
- Remove m_RequiredEnemyList from Battlefield
- Remove SpawnLoot
- Remove bcnm_battlefield.sql
- Remove bcnm_treasure_chests.sql
- Remove globals/bcnm.lua
- Remove xi.battlefield.* lua functions
- Remove bcnm_info usage from CBattlefieldHandler::LoadBattlefield
- Remove CLuaBattlefield::loadMobs
- Remove Battlefield::getMobs
- Simplify Battlefield::rules (only using loseexp and allowsubjob)
- Remove BATTLEFIELDMOBCONDITION and just spawn when inserting if set to spawn
- Cleanup CLuaBaseEntity::registerBattlefield
- Remove ZoneType::BATTLEFIELD_OLD
- Remove CBattlefield::isInteraction()
- Remove xi.battlefield.HandleLootRolls
- Remove xi.battlefield.HandleWipe
Writing lua modules to override behavior of battlefields is easily done with this new framework. Every battlefield can be accessed easily with the following method.
local content = xi.battlefield.contents[xi.battlefield.id.BROTHERS]
With the content
object you can change any existing variables such as the levelCap
or grantXP
. Additionally it is possible to override behavior by following Overriding Battlefield Logic.
content.levelCap = 75
function content:checkRequirements(player, npc, registrant, trade)
-- Always allow entry into the battlefield
return true
end