[SCRIPT] "Spectral" damage, spell with fx, etc.

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
Post Reply
User avatar
Diarmuid
Posts: 807
Joined: Thu Nov 22, 2012 6:59 am
Location: Montreal, Canada
Contact:

[SCRIPT] "Spectral" damage, spell with fx, etc.

Post by Diarmuid »

Hi there!

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
Required hooks:
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,
		}
	)	
So, monsters in the spectralMonsters table must have { "cold", "fire", "poison", "shock" } immunites set, and will only be able to be hit by weapons in the spectralWeapons table. This includes melee or projectiles!

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" },
}
and the spells.lua definition:
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
   }
The same hook in spectralTracker which filters out normal/spectral weapons also filters out the spectral_bolt_effect so that it doesn't damage normal monsters, and that only the spectral_bolt projectile actually does anything. Obviously, spectral_bolt and spectral_bolt_effect have to be launched at the same speed, but there is a +0.1 on the spectral_bolt projectile speed as it has to arrive first when hitting a spectral monster (to be able to cancel the spectral_bolt_effect spell damage).

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.
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: [SCRIPT] "Spectral" damage, spell with fx, etc.

Post by akroma222 »

This looks very cool, always in for new spells and effects! Can't say I can help with the scripting though, it looks quite beyond me lol
I will check back and have a play around once I am done play testing a couple of dungeons for people :D
Post Reply