//
//   boss.c
//
//   Copyright 2007, 2008 Lancer-X/ASCEAI
//
//   This file is part of Meritous.
//
//   Meritous is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   Meritous is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with Meritous.  If not, see <http://www.gnu.org/licenses/>.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <SDL.h>
#include <SDL_image.h>

#include "levelblit.h"
#include "mapgen.h"
#include "demon.h"
#include "gamemap.h"
#include "audio.h"
#include "i18n.h"

char *boss_names[] = {	"MERIDIAN",
						"ATARAXIA",
						"MERODACH",
						"WERVYN ANIXIL" };

int boss_fight_mode = 0;
// 0 - no boss fight
// 1 - entering boss room
// 2 - fiting teh boss
// 3 - boss dying
// 4-23 - final boss dying sequence

int current_boss_room = 0;
int current_boss = 0;
int boss_x, boss_y;
int boss_hp;
int boss_lives;
int boss_tail_len = 0;
int boss_breakpoint;
int boss_flash = 0;
int boss_new_life = 0;
int boss_ox, boss_oy;
int boss_dlg = 0;
int resetboss = 0;
int final_boss_dlg = 0;
float boss_dmgrate = 1;
float boss_dir;

int proxy_seek = 0;

int boss_m_heads;
int boss_m_hx[4];
int boss_m_hy[4];
float boss_m_hd[4];

float boss_2h_dir = 0;
int boss_2h_dst = 64;

int percent_required[4] = {15, 40, 60, 75};

char *artifact_names[] = { 	N_("Holy Sword 'Balmung'"),
				N_("Mystic Halberd 'Amenonuhoko'"),
				N_("Divine Bow 'Gandiva'"),
				N_("Cursed Seal of Yahveh")};
							
int CanGetArtifact();
void DrawArtifactOverhead(int p_obj);

struct dlg_box {
	int pos;
	int lines;
	int last;
	char *txt;
};

struct dlg_box dtext[5][12] =
//      "This string of text is precisely seventy eight characters in length, usefully."
{
  { // Meridian
	{0, 5, 0,
		N_("MERIDIAN:\n"
		"  \n"
		"  Different...\n"
		"  \n"
		"  Your PSI is different. Your magic is perverse. Poisonous. Treacherous.")
	},
	{1, 5, 0,
		N_("MERIT:\n"
		"  \n"
		"  Nothing but darkness carried on the wind of a curse. It's amazing that you\n"
		"  are able to communicate with me at all. Surely a statement on the perversity\n"
		"  of my magic, coming from you, is more than a little bit hypocritical.")
	},
	{0, 6, 1,
		N_("MERIDIAN:\n"
		"  \n"
		"  So. You've even brought that tainted thing with you.\n"
		"  That does not really surprise me, but nonetheless, your weak, impure PSI\n"
		"  will do you little good against one manifest of shadows. Pitiful thing from\n"
		"  the surface, MERIDIAN shall return you to the dust from whence you came!")
	}
  },
  
  { // Ataraxia
	{0, 5, 0,
		N_("ATARAXIA:\n"
		"  \n"
		"  It felt nearly as inconsequential as a stilling in the breeze, but there was\n"
		"  more to it, wasn't it? It feels as though I lost a part of myself. Was that\n"
		"  your doing?")
	},
	{1, 6, 0,
		N_("MERIT:\n"
		"  \n"
		"  Moving Ochra's Keys from the ley lines was only the start, am I correct?\n"
		"  Nonetheless, what you've already caused is enough. The Keys were placed to\n"
		"  prevent corruption in the PSI. Moving them renders the PSI unusable. There\n"
		"  is nothing to be gained from THAT.")
	},
	{0, 8, 0,
		N_("ATARAXIA:\n"
		"  \n"
		"  Statements like that belie your youthful naivety and ignorance. There is no\n"
		"  way to make magic unusable. What you see now is the other side of PSI that\n"
		"  pitiful beings such as yourself turn a blind eye to. Like the sun at dawn\n"
		"  the sun at dusk, it is nonetheless the same sun, and each beautiful in its\n"
		"  own way. PSI isn't just for things like you, but for the entire cosmos.\n"
		"  Nothing can be gained by preventing it from taking its natural course.")
	},
	{1, 8, 0,
		N_("MERIT:\n"
		"  \n"
		"  You delude yourself with the crazed whisperings from the muck that spawned\n"
		"  you. You say that this PSI is still PSI, but it is not. PSI is the energy of\n"
		"  creation. This is the energy of destruction. PSI is a pure force, gathered\n"
		"  from around me and targeted with my mind. This is a foul mixture, existing\n"
		"  as half-mad corruptions like you. It can only break apart when confronted\n"
		"  with true power.")
	},
	{0, 6, 1,
		N_("ATARAXIA:\n"
		"  \n"
		"  You realise that not even your fellow PSI users agree with you? But, it is\n"
		"  of little consequence. I'll show you the true beauty of that which you shun\n"
		"  by smothering you in it and turning your flesh into fuel for the PSI that\n"
		"  runs through the veins of this Dome!")
	}
  },
  
  { // Merodach
	{0, 9, 0,
		N_("MERODACH:\n"
		"  \n"
		"  e v e r y t h i n g   h a s   t u r n e d   t o   d a r k n e s s\n"
		"  \n"
		"  n o   s o u n d   n o   l i g h t   n o   s e n s a t i o n\n"
		"  \n"
		"  w a s   t h i s   w h a t   y o u   w e r e   a f t e r   f r o m\n"
		"  \n"
		"  t h e   b e g i n n i n g ?")
	},
	{0, 7, 1,
		N_("MERODACH\n"
		"  \n"
		"  i ' l l   w r a p   m y   r e m a i n i n g   P S I   a r o u n d   y o u\n"
		"  \n"
		"  a n d   e x t i n g u i s h   t h a t   l i f e   o f   y o u r s\n"
		"  \n"
		"  l e t ' s   s e e   h o w   y o u   e n j o y   e t e r n a l   r e s t")
	}
  },
  
  { // Wervyn Anixil
    {1, 6, 0,
		N_("MERIT:\n"
		"  \n"
		"  ANIXIL? So, that's how it is. I assume you worked out a way of using the\n"
		"  PSI in this state. So, not happy with your allotted PSI from the Dome, you\n"
		"  decided to render it in this form and take it all for yourself.\n"
		"  What was your reasoning for doing something this rash, though?")
	},
	
	{0, 9, 0,
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  All you seem to do is complain. That's what I've discovered through this,\n"
		"  and it's definitely part of the reason I did it. I do creative things like\n"
		"  this sort of 'punishment' because it lets me better understand the nature of\n"
		"  all the forces involved. It tells me that you, MERIT, really do not have a\n"
		"  clue how to relate to other PSI users, and that for the most part you're\n"
		"  entirely self-absorbed. It tells me that, while PSI users in general doesn't\n"
		"  really 'get' the ethos of the forces involved here, and that the rerendered")
	},
	
	{0, 9, 0,
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  PSI, as intelligent as it is, pretty much entirely understands it, though it\n"
		"  was mistaken that the rendering of this form was intentional.\n"
		"  \n"
		"  Regarding that, by the way, THAT is the reason I thought you needed a\n"
		"  punishment. Making ridiculously inefficient, wasteful use of the limited PSI\n"
		"  we have available wouldn't have been a problem if it was just the normal PSI\n"
		"  like we'd planned it to be. But you knew full well that PSI users who wanted")
	},
	
	{0, 9, 0,
                N_("WERVYN ANIXIL:\n"
		"  \n"
		"  nothing to do with it were being greatly inconvenienced by usage clogging up\n"
		"  the limited ley-lines we have. That makes you an asshole, a deliberate prick\n"
		"  to everyone in the world because you thought you could get away with it, and\n"
		"  that it wouldn't matter.\n"
		"  \n"
		"  I also take issue with this nonsense about your punishment being severe,\n"
		"  because it's not. You can still use your PSI as you used it before, to a")
	},
	
	{0, 9, 0,
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  similar level of effectiveness. I specifically allowed access to THIS form\n"
		"  of PSI so that you couldn't complain that you couldn't use magic. Now\n"
		"  exiling you to the Dome had multiple meanings. One was to effectively tell\n"
		"  you, 'I don't think you have anything of value to do, so I'm putting you in\n"
		"  a place where no one does anything of value.' I still think that, by the\n"
		"  way. Another was, like I said, to see what you would do. Maybe you would\n"
		"  actually cast something worth the PSI, then. Or maybe you would take the")
	},
	
	{0, 8, 0,
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  hint and just stop it for the week until I decided you'd had enough. But you\n"
		"  couldn't do either of those things, because all you do is complain.\n"
		"  \n"
		"  I want you to take a look at yourself and ask what you want to accomplish\n"
		"  here, and what you think is keeping you here. Obviously you have some\n"
		"  attachment, even though you keep saying the new PSI is worthless. This isn't")
	},
	
	{0, 9, 0,	
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  true, of course, ATARAXIA really captures a good point here. For the most\n"
		"  part, PSI users just find you extremely annoying because you continually set\n"
		"  yourself at odds with pretty much every kind of PSI usage but your own, and\n"
		"  then complain about it. Do you want things to be different? They certainly\n"
		"  can be. 'EVEN IF I DO CHANGE, THEN MY PSI WON'T BE GOOD ENOUGH!' That's\n"
		"  bullshit. All other PSI users seem to be on the path to figuring out how to\n"
		"  just use it. You can too.")
	},
	
	{0, 9, 0,
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  Your 'punishment' is up tomorrow, I'd only planned to do it for a week. Of\n"
		"  course, you can continue complaining, and I may have to think of something\n"
		"  else to do to you. But I'm not going to kill you. If you want to leave this\n"
		"  mortal coil because you can't take it anymore, then leave. If you don't want\n"
		"  to leave, then start working out your issues with the new PSI, I actually\n"
		"  would love to help. And if you don't want to do that, then I'm sorry, but\n"
		"  you're going to continue being useless at magic, because the way you are")
	},
	
	{0, 4, 0,
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  now, it's not surprising at all that your PSI ability is as stunted as it\n"
		"  is.")
	},
	
	{1, 4, 0,
		N_("MERIT:\n"
		"  \n"
		"  The tainted PSI has driven even you mad. Mad with power. This is a blow I\n"
		"  I must strike, for everyone. For humanity.")
	},
	
	{0, 3, 1,
		N_("WERVYN ANIXIL:\n"
		"  \n"
		"  I'll meet you at the point of diminishing returns.")
	}
  },
  
  { // Wervyn Anixil???? (with Agate Knife)
    {1, 8, 0,
		N_("MERIT:\n"
		"  \n"
		"  ANIXIL? So, that's how it is. I assume you worked out a way of using the\n"
		"  PSI in this state. So, not happy with your allotted PSI from the Dome, you\n"
		"  decided to render it in this form and take it all for yourself.\n"
		"  What was your reasoning for doing something this rash, though?\n"
		"  \n"
		"  That was what you wanted to hear, right?")
	},
	
	{0, 7, 0,
		N_("WERVYN ANIXIL????:\n"
		"  \n"
		"  All you seem to do is complain. That's what I've discovered through this,\n"
		"  and it's definitely part of the ... ... ... ... ... ... ... ... ... ... ...\n"
		"  ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...\n"
		"  ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...\n"
		"  ... ... oh.")
	},
	
	{1, 6, 0,
		N_("MERIT:\n"
		"  \n"
		"  If I've learned anything in life, it's that things are often not quite as\n"
		"  they seem. You couldn't use the Agate Knife, so you attempted to hide it.\n"
		"  \n"
		"  Unfortunately for you, I found it and recognised it.")
	},
	
	{0, 7, 0,
                N_("WERVYN ANIXIL????:\n"
		"  \n"
		"  . . . . no. It's not just recognising the Knife at all. You were actually\n"
		"  able to use it, too.\n"
		"  \n"
		"  So. You too knew the secret?")
	},
	
	{1, 5, 0,
		N_("MERIT:\n"
		"  \n"
		"  I was one of the few that he told.\n"
		"  \n"
		"  And so I know.")
	},
	
	{0, 3, 0,
		N_("????:\n"
		"  \n"
		"  Umm...")
	},
	
	{1, 7, 0,	
		N_("MERIT:\n"
		"  \n"
		"  Well, it wasn't a bad attempt. If I hadn't found THAT, I would never have\n"
		"  known.\n"
		"  \n"
		"  Now that that's taken care of, I might as well finish what I came here for.\n"
		"  Prepare yourself.")
	},
	
	{0, 3, 1,
		N_("????:\n"
		"  \n"
		"  I won't hold back! There's not a lot of point, now.")
	}
  }
};

