Merge branch 'master' into maybe_send_embeds
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -3,3 +3,4 @@
 | 
				
			|||||||
venv/
 | 
					venv/
 | 
				
			||||||
v-data/
 | 
					v-data/
 | 
				
			||||||
database.sqlite3
 | 
					database.sqlite3
 | 
				
			||||||
 | 
					/venv3.4/
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ Cog Function
 | 
				
			|||||||
| ccrole | **Release** | <details><summary>Create custom commands that also assign roles</summary>May have some bugs, please create an issue if you find any</details> |
 | 
					| ccrole | **Release** | <details><summary>Create custom commands that also assign roles</summary>May have some bugs, please create an issue if you find any</details> |
 | 
				
			||||||
| chatter | **Beta** | <details><summary>Chat-bot trained to talk like your guild</summary>Missing some key features, but currently functional. See [Chatter](https://github.com/bobloy/Fox-V3/tree/master/chatter) for install instructions</details> |
 | 
					| chatter | **Beta** | <details><summary>Chat-bot trained to talk like your guild</summary>Missing some key features, but currently functional. See [Chatter](https://github.com/bobloy/Fox-V3/tree/master/chatter) for install instructions</details> |
 | 
				
			||||||
| coglint | **Alpha** | <details><summary>Error check code in python syntax posted to discord</summary>Works, but probably needs more turning to work for cogs</details> |
 | 
					| coglint | **Alpha** | <details><summary>Error check code in python syntax posted to discord</summary>Works, but probably needs more turning to work for cogs</details> |
 | 
				
			||||||
 | 
					| conquest | **Alpha** | <details><summary>Manage maps for war games and RPGs</summary>Lots of additional features are planned, currently function with simple map</details> |
 | 
				
			||||||
| dad | **Beta** | <details><summary>Tell dad jokes</summary>Works great!</details> |
 | 
					| dad | **Beta** | <details><summary>Tell dad jokes</summary>Works great!</details> |
 | 
				
			||||||
| exclusiverole | **Alpha** | <details><summary>Prevent certain roles from getting any other roles</summary>Fully functional, but pretty simple</details> |
 | 
					| exclusiverole | **Alpha** | <details><summary>Prevent certain roles from getting any other roles</summary>Fully functional, but pretty simple</details> |
 | 
				
			||||||
| fight | **Incomplete** | <details><summary>Organize bracket tournaments within discord</summary>Still in-progress, a massive project</details> |
 | 
					| fight | **Incomplete** | <details><summary>Organize bracket tournaments within discord</summary>Still in-progress, a massive project</details> |
 | 
				
			||||||
@ -20,7 +21,7 @@ Cog Function
 | 
				
			|||||||
| infochannel | **Beta** | <details><summary>Create a channel to display server info</summary>Just released, please report bugs</details> |
 | 
					| infochannel | **Beta** | <details><summary>Create a channel to display server info</summary>Just released, please report bugs</details> |
 | 
				
			||||||
| lovecalculator | **Alpha** | <details><summary>Calculate the love between two users</summary>[Snap-Ons] Just updated to V3</details> |
 | 
					| lovecalculator | **Alpha** | <details><summary>Calculate the love between two users</summary>[Snap-Ons] Just updated to V3</details> |
 | 
				
			||||||
| lseen | **Alpha** | <details><summary>Track when a member was last online</summary>Alpha release, please report bugs</details> |
 | 
					| lseen | **Alpha** | <details><summary>Track when a member was last online</summary>Alpha release, please report bugs</details> |
 | 
				
			||||||
| nudity | **Incomplete** | <details><summary>Checks for NSFW images posted in non-NSFW channels</summary>Library this is based on has a bug, waiting for author to merge my PR</details> |
 | 
					| nudity | **Alpha** | <details><summary>Checks for NSFW images posted in non-NSFW channels</summary>Switched libraries, now functional</details> |
 | 
				
			||||||
| planttycoon | **Alpha** | <details><summary>Grow your own plants!</summary>[Snap-Ons] Updated to V3, likely to contain bugs</details> |
 | 
					| planttycoon | **Alpha** | <details><summary>Grow your own plants!</summary>[Snap-Ons] Updated to V3, likely to contain bugs</details> |
 | 
				
			||||||
| qrinvite | **Alpha** | <details><summary>Create a QR code invite for the server</summary>Alpha release, please report any bugs</details> |
 | 
					| qrinvite | **Alpha** | <details><summary>Create a QR code invite for the server</summary>Alpha release, please report any bugs</details> |
 | 
				
			||||||
| reactrestrict | **Alpha** | <details><summary>Removes reactions by role per channel</summary>A bit clunky, but functional</details> |
 | 
					| reactrestrict | **Alpha** | <details><summary>Removes reactions by role per channel</summary>A bit clunky, but functional</details> |
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,10 @@ class AnnounceDaily(Cog):
 | 
				
			|||||||
        self.config.register_global(**default_global)
 | 
					        self.config.register_global(**default_global)
 | 
				
			||||||
        self.config.register_guild(**default_guild)
 | 
					        self.config.register_guild(**default_guild)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def red_delete_data_for_user(self, **kwargs):
 | 
				
			||||||
 | 
					        """Nothing to delete"""
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def _get_msgs(self):
 | 
					    async def _get_msgs(self):
 | 
				
			||||||
        return DEFAULT_MESSAGES + await self.config.messages()
 | 
					        return DEFAULT_MESSAGES + await self.config.messages()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,15 +2,12 @@
 | 
				
			|||||||
  "author": [
 | 
					  "author": [
 | 
				
			||||||
    "Bobloy"
 | 
					    "Bobloy"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "bot_version": [
 | 
					  "min_bot_version": "3.3.0",
 | 
				
			||||||
    3,
 | 
					 | 
				
			||||||
    0,
 | 
					 | 
				
			||||||
    0
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "description": "Send daily announcements to all servers at a specified times",
 | 
					  "description": "Send daily announcements to all servers at a specified times",
 | 
				
			||||||
  "hidden": true,
 | 
					  "hidden": false,
 | 
				
			||||||
  "install_msg": "Thank you for installing AnnounceDaily! Get started with `[p]load announcedaily` and `[p]help AnnounceDaily`",
 | 
					  "install_msg": "Thank you for installing AnnounceDaily! Get started with `[p]load announcedaily` and `[p]help AnnounceDaily`",
 | 
				
			||||||
  "short": "Send daily announcements",
 | 
					  "short": "Send daily announcements",
 | 
				
			||||||
 | 
					  "end_user_data_statement": "This cog does not store any End User Data",
 | 
				
			||||||
  "tags": [
 | 
					  "tags": [
 | 
				
			||||||
    "bobloy"
 | 
					    "bobloy"
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ from redbot.core import commands, Config, checks
 | 
				
			|||||||
from redbot.core.bot import Red
 | 
					from redbot.core.bot import Red
 | 
				
			||||||
from redbot.core.data_manager import cog_data_path
 | 
					from redbot.core.data_manager import cog_data_path
 | 
				
			||||||
from redbot.core.utils.chat_formatting import box
 | 
					from redbot.core.utils.chat_formatting import box
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# from redbot.cogs.audio.utils import userlimit
 | 
					# from redbot.cogs.audio.utils import userlimit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,7 +66,9 @@ class AudioTrivia(Trivia):
 | 
				
			|||||||
        """Set whether or not short audio will be repeated"""
 | 
					        """Set whether or not short audio will be repeated"""
 | 
				
			||||||
        settings = self.audioconf.guild(ctx.guild)
 | 
					        settings = self.audioconf.guild(ctx.guild)
 | 
				
			||||||
        await settings.repeat.set(true_or_false)
 | 
					        await settings.repeat.set(true_or_false)
 | 
				
			||||||
        await ctx.send("Done. Repeating short audio is now set to {}.".format(true_or_false))
 | 
					        await ctx.send(
 | 
				
			||||||
 | 
					            "Done. Repeating short audio is now set to {}.".format(true_or_false)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @commands.group(invoke_without_command=True)
 | 
					    @commands.group(invoke_without_command=True)
 | 
				
			||||||
    @commands.guild_only()
 | 
					    @commands.guild_only()
 | 
				
			||||||
@ -89,19 +92,25 @@ class AudioTrivia(Trivia):
 | 
				
			|||||||
        categories = [c.lower() for c in categories]
 | 
					        categories = [c.lower() for c in categories]
 | 
				
			||||||
        session = self._get_trivia_session(ctx.channel)
 | 
					        session = self._get_trivia_session(ctx.channel)
 | 
				
			||||||
        if session is not None:
 | 
					        if session is not None:
 | 
				
			||||||
            await ctx.send("There is already an ongoing trivia session in this channel.")
 | 
					            await ctx.send(
 | 
				
			||||||
 | 
					                "There is already an ongoing trivia session in this channel."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        status = await self.audio.config.status()
 | 
					        status = await self.audio.config.status()
 | 
				
			||||||
        notify = await self.audio.config.guild(ctx.guild).notify()
 | 
					        notify = await self.audio.config.guild(ctx.guild).notify()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if status:
 | 
					        if status:
 | 
				
			||||||
            await ctx.send(
 | 
					            await ctx.send(
 | 
				
			||||||
                "It is recommended to disable audio status with `{}audioset status`".format(ctx.prefix)
 | 
					                "It is recommended to disable audio status with `{}audioset status`".format(
 | 
				
			||||||
 | 
					                    ctx.prefix
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if notify:
 | 
					        if notify:
 | 
				
			||||||
            await ctx.send(
 | 
					            await ctx.send(
 | 
				
			||||||
                "It is recommended to disable audio notify with `{}audioset notify`".format(ctx.prefix)
 | 
					                "It is recommended to disable audio notify with `{}audioset notify`".format(
 | 
				
			||||||
 | 
					                    ctx.prefix
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not self.audio._player_check(ctx):
 | 
					        if not self.audio._player_check(ctx):
 | 
				
			||||||
@ -109,7 +118,9 @@ class AudioTrivia(Trivia):
 | 
				
			|||||||
                if not ctx.author.voice.channel.permissions_for(
 | 
					                if not ctx.author.voice.channel.permissions_for(
 | 
				
			||||||
                    ctx.me
 | 
					                    ctx.me
 | 
				
			||||||
                ).connect or self.audio.is_vc_full(ctx.author.voice.channel):
 | 
					                ).connect or self.audio.is_vc_full(ctx.author.voice.channel):
 | 
				
			||||||
                    return await ctx.send("I don't have permission to connect to your channel.")
 | 
					                    return await ctx.send(
 | 
				
			||||||
 | 
					                        "I don't have permission to connect to your channel."
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                await lavalink.connect(ctx.author.voice.channel)
 | 
					                await lavalink.connect(ctx.author.voice.channel)
 | 
				
			||||||
                lavaplayer = lavalink.get_player(ctx.guild.id)
 | 
					                lavaplayer = lavalink.get_player(ctx.guild.id)
 | 
				
			||||||
                lavaplayer.store("connect", datetime.datetime.utcnow())
 | 
					                lavaplayer.store("connect", datetime.datetime.utcnow())
 | 
				
			||||||
@ -166,7 +177,10 @@ class AudioTrivia(Trivia):
 | 
				
			|||||||
        # Delay in audiosettings overwrites delay in settings
 | 
					        # Delay in audiosettings overwrites delay in settings
 | 
				
			||||||
        combined_settings = {**settings, **audiosettings}
 | 
					        combined_settings = {**settings, **audiosettings}
 | 
				
			||||||
        session = AudioSession.start(
 | 
					        session = AudioSession.start(
 | 
				
			||||||
            ctx=ctx, question_list=trivia_dict, settings=combined_settings, player=lavaplayer
 | 
					            ctx=ctx,
 | 
				
			||||||
 | 
					            question_list=trivia_dict,
 | 
				
			||||||
 | 
					            settings=combined_settings,
 | 
				
			||||||
 | 
					            player=lavaplayer,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.trivia_sessions.append(session)
 | 
					        self.trivia_sessions.append(session)
 | 
				
			||||||
        LOG.debug("New audio trivia session; #%s in %d", ctx.channel, ctx.guild.id)
 | 
					        LOG.debug("New audio trivia session; #%s in %d", ctx.channel, ctx.guild.id)
 | 
				
			||||||
@ -200,7 +214,9 @@ class AudioTrivia(Trivia):
 | 
				
			|||||||
        try:
 | 
					        try:
 | 
				
			||||||
            path = next(p for p in self._audio_lists() if p.stem == category)
 | 
					            path = next(p for p in self._audio_lists() if p.stem == category)
 | 
				
			||||||
        except StopIteration:
 | 
					        except StopIteration:
 | 
				
			||||||
            raise FileNotFoundError("Could not find the `{}` category.".format(category))
 | 
					            raise FileNotFoundError(
 | 
				
			||||||
 | 
					                "Could not find the `{}` category.".format(category)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with path.open(encoding="utf-8") as file:
 | 
					        with path.open(encoding="utf-8") as file:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
 | 
				
			|||||||
@ -2,15 +2,12 @@
 | 
				
			|||||||
  "author": [
 | 
					  "author": [
 | 
				
			||||||
    "Bobloy"
 | 
					    "Bobloy"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "bot_version": [
 | 
					  "min_bot_version": "3.3.0",
 | 
				
			||||||
    3,
 | 
					 | 
				
			||||||
    0,
 | 
					 | 
				
			||||||
    0
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "description": "Start an Audio Trivia game",
 | 
					  "description": "Start an Audio Trivia game",
 | 
				
			||||||
  "hidden": false,
 | 
					  "hidden": false,
 | 
				
			||||||
  "install_msg": "Thank you for installing Audio trivia!\n You **MUST** unload trivia to use this (`[p]unload trivia`)\n Then you can get started with `[p]load audiotrivia` and `[p]help AudioTrivia`",
 | 
					  "install_msg": "Thank you for installing Audio trivia!\n You **MUST** unload trivia to use this (`[p]unload trivia`)\n Then you can get started with `[p]load audiotrivia` and `[p]help AudioTrivia`",
 | 
				
			||||||
  "short": "Start an Audio Trivia game",
 | 
					  "short": "Start an Audio Trivia game",
 | 
				
			||||||
 | 
					  "end_user_data_statement": "This cog expands the core Audio and Trivia cogs without collecting any additional End User Data.\nSee the core End User Data storage for more information",
 | 
				
			||||||
  "tags": [
 | 
					  "tags": [
 | 
				
			||||||
    "fox",
 | 
					    "fox",
 | 
				
			||||||
    "bobloy",
 | 
					    "bobloy",
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,10 @@ class CCRole(commands.Cog):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.config.register_guild(**default_guild)
 | 
					        self.config.register_guild(**default_guild)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def red_delete_data_for_user(self, **kwargs):
 | 
				
			||||||
 | 
					        """Nothing to delete"""
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @commands.guild_only()
 | 
					    @commands.guild_only()
 | 
				
			||||||
    @commands.group()
 | 
					    @commands.group()
 | 
				
			||||||
    async def ccrole(self, ctx: commands.Context):
 | 
					    async def ccrole(self, ctx: commands.Context):
 | 
				
			||||||
@ -29,7 +33,7 @@ class CCRole(commands.Cog):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        Highly customizable custom commands with role management."""
 | 
					        Highly customizable custom commands with role management."""
 | 
				
			||||||
        if not ctx.invoked_subcommand:
 | 
					        if not ctx.invoked_subcommand:
 | 
				
			||||||
            await ctx.send_help()
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @ccrole.command(name="add")
 | 
					    @ccrole.command(name="add")
 | 
				
			||||||
    @checks.mod_or_permissions(administrator=True)
 | 
					    @checks.mod_or_permissions(administrator=True)
 | 
				
			||||||
 | 
				
			|||||||
@ -2,15 +2,12 @@
 | 
				
			|||||||
  "author": [
 | 
					  "author": [
 | 
				
			||||||
    "Bobloy"
 | 
					    "Bobloy"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "bot_version": [
 | 
					  "min_bot_version": "3.3.0",
 | 
				
			||||||
    3,
 | 
					  "description": "Creates custom commands to adjust roles and send custom messages",
 | 
				
			||||||
    0,
 | 
					 | 
				
			||||||
    0
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "description": "[Incomplete] Creates custom commands to adjust roles and send custom messages",
 | 
					 | 
				
			||||||
  "hidden": false,
 | 
					  "hidden": false,
 | 
				
			||||||
  "install_msg": "Thank you for installing Custom Commands w/ Roles. Get started with `[p]load ccrole` and `[p]help CCRole`",
 | 
					  "install_msg": "Thank you for installing Custom Commands w/ Roles. Get started with `[p]load ccrole` and `[p]help CCRole`",
 | 
				
			||||||
  "short": "[Incomplete] Creates commands that adjust roles",
 | 
					  "short": "Creates commands that adjust roles",
 | 
				
			||||||
 | 
					  "end_user_data_statement": "This cog does not store any End User Data",
 | 
				
			||||||
  "tags": [
 | 
					  "tags": [
 | 
				
			||||||
    "fox",
 | 
					    "fox",
 | 
				
			||||||
    "bobloy",
 | 
					    "bobloy",
 | 
				
			||||||
 | 
				
			|||||||
@ -162,12 +162,53 @@ This command trains Chatter on the specified channel based on the configured
 | 
				
			|||||||
settings. This can take a long time to process.
 | 
					settings. This can take a long time to process.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Train Ubuntu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[p]chatter trainubuntu
 | 
				
			||||||
 | 
					``` 
 | 
				
			||||||
 | 
					*WARNING:* This will trigger a large download and use a lot of processing power
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This command trains Chatter on the publicly available Ubuntu Dialogue Corpus. (It'll talk like a geek)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Switching Algorithms
 | 
					## Switching Algorithms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
[p]chatter algorithm X
 | 
					[p]chatter algorithm X
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					or
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[p]chatter algo X 0.95
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Chatter can be configured to use one of three different Similarity algorithms.
 | 
					Chatter can be configured to use one of three different Similarity algorithms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Changing this can help if the response speed is too slow, but can reduce the accuracy of results.
 | 
					Changing this can help if the response speed is too slow, but can reduce the accuracy of results.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The second argument is the maximum similarity threshold,
 | 
				
			||||||
 | 
					lowering that will make the bot stop searching sooner.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Default maximum similarity threshold is 0.90
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Switching Pretrained Models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[p]chatter model X
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Chatter can be configured to use one of three pretrained statistical models for English.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I have not noticed any advantage to changing this, 
 | 
				
			||||||
 | 
					but supposedly it would help by splitting the search term into more useful parts.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See [here](https://spacy.io/models) for more info on spaCy models.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Before you're able to use the *large* model (option 3), you must install it through pip.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Warning:* This is ~800MB download.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[p]pipinstall https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-2.3.1/en_core_web_lg-2.3.1.tar.gz#egg=en_core_web_lg
 | 
				
			||||||
 | 
					``` 
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										136
									
								
								chatter/chat.py
									
									
									
									
									
								
							
							
						
						@ -1,19 +1,25 @@
 | 
				
			|||||||
import asyncio
 | 
					import asyncio
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import pathlib
 | 
					import pathlib
 | 
				
			||||||
from datetime import datetime, timedelta
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					from typing import Literal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import discord
 | 
					import discord
 | 
				
			||||||
from chatterbot import ChatBot
 | 
					from chatterbot import ChatBot
 | 
				
			||||||
from chatterbot.comparisons import JaccardSimilarity, LevenshteinDistance, SpacySimilarity
 | 
					from chatterbot.comparisons import JaccardSimilarity, LevenshteinDistance, SpacySimilarity
 | 
				
			||||||
from chatterbot.response_selection import get_random_response
 | 
					from chatterbot.response_selection import get_random_response
 | 
				
			||||||
from chatterbot.trainers import ChatterBotCorpusTrainer, ListTrainer
 | 
					from chatterbot.trainers import ChatterBotCorpusTrainer, ListTrainer, UbuntuCorpusTrainer
 | 
				
			||||||
from redbot.core import Config, commands
 | 
					from redbot.core import Config, commands
 | 
				
			||||||
from redbot.core.commands import Cog
 | 
					from redbot.core.commands import Cog
 | 
				
			||||||
from redbot.core.data_manager import cog_data_path
 | 
					from redbot.core.data_manager import cog_data_path
 | 
				
			||||||
 | 
					from redbot.core.utils.predicates import MessagePredicate
 | 
				
			||||||
 | 
					from redbot.core.utils import AsyncIter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log = logging.getLogger("red.fox_v3.chat")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ENG_LG:  # TODO: Add option to use this large model
 | 
					class ENG_LG:
 | 
				
			||||||
    ISO_639_1 = "en_core_web_lg"
 | 
					    ISO_639_1 = "en_core_web_lg"
 | 
				
			||||||
    ISO_639 = "eng"
 | 
					    ISO_639 = "eng"
 | 
				
			||||||
    ENGLISH_NAME = "English"
 | 
					    ENGLISH_NAME = "English"
 | 
				
			||||||
@ -25,6 +31,12 @@ class ENG_MD:
 | 
				
			|||||||
    ENGLISH_NAME = "English"
 | 
					    ENGLISH_NAME = "English"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ENG_SM:
 | 
				
			||||||
 | 
					    ISO_639_1 = "en_core_web_sm"
 | 
				
			||||||
 | 
					    ISO_639 = "eng"
 | 
				
			||||||
 | 
					    ENGLISH_NAME = "English"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Chatter(Cog):
 | 
					class Chatter(Cog):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    This cog trains a chatbot that will talk like members of your Guild
 | 
					    This cog trains a chatbot that will talk like members of your Guild
 | 
				
			||||||
@ -39,7 +51,13 @@ class Chatter(Cog):
 | 
				
			|||||||
        path: pathlib.Path = cog_data_path(self)
 | 
					        path: pathlib.Path = cog_data_path(self)
 | 
				
			||||||
        self.data_path = path / "database.sqlite3"
 | 
					        self.data_path = path / "database.sqlite3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.chatbot = self._create_chatbot(self.data_path, SpacySimilarity, 0.45, ENG_MD)
 | 
					        # TODO: Move training_model and similarity_algo to config
 | 
				
			||||||
 | 
					        # TODO: Add an option to see current settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.tagger_language = ENG_MD
 | 
				
			||||||
 | 
					        self.similarity_algo = SpacySimilarity
 | 
				
			||||||
 | 
					        self.similarity_threshold = 0.90
 | 
				
			||||||
 | 
					        self.chatbot = self._create_chatbot()
 | 
				
			||||||
        # self.chatbot.set_trainer(ListTrainer)
 | 
					        # self.chatbot.set_trainer(ListTrainer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # self.trainer = ListTrainer(self.chatbot)
 | 
					        # self.trainer = ListTrainer(self.chatbot)
 | 
				
			||||||
@ -49,18 +67,22 @@ class Chatter(Cog):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.loop = asyncio.get_event_loop()
 | 
					        self.loop = asyncio.get_event_loop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _create_chatbot(
 | 
					    async def red_delete_data_for_user(self, **kwargs):
 | 
				
			||||||
            self, data_path, similarity_algorithm, similarity_threshold, tagger_language
 | 
					        """Nothing to delete"""
 | 
				
			||||||
    ):
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _create_chatbot(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ChatBot(
 | 
					        return ChatBot(
 | 
				
			||||||
            "ChatterBot",
 | 
					            "ChatterBot",
 | 
				
			||||||
            storage_adapter="chatterbot.storage.SQLStorageAdapter",
 | 
					            storage_adapter="chatterbot.storage.SQLStorageAdapter",
 | 
				
			||||||
            database_uri="sqlite:///" + str(data_path),
 | 
					            database_uri="sqlite:///" + str(self.data_path),
 | 
				
			||||||
            statement_comparison_function=similarity_algorithm,
 | 
					            statement_comparison_function=self.similarity_algo,
 | 
				
			||||||
            response_selection_method=get_random_response,
 | 
					            response_selection_method=get_random_response,
 | 
				
			||||||
            logic_adapters=["chatterbot.logic.BestMatch"],
 | 
					            logic_adapters=["chatterbot.logic.BestMatch"],
 | 
				
			||||||
            # maximum_similarity_threshold=similarity_threshold,
 | 
					            maximum_similarity_threshold=self.similarity_threshold,
 | 
				
			||||||
            tagger_language=tagger_language,
 | 
					            tagger_language=self.tagger_language,
 | 
				
			||||||
 | 
					            logger=log,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def _get_conversation(self, ctx, in_channel: discord.TextChannel = None):
 | 
					    async def _get_conversation(self, ctx, in_channel: discord.TextChannel = None):
 | 
				
			||||||
@ -99,7 +121,7 @@ class Chatter(Cog):
 | 
				
			|||||||
            try:
 | 
					            try:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                async for message in channel.history(
 | 
					                async for message in channel.history(
 | 
				
			||||||
                        limit=None, after=after, oldest_first=True
 | 
					                    limit=None, after=after, oldest_first=True
 | 
				
			||||||
                ).filter(
 | 
					                ).filter(
 | 
				
			||||||
                    predicate=predicate
 | 
					                    predicate=predicate
 | 
				
			||||||
                ):  # type: discord.Message
 | 
					                ):  # type: discord.Message
 | 
				
			||||||
@ -130,6 +152,11 @@ class Chatter(Cog):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return out
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _train_ubuntu(self):
 | 
				
			||||||
 | 
					        trainer = UbuntuCorpusTrainer(self.chatbot)
 | 
				
			||||||
 | 
					        trainer.train()
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _train_english(self):
 | 
					    def _train_english(self):
 | 
				
			||||||
        trainer = ChatterBotCorpusTrainer(self.chatbot)
 | 
					        trainer = ChatterBotCorpusTrainer(self.chatbot)
 | 
				
			||||||
        # try:
 | 
					        # try:
 | 
				
			||||||
@ -182,14 +209,18 @@ class Chatter(Cog):
 | 
				
			|||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    os.remove(self.data_path)
 | 
					                    os.remove(self.data_path)
 | 
				
			||||||
                except PermissionError:
 | 
					                except PermissionError:
 | 
				
			||||||
                    await ctx.maybe_send_embed("Failed to clear training database. Please wait a bit and try again")
 | 
					                    await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                        "Failed to clear training database. Please wait a bit and try again"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self._create_chatbot(self.data_path, SpacySimilarity, 0.45, ENG_MD)
 | 
					            self._create_chatbot()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await ctx.tick()
 | 
					        await ctx.tick()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @chatter.command(name="algorithm")
 | 
					    @chatter.command(name="algorithm", aliases=["algo"])
 | 
				
			||||||
    async def chatter_algorithm(self, ctx: commands.Context, algo_number: int):
 | 
					    async def chatter_algorithm(
 | 
				
			||||||
 | 
					            self, ctx: commands.Context, algo_number: int, threshold: float = None
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Switch the active logic algorithm to one of the three. Default after reload is Spacy
 | 
					        Switch the active logic algorithm to one of the three. Default after reload is Spacy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -198,17 +229,61 @@ class Chatter(Cog):
 | 
				
			|||||||
        2: Levenshtein
 | 
					        2: Levenshtein
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        algos = [(SpacySimilarity, 0.45), (JaccardSimilarity, 0.75), (LevenshteinDistance, 0.75)]
 | 
					        algos = [SpacySimilarity, JaccardSimilarity, LevenshteinDistance]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if algo_number < 0 or algo_number > 2:
 | 
					        if algo_number < 0 or algo_number > 2:
 | 
				
			||||||
            await ctx.send_help()
 | 
					            await ctx.send_help()
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.chatbot = self._create_chatbot(
 | 
					        if threshold is not None:
 | 
				
			||||||
            self.data_path, algos[algo_number][0], algos[algo_number][1], ENG_MD
 | 
					            if threshold >= 1 or threshold <= 0:
 | 
				
			||||||
        )
 | 
					                await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                    "Threshold must be a number between 0 and 1 (exclusive)"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.similarity_algo = threshold
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await ctx.tick()
 | 
					        self.similarity_algo = algos[algo_number]
 | 
				
			||||||
 | 
					        async with ctx.typing():
 | 
				
			||||||
 | 
					            self.chatbot = self._create_chatbot()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await ctx.tick()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @chatter.command(name="model")
 | 
				
			||||||
 | 
					    async def chatter_model(self, ctx: commands.Context, model_number: int):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Switch the active model to one of the three. Default after reload is Medium
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        0: Small
 | 
				
			||||||
 | 
					        1: Medium
 | 
				
			||||||
 | 
					        2: Large (Requires additional setup)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        models = [ENG_SM, ENG_MD, ENG_LG]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if model_number < 0 or model_number > 2:
 | 
				
			||||||
 | 
					            await ctx.send_help()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if model_number == 2:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                "Additional requirements needed. See guide before continuing.\n" "Continue?"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            pred = MessagePredicate.yes_or_no(ctx)
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                await self.bot.wait_for("message", check=pred, timeout=30)
 | 
				
			||||||
 | 
					            except TimeoutError:
 | 
				
			||||||
 | 
					                await ctx.send("Response timed out, please try again later.")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            if not pred.result:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.tagger_language = models[model_number]
 | 
				
			||||||
 | 
					        async with ctx.typing():
 | 
				
			||||||
 | 
					            self.chatbot = self._create_chatbot()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(f"Model has been switched to {self.tagger_language.ISO_639_1}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @chatter.command(name="minutes")
 | 
					    @chatter.command(name="minutes")
 | 
				
			||||||
    async def minutes(self, ctx: commands.Context, minutes: int):
 | 
					    async def minutes(self, ctx: commands.Context, minutes: int):
 | 
				
			||||||
@ -260,6 +335,27 @@ class Chatter(Cog):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            await ctx.maybe_send_embed("Error occurred :(")
 | 
					            await ctx.maybe_send_embed("Error occurred :(")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @chatter.command(name="trainubuntu")
 | 
				
			||||||
 | 
					    async def chatter_train_ubuntu(self, ctx: commands.Context, confirmation: bool = False):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        WARNING: Large Download! Trains the bot using Ubuntu Dialog Corpus data.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not confirmation:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                "Warning: This command downloads ~500MB then eats your CPU for training\n"
 | 
				
			||||||
 | 
					                "If you're sure you want to continue, run `[p]chatter trainubuntu True`"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        async with ctx.typing():
 | 
				
			||||||
 | 
					            future = await self.loop.run_in_executor(None, self._train_ubuntu)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if future:
 | 
				
			||||||
 | 
					            await ctx.send("Training successful!")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await ctx.send("Error occurred :(")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @chatter.command(name="trainenglish")
 | 
					    @chatter.command(name="trainenglish")
 | 
				
			||||||
    async def chatter_train_english(self, ctx: commands.Context):
 | 
					    async def chatter_train_english(self, ctx: commands.Context):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
				
			|||||||
@ -2,11 +2,7 @@
 | 
				
			|||||||
  "author": [
 | 
					  "author": [
 | 
				
			||||||
    "Bobloy"
 | 
					    "Bobloy"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "bot_version": [
 | 
					  "min_bot_version": "3.3.10",
 | 
				
			||||||
    3,
 | 
					 | 
				
			||||||
    3,
 | 
					 | 
				
			||||||
    10
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "description": "Create an offline chatbot that talks like your average member using Machine Learning",
 | 
					  "description": "Create an offline chatbot that talks like your average member using Machine Learning",
 | 
				
			||||||
  "hidden": false,
 | 
					  "hidden": false,
 | 
				
			||||||
  "install_msg": "Thank you for installing Chatter! Get started ith `[p]load chatter` and `[p]help Chatter`",
 | 
					  "install_msg": "Thank you for installing Chatter! Get started ith `[p]load chatter` and `[p]help Chatter`",
 | 
				
			||||||
@ -24,6 +20,7 @@
 | 
				
			|||||||
    "spacy>=2.3,<2.4"
 | 
					    "spacy>=2.3,<2.4"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "short": "Local Chatbot run on machine learning",
 | 
					  "short": "Local Chatbot run on machine learning",
 | 
				
			||||||
 | 
					  "end_user_data_statement": "This cog only stores anonymous conversations data; no End User Data is stored.",
 | 
				
			||||||
  "tags": [
 | 
					  "tags": [
 | 
				
			||||||
    "chat",
 | 
					    "chat",
 | 
				
			||||||
    "chatbot",
 | 
					    "chatbot",
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,10 @@ class CogLint(Cog):
 | 
				
			|||||||
        self.config.register_global(**default_global)
 | 
					        self.config.register_global(**default_global)
 | 
				
			||||||
        self.config.register_guild(**default_guild)
 | 
					        self.config.register_guild(**default_guild)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def red_delete_data_for_user(self, **kwargs):
 | 
				
			||||||
 | 
					        """Nothing to delete"""
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @commands.command()
 | 
					    @commands.command()
 | 
				
			||||||
    async def autolint(self, ctx: commands.Context):
 | 
					    async def autolint(self, ctx: commands.Context):
 | 
				
			||||||
        """Toggles automatically linting code"""
 | 
					        """Toggles automatically linting code"""
 | 
				
			||||||
 | 
				
			|||||||
@ -2,16 +2,15 @@
 | 
				
			|||||||
  "author": [
 | 
					  "author": [
 | 
				
			||||||
    "Bobloy"
 | 
					    "Bobloy"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "bot_version": [
 | 
					  "min_bot_version": "3.3.0",
 | 
				
			||||||
    3,
 | 
					 | 
				
			||||||
    0,
 | 
					 | 
				
			||||||
    0
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "description": "Lint python code posted in chat",
 | 
					  "description": "Lint python code posted in chat",
 | 
				
			||||||
  "hidden": true,
 | 
					  "hidden": true,
 | 
				
			||||||
  "install_msg": "Thank you for installing CogLint! Get started with `[p]load coglint` and `[p]help CogLint`",
 | 
					  "install_msg": "Thank you for installing CogLint! Get started with `[p]load coglint` and `[p]help CogLint`",
 | 
				
			||||||
  "requirements": ["pylint"],
 | 
					  "requirements": [
 | 
				
			||||||
 | 
					    "pylint"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "short": "Python cog linter",
 | 
					  "short": "Python cog linter",
 | 
				
			||||||
 | 
					  "end_user_data_statement": "This cog does not store any End User Data",
 | 
				
			||||||
  "tags": [
 | 
					  "tags": [
 | 
				
			||||||
    "bobloy",
 | 
					    "bobloy",
 | 
				
			||||||
    "utils",
 | 
					    "utils",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								conquest/Map Ideas/AxisAllies_MAP_006L.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/Map Ideas/j0e88vlp28pz.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 144 KiB  | 
							
								
								
									
										15
									
								
								conquest/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					from redbot.core import data_manager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .conquest import Conquest
 | 
				
			||||||
 | 
					from .mapmaker import MapMaker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def setup(bot):
 | 
				
			||||||
 | 
					    cog = Conquest(bot)
 | 
				
			||||||
 | 
					    data_manager.bundled_data_path(cog)
 | 
				
			||||||
 | 
					    await cog.load_data()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bot.add_cog(cog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cog2 = MapMaker(bot)
 | 
				
			||||||
 | 
					    bot.add_cog(cog2)
 | 
				
			||||||
							
								
								
									
										410
									
								
								conquest/conquest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,410 @@
 | 
				
			|||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import pathlib
 | 
				
			||||||
 | 
					from abc import ABC
 | 
				
			||||||
 | 
					from shutil import copyfile
 | 
				
			||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import discord
 | 
				
			||||||
 | 
					from PIL import Image, ImageChops, ImageColor, ImageOps
 | 
				
			||||||
 | 
					from discord.ext.commands import Greedy
 | 
				
			||||||
 | 
					from redbot.core import Config, commands
 | 
				
			||||||
 | 
					from redbot.core.bot import Red
 | 
				
			||||||
 | 
					from redbot.core.data_manager import bundled_data_path, cog_data_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Conquest(commands.Cog):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Cog for
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default_zoom_json = {"enabled": False, "x": -1, "y": -1, "zoom": 1.0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, bot: Red):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        self.bot = bot
 | 
				
			||||||
 | 
					        self.config = Config.get_conf(
 | 
				
			||||||
 | 
					            self, identifier=67111110113117101115116, force_registration=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default_guild = {}
 | 
				
			||||||
 | 
					        default_global = {"current_map": None}
 | 
				
			||||||
 | 
					        self.config.register_guild(**default_guild)
 | 
				
			||||||
 | 
					        self.config.register_global(**default_global)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.data_path: pathlib.Path = cog_data_path(self)
 | 
				
			||||||
 | 
					        self.asset_path: Optional[pathlib.Path] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.current_map = None
 | 
				
			||||||
 | 
					        self.map_data = None
 | 
				
			||||||
 | 
					        self.ext = None
 | 
				
			||||||
 | 
					        self.ext_format = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def red_delete_data_for_user(self, **kwargs):
 | 
				
			||||||
 | 
					        """Nothing to delete"""
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def load_data(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initial loading of data from bundled_data_path and config
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.asset_path = bundled_data_path(self) / "assets"
 | 
				
			||||||
 | 
					        self.current_map = await self.config.current_map()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.current_map:
 | 
				
			||||||
 | 
					            await self.current_map_load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def current_map_load(self):
 | 
				
			||||||
 | 
					        map_data_path = self.asset_path / self.current_map / "data.json"
 | 
				
			||||||
 | 
					        with map_data_path.open() as mapdata:
 | 
				
			||||||
 | 
					            self.map_data: dict = json.load(mapdata)
 | 
				
			||||||
 | 
					        self.ext = self.map_data["extension"]
 | 
				
			||||||
 | 
					        self.ext_format = "JPEG" if self.ext.upper() == "JPG" else self.ext.upper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @commands.group()
 | 
				
			||||||
 | 
					    async def conquest(self, ctx: commands.Context):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Base command for conquest cog. Start with `[p]conquest set map` to select a map.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if ctx.invoked_subcommand is None:
 | 
				
			||||||
 | 
					            if self.current_map is not None:
 | 
				
			||||||
 | 
					                await self._conquest_current(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest.command(name="list")
 | 
				
			||||||
 | 
					    async def _conquest_list(self, ctx: commands.Context):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        List currently available maps
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        maps_json = self.asset_path / "maps.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with maps_json.open() as maps:
 | 
				
			||||||
 | 
					            maps_json = json.load(maps)
 | 
				
			||||||
 | 
					            map_list = "\n".join(map_name for map_name in maps_json["maps"])
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(f"Current maps:\n{map_list}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest.group(name="set")
 | 
				
			||||||
 | 
					    async def conquest_set(self, ctx: commands.Context):
 | 
				
			||||||
 | 
					        """Base command for admin actions like selecting a map"""
 | 
				
			||||||
 | 
					        if ctx.invoked_subcommand is None:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest_set.command(name="resetzoom")
 | 
				
			||||||
 | 
					    async def _conquest_set_resetzoom(self, ctx: commands.Context):
 | 
				
			||||||
 | 
					        """Resets the zoom level of the current map"""
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        zoom_json_path = self.data_path / self.current_map / "settings.json"
 | 
				
			||||||
 | 
					        if not zoom_json_path.exists():
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                f"No zoom data found for {self.current_map}, reset not needed"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with zoom_json_path.open("w+") as zoom_json:
 | 
				
			||||||
 | 
					            json.dump({"enabled": False}, zoom_json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await ctx.tick()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest_set.command(name="zoom")
 | 
				
			||||||
 | 
					    async def _conquest_set_zoom(self, ctx: commands.Context, x: int, y: int, zoom: float):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Set the zoom level and position of the current map
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        x: positive integer
 | 
				
			||||||
 | 
					        y: positive integer
 | 
				
			||||||
 | 
					        zoom: float greater than or equal to 1
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if x < 0 or y < 0 or zoom < 1:
 | 
				
			||||||
 | 
					            await ctx.send_help()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        zoom_json_path = self.data_path / self.current_map / "settings.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        zoom_data = self.default_zoom_json.copy()
 | 
				
			||||||
 | 
					        zoom_data["enabled"] = True
 | 
				
			||||||
 | 
					        zoom_data["x"] = x
 | 
				
			||||||
 | 
					        zoom_data["y"] = y
 | 
				
			||||||
 | 
					        zoom_data["zoom"] = zoom
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with zoom_json_path.open("w+") as zoom_json:
 | 
				
			||||||
 | 
					            json.dump(zoom_data, zoom_json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await ctx.tick()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest_set.command(name="zoomtest")
 | 
				
			||||||
 | 
					    async def _conquest_set_zoomtest(self, ctx: commands.Context, x: int, y: int, zoom: float):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test the zoom level and position of the current map
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        x: positive integer
 | 
				
			||||||
 | 
					        y: positive integer
 | 
				
			||||||
 | 
					        zoom: float greater than or equal to 1
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if x < 0 or y < 0 or zoom < 1:
 | 
				
			||||||
 | 
					            await ctx.send_help()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        zoomed_path = await self._create_zoomed_map(
 | 
				
			||||||
 | 
					            self.data_path / self.current_map / f"current.{self.ext}", x, y, zoom
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await ctx.send(file=discord.File(fp=zoomed_path, filename=f"current_zoomed.{self.ext}",))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _create_zoomed_map(self, map_path, x, y, zoom, **kwargs):
 | 
				
			||||||
 | 
					        current_map = Image.open(map_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        w, h = current_map.size
 | 
				
			||||||
 | 
					        zoom2 = zoom * 2
 | 
				
			||||||
 | 
					        zoomed_map = current_map.crop((x - w / zoom2, y - h / zoom2, x + w / zoom2, y + h / zoom2))
 | 
				
			||||||
 | 
					        # zoomed_map = zoomed_map.resize((w, h), Image.LANCZOS)
 | 
				
			||||||
 | 
					        zoomed_map.save(self.data_path / self.current_map / f"zoomed.{self.ext}", self.ext_format)
 | 
				
			||||||
 | 
					        return self.data_path / self.current_map / f"zoomed.{self.ext}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest_set.command(name="save")
 | 
				
			||||||
 | 
					    async def _conquest_set_save(self, ctx: commands.Context, *, save_name):
 | 
				
			||||||
 | 
					        """Save the current map to be loaded later"""
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_map_folder = self.data_path / self.current_map
 | 
				
			||||||
 | 
					        current_map = current_map_folder / f"current.{self.ext}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not current_map_folder.exists() or not current_map.exists():
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("Current map doesn't exist! Try setting a new one")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        copyfile(current_map, current_map_folder / f"{save_name}.{self.ext}")
 | 
				
			||||||
 | 
					        await ctx.tick()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest_set.command(name="load")
 | 
				
			||||||
 | 
					    async def _conquest_set_load(self, ctx: commands.Context, *, save_name):
 | 
				
			||||||
 | 
					        """Load a saved map to be the current map"""
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_map_folder = self.data_path / self.current_map
 | 
				
			||||||
 | 
					        current_map = current_map_folder / f"current.{self.ext}"
 | 
				
			||||||
 | 
					        saved_map = current_map_folder / f"{save_name}.{self.ext}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not current_map_folder.exists() or not saved_map.exists():
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(f"Saved map not found in the {self.current_map} folder")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        copyfile(saved_map, current_map)
 | 
				
			||||||
 | 
					        await ctx.tick()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest_set.command(name="map")
 | 
				
			||||||
 | 
					    async def _conquest_set_map(self, ctx: commands.Context, mapname: str, reset: bool = False):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Select a map from current available maps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        To add more maps, see the guide (WIP)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        map_dir = self.asset_path / mapname
 | 
				
			||||||
 | 
					        if not map_dir.exists() or not map_dir.is_dir():
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                f"Map `{mapname}` was not found in the {self.asset_path} directory"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.current_map = mapname
 | 
				
			||||||
 | 
					        await self.config.current_map.set(self.current_map)  # Save to config too
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self.current_map_load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # map_data_path = self.asset_path / mapname / "data.json"
 | 
				
			||||||
 | 
					        # with map_data_path.open() as mapdata:
 | 
				
			||||||
 | 
					        #     self.map_data = json.load(mapdata)
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # self.ext = self.map_data["extension"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_map_folder = self.data_path / self.current_map
 | 
				
			||||||
 | 
					        current_map = current_map_folder / f"current.{self.ext}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not reset and current_map.exists():
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                "This map is already in progress, resuming from last game\n"
 | 
				
			||||||
 | 
					                "Use `[p]conquest set map [mapname] True` to start a new game"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if not current_map_folder.exists():
 | 
				
			||||||
 | 
					                os.makedirs(current_map_folder)
 | 
				
			||||||
 | 
					            copyfile(self.asset_path / mapname / f"blank.{self.ext}", current_map)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await ctx.tick()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest.command(name="current")
 | 
				
			||||||
 | 
					    async def _conquest_current(self, ctx: commands.Context):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Send the current map.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_img = self.data_path / self.current_map / f"current.{self.ext}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self._send_maybe_zoomed_map(ctx, current_img, f"current_map.{self.ext}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _send_maybe_zoomed_map(self, ctx, map_path, filename):
 | 
				
			||||||
 | 
					        zoom_data = {"enabled": False}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        zoom_json_path = self.data_path / self.current_map / "settings.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if zoom_json_path.exists():
 | 
				
			||||||
 | 
					            with zoom_json_path.open() as zoom_json:
 | 
				
			||||||
 | 
					                zoom_data = json.load(zoom_json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if zoom_data["enabled"]:
 | 
				
			||||||
 | 
					            map_path = await self._create_zoomed_map(map_path, **zoom_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await ctx.send(file=discord.File(fp=map_path, filename=filename))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest.command("blank")
 | 
				
			||||||
 | 
					    async def _conquest_blank(self, ctx: commands.Context):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Print the blank version of the current map, for reference.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_blank_img = self.asset_path / self.current_map / f"blank.{self.ext}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self._send_maybe_zoomed_map(ctx, current_blank_img, f"blank_map.{self.ext}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest.command("numbered")
 | 
				
			||||||
 | 
					    async def _conquest_numbered(self, ctx: commands.Context):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Print the numbered version of the current map, for reference.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        numbers_path = self.asset_path / self.current_map / f"numbers.{self.ext}"
 | 
				
			||||||
 | 
					        if not numbers_path.exists():
 | 
				
			||||||
 | 
					            await ctx.send(
 | 
				
			||||||
 | 
					                file=discord.File(
 | 
				
			||||||
 | 
					                    fp=self.asset_path / self.current_map / f"numbered.{self.ext}",
 | 
				
			||||||
 | 
					                    filename=f"numbered.{self.ext}",
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_map = Image.open(self.data_path / self.current_map / f"current.{self.ext}")
 | 
				
			||||||
 | 
					        numbers = Image.open(numbers_path).convert("L")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inverted_map = ImageOps.invert(current_map)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loop = asyncio.get_running_loop()
 | 
				
			||||||
 | 
					        current_numbered_img = await loop.run_in_executor(
 | 
				
			||||||
 | 
					            None, Image.composite, current_map, inverted_map, numbers
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_numbered_img.save(
 | 
				
			||||||
 | 
					            self.data_path / self.current_map / f"current_numbered.{self.ext}", self.ext_format
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self._send_maybe_zoomed_map(
 | 
				
			||||||
 | 
					            ctx,
 | 
				
			||||||
 | 
					            self.data_path / self.current_map / f"current_numbered.{self.ext}",
 | 
				
			||||||
 | 
					            f"current_numbered.{self.ext}",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest.command(name="multitake")
 | 
				
			||||||
 | 
					    async def _conquest_multitake(
 | 
				
			||||||
 | 
					        self, ctx: commands.Context, start_region: int, end_region: int, color: str
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            color = ImageColor.getrgb(color)
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(f"Invalid color {color}")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if end_region > self.map_data["region_max"] or start_region < 1:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                f"Max region number is {self.map_data['region_max']}, minimum is 1"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        regions = [r for r in range(start_region, end_region + 1)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self._process_take_regions(color, ctx, regions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _process_take_regions(self, color, ctx, regions):
 | 
				
			||||||
 | 
					        current_img_path = self.data_path / self.current_map / f"current.{self.ext}"
 | 
				
			||||||
 | 
					        im = Image.open(current_img_path)
 | 
				
			||||||
 | 
					        async with ctx.typing():
 | 
				
			||||||
 | 
					            out: Image.Image = await self._composite_regions(im, regions, color)
 | 
				
			||||||
 | 
					            out.save(current_img_path, self.ext_format)
 | 
				
			||||||
 | 
					            await self._send_maybe_zoomed_map(ctx, current_img_path, f"map.{self.ext}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @conquest.command(name="take")
 | 
				
			||||||
 | 
					    async def _conquest_take(self, ctx: commands.Context, regions: Greedy[int], *, color: str):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Claim a territory or list of territories for a specified color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param regions: List of integer regions
 | 
				
			||||||
 | 
					        :param color: Color to claim regions
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not regions:
 | 
				
			||||||
 | 
					            await ctx.send_help()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.current_map is None:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            color = ImageColor.getrgb(color)
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            await ctx.maybe_send_embed(f"Invalid color {color}")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for region in regions:
 | 
				
			||||||
 | 
					            if region > self.map_data["region_max"] or region < 1:
 | 
				
			||||||
 | 
					                await ctx.maybe_send_embed(
 | 
				
			||||||
 | 
					                    f"Max region number is {self.map_data['region_max']}, minimum is 1"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self._process_take_regions(color, ctx, regions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _composite_regions(self, im, regions, color) -> Image.Image:
 | 
				
			||||||
 | 
					        im2 = Image.new("RGB", im.size, color)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loop = asyncio.get_running_loop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        combined_mask = None
 | 
				
			||||||
 | 
					        for region in regions:
 | 
				
			||||||
 | 
					            mask = Image.open(
 | 
				
			||||||
 | 
					                self.asset_path / self.current_map / "masks" / f"{region}.{self.ext}"
 | 
				
			||||||
 | 
					            ).convert("L")
 | 
				
			||||||
 | 
					            if combined_mask is None:
 | 
				
			||||||
 | 
					                combined_mask = mask
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # combined_mask = ImageChops.logical_or(combined_mask, mask)
 | 
				
			||||||
 | 
					                combined_mask = await loop.run_in_executor(
 | 
				
			||||||
 | 
					                    None, ImageChops.multiply, combined_mask, mask
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out = await loop.run_in_executor(None, Image.composite, im, im2, combined_mask)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/HoI/blank.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 400 KiB  | 
							
								
								
									
										3
									
								
								conquest/data/assets/HoI/data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "region_max": 70
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/HoI/numbered.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 480 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/ck2/blank.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 345 KiB  | 
							
								
								
									
										3
									
								
								conquest/data/assets/ck2/data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "region_max": 70
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/ck2/numbered.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 413 KiB  | 
							
								
								
									
										7
									
								
								conquest/data/assets/maps.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "maps": [
 | 
				
			||||||
 | 
					    "simple_blank_map",
 | 
				
			||||||
 | 
						"test",
 | 
				
			||||||
 | 
						"test2"
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/blank.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 312 KiB  | 
							
								
								
									
										4
									
								
								conquest/data/assets/simple/data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "region_max": 70,
 | 
				
			||||||
 | 
					  "extension": "jpg"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/10.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/11.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/12.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/13.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/14.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/15.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/16.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/17.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/18.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/19.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 34 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/2.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/20.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/21.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/22.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/23.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/24.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/25.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/26.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 34 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/27.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/28.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/29.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/3.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/30.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/31.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/32.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/33.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/34.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 29 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/35.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/36.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/37.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/38.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/39.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 28 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/4.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/40.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 56 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/41.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/42.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 37 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/43.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/44.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 28 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/45.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/46.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/47.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/48.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/49.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/5.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/50.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/51.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/52.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/53.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/54.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 28 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/55.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/56.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/57.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 36 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/58.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/59.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/6.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/60.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/61.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/62.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/63.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/64.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/65.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/66.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/67.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/68.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/69.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/7.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 35 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/70.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/8.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/masks/9.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 30 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/numbered.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 480 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/numbers.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 67 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								conquest/data/assets/simple/numbers.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.3 KiB  | 
							
								
								
									
										24
									
								
								conquest/info.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "author": [
 | 
				
			||||||
 | 
					    "Bobloy"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "min_bot_version": "3.4.0",
 | 
				
			||||||
 | 
					  "description": "Handle war games by filling in specified territories with colors",
 | 
				
			||||||
 | 
					  "hidden": false,
 | 
				
			||||||
 | 
					  "install_msg": "Thank you for installing Conquest. Get started with `[p]load conquest`, then `[p]help Conquest`",
 | 
				
			||||||
 | 
					  "short": "War Game Map",
 | 
				
			||||||
 | 
					  "requirements": [
 | 
				
			||||||
 | 
					    "Pillow"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "tags": [
 | 
				
			||||||
 | 
					    "bobloy",
 | 
				
			||||||
 | 
					    "games",
 | 
				
			||||||
 | 
					    "game",
 | 
				
			||||||
 | 
					    "war",
 | 
				
			||||||
 | 
					    "map",
 | 
				
			||||||
 | 
					    "axisandallies",
 | 
				
			||||||
 | 
					    "heartsofiron",
 | 
				
			||||||
 | 
					    "conquest",
 | 
				
			||||||
 | 
					    "rpg"
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||