/*
   base/game.cc
   This file is part of the Osirion project and is distributed under
   the terms of the GNU General Public License version 2
*/

#include <vector>
#include <string>
#include <iomanip>
#include <assert.h>

#include "auxiliary/functions.h"
#include "core/gameserver.h"
#include "core/parser.h"
#include "core/range.h"
#include "core/descriptions.h"
#include "filesystem/filesystem.h"
#include "filesystem/inifile.h"
#include "base/game.h"
#include "base/cargo.h"
#include "base/cargopod.h"
#include "base/faction.h"
#include "base/navpoint.h"
#include "base/jumppoint.h"
#include "base/planet.h"
#include "base/savegame.h"
#include "base/spacemine.h"
#include "base/station.h"
#include "base/racetrack.h"
#include "base/ship.h"
#include "base/star.h"
#include "base/template.h"
#include "base/weapon.h"
#include "math/mathlib.h"
#include "sys/sys.h"

namespace game
{

/* -- class Default ----------------------------------------------- */

// default player settings
core::Zone *Default::zone = 0;
core::Entity *Default::view = 0;
ShipModel *Default::shipmodel = 0;
long Default::credits = 0;

void Default::clear()
{
	zone = 0;
	view = 0;
	shipmodel = 0;
	credits = 0;
}

/* -- class Game static members ----------------------------------- */

// game variables
core::Cvar *Game::g_impulsespeed = 0;
core::Cvar *Game::g_jumppointrange = 0;
core::Cvar *Game::g_devel = 0;
core::Cvar *Game::g_damping = 0;
core::Cvar *Game::g_deplete = 0;

core::Module *factory()
{
	return new Game();
}

// a player joins the game
void Game::func_join(core::Player *player, std::string const &args)
{
	if (player->control())
		return;

	std::string message("^B");
	message.append(player->name());
	message.append("^B joins the game.");
	core::server()->broadcast(message);

	// load existing player data
	core::server()->module()->player_load(player);
	
	if (!player->control()) {
		player->set_credits(Default::credits);
		Ship *ship = new Ship(player, Default::shipmodel);
		ship->set_zone(player->zone());
		player->set_control(ship);

		core::Entity *dock = ship->zone()->default_view();
		if (dock) {
			ship->set_dock(dock);
			player->set_view(dock);
		}
		
		ship->reset();
		
		player->send("^BYou received " + aux::article(Default::shipmodel->name()));
		player->sound("game/buy-ship");
	}
	
	player->set_dirty();
}

// a player joins the spectators
void Game::func_spectate(core::Player *player, std::string const &args)
{
	if (!player->control())
		return;
	
	core::server()->module()->player_save(player);

	std::string message("^B");
	message.append(player->name());
	message.append("^B spectates.");
	core::server()->broadcast(message);

	if (player->control()) {
		player->remove_asset(player->control());
	}

	if (!player->zone())
		player->set_zone(Default::zone);

	player->set_view(0);
}

// a player actives the hyperspace jump drive on his ship
void Game::func_jump(core::Player *player, std::string const &args)
{
	if (!player->control())
		return;
	if (player->control()->moduletype() != ship_enttype)
		return;
	Ship * ship = static_cast<Ship *>(player->control());
	ship->func_jump(args);
}

// a player actives the kinetic impulse drive on his ship
void Game::func_impulse(core::Player *player, std::string const &args)
{
	if (!player->control())
		return;
	if (player->control()->moduletype() != ship_enttype)
		return;
	Ship * ship = static_cast<Ship *>(player->control());
	ship->func_impulse();
}

// a player sends a docking request
void Game::func_target_dock(core::Player *player, core::Entity *entity)
{
	if (!player->control())
		return;

	if (player->control() == entity)
		return;

	if (player->control()->zone() != entity->zone())
		return;
	
	if ((entity->flags() & core::Entity::Dockable) == 0)
		return;

	if (player->control()->moduletype() != ship_enttype)
		return;
	
	if (!(entity->flags() & core::Entity::Dockable)) {
		return;
	}

	Ship * ship = static_cast<Ship *>(player->control());

	// check distance
	float range = entity->radius() + ship->radius();
	
	if (entity->moduletype() == planet_enttype) {
		range += planet_safe_distance;
	}
	
	core::Player *owner = (entity->type() == core::Entity::Controlable ? static_cast<core::EntityControlable *>(entity)->owner() : 0 );
	if (math::distance(entity->location(), ship->location()) > range) {
		if (owner) {
			player->send("^B" + owner->name() + "^B's " + entity->name() + " is out of range");
		} else {
			player->send("^B" + entity->name() + "^B is out of range");
		}
		return;
	}
	
	if ((player->control()->state() == core::Entity::Impulse) || (player->control()->state() == core::Entity::ImpulseInitiate)) {
		player->send("^BCan not dock at impulse speed");
		return;
	}
	
	if (player->control()->state() != core::Entity::Normal)
		return;
		
	if (entity->moduletype() == jumpgate_enttype) {
		// jumpgates have their own docking function
		JumpGate *jumpgate = static_cast<JumpGate *>(entity);
		jumpgate->func_dock(ship);
		return;
	} else if (entity->moduletype() == race_enttype) {
		RaceTrack *race = static_cast<RaceTrack *>(entity);
		race->func_dock(ship);
	} else {
		ship->set_dock(entity);
		
		if (player->control() == ship) {
			player->set_view(entity);
			if (owner) {
				player->send("^BDocking at " + owner->name() + "^B's " + entity->name());
			} else {
				player->send("^BDocking at " + entity->name());
			}
		}
		
		// force save
		core::server()->module()->player_save(player);
	}
}

// a player sends a standard hail
void Game::func_target_hail(core::Player *player, core::Entity *entity)
{
	// TODO spam protection
	if (!entity)
		return;
	
	if (!player->control())
		return;
	
	if (player->control()->zone() != entity->zone())
		return;
		
	if (player->mute()) {
		player->send("^WYou have been muted");
		return;
	}

	core::Player *target = (entity->type() == core::Entity::Controlable ?  static_cast<core::EntityControlable *>(entity)->owner() : 0 );
	if (!target)
		return;

	if (math::distance(player->control()->location(), entity->location()) > core::range::fxdistance) {
		player->send("^B" + target->name() + "^B is out of range");
	}
	player->send("^BYou hail " + target->name());
	player->sound("com/hail");

	target->send("^B" + player->name() + "^B hails you");
	target->sound("com/hail");
}

// a player sends a trade request
void Game::func_target_trade(core::Player *player, core::Entity *entity)
{
	if (!entity)
		return;
	
	if (!player->control())
		return;
	
	core::Player *target = (entity->type() == core::Entity::Controlable ?  static_cast<core::EntityControlable *>(entity)->owner() : 0 );
	if ((!target) || (target == player))
		return;
	
	if (entity != target->control())
		return;
	
	if (target->control()->state() != core::Entity::Normal)
		return;
	
	if (player->control()->state() != core::Entity::Normal)
		return;
		
	
	player->send("^WTrade requests are not implemented at this time");
}

// cheats
void Game::func_give(core::Player *player, const std::string &args)
{
	if (!Game::g_devel->value()) {
		player->send("Cheats disabled");
		return;
	}
	
	if (!player->control()) {
		player->send("^WYou need to join the game first!");
		return;
	}
	
	std::istringstream is(args);
	std::string str;
	
	is >> str;
	aux::to_label(str);
	
	if (str.compare("ship") == 0) {
		std::string labelstr;
		
		ShipModel *shipmodel = 0;
		if (!(is >> labelstr)) {
			player->send("Usage: give ship [string]");
		} else {
			aux::to_label(labelstr);
		 	shipmodel = ShipModel::find(labelstr);
		}
		
		if (!shipmodel) {
			// enable rcon buffering
			sys::ConsoleInterface::instance()->set_rcon(true);
			ShipModel::list();
			// disable rcon buffering
			sys::ConsoleInterface::instance()->set_rcon(false);
			
			while (sys::ConsoleInterface::instance()->rconbuf().size()) {
				player->send((*sys::ConsoleInterface::instance()->rconbuf().begin()));
				sys::ConsoleInterface::instance()->rconbuf().pop_front();
			}
			
			player->send("Unknown ship type '" + labelstr + "'");
			return;
		}
	
		// check if there's enough space available to transfer inventory
		if (shipmodel->maxcargo() < player->control()->inventory()->capacity_used()) {
			player->send("^WNot enough cargo space to transfer inventory!");
			return;
		}
		assert(player->control()->moduletype() == ship_enttype);
		Ship * oldship = static_cast<Ship *>(player->control());
		
		Ship * ship = new Ship(player, shipmodel);
		core::Entity *view = player->view();
		if (view && (view == player->control())) {
			view = ship;
		}
		
		// transfer inventory
		for (core::Inventory::Items::iterator it = player->control()->inventory()->items().begin(); it != player->control()->inventory()->items().end(); it++) {
			core::Item *item = new core::Item(*(*it));
			item->unset_flag(core::Item::Mounted);
			ship->inventory()->add(item);
		}
		ship->inventory()->set_dirty();
			
		// FIME move this into a method in the Ship class
		ship->set_zone(player->control()->zone());
		ship->get_location().assign(player->control()->location());

		ship->set_dock(oldship->dock());
		ship->get_axis().assign(player->control()->axis());
		ship->set_thrust(player->control()->thrust());
		
		ship->set_state(player->control()->state());
		
		// remove the old ship from the physics simulation
		oldship->die();
		oldship->reset();
		
		// add the new ship to the physics simulation
		ship->reset();
		//target_thrust is protected
		//ship->target_thrust = player->control()->target_thrust());

		player->remove_asset(player->control());
		player->set_control(ship);
		player->set_view(view);
		
		player->sound("game/buy-ship");
			
	} else if (str.compare("credits") == 0) {
		long credits;
		
		if (!(is >> credits)) {
			player->send("Usage: give credits [int]");
			return;
		}
		
		if (player->credits() + credits > 0 ) {
			player->set_credits(player->credits() + credits);
		} else {
			player->set_credits(0);
		}
		player->set_dirty();
		player->sound("game/buy");
			
	} else if (str.compare("cargo") == 0) {
		std::string labelstr;
		
		Cargo *cargo = 0;
		if (!(is >> labelstr)) {
			player->send("Usage: give cargo [string] [int]");
		} else {
			aux::to_label(labelstr);
			cargo = Cargo::find(labelstr);
		}
	
		if (!cargo) {
			// enable rcon buffering
			sys::ConsoleInterface::instance()->set_rcon(true);
			Cargo::list();
			// disable rcon buffering
			sys::ConsoleInterface::instance()->set_rcon(false);
			
			while (sys::ConsoleInterface::instance()->rconbuf().size()) {
				player->send((*sys::ConsoleInterface::instance()->rconbuf().begin()));
				sys::ConsoleInterface::instance()->rconbuf().pop_front();
			}
			
			player->send("Unknown cargo type '" + labelstr + "'");
			return;
		} else {
			int amount = 0;	
			if (!(is >> amount)) {
				amount = 1;
			} else {
				if (!amount)
					return;
			}

			int max = 0;
			if (cargo->volume()) {
				max = (int)floorf(player->control()->inventory()->capacity_available() / cargo->volume());
			} else {
				max = amount;
				if (max < 1) {
					max = 1;
				}
			}
			
			if (amount < 0) {
				amount = max;
			}

			if ((amount == 0) || (amount > max)) {
				player->send("^WNot enough cargo space available!");	
				return;
			}
			
			core::Item *item = player->control()->inventory()->find(cargo);
			if (!item) {
				item = new core::Item(cargo);
				player->control()->inventory()->add(item);
			} else {
				assert(item->info() == cargo);	
			}	
			item->inc_amount(amount);
			
			player->control()->inventory()->set_dirty();
			player->control()->owner()->sound("game/buy");	
		}
		
	} else if (str.compare("weapon") == 0) {
		std::string labelstr;
			
		Weapon *weapon = 0;
		if (!(is >> labelstr)) {
			player->send("Usage: give weapon [string] [int]");
		} else {
			aux::to_label(labelstr);
			weapon = Weapon::find(labelstr);
		}
	
		if (!weapon) {
			// enable rcon buffering
			sys::ConsoleInterface::instance()->set_rcon(true);
			Weapon::list();
			// disable rcon buffering
			sys::ConsoleInterface::instance()->set_rcon(false);
			
			while (sys::ConsoleInterface::instance()->rconbuf().size()) {
				player->send((*sys::ConsoleInterface::instance()->rconbuf().begin()));
				sys::ConsoleInterface::instance()->rconbuf().pop_front();
			}
			
			player->send("Unknown weapon type '" + labelstr + "'");
			return;
		} else {			
			int amount = 0;
			if (!(is >> amount)) {
				amount = 1;
			} else {
				if (!amount)
					return;
			}

			int max = 0;
			if (weapon->volume()) {
				max = (int)floorf(player->control()->inventory()->capacity_available() / weapon->volume());
			} else {
				max = amount;
				if (max < 1) {
					max = 1;
				}
			}
			if (amount < 0) {
				switch (weapon->subtype()) {
					case Weapon::Mine:
					case Weapon::Ammo:
						amount = max;
						break;
					default:
						amount = 1;
						break;
				}
			}

			if ((amount == 0) || (amount > max)) {
				player->send("^WNot enough cargo space available!");	
				return;
			}
			
			core::Item *item = 0;
			
			switch (weapon->subtype()) {
				
				case Weapon::Ammo:
				case Weapon::Mine:
					item = player->control()->inventory()->find(weapon);
					if (!item) {
						item = new core::Item(weapon);
						item->set_flag(core::Item::Unrestricted);
						player->control()->inventory()->add(item);
					} else {
						assert(item->info() == weapon);	
					}
					item->inc_amount(amount);
					break;
					
				case Weapon::Cannon:
				case Weapon::Turret:
					for (int n = 0; n < amount; n++) {
						item = new core::Item(weapon);
						item->set_flag(core::Item::Unique);
						item->set_flag(core::Item::Mountable);
						item->set_flag(core::Item::Unrestricted);
						item->set_amount(1);
						player->control()->inventory()->add(item);
					}
					break;
			}
			
			player->control()->inventory()->set_dirty();
			player->sound("game/buy");
		}
		
	} else {
		player->send("Usage: give cargo [string] [int]");
		player->send("       give credits [int]");
		player->send("       give ship [string]");
		player->send("       give weapon [string] [int]");
		return;
	}
}


void Game::func_specs(core::Player *player, const std::string &args)
{
	if (!Game::g_devel->value()) {
		player->send("Cheats disabled");
		return;
	}

	if (!player->control()) {
		player->send("^WYou need to join the game first!");
		return;
	}
	
	if (player->control()->moduletype() != ship_enttype)
		return;
	Ship * ship = static_cast<Ship *>(player->control());
	
	std::istringstream is(args);
	std::string str;
	
	if (!(is >> str)) {
		// enable rcon buffering
		sys::ConsoleInterface::instance()->set_rcon(true);

		con_print << "^B" << ship->name() << std::endl;
		con_print << "Specifications:"  << std::endl;
		con_print << "  ^Nmass    = ^B" << ship->mass() << std::endl;
		con_print << "  ^Nradius  = ^B" << ship->radius() << std::endl;
		con_print << "  ^Ncargo   = ^B" << ship->inventory()->capacity() << std::endl;
		con_print << "  ^Narmor   = ^B" << ship->maxarmor() << std::endl;
		con_print << "Engines:"  << std::endl;
		con_print << "  ^Ndamping = ^B" << ship->body()->getLinearDamping() << " " << ship->body()->getAngularDamping() << std::endl;
		con_print << "  ^Nthrust  = ^B" << ship->thrust_force() << std::endl;
		con_print << "  ^Nimpulse = ^B" << ship->impulse_force() << std::endl;
		con_print << "  ^Nstrafe  = ^B" << ship->strafe_force() << std::endl;
		con_print << "  ^Nturn    = ^B" << ship->turn_force() << std::endl;
		con_print << "  ^Nroll    = ^B" << ship->roll_force() << std::endl;

		// disable rcon buffering
		sys::ConsoleInterface::instance()->set_rcon(false);
		
		while (sys::ConsoleInterface::instance()->rconbuf().size()) {
			player->send((*sys::ConsoleInterface::instance()->rconbuf().begin()));
			sys::ConsoleInterface::instance()->rconbuf().pop_front();
		}
		
	} else  {
		std::stringstream msgstr;
		float value;
		
		// specs label
		aux::to_label(str);
		
		if (str.compare("thrust") == 0) {
			if (is >> value) {
				ship->set_thrust_force(value);
				msgstr << "Ship thrust force set to " << value;
			} else {
				msgstr << "Ship thrust at " << ship->thrust_force();
			}
			
		} else if (str.compare("impulse") == 0) {
			if (is >> value) {
				ship->set_impulse_force(value);
				msgstr << "Ship impulse force set to " << value;
			} else {
				msgstr << "Ship impulse force at " << ship->impulse_force();
			}
			
		} else if (str.compare("strafe") == 0) { 
			if (is >> value) {
				ship->set_strafe_force(value) ;
				msgstr << "Ship strafe force set to " << value;
			} else {
				msgstr << "Ship strafe force at " << ship->strafe_force();
			}
			
		} else if (str.compare("turn") == 0) {
			if (is >> value) {
				ship->set_turn_force(value);
				msgstr << "Ship turn force set to " << value;
			} else {
				msgstr << "Ship turn force at " << ship->turn_force();
			}
			
		} else if (str.compare("roll") == 0) {
			if (is >> value) {
				ship->set_roll_force(value);
				msgstr << "Ship roll force set to " << value;
			} else {
				msgstr << "Ship roll force at " << ship->roll_force();
			}
		
		} else if (str.compare("damping") == 0) {
			if (is >> value) {
				float a;
				if (!(is >> a)) {
					a = value;
				}
				ship->body()->setDamping(value, a);
				msgstr << "Ship damping set to " << value << " " << a;
			} else {
				msgstr << "Ship damping at " << ship->body()->getLinearDamping() << " " << ship->body()->getAngularDamping();
			}
		} else if (str.compare("mass") == 0) {
			msgstr << "Ship mass at " << ship->mass();
		} else {
			msgstr << "^WUnknown ship engine specification '" << str << "'";
		}
		
		player->send(msgstr.str());
	}
	
}

// sell items on a base
void Game::func_sell(core::Player *player, const std::string &args)
{
	// must be joined to buy items
	if (!player->control()) {
		player->send("^WYou need to join the game first!");
		return;
	}
	
	Ship *seller = static_cast<Ship *>(player->control());
	
	// must be docked to buy items
	if ((seller->state() != core::Entity::Docked) || (!seller->dock())) {
		player->send("^WYou need to be docked!");
		return;
	}
	
	core::Entity *buyer = seller->dock();
	
	// can only buy at planets and stations
	if ((buyer->moduletype() != station_enttype) && (buyer->moduletype() != planet_enttype)) {
		player->send("^WCan not sell here");
		return;
	}
	
	if (!seller->inventory() || !buyer->inventory()) {
		player->send("^WCan not sell here");
		return;
	}

	core::Item *seller_item = 0;
	core::Item *buyer_item = 0;
	unsigned int id = 0;
	int amount = 0;
		
	std::istringstream is(args);
	if (is >> id >> amount) {

		seller_item = seller->inventory()->find(id);
		if (!seller_item) {
			std::stringstream msgstr;
			msgstr << "^WItem " << id << " not in inventory";
			player->send(msgstr.str());
			return;
		}
		
		if (amount == 0) {
			player->send("^WNo amount specified");
			return;
		}

		if (seller_item->info()->type() == Cargo::infotype()) {
			// cargo has to be in demand
			buyer_item = buyer->inventory()->find(seller_item->info());
			if (!buyer_item) {
				player->send("^W" + buyer->name() + " ^Bdoes not buy " + seller_item->info()->name());
				return;
			}
		} else if (seller_item->info()->type() == Weapon::infotype()) {
			// weapons can be sold anywhere
			if (!seller_item->unique()) {
				buyer_item = buyer->inventory()->find(seller_item->info());
			}
			if (!buyer_item) {
				buyer_item = new core::Item(seller_item->info());
				buyer_item->set_flags(seller_item->flags());
				buyer->inventory()->add(buyer_item);
			}
		} else {
			player->send("^W" + buyer->name() + " ^Bdoes not buy " + seller_item->info()->name());
			return;
		}

		if (amount < 0) {
			amount = seller_item->amount();
		} else if (amount > seller_item->amount()) {
			amount = seller_item->amount();
		}
			
		int price = buyer_item->price();
		
		seller_item->dec_amount(amount);
		seller->owner()->set_credits(seller->owner()->credits() + price * amount);
		seller->owner()->set_dirty();
		seller->inventory()->set_dirty();
		
		if (buyer_item->amount() >= 0) {
			buyer_item->inc_amount(amount);
			buyer->inventory()->set_dirty();
		}
		
		// unmount if the item was mounted
		core::Slot *slot = (seller->slots() ? seller->slots()->find(seller_item) : 0);
		
		if (slot) {
			slot->set_item(0);
			slot->unset_flag(core::Slot::Active);
			slot->unset_flag(core::Slot::Mounted);
			seller_item->unset_flag(core::Item::Mounted);
		}
		
		// delete the item if the amount is 0
		if (seller_item->amount() == 0) {
			seller->inventory()->erase(seller_item->id());
			seller->inventory()->set_dirty();
			seller_item = 0;
		}
		
		// send a cargo sold message
		std::stringstream msgstr;
		if (buyer_item->unique()) {
			msgstr << "^BSold " << buyer_item->info()->name();
		} else {
			msgstr << "^BSold " << amount << " " << aux::plural("unit", amount) << " of " << buyer_item->info()->name() << " for " << price * amount << " credits";
		}
		player->send(msgstr.str());
		player->sound("game/buy");
	} else {
		player->send("Usage: sell [id] [amount] sell item from inventory");
	}
}

// buy items on a base
void Game::func_buy(core::Player *player, const std::string &args)
{
	// must be joined to buy items
	if (!player->control()) {
		player->send("^WYou need to join the game first!");
		return;
	}
	
	Ship *ship = static_cast<Ship *>(player->control());
	
	// must be docked to buy items
	if ((ship->state() != core::Entity::Docked) || (!ship->dock())) {
		player->send("^WYou need to be docked!");
		return;
	}
	
	core::Entity *seller = ship->dock();
	
	// can only buy at planets and stations
	if ((seller->moduletype() != station_enttype) && (seller->moduletype() != planet_enttype)) {
		player->send("^WCan not buy here");
		return;
	}
	
	if (!ship->inventory() || !seller->inventory()) {
		player->send("^WCan not buy here");
		return;
	}

	core::Item *item = 0;
	unsigned int id = 0;
	int amount = 0;
	
	std::istringstream is_int(args);
	if (is_int >> id >> amount) {
		
		// item id based buy
		item = seller->inventory()->find(id);
		if (!item) {
			std::stringstream msgstr;
			msgstr << "^WItem " << id << " not in seller inventory";
			player->send(msgstr.str());
			return;
		}
		
		if (item->info()->type() == ShipModel::infotype()) {
			ShipModel *shipmodel = ShipModel::find(item->info()->label());
			if (shipmodel) {
				shipmodel->buy(player->control(), player->view());
			}
			return;
			
		} else if ((item->info()->type() == Cargo::infotype()) || (item->info()->type() == Weapon::infotype())) {
			
			int price = item->price();
			long cash = player->credits();
			
			if (item->unique()) {
				amount = 1;
			}
			
			// check if the player has enough cash
			if (price > 0) {
				
				// negative amount means 'as much as possible'
				if (amount < 0) {
					amount = cash / price;
				}
				
				// maximum amount the player can afford
				if (cash < (amount * price)) {
					amount = cash / price;
				}
				
				if (amount < 1) {
					player->send("^WCan not afford transaction!");
					return;
				}
			}
			
			// check cargo size - ignore zero volume cargo
			if (item->info()->volume() > 0 ) {
				
				// maximum cargo size
				if (amount * item->info()->volume() > ship->inventory()->capacity_available()) {
					amount = (int)floorf(ship->inventory()->capacity_available() / item->info()->volume());
				}
				
				if (amount < 1) {
					player->send("^WNot enough cargo space available!");
					return;
				}
			}
			
			if (amount <= 0) {
				// unlimited amount of zero-cost cargo
				player->send("^WUnlimited amounts of free items are not supported!");
				return;
			}
			
			// if amount is set to -1. the base has an unlimited supply
			if (item->amount() == 0) {
				player->send("^W" + item->info()->name() + " not available!");
				return;
				
			} else if (item->amount() > 0) {
				
				if (amount > item->amount()) {
					amount = item->amount();
				}
			}
			
			// buyer is the player
			core::Item *buyer_item = 0;
			if (!item->unique()) {
				buyer_item = ship->inventory()->find(item->info());
			}
			if (!buyer_item) {
				buyer_item = new core::Item(item->info());
				buyer_item->set_flags(item->flags());
				ship->inventory()->add(buyer_item);
			}	
			
			// update buyer inventory
			buyer_item->inc_amount(amount);
			ship->inventory()->set_dirty();			
			player->set_credits(player->credits() - price * amount);
			player->set_dirty();
			
			// update seller inventory
			if (item->amount() >= 0) {
				item->dec_amount(amount);
				seller->inventory()->set_dirty();
			}
			
			if (item->info()->type() == Weapon::infotype()) {
				if (item->amount() == 0) {
					seller->inventory()->erase(item->id());
					seller->inventory()->set_dirty();
				}
			}
				
			// send a message to the player
			std::stringstream msgstr;
			if (buyer_item->unique()) {
				msgstr << "^BPurchased a " << buyer_item->info()->name();
			} else {
				msgstr << "^BPurchased " << amount << " " << aux::plural("unit", amount) << " of " << buyer_item->info()->name() << " for " << price * amount << " credits";
			}
			player->send(msgstr.str());
			player->sound("game/buy");
		} else {
			player->send("^WCan not buy " + item->info()->name());
		}
	
	} else {
		
		std::istringstream is(args);
		std::string typestr;
		std::string labelstr;

		if (!(is >> typestr >> labelstr)) {
			player->send("Usage: buy ship [ship label]   buy a ship from a base");
			player->send("Usage: buy [id] [amount]       buy from base inventory");
			return;
		} else {
			aux::to_label(typestr);
			aux::to_label(labelstr);
		}

		if (typestr.compare("ship") == 0) {
			ShipModel *shipmodel = ShipModel::find(labelstr);
			if (shipmodel) {
				shipmodel->buy(player->control(), player->view());
			} else {
				player->send("Unknown ship type '" + labelstr + "'");
			}
		} else {
			player->send("Unknown item type '" + typestr + "'");
		}
	}
}

// drop item requests
void Game::func_drop(core::Player *player, const std::string &args)
{
	// must be joined to buy items
	if (!player->control()) {
		player->send("^WYou need to join the game first!");
		return;
	}

	Ship *ship = static_cast<Ship *>(player->control());
	
	// cannot be docked to drop items
	if (ship->state() == core::Entity::Docked) {
		player->send("^WCan't drop items while docked!");
		return;
	}
	
	// cannot drop items while jumping
	if ((ship->state() == core::Entity::Jump) || (ship->state() == core::Entity::JumpInitiate)) {
		player->send("^WCan not drop items while hyperspace jump drive is active");
		return;
	}
	
	std::istringstream is(args);
	std::string labelstr;
	
	if (!(is >> labelstr)) {
		player->send("Usage: drop [label] drop an item");
		return;
	} else {
		aux::to_label(labelstr);
	}
	
	if (labelstr.compare("mine") == 0) {
		// drop a mine
		
		// find a mine in inventory
		const Weapon *weapon = 0;
		core::Item *item = 0;
		for (core::Inventory::Items::iterator it = ship->inventory()->items().begin(); (!item) && (it != ship->inventory()->items().end()); it++) {
			if ((*it)->info()->type() == Weapon::infotype()) {
				weapon = static_cast<const Weapon *>((*it)->info());
				
				if (weapon->subtype() == Weapon::Mine) {
					item = (*it);
				}
			}
		}
		
		if (!item) {
			player->send("^WNo mines in inventory!");
			return;
		}
		
		// create a spacemine entity using the Weapon instance as constructiong info
		SpaceMine *spacemine = new SpaceMine(weapon);

		spacemine->set_color(ship->color());
		spacemine->set_owner_id(player->id());
		spacemine->set_color_second(ship->color_second());
		spacemine->set_location(ship->location() + ship->axis().forward() * -1.0f * (ship->radius() + spacemine->radius()));
		spacemine->set_axis(ship->axis());
		spacemine->set_zone(ship->zone());
		
		player->send(weapon->name() + " ejected");
		player->sound("game/eject");
		
		spacemine->reset();
		
		// remove mine from inventory
		item->dec_amount(1);
		if (item->amount() == 0) {
			ship->inventory()->erase(item->id());
		}
	}
}

// eject item request
void Game::func_eject(core::Player *player, const std::string &args)
{
	if (!player->control()) {
		player->send("^WYou need to join the game first!");
		return;
	}
		
	std::istringstream is(args);
	unsigned int id;
	
	if (!(is >> id)) {
		player->send("Usage: eject [id] [int] eject inventory item, amount");
		return;
	}
	
	int amount = 0;
	if (!(is >> amount)) 
		amount = -1;
	
	if (!amount) {
		return;
	}
	
	core::EntityControlable *ejector = player->control();
	
	// cannot eject while jumping
	if ((ejector->state() == core::Entity::Jump) || (ejector->state() == core::Entity::JumpInitiate)) {
		if (ejector->owner()) {
			ejector->owner()->send("^WCan not eject while hyperspace jump drive is active");
		}
		return;
	}
	
	// find the item in the inventory
	core::Item *item = ejector->inventory()->find(id);
	if (!item) {
		if (ejector->owner()) {
			std::stringstream msgstr;
			msgstr << "^WItem " << id << " not in inventory";
			ejector->owner()->send(msgstr.str());
		}
		return;
	}	
	if (!item->amount()) {
		if (ejector->owner()) {
			ejector->owner()->send("^WYou do not own any " + item->info()->name());
		}
		return;	
	}	
	
	if ((amount < 0) || (amount > item->amount())) {		
		amount = item->amount();
	}
	item->dec_amount(amount);
	
	if (ejector->state() == core::Entity::Docked) {
		if (ejector->owner()) {
			std::stringstream msgstr;
			msgstr << "^BDestroyed " << amount << " " << aux::plural("unit", amount) << " of " << item->info()->name();
			ejector->owner()->send(msgstr.str());
			ejector->owner()->sound("game/eject");
		}
	} else {	
		// create cargo pod	
		CargoPod *pod = new CargoPod();
		
		if (item->info()->modelname().size()) {
			pod->set_name(item->info()->name());
			// setting item modesl looks silly, but can be uncommented server-side
			//pod->set_modelname(item->info()->modelname());
		}
		pod->set_color(ejector->color());
		pod->set_color_second(ejector->color_second());
		pod->set_location(ejector->location() + ejector->axis().up() * (ejector->radius() + pod->radius()));
		pod->set_axis(ejector->axis());
		pod->set_zone(ejector->zone());
		pod->set_info(item->info());
		
		// add loot to inventory
		pod->add_inventory();
		pod->inventory()->set_capacity(item->info()->volume() * amount);
		
		core::Item *loot = new core::Item(item->info());
		loot->set_amount(amount);
		loot->set_flags(item->flags());
		loot->unset_flag(core::Item::Mounted);
		
		pod->inventory()->add(loot);
		pod->inventory()->set_dirty();
		
		if (ejector->owner()) {
			std::stringstream msgstr;
			if (item->unique()) {
				msgstr << "^BEjected " << item->info()->name();
			} else {
				msgstr << "^BEjected " << amount << " " << aux::plural("unit", amount) << " of " << item->info()->name();
			}
			ejector->owner()->send(msgstr.str());
			ejector->owner()->sound("game/eject");
		}
		
		pod->reset();
	}
	
	if (item->amount() == 0) {
		if (item->has_flag(core::Item::Mounted)) {
			// unmount
			core::Slot *slot = 0;
			
			for(core::Slots::iterator it = ejector->slots()->begin(); (!slot) && (it != ejector->slots()->end()); ++it) {
				if ((*it)->item() == item) {
					slot = (*it);
				}
			}
		
			if (slot) {
				slot->set_item(0);
				slot->unset_flag(core::Slot::Active);
				slot->unset_flag(core::Slot::Mounted);
				item->unset_flag(core::Item::Mounted);	
			}
			
		}
		ejector->inventory()->erase(item->id());
	}
	ejector->inventory()->set_dirty();
}

// mount weapons into slots
void Game::func_mount(core::Player *player, const std::string &args)
{
	if (!player->control()) {
		return;
	}
	
	Ship *ship = static_cast<Ship *>(player->control());
	
	if (!ship->slots()) {
		return;
	}
	
	std::istringstream is(args);
	unsigned int id = 0;
	
	if (!(is >> id)) {
		ship->owner()->send("Usage: mount [id] mount weapon with id into the first available slot");
		return;
	}
	
	// find item to be mounted
	core::Item *item = ship->inventory()->find(id);
	if (!item) {
		if (ship->owner()) {
			std::stringstream msgstr;
			msgstr << "^WItem " << id << " not in inventory";
			ship->owner()->send(msgstr.str());
		}
		return;
	}
	
	// verify item is mountable
	if (item->info()->type() != Weapon::infotype()) {
		if (ship->owner()) {
			std::stringstream msgstr;
			msgstr << "^W" << item->info()->name() << " can not be mounted";
			ship->owner()->send(msgstr.str());
		}
		return;
	}	
	const Weapon *weapon = static_cast<const Weapon *>(item->info());
	
	if (!item->unique() || ((weapon->subtype() != Weapon::Cannon) && (weapon->subtype() != Weapon::Turret)) ) {
		if (ship->owner()) {
			std::stringstream msgstr;
			msgstr << "^W" << weapon->name() << " can not be mounted";
			ship->owner()->send(msgstr.str());
		}
		return;
	}
	
	if (item->has_flag(core::Item::Mounted)) {
		// unmount
		core::Slot *slot = ship->slots()->find(item);
		if (slot) {
			slot->set_item(0);
			slot->unset_flag(core::Slot::Active);
			slot->unset_flag(core::Slot::Mounted);
			item->unset_flag(core::Item::Mounted);
			
			ship->inventory()->set_dirty();
			if (ship->owner()) {
				std::stringstream msgstr;
				msgstr << "^BUnmounted " << weapon->name();
				ship->owner()->send(msgstr.str());
				player->sound("game/unmount");
			}
		}
		
	} else {
		// mount	
		core::Slot *slot = 0;
		
		for(core::Slots::iterator it = ship->slots()->begin(); (!slot) && (it != ship->slots()->end()); ++it) {
			if (((*it)->type() == weapon->slot_type()) && !(*it)->has_flag(core::Slot::Mounted)) {
				slot = (*it);
			}
		}
		
		if (!slot) {
			if (ship->owner()) {
				std::stringstream msgstr;
				msgstr << "^WNo slot available to mount " << weapon->name();
				ship->owner()->send(msgstr.str());
			}
			return;
		} else {
			slot->set_item(item);
			slot->set_flag(core::Slot::Active);
			slot->set_flag(core::Slot::Mounted);
			item->set_flag(core::Item::Mounted);
			
			ship->inventory()->set_dirty();
			if (ship->owner()) {
				std::stringstream msgstr;
				msgstr << "^BMounted " << weapon->name();
				ship->owner()->send(msgstr.str());
				player->sound("game/mount");
			}
		}
	}
}

// unmount weapons from slots
void Game::func_unmount(core::Player *player, const std::string &args)
{
}

// beam in nearby cargo pods
void Game::func_beam(core::Player *player, const std::string &args)
{
	const float beam_range_squared = 5.0f * 5.0f;
	
	if (!player->control())
		return;

	if (player->control()->state() != core::Entity::Normal)
		return;

	core::Zone *zone = player->control()->zone();
	core::Inventory *inventory = player->control()->inventory();
	
	// entity iterator
	for (core::Zone::Content::iterator eit = zone->content().begin(); eit != zone->content().end(); eit++) {
		// if the entity is a cargo pod and within beaming range
		if (((*eit)->moduletype() == cargopod_enttype) && (math::distancesquared(player->control()->location(), (*eit)->location()) <= beam_range_squared)) {
			core::Inventory *loot = (*eit)->inventory();
			if (!inventory || !loot)
				continue;

			// item iterator
			int loot_left = 0;
			for (core::Inventory::Items::iterator iit = loot->items().begin(); iit != loot->items().end(); iit++) {
				core::Item *item = (*iit);
				int negotiated_amount = item->amount();

				assert(item->info());
				
				if (inventory->capacity_available() < negotiated_amount * item->info()->volume()) {
					negotiated_amount = (int) floorf( inventory->capacity_available() / item->info()->volume());
				}
				
				if (negotiated_amount > 0) {
					core::Item *iteminv = 0;
					if (!item->has_flag(core::Item::Unique)) {
						iteminv = inventory->find(item->info());
					}
					if (!iteminv) {
						iteminv = new core::Item(item->info());
						iteminv->set_flags(item->flags());
						iteminv->unset_flag(core::Item::Mounted);
						inventory->add(iteminv);
					}
					item->dec_amount(negotiated_amount);
					iteminv->inc_amount(negotiated_amount);
					
					// this will recalculate inventory capacity
					inventory->set_dirty();
					loot->set_dirty();
					
					std::stringstream msgstr;
					msgstr << "^BBeamed in " << negotiated_amount << " " << aux::plural("unit", negotiated_amount) << " of " << item->info()->name();
					player->send(msgstr.str());	
					// TODO sound must be emitted at cargo pod location
					player->sound("game/beam");
				}
				
				loot_left += item->amount();
			}
			
			// if there's no loot left, the cargo pod will be destroyed
			if (!loot_left) {
				(*eit)->die();
			}
		}
	}
}

// launch request
void Game::func_launch(core::Player *player, std::string const &args)
{
	if (!player->control()) {
		player->send("^WYou need to join the game first!");
		return;
	}

	if (!player->view())
		return;

	if (player->control()->state() != core::Entity::Docked)
		return;

	assert(player->view()->zone() == player->control()->zone());
	assert(player->control()->moduletype() == ship_enttype);
		
	Ship *ship = static_cast<Ship *>(player->control());

	if (ship->dock()->moduletype() == ship_enttype) {
		
		switch(static_cast<Ship *>(ship->dock())->state()) {

			case core::Entity::Normal:
			case core::Entity::Docked:
				break;
			
			case core::Entity::NoPower:
				player->send("^BCan not launch while carrier has no power!");	
				return;
				break;
			
			case core::Entity::ImpulseInitiate:
			case core::Entity::Impulse:
				player->send("^BCan not launch while carrier is using impulse engines!");
				return;
				break;
				
			case core::Entity::JumpInitiate:
			case core::Entity::Jump:
				player->send("^BCan not launch while carrier is jumping through hyperspace!");	
				return;
				break;
			
			default:
				player->send("^BCan not launch from carrier!");	
				return;
				break;
		}
	}
	
	// force save, at this point, the ship is still docked
	core::server()->module()->player_save(player);
		
	player->send("^BLaunching from " + ship->dock()->name());
	
	ship->launch();
	
	player->set_view(0);

	
}

// respawn
void Game::func_respawn(core::Player *player, std::string const &args)
{
	Ship *ship = static_cast<Ship *>(player->control());
	
	if (!ship) {
		func_join(player, args);
		return;
	}

	if (ship->state() != core::Entity::Destroyed) {
		return;
	}

	// restore armor
	ship->set_armor(ship->maxarmor());
	
	core::Entity *spawn = ship->spawn();	
	if (!spawn) {
		ship->set_zone(Default::zone);
		spawn = ship->zone()->default_view();
	}

	if (spawn && spawn->has_flag(core::Entity::Dockable)) {
		ship->set_dock(spawn);
		player->set_view(spawn);
		player->send("^BRespawning at " + spawn->name());
	} else {
		ship->set_zone(Default::zone);
		ship->get_location().clear();
		ship->get_axis().clear();
		ship->set_state(core::Entity::Normal);
		player->set_view(0);
		player->send("^BRespawning");
	}	
}


// instantaniously goto a specified entity within the zone
void Game::func_goto(core::Player *player, const std::string &args)
{
	if (!args.size())
		return;

	if (!g_devel->value()) {
		player->send("Cheats disabled");
		return;
	}

	if (!player->control())
		return;

	core::Entity *dock = player->control()->zone()->search_entity(args);
	Ship *ship = static_cast<Ship *>(player->control());

	if (dock) {
		if (dock->type() == core::Entity::Globe) {
			// target is a planet: keep a safe distance
			ship->get_location().assign(dock->location() + (dock->axis().forward() * (planet_safe_distance + ship->radius() + dock->radius())));
			ship->get_axis().assign(dock->axis());
			ship->get_axis().change_direction(180.0f);

		} else if (dock->type() == core::Entity::Controlable) {
			// target is a controlable: appear above it
			
			// FIxME target might be docked -> requires ship docked_at awareness
			// we might want to match the target's state() and speed()
			ship->get_location().assign(dock->location() + (dock->axis().up() * (ship->radius() + dock->radius())));
			ship->get_axis().assign(dock->axis());
			
		} else {
			// targe is something else, appear in front of it
			ship->get_location().assign(dock->location() + (dock->axis().forward() * (ship->radius() + dock->radius())));
			ship->get_axis().assign(dock->axis());
			ship->get_axis().change_direction(180.0f);
		}
		ship->set_state(core::Entity::Normal);
		ship->reset();

		player->set_view(0);
		player->send("Going to " + dock->name());
	} else {
		player->send("Entity '" + args + "' not found");
	}
}

/* -- class Game -------------------------------------------------- */

Game::Game() : core::Module("Project::OSiRiON", true)
{
	// clear defaults
	Default::clear();
	
	// read factions.ini
	if (!Faction::init()) {
		abort();
		return;
	}
	
	// read templates.ini
	if (!Template::init()) {
		abort();
		return;
	}

	// read cargo.ini
	if (!Cargo::init()) {
		abort();
		return;
	}
	
	// read weapons.ini
	if (!Weapon::init()) {
		abort();
		return;
	}
	
	// read ships.ini
	if (!ShipModel::init()) {
		abort();
		return;
	}

	// read world.ini and the zones it refers to
	if (!load_world()) {
		abort();
		return;
	}

	// read game.ini
	if (!load_settings()) {
		abort();
		return;
	}

	// add game functions
	core::Func *func = 0;

	func = core::Func::add("join", Game::func_join);
	func->set_info("join the game");

	func = core::Func::add("spectate", Game::func_spectate);
	func->set_info("leave the game and spectate");

	func = core::Func::add("buy", Game::func_buy);
	func->set_info("[string] [string] [int] buy an item: specify type, label and amount");

	func = core::Func::add("sell", Game::func_sell);
	func->set_info("[string] [string] [int] sell an item: specify type, label and amount");

	func = core::Func::add("drop", Game::func_drop);
	func->set_info("[label] drop an item and activate it");
	
	func = core::Func::add("eject", Game::func_eject);
	func->set_info("[int] eject item with id from inventory");
	
	func = core::Func::add("mount", Game::func_mount);
	func->set_info("[int] mount a weapon into the first available slot");
	
	func = core::Func::add("beam", Game::func_beam);
	func->set_info("beam nearby cargo pods in");

	func = core::Func::add("give", Game::func_give);
	func->set_info("cheat functions");
	
	func = core::Func::add("specs", Game::func_specs);
	func->set_info("change your current ship's engine specifications");
		
	func = core::Func::add("jump", Game::func_jump);
	func->set_info("[string] activate or deactivate hyperspace jump drive");

	func = core::Func::add("impulse", Game::func_impulse);
	func->set_info("activate are deactive kinetic impulse drive");

	func = core::Func::add("launch", Game::func_launch);
	func->set_info("launch to space when docked");

	func = core::Func::add("respawn", Game::func_respawn);
	func->set_info("respawn when your ship has been destroyed");

	func = core::Func::add("goto", Game::func_goto);
	func->set_info("[string] goto to an entity within the zone");

	func = core::Func::add("@dock", Game::func_target_dock);
	func->set_info("send a docking request to target");

	func = core::Func::add("@hail", Game::func_target_hail);
	func->set_info("send a standard hail to target");
	
	func = core::Func::add("@trade", Game::func_target_trade);
	func->set_info("send a trade request to target");

	// add engine variables
	g_impulsespeed = core::Cvar::get("g_impulsespeed", "1500", core::Cvar::Game | core::Cvar::Archive);
	g_impulsespeed->set_info("[float] speed of the impulse drive");

	g_jumppointrange = core::Cvar::get("g_jumppointrange", "512", core::Cvar::Game | core::Cvar::Archive);
	g_jumppointrange->set_info("[float] jumppoint range");

	g_devel = core::Cvar::get("g_devel", "0", core::Cvar::Game | core::Cvar::Archive);
	g_devel->set_info("[bool] enable or disable developer mode");
	
	g_damping = core::Cvar::get("g_damping", "0.1", core::Cvar::Game | core::Cvar::Archive);
	g_damping->set_info("[float] physics damping factor (0-1)");
	
	g_deplete = core::Cvar::get("g_deplete", "60", core::Cvar::Game | core::Cvar::Archive);
	g_deplete->set_info("[int] number of seconds to deplete 1 unit of cargo from inventories");
}

Game::~Game()
{
	g_impulsespeed = 0;
	g_jumppointrange = 0;
	g_devel = 0;
	g_damping = 0;
	g_deplete = 0;
	
	// clear defaults
	Default::clear();
	
	// clear Cargo
	Cargo::done();
	
	// clear Weapon
	Weapon::done();
	
	// clear ShipModel
	ShipModel::done();
	
	// clear Templates
	Template::done();
	
	// clear Factions
	Faction::done();
}

bool Game::load_world()
{
	std::string inifilename("ini/world");

	filesystem::IniFile inifile;
	inifile.open(inifilename);

	if (!inifile.is_open()) {
		con_error << "Could not open " << inifile.name() << std::endl;
		return false;
	}

	con_print << "^BLoading world..." << std::endl;

	core::Zone *zone = 0;
	std::string label;
	math::Color color;

	while (inifile.getline()) {

		if (inifile.got_section()) {
			zone = 0;

			if (inifile.got_section("world")) {
				continue;
				
			} else {
				inifile.unknown_section();
			}

		} else if (inifile.got_key()) {

			if (inifile.in_section("world")) {

				if (inifile.got_key_label("zone", label)) {
					zone = new core::Zone(label);
					core::Zone::add(zone);
				} else {
					inifile.unknown_key();
				}			
			}
		}
	}

	inifile.close();

	if (!core::Zone::registry().size()) {
		con_error << "No zones found!" << std::endl;
		return false;
	}

	con_debug << "  " << inifile.name() << " " << core::Zone::registry().size() << " zones" << std::endl;

	for (core::Zone::Registry::iterator it = core::Zone::registry().begin(); it != core::Zone::registry().end(); it++) {
		if (!load_zone((*it).second)) {
			return false;
		}
	}

	for (core::Zone::Registry::iterator it = core::Zone::registry().begin(); it != core::Zone::registry().end(); it++) {
		if (!validate_zone((*it).second)) {
			return false;
		}
	}

	return true;
}

bool Game::load_zone(core::Zone *zone)
{
	using math::Vector3f;
	using math::Color;

	// set zone defaults
	zone->set_color(0.5f);
	zone->set_ambient_color(0.1f);
	zone->set_sky("default");

	std::string inifilename("ini/zones/");
	inifilename.append(zone->label());

	filesystem::IniFile zoneini;
	zoneini.open(inifilename);

	if (!zoneini.is_open()) {
		con_error << "Could not open " << zoneini.name() << std::endl;
		return false;
	}

	con_print << "^BLoading zone " << zone->label() << "..." << std::endl;

	size_t count = 0;
	
	math::Color	color;

	core::Entity 	*entity = 0;	
	core::Inventory *inventory = 0;
	core::Item 	*item = 0;

	Station 	*station = 0;
	Planet		*planet = 0;
	Star 		*star = 0;
	NavPoint 	*navpoint = 0;
	JumpPoint 	*jumppoint = 0;
	RaceTrack 	*racetrack = 0;
	CheckPoint 	*checkpoint = 0;

	bool b;
	long l;
	
	std::string strval;
	math::Vector3f vectorval;

	while (zoneini.getline()) {

		if (zoneini.got_section()) {
			
			if (zoneini.got_section("zone")) {
				continue;

			} else if (zoneini.got_section("star")) {
				star = new Star();
				entity = star;
				star->set_zone(zone);
				count ++;

			} else if (zoneini.got_section("navpoint")) {
				navpoint = new NavPoint();
				entity = navpoint;
				navpoint->set_zone(zone);
				count ++;

			} else if (zoneini.got_section("jumpgate")) {
				jumppoint = new JumpGate();
				entity = jumppoint;
				jumppoint->set_zone(zone);
				//jumppoint->set_radius(0);
				count ++;

			} else if (zoneini.got_section("jumppoint")) {
				jumppoint = new JumpPoint();
				entity = jumppoint;
				jumppoint->set_zone(zone);
				count ++;

			} else if (zoneini.got_section("racetrack")) {
				racetrack = new RaceTrack();
				entity = racetrack;
				racetrack->set_zone(zone);
				racetrack->set_radius(0);

			} else if (zoneini.got_section("checkpoint")) {
				
				if (!racetrack) {
					zoneini.unknown_error("checkpoint without racetrack");
					entity = 0;
					checkpoint = 0;
				} else {
					checkpoint = new CheckPoint(racetrack);
					entity = checkpoint;
					checkpoint->set_radius(0);
				}

			} else if (zoneini.got_section("planet")) {
				planet = new Planet();
				entity = planet;
				planet->set_zone(zone);
				count ++;

			} else if (zoneini.got_section("station")) {
				station = new Station();
				entity = station;
				station->set_zone(zone);
				station->set_radius(0);
				count ++;

			} else if (zoneini.got_section("entity")) {
				entity = new core::Entity();
				entity->set_zone(zone);
				entity->set_radius(0);
				count ++;
				
			} else if (zoneini.got_section("cargo")) {
				// new cargo trading definition for the current base
				item = 0;
				inventory = 0;
				
				if (!entity) {
					zoneini.unknown_error("cargo definition without entity");
				} else if ((entity->moduletype() != planet_enttype) && (entity->moduletype() != station_enttype)) {
					zoneini.unknown_error("cargo definition for invalid entity type");
				} else {
					inventory = entity->inventory();
					if (!inventory) {
						inventory = entity->add_inventory();
					}
				}
				
			} else if (zoneini.got_section("ship")) {
				// new ship trading definition for the current base
				item = 0;
				inventory = 0;
				
				if (!entity) {
					zoneini.unknown_error("ship definition without entity");
				} else if ((entity->moduletype() != planet_enttype) && (entity->moduletype() != station_enttype)) {
					zoneini.unknown_error("ship definition for invalid entity type");
				} else {
					inventory = entity->inventory();
					if (!inventory) {
						inventory = entity->add_inventory();
					}
				}
				
			} else if (zoneini.got_section("weapon")) {
				// new weapon trading definition for the current base
				item = 0;
				inventory = 0;
				
				if (!entity) {
					zoneini.unknown_error("weapon definition without entity");
				} else if ((entity->moduletype() != planet_enttype) && (entity->moduletype() != station_enttype)) {
					zoneini.unknown_error("weapon definition for invalid entity type");
				} else {
					inventory = entity->inventory();
					if (!inventory) {
						inventory = entity->add_inventory();
					}
				}
				
			} else {
				zoneini.unknown_section();
			}

		} else if (zoneini.got_key()) {

			if (zoneini.in_section("zone")) {
				if (zoneini.got_key_string("name", strval)) {
					aux::strip_quotes(strval);
					zone->set_name(strval);
					continue;
				} else if (zoneini.got_key_string("sky", strval)) {
					zone->set_sky(strval);
					continue;
				} else if (zoneini.got_key_color("color", color)) {
					zone->set_color(color);
					continue;
				} else if (zoneini.got_key_label("faction", strval)) {
					Faction *faction = Faction::find(strval);
					if (!faction) {
						zoneini.unknown_error("unknown faction '" + strval + "'");
					} else {
						zone->set_color(faction->color());
					}
				} else if (zoneini.got_key_color("ambient", color)) {
					zone->set_ambient_color(color);
					continue;
				} else if (zoneini.got_key_vector3f("location", vectorval)) {
					zone->set_location(vectorval);
					continue;
				} else if (zoneini.got_key_bool("showonmap", b)) {
					if (b) {
						zone->unset_flag(core::Zone::Hidden);
					} else {
						zone->set_flag(core::Zone::Hidden);
					}
					continue;
				} else if (zoneini.got_key_string("info", strval)) {
					core::Info *info = core::Info::find(zone->info());
					if (!info) {
						std::string labelstr(zone->label());
						info = new core::Info(core::Zone::infotype(), labelstr.c_str());
						zone->set_info(info);
					}
					info->add_text(strval);
					continue;
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("star")) {
				if (core::Parser::got_entity_key(zoneini, star)) {
					continue;
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("navpoint")) {
				if (core::Parser::got_entity_key(zoneini, navpoint)) {
					continue;
				} else if (zoneini.got_key_label("faction", strval)) {
					Faction *faction = Faction::find(strval);
					if (!faction) {
						zoneini.unknown_error("unknown faction '" + strval + "'");
					} else {
						faction->apply(navpoint);
					}
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("jumppoint")) {
				if (core::Parser::got_entity_key(zoneini, jumppoint)) {
					continue;
				} else if (zoneini.got_key_string("target", strval)) {
					jumppoint->set_targetlabel(strval);
					continue;
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("jumpgate")) {
				if (core::Parser::got_entity_key(zoneini, jumppoint)) {
					continue;
				} else if (zoneini.got_key_label("faction", strval)) {
					Faction *faction = Faction::find(strval);
					if (!faction) {
						zoneini.unknown_error("unknown faction '" + strval + "'");
					} else {
						faction->apply(jumppoint);
					}
				} else if (zoneini.got_key_label("template", strval)) {
					Template *entitytemplate = Template::find(strval);
					if (!entitytemplate) {
						zoneini.unknown_error("unknown template '" + strval + "'");
					} else {
						entitytemplate->apply(jumppoint);
					}
				} else if (zoneini.got_key_string("target", strval)) {
					jumppoint->set_targetlabel(strval);
					continue;
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("planet")) {
				if (core::Parser::got_entity_key(zoneini, planet)) {
					continue;
				} else if (zoneini.got_key_bool("dock", b)) {
					if (b) {
						planet->set_flag(core::Entity::Dockable);
					} else  {
						planet->unset_flag(core::Entity::Dockable);
					}
				} else if (zoneini.got_key_bool("default", b)) {
					if (b) {
						zone->set_default_view(planet);
					}
					continue;
				} else if (zoneini.got_key_label("faction", strval)) {
					Faction *faction = Faction::find(strval);
					if (!faction) {
						zoneini.unknown_error("unknown faction '" + strval + "'");
					} else {
						faction->apply(planet);
					}
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("station")) {
				if (core::Parser::got_entity_key(zoneini, station)) {
					continue;
				} else if (zoneini.got_key_bool("default", b)) {
					if (b) {
						zone->set_default_view(station);
					}
				} else if (zoneini.got_key_label("faction", strval)) {
					Faction *faction = Faction::find(strval);
					if (!faction) {
						zoneini.unknown_error("unknown faction '" + strval + "'");
					} else {
						faction->apply(station);
					}
				} else if (zoneini.got_key_label("template", strval)) {
					Template *entitytemplate = Template::find(strval);
					if (!entitytemplate) {
						zoneini.unknown_error("unknown template '" + strval + "'");
					} else {
						entitytemplate->apply(station);
					}
				} else if (zoneini.got_key_label("ship", strval)) {
					ShipModel *shipmodel = ShipModel::find(strval);
					if (!shipmodel) {
						zoneini.unknown_error("unknown ship type '" + strval + "'");
					} else {
						shipmodel->apply(station);
					}
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("racetrack")) {
				if (core::Parser::got_entity_key(zoneini, racetrack)) {
					continue;
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("checkpoint")) {
				if (core::Parser::got_entity_key(zoneini, checkpoint)) {
					continue;
				} else {
					zoneini.unknown_key();
				}

			} else if (zoneini.in_section("entity")) {
				if (core::Parser::got_entity_key(zoneini, entity)) {
					continue;
				} else if (zoneini.got_key_bool("default", b)) {
					if (b) {
						zone->set_default_view(entity);
					}
				} else if (zoneini.got_key_label("faction", strval)) {
					Faction *faction = Faction::find(strval);
					if (!faction) {
						zoneini.unknown_error("unknown faction '" + strval + "'");
					} else {
						entity->set_color(faction->color());
						entity->set_color_second(faction->color_second());
					}
				} else if (zoneini.got_key_label("template", strval)) {
					Template *entitytemplate = Template::find(strval);
					if (!entitytemplate) {
						zoneini.unknown_error("unknown template '" + strval + "'");
					} else {
						entitytemplate->apply(entity);
					}
				} else if (zoneini.got_key_label("ship", strval)) {
					ShipModel *shipmodel = ShipModel::find(strval);
					if (!shipmodel) {
						zoneini.unknown_error("unknown ship type '" + strval + "'");
					} else {
						shipmodel->apply(entity);
					}
					
				} else if (zoneini.got_key_label("ship", strval)) {
					ShipModel *shipmodel = ShipModel::find(strval);
					if (!shipmodel) {
						zoneini.unknown_error("unknown ship type '" + strval + "'");
					} else {
						// TODO apply ship model	
					}
				} else {
					zoneini.unknown_key();
				}
				
			} else if (zoneini.in_section("cargo")) {
				// cargo trade definition for a station or planet
				if (!entity || !inventory) {
					continue;
				}
				
				if (zoneini.got_key_label("label", strval)) {
					Cargo *cargo = Cargo::find(strval);
					if (cargo) {
						item = inventory->find(cargo);
						if (!item) {
							item = new core::Item(cargo);
							item->set_amount(-1);
							item->set_price(cargo->price());
							inventory->add(item);
						}
					} else {
						zoneini.unknown_error("unknown cargo type '" + strval + "'");
					}
					
				} else if (zoneini.got_key_long("price", l)) {
					if (item) {
						item->set_price(l);
					}
				} else if (zoneini.got_key_long("amount", l)) {
					if (item) {
						item->set_amount(l);
					}
				} else {
					zoneini.unknown_key();
				}
			
			} else if (zoneini.in_section("weapon")) {
				// weapon trade definition for a station or planet
				if (!entity || !inventory) {
					continue;
				}
				
				if (zoneini.got_key_label("label", strval)) {
					Weapon *weapon = Weapon::find(strval);
					if (weapon) {
						item = inventory->find(weapon);
						if (!item) {
							item = new core::Item(weapon);
							item->set_amount(-1);
							item->set_price(weapon->price());
							item->set_flag(core::Item::Unrestricted);
							
							switch (weapon->subtype()) {
								case Weapon::Cannon:
									item->set_flag(core::Item::Unique);
									item->set_flag(core::Item::Mountable);
									break;
								case Weapon::Turret:
									item->set_flag(core::Item::Unique);
									item->set_flag(core::Item::Mountable);
									break;
								default:
									break;
							}
							inventory->add(item);
						}
					} else {
						zoneini.unknown_error("unknown weapon type '" + strval + "'");
					}
					
				} else if (zoneini.got_key_long("price", l)) {
					if (item) {
						item->set_price(l);
					}
				} else if (zoneini.got_key_long("amount", l)) {
					if (item) {
						item->set_amount(l);
					}
				} else {
					zoneini.unknown_key();
				}
				
			} else if (zoneini.in_section("ship")) {
				// ship trade definition for a station or planet
				if (!entity || !inventory) {
					continue;
				}
				
				if (zoneini.got_key_label("label", strval)) {
					ShipModel *shipmodel= ShipModel::find(strval);
					if (shipmodel) {
						item = inventory->find(shipmodel);
						if (!item) {
							item = new core::Item(shipmodel);
							item->set_amount(-1);
							item->set_price(shipmodel->price());
							inventory->add(item);
						}
					} else {
						zoneini.unknown_error("unknown ship type '" + strval + "'");
					}
					
				} else if (zoneini.got_key_long("price", l)) {
					if (item) {
						item->set_price(l);
					}
					
				} else {
					zoneini.unknown_key();
				}
			} 
		}
	}
	
	zoneini.close();
	con_debug << "  " << zoneini.name() << " " << zone->content().size() << " entities" << std::endl;
	return true;
}

bool Game::validate_zone(core::Zone *zone)
{
	con_print << "^BValidating zone " << zone->label() << "..." << std::endl;

	for (core::Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); it++) {
		core::Entity *entity = (*it);
		if (entity->entity_moduletypeid == jumppoint_enttype) {
			// validate jump points
			JumpPoint *jumppoint = static_cast<JumpPoint *>(entity);
			jumppoint->validate();
		} else if (entity->entity_moduletypeid == jumpgate_enttype) {
			// validate jump gate
			JumpGate *jumpgate = static_cast<JumpGate *>(entity);
			jumpgate->validate();
		} else {
			if (entity->has_flag(core::Entity::Dockable)) {
				generate_entity_menus(entity);
			}
		}
		
		if (!entity->radius()) {
			if (entity->model()) {
				entity->set_radius(entity->model()->radius());
			} else {
				entity->set_radius(0.25f);
			}
			//con_debug << "  " << entity->label() << " radius set to " << entity->radius() << std::endl;
		}
		
		// initialize physics on planets and entities with a model
		if ((entity->entity_moduletypeid == star_enttype) ||(entity->entity_moduletypeid == planet_enttype) || (entity->model())) {
			entity->reset();
		}
		
		if (entity->model()) {
			entity->add_slots();
			entity->slots()->load(entity->model());
		}
	}

	return true;
}

bool Game::generate_entity_menus(core::Entity *entity)
{
	using core::MenuDescription;
	using core::ButtonDescription;

	if ((entity->moduletype() != planet_enttype) && (entity->moduletype() != station_enttype)) {
		// not dockable
		return false;
	}
			
	MenuDescription *menu_dealer = 0;
	ButtonDescription *button = 0;
	
	// dockable entity
	// add main menu
	MenuDescription *menu_main = new MenuDescription();
	menu_main->set_label("main");
	menu_main->set_text("Launch area");
	entity->add_menu(menu_main);

	// add launch button
	button = new ButtonDescription();
	button->set_text("Launch");
	button->set_command("launch", ButtonDescription::CommandGame);
	button->set_alignment(ButtonDescription::Center);
	menu_main->add_button(button);
	
	// add trade menus	
	if (entity->inventory()) {
		entity->set_flag(core::Entity::KeepAlive);
		size_t nbcargo = 0;
		size_t nbweapon = 0;
		size_t nbships = 0;		
		
		for (core::Inventory::Items::const_iterator it = entity->inventory()->items().begin(); it != entity->inventory()->items().end(); it++) {
			core::Item *item = (*it);
			
			if (item->info()->type() == Cargo::infotype()) {
				nbcargo++;
				
			} else if (item->info()->type() == Weapon::infotype()) {
				nbweapon++;
				
			} else if (item->info()->type() == ShipModel::infotype()) {
				if (!menu_dealer) {
					menu_dealer = new MenuDescription();
					menu_dealer->set_label("ships");
					menu_dealer->set_text("Ship Dealer");
				}
							
				button = new ButtonDescription();
				button->set_text("buy " + item->info()->name());
				
				std::ostringstream str("");			
				str << "buy " << item->info()->id();
				button->set_command(str.str() , ButtonDescription::CommandMenu);
				button->set_info(item->info());
				button->set_alignment(ButtonDescription::Left);
				
				menu_dealer->add_button(button);
				nbships++;
			}
		}
	
		if (nbcargo > 0) {
			con_debug << "  " << entity->label() << " " << nbcargo << " cargo " << aux::plural("type", nbcargo) << std::endl;
			button = new ButtonDescription();
			button->set_text("Trader");
			button->set_command("trade cargo", ButtonDescription::CommandMenu);
			button->set_alignment(ButtonDescription::Center);
			menu_main->add_button(button);
		}
		
		if (nbweapon > 0) {
			con_debug << "  " << entity->label() << " " << nbcargo << " weapon " << aux::plural("type", nbweapon) << std::endl;
			button = new ButtonDescription();
			button->set_text("Weapon Dealer");
			button->set_command("trade weapon", ButtonDescription::CommandMenu);
			button->set_alignment(ButtonDescription::Center);
			menu_main->add_button(button);
		}

		if (nbships > 0) {
			con_debug << "  " << entity->label() << " " << nbships << " ship " << aux::plural("type", nbships) << std::endl;
			button = new ButtonDescription();
			button->set_text("Return");
			button->set_command("main", ButtonDescription::CommandMenu);
			button->set_alignment(ButtonDescription::Center);
			menu_dealer->add_button(button);

			entity->add_menu(menu_dealer);

			button = new ButtonDescription();
			button->set_text("Ship Dealer");
			button->set_command("ships", ButtonDescription::CommandMenu);
			button->set_alignment(ButtonDescription::Center);
			menu_main->add_button(button);
		}
	}
	
	return true;
}

// load game defaults settings
bool Game::load_settings()
{
	filesystem::IniFile inifile;
	inifile.open("ini/game");
	if (!inifile.is_open()) {
		con_error << "Could not open " << inifile.name() << "!" << std::endl;
		return false;
	}

	long l;
	std::string str;

	while (inifile.getline()) {

		if (inifile.got_section()) {

			if (inifile.got_section("player")) {
				continue;
			} else if (inifile.got_section("cargo")) {
				continue;
			} else {
				inifile.unknown_section();
			}

		} else if (inifile.got_key()) {

			if (inifile.in_section("player")) {
				
				if (inifile.got_key_long("credits", l)) {
					Default::credits = l;
					
				} else if (inifile.got_key_label("zone", str)) {
					Default::zone = core::Zone::find(str);
					
				} else if (inifile.got_key_label("ship", str)) {
					Default::shipmodel = ShipModel::find(str);
					
				} else {
					inifile.unknown_key();
				}
			}
		}
	}

	inifile.close();

	if (!Default::zone) {
		con_error << "No default zone found!\n";
		return false;
	}

	if (!Default::zone->default_view()) {
		con_error << "Zone '" << Default::zone->label() << "' has no default view!\n";
		return false;
	}

	Default::view = Default::zone->default_view();

	if (!Default::shipmodel) {
		con_error << "No default ship model found!\n";
		return false;
	}

	return true;
}

void Game::frame(float seconds)
{
	if (!running())
		return;
}

void Game::player_connect(core::Player *player)
{
	std::string args;
	player->set_zone(Default::zone);
	player->set_view(0);	// not docked

	// FIXME load player
	func_spectate(player, args);
}

void Game::player_disconnect(core::Player *player)
{
	if (player->control()) {
		core::server()->module()->player_save(player);
	}
}

// load singleplayer savegame
void Game::game_load(core::Player *player, filesystem::IniFile & inifile)
{
	if (player->control())
		return;
	
	if (core::server()->mode() != core::GameServer::SinglePlayer) {
		return;
	}
	
	SaveGame::load_game(player, inifile);
}

// load multiplayer savegame player data
void Game::player_load(core::Player *player)
{
	if (!player->guid().is_valid()) {
		return;		
	}
	
	if (player->control())
		return;

	if (core::server()->mode() != core::GameServer::MultiPlayer) {
		return;
	}
		
	std::string guid(player->guid().str());
	std::string directory(guid.substr(0,4));
	
	std::string filename;
	filename.append("players");
	filename += '/';
	filename.append(directory);
	filename += '/';
	filename.append(guid);

	filesystem::IniFile inifile;
	inifile.open(filename);
	if (!inifile.is_open()) {
		return;
	}
	
	con_debug << "player " << player->id() << ": " << "loading data" << std::endl;
	
	SaveGame::load_game(player, inifile);
	
	inifile.close();
}

void Game::game_save(core::Player *player, std::ostream & os)
{
	if ((!player->control()) || (player->control()->moduletype() != ship_enttype)) {
		return;
	}
	
	if (core::server()->mode() == core::GameServer::SinglePlayer) {
		// save player data
		SaveGame::player_to_stream(player, os);
	}
}

void Game::player_save(core::Player *player)
{
	if ((!player->control()) || (player->control()->moduletype() != ship_enttype)) {
		return;
	}
	
	if (core::server()->mode() == core::GameServer::SinglePlayer) {
		std::string command("savegame autosave AUTOSAVE");
		core::CommandBuffer::exec(command);
		
	} else if (core::server()->mode() == core::GameServer::MultiPlayer) {
	
		if (!player->guid().is_valid()) {
			return;
		}		

		con_debug << "player " << player->id() << ": " << "saving data" << std::endl;
				
		std::string guid(player->guid().str());
		std::string directory(guid.substr(0,4));
		
		// create players/ directory
		std::string filename(filesystem::writedir());
		filename.append("players");
		if (!sys::directory_exists(filename)) {
			sys::mkdir(filename);
		}
		filename += '/';
		
		// second level directory
		filename.append(directory);
		if (!sys::directory_exists(filename)) {
			sys::mkdir(filename);
		}
		filename += '/';
		
		// guid.ini
		filename.append(guid);
		filename.append(".ini");
		
		std::ofstream ofs(filename.c_str());
		if (!ofs.is_open()) {
			con_warn << "Could not write " << filename << std::endl;
			return;
		}
		
		// save header
		ofs << "; " << guid << ".ini" << std::endl;
		ofs << "; Project::OSiRiON player data" << std::endl;
		ofs << std::endl;
		
		// save player data
		SaveGame::player_to_stream(player, ofs);

		// close output stream
		ofs.close();
	}
}
	
} // namespace game