void BossDialog()
{
	int ypos;
	int lines;
	int dialog_set;
	
	dialog_set = current_boss;
	
	if (current_boss == 3) {
		if (player_shield == 30) {
			dialog_set = 4;
		}
	}
	
	lines = dtext[dialog_set][boss_dlg-1].lines;
	if (enter_pressed) {
		if (dtext[dialog_set][boss_dlg-1].last) {
			boss_dlg = 0;
			boss_fight_mode = 2;
		} else {
			boss_dlg++;
		}
		enter_pressed = 0;
	}
	if (boss_dlg > 0) {
		if (dtext[dialog_set][boss_dlg-1].pos == 0) {
			ypos = 29;
		} else {
			ypos = 466 - lines * 10 - 14;
		}
		DrawRect(0, ypos, 640, lines * 10 + 14, 0);
		DrawRect(2, ypos+2, 636, lines * 10 + 10, 40);
		DrawRect(4, ypos+4, 632, lines * 10 + 6, 80);
		draw_text(8, ypos+8, i18n_nonempty(dtext[dialog_set][boss_dlg-1].txt), 255);
	}
}

void InitBossVars()
{
	current_boss_room = 0;
	current_boss = 0;
	boss_tail_len = 0;
	boss_fight_mode = 0;
	boss_flash = 0;
	boss_new_life = 0;
	final_boss_dlg = 0;
}

void Curse()
{
	int i;
	int x, y;
	unsigned char tile;
	struct RoomConnection *rc;
	// Upon taking the cursed seal

	// Make the place of power your checkpoint
	
	checkpoint_x = rooms[place_of_power].w * 16 + rooms[place_of_power].x * 32;
	checkpoint_y = rooms[place_of_power].h * 16 + rooms[place_of_power].y * 32;
	
	// Turn the start room into a boss room, and clear it
	
	rooms[0].room_type = 2;
	Paint(rooms[0].x+1, rooms[0].y+1, rooms[0].w-2, rooms[0].h-2, DATADIR "/d/fbossroom.loc");
	
	// Lock all unvisited rooms off
	
	for (i = 0; i < 3000; i++) {
		if (!rooms[i].visited) {
			rc = rooms[i].con;
			
			while (rc != NULL) {
				x = rc->x2;
				y = rc->y2;
				tile = Get(x, y);
				if ((tile >= 38)&&(tile <= 41)) tile = tile - 38 + 4;
				if ((tile >= 21)&&(tile <= 24)) tile = tile - 21 + 4;
				if ((tile >= 13)&&(tile <= 16)) tile = tile - 13 + 4;
				Put(x, y, tile, GetRoom(x, y));
				rc = rc->n;
			}
			rooms[i].con = NULL;
			rooms[i].connections = 0;
		}
	}
	
	// Every 25th monster becomes a curse. All other monsters are deleted
	
	CurseEnemies();
}

