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