Welcome to the Builder Academy

Question How do you Balance your game.

More
04 Jan 2017 05:13 #6498 by WhiskyTest
I had fun writing this at work today because it was so quiet :)

It is a new ACMD function called 'simulate'.
Get two characters in a room with you and type:
simulate <arg1> <arg2> <number>
(arg1 and arg2 should be the names of the characters)

It will simulate a battle for the two characters however many times you specify for <number> (currently capped at 100 but feel free to alter!)
Then it will tell you who won, how many times, and the average number of combat rounds each fight took.

I wrote it for stock tbaMUD so you will need to change parts to match your own combat system.
In terms of the data displaued I left it pretty lightweight for now because I'm not sure what data would be useful.

The idea is that we can now "playtest" 100 battles with a mob instantly!
The results do NOT take into account skills that might be used, or combat triggers that might fire, but it will calculate equipment bonuses - so I reckon it should give a fairly accurate approximation.

Add to act.h
Code:
ACMD(do_simulate);

Add to interpreter.c
Code:
{ "simulate" , "simulate", POS_DEAD , do_simulate , LVL_GOD, 0 },

Add to the end of fight.c
Code:
/* Usage: simulate <character> <character> <number of battles> * * Intended to assist with balancing, pick two characters in the room with you and * Example: simulate WhiskyTest Orc 100 * * I've capped it at 100 battles, be careful of the do-while loop. Everything will pause * until this completes, potentially freezing your MUD while it works out each battle. * * Change the battle routine to suit your MUD combat rolls * Add new counters to track additional data! * */ ACMD(do_simulate) { /* Data we wish to track for storing in the arrays */ #define WINS 0 #define LOSSES 1 /* Add data counters in here, remember to increment MAX_DATA */ #define MAX_DATA 2 char arg1[MAX_INPUT_LENGTH]; /* arguments for the ACMD function */ char arg2[MAX_INPUT_LENGTH]; char arg3[MAX_INPUT_LENGTH]; int number, x, round; /* Number = number of battles, x = generic counter, round = which round this is */ bool round_over; /* Is the round over yet? Controls the do-while loop*/ int actor1_results[MAX_DATA], actor2_results[MAX_DATA]; /* arrays to store our data */ int total_rounds, diceroll; /* total_rounds = for averaging how long each fight was, diceroll = for rand_number(1, 20) */ int actor1_hitp, actor1_damage, actor1_ac, actor1_calc_thaco, actor1_success; /* Actor1 variables */ int actor2_hitp, actor2_damage, actor2_ac, actor2_calc_thaco, actor2_success; /* Actor2 variables */ struct char_data *actor1, *actor2; /* Our two actors for the simulation, actor1 will always hit first */ one_argument(two_arguments(argument, arg1, arg2), arg3); /* three_arguments */ /* Check we have enough arguments */ if(!*arg1 || !*arg2|| !*arg3) { send_to_char(ch, "Usage: simulate <character 1> <character 2> <#number of battles>\r\n"); return; } /* Check for visible characters in room */ if (!(actor1 = get_char_vis(ch, arg1, NULL, FIND_CHAR_ROOM))) { send_to_char(ch, "%s? %s", arg1, CONFIG_NOPERSON); return; } if (!(actor2 = get_char_vis(ch, arg2, NULL, FIND_CHAR_ROOM))) { send_to_char(ch, "%s? %s", arg2, CONFIG_NOPERSON); return; } number = MIN(MAX(atoi(arg3), 0), 100); send_to_char(ch, "OK - I will simulate a battle to the death between \tR%s\tn and \tB%s \tG%d\tn times!\r\n", GET_NAME(actor1), GET_NAME(actor2), number); /* Zero out the arrays and set all variables to starting values */ for (x = 0; x < MAX_DATA; x++) { actor1_results[x] = 0; actor2_results[x] = 0; } total_rounds = 0; /* Set all of Actor 1's battle statistics */ actor1_ac = compute_armor_class(actor1) / 10; actor1_calc_thaco = compute_thaco(actor1, actor2); actor1_damage = str_app[STRENGTH_APPLY_INDEX(actor1)].todam; actor1_damage += GET_DAMROLL(actor1); if(GET_EQ(actor1, WEAR_WIELD) && GET_OBJ_TYPE(GET_EQ(actor1, WEAR_WIELD)) == ITEM_WEAPON ) { actor1_damage += ((GET_OBJ_VAL(GET_EQ(actor1, WEAR_WIELD), 2) + 1) / 2.0) * GET_OBJ_VAL(GET_EQ(actor1, WEAR_WIELD), 1); } else { if (IS_NPC(actor1)) actor1_damage += ((actor1->mob_specials.damsizedice + 1) / 2.0) * actor1->mob_specials.damnodice; else actor1_damage += 1; } if (AFF_FLAGGED(actor2, AFF_SANCTUARY)) actor1_damage /= 2; /* Set all of Actor 2's battle statistics */ actor2_ac = compute_armor_class(actor2) / 10; actor2_calc_thaco = compute_thaco(actor2, actor1); actor2_damage = str_app[STRENGTH_APPLY_INDEX(actor2)].todam; actor2_damage += GET_DAMROLL(actor2); if(GET_EQ(actor2, WEAR_WIELD) && GET_OBJ_TYPE(GET_EQ(actor2, WEAR_WIELD)) == ITEM_WEAPON ) { actor2_damage += ((GET_OBJ_VAL(GET_EQ(actor2, WEAR_WIELD), 2) + 1) / 2.0) * GET_OBJ_VAL(GET_EQ(actor2, WEAR_WIELD), 1); } else { if (IS_NPC(actor2)) actor2_damage += ((actor2->mob_specials.damsizedice + 1) / 2.0) * actor2->mob_specials.damnodice; else actor2_damage += 1; } if (AFF_FLAGGED(actor1, AFF_SANCTUARY)) actor2_damage /= 2; /* Now simulate the combat and record stats! * * Begin battle loop : */ for (x = 1; x <= number; x++) { /* Reset values for new combat */ actor1_hitp = GET_MAX_HIT(actor1); actor2_hitp = GET_MAX_HIT(actor2); round_over = 0; round = 0; /* Start combat rounds until one wins */ while (round_over == 0) { round++; /* Actor1 first */ diceroll = rand_number(1, 20); if (diceroll == 20) actor1_success = 1; else if (diceroll == 1) actor1_success = 0; else if (actor1_calc_thaco - diceroll <= actor2_ac) actor1_success = 1; if (actor1_success) actor2_hitp -= actor1_damage; /* Check to see if Actor 2 is out of the fight */ if (actor2_hitp <= 0) { actor1_results[WINS]++; /* Increment data counters here */ actor2_results[LOSSES]++; round_over = 1; break; } /* Actor 2 has a crack at it */ diceroll = rand_number(1, 20); if (diceroll == 20) actor2_success = 1; else if (diceroll == 1) actor2_success = 0; else if (actor2_calc_thaco - diceroll <= actor1_ac) actor2_success = 1; if (actor2_success) actor1_hitp -= actor2_damage; if (actor1_hitp <= 0) { actor2_results[WINS]++; /* Increment data counters here */ actor1_results[LOSSES]++; round_over = 1; break; } } /* End of combat round */ total_rounds += round; } /* End of Battle loop */ /* Display Results! */ send_to_char(ch, "Results:\r\n"); send_to_char(ch, "Average rounds per fight: %d\r\n", total_rounds / number); send_to_char(ch, "\tR%s\tn wins: \tR%d\tn\r\n", GET_NAME(actor1), actor1_results[WINS]); send_to_char(ch, "\tB%s\tn wins: \tB%d\tn\r\n", GET_NAME(actor2), actor2_results[WINS]); return; }

Please Log in or Create an account to join the conversation.

More
05 Jan 2017 03:08 #6500 by Liko
Replied by Liko on topic How do you Balance your game.

WhiskyTest wrote: I had fun writing this at work today because it was so quiet :)

