Compare commits
	
		
			7 Commits
		
	
	
		
			07bcc5de06
			...
			a8d3aa64a1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a8d3aa64a1 | |||
| bbb248da16 | |||
| ca74986b82 | |||
| 9346637462 | |||
| c7d84246b4 | |||
| 9402a7eeb9 | |||
| 430f619f2e | 
							
								
								
									
										9
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | # Default ignored files | ||||||
|  | /shelf/ | ||||||
|  | /workspace.xml | ||||||
|  | # Editor-based HTTP Client requests | ||||||
|  | /httpRequests/ | ||||||
|  | # Datasource local storage ignored files | ||||||
|  | /dataSources/ | ||||||
|  | /dataSources.local.xml | ||||||
|  | discord.xml | ||||||
							
								
								
									
										8
									
								
								.idea/LycacraftPaths.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/LycacraftPaths.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <module type="PYTHON_MODULE" version="4"> | ||||||
|  |   <component name="NewModuleRootManager"> | ||||||
|  |     <content url="file://$MODULE_DIR$" /> | ||||||
|  |     <orderEntry type="inheritedJdk" /> | ||||||
|  |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|  |   </component> | ||||||
|  | </module> | ||||||
							
								
								
									
										25
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | <component name="InspectionProjectProfileManager"> | ||||||
|  |   <profile version="1.0"> | ||||||
|  |     <option name="myName" value="Project Default" /> | ||||||
|  |     <inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||||
|  |       <option name="ourVersions"> | ||||||
|  |         <value> | ||||||
|  |           <list size="3"> | ||||||
|  |             <item index="0" class="java.lang.String" itemvalue="3.13" /> | ||||||
|  |             <item index="1" class="java.lang.String" itemvalue="3.10" /> | ||||||
|  |             <item index="2" class="java.lang.String" itemvalue="3.8" /> | ||||||
|  |           </list> | ||||||
|  |         </value> | ||||||
|  |       </option> | ||||||
|  |     </inspection_tool> | ||||||
|  |     <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||||
|  |       <option name="ignoredPackages"> | ||||||
|  |         <value> | ||||||
|  |           <list size="1"> | ||||||
|  |             <item index="0" class="java.lang.String" itemvalue="PyYAML" /> | ||||||
|  |           </list> | ||||||
|  |         </value> | ||||||
|  |       </option> | ||||||
|  |     </inspection_tool> | ||||||
|  |   </profile> | ||||||
|  | </component> | ||||||
							
								
								
									
										6
									
								
								.idea/inspectionProfiles/profiles_settings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/inspectionProfiles/profiles_settings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <component name="InspectionProjectProfileManager"> | ||||||
|  |   <settings> | ||||||
|  |     <option name="USE_PROJECT_PROFILE" value="false" /> | ||||||
|  |     <version value="1.0" /> | ||||||
|  |   </settings> | ||||||
|  | </component> | ||||||
							
								
								
									
										7
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="Black"> | ||||||
|  |     <option name="sdkName" value="Python 3.10" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" /> | ||||||
|  | </project> | ||||||
							
								
								
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="ProjectModuleManager"> | ||||||
|  |     <modules> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/.idea/LycacraftPaths.iml" filepath="$PROJECT_DIR$/.idea/LycacraftPaths.iml" /> | ||||||
|  |     </modules> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="VcsDirectoryMappings"> | ||||||
|  |     <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										10
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | from src.editor import Editor | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |     editor = Editor() | ||||||
|  |     editor.mainloop() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
							
								
								
									
										0
									
								
								src/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										207
									
								
								src/editor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/editor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | |||||||
