Files
commandergenius/project/jni/application/enigma/src/player.cpp
2010-10-13 17:30:44 +03:00

656 lines
18 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2002,2003,2004,2006 Daniel Heck
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "player.hh"
#include "Inventory.hh"
#include "display.hh"
#include "errors.hh"
#include "sound.hh"
#include "client.hh"
#include "server.hh"
#include "world.hh"
#include "main.hh"
#include "ecl_util.hh"
#include <algorithm>
#include <iostream>
#include <cassert>
using namespace std;
using namespace enigma;
using namespace world;
using world::Actor;
using enigma::Inventory;
namespace
{
class PlayerInfo {
public:
PlayerInfo();
string name;
Inventory inventory;
vector<Actor*> actors;
bool out_of_lives;
double dead_dtime; // number of seconds the player is already dead
bool inhibit_pickup;
};
typedef vector<PlayerInfo> PlayerList;
struct RespawnInfo {
RespawnInfo (Actor *a, double t)
: actor(a), time_left(t)
{}
Actor *actor; // The actor to respawn
double time_left; // Time left before respawning
};
struct LevelLocalData {
// Functions
LevelLocalData() {}
void respawn_dead_actors(double dtime);
void resurrect_actor (Actor *a);
bool remove_extralife (Actor *a);
// Variables
vector<RespawnInfo> respawn_list;
};
}
/* -------------------- PlayerInfo -------------------- */
PlayerInfo::PlayerInfo ()
: name(),
inventory(),
actors(),
out_of_lives(false),
dead_dtime(0),
inhibit_pickup(false)
{}
/* -------------------- LevelLocalData -------------------- */
void LevelLocalData::respawn_dead_actors(double dtime)
{
for (unsigned i=0; i<respawn_list.size(); ) {
RespawnInfo &info = respawn_list[i];
info.time_left -= dtime;
if (info.time_left < 0) {
// if (lua::CallFunc (lua::LevelState(), "Respawn", Value()) != 0) {
// throw enigma_levels::XLevelRuntime(string("Calling 'Respawn' failed:\n")+lua::LastError(lua::LevelState()));
info.actor->respawn();
respawn_list.erase(respawn_list.begin()+i);
continue; // don't increment i
}
++i;
}
}
void LevelLocalData::resurrect_actor (Actor *a)
{
const double RESPAWN_TIME = 1.5;
// a->find_respawnpos();
world::SendMessage(a, "resurrect");
remove_extralife(a);
respawn_list.push_back(RespawnInfo(a, RESPAWN_TIME));
}
bool LevelLocalData::remove_extralife (Actor *a)
{
Inventory *inv = player::GetInventory(a);
int idx = inv->find("it-extralife");
if (idx == -1) // no extralife found
return false;
else {
delete inv->yield_item(idx);
player::RedrawInventory (inv);
return true;
}
}
/* -------------------- Local variables -------------------- */
namespace
{
LevelLocalData leveldat;
PlayerList players(2); // this currently has always size 2
unsigned icurrent_player = 0;
std::vector<Actor *> unassignedActors;
}
/* -------------------- Functions -------------------- */
void player::NewGame (bool isRestart) {
int nplayers = 2; // Always prepare for two players
std::vector<int> extralives(2);
// calculate number of extralives
for (int i=0; i<nplayers; ++i) {
if (isRestart) {
// count existing number of extralives
int idxLife = -1;
extralives[i] = -1;
do {
++extralives[i];
idxLife = players[i].inventory.find("it-extralife", ++idxLife);
} while (idxLife != -1);
} else {
// new game provides 2 extralives
extralives[i] = 2;
}
}
players.clear();
players.resize(nplayers);
for (int i=0; i<nplayers; ++i) {
Inventory *inv = GetInventory(i);
for (int j = 0 ; j < extralives[i]; j++)
inv->add_item (MakeItem (world::it_extralife));
}
unassignedActors.clear();
}
void player::AddYinYang ()
{
for (unsigned i=0; i<players.size(); ++i) {
Inventory *inv = GetInventory (i);
if (inv->find ("it-yinyang") == -1)
inv->add_item (world::MakeItem (world::it_yinyang));
}
}
void player::LevelLoaded(bool isRestart)
{
if (server::TwoPlayerGame && server::SingleComputerGame)
AddYinYang();
RedrawInventory ();
}
void player::PrepareLevel()
{
// Clear up the inventories of all players: keep only extra lifes.
for (unsigned iplayer=0; iplayer<players.size(); ++iplayer)
{
Inventory *inv = GetInventory(iplayer);
int nextralifes=0;
for (size_t i=0; i<inv->size(); ++i)
if (get_id (inv->get_item(i)) == world::it_extralife)
nextralifes += 1;
inv->clear();
for (int i=0; i<nextralifes; ++i)
inv->add_item (world::MakeItem (world::it_extralife));
players[iplayer].actors.clear();
}
SetCurrentPlayer(0);
leveldat = LevelLocalData();
}
void player::LevelFinished()
{
for (unsigned i=0; i<players.size(); ++i) {
for (unsigned j=0; j<players[i].actors.size(); ++j) {
Actor *a = players[i].actors[j];
world::SendMessage(a, "disappear");
world::KillRubberBands (a);
}
}
}
Inventory * player::GetInventory (int iplayer)
{
return &players[iplayer].inventory;
}
Inventory * player::GetInventory (Actor *a)
{
if (const Value *v = a->get_attrib("player"))
return GetInventory(to_int(*v));
return 0;
}
bool player::WieldedItemIs (Actor *a, const string &kind)
{
if (Inventory *inv = GetInventory(a))
if (Item *it = inv->get_item(0))
return it->is_kind(kind);
return false;
}
int player::CurrentPlayer() {
return icurrent_player;
}
void player::SetCurrentPlayer(unsigned iplayer)
{
if (iplayer >= players.size())
Log << ecl::strf("SetCurrentPlayer: no such player %d\n", iplayer);
else {
icurrent_player = iplayer;
RedrawInventory (GetInventory(iplayer));
}
}
unsigned player::NumberOfRealPlayers() {
unsigned real_players = 0;
for (unsigned i=0; i<players.size(); ++i) {
if (!players[i].actors.empty()) {
++real_players;
}
}
return real_players;
}
/*! Sets respawn positions for black or white actors. */
void player::SetRespawnPositions(GridPos pos, bool black)
{
ecl::V2 center = pos.center();
for (unsigned i=0; i<players.size(); ++i) {
vector<Actor *> &al = players[i].actors;
for (unsigned j=0; j<al.size(); ++j) {
const Value *val = al[j]->get_attrib(black ? "blackball" : "whiteball");
if (val) {
al[j]->set_respawnpos(center);
}
}
}
}
/*! Remove respawn positions for black or white actors */
void player::RemoveRespawnPositions(bool black) {
for (unsigned i=0; i<players.size(); ++i) {
vector<Actor *> &al = players[i].actors;
for (unsigned j=0; j<al.size(); ++j) {
const Value *val = al[j]->get_attrib(black ? "blackball" : "whiteball");
if (val) {
al[j]->remove_respawnpos();
}
}
}
}
void player::Suicide()
{
for (unsigned i=0; i<players.size(); ++i) {
vector<Actor *> &al = players[i].actors;
for (unsigned j=0; j<al.size(); ++j) {
world::SendMessage(al[j], "suicide");
}
}
}
Actor *player::ReplaceActor (unsigned iplayer, Actor *old, Actor *a)
{
if (iplayer >= players.size())
server::RaiseError ("Invalid actor number");
vector<Actor *> &al = players[iplayer].actors;
for (unsigned i=0; i<al.size(); ++i) {
if (al[i] == old) {
al[i] = a;
return old;
}
}
return 0;
}
void player::AddActor (unsigned iplayer, Actor *a)
{
if (iplayer >= players.size())
server::RaiseError ("Invalid actor number");
world::ReleaseActor(a);
players[iplayer].actors.push_back(a);
if (players[iplayer].actors.size() == 1) {
// the ``main actor'' was set
client::Msg_PlayerPosition (iplayer, a->get_pos());
}
}
bool player::HasActor(unsigned iplayer, Actor *a) {
if (iplayer >= 0 && iplayer < players.size()) {
for (int i = 0; i < players[iplayer].actors.size(); i++) {
if (players[iplayer].actors[i] == a)
return true;
}
}
return false;
}
void player::SwapPlayers()
{
if (NumberOfRealPlayers() >= 2) {
SetCurrentPlayer(1-icurrent_player);
}
}
static bool has_extralive(unsigned pl, unsigned num) {
size_t idx = 0;
for (int i = 0; i < num; i++) {
int idxLife = players[pl].inventory.find("it-extralife", idx);
if (idxLife == -1)
return false;
else
idx = idxLife + 1;
}
return true;
}
static bool resurrect_actor (unsigned pl, Actor *a)
{
assert(server::ConserveLevel); // no resurrection otherwise!
bool has_life = has_extralive(pl, 1);
if (has_life)
leveldat.resurrect_actor(a); // = resurrect with delay
else
players[pl].out_of_lives = true;
return has_life;
}
void player::AddUnassignedActor (Actor *a) {
unassignedActors.push_back(a);
}
static void CheckDeadActors()
{
bool toggle_player = false;
const unsigned NO_PLAYER = UINT_MAX;
unsigned toggle_to_player = NO_PLAYER;
bool new_game = false; // complete restart (new lives)
// to live means to be not dead and to be able to move
for (int pl = -1; pl<(int)players.size(); ++pl) { // -1 are unassigned actors
vector<Actor*>& actors = (pl == -1) ? unassignedActors : players[pl].actors;
bool has_living_actor = false; // controllable and living
std::map<std::string, int> essMap;
std::map<std::string, int>::iterator itEss;
for (size_t i=0; i<actors.size(); ++i) {
Actor *a = actors[i];
std::string essId;
if (!a->string_attrib ("essential_id", &essId))
essId = a->get_traits().name;
int essential = a->int_attrib("essential");
// count number of necessary actors per kind
if (essential == 1)
--essMap[essId];
if (!a->is_dead() ||
(pl >= 0 && server::ConserveLevel && resurrect_actor(pl, a))) {
// actor is still alive
if (pl >= 0 && a->controlled_by(pl) && a->get_mouseforce() != 0.0) {
has_living_actor = true;
}
// count number of alive actors per kind
if (essential >= 0)
++essMap[essId];
}
else {
// player is dead and could not resurrect
if (essential == -1) {
// actor is personnally essential but dead
new_game = true;
}
}
}
// check if for any kind we have less living actors as required
for (itEss = essMap.begin(); itEss != essMap.end(); itEss++) {
if ((*itEss).second < 0)
new_game = true;
}
if (has_living_actor) {
if (toggle_to_player == NO_PLAYER)
toggle_to_player = pl;
}
else {
if (pl == icurrent_player)
// check if player has yinyang for single gamer mode
if (player::GetInventory(pl)->find("it-yinyang",0) >= 0)
toggle_player = true;
else
new_game = true;
}
}
// if no_living_player -> toggle_player is true and toggle_to_player is NO_PLAYER
// => new_game is set to true below
if ((server::ConserveLevel == false) && !server::IsRestartingLevel() &&
(new_game || (toggle_player && toggle_to_player == NO_PLAYER))) {
// check if we have enough extralives for a restart instead of new game
std::vector<int> numDead;
bool reset_level = true;
for (unsigned pl = 0; pl<players.size(); ++pl) {
numDead.push_back(0);
vector<Actor*>& actors = players[pl].actors;
for (size_t i = 0; i<actors.size(); ++i) {
Actor *a = actors[i];
if (a->is_dead()) {
numDead[pl]++;
}
}
if (!has_extralive(pl, numDead[pl])) {
reset_level = false;
break;
}
}
if (reset_level) {
for (unsigned pl = 0; pl<players.size(); ++pl) {
Inventory *inv = player::GetInventory(pl);
for (int i=0; i<numDead[pl]; i++) {
int idx = inv->find("it-extralife");
ASSERT (idx != -1, XLevelRuntime, "Missing extralife for restart of level");
delete inv->yield_item(idx);
}
}
server::RestartLevel(); // should restart w/o scrolling
return;
}
}
if (new_game) {
server::Msg_RestartGame();
}
else if (toggle_player) {
if (toggle_to_player == NO_PLAYER)
server::Msg_RestartGame();
else
player::SetCurrentPlayer(toggle_to_player);
}
}
Actor *player::GetMainActor (unsigned iplayer)
{
vector<Actor *> &actors = players[iplayer].actors;
return actors.empty() ? 0 : actors[0];
}
void player::Tick(double dtime)
{
STATUSBAR->set_counter (server::GetMoveCounter());
// Tell clients about position of main actor for stereo sound and
// screen position
for (unsigned iplayer = 0; iplayer < players.size(); ++iplayer) {
if (Actor *ac = GetMainActor(iplayer))
client::Msg_PlayerPosition (iplayer, ac->get_pos());
}
// Respawn actors that have been dead for a certain amount of time
leveldat.respawn_dead_actors(dtime);
// Update the respawn list or restart the game when all actors are
// dead and no extra lifes are left.
CheckDeadActors();
}
void player::InhibitPickup(bool flag) {
players[icurrent_player].inhibit_pickup = flag;
}
/*! Return pointer to inventory if actor may pick up items, 0
otherwise. */
Inventory *player::MayPickup(Actor *a, Item *it)
{
int iplayer=-1;
a->int_attrib("player", &iplayer);
if (iplayer < 0 || (unsigned)iplayer >= players.size()) {
// cerr << "PickupItem: illegal 'player' entry\n";
return 0;
}
Inventory *inv = GetInventory(iplayer);
bool dont_pickup = players[iplayer].inhibit_pickup
|| a->is_flying()
|| !inv->willAddItem(it)
|| a->is_dead();
return dont_pickup ? 0 : inv;
}
void player::PickupItem (Actor *a, GridPos p)
{
if (Inventory *inv = MayPickup(a, GetField(p)->item)) {
if (Item *item = world::YieldItem(p)) {
item->on_pickup(a);
inv->add_item(item);
RedrawInventory (inv);
sound::EmitSoundEvent ("pickup", p.center());
}
}
}
void player::PickupStoneAsItem (Actor *a, enigma::GridPos p)
{
if (Inventory *inv = MayPickup(a, GetField(p)->item))
{
if (world::Stone *stone = world::YieldStone(p))
{
string kind = stone->get_kind();
if (kind[0] == 's')
kind[0] = 'i';
if (Item *item = world::MakeItem(kind.c_str())) {
KillRubberBands(stone);
world::DisposeObject (stone);
inv->add_item(item);
player::RedrawInventory(inv);
sound::EmitSoundEvent ("pickup", p.center());
}
}
}
}
void player::ActivateFirstItem()
{
Inventory &inv = players[icurrent_player].inventory;
if (inv.size() > 0) {
Item *it = inv.get_item (0);
world::Actor *ac = 0;
GridPos p;
bool can_drop_item = false;
if (!players[icurrent_player].actors.empty()) {
ac = players[icurrent_player].actors[0];
p = GridPos(ac->get_pos());
can_drop_item = ac->can_drop_items();
}
switch (it->activate(ac, p)) {
case world::ITEM_DROP:
// only drop if no item underneath and actor allows it
if (it->can_drop_at(p) && can_drop_item) {
it = inv.yield_first ();
RedrawInventory (&inv);
it->drop(ac, p);
}
break;
case world::ITEM_KILL:
DisposeObject (inv.yield_first ());
RedrawInventory (&inv);
break;
case world::ITEM_KEEP:
break;
}
}
}
void player::RotateInventory(int dir)
{
sound::EmitSoundEvent ("invrotate", ecl::V2());
Inventory &inv = players[icurrent_player].inventory;
if (dir == 1)
inv.rotate_left ();
else
inv.rotate_right ();
RedrawInventory (&inv);
}
/** Update the specified inventory on the screen, provided it is the
inventory of the current player. For all other inventories, this
function does nothing. */
void player::RedrawInventory (Inventory *inv)
{
if (inv == GetInventory (CurrentPlayer()))
RedrawInventory();
}
void player::RedrawInventory()
{
Inventory *inv = GetInventory (CurrentPlayer());
std::vector<std::string> modelnames;
for (size_t i=0; i<inv->size(); ++i) {
world::Item *it = inv->get_item(i);
modelnames.push_back(it->get_inventory_model());
}
STATUSBAR->set_inventory (modelnames);
}