1028 lines
23 KiB
C
1028 lines
23 KiB
C
|
/*
|
||
|
* Wumpus - 0.2.0
|
||
|
* Copyright (c) 2006, 2011 William Pitcock <nenolod -at- nenolod.net>
|
||
|
* Portions copyright (c) 2006 Kiyoshi Aman <kiyoshi.aman -at- gmail.com>
|
||
|
*
|
||
|
* Rights to this code are as documented in doc/LICENSE.
|
||
|
*
|
||
|
* Hunt the Wumpus game implementation.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "atheme.h"
|
||
|
|
||
|
DECLARE_MODULE_V1
|
||
|
(
|
||
|
"contrib/wumpus", false, _modinit, _moddeinit,
|
||
|
PACKAGE_STRING,
|
||
|
"William Pitcock <nenolod -at- nenolod.net>"
|
||
|
);
|
||
|
|
||
|
/* contents */
|
||
|
typedef enum {
|
||
|
E_NOTHING = 0,
|
||
|
E_WUMPUS,
|
||
|
E_PIT,
|
||
|
E_BATS,
|
||
|
E_ARROWS,
|
||
|
E_CRYSTALBALL
|
||
|
} contents_t;
|
||
|
|
||
|
/* room_t: Describes a room that the wumpus or players could be in. */
|
||
|
struct room_ {
|
||
|
int id; /* room 3 or whatever */
|
||
|
mowgli_list_t exits; /* old int count == exits.count */
|
||
|
contents_t contents;
|
||
|
mowgli_list_t players; /* player_t players */
|
||
|
};
|
||
|
|
||
|
typedef struct room_ room_t;
|
||
|
|
||
|
/* player_t: A player object. */
|
||
|
struct player_ {
|
||
|
user_t *u;
|
||
|
room_t *location;
|
||
|
int arrows;
|
||
|
int hp;
|
||
|
bool has_moved;
|
||
|
};
|
||
|
|
||
|
typedef struct player_ player_t;
|
||
|
|
||
|
struct game_ {
|
||
|
int wumpus;
|
||
|
int mazesize;
|
||
|
mowgli_list_t players;
|
||
|
bool running;
|
||
|
bool starting;
|
||
|
|
||
|
room_t *rmemctx; /* memory page context */
|
||
|
service_t *svs;
|
||
|
int wump_hp;
|
||
|
int speed;
|
||
|
|
||
|
unsigned int wantsize;
|
||
|
|
||
|
mowgli_eventloop_timer_t *move_timer;
|
||
|
mowgli_eventloop_timer_t *start_game_timer;
|
||
|
};
|
||
|
|
||
|
typedef struct game_ game_t;
|
||
|
|
||
|
game_t wumpus;
|
||
|
|
||
|
struct __wumpusconfig
|
||
|
{
|
||
|
char *chan;
|
||
|
char *nick;
|
||
|
char *user;
|
||
|
char *host;
|
||
|
char *real;
|
||
|
} wumpus_cfg = {
|
||
|
"#wumpus",
|
||
|
"Wumpus",
|
||
|
"wumpus",
|
||
|
"services.int",
|
||
|
"Hunt the Wumpus"
|
||
|
};
|
||
|
|
||
|
/* ------------------------------ utility functions */
|
||
|
|
||
|
/* returns 1 or 2 depending on if the wumpus is 1 or 2 rooms away */
|
||
|
static int
|
||
|
distance_to_wumpus(player_t *player)
|
||
|
{
|
||
|
mowgli_node_t *n, *tn;
|
||
|
|
||
|
MOWGLI_ITER_FOREACH(n, player->location->exits.head)
|
||
|
{
|
||
|
room_t *r = (room_t *) n->data;
|
||
|
|
||
|
if (r->contents == E_WUMPUS)
|
||
|
return 1;
|
||
|
|
||
|
MOWGLI_ITER_FOREACH(tn, r->exits.head)
|
||
|
{
|
||
|
room_t *r2 = (room_t *) tn->data;
|
||
|
|
||
|
if (r2->contents == E_WUMPUS)
|
||
|
return 2;
|
||
|
|
||
|
/* we don't evaluate exitpoints at this depth */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* can we move or perform an action on this room? */
|
||
|
static bool
|
||
|
adjacent_room(player_t *p, int id)
|
||
|
{
|
||
|
mowgli_node_t *n;
|
||
|
|
||
|
MOWGLI_ITER_FOREACH(n, p->location->exits.head)
|
||
|
{
|
||
|
room_t *r = (room_t *) n->data;
|
||
|
|
||
|
if (r->id == id)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* finds a player in the list */
|
||
|
static player_t *
|
||
|
find_player(user_t *u)
|
||
|
{
|
||
|
mowgli_node_t *n;
|
||
|
|
||
|
MOWGLI_ITER_FOREACH(n, wumpus.players.head)
|
||
|
{
|
||
|
player_t *p = n->data;
|
||
|
|
||
|
if (p->u == u)
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* adds a player to the game */
|
||
|
static player_t *
|
||
|
create_player(user_t *u)
|
||
|
{
|
||
|
player_t *p;
|
||
|
|
||
|
if (find_player(u))
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, u->nick, "You are already playing the game!");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (wumpus.running)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, u->nick, "The game is already in progress. Sorry!");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
p = smalloc(sizeof(player_t));
|
||
|
memset(p, '\0', sizeof(player_t));
|
||
|
|
||
|
p->u = u;
|
||
|
p->arrows = 10;
|
||
|
p->hp = 30;
|
||
|
|
||
|
mowgli_node_add(p, mowgli_node_create(), &wumpus.players);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
/* destroys a player object and removes them from the game */
|
||
|
static void
|
||
|
resign_player(player_t *player)
|
||
|
{
|
||
|
mowgli_node_t *n;
|
||
|
|
||
|
if (player == NULL)
|
||
|
return;
|
||
|
|
||
|
if (player->location)
|
||
|
{
|
||
|
n = mowgli_node_find(player, &player->location->players);
|
||
|
mowgli_node_delete(n, &player->location->players);
|
||
|
mowgli_node_free(n);
|
||
|
}
|
||
|
|
||
|
n = mowgli_node_find(player, &wumpus.players);
|
||
|
mowgli_node_delete(n, &wumpus.players);
|
||
|
mowgli_node_free(n);
|
||
|
|
||
|
free(player);
|
||
|
}
|
||
|
|
||
|
/* ------------------------------ game functions */
|
||
|
|
||
|
/* builds the maze, and returns false if the maze is too small */
|
||
|
static bool
|
||
|
build_maze(unsigned int size)
|
||
|
{
|
||
|
unsigned int i, j;
|
||
|
room_t *w;
|
||
|
|
||
|
if (size < 10)
|
||
|
return false;
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: building maze of %d chambers", size);
|
||
|
|
||
|
/* allocate rooms */
|
||
|
wumpus.mazesize = size;
|
||
|
wumpus.rmemctx = scalloc(size, sizeof(room_t));
|
||
|
|
||
|
for (i = 0; i < size; i++)
|
||
|
{
|
||
|
room_t *r = &wumpus.rmemctx[i];
|
||
|
memset(r, '\0', sizeof(room_t));
|
||
|
|
||
|
r->id = i;
|
||
|
|
||
|
/* rooms have 3 exit points, exits are one-way */
|
||
|
for (j = 0; j < 3 && r->exits.count < 3; j++)
|
||
|
{
|
||
|
int t = rand() % size;
|
||
|
|
||
|
/* make sure this isn't a tunnel to itself */
|
||
|
while (t == r->id)
|
||
|
{
|
||
|
mowgli_node_t *rn;
|
||
|
t = rand() % size;
|
||
|
|
||
|
/* also check that this path doesn't already exist. */
|
||
|
MOWGLI_ITER_FOREACH(rn, r->exits.head)
|
||
|
{
|
||
|
room_t *rm = (room_t *) rn->data;
|
||
|
|
||
|
if (rm->id == t)
|
||
|
t = r->id;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: creating link for route %d -> %d", i, t);
|
||
|
mowgli_node_add(&wumpus.rmemctx[t], mowgli_node_create(), &r->exits);
|
||
|
}
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: finished creating exit paths for chamber %d", i);
|
||
|
}
|
||
|
|
||
|
/* place the wumpus in the maze */
|
||
|
wumpus.wumpus = rand() % size;
|
||
|
w = &wumpus.rmemctx[wumpus.wumpus];
|
||
|
w->contents = E_WUMPUS;
|
||
|
|
||
|
/* pits */
|
||
|
for (j = 0; j < size; j++)
|
||
|
{
|
||
|
/* 42 will do very nicely */
|
||
|
if (rand() % (42 * 2) == 0)
|
||
|
{
|
||
|
room_t *r = &wumpus.rmemctx[j];
|
||
|
|
||
|
r->contents = E_PIT;
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: added pit to chamber %d", j);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* bats */
|
||
|
for (i = 0; i < 2; i++)
|
||
|
{
|
||
|
for (j = 0; j < size; j++)
|
||
|
{
|
||
|
/* 42 will do very nicely */
|
||
|
if (rand() % 42 == 0)
|
||
|
{
|
||
|
room_t *r = &wumpus.rmemctx[j];
|
||
|
|
||
|
r->contents = E_BATS;
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: added bats to chamber %d", j);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* arrows */
|
||
|
for (i = 0; i < 3; i++)
|
||
|
{
|
||
|
for (j = 0; j < size; j++)
|
||
|
{
|
||
|
/* 42 will do very nicely */
|
||
|
if (rand() % 42 == 0)
|
||
|
{
|
||
|
room_t *r = &wumpus.rmemctx[j];
|
||
|
|
||
|
r->contents = E_ARROWS;
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: added arrows to chamber %d", j);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* find a place to put the crystal ball */
|
||
|
w = &wumpus.rmemctx[rand() % size];
|
||
|
w->contents = E_CRYSTALBALL;
|
||
|
slog(LG_DEBUG, "wumpus: added crystal ball to chamber %d", w->id);
|
||
|
|
||
|
/* ok, do some sanity checking */
|
||
|
for (j = 0; j < size; j++)
|
||
|
if (wumpus.rmemctx[j].exits.count < 3)
|
||
|
{
|
||
|
slog(LG_DEBUG, "wumpus: sanity checking failed");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: built maze");
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* init_game depends on these */
|
||
|
static void move_wumpus(void *unused);
|
||
|
static void look_player(player_t *p);
|
||
|
static void end_game(void);
|
||
|
|
||
|
/* sets the game up */
|
||
|
static void
|
||
|
init_game(unsigned int size)
|
||
|
{
|
||
|
mowgli_node_t *n;
|
||
|
|
||
|
if (!build_maze(size))
|
||
|
{
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "Maze generation failed, please try again.");
|
||
|
end_game();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* place players in random positions */
|
||
|
MOWGLI_ITER_FOREACH(n, wumpus.players.head)
|
||
|
{
|
||
|
player_t *p = (player_t *) n->data;
|
||
|
|
||
|
p->location = &wumpus.rmemctx[rand() % wumpus.mazesize];
|
||
|
mowgli_node_add(p, mowgli_node_create(), &p->location->players);
|
||
|
|
||
|
look_player(p);
|
||
|
}
|
||
|
|
||
|
/* timer initialization */
|
||
|
wumpus.move_timer = mowgli_timer_add(base_eventloop, "move_wumpus", move_wumpus, NULL, 60);
|
||
|
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "The game has started!");
|
||
|
|
||
|
wumpus.running = true;
|
||
|
wumpus.speed = 60;
|
||
|
wumpus.wump_hp = 70;
|
||
|
|
||
|
wumpus.start_game_timer = NULL;
|
||
|
}
|
||
|
|
||
|
/* starts the game */
|
||
|
static void
|
||
|
start_game(void *unused)
|
||
|
{
|
||
|
wumpus.starting = false;
|
||
|
|
||
|
if (wumpus.players.count < 2)
|
||
|
{
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "Not enough players to play. :(");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (wumpus.wantsize >= 300)
|
||
|
wumpus.wantsize = 300;
|
||
|
|
||
|
init_game(wumpus.wantsize);
|
||
|
}
|
||
|
|
||
|
/* destroys game objects */
|
||
|
static void
|
||
|
end_game(void)
|
||
|
{
|
||
|
mowgli_node_t *n, *tn;
|
||
|
int i;
|
||
|
|
||
|
/* destroy players */
|
||
|
MOWGLI_ITER_FOREACH_SAFE(n, tn, wumpus.players.head)
|
||
|
resign_player((player_t *) n->data);
|
||
|
|
||
|
/* free memory vector */
|
||
|
if (wumpus.rmemctx)
|
||
|
{
|
||
|
/* destroy links between rooms */
|
||
|
for (i = 0; i < wumpus.mazesize; i++)
|
||
|
{
|
||
|
room_t *r = &wumpus.rmemctx[i];
|
||
|
|
||
|
MOWGLI_ITER_FOREACH_SAFE(n, tn, r->exits.head)
|
||
|
mowgli_node_delete(n, &r->exits);
|
||
|
}
|
||
|
free(wumpus.rmemctx);
|
||
|
wumpus.rmemctx = NULL;
|
||
|
}
|
||
|
|
||
|
wumpus.wumpus = -1;
|
||
|
wumpus.running = false;
|
||
|
|
||
|
mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
|
||
|
wumpus.move_timer = NULL;
|
||
|
|
||
|
/* game is now ended */
|
||
|
}
|
||
|
|
||
|
/* gives the player information about their surroundings */
|
||
|
static void
|
||
|
look_player(player_t *p)
|
||
|
{
|
||
|
mowgli_node_t *n;
|
||
|
|
||
|
return_if_fail(p != NULL);
|
||
|
return_if_fail(p->location != NULL);
|
||
|
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You are in room %d.", p->location->id);
|
||
|
|
||
|
MOWGLI_ITER_FOREACH(n, p->location->exits.head)
|
||
|
{
|
||
|
room_t *r = (room_t *) n->data;
|
||
|
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You can move to room %d.", r->id);
|
||
|
}
|
||
|
|
||
|
if (distance_to_wumpus(p))
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You smell a wumpus!");
|
||
|
|
||
|
/* provide warnings */
|
||
|
MOWGLI_ITER_FOREACH(n, p->location->exits.head)
|
||
|
{
|
||
|
room_t *r = (room_t *) n->data;
|
||
|
|
||
|
if (r->contents == E_WUMPUS)
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You smell a wumpus!");
|
||
|
if (r->contents == E_PIT)
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You feel a draft!");
|
||
|
if (r->contents == E_BATS)
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You hear bats!");
|
||
|
if (r->players.count > 0)
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You smell humans!");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* shoot and kill other players */
|
||
|
static void
|
||
|
shoot_player(player_t *p, int target_id)
|
||
|
{
|
||
|
room_t *r;
|
||
|
player_t *tp;
|
||
|
/* chance to hit; moved up here for convenience. */
|
||
|
int hit = rand() % 3;
|
||
|
|
||
|
if (!p->arrows)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You have no arrows!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (adjacent_room(p, target_id) == false)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You can't shoot into room %d from here.", target_id);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (p->location->id == target_id)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You can only shoot into adjacent rooms!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
r = &wumpus.rmemctx[target_id];
|
||
|
tp = r->players.head ? r->players.head->data : NULL;
|
||
|
|
||
|
p->arrows--;
|
||
|
|
||
|
if ((!tp) && (r->contents != E_WUMPUS))
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You shoot at nothing.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (tp)
|
||
|
{
|
||
|
if ((hit < 2) && (tp->hp <= 10))
|
||
|
{
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has been killed by \2%s\2!",
|
||
|
tp->u->nick, p->u->nick);
|
||
|
resign_player(tp);
|
||
|
}
|
||
|
else if ((tp->hp > 0) && (hit < 2)) {
|
||
|
notice(wumpus_cfg.nick, tp->u->nick,
|
||
|
"You were hit by an arrow from room %d.",p->location->id);
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You hit something.");
|
||
|
tp->hp -= 10;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, tp->u->nick, "You have been shot at from room %d.",
|
||
|
p->location->id);
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You miss what you were shooting at.");
|
||
|
}
|
||
|
}
|
||
|
else if (r->contents == E_WUMPUS) /* Shootin' at the wumpus, we are... */
|
||
|
{
|
||
|
if (((wumpus.wump_hp > 0) && wumpus.wump_hp <= 5) && (hit < 2))
|
||
|
/* we killed the wumpus */
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You have killed the wumpus!");
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "The wumpus was killed by \2%s\2.",
|
||
|
p->u->nick);
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan,
|
||
|
"%s has won the game! Congratulations!", p->u->nick);
|
||
|
end_game();
|
||
|
}
|
||
|
else if ((wumpus.wump_hp > 5) && (hit < 2))
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick,
|
||
|
"You shoot the Wumpus, but he shrugs it off and seems angrier!");
|
||
|
|
||
|
wumpus.wump_hp -= 5;
|
||
|
wumpus.speed -= 3;
|
||
|
|
||
|
move_wumpus(NULL);
|
||
|
|
||
|
mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
|
||
|
wumpus.move_timer = mowgli_timer_add(base_eventloop, "move_wumpus", move_wumpus, NULL, wumpus.speed);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You miss what you were shooting at.");
|
||
|
move_wumpus(NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* move_wumpus depends on this */
|
||
|
static void regen_obj(contents_t);
|
||
|
|
||
|
/* check for last-man-standing win condition. */
|
||
|
static void
|
||
|
check_last_person_alive(void)
|
||
|
{
|
||
|
if (wumpus.players.count == 1)
|
||
|
{
|
||
|
player_t *p = (player_t *) wumpus.players.head->data;
|
||
|
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "%s won the game! Congratulations!", p->u->nick);
|
||
|
|
||
|
end_game();
|
||
|
}
|
||
|
else if (wumpus.players.count == 0)
|
||
|
{
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "Everyone lost. Sucks. :(");
|
||
|
end_game();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* move the wumpus, the wumpus moves every 60 seconds */
|
||
|
static void
|
||
|
move_wumpus(void *unused)
|
||
|
{
|
||
|
mowgli_node_t *n, *tn;
|
||
|
room_t *r, *tr;
|
||
|
int w_kills = 0;
|
||
|
|
||
|
/* can we do any of this? if this is null, we really shouldn't be here */
|
||
|
if (wumpus.rmemctx == NULL)
|
||
|
{
|
||
|
slog(LG_DEBUG, "wumpus: move_wumpus() called while game not running!");
|
||
|
mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear footsteps...");
|
||
|
|
||
|
/* start moving */
|
||
|
r = &wumpus.rmemctx[wumpus.wumpus]; /* memslice describing the wumpus's current location */
|
||
|
|
||
|
regen_obj(r->contents);
|
||
|
r->contents = E_NOTHING;
|
||
|
|
||
|
tr = mowgli_node_nth_data(&r->exits, rand() % MOWGLI_LIST_LENGTH(&r->exits));
|
||
|
|
||
|
#ifdef DEBUG_AI
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "I moved to chamber %d", tr->id);
|
||
|
#endif
|
||
|
|
||
|
slog(LG_DEBUG, "wumpus: the wumpus is now in room %d! (was in %d)",
|
||
|
tr->id, wumpus.wumpus);
|
||
|
wumpus.wumpus = tr->id;
|
||
|
tr->contents = E_WUMPUS;
|
||
|
|
||
|
#ifdef DEBUG_AI
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "On my next turn, I can move to:");
|
||
|
r = &wumpus.rmemctx[wumpus.wumpus];
|
||
|
|
||
|
MOWGLI_ITER_FOREACH(n, r->exits.head)
|
||
|
{
|
||
|
room_t *tr = (room_t *) n->data;
|
||
|
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "- %d", tr->id);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
MOWGLI_ITER_FOREACH_SAFE(n, tn, wumpus.players.head)
|
||
|
{
|
||
|
player_t *p = (player_t *) n->data;
|
||
|
|
||
|
if (wumpus.wumpus == p->location->id)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "The wumpus has joined your room and eaten you. Sorry.");
|
||
|
w_kills++;
|
||
|
|
||
|
/* player_t *p has been eaten and is no longer in the game */
|
||
|
resign_player(p);
|
||
|
}
|
||
|
|
||
|
/* prepare for the next turn */
|
||
|
p->has_moved = false;
|
||
|
}
|
||
|
|
||
|
/* report any wumpus kills */
|
||
|
if (w_kills)
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear the screams of %d surprised adventurer%s.", w_kills,
|
||
|
w_kills != 1 ? "s" : "");
|
||
|
|
||
|
check_last_person_alive();
|
||
|
}
|
||
|
|
||
|
/* regenerates objects */
|
||
|
static void
|
||
|
regen_obj(contents_t obj)
|
||
|
{
|
||
|
wumpus.rmemctx[rand() % wumpus.mazesize].contents = obj;
|
||
|
}
|
||
|
|
||
|
/* handles movement requests from players */
|
||
|
static void
|
||
|
move_player(player_t *p, int id)
|
||
|
{
|
||
|
mowgli_node_t *n;
|
||
|
|
||
|
if (adjacent_room(p, id) == false)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "Sorry, you cannot get to room %d from here.", id);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* What about bats? We check for this first because yeah... */
|
||
|
if (wumpus.rmemctx[id].contents == E_BATS)
|
||
|
{
|
||
|
int target_id = rand() % wumpus.mazesize;
|
||
|
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "Bats have picked you up and taken you to room %d.",
|
||
|
target_id);
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear a surprised yell.");
|
||
|
|
||
|
/* move the bats */
|
||
|
wumpus.rmemctx[id].contents = E_NOTHING;
|
||
|
wumpus.rmemctx[target_id].contents = E_BATS;
|
||
|
|
||
|
id = target_id;
|
||
|
|
||
|
/* and fall through, sucks if you hit the two conditions below :-P */
|
||
|
}
|
||
|
|
||
|
/* Is the wumpus in here? */
|
||
|
if (wumpus.wumpus == id)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You see the wumpus approaching you. You scream for help, but it is too late.");
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear a blood-curdling scream.");
|
||
|
|
||
|
/* player_t *p has been killed by the wumpus, remove him from the game */
|
||
|
resign_player(p);
|
||
|
check_last_person_alive();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* What about a pit? */
|
||
|
if (wumpus.rmemctx[id].contents == E_PIT)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You have fallen into a bottomless pit. Sorry.");
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear a faint wail, which gets fainter and fainter.");
|
||
|
|
||
|
/* player_t *p has fallen down a hole, remove him from the game */
|
||
|
resign_player(p);
|
||
|
check_last_person_alive();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* and arrows? */
|
||
|
if (wumpus.rmemctx[id].contents == E_ARROWS)
|
||
|
{
|
||
|
if (p->arrows == 0)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You found some arrows. You pick them up and continue on your way.");
|
||
|
p->arrows += 5;
|
||
|
}
|
||
|
else
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You found some arrows. You don't have any room to take them however, "
|
||
|
"so you break them in half and continue on your way.");
|
||
|
|
||
|
wumpus.rmemctx[id].contents = E_NOTHING;
|
||
|
|
||
|
regen_obj(E_ARROWS);
|
||
|
}
|
||
|
|
||
|
/* crystal ball */
|
||
|
if (wumpus.rmemctx[id].contents == E_CRYSTALBALL)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You find a strange pulsating crystal ball. You examine it, and it shows room %d with the wumpus in it.",
|
||
|
wumpus.wumpus);
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "The crystal ball then vanishes into the miasma.");
|
||
|
|
||
|
wumpus.rmemctx[id].contents = E_NOTHING;
|
||
|
wumpus.rmemctx[rand() % wumpus.mazesize].contents = E_CRYSTALBALL;
|
||
|
}
|
||
|
|
||
|
/* we recycle the mowgli_node_t here for speed */
|
||
|
n = mowgli_node_find(p, &p->location->players);
|
||
|
mowgli_node_delete(n, &p->location->players);
|
||
|
mowgli_node_free(n);
|
||
|
|
||
|
p->location = &wumpus.rmemctx[id];
|
||
|
mowgli_node_add(p, mowgli_node_create(), &p->location->players);
|
||
|
|
||
|
/* provide player with information, including their new location */
|
||
|
look_player(p);
|
||
|
|
||
|
/* tell players about joins. */
|
||
|
if (p->location->players.count > 1)
|
||
|
{
|
||
|
MOWGLI_ITER_FOREACH(n, p->location->players.head)
|
||
|
{
|
||
|
if (n->data != p)
|
||
|
{
|
||
|
player_t *tp = (player_t *) n->data;
|
||
|
|
||
|
notice(wumpus_cfg.nick, tp->u->nick, "%s has joined room %d with you.",
|
||
|
p->u->nick, id);
|
||
|
notice(wumpus_cfg.nick, p->u->nick, "You see %s!",
|
||
|
tp->u->nick);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ------------------------------ -*-atheme-*- code */
|
||
|
|
||
|
static void cmd_start(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
if (wumpus.running || wumpus.starting)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "A game is already in progress. Sorry.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has started the game! Use \2/msg Wumpus JOIN\2 to play! You have\2 60 seconds\2.",
|
||
|
si->su->nick);
|
||
|
|
||
|
wumpus.starting = true;
|
||
|
wumpus.wantsize = 100;
|
||
|
|
||
|
if (parv[0])
|
||
|
wumpus.wantsize = atoi(parv[0]);
|
||
|
|
||
|
wumpus.start_game_timer = mowgli_timer_add_once(base_eventloop, "start_game", start_game, NULL, 60);
|
||
|
}
|
||
|
|
||
|
/* reference tuple for the above code: cmd_start */
|
||
|
command_t wumpus_start = { "START", "Starts the game.", AC_NONE, 1, cmd_start, { .path = "" } };
|
||
|
|
||
|
static void cmd_join(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
player_t *p;
|
||
|
|
||
|
if (!wumpus.starting || wumpus.running)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You cannot use this command right now. Sorry.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
p = create_player(si->su);
|
||
|
|
||
|
if (p)
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has joined the game!", si->su->nick);
|
||
|
}
|
||
|
|
||
|
command_t wumpus_join = { "JOIN", "Joins the game.", AC_NONE, 0, cmd_join, { .path = "" } };
|
||
|
|
||
|
static void cmd_look(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
player_t *p = find_player(si->su);
|
||
|
|
||
|
if (p == NULL)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!wumpus.running)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You cannot use this command right now. Sorry.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
look_player(p);
|
||
|
}
|
||
|
|
||
|
command_t wumpus_look = { "LOOK", "View surroundings.", AC_NONE, 0, cmd_look, { .path = "" } };
|
||
|
|
||
|
static void cmd_move(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
player_t *p = find_player(si->su);
|
||
|
char *id = parv[0];
|
||
|
|
||
|
if (!p)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!id)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You must provide a room to move to.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!wumpus.running)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "The game must be running in order to use this command.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
move_player(p, atoi(id));
|
||
|
}
|
||
|
|
||
|
command_t wumpus_move = { "MOVE", "Move to another room.", AC_NONE, 1, cmd_move, { .path = "" } };
|
||
|
|
||
|
static void cmd_shoot(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
player_t *p = find_player(si->su);
|
||
|
char *id = parv[0];
|
||
|
|
||
|
if (!p)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!id)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You must provide a room to shoot at.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!wumpus.running)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "The game must be running in order to use this command.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
shoot_player(p, atoi(id));
|
||
|
}
|
||
|
|
||
|
command_t wumpus_shoot = { "SHOOT", "Shoot at another room.", AC_NONE, 1, cmd_shoot, { .path = "" } };
|
||
|
|
||
|
static void cmd_resign(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
player_t *p = find_player(si->su);
|
||
|
|
||
|
if (!p)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!wumpus.running)
|
||
|
{
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "The game must be running in order to use this command.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has quit the game!", p->u->nick);
|
||
|
|
||
|
resign_player(p);
|
||
|
}
|
||
|
|
||
|
command_t wumpus_resign = { "RESIGN", "Resign from the game.", AC_NONE, 0, cmd_resign, { .path = "" } };
|
||
|
|
||
|
static void cmd_reset(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
if (wumpus.running)
|
||
|
{
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has ended the game.", si->su->nick);
|
||
|
|
||
|
end_game();
|
||
|
|
||
|
wumpus.running = false;
|
||
|
wumpus.starting = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
command_t wumpus_reset = { "RESET", "Resets the game.", AC_IRCOP, 0, cmd_reset, { .path = "" } };
|
||
|
|
||
|
static void cmd_help(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
command_help(si, si->service->commands);
|
||
|
}
|
||
|
|
||
|
command_t wumpus_help = { "HELP", "Displays this command listing.", AC_NONE, 0, cmd_help, { .path = "help" } };
|
||
|
|
||
|
static void cmd_who(sourceinfo_t *si, int parc, char *parv[])
|
||
|
{
|
||
|
mowgli_node_t *n;
|
||
|
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "The following people are playing:");
|
||
|
|
||
|
MOWGLI_ITER_FOREACH(n, wumpus.players.head)
|
||
|
{
|
||
|
player_t *p = (player_t *) n->data;
|
||
|
|
||
|
notice(wumpus_cfg.nick, si->su->nick, "- %s", p->u->nick);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
command_t wumpus_who = { "WHO", "Displays who is playing the game.", AC_NONE, 0, cmd_who, { .path = "" } };
|
||
|
|
||
|
/* removes quitting players */
|
||
|
static void
|
||
|
user_deleted(user_t *u)
|
||
|
{
|
||
|
player_t *p;
|
||
|
|
||
|
if ((p = find_player(u)) != NULL)
|
||
|
{
|
||
|
msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has quit the game!", p->u->nick);
|
||
|
resign_player(p);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
join_wumpus_channel(server_t *s)
|
||
|
{
|
||
|
join(wumpus_cfg.chan, wumpus.svs->me->nick);
|
||
|
|
||
|
hook_del_server_eob(join_wumpus_channel);
|
||
|
}
|
||
|
|
||
|
/* start handler */
|
||
|
void
|
||
|
_modinit(module_t *m)
|
||
|
{
|
||
|
wumpus.svs = service_add("Wumpus", NULL);
|
||
|
service_set_chanmsg(wumpus.svs, false);
|
||
|
|
||
|
if (cold_start)
|
||
|
{
|
||
|
hook_add_event("server_eob");
|
||
|
hook_add_server_eob(join_wumpus_channel);
|
||
|
}
|
||
|
else if (me.connected)
|
||
|
join(wumpus_cfg.chan, wumpus.svs->me->nick);
|
||
|
|
||
|
hook_add_event("user_delete");
|
||
|
hook_add_user_delete(user_deleted);
|
||
|
|
||
|
service_bind_command(wumpus.svs, &wumpus_help);
|
||
|
service_bind_command(wumpus.svs, &wumpus_start);
|
||
|
service_bind_command(wumpus.svs, &wumpus_join);
|
||
|
service_bind_command(wumpus.svs, &wumpus_move);
|
||
|
service_bind_command(wumpus.svs, &wumpus_shoot);
|
||
|
service_bind_command(wumpus.svs, &wumpus_resign);
|
||
|
service_bind_command(wumpus.svs, &wumpus_reset);
|
||
|
service_bind_command(wumpus.svs, &wumpus_who);
|
||
|
service_bind_command(wumpus.svs, &wumpus_look);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_moddeinit(module_unload_intent_t intent)
|
||
|
{
|
||
|
/* cleanup after ourselves if necessary */
|
||
|
if (wumpus.running)
|
||
|
end_game();
|
||
|
|
||
|
service_delete(wumpus.svs);
|
||
|
|
||
|
hook_del_user_delete(user_deleted);
|
||
|
|
||
|
service_unbind_command(wumpus.svs, &wumpus_help);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_start);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_join);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_move);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_shoot);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_resign);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_reset);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_who);
|
||
|
service_unbind_command(wumpus.svs, &wumpus_look);
|
||
|
|
||
|
if (wumpus.move_timer)
|
||
|
mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
|
||
|
|
||
|
if (wumpus.start_game_timer)
|
||
|
mowgli_timer_destroy(base_eventloop, wumpus.start_game_timer);
|
||
|
}
|
||
|
|
||
|
/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
|
||
|
* vim:ts=8
|
||
|
* vim:sw=8
|
||
|
* vim:noexpandtab
|
||
|
*/
|