How to improve my flamethrower ?

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
Post Reply
User avatar
Khollik
Posts: 171
Joined: Tue Aug 29, 2017 6:44 pm
Location: France

How to improve my flamethrower ?

Post by Khollik »

Hello everyone,

Right now I'm trying to create new items for a Mod I'm working on and any help would be appreciated to improve my... flamethrower !

My aim is to create a heavy weapon with a secondary action with a custom spell ("Ignite") which will burn the two next tiles in front of the party. Of course, if there is a wall or an obstacle, the party's tile will be inflamed instead (like with a fireburst or a fireball). I don't know if that kind of spell has already been created ?

I managed to customize my item to launch the spell. I also managed to combine and edit the fireburst and the poison cloud assets to create the visual effects and damage I was looking for (instant damage + damage in time), but I'm still struggling with the spell itself. Here is the code of the asset, with some explanation about what works and what doesn't work.

I put the code in a spoiler tag due to its length
SpoilerShow

Code: Select all

defineSpell{ 
	name = "ignite",
	uiName = "Ignite",
	gesture = 123,
	manaCost = 60,
onCast = function(champion, x, y, direction, elevation, skillLevel)
local ix,iy = getForward(party.facing)  	-- get coordinates of the tile in front of the party
ix = ix + party.x		
iy = iy + party.y
local iix,iiy = getForward(party.facing) 	-- get coordinates of the tile one space away in front of the party
iix = (iix*2) + party.x   	
iiy = (iiy*2) + party.y

-- check the first tile
local bloque1 = true
local bloque2 = true
if not party.map:isWall(ix,iy) then --if the party doesn't face a wall, the tile in front of the party can be targeted
bloque1 = false
end
				
if party.map:isObstacle(ix,iy,party.elevation) then 	-- possibly there is no wall but an obstacle					
   for each in party.map:entitiesAt(ix,iy) do		-- check if this obstacle is a monster
	if each:getComponent("monster") ~= nil then			
		bloque1 = false
		break                      -- if there is at least one monster, that means that the tile is passable. No need to check other entities
	else bloque1 = true
	end
   end
end

if bloque1 == true then					-- if there is a wall or a non-monster obstacle, target the tile of the party
spawn("ignite",party.level, party.x, party.y, direction, elevation,"ignite1")	
else
spawn("ignite",party.level, ix, iy, direction, elevation,"ignite1") -- if there is no wall and no obstacle (or a monster-class obstacle), target the tile in front of the party					
end
local iz = ignite1:getWorldPositionY()
iz = iz-0.5
ignite1:setWorldPositionY(iz)	

-- check the second tile if the first has been targeted. Basically the same code but with coordinates of the tile one space away
if bloque1 == false then
if not party.map:isWall(iix,iiy) then
bloque2 = false
end
				
if party.map:isObstacle(iix,iiy,party.elevation) then 						
   for each in party.map:entitiesAt(iix,iiy) do		
	if each:getComponent("monster") ~= nil then			
		bloque2 = false
		break
	else bloque2 = true
	end
   end
end			

if bloque2 == false then
spawn("ignite",party.level, iix, iiy, direction, elevation,"ignite2")
local iiz = ignite2:getWorldPositionY()
iiz = iiz-0.5
ignite2:setWorldPositionY(iiz)
end
end

end,

	skill = "fire_magic",
	requirements = { "fire_magic", 3, "air_magic", 3 },
	icon = 99,
	spellIcon = 8,
	description = "Burn your way out!",
	
}
So... The flamethrower actually works in almost all situations.
1/ If there is a wall in front of the party, it burns the party...
2/ if there is a wall one tile away in front of the party, it only inflames the first tile
3/ and obviously it doesn't inflame the second tile if the first is a wall or has an obstacle
Now, it can happen there is no wall but another type of obstacle
4/ in that case, the script check if the obstacle is a monster (then it burns it :twisted: ) or something else : a rockpile, a tree etc.

What does not works well is the door-type obstacles (doors,secret doors etc.). In that case, I can't make the script understand that it's an obstacle, which is quite realistic with a portcullis (after all, you could cast a spell through the bars) but not at all with other kinds of doors.