void DrawPowerObject()
{
	int p_x, p_y;
	int p_obj = rooms[player_room].room_param;
	static int tick = 0;
	static int collect = 0;
	int i;
	int required_enemies;
	int n_artifacts;
	int dx, dy;
	int hover_v, off_v;
	float ddir;
	int dmag;
	
	if (place_of_power == player_room) p_obj = 3;
	
	n_artifacts = current_boss + artifacts[8] + artifacts[9] + artifacts[10];
	
	required_enemies = total_enemies * (percent_required[n_artifacts]) / 100;
	
	if (rooms[player_room].room_type == 4) required_enemies = 0;
	
	SDL_Rect from, to;
	
	hover_v = 16;
	off_v = 48;
	
	p_x = (rooms[player_room].w * 32 / 2 - 16) + rooms[player_room].x * 32;
	
	if (!game_paused) {
		if ((rooms[player_room].room_type == 5) || (rooms[player_room].room_type == 6)) {
			if (CanGetArtifact()) {
				if ((Get((player_x+PLAYERW/2)/32, (player_y+PLAYERH/2)/32)==42) ||
					(PlayerDist(rooms[player_room].w * 16 + rooms[player_room].x * 32,
								rooms[player_room].h * 16 + rooms[player_room].y * 32) < 32)) {
					if (rooms[player_room].enemies == 0) {
						off_v = 48 - (collect * 48 / 100);
						hover_v = 16 - (collect * 16 / 100);
						
						//DrawCircle(p_x + 16 - scroll_x, player_y - 48 - scroll_y, collect * 4 + 1, rand()%192+64);
						DrawCircle(player_x + PLAYERW/2 - scroll_x, player_y + PLAYERH/2- scroll_y, (100-collect) * 4 + 1, rand()%192+64);
						
						collect++;
						if (collect == 1) {
							SND_Pos(DATADIR "/a/crystal2.wav", 100, 0);
						}
						if (collect > 100) {
							collect = 0;
							rooms[player_room].room_type = 4;
							artifacts[8 + p_obj] = 1;
							specialmessage = 30 + p_obj;
							specialmessagetimer = 120;
							
							if (p_obj == 3) {
								Curse();
							}
						}
					}
				}
			}
		}
	}
	
	p_y = (rooms[player_room].h * 32 / 2 - 16) + rooms[player_room].y * 32 - off_v + sin((float)tick / 20.0)*hover_v;
	
	from.x = (8 + p_obj) * 32;
	from.y = 0;
	from.w = 32;
	from.h = 32;
	
	to.x = p_x - scroll_x;
	to.y = p_y - scroll_y;
	
	if (killed_enemies >= required_enemies) {
		DrawCircle(p_x + 16 - scroll_x, p_y + 16 - scroll_y, 34+rand()%5 + collect * 4 + 1, rand()%128+128);
	}
	SDL_BlitSurface(artifact_spr, &from, screen, &to);
	
	//printf("Required: %d   Killed %d   Value: %d\n", required_enemies, killed_enemies, (int)(sqrt((required_enemies - killed_enemies)/4)+5));
	for (i = 0; i < (int)(sqrt((required_enemies - killed_enemies))+5); i++) {
		ddir = (float)(rand()%256) * M_PI * 2 / 256;
		dmag = rand()%20;
		dx = p_x + 16 + cos(ddir)*dmag;
		dy = p_y + 16 + sin(ddir)*dmag;
		DrawCircle(dx - scroll_x, dy - scroll_y, rand()%5+1, 0);
		DrawCircle(dx - scroll_x, dy - scroll_y, rand()%3+1, 64);
	}
	
	tick++;
}

void DrawBossHP(int bar_length)
{
	static SDL_Surface *boss_hp_icon = NULL;
	int draw_amt;
	Uint8 draw_col;
	SDL_Rect to;
	to.x = 0;
	to.y = 29;
	
	if (boss_hp_icon == NULL) {
		boss_hp_icon = IMG_Load(DATADIR "/i/boss_icon.png");
	}
	SDL_BlitSurface(boss_hp_icon, NULL, screen, &to);
	DrawRect(16, 28, 624, 17, 0);
	DrawRect(17, 29, 622, 15, 32);
	DrawRect(18, 30, 620, 13, 64);
	
	draw_col = 128 + (boss_hp * 127 / 1000);
	if (bar_length < 100) {
		draw_amt = bar_length * 614 / 100;
	} else {
		draw_amt = boss_hp * 614 / 1000;
	}
	
	DrawRect(20, 32, draw_amt+2, 9, draw_col * 2 / 3);
	DrawRect(21, 33, draw_amt, 7, draw_col);
	
	
	if ( (current_boss == 3) && (player_shield == 30) ) {
		draw_text(22, 33, "??????????????", 0);
	} else {
		draw_text(22, 33, boss_names[current_boss], 0);
	}
}

int PDir(int x1, int y1, int x2, int y2)
{
	int dx, dy;
	
	dx = x2 - x1;
	dy = y2 - y1;
	
	return atan2(dy, dx);
}

float CHDir(float original_dir, float new_dir, float dir_delta)
{
	while (original_dir < 0) original_dir += M_PI * 2;
	while (new_dir < 0) new_dir += M_PI * 2;
	
	original_dir = fmodf(original_dir, M_PI * 2) + M_PI * 2;
	new_dir = fmodf(new_dir, M_PI * 2) + M_PI * 2;

	if (fabs(original_dir - new_dir) <= dir_delta) return new_dir;
	if (fabs((original_dir + M_PI * 2) - new_dir) <= dir_delta) return new_dir;
	if (fabs(original_dir - (new_dir + M_PI * 2)) <= dir_delta) return new_dir;	
	
	if (original_dir > new_dir) {
		if ((original_dir - new_dir) <= M_PI) {
			return original_dir - dir_delta;
		}
		if (((new_dir + M_PI*2) - original_dir) <= M_PI) {
			return original_dir + dir_delta;
		}
	} else {
		if ((new_dir - original_dir) <= M_PI) {
			return original_dir + dir_delta;
		}
		if (((original_dir + M_PI*2) - new_dir) <= M_PI) {
			return original_dir - dir_delta;
		}
	}
	
	exit(1);
	
	return original_dir;
}

void TryHurtBoss(int x, int y, int range, int power)
{
	int atk_power;
	if (boss_new_life) return;
	if (dist(x, y, boss_x, boss_y) <= range) {
		atk_power = 400 * power / circuit_size + power / 75;
		if (atk_power > boss_breakpoint) {
			boss_hp -= (atk_power - boss_breakpoint) * boss_dmgrate;
			SND_Pos(DATADIR "/a/enemyhit.wav", 128, dist(x, y, boss_x, boss_y) / 4);
			boss_flash = 40;
			if (boss_hp <= 0) {
				boss_new_life = 1;
				boss_hp = 0;
			}
		}
	}
}

int BossMovement(int move_x, int move_y)
{
	if (!IsSolid(Get( (move_x - 12 + (current_boss==3)*6)/32, (move_y - 12 + (current_boss==3)*4)/32))) {
		if (!IsSolid(Get( (move_x + 12 - (current_boss==3)*6)/32, (move_y - 12 + (current_boss==3)*4)/32))) {
			if (!IsSolid(Get( (move_x - 12 + (current_boss==3)*6)/32, (move_y + 12 - (current_boss==3)*4)/32))) {
				if (!IsSolid(Get( (move_x + 12 - (current_boss==3)*6)/32, (move_y + 12 - (current_boss==3)*4)/32))) {
					return 1;
				}
			}
		}
	}
	return 0;
}

