This is a bit complex and aimed at the more advanced coders around, I'll assume you are familiar with spell system limitations, "Item" vs "ProjectileSpell" classes, onProjectileHit hooks, etc. and not explain everything. This is also not a "fully working" script as my other recent contributions (the projectiles fix or the goromorg shields) as I don't provide all models/assets, just guidelines to include this in your own dungeons if you wish.
Essentially this is a script which implements "spectral" damage, a specific class of monsters that you can only hit with a specific class of weapons or spells. I've also created a spectral spell and will show how you can have custom fx onHit with it (even on walls).
Many thanks to JKos's magic missile tutorial and everyone from the new spells thread, and also pfreguso's super weapon script.
Here's the main code, a spectralTracker entity:
SpoilerShow
Code: Select all
-- Diarmuid's SpectralTracker script, Version 1.0
-- JKos's Log Framework and Xanathars' grimQ modules required
--
-- Changelog
-- 1.0 Initial release
lastWeaponUsed = nil
spellCaster = 0
spectralBoltPower = 40
spectralBoltSpeed = 10
spectralWeaponsHitNormalMonsters = true
spectralMonsters = {
"light_elemental",
}
spectralWeapons = {
"spectral_blade",
"spectral_bolt",
"spectral_arrow",
}
function spectralDamage(monster, damage, type)
if grimq.fromArray(spectralMonsters)
:any(function(v) return v==monster.name; end) == true then
if lastWeaponUsed == nil then
return false
elseif grimq.fromArray(spectralWeapons)
:any(function(v) return v==lastWeaponUsed; end) == true then
lastWeaponUsed = nil -- to prevent damageTile infinite loop
damageTile(monster.level,monster.x,monster.y,monster.facing,1, "physical",damage)
elseif lastWeaponUsed == "spectralTrackerFakeDamage" then
lastWeaponUsed = nil -- to prevent damageTile infinite loop
return true
else
lastWeaponUsed = "spectralTrackerFakeDamage" -- to allow fake damage to go through once
damageTile(monster.level,monster.x,monster.y,monster.facing,0, "cold",1) -- to generate fake 0 damage
return false
end
else
if lastWeaponUsed == "spectral_bolt" then
return false -- This prevents fake spectral_bolt_effect from dealing damage to normal monsters
elseif spectralWeaponsHitNormalMonsters == false and
grimq.fromArray(spectralWeapons)
:any(function(v) return v==lastWeaponUsed; end) == true then
return false -- This prevents spectral weapons to damage normal monsters if flag is set to false
end
end
return true
end
function lastWeapon(champion, weapon)
if weapon ~= nil then
lastWeaponUsed = weapon.name
else
lastWeaponUsed = "unarmed"
end
end
function castSpectralBolt(caster,x,y,direction,skill)
local dx,dy = getForward(party.facing)
shootProjectile('spectral_bolt', party.level, x+dx, y+dy, direction, spectralBoltSpeed+0.1, 0, 0, 0, 0, 0, 10, party, true)
spawn("spectral_bolt_effect",party.level, party.x+dx, party.y+dy, direction)
spellCaster = caster:getOrdinal()
lastWeaponUsed = "spectral_bolt"
end
function spectralBoltOnProjectileHitHook(monster,projectile)
lastWeaponUsed = projectile.name
if projectile.name == 'spectral_bolt' then
if grimq.fromArray(spectralMonsters)
:any(function(v) return v==monster.name; end) == true then
local originator = 2 ^ (spellCaster+1)
damageTile(monster.level,monster.x,monster.y,(monster.facing + 2)%4,originator+1, 'physical', 40)
end
return false
end
end
SpoilerShow
Code: Select all
fw.addHooks('monsters','spectral',{
onDamage = function(self,damage,type)
return spectralTracker.spectralDamage(self,damage,type)
end,
onProjectileHit = function(monster,projectile)
return spectralTracker.spectralBoltOnProjectileHitHook(monster,projectile)
end,
}
)
fw.addHooks('party','spectral',{
onAttack = function(champion, weapon)
return spectralTracker.lastWeapon(champion, weapon)
end,
}
)
The spectralWeaponsHitNormalMonsters flag tells if normal monsters will be hit by spectral Weapons. In DM for example, this would be true, as vorpal blades could strike normal creatures, not just ghosts. But if you want some kind of "Ghost Sword" that passes through everything but ghosts, set it to false.
Now the spell:
I've read a lot about it and something that was bugging me is the inability to attach custom particles/sounds to the "Item" class like you could for "ProjectileSpell". I could recreate onHit FX within the hook if the spell hit a monster, but not if hit a wall. The solution that struck me was to create two entities launched at the same speed:
- an invisible Item (based on JKos magic missile) which would generate scripted damage/experience
- a ProjectileSpell which would fly for the looks but do nothing
Here's the code in objects.lua:
SpoilerShow
Code: Select all
defineObject{
name = "spell_projectile",
class = "Item",
uiName = "Spell projectile",
model = "mod_assets/models/spell_projectileA.fbx",
gfxIndex = 109,
attackPower = 1,
stackable = false,
sharpProjectile = false,
sharpProjectile = false,
projectileRotationY = 0,
weight = 0,
}
cloneObject{
name = "spectral_bolt",
baseObject = "spell_projectile",
uiName = "Spectral Bolt",
}
defineObject{
name = "spectral_bolt_effect",
class = "ProjectileSpell",
particleSystem = "spectral_bolt",
hitParticleEffect = "spectral_bolt_hit",
lightColor = vec(1, 0.9, 0.9),
lightBrightness = 15,
lightRange = 7,
lightHitBrightness = 40,
lightHitRange = 10,
castShadow = true,
launchSound = "lightning_bolt_launch",
projectileSound = "lightning_bolt",
hitSound = "lightning_bolt_hit",
projectileSpeed = 10,
attackPower = 0,
damageType = "physical",
--cameraShake = true,
tags = { "spell" },
}
SpoilerShow
Code: Select all
defineSpell{
name = "spectral_bolt",
uiName = "Spectral Bolt",
skill = "spellcraft",
level = 5,
runes = "BH",
manaCost = 25,
description = "The mage creates a bolt of spectral energy that unerringly strikes one target.",
onCast = function(caster,x,y,direction,skill)
spectralTracker.castSpectralBolt(caster,x,y,direction,skill)
end
}
I didn't attach my particles, items or monsters definitions, as there's nothing special there (it's custom white lightning-type particles). Also, for the model, model = "mod_assets/models/spell_projectileA.fbx" is just retextured with a null texture.
I'll probably still tweak this, but thought it could be useful in its current state.
All feedback welcome as usual...
EDIT: I forgot to mention that, while experimenting, I found that the bombType = "cold", "fire", "poison" or "shock" item property automatically creates the explosion effect for custom spells using shootProjectile rather than spawn. So if your spell requires a predefined particle system, you can use this instead of spawning a second fake spell. In my case it didn't work because I wanted white lightning.