[Solved] Casting custom spells like built in spells
[Solved] Casting custom spells like built in spells
So I am creating some new spells. I have a new fireball for example. It is not a built-in spell so I define its onCast() as a function having parms (champion, x, y, dir, elev, skillLevel)
Since we do not have a working shootProjectile() function to call, I spawn the new fireball object using the x and y specified.
Realization number 1: You cannot use the same X and Y specified or the fireball explodes in your tile!
That is a bit annoying but I can use getForward() with the direction given and have it spawn in the tile in front of the party.
Realization 2: Now the fireball casts and looks good, except it clearly has spawned in the next tile and does not look at all like it was cast by a champion. You look at the built-in spells and they spawn directly in front of the champion who cast it. This takes into account what position in the party he is in as well.
Now I am getting annoyed. I can do setWorldPosition() calls on the newly spawned fireball to make it appear closer to the party but how do I put it in front of the champion who cast it?
Realization 3: The Champion component does not return the position he is in in the party!?!?!? I can get the ordinal but that is different if the champions moved around in the party. The object given to the hook is the champion and we cannot get his position to use an offset to place the newly cast spell in front of him. OK, do I need to reference the party now and match the name of the champion to get his position???
Am I missing the boat on how to cast a custom spell or is it very difficult? I am going to have to write many lines of custom LUA code to do something that to me should be a single command.
Thoughts? Comments?
Since we do not have a working shootProjectile() function to call, I spawn the new fireball object using the x and y specified.
Realization number 1: You cannot use the same X and Y specified or the fireball explodes in your tile!
That is a bit annoying but I can use getForward() with the direction given and have it spawn in the tile in front of the party.
Realization 2: Now the fireball casts and looks good, except it clearly has spawned in the next tile and does not look at all like it was cast by a champion. You look at the built-in spells and they spawn directly in front of the champion who cast it. This takes into account what position in the party he is in as well.
Now I am getting annoyed. I can do setWorldPosition() calls on the newly spawned fireball to make it appear closer to the party but how do I put it in front of the champion who cast it?
Realization 3: The Champion component does not return the position he is in in the party!?!?!? I can get the ordinal but that is different if the champions moved around in the party. The object given to the hook is the champion and we cannot get his position to use an offset to place the newly cast spell in front of him. OK, do I need to reference the party now and match the name of the champion to get his position???
Am I missing the boat on how to cast a custom spell or is it very difficult? I am going to have to write many lines of custom LUA code to do something that to me should be a single command.
Thoughts? Comments?
Last edited by MrChoke on Wed Feb 04, 2015 1:31 am, edited 1 time in total.
- AndakRainor
- Posts: 674
- Joined: Thu Nov 20, 2014 5:18 pm
Re: How do I cast a custom spell like built in spells are ca
In the scripting reference http://www.grimrock.net/modding/scripti ... eComponent you should use the function ProjectileComponent:setIgnoreEntity(ent)
You spawn your entity, for example a fire ball (any size) then use a syntax similar to myFireballEntity.projectile:setIgnoreEntity(party)
I created spells with projectiles you can see the code in this thread : viewtopic.php?f=22&t=8265
In this old version of my code I made a mistake when I thought those projectiles should not ignore the party as in game it is possible to hit the party with its own spells, but in reality all those built-in spells do in fact ignore the party. It's only the damageTile components they create when they hit another obstacle, like a wall or monster that can damage the party.
So this way you can just launch your projectile spells from the exact party's position and orientation and obtain the same visual as the original spells.
Also those spells do not use the caster's position in the party. If you really want to do it I think you will have to compare the results of the function that gets the champion by its position to the local caster champion parameter.
And yes, the current asset pack doesn't include enough information on various subjects such as spells definition so for now we have to guess a lot !!! For example, be very careful with customs spells not granting XP and always check the castByChampion variable in your damageTile components !
You spawn your entity, for example a fire ball (any size) then use a syntax similar to myFireballEntity.projectile:setIgnoreEntity(party)
I created spells with projectiles you can see the code in this thread : viewtopic.php?f=22&t=8265
In this old version of my code I made a mistake when I thought those projectiles should not ignore the party as in game it is possible to hit the party with its own spells, but in reality all those built-in spells do in fact ignore the party. It's only the damageTile components they create when they hit another obstacle, like a wall or monster that can damage the party.
So this way you can just launch your projectile spells from the exact party's position and orientation and obtain the same visual as the original spells.
Also those spells do not use the caster's position in the party. If you really want to do it I think you will have to compare the results of the function that gets the champion by its position to the local caster champion parameter.
And yes, the current asset pack doesn't include enough information on various subjects such as spells definition so for now we have to guess a lot !!! For example, be very careful with customs spells not granting XP and always check the castByChampion variable in your damageTile components !
Re: How do I cast a custom spell like built in spells are ca
Yup, setIgnoreEntity(party) allows me to fire the spell from the same tile. It looks much better. But it launches center to the party, not cast from a champion. Your suggestion on how to address this is what I was thinking as well. It is a pain to have to do all that work though. Also, I think it is a miss in my opinion that the Champion component has no way to return what position in the party he is in. Oh well. Thanks.AndakRainor wrote:In the scripting reference http://www.grimrock.net/modding/scripti ... eComponent you should use the function ProjectileComponent:setIgnoreEntity(ent)
You spawn your entity, for example a fire ball (any size) then use a syntax similar to myFireballEntity.projectile:setIgnoreEntity(party)
I created spells with projectiles you can see the code in this thread : viewtopic.php?f=22&t=8265
In this old version of my code I made a mistake when I thought those projectiles should not ignore the party as in game it is possible to hit the party with its own spells, but in reality all those built-in spells do in fact ignore the party. It's only the damageTile components they create when they hit another obstacle, like a wall or monster that can damage the party.
So this way you can just launch your projectile spells from the exact party's position and orientation and obtain the same visual as the original spells.
Also those spells do not use the caster's position in the party. If you really want to do it I think you will have to compare the results of the function that gets the champion by its position to the local caster champion parameter.
And yes, the current asset pack doesn't include enough information on various subjects such as spells definition so for now we have to guess a lot !!! For example, be very careful with customs spells not granting XP and always check the castByChampion variable in your damageTile components !
Re: How do I cast a custom spell like built in spells are ca
A few more issues with dealing with casting custom spells. The Projectile component has setCastByChampion() but its a boolean, not a number for his ordinal. And then look at TileDamanger. Its got getCastByChampion() and setCastByChampion() and they use number for the ordinal. Its not consistent. And what's worse yet, is when you spawn a spell object, you get the projectile component and can set true for setCastByChampion(), but you don't get the TileDamager. That doesn't even exist until the spell hits something. And NO, setting true on setCastByChampion() on the Projectile does nothing to what you can get out of the TileDamager. I can add hooks for onHitObstacle() or onHitMonster() and there is no way to tell who/what created the spell. There seems to be some serious holes in this. Yet the built-in spells work great.
- AndakRainor
- Posts: 674
- Joined: Thu Nov 20, 2014 5:18 pm
Re: How do I cast a custom spell like built in spells are ca
Exact, it's really not consistent !
Here are the methods I used to deal with this problem;
- The lazy one : you add "castByChampion = 1," in all the TileDamager components you use with your custom spells. To do it you will have to define all your custom projectiles and linked tile damagers and never use the original game objects. You can just copy/paste those original definitions, change their names (I added "_custom" to theirs names) and add the line for the caster's ordinal. This is an ugly solution, the worst part being the fact that the champion's ordinal will be incorrect most of the time. BUT in the native grimrock 2 XP system, the XP gains are equally shared among champions (before xp modifiers similar to the necklace bonus) so if you don't want to change this system, the solution will work with any valid ordinal number (1 should be always valid).
- The less ugly one : when you spawn a magic projectile, store it's caster ordinal with a syntax similar to "myFireball.projectile.champion = X" (X being the ordinal). Define all your custom projectiles and never use the original game objects. With this method you won't have to define custom tileDamagers. However, you will need to redirect all your custom projectiles onProjectileHit function to a generic function that spawns its hitEffect (this contains the name of the tileDamager to create). From this point in the code you will be able to fully customize your tileDamager, setting its caster's ordinal, and why not change it's attackPower. Unless I missed something important, you can add dynamically any new variable to a component, you just then need the code to use it.
Here are the methods I used to deal with this problem;
- The lazy one : you add "castByChampion = 1," in all the TileDamager components you use with your custom spells. To do it you will have to define all your custom projectiles and linked tile damagers and never use the original game objects. You can just copy/paste those original definitions, change their names (I added "_custom" to theirs names) and add the line for the caster's ordinal. This is an ugly solution, the worst part being the fact that the champion's ordinal will be incorrect most of the time. BUT in the native grimrock 2 XP system, the XP gains are equally shared among champions (before xp modifiers similar to the necklace bonus) so if you don't want to change this system, the solution will work with any valid ordinal number (1 should be always valid).
- The less ugly one : when you spawn a magic projectile, store it's caster ordinal with a syntax similar to "myFireball.projectile.champion = X" (X being the ordinal). Define all your custom projectiles and never use the original game objects. With this method you won't have to define custom tileDamagers. However, you will need to redirect all your custom projectiles onProjectileHit function to a generic function that spawns its hitEffect (this contains the name of the tileDamager to create). From this point in the code you will be able to fully customize your tileDamager, setting its caster's ordinal, and why not change it's attackPower. Unless I missed something important, you can add dynamically any new variable to a component, you just then need the code to use it.
Re: How do I cast a custom spell like built in spells are ca
I'm pretty sure this doesn't serialize properly. I strongly recommend not doing it. What's wrong with using ProjectileComponent:setCastByChampion() and checking ProjectileComponent:getCastByChampion() when it's time to create the TileDamager?AndakRainor wrote:Unless I missed something important, you can add dynamically any new variable to a component, you just then need the code to use it.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
- AndakRainor
- Posts: 674
- Joined: Thu Nov 20, 2014 5:18 pm
Re: How do I cast a custom spell like built in spells are ca
Well in this case, these functions store and return a boolean, when we need an ordinal number.minmay wrote:I'm pretty sure this doesn't serialize properly. I strongly recommend not doing it. What's wrong with using ProjectileComponent:setCastByChampion() and checking ProjectileComponent:getCastByChampion() when it's time to create the TileDamager?AndakRainor wrote:Unless I missed something important, you can add dynamically any new variable to a component, you just then need the code to use it.
Re: How do I cast a custom spell like built in spells are ca
Why would you care about the specific champion ordinal used for the TileDamager? Surely any champion-dependent changes you want to make should apply at the time the projectile is created, not when it hits. Unless you want to remove equal experience gain for some completely insane reason, the ordinal doesn't matter once the projectile is created.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Re: How do I cast a custom spell like built in spells are ca
@Andakrainor, I agree with minmay with setting custom variables to game objects. You are asking for big trouble when you save, in the form of game crashes or warnings. I like your solutions but they still do not address that the spell looks like its cast from the tile and not the champion who cast it. Below I will detail a solution I figured out that does that and is enough for me to call this solved.
But first, @minmay, regarding why worry about the champion who cast the spell in TIleDamager. The game does for "poison_bolt". See this code from poison_bolt.lua:
The above code will not work like that unless it is cast as a built-in spell. I experimented quite a bit on it.
So, here is my solution to this. In order to take advantage of proper setting of the Champion who cast the spell, both on the Projectile and on the TileDamager, and also to see proper casting from that Champion, I had to re-use a built-in spell. But I re-defined it. I created a "Green Fireball". It is the same as the default fireball but I changed all of the color vectors to a shade of green. NOTE, that the built-in "fireball" spell creates game object, "fireball_large".
First my spell (ignore all that test_skill stuff, that was another test)
Then re-define of what the game calls for "FireBall":
The key to this is overriding the default particleSystem, light and projectile behavior by using the onInit() hook. Also, you will see my solution to storing custom data on game objects. I am using a very cool "data" component that someone on the forum came up with. I'd give credit here if I could remember who. I use it everywhere and it works great. Its a "ScriptComponent" definition with built in getter and setters for any data you want to put on an object. Based on the value I get from this data, it determines how to customize the spell. Note that if I don't get anything, it implies the normal game fireball was cast and the defaults are used.
Here is the code from my party's onCastSpell() method where I set the "data" for "castSpell":
And lastly, here is the definition of the custom fireball_blast, set by "hitEffect" in the projectile onInit():
In this code you will see that the print statement with show the ordinal of who cast the spell. Because it started as a built-in spell, it works.
I didn't include the particle defines here. Just copy the ones for fireball and modify as you need.
But first, @minmay, regarding why worry about the champion who cast the spell in TIleDamager. The game does for "poison_bolt". See this code from poison_bolt.lua:
Code: Select all
onHitMonster = function(self, monster)
monster:setCondition("poisoned", 25)
-- mark condition so that exp is awarded if monster is killed by the condition
local poisonedCondition = monster.go.poisoned
local ord = self:getCastByChampion()
if poisonedCondition and ord then
poisonedCondition:setCausedByChampion(ord)
end
end,
So, here is my solution to this. In order to take advantage of proper setting of the Champion who cast the spell, both on the Projectile and on the TileDamager, and also to see proper casting from that Champion, I had to re-use a built-in spell. But I re-defined it. I created a "Green Fireball". It is the same as the default fireball but I changed all of the color vectors to a shade of green. NOTE, that the built-in "fireball" spell creates game object, "fireball_large".
First my spell (ignore all that test_skill stuff, that was another test)
Code: Select all
defineSpell{
name = "green_fireball",
uiName = "Green Fireball",
gesture = 125,
manaCost = 43,
onCast="fireball",
skill = "test_skill",
requirements = { "test_skill", 1},
icon = 61,
spellIcon = 7,
description = "A flaming ball of fire shoots from your hands.",
}
Code: Select all
defineObject{
name = "fireball_large",
baseObject = "base_spell",
components = {
{
class = "Particle",
particleSystem = "fireball_large",
onInit =
function(self)
--print("in fireball override particle init")
local spell = party.data:get("castSpell")
if spell == "green_fireball" then
self:setParticleSystem("green_fireball")
end
end
},
{
class = "Light",
color = vec(1, 0.5, 0.25),
brightness = 15,
range = 7,
castShadow = true,
onInit =
function(self)
--print("in fireball override light init")
local spell = party.data:get("castSpell")
if spell == "green_fireball" then
self:setColor(vec(0.4, 1, 0.25))
end
end
},
{
class = "Projectile",
spawnOffsetY = 1.35,
velocity = 10,
radius = 0.1,
hitEffect = "fireball_blast_large",
onInit =
function(self)
--print("in fireball override projectile init")
local spell = party.data:get("castSpell")
if spell == "green_fireball" then
self:setHitEffect("green_fireball_blast")
end
end,
onProjectileHit =
function(self, what, entity)
local spell = party.data:get("castSpell")
if what == "entity" then
print("fireball_override onProjectileHit", spell, what, entity.name, self:getHitEffect(), self:getCastByChampion())
else
print("fireball_override onProjectileHit", spell, what, entity)
end
end
},
{
class = "Sound",
sound = "fireball",
},
{
class = "Sound",
name = "launchSound",
sound = "fireball_launch",
},
},
}
Here is the code from my party's onCastSpell() method where I set the "data" for "castSpell":
Code: Select all
onCastSpell =
function(self, champion, spell)
print("in party onCastSpell", champion:getOrdinal(), spell)
self.go.data:set("castSpell", spell)
end
Code: Select all
defineObject{
name = "green_fireball_blast",
baseObject = "base_spell",
components = {
{
class = "Particle",
particleSystem = "green_fireball_blast",
destroyObject = true,
},
{
class = "Light",
--color = vec(1, 0.5, 0.25),
color = vec(0.1, 1.0, 0.3),
brightness = 40,
range = 10,
fadeOut = 0.5,
disableSelf = true,
},
{
class = "TileDamager",
attackPower = 70,
damageType = "fire",
sound = "fireball_hit",
screenEffect = "green_fireball_screen",
woundChance = 40,
onHitObstacle = function(self, obstacle)
local ord = self:getCastByChampion()
print("in green_fireball onHitObstacle", obstacle.go.id, ord)
end,
onHitMonster = function(self, monster)
local ord = self:getCastByChampion()
print("in green_fireball onHitMonster", monster.go.id, ord, self:getDestroyObject())
end,
},
},
}
I didn't include the particle defines here. Just copy the ones for fireball and modify as you need.
Last edited by MrChoke on Wed Feb 04, 2015 1:34 am, edited 1 time in total.
Re: How do I cast a custom spell like built in spells are ca
But you don't need this. You can just changeMrChoke wrote:The above code will not work like that unless it is cast as a built-in spell. I experimented quite a bit on it.Code: Select all
onHitMonster = function(self, monster) monster:setCondition("poisoned", 25) -- mark condition so that exp is awarded if monster is killed by the condition local poisonedCondition = monster.go.poisoned local ord = self:getCastByChampion() if poisonedCondition and ord then poisonedCondition:setCausedByChampion(ord) end end,
Code: Select all
poisonedCondition:setCausedByChampion(ord)
Code: Select all
poisonedCondition:setCausedByChampion(1)
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.