void DrawBoss()
{
	int flash_coeff;
	static int t = 0;
	t++;
	
	flash_coeff = (boss_flash > 0)&&((boss_flash/3) % 2) ? 255 : 0;
	if (boss_flash > 0) boss_flash--;
	switch (current_boss) {
		case 0: {
			static SDL_Surface *boss_spr = NULL;
			static int tail_x[10], tail_y[10];
			int i;
			
			SDL_Rect drawpos;
			if (boss_spr == NULL) {
				boss_spr = IMG_Load(DATADIR "/i/boss1.png");
				SDL_SetColorKey(boss_spr, SDL_SRCCOLORKEY | SDL_RLEACCEL, 255);
			}
			
			if (boss_tail_len == 0) {
				tail_x[boss_tail_len] = boss_x;
				tail_y[boss_tail_len] = boss_y;
				boss_tail_len++;
			} else {
				if (dist(tail_x[0], tail_y[0], boss_x, boss_y) >= 24) {
					if (boss_tail_len < 10) boss_tail_len++;
					for (i = 8; i >= 0; i--) {
						tail_x[i+1] = tail_x[i];
						tail_y[i+1] = tail_y[i];
					}
					tail_x[0] = boss_x;
					tail_y[0] = boss_y;
				} else {
					for (i = 0; i < boss_tail_len; i++) {
						tail_x[i] += -1 + rand() % 3;
						tail_y[i] += -1 + rand() % 3;
					}
				}
			}
			
			for (i = 0; i < boss_tail_len; i++) {
				DrawCircleEx(tail_x[i] - scroll_x, tail_y[i] - scroll_y, 48 - i * 3, 0, 96 ^ flash_coeff);
			}
			for (i = 0; i < boss_tail_len; i++) {
				DrawCircleEx(tail_x[i] - scroll_x, tail_y[i] - scroll_y, 44 - i * 3, 0, 0 ^ flash_coeff);
			}
			
			drawpos.x = boss_x - 16 - scroll_x;
			drawpos.y = boss_y - 16 - scroll_y;
			
			SDL_BlitSurface(boss_spr, NULL, screen, &drawpos);
			
			break;
		}
		case 1: {
			static SDL_Surface *boss_spr = NULL;
			float h_dist;
			float new_dst, new_dir;
			int i, j;
			int hx[4], hy[4];
			int heads;
			int mx, my;
			int check_pass;
			
			heads = 5 - boss_lives;
			h_dist = M_PI * 2 / heads;

			SDL_Rect drawpos;
			if (boss_spr == NULL) {
				boss_spr = IMG_Load(DATADIR "/i/boss2.png");
				SDL_SetColorKey(boss_spr, SDL_SRCCOLORKEY | SDL_RLEACCEL, 255);
			}
			
			new_dst = 64 + sin((float)t / 80.0) * 24;
			
			new_dir = boss_2h_dir;
			new_dir += (float)(rand() % 16) / 128.0;
			new_dir -= (float)(rand() % 16) / 128.0;
			
			// Check new dists
			check_pass = 1;
			for (i = 0; i < heads; i++) {
				mx = boss_x + cos(new_dir + h_dist * i) * new_dst;
				my = boss_y + sin(new_dir + h_dist * i) * new_dst;
				
				if (!BossMovement(mx, my))
					check_pass = 0;
			}
			
			if (check_pass) {
				boss_2h_dst = new_dst;
				boss_2h_dir = new_dir;
			}
			
			// Heads
			
			for (i = 0; i < heads; i++) {
				hx[i] = boss_x + cos(boss_2h_dir + h_dist * i) * boss_2h_dst;
				hy[i] = boss_y + sin(boss_2h_dir + h_dist * i) * boss_2h_dst;
			}
			
			// Membranes
			for (j = 0; j < heads; j++) {
				for (i = 0; i < 5; i++) {
					mx = hx[j] + (hx[(j+1)%heads] - hx[j]) * i / 4;
					my = hy[j] + (hy[(j+1)%heads] - hy[j]) * i / 4;
					
					DrawCircleEx(mx - scroll_x, my - scroll_y, 24 + abs(i - 2)*12 + rand()%6, 0, 96 ^ flash_coeff);
				}
			}
			for (j = 0; j < heads; j++) {
				for (i = 0; i < 5; i++) {
					mx = hx[j] + (hx[(j+1)%heads] - hx[j]) * i / 4;
					my = hy[j] + (hy[(j+1)%heads] - hy[j]) * i / 4;
					
					DrawCircleEx(mx - scroll_x, my - scroll_y, 20 + abs(i - 2)*10 + rand()%6, 0, 0 ^ flash_coeff);
				}
			}
			
			// Draw heads
			
			for (i = 0; i < heads; i++) {	
				drawpos.x = hx[i] - 16 - scroll_x;
				drawpos.y = hy[i] - 16 - scroll_y;
				
				SDL_BlitSurface(boss_spr, NULL, screen, &drawpos);
			}
			
			break;
		}
		case 2: {
			static SDL_Surface *boss_spr = NULL;
			int i;
			int mx, my;
			float md;
			SDL_Rect drawfrom, drawpos;
						
			if (boss_spr == NULL) {
				boss_spr = IMG_Load(DATADIR "/i/boss3.png");
				SDL_SetColorKey(boss_spr, SDL_SRCCOLORKEY | SDL_RLEACCEL, 0);
			}
			
			boss_m_heads = boss_lives;
			
			DrawCircleEx(boss_x - scroll_x, boss_y - scroll_y, 48, 0, 0 ^ flash_coeff);
			// Draw appendages
			for (i = 0; i < boss_m_heads; i++) {
				mx = boss_x;
				my = boss_y;
				md = M_PI * 2.0 / (float)boss_m_heads * i + (M_PI / 4);
						
				while (dist(mx, my, boss_m_hx[i], boss_m_hy[i]) > 12) {
					md = CHDir(md, PDir(mx, my, boss_m_hx[i], boss_m_hy[i]), 0.5);
					//md = PDir(mx, my, boss_m_hx[i], boss_m_hy[i]);
					mx += cos(md) * 12;
					my += sin(md) * 12;

					DrawCircleEx(mx - scroll_x, my - scroll_y, 16 + rand()%8, 0, 0 ^ flash_coeff);
				}
			}
			for (i = 0; i < boss_m_heads; i++) {
				DrawCircleEx(boss_m_hx[i] - scroll_x, boss_m_hy[i] - scroll_y, 28, 0, 0 ^ flash_coeff);
			}
			for (i = 0; i < boss_m_heads; i++) {
				mx = boss_x;
				my = boss_y;
				md = M_PI * 2.0 / (float)boss_m_heads * i + (M_PI / 4);
						
				while (dist(mx, my, boss_m_hx[i], boss_m_hy[i]) > 12) {
					md = CHDir(md, PDir(mx, my, boss_m_hx[i], boss_m_hy[i]), 0.5);
					//md = PDir(mx, my, boss_m_hx[i], boss_m_hy[i]);
					mx += cos(md) * 12;
					my += sin(md) * 12;
					drawfrom.x = 32;
					drawfrom.y = 32 * (flash_coeff > 0);
					drawfrom.w = 32;
					drawfrom.h = 32;
					drawpos.x = mx - 16 - scroll_x;
					drawpos.y = my - 16 - scroll_y;
				
					SDL_BlitSurface(boss_spr, &drawfrom, screen, &drawpos);
				}
			}
			// Draw heads
			drawfrom.x = 0;
			drawfrom.y = 32 * (flash_coeff > 0);
			drawfrom.w = 32;
			drawfrom.h = 32;
			for (i = 0; i < boss_m_heads; i++) {
				drawpos.x = boss_m_hx[i] - 16 - scroll_x;
				drawpos.y = boss_m_hy[i] - 16 - scroll_y;
				SDL_BlitSurface(boss_spr, &drawfrom, screen, &drawpos);
			}
			
			// Draw core
			for (i = 0; i < boss_m_heads; i++) {
				drawpos.x = boss_x - 16 - scroll_x;
				drawpos.y = boss_y - 16 - scroll_y;
				
				SDL_BlitSurface(boss_spr, &drawfrom, screen, &drawpos);
			}
			break;
		}
		case 3: {
			SDL_Rect drawto;
			static SDL_Surface *boss_spr = NULL;
			
			if (boss_spr == NULL) {
				boss_spr = IMG_Load(DATADIR "/i/boss4.png");
				SDL_SetColorKey(boss_spr, SDL_SRCCOLORKEY | SDL_RLEACCEL, 0);
			}
			
			// Aura
			
			DrawCircleEx(boss_x - scroll_x, boss_y - scroll_y, 64+rand()%4, 0, (192+rand()%64) ^ flash_coeff);
			DrawCircleEx(boss_x - scroll_x, boss_y - scroll_y, 48+rand()%4, 0, (rand()%64) ^ flash_coeff);
			
			drawto.x = boss_x - 16 - scroll_x;
			drawto.y = boss_y - 16 - scroll_y;
			
			SDL_BlitSurface(boss_spr, NULL, screen, &drawto);
			break;
		}
	}
}