|  | import os | ||||||
|  | from enum import Enum, auto | ||||||
|  | from math import floor | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
|  | import platformdirs | ||||||
|  | import pygame | ||||||
|  |  | ||||||
|  | from src.image_handler import ImageHandler | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Editor: | ||||||
|  |     WIDTH: int = 800 | ||||||
|  |     HEIGHT: int = 600 | ||||||
|  |     MAP_SIZE: int = 1024 | ||||||
|  |     MAPS_DIR: str = os.path.join(platformdirs.user_cache_dir(appname="lycacraft-paths", appauthor="Lycacraft"), "maps") | ||||||
|  |     ZOOMS: tuple[float] = (0.25, 0.5, 1, 2, 4) | ||||||
|  |     CROSSHAIR_SIZE: int = 10 | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         pygame.init() | ||||||
|  |         self.width: int = self.WIDTH | ||||||
|  |         self.height: int = self.HEIGHT | ||||||
|  |         self.win: pygame.Surface = pygame.display.set_mode([self.width, self.height], pygame.RESIZABLE) | ||||||
|  |         pygame.display.set_caption("Lycacraft Map Editor") | ||||||
|  |         self.center: list[int] = [0, 0] | ||||||
|  |         self.zoom_i: int = 2 | ||||||
|  |         self.zoom: float = self.ZOOMS[self.zoom_i] | ||||||
|  |         self.running: bool = False | ||||||
|  |         self.image_handler: ImageHandler = ImageHandler(self.MAPS_DIR, self.MAP_SIZE) | ||||||
|  |         self.clock: pygame.time.Clock = pygame.time.Clock() | ||||||
|  |         self.drag_pos: Optional[tuple[int, int]] = None | ||||||
|  |         self.font: pygame.font.Font = pygame.font.SysFont("Ubuntu", 20) | ||||||
|  |         self.loading_font: pygame.font.Font = pygame.font.SysFont("Ubuntu", 30) | ||||||
|  |         self.zooms_texts: list[pygame.Surface] = list(map( | ||||||
|  |             lambda z: self.font.render(str(z), True, (255, 255, 255)), | ||||||
|  |             self.ZOOMS | ||||||
|  |         )) | ||||||
|  |         self.state: State = State.STOPPING | ||||||
|  |  | ||||||
|  |     def mainloop(self) -> None: | ||||||
|  |         self.state = State.LOADING | ||||||
|  |         while self.state != State.STOPPING: | ||||||
|  |             pygame.display.set_caption(f"Lycacraft Map Editor - {self.clock.get_fps():.2f}fps") | ||||||
|  |             self.process_events() | ||||||
|  |             if self.state == State.LOADING: | ||||||
|  |                 self.render_loading() | ||||||
|  |                 if not self.image_handler.loading: | ||||||
|  |                     self.state = State.RUNNING | ||||||
|  |             elif self.state == State.RUNNING: | ||||||
|  |                 self.render() | ||||||
|  |             self.clock.tick(30) | ||||||
|  |  | ||||||
|  |     def process_events(self) -> None: | ||||||
|  |         events = pygame.event.get() | ||||||
|  |  | ||||||
|  |         for event in events: | ||||||
|  |             if event.type == pygame.QUIT: | ||||||
|  |                 self.state = State.STOPPING | ||||||
|  |             elif event.type == pygame.WINDOWRESIZED: | ||||||
|  |                 self.width = event.x | ||||||
|  |                 self.height = event.y | ||||||
|  |             elif event.type == pygame.KEYDOWN: | ||||||
|  |                 if event.key == pygame.K_ESCAPE: | ||||||
|  |                     self.state = State.STOPPING | ||||||
|  |                 elif event.key == pygame.K_PAGEUP: | ||||||
|  |                     self.zoom_in() | ||||||
|  |                 elif event.key == pygame.K_PAGEDOWN: | ||||||
|  |                     self.zoom_out() | ||||||
|  |             elif event.type == pygame.MOUSEBUTTONDOWN: | ||||||
|  |                 if event.button == 2: | ||||||
|  |                     self.drag_pos = event.pos | ||||||
|  |             elif event.type == pygame.MOUSEBUTTONUP: | ||||||
|  |                 if event.button == 2: | ||||||
|  |                     self.drag_pos = None | ||||||
|  |  | ||||||
|  |         keys = pygame.key.get_pressed() | ||||||
|  |         if keys[pygame.K_LEFT]: | ||||||
|  |             self.center[0] -= 4 / self.zoom | ||||||
|  |         if keys[pygame.K_RIGHT]: | ||||||
|  |             self.center[0] += 4 / self.zoom | ||||||
|  |         if keys[pygame.K_UP]: | ||||||
|  |             self.center[1] -= 4 / self.zoom | ||||||
|  |         if keys[pygame.K_DOWN]: | ||||||
|  |             self.center[1] += 4 / self.zoom | ||||||
|  |  | ||||||
|  |         mbtns = pygame.mouse.get_pressed() | ||||||
|  |         mpos = pygame.mouse.get_pos() | ||||||
|  |         if mbtns[1]: | ||||||
|  |             dx = mpos[0] - self.drag_pos[0] | ||||||
|  |             dy = mpos[1] - self.drag_pos[1] | ||||||
|  |             self.center[0] -= dx / self.zoom | ||||||
|  |             self.center[1] -= dy / self.zoom | ||||||
|  |             self.drag_pos = mpos | ||||||
|  |  | ||||||
|  |     def render(self) -> None: | ||||||
|  |         self.win.fill((0, 0, 0)) | ||||||
|  |         off_x = (self.center[0] * self.zoom) % self.MAP_SIZE | ||||||
|  |         off_y = (self.center[1] * self.zoom) % self.MAP_SIZE | ||||||
|  |  | ||||||
|  |         w2 = self.width / 2 | ||||||
|  |         h2 = self.height / 2 | ||||||
|  |  | ||||||
|  |         # In game top-left / bottom-right corners | ||||||
|  |         x0 = floor(self.center[0] - w2 / self.zoom) | ||||||
|  |         y0 = floor(self.center[1] - h2 / self.zoom) | ||||||
|  |         x1 = floor(self.center[0] + w2 / self.zoom) | ||||||
|  |         y1 = floor(self.center[1] + h2 / self.zoom) | ||||||
|  |  | ||||||
|  |         # Top-left / bottom-right maps | ||||||
|  |         mx0 = floor(x0 * self.zoom / self.MAP_SIZE) | ||||||
|  |         my0 = floor(y0 * self.zoom / self.MAP_SIZE) | ||||||
|  |         mx1 = floor(x1 * self.zoom / self.MAP_SIZE) | ||||||
|  |         my1 = floor(y1 * self.zoom / self.MAP_SIZE) | ||||||
|  |  | ||||||
|  |         h_maps = mx1 - mx0 + 1 | ||||||
|  |         v_maps = my1 - my0 + 1 | ||||||
|  |         cx = floor(self.center[0] * self.zoom / self.MAP_SIZE) | ||||||
|  |         cy = floor(self.center[1] * self.zoom / self.MAP_SIZE) | ||||||
|  |         ox = w2 + (mx0 - cx) * self.MAP_SIZE - off_x | ||||||
|  |         oy = h2 + (my0 - cy) * self.MAP_SIZE - off_y | ||||||
|  |  | ||||||
|  |         for y in range(v_maps): | ||||||
|  |             for x in range(h_maps): | ||||||
|  |                 map_image = self.image_handler.get_for_pos(mx0 + x, my0 + y, self.zoom) | ||||||
|  |                 if map_image is None: | ||||||
|  |                     continue | ||||||
|  |                 self.win.blit(map_image, [ | ||||||
|  |                     ox + x * self.MAP_SIZE, | ||||||
|  |                     oy + y * self.MAP_SIZE | ||||||
|  |                 ]) | ||||||
|  |  | ||||||
|  |         pygame.draw.line(self.win, (150, 150, 150), [w2 - self.CROSSHAIR_SIZE, h2], [w2 + self.CROSSHAIR_SIZE, h2]) | ||||||
|  |         pygame.draw.line(self.win, (150, 150, 150), [w2, h2 - self.CROSSHAIR_SIZE], [w2, h2 + self.CROSSHAIR_SIZE]) | ||||||
|  |         self.render_zoom_slider() | ||||||
|  |  | ||||||
|  |         mpos = pygame.mouse.get_pos() | ||||||
|  |         world_x, world_z = self.screen_to_world(*mpos) | ||||||
|  |         mouse_txt = self.font.render(f"x: {world_x} / z: {world_z}", True, (255, 255, 255)) | ||||||
|  |         pygame.draw.rect(self.win, (80, 80, 80), [0, 0, mouse_txt.get_width() + 10, mouse_txt.get_height() + 10]) | ||||||
|  |         self.win.blit(mouse_txt, [5, 5]) | ||||||
|  |  | ||||||
|  |         pygame.display.flip() | ||||||
|  |  | ||||||
|  |     def render_zoom_slider(self) -> None: | ||||||
|  |         zoom_height = self.height * 0.2 | ||||||
|  |         zoom_h_margin = self.width * 0.02 | ||||||
|  |         zoom_v_margin = self.height * 0.05 | ||||||
|  |         zoom_x = self.width - zoom_h_margin | ||||||
|  |         zoom_y = self.height - zoom_v_margin - zoom_height | ||||||
|  |         zoom_space = zoom_height / 4 | ||||||
|  |         zoom_r = zoom_space / 4 | ||||||
|  |         zoom_width = max(s.get_width() for s in self.zooms_texts) + 2 * zoom_r + 5 | ||||||
|  |         pygame.draw.rect(self.win, (80, 80, 80), [ | ||||||
|  |             zoom_x + zoom_r - zoom_width - 5, | ||||||
|  |             zoom_y - zoom_r - 5, | ||||||
|  |             zoom_width + 10, | ||||||
|  |             zoom_height + 2 * zoom_r + 10 | ||||||
|  |         ]) | ||||||
|  |         pygame.draw.line(self.win, (255, 255, 255), [zoom_x, zoom_y], [zoom_x, zoom_y + zoom_height]) | ||||||
|  |         for i, txt in enumerate(self.zooms_texts): | ||||||
|  |             y = zoom_y + zoom_height - i * zoom_space | ||||||
|  |             col = (255, 0, 0) if i == self.zoom_i else (255, 255, 255) | ||||||
|  |             pygame.draw.circle(self.win, col, [zoom_x, y], zoom_r) | ||||||
|  |             self.win.blit(txt, [zoom_x - txt.get_width() - zoom_r - 5, y - txt.get_height() / 2]) | ||||||
|  |  | ||||||
|  |     def render_loading(self) -> None: | ||||||
|  |         self.win.fill((0, 0, 0)) | ||||||
|  |         count = self.image_handler.count | ||||||
|  |         total = self.image_handler.total | ||||||
|  |         txt = self.loading_font.render(f"Loading maps - {count}/{total}", True, (255, 255, 255)) | ||||||
|  |  | ||||||
|  |         width = self.width * 0.6 | ||||||
|  |         height = self.height * 0.05 | ||||||
|  |         w2 = self.width / 2 | ||||||
|  |         h2 = self.height / 2 | ||||||
|  |         x0 = w2 - width / 2 | ||||||
|  |         y0 = h2 - height / 2 | ||||||
|  |         pygame.draw.rect(self.win, (160, 160, 160), [x0, y0, width, height]) | ||||||
|  |         pygame.draw.rect(self.win, (90, 250, 90), [x0, y0, width * count / total, height]) | ||||||
|  |         self.win.blit(txt, [w2 - txt.get_width() / 2, y0 - txt.get_height() - 5]) | ||||||
|  |  | ||||||
|  |         pygame.display.flip() | ||||||
|  |  | ||||||
|  |     def set_zoom(self, zoom_i: int) -> None: | ||||||
|  |         self.zoom_i = max(0, min(len(self.ZOOMS) - 1, zoom_i)) | ||||||
|  |         self.zoom = self.ZOOMS[self.zoom_i] | ||||||
|  |  | ||||||
|  |     def zoom_in(self) -> None: | ||||||
|  |         self.set_zoom(self.zoom_i + 1) | ||||||
|  |  | ||||||
|  |     def zoom_out(self) -> None: | ||||||
|  |         self.set_zoom(self.zoom_i - 1) | ||||||
|  |  | ||||||
|  |     def screen_to_world(self, x: int, y: int) -> tuple[int, int]: | ||||||
|  |         w2 = self.width / 2 | ||||||
|  |         h2 = self.height / 2 | ||||||
|  |         world_x = floor((x - w2) / self.zoom + self.center[0]) | ||||||
|  |         world_z = floor((y - h2) / self.zoom + self.center[1]) | ||||||
|  |  | ||||||
|  |         return int(world_x), int(world_z) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class State(Enum): | ||||||
|  |     STOPPING = auto() | ||||||
|  |     LOADING = auto() | ||||||
|  |     RUNNING = auto() | ||||||
							
								
								
									
										70
									
								
								src/image_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/image_handler.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | import os | ||||||