I did try with getComponent() as for the monster class, but I can't get the same result. Are doors not considered as obstacle ? Maybe there is something I didn't understand yet in the way they work? What do I miss here ?

I did notice checkLineOfFire() and checkLineOfSight() functions but I couldn't understand them either. Could they be useful in my case ? :?: :?: :?:

Thanks for your help.

Cheers,
Khollik
PS : apologies for possible misspelling, non English native speaking.
User avatar
Leki
Posts: 550
Joined: Wed Sep 12, 2012 3:49 pm

Re: How to improve my flamethrower ?

Post by Leki »

Can you share more info abyout that weapon? There is more possible ways how to solve this issue, but it depends on that weapon and whole modification. If you are working on "Aliens", then you can use firearm component in some way, or you can use projectile insteed of spell etc.
I'm the Gate I'm the Key.
Dawn of Lore
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: How to improve my flamethrower ?

Post by akroma222 »

Leki wrote:Can you share more info abyout that weapon? If you are working on "Aliens", then you can use firearm component in some way.....
Game over, man! Game over! :lol: :D

And yes - more info (as Leki said, lots of ways to do this)
Is it a burst spell?
Projectile?
Fire wall/Ice shards type effect?
Can it pass through sparse doors like grating and portcullis?
Akroma

EDIT - IF you are going for a burst / shards spell ....
an easy but effective way to handle this is to use some of the functions Zimber created (found in his asset pack)
You can use these to spawn all kinds of burst spell / shards spell effects
If this is too much or complicated for what you are looking for, there are simpler fixes ....

These are the functions
SpoilerShow

Code: Select all

---------------------------------------------------------------------------------
--	Function that creates a wave in the form of a cone from its starting point
--	Uses 'coneWaveStraight' and 'coneWaveDiagonal'
--	
--	zim_functions.script.waveStarter(object,x,y,f,e,l,length,delay,power)
--	object			- object that the wave is made out of (tile damagers or cloud spells)
--	x,y,f,e,l		- x,y position, facing, elevation, and level
--	length			- length of the wave
--	delay			- delay between wave advancements
--	allowPlatforms	- [bool] should the wave go over platforms
--	power			- [optional] attack power of the created objects (nil for no changes)
-------------------------------------------------------------------------------------------
function coneWaveStarter(object,x,y,f,e,l,length,delay,allowPlatforms,power)
	local rightFace = (f+1)%4
	local leftFace = (f+3)%4
	
	coneWaveStraight(object,x,y,f,e,l,length,delay,allowPlatforms,power)
	if length - 1 > 0 then
		delayedCall("zim_functions",delay,"coneWaveDiagonal",object,x,y,f,rightFace,e,l,length-1,delay,allowPlatforms,power)
		delayedCall("zim_functions",delay,"coneWaveDiagonal",object,x,y,f,leftFace,e,l,length-1,delay,allowPlatforms,power)
	end
end
-------------------------------------------------------------------------------------------
--	Basically a replica of the ice shards component
--	Can be used with object that don't have an ice shards component (i.e fireburst) without crashing and the need of creating a new object
--	Will stay consistent with its delay between spawns as well
-------------------------------------------------------------------------------------------
function coneWaveStraight(object,x,y,f,e,l,length,delay,allowPlatforms,power)
	local map = Dungeon.getMap(l)
	local dx,dy = getForward(f)
	
	local obj = spawn(object,l,x,y,f,e)
	if obj.iceshards then obj.iceshards:setRange(0) end
	if power then
		if obj.cloudspell then obj.cloudspell:setAttackPower(power)
		elseif obj.tiledamager then obj.tiledamager:setAttackPower(power) end
	end
	
	if map:checkLineOfFire(x, y, x+dx, y+dy, e) then
		local continue = false
		
		if map:getElevation(x+dx,y+dy) == e then continue = true end
		if not continue and allowPlatforms then
			for v in map:entitiesAt(x+dx,y+dy) do
				if v.elevation == e and v.platform and v.platform:isEnabled() then
					continue = true
					break
				end
			end
		end
		
		if continue then
			if length-1 > 0 then
				delayedCall("zim_functions",delay,"coneWaveStraight",object,x+dx,y+dy,f,e,l,length-1,delay,allowPlatforms,power)
			end
		end
	end