void BC_BossIntro()
{
	static int boss_bar_fill = 0;
	static int boss_circle = 0, circle_reduce = 128;
	
	if (boss_dlg != 0) {
		DrawBoss();
		DrawBossHP(100);
		return;
	}
	
	if (boss_bar_fill < 100) {
		boss_bar_fill += 2;
		DrawBossHP(boss_bar_fill);
	} else {
		if (boss_circle < 128) {
			boss_circle += 2;
			DrawCircleEx(boss_x - scroll_x, boss_y - scroll_y, boss_circle, 0, 0);
			DrawCircle(boss_x - scroll_x, boss_y - scroll_y, boss_circle, 96);
		} else {
			if (circle_reduce > 4) {
				circle_reduce -= 2;
				DrawCircleEx(boss_x - scroll_x, boss_y - scroll_y, circle_reduce, 0, 0);
				DrawCircle(boss_x - scroll_x, boss_y - scroll_y, circle_reduce, 96);
				DrawBoss();
			} else {
				DrawBoss();
				if ((final_boss_dlg == 0) && (current_boss == 3)) {
					boss_dlg = 1;
					final_boss_dlg = 1;
				} else {
					boss_fight_mode = 2;
				}
				boss_circle = 0;
				boss_bar_fill = 0;
				circle_reduce = 128;
			}
		}
		DrawBossHP(100);
	}
}