|  | import threading | ||||||
|  | from math import floor | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
|  | import pygame | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ImageHandler: | ||||||
|  |     def __init__(self, maps_dir: str, base_size: int): | ||||||
|  |         self.maps_dir: str = maps_dir | ||||||
|  |         self.base_size: int = base_size | ||||||
|  |         self.cache: dict = {} | ||||||
|  |         self.count: int = 0 | ||||||
|  |         self.total: int = 0 | ||||||
|  |         self.loading: bool = False | ||||||
|  |  | ||||||
|  |         t = threading.Thread(target=self.load_base_images) | ||||||
|  |         t.start() | ||||||
|  |  | ||||||
|  |     def load_base_images(self) -> None: | ||||||
|  |         cache = {} | ||||||
|  |         paths = os.listdir(self.maps_dir) | ||||||
|  |         self.loading = True | ||||||
|  |         self.count = 0 | ||||||
|  |         self.total = len(paths) | ||||||
|  |         for path in paths: | ||||||
|  |             fullpath = os.path.join(self.maps_dir, path) | ||||||
|  |             name, x, y = path.split(".")[0].split("_") | ||||||
|  |             cache[(int(x), int(y))] = pygame.image.load(fullpath).convert_alpha() | ||||||
|  |             self.count += 1 | ||||||
|  |  | ||||||
|  |         self.cache = { | ||||||
|  |             1: cache | ||||||
|  |         } | ||||||
|  |         self.loading = False | ||||||
|  |  | ||||||
|  |     def get_for_pos(self, x: int, y: int, zoom: float = 1) -> Optional[pygame.Surface]: | ||||||
|  |         cache = self.cache.get(zoom, {}) | ||||||
|  |         pos = (x, y) | ||||||
|  |         if pos not in cache: | ||||||
|  |             if zoom == 1: | ||||||
|  |                 return None | ||||||
|  |  | ||||||
|  |             # Multiple maps per zone | ||||||
|  |             elif zoom < 1: | ||||||
|  |                 img = pygame.Surface((self.base_size, self.base_size), pygame.SRCALPHA) | ||||||
|  |                 n_maps = int(1 / zoom) | ||||||
|  |                 sub_size = self.base_size * zoom | ||||||
|  |                 for sub_y in range(n_maps): | ||||||
|  |                     for sub_x in range(n_maps): | ||||||
|  |                         sub_img = self.get_for_pos(x * n_maps + sub_x, y * n_maps + sub_y, 1) | ||||||
|  |                         if sub_img is not None: | ||||||
|  |                             sub_img = pygame.transform.scale_by(sub_img, zoom) | ||||||
|  |                             img.blit(sub_img, [sub_x * sub_size, sub_y * sub_size]) | ||||||
|  |  | ||||||
|  |             # Zone is part of a map | ||||||
|  |             else: | ||||||
|  |                 img = self.get_for_pos(floor(x / zoom), floor(y / zoom), 1) | ||||||
|  |                 if img is not None: | ||||||
|  |                     sub_x = x % zoom | ||||||
|  |                     sub_y = y % zoom | ||||||
|  |                     sub_size = self.base_size / zoom | ||||||
|  |                     img = img.subsurface([sub_x * sub_size, sub_y * sub_size, sub_size, sub_size]) | ||||||
|  |                     img = pygame.transform.scale_by(img, zoom) | ||||||
|  |  | ||||||
|  |             cache[pos] = img | ||||||
|  |  | ||||||
|  |         self.cache[zoom] = cache | ||||||
|  |         return cache[pos] | ||||||
							
								
								
									
										83
									
								
								utils/map_splitter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								utils/map_splitter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | from math import ceil, log10 | ||||||
