1 /*
2 * Adds a command to roll an arbitrary number of dice with an arbitrary
3 * number of sides
4 * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
5 * Copyright (C) 2007 Lucas <reilithion@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
20 */
21
22 /* If you can't figure out what this line is for, DON'T TOUCH IT. */
23 #include "../common/pp_internal.h"
24
25 #include <time.h>
26 #include <stdlib.h>
27
28 #include <cmds.h>
29 #include <conversation.h>
30 #include <debug.h>
31 #include <plugin.h>
32
33 #define DEFAULT_DICE 2
34 #define DEFAULT_SIDES 6
35
36 #define BOUNDS_CHECK(var, min, min_def, max, max_def) { \
37 if((var) < (min)) \
38 (var) = (min_def); \
39 else if((var) > (max)) \
40 (var) = (max_def); \
41 }
42
43 #define ROUND(val) ((gdouble)(val) + 0.5f)
44
45 static PurpleCmdId dice_cmd_id = 0;
46
47 static gchar *
old_school_roll(gint dice,gint sides)48 old_school_roll(gint dice, gint sides) {
49 GString *str = g_string_new("");
50 gchar *ret = NULL;
51 gint c = 0, v = 0;
52
53 BOUNDS_CHECK(dice, 1, 2, 15, 15);
54 BOUNDS_CHECK(sides, 2, 2, 999, 999);
55
56 g_string_append_printf(str, "%d %d-sided %s:",
57 dice, sides,
58 (dice == 1) ? "die" : "dice");
59
60 for(c = 0; c < dice; c++) {
61 v = rand() % sides + 1;
62
63 g_string_append_printf(str, " %d", v);
64 }
65
66 ret = str->str;
67 g_string_free(str, FALSE);
68
69 return ret;
70 }
71
72 static inline gboolean
is_dice_notation(const gchar * str)73 is_dice_notation(const gchar *str) {
74 return (g_utf8_strchr(str, -1, 'd') != NULL);
75 }
76
77 static gchar *
dice_notation_roll_helper(const gchar * dn,gint * value)78 dice_notation_roll_helper(const gchar *dn, gint *value) {
79 GString *str = g_string_new("");
80 gchar *ret = NULL, *ms = NULL;
81 gchar op = '\0';
82 gint dice = 0, sides = 0, i = 0, t = 0, v = 0;
83 gdouble multiplier = 1.0;
84
85 if(!dn || *dn == '\0')
86 return NULL;
87
88 /* at this point, all we have is +/- number for our bonus, so we add it to
89 * our value
90 */
91 if(!is_dice_notation(dn)) {
92 gint bonus = atoi(dn);
93
94 *value += bonus;
95
96 /* the + makes sure we always have a + or - */
97 g_string_append_printf(str, "%s %d",
98 (bonus < 0) ? "-" : "+",
99 ABS(bonus));
100
101 ret = str->str;
102 g_string_free(str, FALSE);
103
104 return ret;
105 }
106
107 /**************************************************************************
108 * Process our block
109 *************************************************************************/
110 purple_debug_info("dice", "processing '%s'\n", dn);
111
112 /* get the number of dice */
113 dice = atoi(dn);
114 BOUNDS_CHECK(dice, 1, 1, 999, 999);
115
116 /* find and move to the character after the d */
117 dn = g_utf8_strchr(dn, -1, 'd');
118 dn++;
119
120 /* get the number of sides */
121 sides = atoi(dn);
122 BOUNDS_CHECK(sides, 2, 2, 999, 999);
123
124 /* i've struggled with a better way to determine the next operator, i've
125 * opted for this.
126 */
127 for(t = sides; t > 0; t /= 10) {
128 dn++;
129 purple_debug_info("dice", "looking for the next operator: %s\n", dn);
130 }
131
132 purple_debug_info("dice", "next operator: %s\n", dn);
133
134 /* check if we're multiplying or dividing this block */
135 if(*dn == 'x' || *dn == '/') {
136 op = *dn;
137 dn++;
138
139 multiplier = v = atof(dn);
140
141 ms = g_strdup_printf("%d", (gint)multiplier);
142
143 /* move past our multiplier */
144 for(t = v; t > 0; t /= 10) {
145 purple_debug_info("dice", "moving past the multiplier: %s\n", dn);
146 dn++;
147 }
148
149 if(op == '/')
150 multiplier = 1 / multiplier;
151 }
152
153 purple_debug_info("dice", "d=%d;s=%d;m=%f;\n", dice, sides, multiplier);
154
155 /* calculate and output our block */
156 g_string_append_printf(str, " (");
157 for(i = 0; i < dice; i++) {
158 t = rand() % sides + 1;
159 v = ROUND(t * multiplier);
160
161 g_string_append_printf(str, "%s%d", (i > 0) ? " " : "", t);
162
163 purple_debug_info("dice", "die %d: %d(%d)\n", i, v, t);
164
165 *value += v;
166 }
167
168 g_string_append_printf(str, ")");
169
170 /* if we have a multiplier, we need to output it as well */
171 if(multiplier != 1.0)
172 g_string_append_printf(str, "%c(%s)", op, ms);
173
174 /* free our string of the multiplier */
175 g_free(ms);
176
177 purple_debug_info("dice", "value=%d;str=%s\n", *value, str->str);
178
179 /* we have more in our string, recurse! */
180 if(*dn != '\0') {
181 gchar *s = dice_notation_roll_helper(dn, value);
182
183 if(s)
184 str = g_string_append(str, s);
185
186 g_free(s);
187 }
188
189 ret = str->str;
190 g_string_free(str, FALSE);
191
192 return ret;
193 }
194
195 static gchar *
dice_notation_roll(const gchar * dn)196 dice_notation_roll(const gchar *dn) {
197 GString *str = g_string_new("");
198 gchar *ret = NULL, *normalized = NULL;
199 gint value = 0;
200
201 g_string_append_printf(str, "%s:", dn);
202
203 /* normalize the input and process it */
204 normalized = g_utf8_strdown(dn, -1);
205 g_string_append_printf(str, "%s",
206 dice_notation_roll_helper(normalized, &value));
207 g_free(normalized);
208
209 g_string_append_printf(str, " = %d", value);
210
211 ret = str->str;
212 g_string_free(str, FALSE);
213
214 return ret;
215 }
216
217 static PurpleCmdRet
roll(PurpleConversation * conv,const gchar * cmd,gchar ** args,gchar * error,void * data)218 roll(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar *error,
219 void *data)
220 {
221 PurpleCmdStatus ret;
222 gchar *str = NULL, *newcmd = NULL;
223
224 if(!args[0]) {
225 str = old_school_roll(DEFAULT_DICE, DEFAULT_SIDES);
226 } else {
227 if(is_dice_notation(args[0])) {
228 str = dice_notation_roll(args[0]);
229 } else {
230 gint dice, sides;
231
232 dice = atoi(args[0]);
233 sides = (args[1]) ? atoi(args[1]) : DEFAULT_SIDES;
234
235 str = old_school_roll(dice, sides);
236 }
237 }
238
239 #if 0
240 i = 1; /* Abuse that iterator! We're saying "We think this is dice notation!" */
241 splitted = g_strsplit(args[0], "d", 2); /* Split the description into two parts: (1)d(20+5); discard the 'd'. */
242 dice = atoi(splitted[0]); /* We should have the number of dice easily now. */
243 if(g_strstr_len(splitted[1], -1, "+") != NULL) /* If our second half contained a '+' (20+5) */
244 {
245 resplitted = g_strsplit(splitted[1], "+", 2); /* Split again: (20)+(5); discard the '+'. */
246 sides = atoi(resplitted[0]); /* Number of sides on the left. */
247 bonus += atoi(resplitted[1]); /* Bonus on the right. */
248 g_strfreev(resplitted); /* Free memory from the split. */
249 }
250 else if(g_strstr_len(splitted[1], -1, "-") != NULL) /* If our second half contained a '-' (20-3) */
251 {
252 resplitted = g_strsplit(splitted[1], "-", 2); /* Split again: (20)-(3); discard the '-'. */
253 sides = atoi(resplitted[0]); /* Number of sides on the left. */
254 bonus -= atoi(resplitted[1]); /* Penalty on the right. */
255 g_strfreev(resplitted); /* Free memory from the split. */
256 }
257 else /* There was neither a '+' nor a '-' in the second half. */
258 sides = atoi(splitted[1]); /* We're assuming it's just a number, then. Number of sides. */
259 g_strfreev(splitted); /* Free the original split. */
260 }
261 }
262
263 if(args[1] && i == 0) /* If there was a second argument, and we care about it (not dice notation) */
264 sides = atoi(args[1]); /* Grab it and make it the number of sides the dice have. */
265
266 str = g_string_new("");
267 if(i) /* Show the output in dice notation format. */
268 {
269 g_string_append_printf(str, "%dd%d", dice, sides); /* For example, 1d20 */
270 if(bonus > 0)
271 g_string_append_printf(str, "+%d", bonus); /* 1d20+5 */
272 else if(bonus < 0)
273 g_string_append_printf(str, "%d", bonus); /* 1d20-3 (saying "-%d" would be redundant, since the '-' gets output with bonus automatically) */
274 g_string_append_printf(str, ":"); /* Final colon. 1d20-4: */
275 }
276
277 for(i = 0; i < dice; i++) /* For each die... */
278 {
279 roll = rand() % sides + 1; /* Roll, and add bonus. */
280 accumulator += roll; /* Accumulate our rolls */
281 g_string_append_printf(str, " %d", roll); /* Append the result of our roll to our output string. */
282 }
283
284 if(bonus != 0) /* If we had a bonus */
285 {
286 accumulator += bonus; /* Accumulate our bonus/penalty */
287 g_string_append_printf(str, " %s%d = %d", (bonus < 0) ? "penalty " : "bonus +", bonus, accumulator); /* Append our bonus/penalty to the output string */
288 }
289 else if(dice > 1) /* Or if we had more than one die */
290 {
291 g_string_append_printf(str, " = %d", accumulator); /* Append our accumulator */
292 }
293 #endif
294
295 newcmd = g_strdup_printf("me rolls %s", str);
296
297 ret = purple_cmd_do_command(conv, newcmd, newcmd, &error);
298
299 g_free(str);
300 g_free(newcmd);
301
302 return ret;
303 }
304
305 static gboolean
plugin_load(PurplePlugin * plugin)306 plugin_load(PurplePlugin *plugin)
307 {
308 const gchar *help;
309
310 help = _("dice [dice] [sides]: rolls dice number of sides sided dice OR\n"
311 "dice [XdY+-Z]: rolls X number of Y sided dice, giving a Z "
312 "bonus/penalty to each. e.g. 1d20+2");
313
314 dice_cmd_id = purple_cmd_register("dice", "wws", PURPLE_CMD_P_PLUGIN,
315 PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
316 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
317 NULL, PURPLE_CMD_FUNC(roll),
318 help, NULL);
319
320 /* we only want to seed this off of the seconds since the epoch once. If
321 * we do it every time, we'll give the same results for each time we
322 * process a roll within the same second. This is bad because it's not
323 * really random then.
324 */
325 srand(time(NULL));
326
327 return TRUE;
328 }
329
330 static gboolean
plugin_unload(PurplePlugin * plugin)331 plugin_unload(PurplePlugin *plugin)
332 {
333 purple_cmd_unregister(dice_cmd_id);
334
335 return TRUE;
336 }
337
338 static PurplePluginInfo info =
339 {
340 PURPLE_PLUGIN_MAGIC,
341 PURPLE_MAJOR_VERSION,
342 PURPLE_MINOR_VERSION,
343 PURPLE_PLUGIN_STANDARD,
344 NULL,
345 0,
346 NULL,
347 PURPLE_PRIORITY_DEFAULT,
348
349 "core-plugin_pack-dice",
350 NULL,
351 PP_VERSION,
352 NULL,
353 NULL,
354 "Gary Kramlich <grim@reaperworld.com>",
355 PP_WEBSITE,
356
357 plugin_load,
358 plugin_unload,
359 NULL,
360
361 NULL,
362 NULL,
363 NULL,
364 NULL,
365 NULL,
366 NULL,
367 NULL,
368 NULL
369 };
370
371 static void
init_plugin(PurplePlugin * plugin)372 init_plugin(PurplePlugin *plugin)
373 {
374 #ifdef ENABLE_NLS
375 bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
376 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
377 #endif /* ENABLE_NLS */
378
379 info.name = _("Dice");
380 info.summary = _("Rolls dice in a chat or im");
381 info.description = _("Adds a command (/dice) to roll an arbitrary "
382 "number of dice with an arbitrary number of sides. "
383 "Now supports dice notation! /help dice for details");
384
385 }
386
387 PURPLE_INIT_PLUGIN(dice, init_plugin, info)
388