void BC_BossCombat()
{
	static int t = 0;
	int move_x, move_y;
	float pdir;
	float pdir_x, pdir_y;
	int i, j;
	t++;
	
	pdir = PlayerDir(boss_x, boss_y);
	pdir_x = cos(pdir);
	pdir_y = sin(pdir);
	
	move_x = boss_x;
	move_y = boss_y;
	DrawBoss();
	switch (current_boss) {
		case 0: {
			static float boss_dir = 0.0;
			static float boss_dir_offset = 1.0;
			
			move_x += cos(boss_dir) * (4 + (3 - boss_lives)/2);
			move_y += sin(boss_dir) * (4 + (3 - boss_lives)/2);
			
			move_x += pdir_x * (2 + (3 - boss_lives)/2);
			move_y += pdir_y * (2 + (3 - boss_lives)/2);
			
			boss_dir += (float)(rand() % 64)/256 * boss_dir_offset;
			if ((rand()%48) == 0) {
				boss_dir_offset *= -1.0;
			}
			while (!BossMovement(move_x, move_y)) {
				boss_dir += (float)(rand() % 64)/256 * boss_dir_offset;
				if ((rand()%48) == 0) {
					boss_dir_offset *= -1.0;
				}
				move_x = boss_x;
				move_y = boss_y;
				
				move_x += cos(boss_dir) * (4 + (3 - boss_lives)/2);
				move_y += sin(boss_dir) * (4 + (3 - boss_lives)/2);
				move_x += pdir_x * (2 + (3 - boss_lives)/2);
				move_y += pdir_y * (2 + (3 - boss_lives)/2);
			}
			if (BossMovement(move_x, move_y)) {
				boss_x = move_x;
				boss_y = move_y;
			}
			if (boss_flash > 15) return;
			
			switch (boss_lives) {
				case 3: {
					int laser_dmg;
					float laser_1_dir, laser_2_dir;
					
					laser_dmg = (rand()%(player_shield/2+1))/2;
					
					for (i = 0; i < player_shield / 5 + 1; i++) {
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 3, pdir + M_PI / 4, 2, 0);
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 0, pdir, 4.5, 0);
					}
					laser_1_dir = pdir + 0.3 + sin((float)t / 8)*0.2;
					laser_2_dir = pdir - 0.3 + sin((float)t / 8)*0.2;
					SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, laser_1_dir, 0, 1, 0, laser_dmg);
					SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, laser_2_dir, 0, 1, 0, laser_dmg);
					break;
				}
				case 2: {
					int laser_dmg;
					float laser_1_dir, laser_2_dir, laser_3_dir;
					
					laser_dmg = (rand()%(player_shield/2+1))/2;
					for (i = 0; i < player_shield / 3 + 1; i++) {
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 3, RandomDir(), 3.5, 0);
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 0, RandomDir(), 4.5, 0);
					}
					laser_1_dir = pdir + 0.4 + sin((float)t / 8)*0.2;
					laser_2_dir = pdir - 0.4 + sin((float)t / 8)*0.2;
					laser_3_dir = RandomDir();
					SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, laser_1_dir, 0, 1, 0, laser_dmg);
					SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, laser_2_dir, 0, 1, 0, laser_dmg);
					if ((t % 12) == 0)
						SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, laser_3_dir, 24, 8, 0, laser_dmg * 4);
					break;
				}
				case 1: {
					int laser_dmg;
					float laser_1_dir, laser_2_dir;
					
					laser_dmg = (rand()%(player_shield/2+1))/2;
					for (i = 0; i < player_shield / 4 + 1; i++) {
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 3, RandomDir(), 3.5, 0);
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 0, pdir, 5.0, 0);
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 0, RandomDir(), 4.5, 0);
					}
					laser_1_dir = pdir + 0.4 + sin((float)t / 8)*0.3;
					laser_2_dir = pdir - 0.4 + sin((float)t / 8)*0.3;
					SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, laser_1_dir, 0, 1, 0, laser_dmg);
					SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, laser_2_dir, 0, 1, 0, laser_dmg);
					if ((t % 5) == 0)
						SpawnLaser(boss_x + pdir_x * 24, boss_y + pdir_y * 8, RandomDir(), 16, 16, 0, laser_dmg * 4);
					if ((t % 10) == 0)
						SpawnBullet(boss_x + (rand() % 48)*pdir_x, boss_y + (rand() % 48)*pdir_y, 5, RandomDir(), 5, 0);
					break;
				}
			}
			break;
		}
		case 1: {
			float h_dist;
			int i, j;
			int hx[4], hy[4];
			int heads;
			int mx, my;
			int check_pass;
			float hd2;
			
			heads = 5 - boss_lives;
			h_dist = M_PI * 2 / heads;
			
			// Heads
			
			for (i = 0; i < heads; i++) {
				hx[i] = boss_x + cos(boss_2h_dir + h_dist * i) * boss_2h_dst;
				hy[i] = boss_y + sin(boss_2h_dir + h_dist * i) * boss_2h_dst;
			}
			
			move_x += -6 + rand() % 13;
			move_y += -6 + rand() % 13;
			
			move_x += pdir_x * (2 + (3 - boss_lives)/2);
			move_y += pdir_y * (2 + (3 - boss_lives)/2);
			
			// Check movement
			check_pass = 1;
			for (i = 0; i < heads; i++) {
				mx = move_x + cos(boss_2h_dir + h_dist * i) * boss_2h_dst;
				my = move_y + sin(boss_2h_dir + h_dist * i) * boss_2h_dst;
				
				if (!BossMovement(mx, my))
					check_pass = 0;
			}
			
			if (check_pass) {
				boss_x = move_x;
				boss_y = move_y;
			}
			
			if (boss_flash > 30) return;
			
			// Main cannons
			if ((t % (10 + boss_lives * 6)) == 1) {
				i = (t / (10 + boss_lives * 6)) % heads;
				for (j = 0; j < player_shield / 2 + 1; j++) {
					SpawnBullet(hx[i] + (rand() % 48)*pdir_x, hy[i] + (rand() % 48)*pdir_y, 0, pdir, 7 + ((float)(rand()%16))/10.0, 0);
				}
				SpawnBullet(hx[i], hy[i], 4, pdir, 10, 0);
			}
			
			// Barrage launcher
			for (i = 0; i < player_shield / 4 + heads; i++) {
				SpawnBullet(boss_x, boss_y, 0, RandomDir(), 3 + (float)(rand()%16)/10.0, 0);
			}
			
			if (boss_lives == 2) {
				// Splitters
				if ((t % 20) == 1) {
					SpawnBullet(boss_x, boss_y, 5, pdir, 5, 0);
				}
			}
			
			// Central laser cannon
			
			if ((t % (100 + boss_lives * 50)) == ((100 + boss_lives * 50)-1)) {
				i = player_shield / 3 + 2;
				hd2 = M_PI * 2.0 / (float)i;
				for (j = 0; j < i; j++) {
					SpawnLaser(boss_x + cos(hd2*j) * 20, boss_y + sin(hd2*j) * 20, pdir, 15 + rand()%16, 5, 0, (player_shield / 3 + 1));
				}
			}
			
			// Star vomit
			
			if (boss_lives == 2) {
				for (i = 0; i < heads; i++) {
					for (j = 0; j < player_shield / 6 + 1; j++) {
						SpawnBullet(hx[i], hy[i], 3, RandomDir(), 6, 0);
					}
				}
			}
			
			// Fusion cannon
			
			if (boss_lives <= 2) {
				if ((t % (12 + (boss_lives - 1) * 30)) == 9) {
					for (i = 0; i < heads; i++) {
						SpawnLaser(hx[i], hy[i], pdir, 6, 6, 0, (player_shield / 6 + 1));
					}
				}
			}
			break;
		}
		case 2: {
			int mx, my;
			static int t2 = 0;
			float md;
			int trying;

			boss_m_heads = boss_lives;
			
			for (i = 0; i < boss_m_heads; i++) {
				mx = boss_m_hx[i];
				my = boss_m_hy[i];
				
				boss_m_hd[i] = CHDir(boss_m_hd[i], PlayerDir(mx, my), 0.2);
				md = M_PI * 2.0 / (float)boss_m_heads * i + (M_PI / 4);
				boss_m_hd[i] = CHDir(boss_m_hd[i], md, 0.1);
				
				md = boss_m_hd[i];
				
				if ( (t % boss_m_heads) == i) {
					mx += cos(md) * 4 + (boss_m_heads == 1) * 2;
					my += sin(md) * 4 + (boss_m_heads == 1) * 2;
				}
				
		
				for (j = 0; j < boss_m_heads; j++) {
					if (j != i) {
						if (dist(boss_m_hx[j], boss_m_hy[j], boss_m_hx[i], boss_m_hy[i]) < 200) {
							md = PDir(boss_m_hx[j], boss_m_hy[j], boss_m_hx[i], boss_m_hy[i]);
							mx += cos(md) * 2;
							my += sin(md) * 2;
							boss_m_hd[i] = CHDir(boss_m_hd[i], md, 0.1);
						}
					}
				}
				
				trying = 10;
				
				while (!BossMovement(mx, my)) {
					trying--;
					if (trying == 0) break;
					md = PlayerDir(boss_m_hx[i], boss_m_hy[i]);
					md = md - 0.3 + (float)(rand()%16) * 0.6;
					
					mx = boss_m_hx[i] + cos(md) * 5;
					my = boss_m_hy[i] + sin(md) * 5;
					
					if (boss_m_heads == 1) {
						boss_m_hd[i] = CHDir(boss_m_hd[i], PlayerDir(mx, my), 0.2);
						md = boss_m_hd[i];
						mx += cos(md) * 3;
						my += sin(md) * 3;
					}
				}
				if (trying > 0) {
					boss_m_hx[i] = mx;
					boss_m_hy[i] = my;
				}
			}
			
			if (boss_flash > 30) return;
			
			if (boss_m_heads > 1) {
				if ((t % 2) == 0) {
					i = t % boss_m_heads;
					SpawnBullet(boss_m_hx[i] + cos(boss_m_hd[i])*8, boss_m_hy[i] + sin(boss_m_hd[i])*8, 0, boss_m_hd[i], 10, 0);
				} else {
					i = (t+2) % boss_m_heads;
					md = PlayerDir(boss_m_hx[i], boss_m_hy[i]);
					SpawnBullet(boss_m_hx[i] + cos(boss_m_hd[i])*8, boss_m_hy[i] + sin(boss_m_hd[i])*8, 0, md, 10, 0);
				}
			} else {
				md = CHDir(RandomDir(), PlayerDir(boss_m_hx[0], boss_m_hy[0]), 1.5);
				SpawnBullet(boss_m_hx[0] + cos(boss_m_hd[0])*8, boss_m_hy[0] + sin(boss_m_hd[0])*8, 0, md, 10, 0);
			}
			// Barrage
			
			for (i = 0; i < 24; i++) {
				if ((t2 % (6 - (player_shield / 6) + boss_m_heads)) == 0) {
					md = RandomDir();
					if (boss_m_heads > 2) {
						SpawnBullet(boss_x + cos(md)*8, boss_y + sin(md)*8, 0, md, 5, 0);
					} else {
						if (boss_m_heads > 1) {
							SpawnBullet(boss_x + cos(md)*24, boss_y + sin(md)*24, 4, md, 12, 0);
						} else {
							md = CHDir(md, pdir, 0.66);
							SpawnBullet(boss_x + cos(md)*24, boss_y + sin(md)*24, 4, md, 12, 0);
						}
					}
				}
				t2++;
			}
			
			// Beams
			if (boss_m_heads < 4) {
				if ((t % 5) == 0) {
					i = (t / 5)%boss_m_heads;
					md = boss_m_hd[i];
					SpawnLaser(boss_m_hx[i], boss_m_hy[i], md, 1, 10, 0.05, (player_shield / 7) + 3 - boss_m_heads);
					md += M_PI / 2;
					SpawnLaser(boss_m_hx[i], boss_m_hy[i], md, 1, 10, 0.05, (player_shield / 7) + 3 - boss_m_heads);
					md += M_PI / 2;
					SpawnLaser(boss_m_hx[i], boss_m_hy[i], md, 1, 10, 0.05, (player_shield / 7) + 3 - boss_m_heads);
					md += M_PI / 2;
					SpawnLaser(boss_m_hx[i], boss_m_hy[i], md, 1, 10, 0.05, (player_shield / 7) + 3 - boss_m_heads);
				}
			}
			
			if (boss_m_heads == 1) {
				if ( (t % 30) < player_shield) {
					md = CHDir(RandomDir(), pdir, 1.0);
					SpawnBullet(boss_m_hx[0] + cos(md)*24, boss_m_hy[0] + sin(md)*24, 3, md, 11, 0);
				}
			}
			break;
		}
		case 3:
		{
			int boss_loop;
			int boss_loop_total;
			int mx, my;
			int npattern;
			int i;
			float firedir;
			float flp;
			float cboss_dir;
			int ls_x, ls_y;
			
						
			mx = boss_x + cos(boss_dir)*(4 + (player_shield==30));
			my = boss_y + sin(boss_dir)*(4 + (player_shield==30));
			
			if (BossMovement(mx, my)) {
				boss_x = mx;
				boss_y = my;
				boss_dir = CHDir(boss_dir, pdir, 0.3);
			} else {
				boss_dir = CHDir(boss_dir, RandomDir(), 1);
			}
			
			if ((boss_lives == 1) && (player_shield == 30)) {
				{
					int room_w, room_h;
					int room_x, room_y;
					float tmr_t;
					float b_m_dir;
					
					float boss_x_bias, boss_y_bias;
					
					boss_x_bias = 1.33 - ((float)(boss_hp) / 1200.0);
					boss_y_bias = 0.7 + ((float)(boss_hp) / 1500.0);
					
					room_x = rooms[player_room].x * 32 + 64;
					room_y = rooms[player_room].y * 32 + 64;
					room_w = rooms[player_room].w * 32 - 128;
					room_h = rooms[player_room].h * 32 - 128;
					
					tmr_t = (float)t / 30.0;
					
					mx = (int)((sin(tmr_t * boss_x_bias)*0.5+0.5) * (float)room_w) + room_x;
					my = (int)((cos(tmr_t * boss_y_bias)*0.5+0.5) * (float)room_h) + room_y;
					
					if ( dist(mx, my, boss_x, boss_y) < 24) {
						boss_x = mx;
						boss_y = my;
					} else {
						b_m_dir = PDir(boss_x, boss_y, mx, my);
						boss_x += cos(b_m_dir)*24;
						boss_y += sin(b_m_dir)*24;
					}
				}
				if ((t % 50) < 49) {
					proxy_seek = 0;
					for (i = 0; i < 20; i++) {
						firedir = RandomDir();
						SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 7, firedir, 1, 0);
					}
				} else {
					proxy_seek = 1;
				}
				if ( ((t / 50) % 4) == 3) {
					proxy_seek = 1;
				}
				
				for (i = 0; i < 24; i++) {
					firedir = M_PI / 15 * (float)i + (float)t / 33.0 * M_PI;
					SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 0, firedir, 10, 0);
				}
			
				break;
			}
			
			boss_loop_total = 1 + (player_shield == 30)*2;
			
			cboss_dir = boss_dir;
			
			for (boss_loop = 0; boss_loop < boss_loop_total; boss_loop++) {
				if (player_shield == 30) {
					npattern = (3 - boss_lives) * 3 + 2 - (boss_hp / 334);
				} else {
					npattern = (2 - boss_lives) * 3 + 2 - (boss_hp / 334);
				}
				
				switch (npattern) {
					case 0:
						// Spirally pattern
						for (i = 0; i < 4; i++) {
							firedir = M_PI / 2 * (float)i + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 0, firedir, 5, 0);
						}
						for (i = 0; i < 4; i++) {
							firedir = M_PI / 2 * (float)i + (float)t / 33.0 * M_PI * 2.0;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 0, firedir, 8, 0);
						}
						for (i = 0; i < 4; i++) {
							firedir = RandomDir();
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 0, firedir, 12, 0);
						}
					break;
					case 1:
						// Proxies
						if ((t % 100) < 80) {
							for (i = 0; i < 6; i++) {
								firedir = RandomDir();
								SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 7, firedir, 1, 0);
							}
						} else {
							if ((t % 100) == 88) {
								proxy_seek = 1;
							}
							if ((t % 100) == 92) {
								proxy_seek = 0;
							}
						}
						for (i = 0; i < 8; i++) {
							firedir = M_PI / 4 * (float)i + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 0, firedir, 10, 0);
						}
					break;
					case 2:
						// Laserwalls
						if ((t % 4) == 2) {
							firedir = cboss_dir + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 10, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 5, 1);
						}
						
						for (i = 0; i < 4; i++) {
							firedir = cboss_dir + M_PI / 6 * (float)i + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 0, firedir, 5, 0);
						}
					break;
					case 3:
						if ((t % 30) == 29) {
							firedir = RandomDir();
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 10, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.99, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.98, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.97, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.96, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.95, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.94, 1);
						}
						if ((t % 40) == 39) {
							firedir = RandomDir();
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 1, firedir, 6, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 5, firedir+1, 6, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 6, firedir+1, 6, 1);
						}
						if ((t % 50) == 49) {
							proxy_seek = 0;
							firedir = RandomDir();
							for (i = 0; i < 60; i++) {
								SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 7, firedir, 1, 1);
								firedir += M_PI * 2.0 * (float)firedir / 60.0;
							}
						}
						if ((t % 50) == 45) {
							proxy_seek = 1;
						}
						
						if ((t % 10) == 2) {
							firedir = cboss_dir + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 10, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 7.5, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 5, 1);
						}
					break;
					case 4:
						if ((t % 80) == 79) {
							firedir = RandomDir();
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 10, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.98, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.96, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.94, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 9.92, 1);
						}
						if ((t % 90) == 89) {
							firedir = RandomDir();
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 1, firedir, 6, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 5, firedir+1, 6, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 6, firedir+1, 6, 1);
						}
						if ((t % 100) == 99) {
							proxy_seek = 0;
							firedir = RandomDir();
							for (i = 0; i < 60; i++) {
								SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 7, firedir, 1, 1);
								SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 7, firedir, 2, 1);
								SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 7, firedir, 3, 1);
								SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 7, firedir, 4, 1);
								firedir += M_PI * 2.0 * (float)firedir / 60.0;
							}
						}
						if ((t % 100) == 95) {
							proxy_seek = 1;
						}
						
						flp = (float)(t % 50) / 49.0;
						
						if ((t % 10) == 3) {
							i = (t / 10) % 4;
							switch (i) {
								case 0:
									ls_x = 7904 + flp * 576;
									ls_y = 8000;
									break;
								case 1:
									ls_x = 8480 - flp * 576;
									ls_y = 8416;
									break;
								case 2:
									ls_x = 7904;
									ls_y = 8000 + flp * 416;
									break;
								case 3:
								default:
									ls_x = 8480;
									ls_y = 8416 - flp * 416;
									break;
							}
							firedir = PlayerDir(ls_x, ls_y) - 0.1 + ((float)(rand()%16))/8.0;
							SpawnLaser(ls_x, ls_y, firedir, 8, 2, 0, player_shield/2+1);
						}
						
						if ((t % 6) == 2) {
							firedir = cboss_dir + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 10, 1);
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 8, firedir, 5, 1);
						}
					break;
					case 5:
						flp = (float)(t % 50) / 49.0;
						
						if ((t % 8) == 7) {
							i = (t / 8) % 4;
							switch (i) {
								case 0:
									ls_x = 7904 + flp * 576;
									ls_y = 8000;
									break;
								case 1:
									ls_x = 8480 - flp * 576;
									ls_y = 8416;
									break;
								case 2:
									ls_x = 7904;
									ls_y = 8000 + flp * 416;
									break;
								case 3:
								default:
									ls_x = 8480;
									ls_y = 8416 - flp * 416;
									break;
							}
							firedir = PlayerDir(ls_x, ls_y) - 0.1 + ((float)(rand()%16))/8.0;
							SpawnLaser(ls_x, ls_y, firedir - 0.03*6, 5, 5, 0.03, player_shield/3+1);
						}
						
						for (i = 0; i < 7; i++) {
							firedir = cboss_dir + M_PI/2 * (float)i + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 0, firedir, 8, 0);
						}
						for (i = 0; i < 7; i++) {
							firedir = cboss_dir + M_PI/4 + M_PI/2 * (float)i + (float)t / 33.0 * M_PI;
							SpawnBullet(boss_x + cos(firedir)*12, boss_y + sin(firedir)*12, 3, firedir, 8, 0);
						}
					break;
				}
				
				cboss_dir += -0.1 + ((float)(rand()%16))/8.0;
			}
		break;
		}
	}
}

