1 /*
2  * Wumpus - 0.2.0
3  * Copyright (c) 2006, 2011 William Pitcock <nenolod -at- nenolod.net>
4  * Portions copyright (c) 2006 Kiyoshi Aman <kiyoshi.aman -at- gmail.com>
5  *
6  * Rights to this code are as documented in doc/LICENSE.
7  *
8  * Hunt the Wumpus game implementation.
9  *
10  */
11 
12 #include "atheme-compat.h"
13 
14 DECLARE_MODULE_V1
15 (
16 	"contrib/wumpus", false, _modinit, _moddeinit,
17 	PACKAGE_STRING,
18 	"William Pitcock <nenolod -at- nenolod.net>"
19 );
20 
21 /* contents */
22 typedef enum {
23 	E_NOTHING = 0,
24 	E_WUMPUS,
25 	E_PIT,
26 	E_BATS,
27 	E_ARROWS,
28 	E_CRYSTALBALL
29 } contents_t;
30 
31 /* room_t: Describes a room that the wumpus or players could be in. */
32 struct room_ {
33 	int id;			/* room 3 or whatever */
34 	mowgli_list_t exits;		/* old int count == exits.count */
35 	contents_t contents;
36 	mowgli_list_t players;		/* player_t players */
37 };
38 
39 typedef struct room_ room_t;
40 
41 /* player_t: A player object. */
42 struct player_ {
43 	user_t    *u;
44 	room_t    *location;
45 	int        arrows;
46 	int	   hp;
47 	bool  has_moved;
48 };
49 
50 typedef struct player_ player_t;
51 
52 struct game_ {
53 	int wumpus;
54 	int mazesize;
55 	mowgli_list_t players;
56 	bool running;
57 	bool starting;
58 
59 	room_t *rmemctx;	/* memory page context */
60 	service_t *svs;
61 	int wump_hp;
62 	int speed;
63 
64 	unsigned int wantsize;
65 
66 	mowgli_eventloop_timer_t *move_timer;
67 	mowgli_eventloop_timer_t *start_game_timer;
68 };
69 
70 typedef struct game_ game_t;
71 
72 game_t wumpus;
73 
74 struct __wumpusconfig
75 {
76 	char *chan;
77 	char *nick;
78 	char *user;
79 	char *host;
80 	char *real;
81 } wumpus_cfg = {
82 	"#wumpus",
83 	"Wumpus",
84 	"wumpus",
85 	"services.int",
86 	"Hunt the Wumpus"
87 };
88 
89 /* ------------------------------ utility functions */
90 
91 /* returns 1 or 2 depending on if the wumpus is 1 or 2 rooms away */
92 static int
distance_to_wumpus(player_t * player)93 distance_to_wumpus(player_t *player)
94 {
95 	mowgli_node_t *n, *tn;
96 
97 	MOWGLI_ITER_FOREACH(n, player->location->exits.head)
98 	{
99 		room_t *r = (room_t *) n->data;
100 
101 		if (r->contents == E_WUMPUS)
102 			return 1;
103 
104 		MOWGLI_ITER_FOREACH(tn, r->exits.head)
105 		{
106 			room_t *r2 = (room_t *) tn->data;
107 
108 			if (r2->contents == E_WUMPUS)
109 				return 2;
110 
111 			/* we don't evaluate exitpoints at this depth */
112 		}
113 	}
114 
115 	return 0;
116 }
117 
118 /* can we move or perform an action on this room? */
119 static bool
adjacent_room(player_t * p,int id)120 adjacent_room(player_t *p, int id)
121 {
122 	mowgli_node_t *n;
123 
124 	MOWGLI_ITER_FOREACH(n, p->location->exits.head)
125 	{
126 		room_t *r = (room_t *) n->data;
127 
128 		if (r->id == id)
129 			return true;
130 	}
131 
132 	return false;
133 }
134 
135 /* finds a player in the list */
136 static player_t *
find_player(user_t * u)137 find_player(user_t *u)
138 {
139 	mowgli_node_t *n;
140 
141 	MOWGLI_ITER_FOREACH(n, wumpus.players.head)
142 	{
143 		player_t *p = n->data;
144 
145 		if (p->u == u)
146 			return p;
147 	}
148 
149 	return NULL;
150 }
151 
152 /* adds a player to the game */
153 static player_t *
create_player(user_t * u)154 create_player(user_t *u)
155 {
156 	player_t *p;
157 
158 	if (find_player(u))
159 	{
160 		notice(wumpus_cfg.nick, u->nick, "You are already playing the game!");
161 		return NULL;
162 	}
163 
164 	if (wumpus.running)
165 	{
166 		notice(wumpus_cfg.nick, u->nick, "The game is already in progress. Sorry!");
167 		return NULL;
168 	}
169 
170 	p = smalloc(sizeof(player_t));
171 	memset(p, '\0', sizeof(player_t));
172 
173 	p->u = u;
174 	p->arrows = 10;
175 	p->hp = 30;
176 
177 	mowgli_node_add(p, mowgli_node_create(), &wumpus.players);
178 
179 	return p;
180 }
181 
182 /* destroys a player object and removes them from the game */
183 static void
resign_player(player_t * player)184 resign_player(player_t *player)
185 {
186 	mowgli_node_t *n;
187 
188 	if (player == NULL)
189 		return;
190 
191 	if (player->location)
192 	{
193 		n = mowgli_node_find(player, &player->location->players);
194 		mowgli_node_delete(n, &player->location->players);
195 		mowgli_node_free(n);
196 	}
197 
198 	n = mowgli_node_find(player, &wumpus.players);
199 	mowgli_node_delete(n, &wumpus.players);
200 	mowgli_node_free(n);
201 
202 	free(player);
203 }
204 
205 /* ------------------------------ game functions */
206 
207 /* builds the maze, and returns false if the maze is too small */
208 static bool
build_maze(unsigned int size)209 build_maze(unsigned int size)
210 {
211 	unsigned int i, j;
212 	room_t *w;
213 
214 	if (size < 10)
215 		return false;
216 
217 	slog(LG_DEBUG, "wumpus: building maze of %d chambers", size);
218 
219 	/* allocate rooms */
220 	wumpus.mazesize = size;
221 	wumpus.rmemctx = scalloc(size, sizeof(room_t));
222 
223 	for (i = 0; i < size; i++)
224 	{
225 		room_t *r = &wumpus.rmemctx[i];
226 		memset(r, '\0', sizeof(room_t));
227 
228 		r->id = i;
229 
230 		/* rooms have 3 exit points, exits are one-way */
231 		for (j = 0; j < 3 && r->exits.count < 3; j++)
232 		{
233 			int t = rand() % size;
234 
235 			/* make sure this isn't a tunnel to itself */
236 			while (t == r->id)
237 			{
238 				mowgli_node_t *rn;
239 				t = rand() % size;
240 
241 				/* also check that this path doesn't already exist. */
242 				MOWGLI_ITER_FOREACH(rn, r->exits.head)
243 				{
244 					room_t *rm = (room_t *) rn->data;
245 
246 					if (rm->id == t)
247 						t = r->id;
248 				}
249 			}
250 
251 			slog(LG_DEBUG, "wumpus: creating link for route %d -> %d", i, t);
252 			mowgli_node_add(&wumpus.rmemctx[t], mowgli_node_create(), &r->exits);
253 		}
254 
255 		slog(LG_DEBUG, "wumpus: finished creating exit paths for chamber %d", i);
256 	}
257 
258 	/* place the wumpus in the maze */
259 	wumpus.wumpus = rand() % size;
260 	w = &wumpus.rmemctx[wumpus.wumpus];
261 	w->contents = E_WUMPUS;
262 
263 	/* pits */
264 	for (j = 0; j < size; j++)
265 	{
266 		/* 42 will do very nicely */
267 		if (rand() % (42 * 2) == 0)
268 		{
269 			room_t *r = &wumpus.rmemctx[j];
270 
271 			r->contents = E_PIT;
272 
273 			slog(LG_DEBUG, "wumpus: added pit to chamber %d", j);
274 		}
275 	}
276 
277 	/* bats */
278 	for (i = 0; i < 2; i++)
279 	{
280 		for (j = 0; j < size; j++)
281 		{
282 			/* 42 will do very nicely */
283 			if (rand() % 42 == 0)
284 			{
285 				room_t *r = &wumpus.rmemctx[j];
286 
287 				r->contents = E_BATS;
288 
289 				slog(LG_DEBUG, "wumpus: added bats to chamber %d", j);
290 			}
291 		}
292 	}
293 
294 	/* arrows */
295 	for (i = 0; i < 3; i++)
296 	{
297 		for (j = 0; j < size; j++)
298 		{
299 			/* 42 will do very nicely */
300 			if (rand() % 42 == 0)
301 			{
302 				room_t *r = &wumpus.rmemctx[j];
303 
304 				r->contents = E_ARROWS;
305 
306 				slog(LG_DEBUG, "wumpus: added arrows to chamber %d", j);
307 			}
308 		}
309 	}
310 
311 	/* find a place to put the crystal ball */
312 	w = &wumpus.rmemctx[rand() % size];
313 	w->contents = E_CRYSTALBALL;
314 	slog(LG_DEBUG, "wumpus: added crystal ball to chamber %d", w->id);
315 
316 	/* ok, do some sanity checking */
317 	for (j = 0; j < size; j++)
318 		if (wumpus.rmemctx[j].exits.count < 3)
319 		{
320 			slog(LG_DEBUG, "wumpus: sanity checking failed");
321 			return false;
322 		}
323 
324 	slog(LG_DEBUG, "wumpus: built maze");
325 
326 	return true;
327 }
328 
329 /* init_game depends on these */
330 static void move_wumpus(void *unused);
331 static void look_player(player_t *p);
332 static void end_game(void);
333 
334 /* sets the game up */
335 static void
init_game(unsigned int size)336 init_game(unsigned int size)
337 {
338 	mowgli_node_t *n;
339 
340 	if (!build_maze(size))
341 	{
342 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "Maze generation failed, please try again.");
343 		end_game();
344 		return;
345 	}
346 
347 	/* place players in random positions */
348 	MOWGLI_ITER_FOREACH(n, wumpus.players.head)
349 	{
350 		player_t *p = (player_t *) n->data;
351 
352 		p->location = &wumpus.rmemctx[rand() % wumpus.mazesize];
353 		mowgli_node_add(p, mowgli_node_create(), &p->location->players);
354 
355 		look_player(p);
356 	}
357 
358 	/* timer initialization */
359 	wumpus.move_timer = mowgli_timer_add(base_eventloop, "move_wumpus", move_wumpus, NULL, 60);
360 
361 	msg(wumpus_cfg.nick, wumpus_cfg.chan, "The game has started!");
362 
363 	wumpus.running = true;
364 	wumpus.speed = 60;
365 	wumpus.wump_hp = 70;
366 
367 	wumpus.start_game_timer = NULL;
368 }
369 
370 /* starts the game */
371 static void
start_game(void * unused)372 start_game(void *unused)
373 {
374 	wumpus.starting = false;
375 
376 	if (wumpus.players.count < 2)
377 	{
378 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "Not enough players to play. :(");
379 		return;
380 	}
381 
382 	if (wumpus.wantsize >= 300)
383 		wumpus.wantsize = 300;
384 
385 	init_game(wumpus.wantsize);
386 }
387 
388 /* destroys game objects */
389 static void
end_game(void)390 end_game(void)
391 {
392 	mowgli_node_t *n, *tn;
393 	int i;
394 
395 	/* destroy players */
396 	MOWGLI_ITER_FOREACH_SAFE(n, tn, wumpus.players.head)
397 		resign_player((player_t *) n->data);
398 
399 	/* free memory vector */
400 	if (wumpus.rmemctx)
401 	{
402 		/* destroy links between rooms */
403 		for (i = 0; i < wumpus.mazesize; i++)
404 		{
405 			room_t *r = &wumpus.rmemctx[i];
406 
407 			MOWGLI_ITER_FOREACH_SAFE(n, tn, r->exits.head)
408 				mowgli_node_delete(n, &r->exits);
409 		}
410 		free(wumpus.rmemctx);
411 		wumpus.rmemctx = NULL;
412 	}
413 
414 	wumpus.wumpus = -1;
415 	wumpus.running = false;
416 
417 	mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
418 	wumpus.move_timer = NULL;
419 
420 	/* game is now ended */
421 }
422 
423 /* gives the player information about their surroundings */
424 static void
look_player(player_t * p)425 look_player(player_t *p)
426 {
427 	mowgli_node_t *n;
428 
429 	return_if_fail(p != NULL);
430 	return_if_fail(p->location != NULL);
431 
432 	notice(wumpus_cfg.nick, p->u->nick, "You are in room %d.", p->location->id);
433 
434 	MOWGLI_ITER_FOREACH(n, p->location->exits.head)
435 	{
436 		room_t *r = (room_t *) n->data;
437 
438 		notice(wumpus_cfg.nick, p->u->nick, "You can move to room %d.", r->id);
439 	}
440 
441 	if (distance_to_wumpus(p))
442 		notice(wumpus_cfg.nick, p->u->nick, "You smell a wumpus!");
443 
444 	/* provide warnings */
445 	MOWGLI_ITER_FOREACH(n, p->location->exits.head)
446 	{
447 		room_t *r = (room_t *) n->data;
448 
449 		if (r->contents == E_WUMPUS)
450 			notice(wumpus_cfg.nick, p->u->nick, "You smell a wumpus!");
451 		if (r->contents == E_PIT)
452 			notice(wumpus_cfg.nick, p->u->nick, "You feel a draft!");
453 		if (r->contents == E_BATS)
454 			notice(wumpus_cfg.nick, p->u->nick, "You hear bats!");
455 		if (r->players.count > 0)
456 			notice(wumpus_cfg.nick, p->u->nick, "You smell humans!");
457 	}
458 }
459 
460 /* shoot and kill other players */
461 static void
shoot_player(player_t * p,int target_id)462 shoot_player(player_t *p, int target_id)
463 {
464 	room_t *r;
465 	player_t *tp;
466 	/* chance to hit; moved up here for convenience. */
467 	int hit = rand() % 3;
468 
469 	if (!p->arrows)
470 	{
471 		notice(wumpus_cfg.nick, p->u->nick, "You have no arrows!");
472 		return;
473 	}
474 
475 	if (adjacent_room(p, target_id) == false)
476 	{
477 		notice(wumpus_cfg.nick, p->u->nick, "You can't shoot into room %d from here.", target_id);
478 		return;
479 	}
480 
481 	if (p->location->id == target_id)
482 	{
483 		notice(wumpus_cfg.nick, p->u->nick, "You can only shoot into adjacent rooms!");
484 		return;
485 	}
486 
487 	r = &wumpus.rmemctx[target_id];
488 	tp = r->players.head ? r->players.head->data : NULL;
489 
490 	p->arrows--;
491 
492 	if ((!tp) && (r->contents != E_WUMPUS))
493 	{
494 		notice(wumpus_cfg.nick, p->u->nick, "You shoot at nothing.");
495 		return;
496 	}
497 
498 	if (tp)
499 	{
500 		if ((hit < 2) && (tp->hp <= 10))
501 		{
502 			msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has been killed by \2%s\2!",
503 				tp->u->nick, p->u->nick);
504 			resign_player(tp);
505 		}
506 		else if ((tp->hp > 0) && (hit < 2)) {
507 			notice(wumpus_cfg.nick, tp->u->nick,
508 				"You were hit by an arrow from room %d.",p->location->id);
509 			notice(wumpus_cfg.nick, p->u->nick, "You hit something.");
510 			tp->hp -= 10;
511 		}
512 		else
513 		{
514 			notice(wumpus_cfg.nick, tp->u->nick, "You have been shot at from room %d.",
515 				p->location->id);
516 			notice(wumpus_cfg.nick, p->u->nick, "You miss what you were shooting at.");
517 		}
518 	}
519 	else if (r->contents == E_WUMPUS) /* Shootin' at the wumpus, we are... */
520 	{
521 		if (((wumpus.wump_hp > 0) && wumpus.wump_hp <= 5) && (hit < 2))
522 			/* we killed the wumpus */
523 		{
524 			notice(wumpus_cfg.nick, p->u->nick, "You have killed the wumpus!");
525 			msg(wumpus_cfg.nick, wumpus_cfg.chan, "The wumpus was killed by \2%s\2.",
526 				p->u->nick);
527 			msg(wumpus_cfg.nick, wumpus_cfg.chan,
528 				"%s has won the game! Congratulations!", p->u->nick);
529 			end_game();
530 		}
531 		else if ((wumpus.wump_hp > 5) && (hit < 2))
532 		{
533 			notice(wumpus_cfg.nick, p->u->nick,
534 				"You shoot the Wumpus, but he shrugs it off and seems angrier!");
535 
536 			wumpus.wump_hp -= 5;
537 			wumpus.speed -= 3;
538 
539 			move_wumpus(NULL);
540 
541 			mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
542 			wumpus.move_timer = mowgli_timer_add(base_eventloop, "move_wumpus", move_wumpus, NULL, wumpus.speed);
543 		}
544 		else
545 		{
546 			notice(wumpus_cfg.nick, p->u->nick, "You miss what you were shooting at.");
547 			move_wumpus(NULL);
548 		}
549 	}
550 }
551 
552 /* move_wumpus depends on this */
553 static void regen_obj(contents_t);
554 
555 /* check for last-man-standing win condition. */
556 static void
check_last_person_alive(void)557 check_last_person_alive(void)
558 {
559 	if (wumpus.players.count == 1)
560 	{
561 		player_t *p = (player_t *) wumpus.players.head->data;
562 
563 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "%s won the game! Congratulations!", p->u->nick);
564 
565 		end_game();
566 	}
567 	else if (wumpus.players.count == 0)
568 	{
569 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "Everyone lost. Sucks. :(");
570 		end_game();
571 	}
572 }
573 
574 /* move the wumpus, the wumpus moves every 60 seconds */
575 static void
move_wumpus(void * unused)576 move_wumpus(void *unused)
577 {
578 	mowgli_node_t *n, *tn;
579 	room_t *r, *tr;
580 	int w_kills = 0;
581 
582 	/* can we do any of this? if this is null, we really shouldn't be here */
583 	if (wumpus.rmemctx == NULL)
584 	{
585 		slog(LG_DEBUG, "wumpus: move_wumpus() called while game not running!");
586 		mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
587 		return;
588 	}
589 
590 	msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear footsteps...");
591 
592 	/* start moving */
593 	r = &wumpus.rmemctx[wumpus.wumpus]; /* memslice describing the wumpus's current location */
594 
595 	regen_obj(r->contents);
596 	r->contents = E_NOTHING;
597 
598 	tr = mowgli_node_nth_data(&r->exits, rand() % MOWGLI_LIST_LENGTH(&r->exits));
599 
600 #ifdef DEBUG_AI
601 	msg(wumpus_cfg.nick, wumpus_cfg.chan, "I moved to chamber %d", tr->id);
602 #endif
603 
604 	slog(LG_DEBUG, "wumpus: the wumpus is now in room %d! (was in %d)",
605 	     tr->id, wumpus.wumpus);
606 	wumpus.wumpus = tr->id;
607 	tr->contents = E_WUMPUS;
608 
609 #ifdef DEBUG_AI
610 	msg(wumpus_cfg.nick, wumpus_cfg.chan, "On my next turn, I can move to:");
611 	r = &wumpus.rmemctx[wumpus.wumpus];
612 
613 	MOWGLI_ITER_FOREACH(n, r->exits.head)
614 	{
615 		room_t *tr = (room_t *) n->data;
616 
617 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "- %d", tr->id);
618 	}
619 #endif
620 
621 	MOWGLI_ITER_FOREACH_SAFE(n, tn, wumpus.players.head)
622 	{
623 		player_t *p = (player_t *) n->data;
624 
625 		if (wumpus.wumpus == p->location->id)
626 		{
627 			notice(wumpus_cfg.nick, p->u->nick, "The wumpus has joined your room and eaten you. Sorry.");
628 			w_kills++;
629 
630 			/* player_t *p has been eaten and is no longer in the game */
631 			resign_player(p);
632 		}
633 		else
634 		{
635 			/* prepare for the next turn */
636 			p->has_moved = false;
637 		}
638 	}
639 
640 	/* report any wumpus kills */
641 	if (w_kills)
642 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear the screams of %d surprised adventurer%s.", w_kills,
643 			w_kills != 1 ? "s" : "");
644 
645 	check_last_person_alive();
646 }
647 
648 /* regenerates objects */
649 static void
regen_obj(contents_t obj)650 regen_obj(contents_t obj)
651 {
652 	wumpus.rmemctx[rand() % wumpus.mazesize].contents = obj;
653 }
654 
655 /* handles movement requests from players */
656 static void
move_player(player_t * p,int id)657 move_player(player_t *p, int id)
658 {
659 	mowgli_node_t *n;
660 
661 	if (adjacent_room(p, id) == false)
662 	{
663 		notice(wumpus_cfg.nick, p->u->nick, "Sorry, you cannot get to room %d from here.", id);
664 		return;
665 	}
666 
667 	/* What about bats? We check for this first because yeah... */
668 	if (wumpus.rmemctx[id].contents == E_BATS)
669 	{
670 		int target_id = rand() % wumpus.mazesize;
671 
672 		notice(wumpus_cfg.nick, p->u->nick, "Bats have picked you up and taken you to room %d.",
673 			target_id);
674 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear a surprised yell.");
675 
676 		/* move the bats */
677 		wumpus.rmemctx[id].contents = E_NOTHING;
678 		wumpus.rmemctx[target_id].contents = E_BATS;
679 
680 		id = target_id;
681 
682 		/* and fall through, sucks if you hit the two conditions below :-P */
683 	}
684 
685 	/* Is the wumpus in here? */
686 	if (wumpus.wumpus == id)
687 	{
688 		notice(wumpus_cfg.nick, p->u->nick, "You see the wumpus approaching you. You scream for help, but it is too late.");
689 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear a blood-curdling scream.");
690 
691 		/* player_t *p has been killed by the wumpus, remove him from the game */
692 		resign_player(p);
693 		check_last_person_alive();
694 		return;
695 	}
696 
697 	/* What about a pit? */
698 	if (wumpus.rmemctx[id].contents == E_PIT)
699 	{
700 		notice(wumpus_cfg.nick, p->u->nick, "You have fallen into a bottomless pit. Sorry.");
701 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "You hear a faint wail, which gets fainter and fainter.");
702 
703 		/* player_t *p has fallen down a hole, remove him from the game */
704 		resign_player(p);
705 		check_last_person_alive();
706 		return;
707 	}
708 
709 	/* and arrows? */
710 	if (wumpus.rmemctx[id].contents == E_ARROWS)
711 	{
712 		if (p->arrows == 0)
713 		{
714 			notice(wumpus_cfg.nick, p->u->nick, "You found some arrows. You pick them up and continue on your way.");
715 			p->arrows += 5;
716 		}
717 		else
718 			notice(wumpus_cfg.nick, p->u->nick, "You found some arrows. You don't have any room to take them however, "
719 						"so you break them in half and continue on your way.");
720 
721 		wumpus.rmemctx[id].contents = E_NOTHING;
722 
723 		regen_obj(E_ARROWS);
724 	}
725 
726 	/* crystal ball */
727 	if (wumpus.rmemctx[id].contents == E_CRYSTALBALL)
728 	{
729 		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.",
730 			wumpus.wumpus);
731 		notice(wumpus_cfg.nick, p->u->nick, "The crystal ball then vanishes into the miasma.");
732 
733 		wumpus.rmemctx[id].contents = E_NOTHING;
734 		wumpus.rmemctx[rand() % wumpus.mazesize].contents = E_CRYSTALBALL;
735 	}
736 
737 	/* we recycle the mowgli_node_t here for speed */
738 	n = mowgli_node_find(p, &p->location->players);
739 	mowgli_node_delete(n, &p->location->players);
740 	mowgli_node_free(n);
741 
742 	p->location = &wumpus.rmemctx[id];
743 	mowgli_node_add(p, mowgli_node_create(), &p->location->players);
744 
745 	/* provide player with information, including their new location */
746 	look_player(p);
747 
748 	/* tell players about joins. */
749 	if (p->location->players.count > 1)
750 	{
751 		MOWGLI_ITER_FOREACH(n, p->location->players.head)
752 		{
753 			if (n->data != p)
754 			{
755 				player_t *tp = (player_t *) n->data;
756 
757 				notice(wumpus_cfg.nick, tp->u->nick, "%s has joined room %d with you.",
758 					p->u->nick, id);
759 				notice(wumpus_cfg.nick, p->u->nick, "You see %s!",
760 					tp->u->nick);
761 			}
762 		}
763 	}
764 }
765 
766 /* ------------------------------ -*-atheme-*- code */
767 
cmd_start(sourceinfo_t * si,int parc,char * parv[])768 static void cmd_start(sourceinfo_t *si, int parc, char *parv[])
769 {
770 	if (wumpus.running || wumpus.starting)
771 	{
772 		notice(wumpus_cfg.nick, si->su->nick, "A game is already in progress. Sorry.");
773 		return;
774 	}
775 
776 	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.",
777 		si->su->nick);
778 
779 	wumpus.starting = true;
780 	wumpus.wantsize = 100;
781 
782 	if (parv[0])
783 		wumpus.wantsize = atoi(parv[0]);
784 
785 	wumpus.start_game_timer = mowgli_timer_add_once(base_eventloop, "start_game", start_game, NULL, 60);
786 }
787 
788 /* reference tuple for the above code: cmd_start */
789 command_t wumpus_start = { "START", "Starts the game.", AC_NONE, 1, cmd_start, { .path = "" } };
790 
cmd_join(sourceinfo_t * si,int parc,char * parv[])791 static void cmd_join(sourceinfo_t *si, int parc, char *parv[])
792 {
793 	player_t *p;
794 
795 	if (!wumpus.starting || wumpus.running)
796 	{
797 		notice(wumpus_cfg.nick, si->su->nick, "You cannot use this command right now. Sorry.");
798 		return;
799 	}
800 
801 	p = create_player(si->su);
802 
803 	if (p)
804 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has joined the game!", si->su->nick);
805 }
806 
807 command_t wumpus_join = { "JOIN", "Joins the game.", AC_NONE, 0, cmd_join, { .path = "" } };
808 
cmd_look(sourceinfo_t * si,int parc,char * parv[])809 static void cmd_look(sourceinfo_t *si, int parc, char *parv[])
810 {
811 	player_t *p = find_player(si->su);
812 
813 	if (p == NULL)
814 	{
815 		notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
816 		return;
817 	}
818 
819 	if (!wumpus.running)
820 	{
821 		notice(wumpus_cfg.nick, si->su->nick, "You cannot use this command right now. Sorry.");
822 		return;
823 	}
824 
825 	look_player(p);
826 }
827 
828 command_t wumpus_look = { "LOOK", "View surroundings.", AC_NONE, 0, cmd_look, { .path = "" } };
829 
cmd_move(sourceinfo_t * si,int parc,char * parv[])830 static void cmd_move(sourceinfo_t *si, int parc, char *parv[])
831 {
832 	player_t *p = find_player(si->su);
833 	char *id = parv[0];
834 
835 	if (!p)
836 	{
837 		notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
838 		return;
839 	}
840 
841 	if (!id)
842 	{
843 		notice(wumpus_cfg.nick, si->su->nick, "You must provide a room to move to.");
844 		return;
845 	}
846 
847 	if (!wumpus.running)
848 	{
849 		notice(wumpus_cfg.nick, si->su->nick, "The game must be running in order to use this command.");
850 		return;
851 	}
852 
853 	move_player(p, atoi(id));
854 }
855 
856 command_t wumpus_move = { "MOVE", "Move to another room.", AC_NONE, 1, cmd_move, { .path = "" } };
857 
cmd_shoot(sourceinfo_t * si,int parc,char * parv[])858 static void cmd_shoot(sourceinfo_t *si, int parc, char *parv[])
859 {
860 	player_t *p = find_player(si->su);
861 	char *id = parv[0];
862 
863 	if (!p)
864 	{
865 		notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
866 		return;
867 	}
868 
869 	if (!id)
870 	{
871 		notice(wumpus_cfg.nick, si->su->nick, "You must provide a room to shoot at.");
872 		return;
873 	}
874 
875 	if (!wumpus.running)
876 	{
877 		notice(wumpus_cfg.nick, si->su->nick, "The game must be running in order to use this command.");
878 		return;
879 	}
880 
881 	shoot_player(p, atoi(id));
882 }
883 
884 command_t wumpus_shoot = { "SHOOT", "Shoot at another room.", AC_NONE, 1, cmd_shoot, { .path = "" } };
885 
cmd_resign(sourceinfo_t * si,int parc,char * parv[])886 static void cmd_resign(sourceinfo_t *si, int parc, char *parv[])
887 {
888 	player_t *p = find_player(si->su);
889 
890 	if (!p)
891 	{
892 		notice(wumpus_cfg.nick, si->su->nick, "You must be playing the game in order to use this command.");
893 		return;
894 	}
895 
896 	if (!wumpus.running)
897 	{
898 		notice(wumpus_cfg.nick, si->su->nick, "The game must be running in order to use this command.");
899 		return;
900 	}
901 
902 	msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has quit the game!", p->u->nick);
903 
904 	resign_player(p);
905 }
906 
907 command_t wumpus_resign = { "RESIGN", "Resign from the game.", AC_NONE, 0, cmd_resign, { .path = "" } };
908 
cmd_reset(sourceinfo_t * si,int parc,char * parv[])909 static void cmd_reset(sourceinfo_t *si, int parc, char *parv[])
910 {
911 	if (wumpus.running)
912 	{
913 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has ended the game.", si->su->nick);
914 
915 		end_game();
916 
917 		wumpus.running = false;
918 		wumpus.starting = false;
919 	}
920 }
921 
922 command_t wumpus_reset = { "RESET", "Resets the game.", AC_IRCOP, 0, cmd_reset, { .path = "" } };
923 
cmd_help(sourceinfo_t * si,int parc,char * parv[])924 static void cmd_help(sourceinfo_t *si, int parc, char *parv[])
925 {
926 	command_help(si, si->service->commands);
927 }
928 
929 command_t wumpus_help = { "HELP", "Displays this command listing.", AC_NONE, 0, cmd_help, { .path = "help" } };
930 
cmd_who(sourceinfo_t * si,int parc,char * parv[])931 static void cmd_who(sourceinfo_t *si, int parc, char *parv[])
932 {
933 	mowgli_node_t *n;
934 
935 	notice(wumpus_cfg.nick, si->su->nick, "The following people are playing:");
936 
937 	MOWGLI_ITER_FOREACH(n, wumpus.players.head)
938 	{
939 		player_t *p = (player_t *) n->data;
940 
941 		notice(wumpus_cfg.nick, si->su->nick, "- %s", p->u->nick);
942 	}
943 }
944 
945 command_t wumpus_who = { "WHO", "Displays who is playing the game.", AC_NONE, 0, cmd_who, { .path = "" } };
946 
947 /* removes quitting players */
948 static void
user_deleted(user_t * u)949 user_deleted(user_t *u)
950 {
951 	player_t *p;
952 
953 	if ((p = find_player(u)) != NULL)
954 	{
955 		msg(wumpus_cfg.nick, wumpus_cfg.chan, "\2%s\2 has quit the game!", p->u->nick);
956 		resign_player(p);
957 	}
958 }
959 
960 static void
join_wumpus_channel(server_t * s)961 join_wumpus_channel(server_t *s)
962 {
963 	join(wumpus_cfg.chan, wumpus.svs->me->nick);
964 
965 	hook_del_server_eob(join_wumpus_channel);
966 }
967 
968 /* start handler */
969 void
_modinit(module_t * m)970 _modinit(module_t *m)
971 {
972 	wumpus.svs = service_add("Wumpus", NULL);
973 	service_set_chanmsg(wumpus.svs, false);
974 
975 	if (cold_start)
976 	{
977 		hook_add_event("server_eob");
978 		hook_add_server_eob(join_wumpus_channel);
979 	}
980 	else if (me.connected)
981 		join(wumpus_cfg.chan, wumpus.svs->me->nick);
982 
983 	hook_add_event("user_delete");
984 	hook_add_user_delete(user_deleted);
985 
986 	service_bind_command(wumpus.svs, &wumpus_help);
987 	service_bind_command(wumpus.svs, &wumpus_start);
988 	service_bind_command(wumpus.svs, &wumpus_join);
989 	service_bind_command(wumpus.svs, &wumpus_move);
990 	service_bind_command(wumpus.svs, &wumpus_shoot);
991 	service_bind_command(wumpus.svs, &wumpus_resign);
992 	service_bind_command(wumpus.svs, &wumpus_reset);
993 	service_bind_command(wumpus.svs, &wumpus_who);
994 	service_bind_command(wumpus.svs, &wumpus_look);
995 }
996 
997 void
_moddeinit(module_unload_intent_t intent)998 _moddeinit(module_unload_intent_t intent)
999 {
1000 	/* cleanup after ourselves if necessary */
1001 	if (wumpus.running)
1002 		end_game();
1003 
1004 	service_delete(wumpus.svs);
1005 
1006 	hook_del_user_delete(user_deleted);
1007 
1008 	service_unbind_command(wumpus.svs, &wumpus_help);
1009 	service_unbind_command(wumpus.svs, &wumpus_start);
1010 	service_unbind_command(wumpus.svs, &wumpus_join);
1011 	service_unbind_command(wumpus.svs, &wumpus_move);
1012 	service_unbind_command(wumpus.svs, &wumpus_shoot);
1013 	service_unbind_command(wumpus.svs, &wumpus_resign);
1014 	service_unbind_command(wumpus.svs, &wumpus_reset);
1015 	service_unbind_command(wumpus.svs, &wumpus_who);
1016 	service_unbind_command(wumpus.svs, &wumpus_look);
1017 
1018 	if (wumpus.move_timer)
1019 		mowgli_timer_destroy(base_eventloop, wumpus.move_timer);
1020 
1021 	if (wumpus.start_game_timer)
1022 		mowgli_timer_destroy(base_eventloop, wumpus.start_game_timer);
1023 }
1024 
1025 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
1026  * vim:ts=8
1027  * vim:sw=8
1028  * vim:noexpandtab
1029  */
1030