diff --git a/commands/dice/rolls.py b/commands/dice/rolls.py index 745a362..2dfaa82 100644 --- a/commands/dice/rolls.py +++ b/commands/dice/rolls.py @@ -29,6 +29,127 @@ class DiceRoll: rolls: list[int] total: int +INFIELD_X_CHART = { + 'si1': { + 'rp': 'Runner on first: Line drive hits the runner! Runner on first is out. Batter goes to first with single ' + 'and all other runners hold.\nNo runner on first: batter singles, runners advance 1 base.', + 'e1': 'Single and Error, batter to second, runners advance 2 bases.', + 'e2': 'Single and Error, batter to third, all runners score.', + 'no': 'Single, runners advance 1 base.' + }, + 'spd': { + 'rp': 'No effect; proceed with speed check', + 'e1': 'Single and Error, batter to second, runners advance 2 bases.', + 'e2': 'Single and Error, batter to third, all runners score.', + 'no': 'Speed check, safe range equals batter\'s running rating, SI* result if safe, gb C if out' + }, + 'po': { + 'rp': 'The batters hits a popup. None of the fielders take charge on the play and the ball drops in the ' + 'infield for a single! All runners advance 1 base.', + 'e1': 'The catcher drops a popup for an error. All runners advance 1 base.', + 'e2': 'The catcher grabs a squib in front of the plate and throws it into right field. The batter goes to ' + 'second and all runners score.', + 'no': 'The batter pops out to the catcher.' + }, + 'wp': { + 'rp': 'Automatic wild pitch. Catcher has trouble finding it and all base runners advance 2 bases.', + 'no': 'Automatic wild pitch, all runners advance 1 base and batter rolls AB again.' + }, + 'x': { + 'rp': 'Runner(s) on base: pitcher trips during his delivery and the ball sails for automatic wild pitch, ' + 'runners advance 1 base and batter rolls AB again.', + 'no': 'Wild pitch check (credited as a PB). If a passed ball occurs, batter rerolls AB. ' + 'If no passed ball occurs, the batter fouls out to the catcher.' + }, + 'fo': { + 'rp': 'Batter swings and misses, but is awarded first base on a catcher interference call! Baserunners advance ' + 'only if forced.', + 'e1': 'The catcher drops a foul popup for an error. Batter rolls AB again.', + 'e2': 'The catcher drops a foul popup for an error. Batter rolls AB again.', + 'no': 'Runner(s) on base: make a passed ball check. If no passed ball, batter pops out to the catcher. If a ' + 'passed ball occurs, batter roll his AB again.\nNo runners: batter pops out to the catcher' + }, + 'g1': { + 'rp': 'Runner on first: runner on first breaks up the double play, but umpires call runner interference and ' + 'the batter is out on GIDP.\nNo runners: Batter grounds out.', + 'e1': 'Error, batter to first, runners advance 1 base.', + 'e2': 'Error, batter to second, runners advance 2 bases.', + 'no': 'Consult Groundball Chart: `!gbA`' + }, + 'g2': { + 'rp': 'Batter lines the ball off the pitcher to the fielder who makes the play to first for the out! Runners ' + 'advance only if forced.', + 'e1': 'Error, batter to first, runners advance 1 base.', + 'e2': 'Error, batter to second, runners advance 2 bases.', + 'no': 'Consult Groundball Chart: `!gbB`' + }, + 'g3': { + 'rp': 'Batter lines the ball off the mound and deflects to the fielder who makes the play to first for the ' + 'out! Runners advance 1 base.', + 'e1': 'Error, batter to first, runners advance 1 base.', + 'e2': 'Error, batter to second, runners advance 2 bases.', + 'no': 'Consult Groundball Chart: `!gbC`' + }, +} +OUTFIELD_X_CHART = { + 'si2': { + 'rp': 'Batter singles, baserunners advance 2 bases. As the batter rounds first, the fielder throws behind him ' + 'and catches him off the bag for an out!', + 'e1': 'Single and error, batter to second, runners advance 2 bases.', + 'e2': 'Single and error, batter to third, all runners score.', + 'e3': 'Single and error, batter to third, all runners score', + 'no': 'Single, all runners advance 2 bases.' + }, + 'do2': { + 'rp': 'Batter doubles, runners advance 2 bases. The outfielder throws the ball to the shortstop who executes a ' + 'hidden ball trick! Runner on second is called out!', + 'e1': 'Double and error, batter to third, all runners score.', + 'e2': 'Double and error, batter to third, and all runners score.', + 'e3': 'Double and error, batter and all runners score. Little league home run!', + 'no': 'Double, all runners advance 2 bases.' + }, + 'do3': { + 'rp': 'Runner(s) on base: batter doubles and runners advance three bases as the outfielders collide!\n' + 'No runners: Batter doubles, but the play is appealed. The umps rule the batter missed first base so is ' + 'out on the appeal!', + 'e1': 'Double and error, batter to third, all runners score.', + 'e2': 'Double and error, batter and all runners score. Little league home run!', + 'e3': 'Double and error, batter and all runners score. Little league home run!', + 'no': 'Double, all runners score.' + }, + 'tr3': { + 'rp': 'Batter hits a ball into the gap and the outfielders collide trying to make the play! The ball rolls to ' + 'the wall and the batter trots home with an inside-the-park home run!', + 'e1': 'Triple and error, batter and all runners score. Little league home run!', + 'e2': 'Triple and error, batter and all runners score. Little league home run!', + 'e3': 'Triple and error, batter and all runners score. Little league home run!', + 'no': 'Triple, all runners score.' + }, + 'f1': { + 'rp': 'The outfielder races back and makes a diving catch and collides with the wall! In the time he takes to ' + 'recuperate, all baserunners tag-up and advance 2 bases.', + 'e1': '1 base error, runners advance 1 base.', + 'e2': '2 base error, runners advance 2 bases.', + 'e3': '3 base error, batter to third, all runners score.', + 'no': 'Flyball A' + }, + 'f2': { + 'rp': 'The outfielder catches the flyball for an out. If there is a runner on third, he tags-up and scores. ' + 'The play is appealed and the umps rule that the runner left early and is out on the appeal!', + 'e1': '1 base error, runners advance 1 base.', + 'e2': '2 base error, runners advance 2 bases.', + 'e3': '3 base error, batter to third, all runners score.', + 'no': 'Flyball B' + }, + 'f3': { + 'rp': 'The outfielder makes a running catch in the gap! The lead runner lost track of the ball and was ' + 'advancing - he cannot return in time and is doubled off by the outfielder.', + 'e1': '1 base error, runners advance 1 base.', + 'e2': '2 base error, runners advance 2 bases.', + 'e3': '3 base error, batter to third, all runners score.', + 'no': 'Flyball C' + } +} class DiceRollCommands(commands.Cog): """Dice rolling command handlers for gameplay.""" @@ -191,16 +312,17 @@ class DiceRollCommands(commands.Cog): ): """Roll Super Advanced fielding dice for a defensive position.""" await interaction.response.defer() + embed_color = await self._get_channel_embed_color(interaction) # Get the position value from the choice pos_value = position.value # Roll the dice - 1d20 and 3d6 - dice_notation = "1d20;3d6" + dice_notation = "1d20;3d6;1d100" roll_results = self._parse_and_roll_multiple_dice(dice_notation) # Create fielding embed - embed = self._create_fielding_embed(pos_value, roll_results, interaction.user) + embed = self._create_fielding_embed(pos_value, roll_results, interaction.user, embed_color) await interaction.followup.send(embed=embed) @commands.command(name="f", aliases=["fielding", "saf"]) @@ -326,16 +448,23 @@ class DiceRollCommands(commands.Cog): return position_map.get(pos) - def _create_fielding_embed(self, position: str, roll_results: list[DiceRoll], user) -> discord.Embed: + def _create_fielding_embed( + self, + position: str, + roll_results: list[DiceRoll], + user: discord.User | discord.Member, + embed_color: int = EmbedColors.PRIMARY + ) -> discord.Embed: """Create an embed for fielding roll results.""" d20_result = roll_results[0].total d6_total = roll_results[1].total d6_rolls = roll_results[1].rolls + d100_result = roll_results[2].total # Create base embed embed = EmbedTemplate.create_base_embed( title=f"SA Fielding roll for {user.display_name}", - color=EmbedColors.PRIMARY + color=embed_color ) # Set user info @@ -364,8 +493,12 @@ class DiceRollCommands(commands.Cog): inline=False ) - # Add error result - error_result = self._get_error_result(position, d6_total) + # Add rare play + if d100_result >= 1: + error_result = self._get_rare_play(position, d20_result) + else: + # Add error result + error_result = self._get_error_result(position, d6_total) if error_result: embed.add_field( name="Error Result", @@ -373,12 +506,12 @@ class DiceRollCommands(commands.Cog): inline=False ) - # # Add help commands - # embed.add_field( - # name="Help Commands", - # value="Run ! for full chart readout (e.g. !g1 or !do3)", - # inline=False - # ) + # Add help commands + embed.add_field( + name="Help Commands", + value="Run /charts for full chart readout", + inline=False + ) # # Add references # embed.add_field( @@ -564,6 +697,26 @@ class DiceRollCommands(commands.Cog): 20: '--------G1---------' } return pitcher_ranges.get(d20_roll, 'Unknown') + + def _get_rare_play(self, position: str, d20_total: int) -> str: + """Get the rare play result for a position and d20 total""" + starter = 'Rare play! Take the range result from above and consult the chart below.\n\n' + if position == 'P': + return starter + self._get_pitcher_rare_play(d20_total) + elif position == '1B': + return starter + self._get_infield_rare_play(d20_total) + elif position == '2B': + return starter + self._get_infield_rare_play(d20_total) + elif position == '3B': + return starter + self._get_infield_rare_play(d20_total) + elif position == 'SS': + return starter + self._get_infield_rare_play(d20_total) + elif position in ['LF', 'RF']: + return starter + self._get_outfield_rare_play(d20_total) + elif position == 'CF': + return starter + self._get_outfield_rare_play(d20_total) + + raise ValueError(f'Unknown position: {position}') def _get_error_result(self, position: str, d6_total: int) -> str: """Get the error result for a position and 3d6 total.""" @@ -723,22 +876,22 @@ class DiceRollCommands(commands.Cog): def _get_catcher_error(self, d6_total: int) -> str: """Get Catcher error result based on 3d6 total.""" errors = { - 18: 'Passed ball for sb2 -> sb12, sb16 -> sb26\nNo error for sb14', - 17: 'Passed ball for sb3 -> sb12, sb17 -> sb26\nNo error for sb1, sb13 -> sb15', - 16: 'Passed ball for sb4 -> sb12, sb18 -> sb26', - 15: 'Passed ball for sb5 -> sb12, sb19 -> sb26', - 14: 'Passed ball for sb6 -> sb12, sb20 -> sb26', - 13: 'Passed ball for sb7 -> sb12, sb21 -> sb26', - 12: 'Passed ball for sb8 -> sb12, sb22 -> sb26', - 11: 'Passed ball for sb9 -> sb12, sb23 -> sb26', - 10: 'Passed ball for sb10 -> sb12, sb24 -> sb26', - 9: 'Passed ball for sb11, sb12, sb25, sb26', + 18: '2-base error for e4 -> 16\n1-base error for e2, e3', + 17: '1-base error for e1, e2, e4, e5, e12 -> e14, e16', + 16: '1-base error for e3 -> e5, e7, e12 -> e14, e16', + 15: '1-base error for e7, e8, e12, e13, e15', + 14: '1-base error for e6', + 13: '1-base error for e9', + 12: '1-base error for e10, e14', + 11: '1-base error for e11, e15', + 10: 'No error', + 9: 'No error', 8: 'No error', - 7: 'No error', - 6: 'No error', + 7: '1-base error for e16', + 6: '1-base error for e8, e12, e13', 5: 'No error', - 4: 'Passed ball for sb1 -> sb12, sb15 -> sb26\nNo error for sb13, sb14', - 3: 'Passed ball for sb1 -> sb26' + 4: '1-base error for e5, e13', + 3: '2-base error for e12 -> e16\n1-base error for e2, e3, e7, e11' } return errors.get(d6_total, 'No error') @@ -764,6 +917,34 @@ class DiceRollCommands(commands.Cog): } return errors.get(d6_total, 'No error') + def _get_pitcher_rare_play(self, d20_total: int) -> str: + return ( + f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n' + f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n' + f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n' + f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n' + ) + + def _get_infield_rare_play(self, d20_total: int) -> str: + return ( + f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n' + f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n' + f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n' + f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n' + f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' + ) + + def _get_outfield_rare_play(self, d20_total: int) -> str: + return ( + f'**F1**: {OUTFIELD_X_CHART["f1"]["rp"]}\n' + f'**F2**: {OUTFIELD_X_CHART["f2"]["rp"]}\n' + f'**F3**: {OUTFIELD_X_CHART["f3"]["rp"]}\n' + f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' + f'**DO2**: {OUTFIELD_X_CHART["do2"]["rp"]}\n' + f'**DO3**: {OUTFIELD_X_CHART["do3"]["rp"]}\n' + f'**TR3**: {OUTFIELD_X_CHART["tr3"]["rp"]}\n' + ) + def _parse_and_roll_multiple_dice(self, dice_notation: str) -> list[DiceRoll]: """Parse dice notation (supports multiple rolls) and return roll results.""" # Split by semicolon for multiple rolls