void BC_BossDying()
{
	int x, y, rx, ry, rt;
	static int t_timer = 0;
	static int bxp, bdef;
	int i;
	static SDL_Surface *endpics[1] = {NULL};
	static float dr = 0;
	
	if (current_boss < 3) {
		specialmessage = 40 + rooms[player_room].room_param;
		specialmessagetimer = 120;
		rooms[player_room].room_type = 4;
		boss_fight_mode = 0;
		current_boss += 1;
		artifacts[8 + rooms[player_room].room_param] = 0;
		
		CullEnemies(4);
		
		// unlock doors
		
		for (y = 0; y < rooms[player_room].h; y++) {
			for (x = 0; x < rooms[player_room].w; x++) {
				rx = x + rooms[player_room].x;
				ry = y + rooms[player_room].y;
				rt = Get(rx, ry);
				
				if ((rt >= 21) && (rt <= 24)) {
					Put(rx, ry, rt - 21 + 13, player_room);
					
					if (rt == 21) {
						Put(rx, ry+1, 14, GetRoom(rx, ry+1));
					}
					if (rt == 22) {
						Put(rx, ry-1, 13, GetRoom(rx, ry-1));
					}
					if (rt == 23) {
						Put(rx+1, ry, 16, GetRoom(rx+1, ry));
					}
					if (rt == 24) {
						Put(rx-1, ry, 15, GetRoom(rx-1, ry));
					}
				}
			}
		}
		
		if (current_boss > 0) {
			SoupUpEnemies();
		}
	} else {
		if (boss_fight_mode == 3) {
			t_timer = 0;
			boss_fight_mode = 4;
			magic_circuit = 0;
			bxp = 4;
			bdef = 256;
			dr = RandomDir();
			
			if (training) t_timer = 2000;
		} else {
			DrawArtifactOverhead(3);
			if (bdef > 0) {
				DrawCircleEx(boss_x-scroll_x, boss_y-scroll_y, bxp, bxp-bdef, 0);
				bxp += 4;
				bdef -= 2;
			} else {
				if (t_timer < 220) {
					draw_text(244, 100, "*** Divine Seal ***", 1);
					draw_text(244, 380, "*** Divine Seal ***", 1);
					magic_circuit = circuit_size * 0.75 * t_timer / 220;
					for (i = 0; i < 10; i++) {
						rt = (rand() % 350) + 50;
						DrawCircle(player_x + PLAYERW/2 - scroll_x + cos(dr)*rt, player_y - scroll_y + PLAYERH/2
						+ sin(dr)*rt, rand()%30+5, rand()%128+128);
					}
					
					dr -= 0.025;
					for (i = 0; i < 5; i++) {
						Arc(screen, player_x + PLAYERW/2 - scroll_x, player_y + PLAYERH/2 - scroll_y, 450, dr);
						dr += 0.01;
					}
					dr += 0.03;
					
					if ((t_timer % 30) == 29) {
						dr = RandomDir();
					}
					
					DrawCircle(player_x - scroll_x + PLAYERW/2, player_y - scroll_y + PLAYERH/2, (t_timer % 30) * 15, 255);
					DrawCircle(player_x - scroll_x + PLAYERW/2, player_y - scroll_y + PLAYERH/2, ( (t_timer + 15) % 30) * 15, 255);
				} else {
					magic_circuit = 0;
					if (boss_fight_mode < 23) {
						if ( (t_timer % 4)==3) {
							boss_fight_mode++;
						}
					} else {
						if (endpics[0] == NULL) {
							if (training) {
								endpics[0] = IMG_Load(DATADIR "/i/wuss_ending.png");
							} 
						}
						
						if (training) {
							SDL_BlitSurface(endpics[0], NULL, screen, NULL);
						} else {
							show_ending = 1;
						}
					}
				}
				
				if (t_timer == 200) {
					CullEnemies(1);
				}
				
				t_timer++;
			}
		}
	}
}

