Hello Anon, Login?
IT'S DANGEROUS TO GO ALONE, TAKE THIS!
Notice: This is a mirrored post from /lua.

Today's task was similar to yesterday - making things shoot at other things then make those things disappear. So all we do is recycle the same bits of code that make those things happen to enemies.lua. However if I just left the similar bits of code in as it is, the bullets that the enemies shoot are technically the same ones that the hero ship is shooting which in turn, will remove the enemies themselves. What we need to do next is make different sets of bullets for different sets of things; in this case - the enemies.

For bullet.setup:
it.flava=opt.flava or "ship"

I've added a new variable flava and set its state to whatever I set it to later when it's called otherwise, it defaults back to the ship. This will make it easier to assign bullets to different items later on; ie. end of level boss, different types of enemies, more hero ships and so forth. Also, since I will be referencing the ship functions here, I will add local ship=oven.rebake(oven.modgame..".ship") to M.bake=function.

For bullet.update:
if it.flava=="ship" then
	for i,v in ipairs(enemies.tab) do
		local dx=it.px-v.px
		local dy=it.py-v.py
		
		if dx*dx+dy*dy<=40*40 then
			bullets.remove(it)
			enemies.remove(v)
			enemies.add({px=math.random(0,512),	py=-math.random(0,512)})
			sscores.add(23)
			return
		end
	end
elseif it.flava=="enemy" then
	local dx=it.px-ship.px
	local dy=it.py-ship.py
	
	if dx*dx+dy*dy<=40*40 then
		bullets.remove(it)
		ship.die()
		return
	end
end

I shove the previous code for the bullets into an if statement to check if the ship was shooting and if it was the enemy instead, a different set of instructions will be carried out; namely to remove the bullets and retire the hero. I've also changed the distance of collision for the "enemy" where it is now the difference between the bullet and the ship instead. The other thing I had to change was omit the second enemies.add line that was previously there as not only did it overload the program with assets, the game got ridiculously hard.

At this point, the next thing to do is make sure the bullets that the enemies shoot look different than the hero ship ones. Of course, you can always keep them the same colour if you wanted.

For bullet.draw:
if it.flava=="ship" then
	gl.Color(1,0,1/2,1)
elseif it.flava=="enemy" then
	gl.Color(math.random(),math.random(),math.random(),1)
end

The code is pretty obvious - I assign the different profiles accordingly.

Now we head on to ship.lua and add a new variable ship.state="alive" to the ship.setup function which brings us to the next bit - adding the new ship.die function.

ship.die=function()
	if ship.state=="dead" then return end
	ship.state="dead"
	ship.dead=0
end

Here we tell the program to check that when the ship is "dead", it should just stay where it is and not do anything. At this point, I have also defined the state of the ship at this point to be, well, "dead" and that the value of it is 0. Or rather, this ship.diefunction to mean as such. See, it's at moments like these that the whole programming logic just seem gobbledygook to me. Anyway, the point of this is to make a "paused" state after the hero ship is destroyed so that I can put in some sort "game over" screen instead of going straight to the main menu.

For ship.update:
if ship.state=="dead" then
	ship.dead=ship.dead+1
	ship.rz=ship.dead
	if ship.dead>240 then
		main.next=oven.rebake(oven.modgame..".main_menu")
	end
	return
end

I place this at the top most part of the code as it is a priority check. Here I tell the program to wait 4 seconds before going to the main menu screen, where it counts up a frame(ship.dead+1) until it reaches 241 frames (240 being 240/60 seconds). As an added bonus, I also tell the program to rotate the ship during this waiting period.

At this point, the next thing to do is to assign the right bullets to the right item.

if ship.fire then
	if ship.cool<=0 then
		bullets.add{px=ship.px,py=ship.py-32,vy=-8+math.random(),flava="ship"}
		ship.cool=16
	end
end
for i,v in ipairs(enemies.tab) do
	local dx=ship.px-v.px
	local dy=ship.py-v.py
	
	if dx*dx+dy*dy<=32*32 then
		ship.die()
		return
	end
end

Again, pretty obvious what the code does. All I did was add in the flava variable and set its state to ship to the end of that table. I've also edited the next bit of code to call the ship.die function when the game is over instead of going to the main menu screen.

Finally, we head over to the enemies.lua file and add local bullets=oven.rebake(oven.modgame..".bullets") to M.bake=function since we will be referring to the bullets function.

Right, so I add a cooldown variable to the enemy.setup function but set it to random like so - it.cool=math.random(100,200). This is more like a timer so the enemies don't shoot straightaway.

For enemy.update:
it.cool=it.cool-1

if it.cool<=0 then
	local dx=ship.px-it.px
	local dy=ship.py-it.py
	local dd=dx*dx+dy*dy
	local  d=math.sqrt(dd)
	
	if d==0 then d=1 end
	
	bullets.add{px=it.px,py=it.py+32,vy=8*((dy/d)+math.random()-0.5),vx=8*((dx/d)+math.random()-0.5),flava="enemy"}
	it.cool=math.random(100,200)
end	

Here is where that similar bit of code we did yesterday comes back in but instead of the enemy ship firing only when a spacebar is held down, the enemies just start firing away on their own accord. Well, not technically since I've explicitly told the program to randomly assign the shooting to happen during a time period between 100 to 200 frames.

Velocity is a unit vector; a magnitude with direction whose length is 1. As we are working in 2 dimensional space, there are just two parts to the vector - 8 and (dy/d,dx/d), speed and direction respectively. Since I wanted the enemies to shoot at the direction of the ship, wherever it may be at the time, I've enlisted the help of Pythogoras to figure out the direction and distance between the ship from the enemies to direct the enemy bullets at the hero with 8 frames being the speed of the bullet.

At first I made it so the enemies literally tracked me with bullets but this really made the game too hardcore for me. Instead, I have "soften" the blow with a little randomness.

Probably the most vital bit for this code and the whole game to not break is if d==0 then d=1 end because we all know what happens if you divide by zero.

Before the end, I edited the enemies.setup function like so:
local sx=85
local sy=64
local sya=-128

enemies.tab = {}
enemies.add({px=0*sx,	py=sya+1*sy})
enemies.add({px=1*sx,	py=sya+2*sy})
enemies.add({px=2*sx,	py=sya+3*sy})
enemies.add({px=3*sx,	py=sya+4*sy})
enemies.add({px=4*sx,	py=sya+3*sy})
enemies.add({px=5*sx,	py=sya+2*sy})
enemies.add({px=6*sx,	py=sya+1*sy})

I added the variable sya and adjusted the vertical positions of all the enemies since they were far too low down on the screen at startup.

The code lives here if you want a poke.