424 lines
22 KiB
Python
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)) |