Initial Commit
This commit is contained in:
BIN
lib/__pycache__/effect.cpython-311.pyc
Normal file
BIN
lib/__pycache__/effect.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/entities.cpython-311.pyc
Normal file
BIN
lib/__pycache__/entities.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/entity.cpython-311.pyc
Normal file
BIN
lib/__pycache__/entity.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/interface.cpython-311.pyc
Normal file
BIN
lib/__pycache__/interface.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/level.cpython-311.pyc
Normal file
BIN
lib/__pycache__/level.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/levels.cpython-311.pyc
Normal file
BIN
lib/__pycache__/levels.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/scene.cpython-311.pyc
Normal file
BIN
lib/__pycache__/scene.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/scenes.cpython-311.pyc
Normal file
BIN
lib/__pycache__/scenes.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/section.cpython-311.pyc
Normal file
BIN
lib/__pycache__/section.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/sections.cpython-311.pyc
Normal file
BIN
lib/__pycache__/sections.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/system.cpython-311.pyc
Normal file
BIN
lib/__pycache__/system.cpython-311.pyc
Normal file
Binary file not shown.
51
lib/effect.py
Normal file
51
lib/effect.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#!~/.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: Effects for entities.
|
||||
#
|
||||
# @Script: effect.py
|
||||
# @Date Created: 11 Jun, 2024
|
||||
# @Last Modified: 11 Jun, 2024
|
||||
# @Last Modified by: Cutieguwu | Olivia Brooks
|
||||
# ----------------------------------------------------------
|
||||
|
||||
from pygame.image import load
|
||||
from pygame.transform import scale
|
||||
from lib.system import DIRWORKING
|
||||
|
||||
class War_Paint():
|
||||
def __init__(self, game, character):
|
||||
"""
|
||||
War paint for player.
|
||||
"""
|
||||
|
||||
self.GAME = game
|
||||
|
||||
self.imagePaths = {}
|
||||
self.images = {}
|
||||
|
||||
self.update_images(character)
|
||||
|
||||
def function():
|
||||
"""
|
||||
War Paint is just a texture effect.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def update_images(self, character):
|
||||
"""
|
||||
Updates effect images.
|
||||
"""
|
||||
|
||||
for key in ["neutral_1", "neutral_2", "run_l_1", "run_l_2", "run_r_1", "run_r_2", "run_u_1", "run_u_2", "run_d_1", "run_d_2"]:
|
||||
self.imagePaths[key] = DIRWORKING + f"/data/image/game/player/{character}/war_paint/{key}.png"
|
||||
|
||||
for key, value in self.imagePaths.items():
|
||||
self.images[key] = load(self.imagePaths[key]).convert_alpha()
|
||||
self.images[key] = scale(self.images[key], (self.GAME.SCALE.unitBlockPx, self.GAME.SCALE.unitBlockPx))
|
||||
617
lib/entity.py
Normal file
617
lib/entity.py
Normal file
@@ -0,0 +1,617 @@
|
||||
#!~/.pyenv/versions/3.11.6/bin/python
|
||||
#
|
||||
# Copyright (c) 2024 Cutieguwu | Olivia Brooks
|
||||
#
|
||||
# -*- coding:utf-8 -*-
|
||||
# @Title: Players and Characters.
|
||||
# @Author: Cutieguwu | Olivia Brooks
|
||||
# @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca
|
||||
# @Description: AIs for players and characters.
|
||||
#
|
||||
# @Script: entity.py
|
||||
# @Date Created: 22 Mar, 2024
|
||||
# @Last Modified: 19 Jun, 2024
|
||||
# @Last Modified by: Cutieguwu | Olivia Brooks
|
||||
# ----------------------------------------------------------
|
||||
|
||||
import pygame
|
||||
from pygame import mixer
|
||||
from icecream import ic
|
||||
from random import randint
|
||||
from lib.system import DIRWORKING, FEATURES
|
||||
from lib.section import Blocks
|
||||
|
||||
|
||||
class Player():
|
||||
"""
|
||||
Handles the player character and associated animations and states.
|
||||
"""
|
||||
|
||||
def __init__(self, game, character:str = "default_a"):
|
||||
self.GAME = game
|
||||
self.WINDOW = self.GAME.WINDOW
|
||||
self.SCALE = self.GAME.SCALE
|
||||
self.LEVEL = self.GAME.LEVEL
|
||||
|
||||
self.images = {}
|
||||
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.layer = 1
|
||||
|
||||
self.health = 100
|
||||
self.effects = []
|
||||
|
||||
self.update_images(character)
|
||||
|
||||
self.animationClock = 0
|
||||
self.stateDirection = "neutral" # String of character state
|
||||
self.stateDirectionOld = "run_d" # Last movement action by player.
|
||||
self.stateDirectionOldX = "run_l" # Last x movement action for rotating to neutral from run_u
|
||||
self.stateFrame = 1 # Animation frame number
|
||||
self.stateImage = self.images[f"{self.stateDirection}_{self.stateFrame}"] # Default inital character state.
|
||||
self.timeSinceInteraction = 0
|
||||
self.moveClock = 0
|
||||
|
||||
self.update_scale()
|
||||
|
||||
def adjust_center(self, x, y):
|
||||
"""
|
||||
Determines if the player can be centered on the level instead of shifting position from center.
|
||||
"""
|
||||
|
||||
# if newPosition is greater than half the viewable area relative to dx if it were adjusted.
|
||||
# less efficient:
|
||||
# if dx were adjusted, if any wall becomes only void, adjust player position.
|
||||
|
||||
dCenterX = self.SCALE.gameX / 2
|
||||
dCenterY = self.SCALE.gameY / 2
|
||||
|
||||
if x <= dCenterX and self.LEVEL.dx == 0:
|
||||
# Player in left region of layout.
|
||||
# Player must move.
|
||||
pass
|
||||
elif x > (self.LEVEL.width - dCenterX) + 1:
|
||||
# Player in right region of layout.
|
||||
# Player must move.
|
||||
pass
|
||||
else:
|
||||
# Level should move.
|
||||
|
||||
newDx = self.LEVEL.dx + (x - self.x)
|
||||
|
||||
if newDx >= 0:
|
||||
self.LEVEL.dx = newDx
|
||||
|
||||
if y <= dCenterY and self.LEVEL.dy == 0:
|
||||
# Player in top region of layout
|
||||
# Player must move.
|
||||
pass
|
||||
elif y > (self.LEVEL.height - dCenterY) + 1:
|
||||
# Player in bottom region of layout
|
||||
# Player must move.
|
||||
pass
|
||||
else:
|
||||
# Level should move.
|
||||
newDy = self.LEVEL.dy + (y - self.y)
|
||||
|
||||
if newDy >= 0:
|
||||
self.LEVEL.dy = newDy
|
||||
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def attack(self):
|
||||
"""
|
||||
Makes the player attack.
|
||||
"""
|
||||
|
||||
attackPositions = []
|
||||
|
||||
if self.stateDirection == "run_l":
|
||||
x = self.x - 1
|
||||
|
||||
attackPositions.append((x, self.y - 1))
|
||||
attackPositions.append((x, self.y))
|
||||
attackPositions.append((x, self.y + 1))
|
||||
|
||||
elif self.stateDirection == "run_r":
|
||||
x = self.x + 1
|
||||
|
||||
attackPositions.append((x, self.y - 1))
|
||||
attackPositions.append((x, self.y))
|
||||
attackPositions.append((x, self.y + 1))
|
||||
|
||||
elif self.stateDirection == "run_u":
|
||||
y = self.y - 1
|
||||
|
||||
attackPositions.append((self.x - 1, y))
|
||||
attackPositions.append((self.x, y))
|
||||
attackPositions.append((self.x + 1, y))
|
||||
|
||||
elif self.stateDirection == "run_d":
|
||||
y = self.y + 1
|
||||
|
||||
attackPositions.append((self.x - 1, y))
|
||||
attackPositions.append((self.x, y))
|
||||
attackPositions.append((self.x + 1, y))
|
||||
|
||||
for entity in self.GAME.entityList:
|
||||
if (entity.x, entity.y) in attackPositions:
|
||||
entity.health = entity.health - 25
|
||||
|
||||
def can_interact(self, x, y):
|
||||
"""
|
||||
Checks if the player can interact with the section in front of it.
|
||||
"""
|
||||
|
||||
try: # Try to set the section based on coords.
|
||||
coords = (x, y)
|
||||
section = self.LEVEL.layout.LAYOUT[coords]
|
||||
except KeyError:
|
||||
for coords, block in self.LEVEL.layout.LAYOUT.items(): # Deeper search, check if block in an area fill.
|
||||
if block.layer == self.layer:
|
||||
try:
|
||||
if y in range(coords[1], coords[3] + 1):
|
||||
if x in range(coords[0], coords[2] + 1):
|
||||
section = self.LEVEL.layout.LAYOUT[coords] # Set the definition as the desired section.
|
||||
break
|
||||
except IndexError: # Block doesn't define a range.
|
||||
pass
|
||||
|
||||
try:
|
||||
if section.TEXTURE[section.state]["IS_INTERACTABLE"]:
|
||||
return coords
|
||||
else:
|
||||
return False
|
||||
except UnboundLocalError: # Could not find a block drawn at those coords.
|
||||
return False
|
||||
|
||||
def can_move(self, x, y):
|
||||
"""
|
||||
Checks if the player can move to a position.
|
||||
"""
|
||||
|
||||
try: # Try to set the section based on coords.
|
||||
section = self.LEVEL.layout.LAYOUT[(x, y)]
|
||||
except KeyError:
|
||||
layerLast = -1
|
||||
|
||||
for coords, block in self.LEVEL.layout.LAYOUT.items(): # Deeper search, check if block in an area fill.
|
||||
if block.layer <= self.layer and block.layer >= layerLast:
|
||||
try:
|
||||
if y in range(coords[1], coords[3] + 1):
|
||||
if x in range(coords[0], coords[2] + 1):
|
||||
section = self.LEVEL.layout.LAYOUT[coords] # Set the definition as the desired section.
|
||||
layerLast = block.layer
|
||||
except IndexError: # Block doesn't define a range.
|
||||
pass
|
||||
|
||||
try:
|
||||
if section.TEXTURE[section.state]["IS_COLLIDABLE"]:
|
||||
return False
|
||||
else:
|
||||
for entity in self.GAME.entityList:
|
||||
if entity.x == x and entity.y == y: # If there is an entity at this location, cannot move.
|
||||
return False
|
||||
return True
|
||||
except UnboundLocalError: # Could not find a block drawn at those coords.
|
||||
return False
|
||||
|
||||
def dash(self, direction):
|
||||
"""
|
||||
Dashes player in gameX and gameY.
|
||||
"""
|
||||
|
||||
x, y = self.x, self.y
|
||||
|
||||
dashDist = 0
|
||||
|
||||
canDash = False
|
||||
|
||||
# If Player is not moving to same position as currently located
|
||||
while dashDist != 5:
|
||||
dashDist = dashDist + 1
|
||||
|
||||
if FEATURES["matchcase"]["is_active"]:
|
||||
match direction:
|
||||
case "run_l":
|
||||
x = self.x - dashDist
|
||||
self.stateDirectionOldX = direction
|
||||
case "run_r":
|
||||
x = self.x + dashDist
|
||||
self.stateDirectionOldX = direction
|
||||
case "run_u":
|
||||
y = self.y - dashDist
|
||||
case "run_d":
|
||||
y = self.y + dashDist
|
||||
|
||||
# Iterate until player is trying to move to a position that it can move.
|
||||
elif direction == "run_l":
|
||||
x = self.x - dashDist
|
||||
self.stateDirectionOldX = direction
|
||||
elif direction == "run_r":
|
||||
x = self.x + dashDist
|
||||
self.stateDirectionOldX = direction
|
||||
elif direction == "run_u":
|
||||
y = self.y - dashDist
|
||||
elif direction == "run_d":
|
||||
y = self.y + dashDist
|
||||
|
||||
if self.can_move(x, y):
|
||||
canDash = True
|
||||
moveX, moveY = x, y
|
||||
else: # Prevent warping through blocks if dashing immediately against them.
|
||||
moveX, moveY = self.x, self.y
|
||||
canDash = False
|
||||
break
|
||||
|
||||
if canDash:
|
||||
self.adjust_center(moveX, moveY)
|
||||
self.moveClock = 0
|
||||
|
||||
self.update_animation(direction, is_updateMove = True)
|
||||
|
||||
def draw(self, direction):
|
||||
"""
|
||||
Draw the player on the screen.
|
||||
"""
|
||||
|
||||
self.update_move(direction)
|
||||
|
||||
location = ((self.x - self.LEVEL.dx) * self.SCALE.unitBlockPx, (self.y - self.LEVEL.dy) * self.SCALE.unitBlockPx)
|
||||
|
||||
self.WINDOW.window.blit(self.stateImage, location)
|
||||
|
||||
for effect in self.effects:
|
||||
self.WINDOW.window.blit(effect.images[f"{self.stateDirection}_{self.stateFrame}"], location)
|
||||
|
||||
def interact(self):
|
||||
"""
|
||||
Interacts with the object in front of the player.
|
||||
"""
|
||||
|
||||
x = self.x
|
||||
y = self.y
|
||||
|
||||
if self.stateDirection == "neutral":
|
||||
if self.stateDirectionOld == "run_l":
|
||||
x = self.x - 1
|
||||
elif self.stateDirectionOld == "run_r":
|
||||
x = self.x + 1
|
||||
elif self.stateDirectionOld == "run_u":
|
||||
y = self.y - 1
|
||||
elif self.stateDirectionOld == "run_d":
|
||||
y = self.y + 1
|
||||
else:
|
||||
if self.stateDirection == "run_l":
|
||||
x = self.x - 1
|
||||
elif self.stateDirection == "run_r":
|
||||
x = self.x + 1
|
||||
elif self.stateDirection == "run_u":
|
||||
y = self.y - 1
|
||||
elif self.stateDirection == "run_d":
|
||||
y = self.y + 1
|
||||
|
||||
coords = self.can_interact(x, y)
|
||||
|
||||
if isinstance(coords, tuple):
|
||||
section = self.LEVEL.layout.LAYOUT[coords]
|
||||
|
||||
if section.layer == self.layer:
|
||||
section.on_interact()
|
||||
|
||||
def update_animation(self, direction: str = None, is_updateMove = False, ovrrScaling = False):
|
||||
"""
|
||||
Sets the current animation image of the player.
|
||||
"""
|
||||
|
||||
self.animationClock = self.animationClock + 1
|
||||
|
||||
# Check time since interacted.
|
||||
if is_updateMove == True:
|
||||
self.timeSinceInteraction = 0
|
||||
elif self.stateDirection != "neutral" and is_updateMove == False:
|
||||
self.timeSinceInteraction = self.timeSinceInteraction + 1
|
||||
|
||||
if ovrrScaling: # Replace current image with scaled.
|
||||
self.stateImage = self.images[f"{self.stateDirection}_{self.stateFrame}"]
|
||||
elif self.animationClock >= (self.GAME.clock.get_fps() / 3) or (direction is not None and self.stateDirection != direction):
|
||||
# Update animation every half a second
|
||||
# Or when direction has changed.
|
||||
|
||||
if self.stateFrame == 1:
|
||||
self.stateFrame = 2
|
||||
elif self.stateFrame == 2:
|
||||
self.stateFrame = 1
|
||||
|
||||
# Change Direction
|
||||
if direction is not None and self.stateDirection != direction: # If direction has changed, update the animation at the same time.
|
||||
self.stateDirection = direction
|
||||
|
||||
# Make player idle if not moving for 3 frame updates.
|
||||
if self.timeSinceInteraction > (3 * (self.GAME.clock.get_fps() / 2)):
|
||||
if self.stateDirection == "run_u":
|
||||
self.stateDirection = self.stateDirectionOldX
|
||||
#ic("Returning to neutral via the last L/R direction")
|
||||
else:
|
||||
self.stateDirection = "neutral"
|
||||
self.timeSinceInteraction = 0
|
||||
#ic("Returning to neutral and resetting.")
|
||||
|
||||
self.stateImage = self.images[f"{self.stateDirection}_{self.stateFrame}"]
|
||||
|
||||
self.animationClock = 0
|
||||
|
||||
def update_images(self, character):
|
||||
"""
|
||||
Updates animation images based on the character.
|
||||
"""
|
||||
|
||||
self.character = character
|
||||
|
||||
for key in ["neutral_1", "neutral_2", "run_l_1", "run_l_2", "run_r_1", "run_r_2", "run_u_1", "run_u_2", "run_d_1", "run_d_2"]:
|
||||
self.images[key] = pygame.image.load(DIRWORKING + f"/data/image/game/player/{self.character}/base/{key}.png").convert_alpha()
|
||||
|
||||
for effect in self.effects:
|
||||
effect.update_images(self.character)
|
||||
|
||||
def update_move(self, direction:str):
|
||||
"""
|
||||
Moves player in gameX and gameY.
|
||||
"""
|
||||
|
||||
x = self.x
|
||||
y = self.y
|
||||
|
||||
self.stateDirectionOld = self.stateDirection
|
||||
|
||||
if direction == "run_l":
|
||||
x = self.x - 1
|
||||
self.stateDirectionOldX = direction
|
||||
elif direction == "run_r":
|
||||
x = self.x + 1
|
||||
self.stateDirectionOldX = direction
|
||||
elif direction == "run_u":
|
||||
y = self.y - 1
|
||||
elif direction == "run_d":
|
||||
y = self.y + 1
|
||||
|
||||
if direction != "neutral":
|
||||
if self.can_move(x, y) and self.moveClock >= self.GAME.clock.get_fps() / 30:
|
||||
self.adjust_center(x, y)
|
||||
self.moveClock = 0
|
||||
else:
|
||||
self.moveClock = self.moveClock + 1
|
||||
|
||||
self.update_animation(direction, is_updateMove = True)
|
||||
else:
|
||||
self.update_animation()
|
||||
|
||||
def update_scale(self):
|
||||
"""
|
||||
Updates the scaling of all player textures.
|
||||
"""
|
||||
|
||||
self.update_images(self.character)
|
||||
|
||||
self.rect = (self.SCALE.unitBlockPx, self.SCALE.unitBlockPx)
|
||||
|
||||
# Scale each image
|
||||
for key, value in self.images.items():
|
||||
self.images[key] = pygame.transform.scale(value, self.rect)
|
||||
|
||||
for effect in self.effects:
|
||||
effect.update_scale(self.character)
|
||||
|
||||
self.update_animation(ovrrScaling=True)
|
||||
|
||||
class Unnamed():
|
||||
"""
|
||||
Spawns one of The Unnamed.
|
||||
"""
|
||||
|
||||
def __init__(self, game, x, y, layer=1):
|
||||
self.GAME = game
|
||||
self.WINDOW = self.GAME.WINDOW
|
||||
self.SCALE = self.GAME.SCALE
|
||||
self.LEVEL = self.GAME.LEVEL
|
||||
self.PLAYER = self.GAME.PLAYER
|
||||
|
||||
self.images = {}
|
||||
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.layer = layer
|
||||
|
||||
self.health = 25
|
||||
|
||||
for k in ["neutral_1", "neutral_2", "run_l_1", "run_l_2", "run_r_1", "run_r_2", "run_u_1", "run_u_2", "run_d_1", "run_d_2"]:
|
||||
self.images[k] = ""
|
||||
|
||||
self.update_images()
|
||||
|
||||
self.animationClock = 0
|
||||
self.stateDirection = "neutral" # String of character state
|
||||
self.stateDirectionOld = "run_d" # Last movement action by player.
|
||||
self.stateFrame = 1 # Animation frame number
|
||||
self.stateImage = self.images[f"{self.stateDirection}_{self.stateFrame}"] # Default inital character state.
|
||||
self.timeSinceInteraction = 0
|
||||
self.actionClock = 0
|
||||
|
||||
self.update_scale()
|
||||
|
||||
self.GAME.entityList.append(self)
|
||||
|
||||
def attack(self):
|
||||
"""
|
||||
Makes the unnamed attack player.
|
||||
"""
|
||||
|
||||
self.PLAYER.health = self.PLAYER.health - 25
|
||||
self.actionClock = pygame.time.get_ticks()
|
||||
|
||||
def calc_x(self):
|
||||
"""
|
||||
Calculates an x value movement based on PLAYER.
|
||||
"""
|
||||
|
||||
if self.x > self.PLAYER.x:
|
||||
self.stateDirectionOld = self.stateDirection
|
||||
self.stateDirection = "run_l"
|
||||
return self.x - 1, self.y
|
||||
elif self.x < self.PLAYER.x:
|
||||
self.stateDirectionOld = self.stateDirection
|
||||
self.stateDirection = "run_r"
|
||||
return self.x + 1, self.y
|
||||
elif self.x == self.PLAYER.x and self.y == self.PLAYER.y:
|
||||
return self.x, self.y
|
||||
else:
|
||||
return self.calc_y()
|
||||
|
||||
def calc_y(self):
|
||||
"""
|
||||
Calculates a y value movement based on PLAYER.
|
||||
"""
|
||||
|
||||
if self.y > self.PLAYER.y:
|
||||
self.stateDirectionOld = self.stateDirection
|
||||
self.stateDirection = "run_u"
|
||||
return self.x, self.y - 1
|
||||
elif self.y < self.PLAYER.y:
|
||||
self.stateDirectionOld = self.stateDirection
|
||||
self.stateDirection = "run_d"
|
||||
return self.x, self.y + 1
|
||||
elif self.x == self.PLAYER.x and self.y == self.PLAYER.y:
|
||||
return self.x, self.y
|
||||
else:
|
||||
return self.calc_x()
|
||||
|
||||
def can_move(self, x:int, y:int):
|
||||
"""
|
||||
Checks if the player can move to a position.
|
||||
"""
|
||||
|
||||
try: # Try to set the section based on coords.
|
||||
section = self.LEVEL.layout.LAYOUT[(x, y)]
|
||||
except KeyError:
|
||||
layerLast = -1
|
||||
|
||||
for coords, block in self.LEVEL.layout.LAYOUT.items(): # Deeper search, check if block in an area fill.
|
||||
if block.layer <= self.layer and block.layer >= layerLast:
|
||||
try:
|
||||
if y in range(coords[1], coords[3] + 1):
|
||||
if x in range(coords[0], coords[2] + 1):
|
||||
section = self.LEVEL.layout.LAYOUT[coords] # Set the definition as the desired section.
|
||||
layerLast = block.layer
|
||||
except IndexError: # Block doesn't define a range.
|
||||
pass
|
||||
|
||||
try:
|
||||
if section.TEXTURE[section.state]["IS_COLLIDABLE"]:
|
||||
return False
|
||||
elif self.PLAYER.x == x and self.PLAYER.y == y: # If player at this location, attack.
|
||||
self.attack()
|
||||
return False
|
||||
else:
|
||||
for entity in self.GAME.entityList:
|
||||
if entity.x == x and entity.y == y: # If there is an entity at this location, cannot move.
|
||||
return False
|
||||
return True
|
||||
except UnboundLocalError: # Could not find a block drawn at those coords.
|
||||
return False
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Draw the unnamed on the screen.
|
||||
"""
|
||||
|
||||
self.update_move()
|
||||
self.update_animation()
|
||||
|
||||
self.WINDOW.window.blit(self.stateImage, ((self.x - self.LEVEL.dx) * self.SCALE.unitBlockPx, (self.y - self.LEVEL.dy) * self.SCALE.unitBlockPx))
|
||||
|
||||
def destroy(self):
|
||||
"""
|
||||
Removes the unnamed from the entityList.
|
||||
"""
|
||||
|
||||
if self.GAME.levelOn and len(self.GAME.entityList) > 1:
|
||||
self.LEVEL.layout.LAYOUT[(self.x, self.y)] = Blocks.puddle_of_blood(self.GAME, self.layer)
|
||||
deathSound = mixer.Sound(DIRWORKING + "/data/sound/game/entity/death/jenna_cough.mp3")
|
||||
deathSound.set_volume(0.2)
|
||||
deathSound.play()
|
||||
|
||||
temp = []
|
||||
|
||||
for entity in self.GAME.entityList:
|
||||
if entity != self:
|
||||
temp.append(entity)
|
||||
|
||||
self.GAME.entityList = temp
|
||||
|
||||
del self
|
||||
|
||||
def update_animation(self, ovrrScaling = False):
|
||||
"""
|
||||
Sets the current animation image of the unnamed.
|
||||
"""
|
||||
|
||||
self.animationClock = self.animationClock + 1
|
||||
|
||||
if ovrrScaling: # Replace current image with scaled.
|
||||
self.stateImage = self.images[f"{self.stateDirection}_{self.stateFrame}"]
|
||||
elif self.animationClock >= (self.GAME.clock.get_fps() / 2): # Update animation every half a second.
|
||||
|
||||
if self.stateFrame == 1:
|
||||
self.stateFrame = 2
|
||||
elif self.stateFrame == 2:
|
||||
self.stateFrame = 1
|
||||
|
||||
self.stateImage = self.images[f"{self.stateDirection}_{self.stateFrame}"]
|
||||
|
||||
self.animationClock = 0
|
||||
|
||||
def update_images(self):
|
||||
"""
|
||||
Updates animation images .
|
||||
"""
|
||||
|
||||
for key, value in self.images.items():
|
||||
self.images[key] = pygame.image.load(DIRWORKING + f"/data/image/game/entity/unnamed/{key}.png").convert_alpha()
|
||||
|
||||
def update_move(self):
|
||||
"""
|
||||
Updates the position of the unnamed.
|
||||
"""
|
||||
|
||||
if pygame.time.get_ticks() - self.actionClock >= 1000:
|
||||
|
||||
if bool(randint(0, 1)):
|
||||
x, y = self.calc_x()
|
||||
else:
|
||||
x, y = self.calc_y()
|
||||
|
||||
if self.can_move(x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.actionClock = pygame.time.get_ticks()
|
||||
else:
|
||||
self.stateDirection = self.stateDirectionOld
|
||||
|
||||
self.update_animation()
|
||||
|
||||
def update_scale(self):
|
||||
"""
|
||||
Updates the scaling of all unnamed textures.
|
||||
"""
|
||||
|
||||
self.update_images()
|
||||
|
||||
self.rect = (self.SCALE.unitBlockPx, self.SCALE.unitBlockPx)
|
||||
|
||||
# Scale each image
|
||||
for key, value in self.images.items():
|
||||
self.images[key] = pygame.transform.scale(value, self.rect)
|
||||
|
||||
self.update_animation(ovrrScaling=True)
|
||||
300
lib/interface.py
Normal file
300
lib/interface.py
Normal file
@@ -0,0 +1,300 @@
|
||||
#!~/.pyenv/versions/3.11.6/bin/python
|
||||
#
|
||||
# Copyright (c) 2024 Cutieguwu | Olivia Brooks
|
||||
#
|
||||
# -*- coding:utf-8 -*-
|
||||
# @Title: Interface Items.
|
||||
# @Author: Cutieguwu | Olivia Brooks
|
||||
# @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca
|
||||
# @Description: Interface items for menus and scenes in a pygame game.
|
||||
#
|
||||
# @Script: interface.py
|
||||
# @Date Created: 22 Mar, 2024
|
||||
# @Last Modified: 13 Jun, 2024
|
||||
# @Last Modified by: Cutieguwu | Olivia Brooks
|
||||
# ----------------------------------------------------------
|
||||
|
||||
import pygame
|
||||
from lib.system import FEATURES
|
||||
|
||||
|
||||
class Button():
|
||||
"""
|
||||
Creates a button on a window.
|
||||
"""
|
||||
|
||||
def __init__(self, game, name, x, y, image_neutral, image_hover, function, args=None):
|
||||
self.TYPE = "button"
|
||||
|
||||
width = image_neutral.get_width() # Can use neutral image as all images should have identical sizes.
|
||||
height = image_neutral.get_height()
|
||||
|
||||
self.GAME = game
|
||||
self.WINDOW = self.GAME.WINDOW
|
||||
scale = self.GAME.SCALE.scaleUi
|
||||
|
||||
self.function = function
|
||||
self.functionArgs = args
|
||||
|
||||
self.ID = name
|
||||
|
||||
self.IMAGES = {
|
||||
"neutral": pygame.transform.scale(image_neutral, (width * scale, height * scale)),
|
||||
"hover": pygame.transform.scale(image_hover, (width * scale, height * scale))
|
||||
}
|
||||
|
||||
Interface_Functions.convert_images(self)
|
||||
|
||||
self.rect = self.IMAGES["neutral"].get_rect()
|
||||
self.rect.topleft = (x * scale, y * scale)
|
||||
|
||||
# Cannot overwrite rect.collidepoint with Interface_Functions.collidepoint.
|
||||
|
||||
self.time_delay = 0
|
||||
|
||||
self.GAME.gameInteractables.append(self) # Add to button to refresh.
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Draws button on screen.
|
||||
"""
|
||||
|
||||
Interface_Functions.draw(self)
|
||||
|
||||
def is_hovered(self):
|
||||
"""
|
||||
Checks if button is hovered.
|
||||
"""
|
||||
|
||||
return Interface_Functions.collidepoint(self, pygame.mouse.get_pos())
|
||||
|
||||
def remove(self):
|
||||
"""
|
||||
Clears button from screen.
|
||||
"""
|
||||
|
||||
Interface_Functions.destroy(self)
|
||||
|
||||
class Progress_bar():
|
||||
"""
|
||||
Creates a progress bar that updates based on a scale.
|
||||
"""
|
||||
|
||||
def __init__(self, game, x, y, barWidth:int, bobberImages:list, barImageLeft, barImageRight, barImageClear, barImageFill):
|
||||
"""
|
||||
x: distance in percent from window origin.
|
||||
y: distance in percent from window origin.
|
||||
barWidth: Percent of window width filled.
|
||||
"""
|
||||
|
||||
self.GAME = game
|
||||
self.WINDOW = self.GAME.WINDOW
|
||||
|
||||
self.x = (x / 100) * self.WINDOW.width
|
||||
self.y = (y / 100) * self.WINDOW.height
|
||||
|
||||
for image in bobberImages:
|
||||
image.convert_alpha()
|
||||
|
||||
self.bobberImages = bobberImages
|
||||
|
||||
self.barImages = {
|
||||
"left": barImageLeft.convert_alpha(),
|
||||
"right": barImageRight.convert_alpha(),
|
||||
"clear": barImageClear.convert_alpha(),
|
||||
"fill": barImageFill.convert_alpha()
|
||||
}
|
||||
|
||||
self.bobberState = 0
|
||||
self.bobberStateRange = len(bobberImages)
|
||||
|
||||
self.widthLeft = self.barImages["left"].get_width()
|
||||
self.widthRight = self.barImages["right"].get_width()
|
||||
|
||||
self.barFillWidth = int("%.0f" % ((self.WINDOW.width * (barWidth / 100)) - (self.widthLeft + self.widthRight)))
|
||||
|
||||
self.barHeight = self.barImages["left"].get_height()
|
||||
self.bobberHeight = self.bobberImages[self.bobberState].get_height()
|
||||
|
||||
self.barCenterOffset = (self.barHeight / 2) - (self.barImages["clear"].get_height() / 2)
|
||||
|
||||
self.bobberTimer = 0
|
||||
|
||||
def draw(self, rangeComplete, rangeTotal, framerate=30):
|
||||
|
||||
self.bobberTimer = self.bobberTimer + 1
|
||||
|
||||
rangeComplete = int("%.0f" % (rangeComplete * self.barFillWidth / rangeTotal))
|
||||
|
||||
self.WINDOW.window.blit(self.barImages["left"], (self.x, self.y))
|
||||
self.WINDOW.window.blit(self.barImages["right"], (self.x + self.widthLeft + self.barFillWidth, self.y))
|
||||
|
||||
for i in range(0, rangeComplete):
|
||||
self.WINDOW.window.blit(self.barImages["fill"], (self.x + self.widthLeft + i, self.y + self.barCenterOffset))
|
||||
|
||||
for i in range(rangeComplete, self.barFillWidth):
|
||||
self.WINDOW.window.blit(self.barImages["clear"], (self.x + self.widthLeft + i, self.y + self.barCenterOffset))
|
||||
|
||||
if (self.bobberTimer / framerate) > 0.25:
|
||||
self.bobberTimer = 0
|
||||
if self.bobberState == self.bobberStateRange - 1:
|
||||
self.bobberState = 0
|
||||
else:
|
||||
self.bobberState = self.bobberState + 1
|
||||
|
||||
self.WINDOW.window.blit(self.bobberImages[self.bobberState], (self.x + rangeComplete, self.y - (self.bobberHeight / 2)))
|
||||
|
||||
class Checkbox():
|
||||
"""
|
||||
Creates a Checkbox on a window.
|
||||
"""
|
||||
|
||||
def __init__(self, game, name, x, y, image_neutral, image_hover, image_active, condition, function=None, args=None):
|
||||
self.TYPE = "checkbox"
|
||||
|
||||
width = image_neutral.get_width() # Can use neutral image as all images should have identical sizes.
|
||||
height = image_neutral.get_height()
|
||||
|
||||
self.GAME = game
|
||||
self.WINDOW = self.GAME.WINDOW
|
||||
scale = self.GAME.SCALE.scaleUi
|
||||
|
||||
self.function = function
|
||||
self.functionArgs = args
|
||||
|
||||
self.ID = name
|
||||
|
||||
self.IMAGES = {
|
||||
"neutral": pygame.transform.scale(image_neutral, (width * scale, height * scale)),
|
||||
"hover": pygame.transform.scale(image_hover, (width * scale, height * scale)),
|
||||
"active": pygame.transform.scale(image_active, (width * scale, height * scale))
|
||||
}
|
||||
|
||||
Interface_Functions.convert_images(self)
|
||||
|
||||
self.rect = self.image_neutral.get_rect()
|
||||
self.rect.topleft = (x * scale, y * scale)
|
||||
|
||||
self.time_delay = 0
|
||||
|
||||
self.condition = condition
|
||||
|
||||
self.GAME.gameInteractables.append(self) # Add to checkbox to refresh.
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Draws checkbox on screen.
|
||||
"""
|
||||
|
||||
Interface_Functions.draw(self)
|
||||
|
||||
def is_hovered(self):
|
||||
"""
|
||||
Checks if checkbox is hovered.
|
||||
"""
|
||||
|
||||
return Interface_Functions.is_hovered(self)
|
||||
|
||||
def remove(self):
|
||||
"""
|
||||
Clears checkbox from screen.
|
||||
"""
|
||||
|
||||
Interface_Functions.destroy(self)
|
||||
|
||||
|
||||
class Interface_Functions():
|
||||
"""
|
||||
All functions called by interface objects.
|
||||
"""
|
||||
|
||||
def __init__(self, game):
|
||||
self.GAME = game
|
||||
|
||||
def convert_images(object):
|
||||
"""
|
||||
Converts the object's images to pygame format increasing performance.
|
||||
"""
|
||||
|
||||
temp = {}
|
||||
|
||||
for image in object.IMAGES.items():
|
||||
temp[image[0]] = image[1].convert_alpha()
|
||||
|
||||
object.IMAGES = temp
|
||||
|
||||
def destroy(object):
|
||||
"""
|
||||
Destroys the interface object.
|
||||
"""
|
||||
|
||||
temp = []
|
||||
|
||||
for interactable in object.GAME.gameInteractables:
|
||||
if interactable != object:
|
||||
temp.append(interactable)
|
||||
|
||||
object.GAME.gameInteractables = temp
|
||||
|
||||
del object
|
||||
|
||||
def draw(object):
|
||||
"""
|
||||
Draws interface object.
|
||||
"""
|
||||
|
||||
if object.TYPE == "checkbox" and object.condition():
|
||||
object.WINDOW.window.blit(object.IMAGES["active"], (object.rect.x, object.rect.y))
|
||||
elif object.is_hovered():
|
||||
object.WINDOW.window.blit(object.IMAGES["hover"], (object.rect.x, object.rect.y))
|
||||
else:
|
||||
object.WINDOW.window.blit(object.IMAGES["neutral"], (object.rect.x, object.rect.y))
|
||||
|
||||
def enter_fullscreen(self):
|
||||
"""
|
||||
Enters Fullscreen if possible.
|
||||
"""
|
||||
|
||||
if self.GAME.WINDOW.toggle_fullscreen():
|
||||
FEATURES["Fullscreen"]["is_active"] = True
|
||||
|
||||
def exit_to_desktop(self):
|
||||
|
||||
self.GAME.levelOn = False
|
||||
self.GAME.menuOn = False
|
||||
self.GAME.sceneOn = False
|
||||
self.GAME.gameOn = False
|
||||
|
||||
def collidepoint(object, coords:tuple):
|
||||
"""
|
||||
Rewritten version of pygame rect.collidepoint.
|
||||
"""
|
||||
|
||||
x, y, width, height = object.rect
|
||||
dx = width + x
|
||||
dy = height + y
|
||||
|
||||
|
||||
if coords[0] in range(x, dx + 1) and coords[1] in range(y, dy + 1):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_hovered(object):
|
||||
"""
|
||||
Checks if mouse is hovering interface object.
|
||||
"""
|
||||
|
||||
return Interface_Functions.collidepoint(object, pygame.mouse.get_pos())
|
||||
|
||||
def return_to_game(self):
|
||||
"""
|
||||
Exits a pause state.
|
||||
"""
|
||||
|
||||
for button in self.GAME.LEVEL.layout.INTERACTABLES:
|
||||
button.remove()
|
||||
|
||||
self.GAME.menuOn = False
|
||||
|
||||
self.GAME.LEVEL.load_level_state()
|
||||
572
lib/level.py
Normal file
572
lib/level.py
Normal file
@@ -0,0 +1,572 @@
|
||||
#!~/.pyenv/versions/3.11.6/bin/python
|
||||
#
|
||||
# Copyright (c) 2024 Cutieguwu | Olivia Brooks
|
||||
#
|
||||
# -*- coding:utf-8 -*-
|
||||
# @Title: Levels in a game.
|
||||
# @Author: Cutieguwu | Olivia Brooks
|
||||
# @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca
|
||||
# @Description: Classes descibing different levels in a pygame game.
|
||||
#
|
||||
# @Script: level.py
|
||||
# @Date Created: 16 Apr, 2024
|
||||
# @Last Modified: 19 Jun, 2024
|
||||
# @Last Modified by: Cutieguwu | Olivia Brooks
|
||||
# ----------------------------------------------------------
|
||||
|
||||
import pygame
|
||||
from pygame import mixer
|
||||
from pygame.image import load
|
||||
from icecream import ic
|
||||
from lib.system import DIRWORKING, FEATURES
|
||||
from lib.section import Section, Blocks
|
||||
from lib.interface import Button, Checkbox
|
||||
from lib.entity import * # Bad practise and loads player class, but loads all entities for easy reference.
|
||||
|
||||
|
||||
class Level():
|
||||
"""
|
||||
Creates and manages the level scape.
|
||||
"""
|
||||
|
||||
def __init__(self, game):
|
||||
self.GAME = game
|
||||
self.WINDOW = self.GAME.WINDOW
|
||||
self.SCALE = self.GAME.SCALE
|
||||
self.SECTION = Section(self.GAME)
|
||||
self.FRAMES = Frames(self.GAME)
|
||||
|
||||
self.layout = Menus.Blank()
|
||||
|
||||
self.dx = 0
|
||||
self.dy = 0
|
||||
|
||||
self.musicStartTime = 0
|
||||
|
||||
if self.layout.TYPE == "level":
|
||||
self.get_level_size()
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Draws the level on the screen.
|
||||
"""
|
||||
|
||||
if self.layout.TYPE == "level":
|
||||
self.SECTION.draw(self.layout.LAYOUT)
|
||||
elif self.layout.TYPE == "scene":
|
||||
self.FRAMES.draw()
|
||||
|
||||
try:
|
||||
if pygame.time.get_ticks() - self.musicStartTime >= self.layout.MUSIC.get_length() * 1000:
|
||||
ic(self.layout.MUSIC.play())
|
||||
self.musicStartTime = pygame.time.get_ticks()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def load_layout(self, layout:object, startPositionKey="default"):
|
||||
"""
|
||||
Load a new layout configuration.
|
||||
"""
|
||||
|
||||
self.GAME.playerActionMove = "neutral"
|
||||
|
||||
try:
|
||||
ic(self.layout.MUSIC.stop())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if self.layout.TYPE == "menu":
|
||||
for interactable in self.layout.INTERACTABLES:
|
||||
interactable.remove()
|
||||
|
||||
layout = layout(game=self.GAME)
|
||||
|
||||
if isinstance(layout, Menus.Pause): # Save the level state before loading something that isn't the next level.
|
||||
self.save_level_state()
|
||||
|
||||
if self.layout.TYPE == "level" and layout.TYPE != "level":
|
||||
self.GAME.levelOn = False
|
||||
elif self.layout.TYPE == "menu" and layout.TYPE != "menu":
|
||||
self.GAME.menuOn = False
|
||||
elif self.layout.TYPE == "scene" and layout.TYPE != "scene":
|
||||
self.GAME.sceneOn = False
|
||||
|
||||
if self.layout.TYPE == "level":
|
||||
for entity in self.GAME.entityList:
|
||||
entity.destroy()
|
||||
|
||||
self.layout = layout
|
||||
ic(self.layout.__class__)
|
||||
|
||||
if self.layout.TYPE == "level":
|
||||
self.update_level_size()
|
||||
|
||||
self.GAME.PLAYER.health = 100
|
||||
self.GAME.PLAYER.effects = []
|
||||
self.GAME.PLAYER.x = self.layout.STARTPOSITIONS[startPositionKey][0]
|
||||
self.GAME.PLAYER.y = self.layout.STARTPOSITIONS[startPositionKey][1]
|
||||
|
||||
self.update_deltas_relative_to_player()
|
||||
|
||||
elif self.layout.TYPE == "scene":
|
||||
self.FRAMES.load_images()
|
||||
|
||||
# The following shouldn't need to exist, however pygame does not like using a low musicStartTime to trigger the first audio playback.
|
||||
try:
|
||||
self.layout.MUSIC.play()
|
||||
self.musicStartTime = pygame.time.get_ticks()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def load_level_state(self):
|
||||
"""
|
||||
Loads the last state of a level.
|
||||
"""
|
||||
|
||||
self.layout = self.layoutOld
|
||||
self.GAME.entityList = self.entityOld
|
||||
self.GAME.levelLoad = True
|
||||
|
||||
def save_level_state(self):
|
||||
"""
|
||||
Saves the last state of a level.
|
||||
"""
|
||||
|
||||
self.layoutOld = self.layout
|
||||
self.entityOld = self.GAME.entityList
|
||||
|
||||
def update_deltas_relative_to_player(self):
|
||||
"""
|
||||
Called with a scale update.
|
||||
Recalculates self.dx and self.dy so that player is centered properly.
|
||||
"""
|
||||
|
||||
borderWidthX = (self.SCALE.gameX // 2) + 1
|
||||
borderWidthY = (self.SCALE.gameY // 2) + 1
|
||||
|
||||
if self.GAME.PLAYER.x < borderWidthX:
|
||||
# Player is in left border
|
||||
self.dx = 0
|
||||
|
||||
elif self.GAME.PLAYER.x > self.width - borderWidthX:
|
||||
# Player is in right border
|
||||
self.dx = self.width - self.SCALE.gameX
|
||||
|
||||
else:
|
||||
# Player is within x boundaries
|
||||
self.dx = self.GAME.PLAYER.x - borderWidthX
|
||||
|
||||
|
||||
if self.GAME.PLAYER.y < borderWidthY:
|
||||
# Player is in the top border
|
||||
self.dy = 0
|
||||
|
||||
elif self.GAME.PLAYER.y > self.height - borderWidthY:
|
||||
# Player is in bottom border.
|
||||
self.dy = self.height - self.SCALE.gameY
|
||||
|
||||
else:
|
||||
# Player is within y boundaries.
|
||||
self.dy = self.GAME.PLAYER.y - borderWidthY
|
||||
|
||||
self.dx = int(self.dx)
|
||||
self.dy = int(self.dy)
|
||||
|
||||
def update_level_size(self):
|
||||
"""
|
||||
Updates the size of the active layout
|
||||
"""
|
||||
width = 0
|
||||
height = 0
|
||||
|
||||
for coords, block in self.layout.LAYOUT.items():
|
||||
try:
|
||||
if coords[2] > width:
|
||||
width = coords[2]
|
||||
|
||||
if coords[3] > height:
|
||||
height = coords[3]
|
||||
|
||||
except IndexError:
|
||||
if coords[0] > width:
|
||||
width = coords[0]
|
||||
|
||||
if coords[1] > height:
|
||||
height = coords[1]
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
"""
|
||||
NAME :str
|
||||
HAS_PLAYER :bool
|
||||
TYPE :str = "scene", "menu", "level"
|
||||
|
||||
scene # Does not scale with scaleGame or scaleUi
|
||||
# Each obj in LAYOUT is a different frame, not block sections.
|
||||
# Does not have a player, so HAS_PLAYER is not checked, and can be removed from definition.
|
||||
|
||||
menu # Does not scale with scaleGame
|
||||
# Has a custom event handler used while menu is active. Handler defined in run().
|
||||
|
||||
LAYOUT :dict = {(x, y, rangeX, rangeY): obj}
|
||||
BGFILL :tuple = (r, g, b)
|
||||
:pygame.surface.Surface
|
||||
FOLLOW_UP_LEVEL :level.Levels, level.Menus, level.Scenes
|
||||
"""
|
||||
|
||||
class Scenes():
|
||||
"""
|
||||
Class for organising scenes.
|
||||
"""
|
||||
|
||||
"""
|
||||
class Example():
|
||||
def __init__(self):
|
||||
self.IS_SKIPPABLE = True
|
||||
self.LAYOUT = [
|
||||
{
|
||||
"frame": Textures.EXAMPLE_0, # Frame image
|
||||
"frameTime": 5 # Time in seconds that this frame is displayed.
|
||||
},
|
||||
{
|
||||
"frame": Textures.EXAMPLE_1,
|
||||
"frameTime": 2
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
|
||||
class Intro():
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
self.TYPE = "scene"
|
||||
self.LAYOUT = [
|
||||
DIRWORKING + "/data/image/game/blocks/wool_colored_blue.png",
|
||||
DIRWORKING + "/data/image/game/blocks/wool_colored_cyan.png",
|
||||
DIRWORKING + "/data/image/game/blocks/wool_colored_light_blue.png"
|
||||
]
|
||||
self.FOLLOW_UP_LEVEL = Menus.Main
|
||||
self.IS_SKIPPABLE = True
|
||||
self.TIME_PER_FRAME = 1
|
||||
self.BG_FILL = (0, 0, 0)
|
||||
|
||||
class Commencing_Slaughter():
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
self.TYPE = "scene"
|
||||
self.LAYOUT = [
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_4.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png",
|
||||
DIRWORKING + "/data/image/game/scenes/commencing_slaughter/commencing_slaughter_3.png"
|
||||
]
|
||||
self.FOLLOW_UP_LEVEL = Levels.Example
|
||||
self.IS_SKIPPABLE = False
|
||||
self.TIME_PER_FRAME = 0.25
|
||||
|
||||
self.MUSIC = mixer.Sound(DIRWORKING + "/data/sound/game/scenes/commencing_slaughter.mp3")
|
||||
|
||||
class Player_Died():
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
self.TYPE = "scene"
|
||||
self.LAYOUT = [
|
||||
DIRWORKING + "/data/image/game/blocks/wool_colored_red.png"
|
||||
]
|
||||
self.FOLLOW_UP_LEVEL = Menus.Player_Died
|
||||
self.IS_SKIPPABLE = False
|
||||
|
||||
self.MUSIC = mixer.Sound(DIRWORKING + "/data/sound/game/weapon/Explosion_Ultra_Bass-Mark_DiAngelo-1810420658.wav")
|
||||
|
||||
self.TIME_PER_FRAME = self.MUSIC.get_length()
|
||||
|
||||
class Player_Won():
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
self.TYPE = "scene"
|
||||
self.LAYOUT = [
|
||||
DIRWORKING + "/data/image/game/blocks/wool_colored_lime.png"
|
||||
]
|
||||
self.FOLLOW_UP_LEVEL = Menus.Player_Won
|
||||
self.IS_SKIPPABLE = True
|
||||
|
||||
self.MUSIC = mixer.Sound(DIRWORKING + "/data/sound/game/weapon/Explosion_Ultra_Bass-Mark_DiAngelo-1810420658.wav")
|
||||
|
||||
self.TIME_PER_FRAME = self.MUSIC.get_length()
|
||||
|
||||
class Menus():
|
||||
"""
|
||||
Class for organising menus.
|
||||
"""
|
||||
|
||||
class Blank():
|
||||
def __init__(self, **kwargs):
|
||||
self.TYPE = "menu"
|
||||
self.HAS_PLAYER = False
|
||||
self.INTERACTABLES = []
|
||||
self.BG_FILL = (0, 0, 0)
|
||||
|
||||
class Main():
|
||||
def __init__(self, game):
|
||||
|
||||
self.TYPE = "menu"
|
||||
self.HAS_PLAYER = False
|
||||
self.INTERACTABLES = [
|
||||
Button(
|
||||
game,
|
||||
"start",
|
||||
100,
|
||||
100,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.LEVEL.load_layout,
|
||||
args=Scenes.Commencing_Slaughter
|
||||
),
|
||||
Button(
|
||||
game,
|
||||
"settings",
|
||||
100,
|
||||
200,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.LEVEL.load_layout,
|
||||
#args=Menus.Settings
|
||||
args=Menus.Main
|
||||
),
|
||||
Button(
|
||||
game,
|
||||
"exit_to_desktop",
|
||||
100,
|
||||
300,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.MENU_FUNCTIONS.exit_to_desktop
|
||||
)
|
||||
]
|
||||
|
||||
self.BG_FILL = pygame.image.load(DIRWORKING + "/data/image/game/blocks/wool_colored_silver.png").convert()
|
||||
self.MUSIC = mixer.Sound(DIRWORKING + "/data/sound/ui/menu_main/Backwards-Souls-SoundBible.com-87826574.wav")
|
||||
|
||||
class Settings():
|
||||
def __init__(self, game):
|
||||
|
||||
self.TYPE = "menu"
|
||||
self.HAS_PLAYER = False
|
||||
self.INTERACTABLES = [
|
||||
Checkbox(
|
||||
game,
|
||||
"fullscreen",
|
||||
100,
|
||||
100,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/icons/checkbox_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/icons/checkbox_hover.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/icons/checkbox_active.png").convert_alpha(),
|
||||
lambda: FEATURES["fullscreen"]["is_active"], # Needs to be re-evaluated. Thus lambda function.
|
||||
game.MENU_FUNCTIONS.enter_fullscreen
|
||||
)
|
||||
]
|
||||
|
||||
self.BG_FILL = pygame.image.load(DIRWORKING + "/data/image/game/blocks/wool_colored_silver.png").convert()
|
||||
|
||||
class Pause():
|
||||
def __init__(self, game):
|
||||
|
||||
self.TYPE = "menu"
|
||||
self.HAS_PLAYER = False
|
||||
self.INTERACTABLES = [
|
||||
Button(
|
||||
game,
|
||||
"return_to_game",
|
||||
100,
|
||||
100,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.MENU_FUNCTIONS.return_to_game
|
||||
),
|
||||
Button(
|
||||
game,
|
||||
"settings",
|
||||
100,
|
||||
200,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.MENU_FUNCTIONS.return_to_game
|
||||
#game.LEVEL.load_layout,
|
||||
#args=Menus.Settings
|
||||
),
|
||||
Button(
|
||||
game,
|
||||
"exit_to_menu",
|
||||
100,
|
||||
300,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.LEVEL.load_layout,
|
||||
args=Menus.Main
|
||||
)
|
||||
]
|
||||
|
||||
# Save a snapshot of the window to overlay the pause menu on.
|
||||
pygame.image.save(game.WINDOW.window, DIRWORKING + "/temp/pausescreen.png")
|
||||
self.BG_FILL = pygame.image.load(DIRWORKING + "/temp/pausescreen.png").convert()
|
||||
|
||||
class Player_Died():
|
||||
def __init__(self, game):
|
||||
self.TYPE = "menu"
|
||||
self.HAS_PLAYER = False
|
||||
self.INTERACTABLES = [
|
||||
Button(
|
||||
game,
|
||||
"exit_to_menu",
|
||||
100,
|
||||
300,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.LEVEL.load_layout,
|
||||
args=Menus.Main
|
||||
)
|
||||
]
|
||||
self.BG_FILL = load(DIRWORKING + "/data/image/game/blocks/wool_colored_red.png")
|
||||
|
||||
class Player_Won():
|
||||
def __init__(self, game):
|
||||
self.TYPE = "menu"
|
||||
self.HAS_PLAYER = False
|
||||
self.INTERACTABLES = [
|
||||
Button(
|
||||
game,
|
||||
"exit_to_menu",
|
||||
100,
|
||||
300,
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_neutral.png").convert_alpha(),
|
||||
pygame.image.load(DIRWORKING + "/data/image/ui/menu_main/start_hover.png").convert_alpha(),
|
||||
game.LEVEL.load_layout,
|
||||
args=Menus.Main
|
||||
)
|
||||
]
|
||||
self.BG_FILL = load(DIRWORKING + "/data/image/game/blocks/wool_colored_lime.png")
|
||||
|
||||
class Levels():
|
||||
"""
|
||||
Class for organising levels.
|
||||
"""
|
||||
|
||||
class Example():
|
||||
def __init__(self, game, **kwargs):
|
||||
|
||||
self.TYPE = "level"
|
||||
self.HAS_PLAYER = True
|
||||
self.LAYOUT = {
|
||||
(0, 0, 96, 64): Blocks.block_blue(),
|
||||
(2, 1): Blocks.block_rainbow(layer=1),
|
||||
(14, 5): Blocks.block_rainbow(layer=1),
|
||||
(3, 3, 5, 5): Blocks.puddle_of_souls(game, layer=1),
|
||||
(7, 7): Blocks.puddle_of_souls(game, layer=1),
|
||||
(4, 9): Blocks.puddle_of_souls(game, layer=1)
|
||||
}
|
||||
self.ENTITIES = [
|
||||
Unnamed(game, 16, 16),
|
||||
Unnamed(game, 16, 17),
|
||||
Unnamed(game, 17, 16),
|
||||
Unnamed(game, 17, 17),
|
||||
Unnamed(game, 15, 23),
|
||||
Unnamed(game, 41, 52),
|
||||
Unnamed(game, 5, 17)
|
||||
]
|
||||
self.STARTPOSITIONS = {"default": (0, 0)}
|
||||
self.BG_FILL = (0, 0, 0)
|
||||
self.FOLLOW_UP_LEVEL = Scenes.Player_Won
|
||||
|
||||
class Frames():
|
||||
"""
|
||||
Class for methods pertaining to scenes.
|
||||
"""
|
||||
|
||||
def __init__(self, game):
|
||||
|
||||
self.GAME = game
|
||||
self.WINDOW = self.GAME.WINDOW
|
||||
self.clear()
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear the frames from memory.
|
||||
"""
|
||||
|
||||
self.images = [
|
||||
pygame.image.load(DIRWORKING + "/data/image/game/blocks/wool_colored_silver.png").convert()
|
||||
]
|
||||
self.imageNumber = -1
|
||||
self.frames = 0 # Frames rendered by system
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Draws the scene.
|
||||
"""
|
||||
|
||||
if self.frames / self.GAME.clock.get_fps() > self.GAME.LEVEL.layout.TIME_PER_FRAME: # Use number of frames displaying an image / framerate to get time in sec.
|
||||
self.imageNumber = self.imageNumber + 1 # Increment the image index
|
||||
self.frames = 0
|
||||
|
||||
if len(self.images) == self.imageNumber:
|
||||
self.GAME.LEVEL.load_layout(self.GAME.LEVEL.layout.FOLLOW_UP_LEVEL)
|
||||
self.clear()
|
||||
else:
|
||||
self.WINDOW.window.blit(self.images[self.imageNumber], (0, 0))
|
||||
elif self.imageNumber == -1:
|
||||
self.imageNumber = 0
|
||||
self.WINDOW.window.blit(self.images[self.imageNumber], (0, 0))
|
||||
|
||||
else:
|
||||
self.frames = self.frames + 1
|
||||
|
||||
def load_images(self):
|
||||
"""
|
||||
Load frames for a scene.
|
||||
"""
|
||||
|
||||
self.images = []
|
||||
|
||||
for path in self.GAME.LEVEL.layout.LAYOUT:
|
||||
self.images.append(load(path).convert_alpha())
|
||||
|
||||
self.scale_images()
|
||||
|
||||
def scale_images(self):
|
||||
"""
|
||||
Scales all images currently loaded.
|
||||
"""
|
||||
|
||||
for image in self.images:
|
||||
self.images[self.images.index(image)] = pygame.transform.scale(image, (self.WINDOW.width, self.WINDOW.height))
|
||||
|
||||
def skip(self):
|
||||
"""
|
||||
Skips the level.
|
||||
"""
|
||||
|
||||
self.clear()
|
||||
self.GAME.LEVEL.load_layout(self.GAME.LEVEL.layout.FOLLOW_UP_LEVEL)
|
||||
|
||||
def update_scale(self):
|
||||
"""
|
||||
Updates the scaling of the active frames.
|
||||
"""
|
||||
|
||||
if self.WINDOW.width > self.images[0].get_width() or self.WINDOW.height > self.images[0].get_height():
|
||||
self.load_images()
|
||||
else:
|
||||
self.scale_images()
|
||||
424
lib/section.py
Normal file
424
lib/section.py
Normal file
@@ -0,0 +1,424 @@
|
||||
#!~/.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))
|
||||
252
lib/system.py
Executable file
252
lib/system.py
Executable file
@@ -0,0 +1,252 @@
|
||||
#!~/.pyenv/versions/3.11.6/bin/python
|
||||
#
|
||||
# Copyright (c) 2024 Cutieguwu | Olivia Brooks
|
||||
#
|
||||
# -*- coding:utf-8 -*-
|
||||
# @Title: System utilities.
|
||||
# @Author: Cutieguwu | Olivia Brooks
|
||||
# @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca
|
||||
# @Description: Utilities for a PyGame game.
|
||||
#
|
||||
# @Script: system.py
|
||||
# @Date Created: 17 Apr, 2024
|
||||
# @Last Modified: 14 Jun, 2024
|
||||
# @Last Modified by: Cutieguwu | Olivia Brooks
|
||||
# ----------------------------------------------------------
|
||||
|
||||
"""
|
||||
Modules that are loaded will always be loaded globally.
|
||||
Some will only be loaded based on conditions, but made global.
|
||||
Thus, they can be checked for in globals() and be easily accessible anywhere.
|
||||
Even if a module were loaded and unloaded, it would remain in cache, so unloading it is not really worth it.
|
||||
Imports will be loaded as the program needs them, thus reducing the initial memory consumption.
|
||||
"""
|
||||
|
||||
# Although they won't be reloaded and the imports will be skipped if called again,
|
||||
# avoid wasting processing time by keeping out of import_dependencies()
|
||||
from pkg_resources import working_set
|
||||
from subprocess import check_call, CalledProcessError
|
||||
from sys import executable
|
||||
|
||||
|
||||
def install_dependencies(dependencies:dict):
|
||||
"""
|
||||
Tries to install and import any missing dependencies from list.
|
||||
"""
|
||||
|
||||
# Adapted from Serge Stroobandt on https://stackoverflow.com/questions/46419607/how-to-automatically-install-required-packages-from-a-python-script-as-necessary
|
||||
|
||||
libsInst = {
|
||||
pkg.key for pkg in working_set # Find required and installed libraries.
|
||||
}
|
||||
|
||||
libsMiss = dependencies - 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.
|
||||
for library in libsMiss:
|
||||
check_call([executable, "-m", "pip", "install", library]) # Install all missing libraries.
|
||||
|
||||
except CalledProcessError:
|
||||
print("Error | CANNOT FIND OR INSTALL DEPENDENCY:")
|
||||
print(library)
|
||||
raise SystemExit
|
||||
|
||||
install_dependencies({"icecream", "pygame"})
|
||||
|
||||
from icecream import ic
|
||||
from pygame.version import vernum
|
||||
from pygame.display import get_driver
|
||||
from os import path
|
||||
from sys import version as pyver
|
||||
from platform import uname
|
||||
from json import load as jload
|
||||
|
||||
|
||||
DISPLAY = {}
|
||||
PLATFORM = {"libraries": {}}
|
||||
PLATFORM["kernel"] = uname().system # `system` is a variable of uname()
|
||||
PLATFORM["libraries"]["pygame"] = vernum
|
||||
|
||||
# Python version cleanup.
|
||||
versionStr = ""
|
||||
|
||||
for c in pyver:
|
||||
if c == " ":
|
||||
break
|
||||
else:
|
||||
versionStr = versionStr + c
|
||||
|
||||
versionTuple = tuple(versionStr.split("."))
|
||||
|
||||
versionTuple = (int(versionTuple[0]), int(versionTuple[1]), int(versionTuple[2]))
|
||||
|
||||
PLATFORM["libraries"]["python"] = versionTuple
|
||||
|
||||
# GCC version cleanup.
|
||||
versionStr = ""
|
||||
startFlag = False
|
||||
|
||||
for c in pyver:
|
||||
if startFlag:
|
||||
if c not in ["G", "C", "]"]:
|
||||
versionStr = versionStr + c
|
||||
elif c == "[":
|
||||
startFlag = True
|
||||
|
||||
PLATFORM["libraries"]["GCC"] = versionStr
|
||||
|
||||
del pyver, versionStr, startFlag, versionTuple
|
||||
|
||||
# Fetch compatability settings.
|
||||
DIRWORKING = path.dirname(__file__) + "/.."
|
||||
|
||||
with open(DIRWORKING + "/config/features.json") as f:
|
||||
FEATURES = jload(f)
|
||||
|
||||
# All features in file default to is_active: false
|
||||
# Enable supported features.
|
||||
|
||||
for feature in FEATURES.items():
|
||||
is_active = False
|
||||
|
||||
for lib in feature[1]["libs"].items():
|
||||
if PLATFORM["libraries"][lib[0]] > tuple(lib[1]["ver_implemented"]):
|
||||
is_active = True
|
||||
|
||||
FEATURES[feature[0]]["is_active"] = is_active
|
||||
|
||||
class Compat():
|
||||
def linux():
|
||||
# Adapted from Glyph, Eevee on https://stackoverflow.com/questions/1225057/how-can-i-determine-the-monitor-refresh-rate
|
||||
|
||||
"""
|
||||
if "environ" not in globals(): # Import if not already.
|
||||
global environ
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
if environ["WAYLAND_DISPLAY"] != "" and globals["XDG_SESSION_TYPE"] != "x11": # Check for environment variable present on Wayland systems.
|
||||
print("Sorry, you are running Wayland. Configuring FPS on Wayland is not yet supported.")
|
||||
|
||||
except KeyError: # Wayland keys won't exist on X11 systems (mostly).
|
||||
install_dependencies({"Xlib"})
|
||||
global display
|
||||
from Xlib import display
|
||||
global randr
|
||||
from Xlib.ext import randr
|
||||
|
||||
displayGlobal = display.Display() # Get every display thing. Maybe a WM object?
|
||||
default_screen = displayGlobal.get_default_screen() # Find default display for some reason.
|
||||
info = displayGlobal.screen(default_screen) # Use this as a point to get information about all screens.
|
||||
|
||||
displayConfigs = randr.get_screen_resources(info.root) # Get every screen's possible configurations.
|
||||
configsActive = set() # Create an empty set to add configurations to.
|
||||
|
||||
for config in displayConfigs.crtcs: # For every display configuration, determine if it's active and add its identifier to the configurations in use.
|
||||
crtc_info = randr.get_crtc_info(info.root, config, displayConfigs.config_timestamp)
|
||||
if crtc_info.mode:
|
||||
configsActive.add(crtc_info.mode)
|
||||
|
||||
for config in displayConfigs.modes:
|
||||
if config.id in configsActive: # For every active display mode, figure out its framerate.
|
||||
framerate = config.dot_clock / (config.h_total * config.v_total)
|
||||
"""
|
||||
|
||||
if get_driver() == "wayland":
|
||||
ic("Sorry, you are running Wayland. Configuring FPS on Wayland is not yet supported.")
|
||||
elif get_driver() == "x11":
|
||||
install_dependencies({"Xlib"})
|
||||
global display
|
||||
from Xlib import display
|
||||
global randr
|
||||
from Xlib.ext import randr
|
||||
|
||||
displayGlobal = display.Display() # Get every display thing. Maybe a WM object?
|
||||
default_screen = displayGlobal.get_default_screen() # Find default display for some reason.
|
||||
info = displayGlobal.screen(default_screen) # Use this as a point to get information about all screens.
|
||||
|
||||
displayConfigs = randr.get_screen_resources(info.root) # Get every screen's possible configurations.
|
||||
configsActive = set() # Create an empty set to add configurations to.
|
||||
|
||||
for config in displayConfigs.crtcs: # For every display configuration, determine if it's active and add its identifier to the configurations in use.
|
||||
crtc_info = randr.get_crtc_info(info.root, config, displayConfigs.config_timestamp)
|
||||
if crtc_info.mode:
|
||||
configsActive.add(crtc_info.mode)
|
||||
|
||||
framerate = 30
|
||||
|
||||
# With multiple displays, get the highest framerate that any one monitor can do.
|
||||
for config in displayConfigs.modes:
|
||||
if config.id in configsActive: # For every active display mode, figure out its framerate.
|
||||
framerateDisplay = (config.dot_clock / (config.h_total * config.v_total))
|
||||
if framerateDisplay > framerate:
|
||||
framerate = framerateDisplay
|
||||
|
||||
return framerate
|
||||
|
||||
def nt():
|
||||
# Cannot use pygame.display.get_driver for windows or macos in pygame < v2
|
||||
|
||||
# Adapted from Anurag Uniyal on https://stackoverflow.com/questions/1225057/how-can-i-determine-the-monitor-refresh-rate
|
||||
|
||||
install_dependencies({"pywin32"})
|
||||
|
||||
if "EnumDisplayDevices" not in globals(): # Import if not already.
|
||||
global EnumDisplayDevices
|
||||
from win32api import EnumDisplayDevices
|
||||
if "EnumDisplaySettings" not in globals(): # Import if not already.
|
||||
global EnumDisplaySettings
|
||||
from win32api import EnumDisplaySettings
|
||||
|
||||
display_info = EnumDisplayDevices() # Returns PyDISPLAY_DEVICE object
|
||||
|
||||
display_settings = EnumDisplaySettings(display_info.DeviceName, -1) # Picks display that seems to have highest framerate?...
|
||||
return getattr(display_settings, "DisplayFrequency")
|
||||
|
||||
def darwin():
|
||||
# Cannot use pygame.display.get_driver for windows or macos in pygame < v2
|
||||
|
||||
# Adapted from Glyph, Eevee on https://stackoverflow.com/questions/1225057/how-can-i-determine-the-monitor-refresh-rate
|
||||
|
||||
install_dependencies({"Appkit"})
|
||||
|
||||
if "NSScreen" not in globals(): # Import if not already.
|
||||
global NSScreen
|
||||
from AppKit import NSScreen
|
||||
|
||||
for screen in NSScreen.screens():
|
||||
if framerate < screen.maximumFramesPerSecond(): # I the framerate of this display is higher, set its as the new maximum rate.
|
||||
framerate = screen.maximumFramesPerSecond()
|
||||
|
||||
return framerate
|
||||
|
||||
def update_framerate():
|
||||
"""
|
||||
Used for mock vsync to support Pygame < v2.0.0
|
||||
"""
|
||||
|
||||
if FEATURES["matchcase"]["is_active"]:
|
||||
match PLATFORM["kernel"]:
|
||||
case "Linux":
|
||||
framerate = Compat.linux()
|
||||
case "Windows":
|
||||
framerate = Compat.nt()
|
||||
case "Darwin":
|
||||
framerate = Compat.darwin()
|
||||
|
||||
elif PLATFORM["kernel"] == "Linux":
|
||||
framerate = Compat.linux()
|
||||
|
||||
elif PLATFORM["kernel"] == "Windows":
|
||||
framerate = Compat.nt()
|
||||
|
||||
elif PLATFORM["kernel"] == "Darwin": # I'm trusting that the info I found for MacOS does actually still work this way.
|
||||
framerate = Compat.darwin()
|
||||
|
||||
else:
|
||||
print("Unsupported or unable to detect system kernel. Defaulting to 30 FPS. This can be overridden in the user settings file.")
|
||||
|
||||
framerate = int("%.0f" % framerate)
|
||||
DISPLAY["framerate"] = framerate
|
||||
Reference in New Issue
Block a user