end
-------------------------------------------------------------------------------------------
--	Diagonal checker if there is nothing blocking the wave from expanding
-------------------------------------------------------------------------------------------
function coneWaveDiagonal(object,x,y,f1,f2,e,l,length,delay,allowPlatforms,power)
	local map = Dungeon.getMap(l)
	local dx1,dy1 = getForward(f1)
	local dx2,dy2 = getForward(f2)
	
	if map:checkLineOfFire(x, y, x+dx1, y+dy1, e) then
		local continue1 = false
		
		if map:getElevation(x+dx1,y+dy1) == e then continue1 = true end
		if not continue1 and allowPlatforms then
			for v1 in map:entitiesAt(x+dx1,y+dy1) do
				if v1.elevation == e and v1.platform and v1.platform:isEnabled() then
					continue1 = true
					break
				end
			end
		end
		
		if continue1 then
			if map:checkLineOfFire(x+dx1, y+dy1, x+dx1+dx2, y+dy1+dy2, e) then
				local continue2 = false
				
				if map:getElevation(x+dx1+dx2,y+dy1+dy2) == e then continue2 = true end
				if not continue2 and allowPlatforms then
					for v2 in map:entitiesAt(x+dx1+dx2,y+dy1+dy2) do
						if v2.elevation == e and v2.platform and v2.platform:isEnabled() then
							continue2 = true
							break
						end
					end
				end
				
				if continue2 then
					coneWaveStraight(object,x+dx1+dx2,y+dy1+dy2,f1,e,l,length,delay,allowPlatforms,power)
					if length-1 > 0 then
						delayedCall("zim_functions",delay,"coneWaveDiagonal",object,x+dx1+dx2,y+dy1+dy2,f1,f2,e,l,length-1,delay,allowPlatforms,power)
					end
				end
			end
		end
	end
end

Ive since added to these functions and have replicated the Blast Component effect:
SpoilerShow

Code: Select all

-------------------------------------------------------
--BLAST component mimic
--------------------------------------------------------

function blastWaveStarter(object,x,y,f,e,l,length,delay,allowPlatforms,power)
	---------------------------------------
	local obj = spawn(object,l,x,y,f,e)
	if obj.iceshards then obj.iceshards:setRange(0) end
	if power then
		if obj.cloudspell then obj.cloudspell:setAttackPower(power)
		elseif obj.tiledamager then obj.tiledamager:setAttackPower(power) end
	end
	--------------------------------------
	local rightFace = (f+1)%4
	local leftFace = (f+3)%4
	local oppF = (f + 2)%4
	local oppRightFace = (oppF+1)%4
	local oppLeftFace = (oppF+3)%4
	print(f, rightFace, leftFace, oppF, oppRightFace, oppLeftFace)
	---------------------------------------
	if length - 1 > 0 then
		for dir = 0,3 do
			local dx,dy = getForward(dir)
			delayedCall("aoeSpell",delay/2,"coneWaveStraight",object,x + dx,y + dy,dir,e,l,length,delay,allowPlatforms,power)
		end
		delayedCall("aoeSpell",delay,"coneWaveDiagonal",object,x,y,f,rightFace,e,l,length-1,delay*1.5,allowPlatforms,power)
		delayedCall("aoeSpell",delay,"coneWaveDiagonal",object,x,y,oppF,leftFace,e,l,length-1,delay*1.5,allowPlatforms,power)
		delayedCall("aoeSpell",delay,"coneWaveDiagonal",object,x,y,f,leftFace,e,l,length-1,delay*1.5,allowPlatforms,power)
		delayedCall("aoeSpell",delay,"coneWaveDiagonal",object,x,y,oppF,rightFace,e,l,length-1,delay*1.5,allowPlatforms,power)
	end
end

User avatar
Khollik
Posts: 171
Joined: Tue Aug 29, 2017 6:44 pm
Location: France

Re: How to improve my flamethrower ?

Post by Khollik »

Thanks you both for your answer.

