/**
* The base chunk for doing multiplayer board games.
* @author Pinkfish
* @started Thu Dec 7 01:27:46 PST 2000
*/
private inherit "/std/basic/auto_load";
#define LOAD_TAG "multiplayer base"
class player_info {
int number;
string cap_name;
string name;
mixed data;
}
private mapping _players;
private string _winner;
private string* _ids_when_started;
private string _current_player;
private int _minimum_needed;
private int _maximum_allowed;
private int _can_join_as_new;
private int _game_started;
string* query_currently_playing_ids();
void delete_gone_players();
string find_player_id_of_person(object person);
void create() {
_players = ([ ]);
_ids_when_started = ({ });
} /* create() */
/**
* This method should be defined in the top level, it should also
* call this method to setup stuff as needed when a game starts.
*/
void reset_game() {
// renumber_players();
} /* reset_game() */
/**
* This method should be called when a game starts.
* @return 1 if started, 0 if not enough players
*/
int start_game() {
delete_gone_players();
if (sizeof(query_currently_playing_ids()) < _minimum_needed) {
return 0;
}
_winner = 0;
_game_started = 1;
_ids_when_started = query_currently_playing_ids();
//
// Pick one of these guys to start with.
//
_current_player = _ids_when_started[random(sizeof(_ids_when_started))];
return 1;
} /* start_game() */
/**
* This method is called when the game ends to set the winner.
* @param winner the winner
*/
void finish_game(string winner) {
_winner = winner;
_game_started = 0;
} /* finish_game() */
/**
* This method returns the current winner of the game.
* @return the winner of the game
*/
string query_winner() {
return _winner;
} /* query_winner() */
/**
* This method checks to see if the game has started.
* @return 1 if it has, 0 if it has not
*/
int is_game_started() {
return _game_started;
} /* is_game_started() */
/**
* This method checks to see if you can join as a new player, or only
* occupy the slots from the start of the game.
* @return 1 if you can join as new
*/
int can_join_as_new_player() {
return _can_join_as_new;
} /* can_join_as_new_player() */
/**
* This method sets the flag to allow you to make the game allow players
* to join after they have started the game.
* @param join 1 if they can join after the game has started
*/
void set_can_join_as_new_player(int join) {
_can_join_as_new = join;
} /* set_can_join_as_new_player() */
/**
* This method checks to see if the person is here.
* @param person the person to check
* @return 1 if they are, 0 if not
*/
int is_person_playing(string id) {
if (_players[id]->name) {
if (find_player(_players[id]->name) &&
is_in_me_or_environment(this_object(),
find_player(_players[id]->name)) &&
interactive(find_player(_players[id]->name))) {
return 1;
}
}
return 0;
} /* is_person_player() */
/**
* This method checks to see if the player is player.
* @return 1 if they are playing, 0 if not
*/
int is_playing(object ob) {
string id;
id = find_player_id_of_person(ob);
return id != 0;
} /* is_playing() */
/**
* This method checks to see if the object is the current player or not.
* @return 1 if they are, 0 if not
*/
int is_current_player(object ob) {
string id;
id = find_player_id_of_person(ob);
return id == _current_player;
} /* is_current_player() */
/**
* This method lets the person join the game.
* @param id the id of the person to add
* @param person the person to join
* @return 1 on success, 0 on failure
*/
int add_person_to_game(string id, object person) {
class player_info info;
if (is_person_playing(id)) {
return 0;
}
info = _players[id];
//info->player_num = _current_player_num++;
info->name = person->query_name();
info->cap_name = person->query_cap_name();
return 1;
} /* add_person_to_game() */
/**
* This method removes a person from the game.
* @param person the person to remove
* @return 1 on success, 0 on failure
*/
int remove_person_object_from_game(object person) {
class player_info info;
string id;
foreach (id, info in _players) {
if (find_player(info->name) == person) {
info->name = 0;
info->cap_name = 0;
return 1;
}
}
return 0;
} /* remove_person_object_from_game() */
/**
* This method removes a person from the game.
* @param id the id to remove
* @return 1 on success, 0 on failure
*/
int remove_person_id_from_game(string id) {
if (_players[id]->name) {
_players[id]->name = 0;
return 1;
}
return 0;
} /* remove_person_object_from_game() */
/**
* This method randomises the numbers of the players.
*/
void randomise_player_numbers() {
string* ids;
int num;
int pos;
ids = keys(_players);
num = 0;
while (sizeof(ids)) {
pos = random(sizeof(ids));
_players[ids[pos]]->number = num;
ids = ids[0..pos-1] + ids[pos+1..];
num++;
}
} /* randomise_player_nambers() */
/**
* This method adds a type of allowed player.
* @param id the id to be allowed
* @param number the start player number
*/
void add_player_id_type(string id, int number) {
_players[id] = new(class player_info);
_players[id]->number = number;
} /* add_player_id_type() */
/**
* This method removes a player id type.
* @param id the id to remove
*/
void remove_player_id_type(string id) {
map_delete(_players, id);
} /* remove_player_id_type() */
/**
* This method figures out the info structure from the number of the player.
* @param number the number to lookup
* @return the info structure, 0 if not found
*/
class player_info find_player_info_from_number(int num) {
class player_info info;
string id;
foreach (id, info in _players) {
if (info->number == num) {
return info;
}
}
return 0;
} /* find_player_info_from_number() */
/**
* This method figures out the id from the number of the player.
* @param number the number to lookup
* @return the id, 0 if not found
*/
string find_player_id_from_number(int num) {
class player_info info;
string id;
foreach (id, info in _players) {
if (info->number == num) {
return id;
}
}
return 0;
} /* find_player_info_from_number() */
/**
* This method returns all the ids for the players.
* @return the ids for the player
*/
string* query_player_ids() {
return keys(_players);
} /* query_player_ids() */
/**
* This method returns the list of ids who are current playing.
* @return the list of currently playing ids
*/
string* query_currently_playing_ids() {
return filter(keys(_players), (: is_person_playing($1) :));
} /* query_currently_playing_ids() */
/**
* This method returns the list of ids of people who started playing the
* game.
* @return the list of people who started playing the game
*/
string* query_started_player_ids() {
return _ids_when_started;
} /* query_started_player_ids() */
/**
* This method returns the players cap name from the player id.
* @param id the id to look up from
* @return the players cap name
*/
string query_player_cap_name(string id) {
if (_players[id] && _players[id]->name) {
return _players[id]->cap_name;
}
return "No one";
} /* query_player_cap_name() */
/**
* This method returns the player object from the player id.
* @param id the id for the player
* @return the player object
*/
object query_player_object(string id) {
return find_player(_players[id]->name);
} /* query_player_object() */
/**
* This method returns the players the short from the player id.
* @param id the id to look up from
* @return the players 'the short' string
*/
string query_player_the_short(string id) {
object ob;
if (_players[id]->name) {
ob = query_player_object(id);
if (ob) {
return ob->the_short();
}
}
return "No one";
} /* query_player_the_short() */
/**
* This method returns the players poss short from the player id.
* @param id the id to look up from
* @return the players 'poss short' string
*/
string query_player_poss_short(string id) {
object ob;
if (_players[id]->name) {
ob = query_player_object(id);
if (ob) {
return ob->poss_short();
}
}
return "No one";
} /* query_player_poss_short() */
/**
* This method nips through the list of players and checks to see if they
* are in the room or not. If they are not it deletes them from the array.
* Useful for start games or cleanups.
*/
void delete_gone_players() {
string id;
class player_info info;
foreach (id, info in _players) {
if (info->name) {
if (!find_player(info->name) ||
!is_in_me_or_environment(this_object(), find_player(info->name))) {
info->name = 0;
}
}
}
} /* delete_gone_players() */
/**
* This method sets the minimum number of players needed to play the
* game.
* @param minimum the minimum needed
*/
void set_minimum_needed(int minimun) {
_minimum_needed = minimun;
} /* set_minimum_needed() */
/**
* This method checks to see if the minimum requirements for starting the
* game have been met.
* @return 1 on success, 0 on failure
*/
int can_start_game() {
if (sizeof(query_currently_playing_ids()) > _minimum_needed) {
return 1;
}
return 0;
} /* can_start_game() */
/**
* This method finds the next player to the specified one.
* @param id the id of the player to find, 0 means current player
* @return the next id
*/
string find_next_player(string id) {
class player_info info;
int start;
int cur;
string new_id;
if (!id) {
id = _current_player;
}
start = _players[id]->number;
cur = start;
do {
cur = (cur + 1) % sizeof(_players);
new_id = find_player_id_from_number(cur);
info = _players[new_id];
} while (((_can_join_as_new && !info->name) ||
(!_can_join_as_new &&
member_array(new_id, _ids_when_started) == -1)) &&
cur != start);
return new_id;
} /* find_next_player() */
/**
* This method finds the previous player to the specified one.
* @param id the id of the player to find, 0 means current player
* @return the previous id
*/
string find_previous_player(string id) {
class player_info info;
int start;
string new_id;
int cur;
if (!id) {
id = _current_player;
}
start = _players[id]->number;
cur = start;
do {
cur = (cur - 1 + sizeof(_players)) % sizeof(_players);
new_id = find_player_id_from_number(cur);
info = _players[new_id];
} while (((_can_join_as_new && !info->name) ||
(!_can_join_as_new &&
member_array(new_id, _ids_when_started) == -1)) &&
cur != start);
return new_id;
} /* find_previous_player() */
/**
* This method increments the player number to the next available player.
*/
void increment_current_player() {
_current_player = find_next_player(_current_player);
} /* increment_current_player() */
/**
* This method finds the id of the person from their object.
* @param person the person to find
* @return the id of the person
*/
string find_player_id_of_person(object person) {
class player_info info;
string id;
foreach (id, info in _players) {
if (find_player(info->name) == person) {
return id;
}
}
return 0;
} /* find_id_of_person() */
/**
* This method finds the id of the current player.
* @return the id of the current player
*/
string query_current_player() {
if (!_current_player) {
_current_player = find_player_id_from_number(0);
}
return _current_player;
} /* query_current_player() */
/**
* This method finds the specified person and sets the current player
* number to them.
* @param person the person to find
* @return the id the of the person, 0 on failure
*/
string set_current_player(string id) {
_current_player = id;
} /* set_current_player() */
/**
* This method tells the specified player the message.
* @param id the id to tell the message to
* @param message the message to send
*/
void tell_player(string id, string message) {
object player;
player = find_player(_players[id]->name);
if (player && (environment(player) == environment() ||
player == environment())) {
tell_object(player, message);
}
} /* tell_player() */
/**
* THis method tells a message to the current player.
* @param message the message to send
*/
void tell_current_player(string message) {
tell_player(_current_player, message);
} /* tell_current_player() */
/**
* This method tells everyone playing the game something.
* @param message the message to send to everyone
* @param exclude the ids to optionaly exclude
*/
varargs void tell_all_players(string message, string* exclude) {
class player_info info;
string id;
if (!exclude) {
exclude = ({ });
}
foreach (id, info in _players) {
if (member_array(id, exclude) == -1) {
tell_player(id, message);
}
}
} /* tell_all_players() */
/**
* This method gets the extra data associated with the player id.
* @param id the id to look up the data for
* @return the extra data
*/
mixed query_player_data(string id) {
return _players[id]->data;
} /* query_player_data() */
/**
* This method sets the extra data associated with the player id.
* @param id the id to set the data for
* @param data the data to set
*/
void set_player_data(string id, mixed data) {
_players[id]->data = data;
} /* set_player_data() */
/**
* This method is called when an id joins the game. This method should
* be overridden in higher up objects to handle special events.
* @param id the id of the person joining
*/
void multiplayer_someone_joins(string id) {
} /* multiplayer_someone_joins() */
/**
* This method is called when an id resigns from the game. This method should
* be overridden in higher up objects to handle special events.
* @param id the id of the person joining
* @param name the name of the person leaving
*/
void multiplayer_someone_resigns(string id, string name) {
} /* multiplayer_someone_resigns() */
/**
* This method scrambles the array.
* @return the array shuffled
*/
mixed* shuffle_array(mixed *arr) {
int i;
int pos;
mixed* new_arr;
for (i = 0; i < 2; i++) {
new_arr = ({ });
while (sizeof(arr)) {
pos = random(sizeof(arr));
new_arr += arr[pos..pos];
arr = arr[0..pos - 1] + arr[pos + 1..];
}
arr = new_arr;
}
return arr;
} /* shuffle_array() */
/**
* This method is the one that does the joining.
* @param id the id they wish to join as
* @return 1 on success, 0 on failure
*/
int do_join(string id) {
if (is_person_playing(id)) {
add_failed_mess("Someone is already playing " + id + " on $D.\n");
return 0;
}
if (is_playing(this_player())) {
add_failed_mess("You are already playing on $D.\n");
return 0;
}
if (is_game_started() &&
!can_join_as_new_player() &&
member_array(id, query_started_player_ids()) == -1) {
add_failed_mess("You can only take over one of the spots vacated by "
"someone else.\n");
return 0;
}
if (add_person_to_game(id, this_player())) {
add_succeeded_mess("$N $V as " + id + " on $D.\n");
multiplayer_someone_joins(id);
return 1;
}
add_failed_mess("Some weird error joining game on $D.\n");
return 0;
} /* do_join() */
/**
* This method is the one that does the resignation from the game.
* @return 1 on success, 0 on failure
*/
int do_resign() {
string id;
id = find_player_id_of_person(this_player());
if (remove_person_object_from_game(this_player())) {
add_succeeded_mess("$N $V from the game on $D.\n");
multiplayer_someone_resigns(id, this_player()->query_cap_name());
if (id == query_current_player()) {
increment_current_player();
}
return 1;
}
add_failed_mess("You are not playing on $D to resign.\n");
return 0;
} /* do_resign() */
/** @ignore yes */
void init() {
string ids;
ids = implode(keys(_players), "|");
add_command("join", "[game] [as] {" + ids + "} on <direct:object>",
(: do_join($4[0]) :));
add_command("resign", "[from] [game] on <direct:object>",
(: do_resign() :));
} /* init() */
/** @ignore yes */
mapping query_dynamic_auto_load(mapping map) {
if (!map) {
return 0;
}
add_auto_load_value(map, LOAD_TAG, "players", _players);
add_auto_load_value(map, LOAD_TAG, "current player", _current_player);
add_auto_load_value(map, LOAD_TAG, "started ids", _ids_when_started);
add_auto_load_value(map, LOAD_TAG, "winner", _winner);
add_auto_load_value(map, LOAD_TAG, "game started", _game_started);
return map;
} /* query_dynamic_auto_load() */
/** @ignore yes */
void init_dynamic_arg(mapping map, object player) {
if (!map) {
return ;
}
_players = query_auto_load_value(map, LOAD_TAG, "players");
_current_player = query_auto_load_value(map, LOAD_TAG, "current player");
_ids_when_started = query_auto_load_value(map, LOAD_TAG, "started ids");
_winner = query_auto_load_value(map, LOAD_TAG, "winner");
_game_started = query_auto_load_value(map, LOAD_TAG, "game started");
} /* init_dynamic_arg() */