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/lib/section.py
2025-02-26 14:40:25 -05:00

424 lines
22 KiB
Python

#!~/.pyenv/versions/3.11.6/bin/python
#
# Copyright (c) 2024 Cutieguwu | Olivia Brooks
#
# -*- coding:utf-8 -*-
# @Title: Sections of layers in a game.
# @Author: Cutieguwu | Olivia Brooks
# @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca
# @Description: Classes descibing different sections in a pygame game.
#
# @Script: section.py
# @Date Created: 16 Apr, 2024
# @Last Modified: 19 Jun, 2024
# @Last Modified by: Cutieguwu | Olivia Brooks
# ----------------------------------------------------------
from icecream import ic
from random import randint
from pygame.image import load
from pygame.transform import scale
from lib import effect
from lib.system import DIRWORKING
class Section():
"""
A 16x16 area of the layer.
If this section can be interacted with by a player, `Section` will store the function.
"""
def __init__(self, game):
self.GAME = game # Cannot shortcut self.LEVEL due to Section befing used as part the init
self.SCALE = game.SCALE
self.WINDOW = game.WINDOW
def draw(self, layout):
"""
Draw the section-built layout on the screen.
"""
blocksInView = {}
blocksDrawn = 0
is_sorted = False
layers = []
currentLayerIndex = 0
# Improve processing time, only blocks on display will be drawn.
# Improve engine to support drawing areas with a single block, and not blitting blocks without alpha over blocks of lower layers.
for coords, block in layout.items():
try: # Does this block define a range fill.
for y in range(coords[1], coords[3] + 1):
if y in range(self.GAME.LEVEL.dy, self.GAME.LEVEL.dy + self.SCALE.gameY + 1): # Only try to blit blocks on a displayed row.
for x in range(coords[0], coords[2] + 1):
if x in range(self.GAME.LEVEL.dx, self.GAME.LEVEL.dx + self.SCALE.gameX + 1):
blocksInView[(x, y, block.layer)] = block # Add block layer to prevent overwriting blocks underneath.
if block.layer not in layers: # Get all the layers present of displayed blocks
layers.append(block.layer)
except IndexError:
if coords[0] in range(self.GAME.LEVEL.dx, self.GAME.LEVEL.dx + self.SCALE.gameX + 1) and coords[1] in range(self.GAME.LEVEL.dy, self.GAME.LEVEL.dy + self.SCALE.gameY + 1): # If block is in display area.
blocksInView[coords] = block
if block.layer not in layers: # Get all the layers present of displayed blocks
layers.append(block.layer)
while not is_sorted: # Sort layers to draw their blocks at the correct heights.
is_sorted = True
for i in range(0, len(layers) - 1):
if layers[i] > layers[i + 1]:
is_sorted = False
temp = layers[i]
layers[i] = layers[i + 1]
layers[i + 1] = temp
while blocksDrawn < len(blocksInView): # While there are blocks still not drawn
for location, block in blocksInView.items(): # Go through each block and draw it if it is on the layer currently being drawn.
if block.layer == layers[currentLayerIndex]: # Use a list of layers so that if no blocks in view are on a layer, no time is wasted parsing blocks on a layer that doesn't show.
x = (location[0] - self.GAME.LEVEL.dx) * self.SCALE.unitBlockPx # Get its x, y location in pixels.
y = (location[1] - self.GAME.LEVEL.dy) * self.SCALE.unitBlockPx
if block.TEXTURE[block.state]["IS_ANIMATED"]: # Get the current texture frame for animated textures.
block.animationClock = block.animationClock + 1
if block.animationClock >= (self.GAME.clock.get_fps() * block.TEXTURE[block.state]["TIME_PER_FRAME"]): # If the time in seconds passed is equal or greater than the time per frame of the animated texture
block.animationClock = 0
textures = len(block.TEXTURE[block.state]["texture"])
currentTexture = block.textureIndex # Fetch value to marginally reduce time to reach value repeatedly from dict.
if (currentTexture + 1) > (textures - 1):
block.textureIndex = 0
if block.TEXTURE[block.state]["ANIMATION_REPEATS"] != -1: # If a limited animation
block.animationRepeats = block.animationRepeats + 1
if block.animationRepeats == block.TEXTURE[block.state]["ANIMATION_REPEATS"]: # Block has animated for its state's full time.
Section.update_state(block) # Cannot call self.update_state for some reason.
# Causes two positional arugments by witchcraft.
else:
block.textureIndex = currentTexture + 1
texture = block.TEXTURE[block.state]["texture"][block.textureIndex]
else: # Otherwise print its static texture for its state.
texture = block.TEXTURE[block.state]["texture"][0]
if texture.get_width() != self.SCALE.unitBlockPx:
texture = scale(texture, (self.SCALE.unitBlockPx, self.SCALE.unitBlockPx))
self.WINDOW.window.blit(texture, (x, y))
blocksDrawn = blocksDrawn + 1
#ic(f"Printed blocks {blocksDrawn}/{len(blocksInView)}")
currentLayerIndex = currentLayerIndex + 1
def update_state(section):
"""
Updates the state of the section.
"""
section.state = section.TEXTURE[section.state]["FOLLOW_UP_STATE"]
section.textureIndex = 0
class Textures():
"""
Contains all textures used by the game.
Not the most memory-efficient. May want to rework this simple memory hog.
"""
def __init__(self, unitBlockPx):
self.load_textures()
self.update_scale(unitBlockPx)
def load_textures(self):
"""
Loads textures from file.
"""
BLOCKSDIR = DIRWORKING + "/data/image/game/blocks/"
self.TEXTURES = {
"BLOCK_BLUE": load(BLOCKSDIR + "wool_colored_blue.png").convert(),
"BLOCK_BROWN": load(BLOCKSDIR + "wool_colored_brown.png").convert(),
"BLOCK_CYAN": load(BLOCKSDIR + "wool_colored_cyan.png").convert(),
"BLOCK_GRAY": load(BLOCKSDIR + "wool_colored_gray.png").convert(),
"BLOCK_GREEN": load(BLOCKSDIR + "wool_colored_green.png").convert(),
"BLOCK_LIGHT_BLUE": load(BLOCKSDIR + "wool_colored_light_blue.png").convert(),
"BLOCK_LIME": load(BLOCKSDIR + "wool_colored_lime.png").convert(),
"BLOCK_MAGENTA": load(BLOCKSDIR + "wool_colored_magenta.png").convert(),
"BLOCK_ORANGE": load(BLOCKSDIR + "wool_colored_orange.png").convert(),
"BLOCK_PINK": load(BLOCKSDIR + "wool_colored_pink.png").convert(),
"BLOCK_PURPLE": load(BLOCKSDIR + "wool_colored_purple.png").convert(),
"BLOCK_RED": load(BLOCKSDIR + "wool_colored_red.png").convert(),
"BLOCK_SILVER": load(BLOCKSDIR + "wool_colored_silver.png").convert(),
"BLOCK_WHITE": load(BLOCKSDIR + "wool_colored_white.png").convert(),
"BLOCK_YELLOW": load(BLOCKSDIR + "wool_colored_yellow.png").convert(),
"PUDDLE_SOULS_RICH": load(BLOCKSDIR + "puddle_souls/puddle_souls_rich.png").convert_alpha(),
"PUDDLE_SOULS_SPENT": load(BLOCKSDIR + "puddle_souls/puddle_souls_spent.png").convert_alpha(),
"PUDDLE_BLOOD_0": load(BLOCKSDIR + "puddle_blood/puddle_blood_0.png").convert_alpha(),
"PUDDLE_BLOOD_1": load(BLOCKSDIR + "puddle_blood/puddle_blood_1.png").convert_alpha(),
"PUDDLE_BLOOD_2": load(BLOCKSDIR + "puddle_blood/puddle_blood_2.png").convert_alpha(),
"PUDDLE_BLOOD_3": load(BLOCKSDIR + "puddle_blood/puddle_blood_3.png").convert_alpha(),
"PUDDLE_BLOOD_4": load(BLOCKSDIR + "puddle_blood/puddle_blood_4.png").convert_alpha(),
"PUDDLE_BLOOD_NEUTRAL": load(BLOCKSDIR + "puddle_blood/puddle_blood_neutral.png").convert_alpha()
}
def update_scale(self, unitBlockPx):
"""
Updates the scaling of all textures.
"""
if unitBlockPx > self.TEXTURES["BLOCK_BLUE"].get_width():
self.load_textures()
temp = {}
for texture in self.TEXTURES.items():
temp[texture[0]] = scale(texture[1], (unitBlockPx, unitBlockPx))
self.TEXTURES = temp
class Blocks():
"""
Contains all section block definitions.
"""
"""
class block_demo():
def __init__(self):
self.TEXTURE :dict = {
"neutral": # Should be used as standard naming.
# NOTE: Can remove keys that are not in STATES, unlike shown.
{
"texture": [
Textures.TEXTURES["BLOCK_EXAMPLE_0"],
Textures.TEXTURES["BLOCK_EXAMPLE_1"]
],
"IS_ANIMATED": True, # Does this state has an animation, if so execute additional code to iterate through texture list.
"ANIMATION_REPEATS":-1, # Value of -1 indicates that animation runs infinitely.
# Can be left out if not IS_ANIMATED.
# Indicates the number of times the animation will loop in this state before passing to FOLLOW_UP_STATE.
"IS_INTERACTABLE": True, # Can be interacted with by the player during this state.
"IS_COLLIDABLE": False, # Player and entities will collide with block during this state.
"TIME_PER_FRAME": 1.0, # Amount of time (s) given to display each frame. Can be left out if not IS_ANIMATED.
"FOLLOW_UP_STATE": "on_trigger" # State that the block should progress to next. Can be left out if not IS_INTERACTABLE.
},
"on_trigger": # Should be used as standard naming.
{
"texture": [
Textures.TEXTURES["BLOCK_EXAMPLE"]
],
"IS_ANIMATED": False,
"IS_INTERACTABLE": False,
"IS_COLLIDABLE": False,
"TIME_PER_FRAME": 1.0, # TFP remains due to special temporary nature of on_trigger and off_trigger states.
"FOLLOW_UP_STATE": "while_trigger" # FUS remains due to special temporary nature of on_trigger and off_trigger states.
},
"while_trigger": # Should be used as standard naming.
{
"texture": [
Textures.TEXTURES["BLOCK_EXAMPLE"]
],
"IS_ANIMATED": False,
"IS_INTERACTABLE": True,
"IS_COLLIDABLE": False,
"TIME_PER_FRAME": 1.0,
"FOLLOW_UP_STATE": "off_trigger"
},
"off_trigger": # Should be used as standard naming.
{
"texture": [
Textures.TEXTURES["BLOCK_EXAMPLE"]
],
"IS_ANIMATED": False,
"IS_INTERACTABLE": False,
"IS_COLLIDABLE": False,
"TIME_PER_FRAME": 1.00,
"FOLLOW_UP_STATE": "neutral",
"ANIMATION_REPEATS":1
}
}
self.state :str = "neutral", # Variable for tracking the current texture.
self.textureIndex :int = "textureIndex": 0, # Tracks the index of the current animation frame.
self.animationClock :int = "animationClock": 0 # Used for tracking the time in frames, that a frame has been shown.
self.animationRepeats:int = 0 # Number of times that a limited animation has been run.
self.is_triggered :bool = False # Only needed if block in current state IS_INTERACTABLE
self.time :float = 0.0 # Used to track how long each frame has been displayed for animated textures.
self.layer :int = 0 # Used to track the layer of the block. For example, a door will be layer 1.
# while the floor below it is layer 0, so door gets interaction check priority.
def on_interact(self):
'''
Set of actions to execute upon interaction.
'''
"""
class block_blue():
def __init__(self, layer:int = 0):
self.TEXTURE = {
"neutral":{
"texture": [
Textures.TEXTURES["BLOCK_BLUE"]
],
"IS_ANIMATED": False,
"IS_INTERACTABLE": False,
"IS_COLLIDABLE": False
}
}
self.state = "neutral"
self.layer = layer
class block_rainbow():
def __init__(self, layer:int = 1):
self.TEXTURE = {
"neutral": {
"texture": [
Textures.TEXTURES["BLOCK_RED"],
Textures.TEXTURES["BLOCK_ORANGE"],
Textures.TEXTURES["BLOCK_YELLOW"],
Textures.TEXTURES["BLOCK_LIME"],
Textures.TEXTURES["BLOCK_GREEN"],
Textures.TEXTURES["BLOCK_CYAN"],
Textures.TEXTURES["BLOCK_LIGHT_BLUE"],
Textures.TEXTURES["BLOCK_BLUE"],
Textures.TEXTURES["BLOCK_PURPLE"],
Textures.TEXTURES["BLOCK_MAGENTA"],
Textures.TEXTURES["BLOCK_PINK"]
],
"IS_ANIMATED": True,
"ANIMATION_REPEATS": -1,
"IS_INTERACTABLE": True,
"IS_COLLIDABLE": True,
"TIME_PER_FRAME": 1.0,
"FOLLOW_UP_STATE": "on_trigger"
},
"on_trigger": {
"texture": [
Textures.TEXTURES["BLOCK_YELLOW"],
Textures.TEXTURES["BLOCK_ORANGE"],
Textures.TEXTURES["BLOCK_RED"]
],
"IS_ANIMATED": True,
"IS_INTERACTABLE": False,
"IS_COLLIDABLE": True,
"TIME_PER_FRAME": 1.0,
"ANIMATION_REPEATS": 5,
"FOLLOW_UP_STATE": "while_trigger"
},
"while_trigger": {
"texture": [
Textures.TEXTURES["BLOCK_PINK"],
Textures.TEXTURES["BLOCK_MAGENTA"],
Textures.TEXTURES["BLOCK_PURPLE"],
Textures.TEXTURES["BLOCK_BLUE"],
Textures.TEXTURES["BLOCK_LIGHT_BLUE"],
Textures.TEXTURES["BLOCK_CYAN"],
Textures.TEXTURES["BLOCK_GREEN"],
Textures.TEXTURES["BLOCK_LIME"],
Textures.TEXTURES["BLOCK_YELLOW"],
Textures.TEXTURES["BLOCK_ORANGE"],
Textures.TEXTURES["BLOCK_RED"]
],
"IS_ANIMATED": True,
"ANIMATION_REPEATS": -1,
"IS_INTERACTABLE": False,
"IS_COLLIDABLE": True,
"TIME_PER_FRAME": 1.0
}
}
self.state = "neutral"
self.textureIndex = 0
self.animationClock = 0
self.animationRepeats = 0
self.layer = layer
def on_interact(self):
"""
Set of actions to execute on interaction
"""
Section.update_state(section=self)
class puddle_of_souls():
def __init__(self, game, layer:int = 1):
self.TEXTURE = {
"neutral": {
"texture": [
Textures.TEXTURES["PUDDLE_SOULS_RICH"]
],
"IS_ANIMATED": False,
"IS_INTERACTABLE": True,
"IS_COLLIDABLE": True,
"FOLLOW_UP_STATE": "while_trigger"
},
"while_trigger": {
"texture": [
Textures.TEXTURES["PUDDLE_SOULS_SPENT"]
],
"IS_ANIMATED": False,
"IS_INTERACTABLE": False,
"IS_COLLIDABLE": False,
}
}
self.state = "neutral"
self.layer = layer
self.GAME = game
def on_interact(self):
"""
Set of actions to execute on interaction
"""
Section.update_state(section=self)
if randint(1, 100) <= 25:
self.GAME.PLAYER.health = self.GAME.PLAYER.health + 25
class puddle_of_blood():
def __init__(self, game, layer:int = 1):
self.TEXTURE = {
"on_death": {
"texture": [
Textures.TEXTURES["PUDDLE_BLOOD_0"],
Textures.TEXTURES["PUDDLE_BLOOD_1"],
Textures.TEXTURES["PUDDLE_BLOOD_2"],
Textures.TEXTURES["PUDDLE_BLOOD_3"],
Textures.TEXTURES["PUDDLE_BLOOD_4"]
],
"IS_ANIMATED": True,
"ANIMATION_REPEATS": 1,
"IS_INTERACTABLE": True,
"IS_COLLIDABLE": False,
"TIME_PER_FRAME": 0.25,
"FOLLOW_UP_STATE": "neutral"
},
"neutral": {
"texture": [
Textures.TEXTURES["PUDDLE_BLOOD_NEUTRAL"]
],
"IS_ANIMATED": False,
"IS_INTERACTABLE": True,
"IS_COLLIDABLE": False
}
}
self.state = "on_death"
self.textureIndex = 0
self.animationClock = 0
self.animationRepeats = 0
self.layer = layer
self.GAME = game
def on_interact(self):
"""
Set of actions to execute on interaction.
"""
self.GAME.PLAYER.effects.append(effect.War_Paint(self.GAME, self.GAME.PLAYER.character))