This repository has been archived on 2025-07-12. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
shroom_of_doom/main.py
2025-02-26 14:40:25 -05:00

969 lines
44 KiB
Python
Executable File

#!~/.pyenv/versions/3.11.6/bin/python
#
# Copyright (c) 2024 Cutieguwu | Olivia Brooks
#
# -*- coding:utf-8 -*-
# @Title: Intro to Pygame.
# @Author: Cutieguwu | Olivia Brooks
# @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca
# @Description: Getting started with using PyGame engine.
#
# @Script: main.py
# @Date Created: 22 Mar, 2024
# @Last Modified: 19 Jun, 2024
# @Last Modified by: Cutieguwu | Olivia Brooks
# ----------------------------------------------------------
# Load lib.system to ensure everything is good to go dependency and system config wise.
from lib import system
# Knowing that all external dependencies are acessible, import the rest.
import pygame, json, threading
from pygame import freetype, mixer # Requires explicit importing.
from icecream import ic
from time import sleep
from lib import interface, level, entity, section
# Configure IceCream a bit.
# Use for release version.
def debug(msg, file="debug.txt"):
open(system.DIRWORKING + "/logs/" + file, "w") # Build a debug file if one doesn't exist.
with open(system.DIRWORKING + "/logs/" + file, "a") as f: # Append each debugging report to file
f.write(msg + "\n")
ic.configureOutput(prefix="DEBUG | ", outputFunction=debug, includeContext=True)
#ic.configureOutput(prefix="DEBUG | ")
class Game():
def __init__(self):
self.initOn = True
self.initSkip = False
self.clock = pygame.time.Clock()
# Set game icon for system.
gameIcon = pygame.image.load(system.DIRWORKING + "/data/image/game/icon.png")
pygame.display.set_icon(gameIcon)
self.VERSION = "0.0.1-alpha"
pygame.display.set_caption(f"Shroom of Doom: The Massacring Mushroom v{self.VERSION}")
self.SETTINGS = {}
self.load_settings(reset=False)
# Initialise the window.
self.WINDOW = self.Window(
self,
self.SETTINGS["window"]["size"]["width"],
self.SETTINGS["window"]["size"]["height"]
)
self.bgFill = (255, 255, 255)
self.WINDOW.window.fill(self.bgFill)
self.SCALE = self.Scale(game=self)
self.LEVEL = level.Level(self)
# Set up a font
freetype.init()
self.REPORTFONT = freetype.Font(system.DIRWORKING + "/data/font/Domestic_Manners.ttf", self.WINDOW.height * 0.03)
# Start audio mixer
mixer.init()
# Prep for BS loading screen.
self.gameProgressBars = []
self.bgImages = []
self.bgImagesIndex = 0
# Add more images to animate bg.
for imageDir in [
"/data/image/game/blocks/wool_colored_gray.png"
]:
image = pygame.image.load(system.DIRWORKING + imageDir)
image = pygame.transform.scale(image, (self.WINDOW.width, self.WINDOW.height))
self.bgImages.append(image)
# The following variables are created so that the progress bar is initialized to display immediately with the loading screen.
# If not listed, can run into a condition where these are undefined as the thread hasn't reached defining them fast enough.
self.initJobsComplete = 0
self.initJobsTotal = 1
self.initJobsImportIncomplete = 3 # Variable posessing the total number of BS Import report groups to fix scaling on progress bar if init is sped up.
self.loadStatus = ""
self.is_initSkipFirst = False # Variable describing if it is the first time that update_report() has seen the initSkip.
self.initProgressBar = interface.Progress_bar(
self,
5,
90,
90,
[
pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/mushroom_silhouette_1.png"),
pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/mushroom_silhouette_2.png")
],
pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_left.png"),
pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_right.png"),
pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_clear.png"),
pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_fill.png")
)
windowRefresh = threading.Thread(target=self.init_jobs) # Create a thread to run the mostly bs readouts.
windowRefresh.start() # Start that thread.
p = 0 # Number of times that pygame.K_p has been pressed.
# Event handler and display rendering must be done on master thread due to pygame structure.
while self.initOn:
self.clock.tick(30) # Run at a lower framerate to reduce system resource consumption when not needed.
self.draw_background()
# Update objects on screen.
pygame.display.flip()
# Event handler
for event in pygame.event.get(): # Short event handler to allow the game to be exited during startup.
if event.type == pygame.QUIT:
# Cannot currently kill thread. Must let it run out, otherwise employ spaghetti code in its target function.
self.initOn = False
self.gameOn = False
raise SystemExit
elif event.type == pygame.VIDEORESIZE:
self.WINDOW.update_window(event.w, event.h)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_p:
p = p + 1
if p == 2:
# Cannot currently kill thread. Must let it run out, otherwise employ spaghetti code in its target function.
self.initSkip = True
# Clean memory
del self.initJobsComplete, self.initJobsTotal, self.loadStatus, self.is_initSkipFirst, self.initSkip, self.initProgressBar, self.bgImagesIndex, self.bgImages, self.textRendered
def init_jobs(self):
"""
The actual init while the main thread handles the window rendering.
"""
# Dicts in list to alow iteration as dicts not iterable in python 3.3.1
self.reportsImports1 = [
{"report": "pygame", "time": 0.25},
{"report": "pygame.freetype", "time": 0.25},
{"report": "icecream.ic", "time": 0.25},
{"report": "json", "time": 0.25},
{"report": "threading", "time": 0.5},
{"report": "math", "time": 0.5},
{"report": "time.sleep ;)", "time": 0.5},
{"report": "levels", "time": 0.25},
{"report": "entities", "time": 0.25},
{"report": "system", "time": 0.25},
{"report": "urmom", "time": 0.5},
{"report": "sections.Section", "time": 0.4},
{"report": "scenes.Scene", "time": 0.25},
{"report": "smingusdinglebus.Dinglebob", "time": 0.5},
{"report": "pills.BLUE - Yes, this is all a lie.", "time": 1},
{"report": "thebritish.Food.OldeTimeFishandChips - Wait until I'm done lying.", "time": 1},
{"report": "Cannot find randos-free-ram-bin in system repos", "time": 2},
{"report": "Cannot find randos-free-ram-git in system repos", "time": 2}
]
self.reportsImports2 = [
{"report": "001%", "time": 2},
{"report": "002%", "time": 1},
{"report": "003%", "time": 2},
{"report": "030%", "time": 0.5},
{"report": "031%", "time": 0.5},
{"report": "099%", "time": 4},
{"report": "105%", "time": 2},
{"report": "400%", "time": 3},
{"report": "666%", "time": 3},
{"report": "6546588795009842089420845126960248328684206489564465987951613174159489636682%", "time": 0.75}
]
self.reportsImports3 = [
{"report": "Decompressing with gzip", "time": 1},
{"report": "Running build", "time": 1},
{"report": "Aquiring additional permissions without permission", "time": 2},
{"report": "Running makefile", "time": 1},
{"report": "Build Complete.", "time": 2}
]
self.initJobsTotal = len(self.reportsImports1) + len(self.reportsImports2) + len(self.reportsImports3) + 7 # 7 additional config reports.
self.initJobsComplete = 0
for i in range(0, 3): # Report all import lies.
self.update_init_load("import", i)
self.update_init_load("config", 0)
self.update_init_load("config", 1)
# Bad practice, but overwrite the class to load the textures once only.
section.Textures = section.Textures(self.SCALE.unitBlockPx)
self.update_init_load("config", 2)
self.PLAYER = entity.Player(self)
self.update_init_load("config", 3)
self.entityList = []
self.update_init_load("config", 4)
# Temp hardcoded player control buttons.
self.controlsPlayerMovement = {
"run_u": [pygame.K_UP, pygame.K_w],
"run_d": [pygame.K_DOWN, pygame.K_s],
"run_l": [pygame.K_LEFT, pygame.K_a],
"run_r": [pygame.K_RIGHT, pygame.K_d],
"dash": [pygame.K_LSHIFT, pygame.K_RSHIFT]
}
self.controlsPlayerInteraction = {
"trigger": [pygame.K_RETURN, pygame.K_e]
}
self.update_init_load("config", 5)
self.MENU_FUNCTIONS = interface.Interface_Functions(game=self)
self.update_init_load("config", 6)
self.gameInteractables = [] # Contains button classes that must have their conditions updated.
self.LEVEL.load_layout(level.Scenes.Intro)
# Temp override for user settings
self.WINDOW.auto_configure()
self.update_init_load("config", 7)
self.gameOn = True
self.initOn = False
def update_init_load(self, type:str, item:int = 0):
"""
Initial load reports and lies.
"""
try:
if not self.initSkip:
if system.FEATURES["matchcase"]["is_active"]:
match type:
case "import":
match item:
case 0:
reports = self.reportsImports1
msg = "Importing Libraries: "
case 1:
reports = self.reportsImports2
msg = "Importing Libraries: ftp://www.ram.sus/downloads/nightlybuilds - Cloning... "
case 2:
reports = self.reportsImports3
msg = "Importing Libraries: randos-free-ram - "
case _:
pass
self.initJobsImportIncomplete = self.initJobsImportIncomplete - 1
case "config":
msg = "Configuration: "
match item:
case 0:
reports = [{"report": "Not entirely a lie... if only.", "time": 2}]
case 1:
reports = [{"report": "Section - Textures", "time": 0.25}]
case 2:
reports = [{"report": "Entity - Player", "time": 0.25}]
case 3:
reports = [{"report": "Entity - Other", "time": 0.25}]
case 4:
reports = [{"report": "Game - Controls", "time": 0.25}]
case 5:
reports = [{"report": "Interface - Control Functions", "time": 0.25}]
case 6:
reports = [{"report": "Loading Menu Interface...", "time": 0.25}]
case 7:
# Final report time is very quick as a manual wait is applied afterwards so that there is time for reader to register the "Complete." on display.
reports = [{"report": "Complete.", "time": 2}]
case _:
pass
case _:
reports = [{"report": "Skipping BS", "time": 0.1}]
msg = "ha ha, you typed pp: "
self.update_report(reports, msg)
else:
if type == "import":
if item == 0:
reports = self.reportsImports1
msg = "Importing Libraries: "
elif item == 1:
reports = self.reportsImports2
msg = "Importing Libraries: ftp://www.ram.sus/downloads/nightlybuilds - Cloning... "
elif item == 2:
reports = self.reportsImports3
msg = "Importing Libraries: randos-free-ram - "
self.initJobsImportIncomplete = self.initJobsImportIncomplete - 1
elif type == "config":
msg = "Configuration: "
if item == 0:
reports = [{"report": "Not entirely a lie... if only.", "time": 2}]
elif item == 1:
reports = [{"report": "Section - Textures", "time": 0.5}]
elif item == 2:
reports = [{"report": "Entity - Player", "time": 0.5}]
elif item == 3:
reports = [{"report": "Entity - Other", "time": 0.25}]
elif item == 4:
reports = [{"report": "Game - Controls", "time": 0.5}]
elif item == 5:
reports = [{"report": "Interface - Control Functions", "time": 1}]
elif item == 6:
reports = [{"report": "Loading Menu Interface...", "time": 1}]
elif item == 7:
# Final report time is very quick as a manual wait is applied afterwards so that there is time for reader to register the "Complete." on display.
reports = [{"report": "Complete.", "time": 1}]
else:
reports = [{"report": "Skipping BS", "time": 0.1}]
msg = "ha ha, you typed pp: "
self.update_report(reports, msg)
except AttributeError: # If game stopped during init, avoid erroring out.
pass
def update_report(self, reports, msg):
"""
Updates the displayed reports.
"""
for report in reports:
if self.is_initSkipFirst: # If skipping and done the action below, just pass. Nothing to waste time processing.
pass
elif self.initSkip and not self.is_initSkipFirst: # If supposed to skip BS, but in the middle of a series of reports, recalculate and skip. Only ic skipping report to sdtout once.
self.initJobsTotal = self.initJobsComplete + 8 + self.initJobsImportIncomplete
# Clean memory
del self.initJobsImportIncomplete, self.reportsImports1, self.reportsImports2, self.reportsImports3
report = {"report": "Skipping BS", "time": 0.1}
msg = "ha ha, you typed pp: "
self.loadStatus = msg + report["report"]
ic(self.loadStatus)
self.is_initSkipFirst = True
break
else: # Otherwise ic the bs reports.
self.loadStatus = msg + report["report"]
ic(self.loadStatus)
sleep(report["time"])
self.initJobsComplete = self.initJobsComplete + 1
def draw_background(self):
"""
Sets and redraws the background.
"""
if self.initOn:
self.bgImagesIndex = self.bgImagesIndex + 1
if self.bgImagesIndex >= len(self.bgImages):
self.bgImagesIndex = 0
self.WINDOW.window.blit(self.bgImages[self.bgImagesIndex], (0, 0))
self.initProgressBar.draw(self.initJobsComplete, self.initJobsTotal)
# Render and display the mostly bs reports.
self.textRendered = self.REPORTFONT.render(self.loadStatus, (255, 255, 255))[0]
self.WINDOW.window.blit(
self.textRendered,
(
self.initProgressBar.x + self.initProgressBar.barImages["left"].get_width(),
self.initProgressBar.y - self.textRendered.get_height()
)
)
else:
# Clear window
if self.bgFill != self.LEVEL.layout.BG_FILL: # If the background has changed (may or may not change between levels)
self.bgFill = self.LEVEL.layout.BG_FILL # Update it locally.
if isinstance(self.bgFill, pygame.surface.Surface): # If it's an image, scale it accordingly.
self.bgFill = pygame.transform.scale(self.bgFill, (self.WINDOW.width, self.WINDOW.height))
if isinstance(self.bgFill, tuple): # If background is a solid colour fill
self.WINDOW.window.fill(self.bgFill)
else: # Else background is an image fill.
self.WINDOW.window.blit(self.bgFill, (0, 0))
def load_settings(self, reset:bool = False):
"""
Loads user settings from file.
"""
if reset is False: # If not told to reset settings to default, load user settings.
with open(system.DIRWORKING + "/config/settings/user.json") as f:
self.SETTINGS = json.load(f)
else:
with open(system.DIRWORKING + "/config/settings/default.json") as f:
self.INFO["settings"] = json.load(f)
ic(self.SETTINGS)
def run(self):
"""
Main run loop.
"""
self.playerActionMove = "neutral"
self.levelLoad = False
self.menuOn = True
# Required for self.update_framerate_average
#self.framerates = []
while self.gameOn:
# For menus that pause the game, self.levelLoad must be made True on resume.
if self.levelLoad:
self.LEVEL.load_level_state()
self.levelLoad = False
if system.FEATURES["matchcase"]["is_active"]:
match self.LEVEL.layout.TYPE:
case "level":
self.levelOn = True
while self.levelOn:
self.clock.tick(self.WINDOW.framerate)
if self.PLAYER.health <= 0:
# Player died, end game.
self.levelOn = False
self.LEVEL.load_layout(level.Scenes.Player_Died)
break
self.draw_background()
self.LEVEL.draw()
self.PLAYER.draw(self.playerActionMove)
if len(self.entityList) != 0: # There are still entities to kill.
for entity in self.entityList:
if entity.health > 0: # Draw entities that are alive..
entity.draw()
else:
entity.destroy() # Remove entities that have died.
else:
# All entities killed, end game.
self.LEVEL.load_layout(self.LEVEL.layout.FOLLOW_UP_LEVEL)
# Render Framerate
textRendered = self.REPORTFONT.render(str(self.clock.get_fps()), (255, 255, 255))[0]
self.WINDOW.window.blit(textRendered, (0, 0))
# Update objects on screen
pygame.display.flip()
# Event handler
for event in pygame.event.get():
match event.type:
case pygame.QUIT:
self.MENU_FUNCTIONS.exit_to_desktop()
case pygame.VIDEORESIZE:
self.WINDOW.update_window(event.w, event.h)
case pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE: # Pause game on ESC
self.LEVEL.load_layout(level.Menus.Pause)
else:
for controlsDict in [
self.controlsPlayerMovement,
self.controlsPlayerInteraction
]:
for action, keyList in controlsDict.items(): # Check movement triggers
if event.key in keyList: # Key pressed is associated with a movement action.
match controlsDict:
case self.controlsPlayerMovement:
if action in ["run_l", "run_r", "run_u", "run_d"]:
self.playerActionMove = action
elif action == "dash":
self.PLAYER.dash(self.playerActionMove)
case self.controlsPlayerInteraction:
if action == "trigger":
self.PLAYER.interact()
case pygame.KEYUP:
if self.LEVEL.layout.HAS_PLAYER: # If layout shows a player
for controlsDict in [
self.controlsPlayerMovement,
self.controlsPlayerInteraction
]:
for action, keyList in controlsDict.items(): # Check movement triggers
if event.key in keyList: # Key released is associated with a movement action.
if controlsDict == self.controlsPlayerMovement:
if action == self.playerActionMove: # Only if the most recent movement action key was released
self.playerActionMove = "neutral" # stop the player.
case pygame.MOUSEBUTTONDOWN:
if 1 in pygame.mouse.get_pressed():
self.PLAYER.attack()
case pygame.MOUSEWHEEL:
# Scale the level
if self.SETTINGS["controls"]["mouse"]["is_inverted"]:
scaleNew = self.SCALE.scaleGame + (event.y * 2)
else:
scaleNew = self.SCALE.scaleGame + (event.y * 2)
if scaleNew in self.SCALE.scaleRange: # Limit the scaling range (limit level zoom).
self.SCALE.scaleGame = scaleNew
ic(self.SCALE.update_units_game())
ic(self.PLAYER.update_scale())
for entity in self.entityList:
entity.update_scale()
section.Textures.update_scale(self.SCALE.unitBlockPx)
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.GAME.gameX
height = self.WINDOW.height / self.GAME.gameY
pygame.transform.scale(self.bgFill, (width, height))
case _: # Except all other events.
pass
case "menu":
self.menuOn = True
while self.menuOn:
self.clock.tick(self.WINDOW.framerate)
self.draw_background()
self.LEVEL.draw()
if self.LEVEL.layout.HAS_PLAYER:
self.PLAYER.draw()
# Refresh interactables.
for interactable in self.gameInteractables:
interactable.draw()
pygame.display.flip()
for event in pygame.event.get():
match event.type:
case pygame.QUIT: # Exit game.
self.MENU_FUNCTIONS.exit_to_desktop()
case pygame.VIDEORESIZE:
self.WINDOW.update_window(event.w, event.h)
case pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
if isinstance(self.LEVEL.layout, level.Menus.Pause):
self.MENU_FUNCTIONS.return_to_game()
case pygame.MOUSEBUTTONDOWN:
if 1 in pygame.mouse.get_pressed():
for interactable in self.gameInteractables:
if interactable.is_hovered():
if interactable.functionArgs is not None:
interactable.function(interactable.functionArgs)
else:
interactable.function()
case _:
pass
case "scene":
self.sceneOn = True
escapes = 0
while self.sceneOn:
self.clock.tick(self.WINDOW.framerate)
self.LEVEL.FRAMES.draw()
pygame.display.flip()
for event in pygame.event.get():
match event.type:
case pygame.QUIT: # Exit game.
self.MENU_FUNCTIONS.exit_to_desktop()
case pygame.VIDEORESIZE:
self.WINDOW.update_window(event.w, event.h)
self.LEVEL.FRAMES.update_scale()
case pygame.KEYDOWN:
if self.LEVEL.layout.IS_SKIPPABLE and event.key == pygame.K_ESCAPE:
if escapes == 1:
self.LEVEL.FRAMES.skip()
else:
escapes = escapes + 1
case _:
pass
elif self.LEVEL.layout.TYPE == "level": # Run the general level rederer and event handler.
self.levelOn = True
while self.levelOn:
self.clock.tick(self.WINDOW.framerate)
if self.PLAYER.health <= 0:
# Player died, end game.
self.LEVEL.load_layout(level.Scenes.Player_Died)
self.draw_background()
self.LEVEL.draw()
self.PLAYER.draw(self.playerActionMove)
if len(self.entityList) != 0:
for entity in self.entityList:
if entity.health > 0:
entity.draw()
else:
entity.destroy()
else:
# All entities killed, end game.
self.LEVEL.load_layout(self.LEVEL.layout.FOLLOW_UP_LEVEL)
# Render Framerate
textRendered = self.REPORTFONT.render(str(self.clock.get_fps()), (255, 255, 255))[0]
self.WINDOW.window.blit(textRendered, (0, 0))
# Update objects on screen
pygame.display.flip()
# Event handler
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.MENU_FUNCTIONS.exit_to_desktop()
elif event.type == pygame.VIDEORESIZE:
self.WINDOW.update_window(event.w, event.h)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE: # Pause game on ESC
self.LEVEL.load_layout(level.Menus.Pause)
else:
for controlsDict in [
self.controlsPlayerMovement,
self.controlsPlayerInteraction
]:
for action, keyList in controlsDict.items(): # Check movement triggers
if event.key in keyList: # Key pressed is associated with a movement action.
if controlsDict == self.controlsPlayerMovement:
if action in ["run_l", "run_r", "run_u", "run_d"]:
self.playerActionMove = action # Keep track of the current movement action.
ic(self.playerActionMove)
elif action == "dash":
self.PLAYER.dash(self.playerActionMove)
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, keyList in controlsDict.items(): # Check movement triggers
if event.key in keyList: # Key released is associated with a movement action.
if controlsDict == self.controlsPlayerMovement:
if action == self.playerActionMove: # Only if the most recent movement action key was released
self.playerActionMove = "neutral" # stop the player.
elif event.type == pygame.MOUSEBUTTONDOWN:
if 1 in pygame.mouse.get_pressed():
self.PLAYER.attack()
elif event.type == pygame.MOUSEWHEEL:
# Scale the level
if self.SETTINGS["controls"]["mouse"]["is_inverted"]:
scaleNew = self.SCALE.scaleGame + (event.y * 2)
else:
scaleNew = self.SCALE.scaleGame + (event.y * 2)
if scaleNew in self.SCALE.scaleRange: # Limit the scaling range (limit level zoom).
self.SCALE.scaleGame = scaleNew
ic(self.SCALE.update_units_game())
ic(self.PLAYER.update_scale())
for entity in self.entityList:
entity.update_scale()
section.Textures.update_scale(self.SCALE.unitBlockPx)
self.LEVEL.FRAMES.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.GAME.gameX
height = self.WINDOW.height / self.GAME.gameY
pygame.transform.scale(self.bgFill, (width, height))
elif self.LEVEL.layout.TYPE == "menu": # Run the general menu renderer and event handler.
self.menuOn = True
while self.menuOn:
self.clock.tick(self.WINDOW.framerate)
self.draw_background()
self.LEVEL.draw()
if self.LEVEL.layout.HAS_PLAYER:
self.PLAYER.draw()
# Refresh interactables.
for interactable in self.gameInteractables:
interactable.draw()
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT: # Exit game.
self.MENU_FUNCTIONS.exit_to_desktop()
elif event.type == pygame.VIDEORESIZE:
self.WINDOW.update_window(event.w, event.h)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
if isinstance(self.LEVEL.layout, level.Menus.Pause):
self.MENU_FUNCTIONS.return_to_game()
elif event.type == pygame.MOUSEBUTTONDOWN:
if 1 in pygame.mouse.get_pressed():
for interactable in self.gameInteractables:
if interactable.is_hovered():
if interactable.functionArgs is not None:
interactable.function(interactable.functionArgs)
else:
interactable.function()
elif self.LEVEL.layout.TYPE == "scene": # Run the general scene renderer and event handler.
self.sceneOn = True
escapes = 0
while self.sceneOn:
self.clock.tick(self.WINDOW.framerate)
self.LEVEL.FRAMES.draw()
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT: # Exit game.
self.MENU_FUNCTIONS.exit_to_desktop()
elif event.type == pygame.VIDEORESIZE:
self.WINDOW.update_window(event.w, event.h)
elif event.type == pygame.KEYDOWN:
if self.LEVEL.layout.IS_SKIPPABLE and event.key == pygame.K_ESCAPE:
if escapes == 1:
self.LEVEL.FRAMES.skip()
else:
escapes = escapes + 1
def update_framerate_average(self):
framerateAverage = 0
for i in self.framerates:
framerateAverage += i
return framerateAverage / len(self.framerates)
class Window():
"""
Creates a window.
"""
def __init__(self, game, width:int = 1440, height:int = 720, framerate:int = 30):
self.GAME = game
self.width = width
self.height = height
self.framerate = framerate
self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE)
def auto_configure(self):
"""
Autoconfigures display settings.
"""
system.update_framerate()
framerate = system.DISPLAY["framerate"]
ic("Detected highest framerate for this window as", framerate)
ic("Applying...")
self.framerate = framerate
self.GAME.clock.tick(self.framerate)
ic("Done.")
def toggle_fullscreen(self):
"""
Toggles window fullscreening.
Returns False if cannot.
"""
# Isolate Linux platforms as this method has always worked as get_driver has supported x11 and wayland since pagame v1.
if system.PLATFORM["kernel"] == "Linux" or system.PLATFORM["libraries"]["pygame"] >= (2, 0, 0):
pygame.display.toggle_fullscreen()
return True
else:
ic("Cannot enter fullscreen. Please update pygame to v2.0.0 or newer.")
return False
def toggle_vsync(self):
"""
Sets up vsync if possible. Falls back to pseudo vsync from system library.
"""
if system.FEATURES["vsync"]["is_active"]:
self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE, vsync=1)
else:
system.update_framerate()
def update_window(self, width, height):
"""
Updates the window.
"""
# Window resizing handling changed in pygame v2 to be handled automatically.
if system.FEATURES["resizeable"]["is_active"]:
self.width = self.window.get_width()
self.height = self.window.get_height()
else:
self.width = width
self.height = height
if system.FEATURES["vsync"]["is_active"]:
self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE, vsync=1)
else:
self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE)
self.GAME.SCALE.update_units_game()
class Scale():
"""
Handles all globally referenced scaling.
"""
def __init__(self, game):
self.GAME = game
self.WINDOW = self.GAME.WINDOW
self.scaleGlobal = 2 # Adjusts other scales for HiDPI screens.
self.scaleGame = 36 * self.scaleGlobal
self.scaleUi = 1 * self.scaleGlobal
self.scaleRange = range(int("%.0f" % (self.scaleGame * (1/2))), int("%.0f" % (self.scaleGame * 2)))
self.update_units_game()
def update_units_game(self):
"""
Gets game units.
"""
self.gameY = (1 / self.scaleGame) * self.WINDOW.height # Blocks along Y
if (self.WINDOW.height / self.gameY) % 2 != 0: # If not filling full screen, add a block.
self.gameY = int((self.gameY // 1) + 2) # Produce a clean integer.
else:
self.gameY = int((self.gameY // 1) + 1)
self.unitBlockPx = self.WINDOW.height / self.gameY # 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.
self.gameX = self.WINDOW.width / self.unitBlockPx # Blocks along X
if (self.WINDOW.width / self.gameX) % 2 != 0: # If not filling full screen, add a block.
self.gameX = int((self.gameX // 1) + 2) # Produce a clean integer.
else:
self.gameX = int((self.gameX // 1) + 1)
game = Game()
game.run()
pygame.quit()