|  | import os.path | ||||||
|  |  | ||||||
|  | from PIL import Image | ||||||
|  | import platformdirs | ||||||
|  |  | ||||||
|  | Image.MAX_IMAGE_PIXELS = 200000000 | ||||||
|  | MAP_SIZE = 1024 | ||||||
|  | DEFAULT_PATH = os.path.join(platformdirs.user_cache_dir(appname="lycacraft-paths", appauthor="Lycacraft"), "maps") | ||||||
|  |  | ||||||
|  | def clamp(mn, value, mx): | ||||||
|  |     return max(mn, min(mx, value)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |     # 2024-06-27_21.01.45_Lycacraft_minecraft~overworld_day.png | ||||||
|  |     # 6144,10240 | ||||||
|  |     input_path = input("Input image: ") | ||||||
|  |     output_path = input(f"Output dir (default: {DEFAULT_PATH}): ") | ||||||
|  |     if output_path.strip() == "": | ||||||
|  |         output_path = DEFAULT_PATH | ||||||
|  |  | ||||||
|  |     if not os.path.exists(output_path): | ||||||
|  |         os.makedirs(output_path) | ||||||
|  |     elif not os.path.isdir(output_path): | ||||||
|  |         print("Output path must be a directory") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     center_pos = input("(0, 0) = ").split(",") | ||||||
|  |     cx, cy = list(map(lambda p: int(p), center_pos)) | ||||||
|  |     image = Image.open(input_path) | ||||||
|  |     width, height = image.width, image.height | ||||||
|  |  | ||||||
|  |     left_maps = ceil(cx / MAP_SIZE) | ||||||
|  |     right_maps = ceil((width - cx) / MAP_SIZE) | ||||||
|  |     top_maps = ceil(cy / MAP_SIZE) | ||||||
|  |     bottom_maps = ceil((height - cy) / MAP_SIZE) | ||||||
|  |     h_maps = right_maps + left_maps | ||||||
|  |     v_maps = bottom_maps + top_maps | ||||||
|  |  | ||||||
|  |     total_maps = h_maps * v_maps | ||||||
|  |     digits = ceil(log10(total_maps - 1)) | ||||||
|  |     map_i_str = f"{{:0{digits}d}}" | ||||||
|  |     print(f"{total_maps} maps will be created") | ||||||
|  |     proceed = input("Do you want to proceed ? y/[n] ") | ||||||
|  |  | ||||||
|  |     if proceed.lower().strip() != "y": | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     for y in range(v_maps): | ||||||
|  |         y0 = y * MAP_SIZE | ||||||
|  |         y1 = y0 + MAP_SIZE | ||||||
|  |         # y0 = clamp(0, y0, height) | ||||||
|  |         # y1 = clamp(0, y1, height) | ||||||
|  |  | ||||||
|  |         start_y = clamp(0, y0, height) - cy | ||||||
|  |         end_y = clamp(0, y1, height) - cy | ||||||
|  |  | ||||||
|  |         for x in range(h_maps): | ||||||
|  |             x0 = x * MAP_SIZE | ||||||
|  |             x1 = x0 + MAP_SIZE | ||||||
|  |             # x0 = clamp(0, x0, width) | ||||||
|  |             # x1 = clamp(0, x1, width) | ||||||
|  |             start_x = clamp(0, x0, width) - cx | ||||||
|  |             end_x = clamp(0, x1, width) - cx | ||||||
|  |  | ||||||
|  |             map_i = x + y * h_maps | ||||||
|  |             print(f"\r{map_i + 1}/{total_maps} ({map_i / total_maps * 100:.2f}%)", end="") | ||||||
|  |             map_image = image.crop((x0, y0, x1, y1)) | ||||||
|  |             # map_image.save( | ||||||
|  |             #     os.path.join( | ||||||
|  |             #         output_path, | ||||||
|  |             #         f"map{map_i_str.format(map_i)}_{start_x},{start_y}_{end_x},{end_y}.png" | ||||||
|  |             #     ) | ||||||
|  |             # ) | ||||||
|  |             map_path = os.path.join(output_path, f"map{map_i_str.format(map_i)}_{x - left_maps}_{y - top_maps}.png") | ||||||
|  |             map_image.save(map_path) | ||||||
|  |  | ||||||
|  |     print(f"\r{total_maps}/{total_maps} (100%)") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
		Reference in New Issue
	
	Block a user