It is a new ACMD function called 'simulate'.
Get two characters in a room with you and type:
simulate <arg1> <arg2> <number>
(arg1 and arg2 should be the names of the characters)

It will simulate a battle for the two characters however many times you specify for <number> (currently capped at 100 but feel free to alter!)
Then it will tell you who won, how many times, and the average number of combat rounds each fight took.

I wrote it for stock tbaMUD so you will need to change parts to match your own combat system.
In terms of the data displaued I left it pretty lightweight for now because I'm not sure what data would be useful.

The idea is that we can now "playtest" 100 battles with a mob instantly!
The results do NOT take into account skills that might be used, or combat triggers that might fire, but it will calculate equipment bonuses - so I reckon it should give a fairly accurate approximation.

Add to act.h

Code:
ACMD(do_simulate);

Add to interpreter.c
Code:
{ "simulate" , "simulate", POS_DEAD , do_simulate , LVL_GOD, 0 },

Add to the end of fight.c
Code:
/* Usage: simulate <character> <character> <number of battles> * * Intended to assist with balancing, pick two characters in the room with you and * Example: simulate WhiskyTest Orc 100 * * I've capped it at 100 battles, be careful of the do-while loop. Everything will pause * until this completes, potentially freezing your MUD while it works out each battle. * * Change the battle routine to suit your MUD combat rolls * Add new counters to track additional data! * */ ACMD(do_simulate) { /* Data we wish to track for storing in the arrays */ #define WINS 0 #define LOSSES 1 /* Add data counters in here, remember to increment MAX_DATA */ #define MAX_DATA 2 char arg1[MAX_INPUT_LENGTH]; /* arguments for the ACMD function */ char arg2[MAX_INPUT_LENGTH]; char arg3[MAX_INPUT_LENGTH]; int number, x, round; /* Number = number of battles, x = generic counter, round = which round this is */ bool round_over; /* Is the round over yet? Controls the do-while loop*/ int actor1_results[MAX_DATA], actor2_results[MAX_DATA]; /* arrays to store our data */ int total_rounds, diceroll; /* total_rounds = for averaging how long each fight was, diceroll = for rand_number(1, 20) */ int actor1_hitp, actor1_damage, actor1_ac, actor1_calc_thaco, actor1_success; /* Actor1 variables */ int actor2_hitp, actor2_damage, actor2_ac, actor2_calc_thaco, actor2_success; /* Actor2 variables */ struct char_data *actor1, *actor2; /* Our two actors for the simulation, actor1 will always hit first */ one_argument(two_arguments(argument, arg1, arg2), arg3); /* three_arguments */ /* Check we have enough arguments */ if(!*arg1 || !*arg2|| !*arg3) { send_to_char(ch, "Usage: simulate <character 1> <character 2> <#number of battles>\r\n"); return; } /* Check for visible characters in room */ if (!(actor1 = get_char_vis(ch, arg1, NULL, FIND_CHAR_ROOM))) { send_to_char(ch, "%s? %s", arg1, CONFIG_NOPERSON); return; } if (!(actor2 = get_char_vis(ch, arg2, NULL, FIND_CHAR_ROOM))) { send_to_char(ch, "%s? %s", arg2, CONFIG_NOPERSON); return; } number = MIN(MAX(atoi(arg3), 0), 100); send_to_char(ch, "OK - I will simulate a battle to the death between \tR%s\tn and \tB%s \tG%d\tn times!\r\n", GET_NAME(actor1), GET_NAME(actor2), number); /* Zero out the arrays and set all variables to starting values */ for (x = 0; x < MAX_DATA; x++) { actor1_results[x] = 0; actor2_results[x] = 0; } total_rounds = 0; /* Set all of Actor 1's battle statistics */ actor1_ac = compute_armor_class(actor1) / 10; actor1_calc_thaco = compute_thaco(actor1, actor2); actor1_damage = str_app[STRENGTH_APPLY_INDEX(actor1)].todam; actor1_damage += GET_DAMROLL(actor1); if(GET_EQ(actor1, WEAR_WIELD) && GET_OBJ_TYPE(GET_EQ(actor1, WEAR_WIELD)) == ITEM_WEAPON ) { actor1_damage += ((GET_OBJ_VAL(GET_EQ(actor1, WEAR_WIELD), 2) + 1) / 2.0) * GET_OBJ_VAL(GET_EQ(actor1, WEAR_WIELD), 1); } else { if (IS_NPC(actor1)) actor1_damage += ((actor1->mob_specials.damsizedice + 1) / 2.0) * actor1->mob_specials.damnodice; else actor1_damage += 1; } if (AFF_FLAGGED(actor2, AFF_SANCTUARY)) actor1_damage /= 2; /* Set all of Actor 2's battle statistics */ actor2_ac = compute_armor_class(actor2) / 10; actor2_calc_thaco = compute_thaco(actor2, actor1); actor2_damage = str_app[STRENGTH_APPLY_INDEX(actor2)].todam; actor2_damage += GET_DAMROLL(actor2); if(GET_EQ(actor2, WEAR_WIELD) && GET_OBJ_TYPE(GET_EQ(actor2, WEAR_WIELD)) == ITEM_WEAPON ) { actor2_damage += ((GET_OBJ_VAL(GET_EQ(actor2, WEAR_WIELD), 2) + 1) / 2.0) * GET_OBJ_VAL(GET_EQ(actor2, WEAR_WIELD), 1); } else { if (IS_NPC(actor2)) actor2_damage += ((actor2->mob_specials.damsizedice + 1) / 2.0) * actor2->mob_specials.damnodice; else actor2_damage += 1; } if (AFF_FLAGGED(actor1, AFF_SANCTUARY)) actor2_damage /= 2; /* Now simulate the combat and record stats! * * Begin battle loop : */ for (x = 1; x <= number; x++) { /* Reset values for new combat */ actor1_hitp = GET_MAX_HIT(actor1); actor2_hitp = GET_MAX_HIT(actor2); round_over = 0; round = 0; /* Start combat rounds until one wins */ while (round_over == 0) { round++; /* Actor1 first */ diceroll = rand_number(1, 20); if (diceroll == 20) actor1_success = 1; else if (diceroll == 1) actor1_success = 0; else if (actor1_calc_thaco - diceroll <= actor2_ac) actor1_success = 1; if (actor1_success) actor2_hitp -= actor1_damage; /* Check to see if Actor 2 is out of the fight */ if (actor2_hitp <= 0) { actor1_results[WINS]++; /* Increment data counters here */ actor2_results[LOSSES]++; round_over = 1; break; } /* Actor 2 has a crack at it */ diceroll = rand_number(1, 20); if (diceroll == 20) actor2_success = 1; else if (diceroll == 1) actor2_success = 0; else if (actor2_calc_thaco - diceroll <= actor1_ac) actor2_success = 1; if (actor2_success) actor1_hitp -= actor2_damage; if (actor1_hitp <= 0) { actor2_results[WINS]++; /* Increment data counters here */ actor1_results[LOSSES]++; round_over = 1; break; } } /* End of combat round */ total_rounds += round; } /* End of Battle loop */ /* Display Results! */ send_to_char(ch, "Results:\r\n"); send_to_char(ch, "Average rounds per fight: %d\r\n", total_rounds / number); send_to_char(ch, "\tR%s\tn wins: \tR%d\tn\r\n", GET_NAME(actor1), actor1_results[WINS]); send_to_char(ch, "\tB%s\tn wins: \tB%d\tn\r\n", GET_NAME(actor2), actor2_results[WINS]); return; }


From just glancing it seems that you need to factor in some more things. Skills, racial bonuses, and the other stats should be considered.

Randian(0.0.0)
Owner/Developer

Please Log in or Create an account to join the conversation.

More
05 Jan 2017 03:14 #6501 by Liko
Replied by Liko on topic How do you Balance your game.
I believe I have figured out how I am going to do the equipment system in my Dragon Ball MUD.
I will had it use randomly generated stats.

I'll have it go something like this.

enemy is killed.
enemy droplist is loaded into corpse.
lets say scouter is loaded into inventory.
When the scouter is created and loaded into inventory it will generate its stats based on the item level.
I programmed a proc that will assign values between certain ilvls.

Ilvl: 10
could have stats beween 1-20.

Saves me time from having to program each item at random.

Randian(0.0.0)
Owner/Developer

Please Log in or Create an account to join the conversation.

Time to create page: 0.189 seconds