#!~/.pyenv/versions/3.11.6/bin/python # # Copyright (c) 2024 Cutieguwu | Olivia Brooks # # -*- coding:utf-8 -*- # @Title: SoD: The Massacring Mushroom Level Editor. # @Author: Cutieguwu | Olivia Brooks # @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca # @Description: A Level Editor for SoD: The Massacring Mushroom. # # @Script: main.py # @Date Created: 10 May, 2024 # @Last Modified: 10 May, 2024 # @Last Modified by: Cutieguwu | Olivia Brooks # ---------------------------------------------------------- # Adapted from Serge Stroobandt on https://stackoverflow.com/questions/46419607/how-to-automatically-install-required-packages-from-a-python-script-as-necessary from pkg_resources import working_set from subprocess import check_call, CalledProcessError from sys import executable # Non built-ins libsReq = { "pygame", "icecream" } libsInst = { pkg.key for pkg in working_set # Find required and installed libraries. } libsMiss = libsReq - libsInst # Remove the already installed libraries from the dependency list. try: if len(libsMiss) != 0: check_call([executable, "-m", "pip", "install", "--upgrade", "pip"]) # Ensure pip is up-to-date. check_call([executable, "-m", "pip", "install", *libsMiss]) # Install all missing libraries. except CalledProcessError: print("Debug | CANNOT FIND OR INSTALL DEPENDENCIES - EXITING") raise SystemExit import pygame, level from icecream import ic from os import path import math ic.configureOutput(prefix="DEBUG | ") class Editor(): """ Level Editor """ def __init__(self): self.clock = pygame.time.Clock() editorIcon = pygame.image.load(path.dirname(__file__) + "/data/image/editor/icon.png") pygame.display.set_icon(editorIcon) self.VERSION = "0.0.1-alpha" pygame.display.set_caption(f"SoD: TMM Level Editor v{self.VERSION}") # Initialise the window. self.WINDOW = self.Window(self, 1440, 720) self.bgFill = (255, 255, 255) self.WINDOW.window.fill(self.bgFill) self.SCALE = self.Scale(parent=self) self.LEVEL = level.Level(self) self.editorOn = True def run(self): """ Editor run loop. """ while self.editorOn: self.clock.tick(self.WINDOW.framerate) # Clear window if self.bgFill != self.LEVEL.layout.BGFILL: # If the background has changed (may or may not change between levels) bgFill = self.LEVEL.layout.BGFILL # Update it locally. if isinstance(self.bgFill, pygame.surface.Surface): # If it's an image, scale it accordingly. width = bgFill.get_width() * self.PARENT.gameScale height = bgFill.get_height() * self.PARENT.gameScale pygame.transform.scale(self.bgFill, (width, height)) if isinstance(bgFill, tuple): # If background is a solid colour fill self.WINDOW.window.fill(bgFill) elif isinstance(bgFill, pygame.surface.Surface): # Elif background is an image fill. self.WINDOW.window.blit(bgFill, (self.LEVEL.dx * self.PARENT.gameScale, self.LEVEL.dy * self.PARENT.gameScale)) self.LEVEL.draw() self.PLAYER.update_move(playerActionMove) self.PLAYER.draw() # Refresh buttons. for b in self.gameButtons: b.draw() # Update objects on screen. pygame.display.flip() # Event handler for event in pygame.event.get(): if event.type == pygame.QUIT: self.gameOn = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: # Window exit via esc. ic(event.key) self.gameOn = False if self.LEVEL.layout.HAS_PLAYER: # If layout shows a player for controlsDict in [ self.controlsPlayerMovement, self.controlsPlayerInteraction ]: for action, key in controlsDict.items(): # Check movement triggers if key == event.key: # Key pressed is associated with a movement action. if controlsDict == self.controlsPlayerMovement: playerActionMove = action elif controlsDict == self.controlsPlayerInteraction: if action == "trigger": self.PLAYER.interact() elif event.type == pygame.KEYUP: if self.LEVEL.layout.HAS_PLAYER: # If layout shows a player for controlsDict in [ self.controlsPlayerMovement, self.controlsPlayerInteraction ]: for action, key in controlsDict.items(): # Check movement triggers if key == event.key: # Key released is associated with a movement action. if controlsDict == self.controlsPlayerMovement: if action == playerActionMove: # Only if the most recent movement action key was released playerActionMove = "neutral" # stop the player. elif event.type == pygame.MOUSEBUTTONDOWN: for button in self.gameButtons: if button.is_clicked: if button.ID == "settings": # open settings panel. pass elif event.type == pygame.MOUSEWHEEL: # Scale the level if self.SETTINGS["controls"]["mouse"]["is_inverted"]: scaleNew = self.SCALE.gameScale + event.y # event.y should be +1 or -1 generally. I have seen 0 and others before somehow. else: scaleNew = self.SCALE.gameScale - event.y if scaleNew in range(4, 30): # Limit the scaling range (limit level zoom). if self.SCALE.gameScale < scaleNew: # Source images need to be refreshed if character is growing in size. ic(self.PLAYER.update_images(self.PLAYER.character)) else: ic(self.PLAYER.update_scale()) self.SCALE.gameScale = scaleNew ic(self.SCALE.update_units_game()) ic(self.PLAYER.update_scale()) self.LEVEL.update_deltas_relative_to_player() if isinstance(self.bgFill, pygame.surface.Surface): # Update the scaling of the bgFill if it's an image fill. width = self.WINDOW.width / self.PARENT.gameX height = self.WINDOW.height / self.PARENT.gameY pygame.transform.scale(self.bgFill, (width, height)) class Window(): """ Creates a window. """ def __init__(self, parent, width:int = 1440, height:int = 720, framerate:int = 30): self.PARENT = parent self.width = width self.height = height self.size = self.width, self.height self.framerate = framerate self.update_aspect_ratio() ic(self.aspectX, self.aspectY) self.window = pygame.display.set_mode(self.size) def update_aspect_ratio(self): """ Updates the aspect ratio for correct block scaling. """ gcd = math.gcd(self.height, self.width) self.aspectX = self.width / gcd self.aspectY = self.height / gcd def get_smallest_window_edge(self): """ Returns the smallest edge of the window. """ if self.height < self.width: return self.height else: return self.width class Scale(): """ Handles all globally referenced scaling. """ def __init__(self, parent): self.PARENT = parent self.WINDOW = self.PARENT.WINDOW self.editorScale = 4 self.uiScale = 1 self.update_units_editor() def update_units_editor(self): """ Gets editor units. """ self.WINDOW.update_aspect_ratio() self.editorX = self.editorScale * self.WINDOW.aspectX # Blocks along X self.editorY = self.editorScale * self.WINDOW.aspectY # Blocks along Y ic(self.editorX, self.editorY) if self.editorX > self.editorY: # Determine the blocks along the shortest edge. blocksAlongEdge = self.editorY else: blocksAlongEdge = self.editorX self.unitBlockPx = self.WINDOW.get_smallest_window_edge() / blocksAlongEdge # Defines the edge length in pixels of a section. """ if (self.unitBlockPx % 2) != 0: # Is not an integer. self.unitBlockPx = (self.unitBlockPx // 1) + 1 # Round up. """ ic(self.unitBlockPx) editor = Editor() editor.run() pygame.quit()