WIP ConquestGame and per guild saving

conquest_develop
bobloy 4 years ago
parent f0d33f0354
commit 7574117d9a

@ -13,7 +13,7 @@ from redbot.core.bot import Red
from redbot.core.data_manager import bundled_data_path, cog_data_path
from redbot.core.utils.predicates import MessagePredicate
from conquest.regioner import ConquestMap, composite_regions
from conquest.regioner import ConquestGame, ConquestMap, MapMaker, composite_regions
class Conquest(commands.Cog):
@ -25,6 +25,9 @@ class Conquest(commands.Cog):
default_maps_json = {"maps": []}
# Usage: self.config.games.get_raw("game_name", "is_custom")
default_games = {"map_name": None, "is_custom": False}
def __init__(self, bot: Red):
super().__init__()
self.bot = bot
@ -32,8 +35,8 @@ class Conquest(commands.Cog):
self, identifier=67_111_110_113_117_101_115_116, force_registration=True
)
default_guild = {}
default_global = {"current_map": None, "is_custom": False}
default_guild = {"current_game": None}
default_global = {"games": {}}
self.config.register_guild(**default_guild)
self.config.register_global(**default_global)
@ -51,14 +54,13 @@ class Conquest(commands.Cog):
self.asset_path: Optional[pathlib.Path] = None
self.is_custom = False
self.current_map = None
self.map_data = None
self.current_maps = {} # key, value = guild.id, game_name
self.map_data = {} # key, value = guild.id, ConquestGame
self.ext = "PNG"
self.ext_format = "PNG"
self.mm: Union[ConquestMap, None] = None
self.mm: Union[MapMaker, None] = None
async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete"""
@ -75,22 +77,34 @@ class Conquest(commands.Cog):
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()
for guild in self.bot.guilds:
game_name = await self.config.guild(guild).current_game()
if game_name is not None:
await self.load_guild_data(guild, game_name)
self.is_custom = await self.config.is_custom()
if self.current_map:
await self.current_map_load()
async def load_guild_data(self, guild: discord.Guild, game_name: str):
game_name = await self.config.guild(guild).current_game()
if game_name is not None:
map_data = self.config.games.get_raw(game_name)
self.current_maps[guild.id] = ConquestGame()
async def current_map_load(self):
map_path = self._path_if_custom()
map_data_path = map_path / self.current_map / "data.json"
try:
with map_data_path.open() as mapdata:
self.map_data: dict = json.load(mapdata)
except FileNotFoundError as e:
print(e)
await self.config.current_map.set(None)
return
self.map_data = ConquestMap(map_path / self.current_map)
await self.map_data.load_data()
# map_data_path = map_path / self.current_map / "data.json"
# try:
# with map_data_path.open() as mapdata:
# self.map_data: dict = json.load(mapdata)
# except FileNotFoundError as e:
# print(e)
# await self.config.current_map.set(None)
# return
async def _get_current_map_path(self):
return self.current_map_folder / self.current_map
@ -205,7 +219,7 @@ class Conquest(commands.Cog):
return
if not self.mm:
self.mm = ConquestMap(self.custom_map_path)
self.mm = MapMaker(self.custom_map_path)
self.mm.custom = True
if map_path:
@ -229,6 +243,10 @@ class Conquest(commands.Cog):
# Wait what?
return
if mm_img.mode == "P":
# Maybe convert to L to prevent RGB?
mm_img = mm_img.convert() # No P mode, convert it
result = await self.mm.init_directory(map_name, target_save, mm_img)
if not result:
@ -274,7 +292,7 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed(f"Map {map_name} not found in {self.custom_map_path}")
return
self.mm = ConquestMap(map_path)
self.mm = MapMaker(map_path)
await self.mm.load_data()
await ctx.tick()
@ -311,6 +329,31 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed(f"{len(regions)} masks generated into {masks_dir}")
@_mapmaker_masks.command(name="delete")
async def _mapmaker_masks_delete(self, ctx: commands.Context, mask_list: Greedy[int]):
"""
Delete the listed masks from the map
"""
if not mask_list:
await ctx.send_help()
return
if not self.mm:
await ctx.maybe_send_embed("No map currently being worked on")
return
masks_dir = self.mm.masks_path()
if not masks_dir.exists() or not masks_dir.is_dir():
await ctx.maybe_send_embed("There are no masks")
return
async with ctx.typing():
result = await self.mm.delete_masks(mask_list)
if result:
await ctx.maybe_send_embed(f"Delete masks: {mask_list}")
else:
await ctx.maybe_send_embed(f"Failed to delete masks")
@_mapmaker_masks.command(name="combine")
async def _mapmaker_masks_combine(
self, ctx: commands.Context, mask_list: Greedy[int], recommended=False

@ -107,6 +107,27 @@ def floodfill(image, xy, value, border=None, thresh=0) -> set:
return filled_pixels
def create_number_mask(regions, filepath, filename):
base_img_path = filepath / filename
if not base_img_path.exists():
return False
base_img: Image.Image = Image.open(base_img_path).convert("L")
number_img = Image.new("L", base_img.size, 255)
fnt = ImageFont.load_default()
d = ImageDraw.Draw(number_img)
for region_num, region in regions.items():
text = getattr(region, "name", str(region_num))
w1, h1 = region.center
w2, h2 = fnt.getsize(text)
d.text((w1 - (w2 / 2), h1 - (h2 / 2)), text, font=fnt, fill=0)
number_img.save(filepath / f"numbers.png", "PNG")
return True
class ConquestMap:
def __init__(self, path: pathlib.Path):
self.path = path
@ -116,33 +137,6 @@ class ConquestMap:
self.region_max = None
self.regions = {}
async def change_name(self, new_name: str, new_path: pathlib.Path):
if new_path.exists() and new_path.is_dir():
# This is an overwrite operation
# await ctx.maybe_send_embed(f"{map_name} already exists, okay to overwrite?")
#
# pred = MessagePredicate.yes_or_no(ctx)
# try:
# await self.bot.wait_for("message", check=pred, timeout=30)
# except TimeoutError:
# await ctx.maybe_send_embed("Response timed out, cancelling save")
# return
# if not pred.result:
# return
return False, "Overwrite currently not supported"
# This is a new name
new_path.mkdir()
shutil.copytree(self.path, new_path)
self.name = new_name
self.path = new_path
await self.save_data()
return True
def masks_path(self):
return self.path / "masks"
@ -158,29 +152,6 @@ class ConquestMap:
def numbered_path(self):
return self.path / "numbered.png"
async def init_directory(self, name: str, path: pathlib.Path, image: Image.Image):
if not path.exists() or not path.is_dir():
path.mkdir()
self.name = name
self.path = path
await self.save_data()
image.save(self.blank_path(), "PNG")
return True
async def save_data(self):
to_save = {
"name": self.name,
"custom": self.custom,
"region_max": self.region_max,
"regions": {num: r.get_json() for num, r in self.regions.items()},
}
with self.data_path().open("w+") as dp:
json.dump(to_save, dp, sort_keys=True, indent=4)
async def load_data(self):
with self.data_path().open() as dp:
data = json.load(dp)
@ -196,72 +167,23 @@ class ConquestMap:
async def save_region(self, region):
if not self.custom:
return False
pass
async def generate_masks(self):
regioner = Regioner(filename="blank.png", filepath=self.path)
loop = asyncio.get_running_loop()
regions = await loop.run_in_executor(None, regioner.execute)
if not regions:
return regions
self.regions = regions
self.region_max = len(regions) + 1
await self.save_data()
return regions
pass # TODO: region data saving
async def create_number_mask(self):
regioner = Regioner(filename="blank.png", filepath=self.path)
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, regioner.create_number_mask, self.regions)
async def combine_masks(self, mask_list: List[int]):
loop = asyncio.get_running_loop()
lowest, eliminated = await loop.run_in_executor(None, self._img_combine_masks, mask_list)
if not lowest:
return lowest
try:
elim_regions = [self.regions[n] for n in eliminated]
lowest_region = self.regions[lowest]
except KeyError:
return False
# points = [self.mm["regions"][f"{n}"]["center"] for n in mask_list]
#
# points = [(r.center, r.weight) for r in elim_regions]
weighted_points = [r.center for r in elim_regions for _ in range(r.weight)] + [
lowest_region.center for _ in range(lowest_region.weight)
]
lowest_region.center = get_center(weighted_points)
for key in eliminated:
self.regions.pop(key)
# self.mm["regions"].pop(f"{key}")
if self.region_max in eliminated: # Max region has changed
self.region_max = max(self.regions.keys())
await self.create_number_mask()
await self.save_data()
return lowest
return await loop.run_in_executor(
None, create_number_mask, self.regions, self.path, "blank.png"
)
def _img_combine_masks(self, mask_list: List[int]):
if not mask_list:
return False, None
return False, None, None
if not self.blank_path().exists():
return False, None
return False, None, None
if not self.masks_path().exists():
return False, None
return False, None, None
base_img: Image.Image = Image.open(self.blank_path())
mask = Image.new("1", base_img.size, 1)
@ -278,8 +200,7 @@ class ConquestMap:
mask2 = Image.open(self.masks_path() / f"{mask_num}.png").convert("1")
mask = ImageChops.logical_and(mask, mask2)
mask.save(self.masks_path() / f"{lowest_num}.png", "PNG")
return lowest_num, eliminated_masks
return lowest_num, eliminated_masks, mask
async def get_sample(self):
files = [self.blank_path()]
@ -344,6 +265,140 @@ class ConquestMap:
return current_numbered_img
class ConquestGame(ConquestMap):
def __init__(self, path: pathlib.Path):
super().__init__(path)
self
async def start_game(self):
pass
class MapMaker(ConquestMap):
async def change_name(self, new_name: str, new_path: pathlib.Path):
if new_path.exists() and new_path.is_dir():
# This is an overwrite operation
# await ctx.maybe_send_embed(f"{map_name} already exists, okay to overwrite?")
#
# pred = MessagePredicate.yes_or_no(ctx)
# try:
# await self.bot.wait_for("message", check=pred, timeout=30)
# except TimeoutError:
# await ctx.maybe_send_embed("Response timed out, cancelling save")
# return
# if not pred.result:
# return
return False, "Overwrite currently not supported"
# This is a new name
new_path.mkdir()
shutil.copytree(self.path, new_path)
self.custom = True # If this wasn't a custom map, it is now
self.name = new_name
self.path = new_path
await self.save_data()
return True
async def generate_masks(self):
regioner = Regioner(filename="blank.png", filepath=self.path)
loop = asyncio.get_running_loop()
regions = await loop.run_in_executor(None, regioner.execute)
if not regions:
return regions
self.regions = regions
self.region_max = len(regions) + 1
await self.save_data()
return regions
async def combine_masks(self, mask_list: List[int]):
loop = asyncio.get_running_loop()
lowest, eliminated, mask = await loop.run_in_executor(
None, self._img_combine_masks, mask_list
)
if not lowest:
return lowest
try:
elim_regions = [self.regions[n] for n in eliminated]
lowest_region = self.regions[lowest]
except KeyError:
return False
mask.save(self.masks_path() / f"{lowest}.png", "PNG")
# points = [self.mm["regions"][f"{n}"]["center"] for n in mask_list]
#
# points = [(r.center, r.weight) for r in elim_regions]
weighted_points = [r.center for r in elim_regions for _ in range(r.weight)] + [
lowest_region.center for _ in range(lowest_region.weight)
]
lowest_region.center = get_center(weighted_points)
for key in eliminated:
self.regions.pop(key)
# self.mm["regions"].pop(f"{key}")
if self.region_max in eliminated: # Max region has changed
self.region_max = max(self.regions.keys())
await self.create_number_mask()
await self.save_data()
return lowest
async def delete_masks(self, mask_list):
try:
for key in mask_list:
self.regions.pop(key)
# self.mm["regions"].pop(f"{key}")
except KeyError:
return False
if self.region_max in mask_list: # Max region has changed
self.region_max = max(self.regions.keys())
await self.create_number_mask()
await self.save_data()
return mask_list
async def save_data(self):
to_save = {
"name": self.name,
"custom": self.custom,
"region_max": self.region_max,
"regions": {num: r.get_json() for num, r in self.regions.items()},
}
with self.data_path().open("w+") as dp:
json.dump(to_save, dp, sort_keys=True, indent=4)
async def init_directory(self, name: str, path: pathlib.Path, image: Image.Image):
if not path.exists() or not path.is_dir():
path.mkdir()
self.name = name
self.path = path
await self.save_data()
image.save(self.blank_path(), "PNG")
return True
class Region:
def __init__(self, center, weight, **kwargs):
self.center = center
@ -356,11 +411,14 @@ class Region:
class Regioner:
def __init__(
self, filepath: pathlib.Path, filename: str, wall_color="black", region_color="white"
self, filepath: pathlib.Path, filename: str, region_color=None, wall_color="black"
):
self.filepath = filepath
self.filename = filename
self.wall_color = ImageColor.getcolor(wall_color, "L")
if region_color is None:
self.region_color = None
else:
self.region_color = ImageColor.getcolor(region_color, "L")
def execute(self):
@ -395,8 +453,10 @@ class Regioner:
for x1 in range(base_img.width):
if (x1, y1) in already_processed:
continue
if base_img.getpixel((x1, y1)) == self.region_color:
filled = floodfill(base_img, (x1, y1), black, self.wall_color)
if (
self.region_color is None and base_img.getpixel((x1, y1)) != self.wall_color
) or base_img.getpixel((x1, y1)) == self.region_color:
filled = floodfill(base_img, (x1, y1), self.wall_color, self.wall_color)
if filled: # Pixels were updated, make them into a mask
mask = Image.new("L", base_img.size, 255)
for x2, y2 in filled:
@ -412,25 +472,5 @@ class Regioner:
# TODO: save mask_centers
self.create_number_mask(regions)
create_number_mask(regions, self.filepath, self.filename)
return regions
def create_number_mask(self, regions):
base_img_path = self.filepath / self.filename
if not base_img_path.exists():
return False
base_img: Image.Image = Image.open(base_img_path).convert("L")
number_img = Image.new("L", base_img.size, 255)
fnt = ImageFont.load_default()
d = ImageDraw.Draw(number_img)
for region_num, region in regions.items():
text = getattr(region, "name", str(region_num))
w1, h1 = region.center
w2, h2 = fnt.getsize(text)
d.text((w1 - (w2 / 2), h1 - (h2 / 2)), text, font=fnt, fill=0)
number_img.save(self.filepath / f"numbers.png", "PNG")
return True

Loading…
Cancel
Save