Hello folks,
For a little while now, I've been toying with the idea of making some sort of generic ability manager framework.
Basically, have something that loads up all sorts of data regarding special abilities (name, cooldown, valid target types, etc.), that would allow a modder to specify either through a script-eval or a custom table that a specific ship has an ability and determine when that ship is allowed to cast that ability.
A modder would then only have to plug in his/her own script that fires up that ability.
I'm still thinking up about use cases, but here is the table spec I have so far, along with a couple of note :Code: [Select]$Name: string
$Target Type: list of string (self, fighter, corvette, tagged, buff:buffType)
$Target Team: list of string (hostile, friendly, neutral, relative to the caster)
$Range: integer (optional)
+Min: integer (minimum range) (optional)
$Target change condition: string (destroyed, every shot, target no longer valid)
$Cost: integer/list of integer (tied to diffculty level) (optional)
+Cost type: string (weapon energy reserve, "ammo" specific to that ability, "mana" reserve shared by all abilities) (optional)
$Buff: list of string (optional)
Heh, I've actually made my own already, but it's heavily intwined with my powerup system right now (so it can do stuff like regenerating health or unlimited weapon energy over a period of time). A perfectly generic system was on my to-do list but I can see it's already being handled nicely. ;) (You can check it out in the JAD2.22 demo release, itemdrop2-sct.tbm or something.)I figured you must have had to build something in that spirit for JAD. I'll be sure to check that out.
My suggestion would be instead of Buff, call it Action so you can have abilities like calling in newly spawned fighters or instant teleport or anything else.I'm still not too clear on what I want buffs to do or be handled, I vaguely envisioned them as some sort of status effect or TAG-like thing that could then be used to trigger other abilities. Kinda like the stuff you'd do in say, Warcraft 3 when building new spells : have a spell apply a buff that does nothing on its target, then have a trigger handle the actual effect.
And what is $Target change condition: supposed to do? Destroyed/Target no longer valid are basically identical conditions.That one's related to target management, which I'm still not clear on how to handle, so there's bound to be some redundancy related to that while in the design phase :). The idea was to provide more targetting conditions unrelated to target type, but without a clear use case in mind, it's probably better to shelve it.
#Abilities
; $Name: string
; $Target Type: list of string (self, fighter, corvette, tagged, buff:buffType)
; $Target Team: list of string (hostile, friendly, neutral, relative to the caster)
; $Range: integer (optional)
; +Min: integer (minimum range) (optional)
; $Cost: integer/list of integer (tied to diffculty level) (optional)
; +Cost type: string (weapon energy reserve, "ammo" specific to that ability, "mana" reserve shared by all abilities) (optional)
; $Buff: list of string (optional)
; $Duration: list of int (optional)
; $Ability Data: metadata used by whatever script actually uses the ability
$Name: SSM-moloch
$Target Type: big ship
$Target Team: Hostile
$Cost: 1
+Cost Type: global-ammo:SSM-moloch-ammo/ammo
;; how about having multiple cost types ?
$Ability Data:
+Strike Type: Shivan SSM Strike
$Name: Shivan Command aura ;; TODO: ai buff ?
$Target Type: fighter, bomber
$Target Team: Friendly
$Range: 4000
$Buff: command aura
$Ability Data:
+Max AI: Colonel
$Name: Energy Dampening Field ;; TODO : energy drain thingy
$Target Type: fighter, bomber
$Target Team: Hostile
$Range: 1500
$Cost: 70
+Cost Type: energy:weapon
$Cooldown: 90
$Duration: 15
#End
------------------------
--- Global Variables ---
------------------------
ability_classes = {}
ability_instances = {}
-- set to true to enable prints
ability_enableDebugPrints = true
----------------------
--- ??? Functions ---
----------------------
function ability_fireAllPossible()
-- TODO : cycle through ability instances & fire them
end
-------------------------
--- Utility Functions ---
-------------------------
function dPrint_ability(message)
if (ability_enableDebugPrints) then
ba.print("[abilityManager.lua] "..message)
end
end
--[[
Return the specified class as a string.
@param className : name of the class
]]
function ability_getClassAsString(className)
--TODO
end
----------------------
--- Core Functions ---
----------------------
function ability_createClass(name, attributes)
-- Initialize the class
ability_classes[name] = {
Name = name,
TargetType = attributes['Target Type']['value'],
TargetTeam = attributes['Target Team']['value'],
Cooldown = nil,
Duration = nil,
Range = -1,
Cost = 0,
CostType = nil
}
if not (attributes['Cooldown'] == nil) then
ability_classes[name].Cooldown = attributes['Cooldown']['value']
end
if not (attributes['Range'] == nil) then
ability_classes[name].Range = attributes['Range']['value']
end
if not (attributes['Cost'] == nil) then
ability_classes[name].Cost = attributes['Cost']['value']
if not (attributes['Cost']['sub'] == nil) then --TODO : utility function to grab a sub attributes' value ???
if not (attributes['Cost']['sub']['Cost Type'] == nil) then
ability_classes[name].CostType = attributes['Cost']['sub']['Cost Type']
end
end
end
if not (attributes['Duration'] == nil) then
ability_classes[name].Duration = attributes['Duration']['value']
end
-- Print class
dPrint_ability(ability_getClassAsString(name))
end
--[[
Reset the instance array. Should be called $On Gameplay Start.
]]
function ability_resetInstances()
ability_instances = {}
end
--[[
Create an instance of the specified ability and tie it to the specified shipName.
@param instanceId : unique identifier for this instance
@param className : name of the ability
@param shipName : ship to tie the ability to. Can be nil.
]]
function ability_createInstance(instanceId, className, shipName)
ability_instances[instanceId] = {
Class = className,
Ship = shipName,
LastFired = 0,
Active = true,
Manual = false, --if that instance must be fire manually
Ammo = -1 --needs to be set after creation if necessarys
}
end
--[[
Verify that this ability instance can be fired
@param instanceId : id of the ability instance to test
@return true if it can
]]
function ability_canBeFired(instanceId)
-- Check that this is a valid idea
if (ability_instances[instanceId] == nil) then
ba.warning("[abilityManager.lua] Unknown instance id : "..instanceId)
return false
end
local instance = ability_instances[instanceId]
local class = ability_classes[instance.Class]
-- Verify that this instance is active
if (instance.Active) then
-- Verify cooldown
local missionTime = mn.getMissionTime()
local cooldown = class.Cooldown
if (instance.LastFired + cooldown >= missionTime) then
--TODO : Verify cost
return true
end
end
-- Default
return false
end
------------
--- main ---
------------
abilityTable = parseTableFile("data/config/", "abilities.tbl")
ba.print(getTableObjectAsString(abilityTable))
for name, attributes in pairs(abilityTable['Abilities']) do
ability_createClass(name, attributes)
end
Wouldn't the cost be paid by the caster, not the targets? It would be rather odd if an energy dampening field drained its bearer's weapon energy for every hostile strikecraft in range...Code: [Select]$Name: Energy Dampening Field ;; TODO : energy drain thingy
$Target Type: fighter, bomber
$Target Team: Hostile
$Range: 1500
$Cost: 70
+Cost Type: energy:weapon
$Cooldown: 90
$Duration: 15
The only thing that leaps out at me right now is...
abilityTable = parseTableFile("data/config/", "abilities.tbl")
Beware, while it may work for you in testing, FreeSpace will not look for a tbl file in /config/ in a VP file. FS will only look at cfg files in config, and tbl/tbm in tables. (Atleast if I'm correct in guessing that first argument is what directory to look in?)
#Abilities
; $Name: string
; $Target Type: list of string (self, fighter, corvette, tagged, buff:buffType)
; $Target Team: list of string (hostile, friendly, neutral, relative to the caster)
; $Range: integer (optional)
; +Min: integer (minimum range) (optional)
; $Cost: integer/list of integer (tied to diffculty level) (optional)
; +Cost type: string (weapon energy reserve, what ammo consumption system is to be used) (optional)
; $Buff: list of string (optional)
; $Cooldown: 90
; $Duration: list of int (optional)
; $Ability Data: metadata used by whatever script actually uses the ability
$Name: SSM-moloch-std
$Target Type: big ship
$Target Team: Hostile
$Cooldown: 24, 20, 17, 15, 13
$Ability Data:
+Strike Type: Shivan SSM Strike
$Name: SSM-moloch-std-global ;; concept : global ammo
$Target Type: big ship
$Target Team: Hostile
$Cost: 1
+Cost Type: global:SSM-moloch-ammo
+Starting Reserve: 15;;TODO : handle + add to the spec above
$Cooldown: 24, 20, 17, 15, 13
$Ability Data:
+Strike Type: Shivan SSM Strike
$Name: Shivan Command aura ;; concept : passive ability
$Target Type: fighter, bomber
$Target Team: Friendly
$Range: 4000
$Buff: command aura
$Ability Data:
+Max AI: Colonel
$Name: Energy Dampening Field ;; concept : energy cost type
$Target Type: fighter, bomber
$Target Team: Hostile
$Range: 1500
$Cost: 70
+Cost Type: energy:weapon
$Cooldown: 90
$Duration: 15
#End
It seems to track a bunch of conditions and checks wether an ability could be used.This in a nushell covers my initial needs for this script : provide a basic set of functions that I could rely on when designing special abilities elsewhere.
What I don't see is how the player gets to know that he can fire something and what triggers any skill activation, either for the player or an ai controlled ship. The exception are automatic activation abilities like the passive ability in your list.With this script alone ? The player doesn't, although whipping up a custom HUD gauge or lua display giving the status of the ability would probably not be too difficult. My initial need was for automated SSM calls while certain ships are present (now that I think about it, I'm pretty sure I forgot the "is that ship in mission" condition in the canBeFired function :banghead:), so I didn't have any kind of player interaction in mind.
Here are the things I want to do before putting together a demo mod :
-Proof of concept : ability targetting friendlies
-Proof of concept : ability targetting the caster itself
-(optional) Write up some quick-and-dirty hud feedback regarding an ability's status
Out of interest can the targeting work for objects other than ships? (could you have an ability targeting nearby secondaries/plasma bolts etc?That's one of the big points on my TODO list, right after over-time effects & multi-targetting. Right now, it's limited to ships, with valid targets determined by ship types, but I want to expand on this down the line to include other object types and ship flags.
Ability Management FrameworkVersion 1.0 "Alpha" (https://github.com/X3N0-Life-Form/AbilityManagerDemo/releases/tag/v1.0)
Download link (https://github.com/X3N0-Life-Form/AbilityManagerDemo/releases/tag/v1.0).
Modpack includes :
- All the framework scripts
- Sample config files
- A user manual
- A sample mission that uses a number of abilities, including player-triggered ones
- A bunch of mostly irrelevant stuff due to me ripping this modpack out of another and not bothering to clean up too much
I've written a user manual that details a bit how things work, but here is the abridged version of it :
Required files
- data/scripts
* parse.lua
* abilityManager.lua
* abilityLibrary.lua (--> new ability templates go there)
* shipVariant.lua
* shipVariantMissionWide.lua
- data/tables
- data/config
* abilities.tbl
* ship_variants.tbl (--> not actually related to abilities (for now))
* ship_variants_mission_wide.tbl (aka SVMW.tbl)
SEXP calls
- Set abilities for everybody = setShipVariant('some category name from SVMW.tbl')
- Set ability for someone = ability_attachAbility(className, shipName, isManuallyFired)
- Manual trigger = ability_trigger(' AWAXEM::DISCO FURY') (-->ship name::ability name)
The full version of the user manual is located in the doc folder of the modpack.
Outside the user manual itself, each script has its own documentation, with further explainations & specs.
Keep in mind that I am classifying the as an "Alpha" version, meaning that there's still a lot of room for improvement and it hasn't been thoroughly tested yet.
I'm still honing my lua skills, and some areas are likely to get refactored down the line.
Still, I'm kind of proud of what I've done so far, and I hope I'll get more time to work on it next year.
Please feel comment, criticise, report bugs, offer suggestions, cookies, or post whatever crazy ability you come with :)
SEXP's are restricted to only the host, but if my understanding is correct, this ability manager wouldn't have that limitation? Meaning that it's even multiplayer-compatible?I have no idea how FSO handles scripting in multiplayer, so I don't know.
One of the biggest features of my upcoming campaign is that all the player-flyable Shivan fighters can do tactical subspace jumps. Can your ability manager handle teleportation? Getting reusable & combat-useful player subspace jumps with SEXP's was a bit of a challenge.The framework can handle any type of ability that requires a single target, so as long as you write all the warping subroutines it shouldn't be an issue.