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