No, I'm not working on "Aliens" ( :?:), I'm testing some asset customization to see if I can go through a Mod idea I have now that I have finished my first one (and I'm a little bit more experienced with the LoG editor).

My flamethrower is a heavy weapon which doesn't have a primary action, only a secondary action, to force player to charge the flamethrower (I found it more realistic than just clicking on it).
I modified the wizard's virge asset because it was the closest graphic I found to what I had in mind. Here is the asset code :
SpoilerShow

Code: Select all

defineObject{
	name = "flamethrower",
	baseObject = "base_item",
	components = {
		{
			class = "Model",
			model = "assets/models/items/wizards_virge.fbx",
		},
		{
			class = "Item",
			uiName = "Flame Thrower",
			description = "Useful to burn mummies or cook sausages",
			gfxIndex = 309,
			gfxIndexPowerAttack = 444,
			impactSound = "impact_blunt",
			weight = 5,
			traits = { "heavy_weapon", "two_handed" },
			secondaryAction = "ignite",			
		},
				{
			class = "EquipmentItem",
			slot = "Weapon",
		},
		{
			class = "CastSpell",
			name = "ignite",
			uiName = "Ignite",
			cooldown = 6,
			spell = "ignite",
			energyCost = 15,
			--power = 3,
			charges = 6,			
		},
		{
			class = "Particle",
			particleSystem = "flamethrower_particle",
			offset = vec(0.9, 0, 0.2),
		},
	},
}
As you can see, it works with a "CastSpell" class, which triggers the "ignite spell".
Can it pass through sparse doors like grating and portcullis?
Actually, I don't mind if the flamethrower passes through portcullis and gratings (which it already does), I'm concerned by the fact that my isObstacle() check doesn't "understand" if there is a door or not.

Thanks to Akroma's code, I understand that the CheckLineOfSight() works with the two first parameters being the starting tile, the next two ones the arriving tile and the last one the elevation? Am I correct here?
Could i use that instead of my chained isWall()/isObstacle()/getComponent() tests? Or will I have the same issue with the doors?

Again, thanks for any help you could provide!
User avatar
Isaac
Posts: 3172
Joined: Fri Mar 02, 2012 10:02 pm

Re: How to improve my flamethrower ?

Post by Isaac »

Khollik wrote:No, I'm not working on "Aliens" ( :?:)...


Actually, I don't mind if the flamethrower passes through portcullis and gratings (which it already does), I'm concerned by the fact that my isObstacle() check doesn't "understand" if there is a door or not.
Manually check for a door... Done using both the tile location, and the facing attribute of any doors on the tile; (0 means a North door, 2 means a South door...).

*You also have to check the next adjacent tile, as a door facing 0 in the Party's location, is visually in the same spot as a door in the next tile to the North, facing 2.
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: How to improve my flamethrower ?

Post by akroma222 »

Khollik wrote: Thanks to Akroma's code, I understand that the CheckLineOfSight() works with the two first parameters being the starting tile, the next two ones the arriving tile and the last one the elevation? Am I correct here?
Could i use that instead of my chained isWall()/isObstacle()/getComponent() tests? Or will I have the same issue with the doors?
Ohhh important! The set of functions I posted above were written by ZimberZimber
...and can be found in his Asset pack >> viewtopic.php?f=22&t=14538

CheckLineOfSight( ) takes 5 'number' arguments.....(it checks LOS between pointA and pointB for elevation E)

CheckLineOfSight( xA, yA, xB, yB, E)
(So Im guessing make pointA the party's x,y coords .... and pointB the furtherest square the flamethrower can reach in the party's facing direction)

You can use one of Zimbers functions to do this.... by calling:

Code: Select all

yourFlamethrowerScript.script.coneWaveStraight(object, x, y, f, e, l, length, delay, allowPlatforms, power)

Code: Select all

  
--Legend for coneWaveStraight Function Arguments
---------------------------------------------------------------------------------------
--   object         - object that the wave is made out of (tile damagers or cloud spells)
--   x,y,f,e,l      - x,y position, facing, elevation, and level
--   length         - length of the wave
--   delay         - delay between wave advancements
--   allowPlatforms   - [bool] should the wave go over platforms
--   power         - [optional] attack power of the created objects (nil for no changes)
-----------------------------------------------------------------------------------------
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: How to improve my flamethrower ?

Post by akroma222 »

Isaac wrote: *You also have to check the next adjacent tile, as a door facing 0 in the Party's location, is visually in the same spot as a door in the next tile to the North, facing 2.
Isaac - a good reminder!
Here is the first part of an unnecessarily long winded function I wrote that checks where to spawn a spell effect on cast
It deals with the door - sparse issue (and checks both ways for the door)
SpoilerShow

Code: Select all

function getAoeTarget(x, y, direction, elevation, burstSpell)
		---------------------------------------------------------VARS
		local x	= party.x		
		local y	= party.y				
		local direction = party.facing
		local elevation = party.elevation
		local dx,dy = getForward(party.facing)
  		local ldx,ldy = getForward((party.facing+3)%4) 
  		local rdx,rdy = getForward((party.facing+1)%4)
		local x1 = x + dx
		local y1 = y + dy
		local x2 = x + 2*dx			
		local y2 = y + 2*dy
		------------------------------------------------------------------
		local obs = party.map:checkObstacle(party, party.facing)
		local cloudSpellTab = {"poison_cloud", "poison_cloud_small", "poison_cloud_medium", "poison_cloud_pocket", "poison_cloud_limeburst", 
								"draining_cloud", "draining_cloud2", "draining_cloud_improved", "draining_cloud_greater","drain_cloud_eater"}
		---------------------------------------------------------SPARSE GATES
		if obs == "door" or obs == "wall" then																
			for o in party.map:entitiesAt(party.x + dx, party.y + dy) do	
				if o and o.door and o.facing == ((party.facing + 2)%4) then									
					for _,comp in o:componentIterator() do
						if comp:getClass() == "DoorComponent" and o.door:getSparse() then					
							if burstSpell and table.contains(cloudSpellTab, burstSpell) then				
								x1 = party.x + dx															
								y1 = party.y + dy																						
								return party.level, party.x + dx, party.y + dy, party.facing, party.elevation
							end
						end
					end
				end
			end
			for o in party.map:entitiesAt(party.x, party.y) do												
				if o and o.door and party.facing == o.facing then
					for _,comp in o:componentIterator() do
						if comp:getClass() == "DoorComponent" and o.door:getSparse() then					
							if burstSpell and table.contains(cloudSpellTab, burstSpell) then				
								x1 = party.x + dx															
								y1 = party.y + dy																					
								return party.level, party.x + dx, party.y + dy, party.facing, party.elevation
							end
						end
					end
				end
			end
			x = party.x			
			y = party.y																						
			return party.level, party.x, party.y, party.facing, party.elevation
		end
-----------------------------------------------------------ELEVATION = Same
		if party.map:getElevation(x1, y1) == party.elevation then
			--print("same elevation")
			-------------------------	
			if obs == "obstacle" then
				for e in party.map:entitiesAt(x1, y1) do
					if e and e.elevation == party.elevation then
						for _,c in e:componentIterator() do
							if (c:getClass() == "ObstacleComponent" and c:getBlockParty()) 
							or c:getClass() == "ForceFieldComponent" or c:getClass() == "DynamicObstacleComponent" then
								--print(obs, c:getClass())
								x = party.x
								y = party.y
								return party.level, x, y, direction, elevation
								
							elseif c:getClass() == "Health" then
								local immTab = e.health:getImmunities()
								if not e.health:getInvulnerable() then 
									--print("breakable_obstacle!!")
									x = party.x + dx
									y = party.y + dy	
									return party.level, x, y, direction, elevation
								end
							end
							--print("obstacle without obstacle comp???")
						end
					end		
				end
			end
			print("clear")
			x = party.x + dx
			y = party.y + dy	
			return party.level, x, y, direction, elevation
			----------------------------------------------ELEVATION = lower than party
		elseif party.map:getElevation(x1, y1) < party.elevation then
			for e in party.map:entitiesAt(x1, y1) do
				if not checkPartyUnderwater() then
					if party.map:getAutomapTile(party.x + dx, party.y + dy) == 2 then
						--print("water_ahead")
						if e and e.platform and e.platform:isEnabled() and e.elevation == party.elevation then 
							--print("platform")
							x = party.x + dx
							y = party.y + dy
							return party.level, x, y, direction, elevation
						else								
							if burstSpell == "ice_shards" then
								--print("ice_shards")
								x = party.x
								y = party.y
								return party.level, x, y, direction, elevation
							else
								--print("not ice shards")
								x = party.x + dx
								y = party.y + dy
								return party.level, x, y, direction, elevation
							end
						end
					end
				else
					--print("canYouCastThisUnderwater()")
					x = party.x + dx
					y = party.y + dy
					return party.level, x, y, direction, elevation
				end
			end
		end
		--print("END")
		x = party.x + dx
		y = party.y + dy
		return party.level, x, y, direction, elevation
	end
	
end
(EDIT - all of it added now - ive been meaning to double check this function w everyone, now is a good time)
Khollik - you are interested in the first part checking doors on partys square and the square in front of the party
hope this helps
User avatar
Khollik
Posts: 171
Joined: Tue Aug 29, 2017 6:44 pm
Location: France

Re: How to improve my flamethrower ?

Post by Khollik »

Aaaah this "Aliens" :D Sorry, I thought you were talking about some collective work in progress.

Actually, I do try to make a new Mod with some spooky atmosphere (sparse music, few light etc.), a kind of tribute to survival horror and resident evil-alike. I'm still in the item creation phase of my reflexions, for instance this torchlight (which works fine now :mrgreen: ) :

https://www.nexusmods.com/legendofgrimrock2/Images/9/?

Regarding the flamethrower, I didn't immediately check your answers. Meanwhile I did manage to improve it a little thanks to CheckLineOfFire(), and now it works in almost every situation except when there is a monster just behind a door :twisted:
SpoilerShow

Code: Select all

defineSpell{ 
	name = "ignite",
	uiName = "Ignite",
	gesture = 123,
	manaCost = 60,
onCast = function(champion, x, y, direction, elevation, skillLevel)
local ix,iy = getForward(party.facing)  	-- get coordinates of the tile in front of the party
ix = (ix) + party.x   	
iy = (iy) + party.y
local iix,iiy = getForward(party.facing) 	-- get coordinates of the tile one space away in front of the party
iix = (iix*2) + party.x   	
iiy = (iiy*2) + party.y
local iiix,iiiy = getForward(party.facing) 	-- get coordinates of the tile two spaces away in front of the party
iiix = (iiix*3) + party.x   	
iiiy = (iiiy*3) + party.y

-- check the first tile
local bloque1 = true
local bloque2 = true
if party.map:isWall(ix,iy) then --if the party doesn't face a wall, the tile in front of the party can be targeted
bloque1 = false
end

local i = party.map:checkLineOfFire(party.x,party.y,ix,iy,party.elevation)
if i == true then bloque1 = false
else bloque1 = true
for each in party.map:entitiesAt(ix,iy) do		-- check if this obstacle is a monster
  if each:getComponent("monster") ~= nil then			
		bloque1 = false
		break
  --else bloque1 = true
  end
end
end

if bloque1 == true then					-- if there is a wall or a non-monster obstacle, target the tile of the party
spawn("ignite",party.level, party.x, party.y, direction, elevation,"ignite1")	
else
spawn("ignite",party.level, ix, iy, direction, elevation,"ignite1") -- if there is no and no obstacle (or a monster-class obstacle), target the tile in front of the party					
end
local iz = ignite1:getWorldPositionY()
iz = iz-0.5
ignite1:setWorldPositionY(iz)	

-- check the second tile if the first has been targeted
if bloque1 == false then
  if not party.map:isWall(iix,iiy) then bloque2 = false end
  local i = party.map:checkLineOfFire(ix,iy,iix,iiy,party.elevation)
  if i == true then bloque2 = false
  else bloque2 = true 
  for each in party.map:entitiesAt(iix,iiy) do
    if each:getComponent("monster") ~= nil then			
	bloque2 = false
	break
    end
  end	
  end
end

if bloque2 == false then
spawn("ignite",party.level, iix, iiy, direction, elevation,"ignite2")
local iiz = ignite2:getWorldPositionY()
iiz = iiz-0.5
ignite2:setWorldPositionY(iiz)
end

end,

	skill = "fire_magic",
	requirements = { "fire_magic", 3, "air_magic", 3 },
	icon = 99,
	spellIcon = 8,
	description = "Burn your way out!",
	
}
But I shall try your solutions now... (and Zimber asset!)

Thanks again!
Khollik
Post Reply