void BC_NewLife()
{
	static int circle_size = 0;
	static int circle_size2 = 128;
	
	if (boss_flash > 0) {
		DrawBoss();
		return;
	}
	if (boss_new_life == 1) {
		circle_size = 0;
		circle_size2 = 128;
		boss_ox = rooms[current_boss_room].w * 16 + rooms[current_boss_room].x * 32;
		boss_oy = rooms[current_boss_room].h * 16 + rooms[current_boss_room].y * 32;
		boss_new_life = 2;
	} else {
		if (circle_size < 128) {
			circle_size += 4;
			DrawCircleEx(boss_x - scroll_x, boss_y - scroll_y, circle_size, 0, 0);
			DrawCircle(boss_x - scroll_x, boss_y - scroll_y, circle_size, 96);
			DrawBoss();
			
			if (boss_lives > 1) {
				DrawCircleEx(boss_ox - scroll_x, boss_oy - scroll_y, circle_size, 0, 0);
				DrawCircle(boss_ox - scroll_x, boss_oy - scroll_y, circle_size, 96);
			}
		} else {
			if (circle_size2 == 128) {
				boss_ox = boss_x;
				boss_oy = boss_y;
				boss_x = rooms[current_boss_room].w * 16 + rooms[current_boss_room].x * 32;
				boss_y = rooms[current_boss_room].h * 16 + rooms[current_boss_room].y * 32;
				boss_tail_len = 0;
				boss_lives--;
			}
			if (circle_size2 > 4) {
				circle_size2 -= 4;
				if (boss_lives > 0) {
					DrawCircleEx(boss_x - scroll_x, boss_y - scroll_y, circle_size2, 0, 0);
					DrawCircle(boss_x - scroll_x, boss_y - scroll_y, circle_size2, 96);
					DrawBoss();
				}
				
				DrawCircleEx(boss_ox - scroll_x, boss_oy - scroll_y, circle_size2, 0, 0);
				DrawCircle(boss_ox - scroll_x, boss_oy - scroll_y, circle_size2, 96);
			} else {
				if (boss_lives > 0) {
					boss_new_life = 0;
					boss_hp = 1000;
					if ( (boss_lives == 1) && (current_boss == 3) && (player_shield == 30) ) {
						player_hp = 6;
						boss_dmgrate = 0.2;
					}
				} else {
					boss_new_life = 0;
					boss_fight_mode = 3;
				}
			}
		}
	}
}

void BossControl()
{
	if ((player_room != current_boss_room)) {
		// Player left, so roll back the boss
		resetboss = 0;
		current_boss_room = 0;
		boss_tail_len = 0;
		boss_fight_mode = 0;
		boss_new_life = 0;
	}
	
	if (boss_fight_mode == 1) {
		BC_BossIntro();
		return;
	}
	if (boss_fight_mode == 2) {
		if (boss_new_life > 0) {
			BC_NewLife();
		} else {
			BC_BossCombat();
		}
		return;
	}
	if (boss_fight_mode >= 3) {
		BC_BossDying();
		return;
	}
}

void DrawArtifactOverhead(int p_obj)
{
	int p_x, p_y;
	static int tick = 0;

	SDL_Rect from, to;
	
	p_x = player_x - 8;
	p_y = player_y - 36 + sin((float)tick / 20.0)*4;
	
	from.x = (8 + p_obj) * 32;
	from.y = 0;
	from.w = 32;
	from.h = 32;
	
	to.x = p_x - scroll_x;
	to.y = p_y - scroll_y;
	SDL_BlitSurface(artifact_spr, &from, screen, &to);

	
	tick++;
}

int CanGetArtifact()
{
	int required_enemies;
	int n_artifacts;
	n_artifacts = current_boss + artifacts[8] + artifacts[9] + artifacts[10];
	required_enemies = total_enemies * (percent_required[n_artifacts]) / 100;

	if (killed_enemies >= required_enemies) return 1;
	return 0;
}

void BossRoom(int room)
{
	int i;
	
	boss_fight_mode = 1;
	current_boss_room = room;
	boss_x = rooms[room].w * 16 + rooms[room].x * 32;
	boss_y = rooms[room].h * 16 + rooms[room].y * 32;
	boss_hp = 1000;
	boss_flash = 0;
	magic_circuit = 0;
	boss_dlg = 0;
	boss_new_life = 0;
	
	switch (current_boss) {
		case 0:
			boss_lives = 3;
			boss_breakpoint = 75;
			boss_dmgrate = 1.00;
		break;
		case 1:
			boss_lives = 3;
			boss_breakpoint = 75;
			boss_dmgrate = 0.80;
		break;
		case 2:
			boss_lives = 4;
			boss_breakpoint = 90;
			boss_dmgrate = 1.25;
			boss_m_heads = 4;
			for (i = 0; i < boss_m_heads; i++) {
				boss_m_hd[i] = M_PI / 2 * i + (M_PI / 4);
				boss_m_hx[i] = boss_x + cos(boss_m_hd[i]) * 128;
				boss_m_hy[i] = boss_y + sin(boss_m_hd[i]) * 128;
			}
		break;
		case 3:
			boss_lives = 2 + (player_shield == 30);
			boss_breakpoint = 120;
			boss_dmgrate = 0.40 + 0.10*(player_shield == 30);
			boss_dir = M_PI * 3.0 / 2.0;
		break;
	}
	
	if (training) {
		boss_dmgrate *= 1.2;
		boss_breakpoint *= 0.8;
	}
}
