Compare commits

..

19 Commits

Author SHA1 Message Date
ef4fc8dfc7 Added time delta 2025-06-26 07:28:38 +01:00
60cabb227c Did some bullshit to make popups "work" 2025-06-24 14:36:17 +01:00
40b00a71dc More linting 2025-06-24 06:07:45 +01:00
db943fef5a Started to implement punish() 2025-06-24 06:07:37 +01:00
c7156112dc Started to implement popup() 2025-06-24 06:07:23 +01:00
8893fd90c6 Separated some functinos into different "front end" and "back end" sides 2025-06-24 06:06:21 +01:00
9d6f775ab9 Linted code, added documentation 2025-06-24 06:04:20 +01:00
a9d95c65cb Updated dependencies in preparation for self.popup() 2025-06-24 06:02:09 +01:00
28f9c01f14 Added more notes / todos 2025-06-23 22:20:36 +01:00
24d6addc24 Added note 2025-06-23 21:18:59 +01:00
c0ebfa42b7 Added more TODOs for me (: 2025-06-23 20:37:52 +01:00
44696a0ac3 Added TODO for me to deal with later 2025-06-23 20:10:55 +01:00
1686e53b28 Did more unforgivable sins 2025-06-23 14:45:29 +01:00
132cfab990 Started work on turn based systems 2025-06-23 12:30:39 +01:00
c2db8b386e Added TODO 2025-06-22 20:01:19 +01:00
265322f58b My bad team, this one will work for sure 2025-06-22 18:09:09 +01:00
5dbd9211de Added awaitingTimeout function (not tested, good luck team) 2025-06-22 18:00:42 +01:00
2a0def7fa6 Added NoPELib integration 2025-06-22 17:36:43 +01:00
08d6261d3f Updated version 2025-06-21 23:08:39 +01:00
6 changed files with 215 additions and 20 deletions

2
TODO.md Normal file
View File

@ -0,0 +1,2 @@
# TODO
- Add turn / round based systems

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "gameUtils" name = "gameUtils"
version = "0.0.1.dev0" version = "1.0.0"
description = "A set of utilities to make the game development process less painful (despite the organisation name) and more unified." description = "A set of utilities to make the game development process less painful (despite the organisation name) and more unified."
authors = [{ name = "Brosef" }] authors = [{ name = "Brosef" }]
dependencies = ["pygame-ce"] dependencies = ["pygame-ce", "pygame_gui"]

View File

@ -4,4 +4,6 @@ from .anim import AnimationHandler
from .anim import AnimatedObject from .anim import AnimatedObject
from .events import AnimStart from .events import AnimStart
from .events import AnimFinish from .events import AnimFinish
from .events import Timeout from .events import Timeout
from .turns import BaseTurnHandler
from .turns import RoundTable

View File

@ -1,8 +1,13 @@
from .events import Timeout
from .anim import AnimatedObject
import tomllib import tomllib
import time import time
import NoPELib
import pygame
from .events import Timeout
from .anim import AnimatedObject
from .utils import centre
class Game: class Game:
def __init__(self, surface): def __init__(self, surface):
""" """
@ -12,31 +17,92 @@ class Game:
surface (pygame.Surface): The surface the game devs draw on. surface (pygame.Surface): The surface the game devs draw on.
""" """
self.surf = surface
self.size = self.surf.size
self.pm = None
# Holds all the timeouts that haven't been fired yet
self._timeouts = []
with open('./game.toml', 'r') as f: with open('./game.toml', 'r') as f:
self.cfg = tomllib.loads(f.read()) self.cfg = tomllib.loads(f.read())
# The surface that gets drawn by the launcher.
# Contains the game surface, and overlays.
self._surf = surface
# The games serface
self.surf = pygame.Surface(self._surf.size)
self.size = self.surf.size
# Used for time delta, stores when the last frame was drawn
self._lastFrame = time.perf_counter()
# How many seconds it's been between frames
self.timeDelta = 0
# Pre-render semi-transparent black surface
self._dimSurf = pygame.Surface(self._surf.size)
self._dimSurf.set_alpha(128)
pygame.draw.rect(self._dimSurf, (50, 50, 50), (0, 0, self.size[0], self.size[1]))
self.pm = NoPELib.PlayersManager(self.cfg['name'], playersPath='../players.json')
# Holds all the timeouts that haven't been fired yet
self._timeouts = []
# The following are used by self.popup() to halt some events
self._haltUpdate = False
self._haltEvents = False
# Will become a pygame.Surface with the popup contents
self._popupMenu = None
def update(self): def update(self):
""" """
Updates some core things in the background. Intended to be overridden by the game developer.
See BaseGame for documentation.
""" """
def _update(self):
"""
Updates some core things in the background, and calls update()
"""
# Handle timeouts
for timeout in self._timeouts.copy(): for timeout in self._timeouts.copy():
if timeout.fireOn <= time.perf_counter(): if timeout.fireOn <= time.perf_counter():
self.onEvent(timeout) self.onEvent(timeout)
self._timeouts.remove(timeout) self._timeouts.remove(timeout)
# Game update
if not self._haltUpdate:
self.update()
# Draw the game surface onto the main surface
self._surf.blit(self.surf, (0, 0))
# If there's a popup
if self._popupMenu is not None:
# Dim the screen
self._surf.blit(self._dimSurf, (0, 0))
self._surf.blit(centre(self._popupMenu, self.size), (0, 0))
self.timeDelta = time.perf_counter() - self._lastFrame
self._lastFrame = time.perf_counter()
def onEvent(self, event): def onEvent(self, event):
pass """
Intended to be overridden by the game developer.
See BaseGame for documentation.
"""
def _onEvent(self, event):
"""
Updates some core things in the background, and calls onEvent()
"""
if not self._haltEvents:
self.onEvent(event)
if event.type == pygame.KEYDOWN and self._popupMenu is not None:
if event.key == 13 and event.mod & 0b10000000: # R-CTRL + ENTER
self._popupMenu = None
self._haltUpdate = False
self._haltEvents = False
def close(self): def close(self):
pass """
Intended to be overridden by the game developer.
See BaseGame for documentation.
"""
def createAnimObj(self, *args, **kwargs): def createAnimObj(self, *args, **kwargs):
""" """
Creates an animated object. Creates an animated object.
@ -58,5 +124,62 @@ class Game:
delay (float): How long (in seconds) delay (float): How long (in seconds)
to wait before firing. to wait before firing.
""" """
self._timeouts.append(Timeout(id, time.perf_counter()+delay))
def awaitingTimeout(self, timeoutID: str):
"""
Tells you if a timeout ID is on the timeout stack.
Args:
timeoutID (str): The timeout ID.
self._timeouts.append(Timeout(id, time.perf_counter()+delay)) Returns:
bool: True if there is a timeout with that ID
ID on the stack, False otherwise.
"""
return any(timeout.timeoutID == timeoutID for timeout in self._timeouts)
def popup(self, title: str, message: str, haltUpdate: bool=False, haltEvents: bool=True):
"""
Pops up a dialogue box.
Args:
title (str): The big "title" text to be displayed.
message (str): The main body text to be displayed.
haltUpdate (bool): If True, the games update() method will not be called until
the popup is dismissed. Default: False.
haltEvents (bool): If True, the games onEvent() method will not be called until
the popup is dismissed. Default: True.
"""
POPUP_MARGINS = 10
FONT_BIG = pygame.font.SysFont('', 50)
FONT_NORM = pygame.font.SysFont('', 32)
titleSurf = FONT_BIG.render(title, True, (128, 128, 128))
messageSurf = FONT_NORM.render(message, True, (128, 128, 128))
w = max(titleSurf.get_width(), messageSurf.get_width())
w += POPUP_MARGINS*2
h = titleSurf.get_height() + messageSurf.get_height()
h += POPUP_MARGINS*3
self._haltUpdate = haltUpdate
self._haltEvents = haltEvents
self._popupMenu = pygame.Surface((w, h))
self._popupMenu.fill((0, 0, 0))
self._popupMenu.blit(centre(titleSurf, (w, titleSurf.get_height())), (0, POPUP_MARGINS))
self._popupMenu.blit(centre(messageSurf, (w, messageSurf.get_height())), (0, titleSurf.get_height()+POPUP_MARGINS*2))
def punish(self, player, intensity: float):
"""
Handles punishing players for most games.
Handles games preferences on PDO order?
Handles auto popups for messaged events.
Args:
player (NoPELib.Player): The player to punish.
intensity (float): The intensity to punish the player.
"""

69
src/gameUtils/turns.py Normal file
View File

@ -0,0 +1,69 @@
# TODO: Add a variable for the game developers to see if pain is "avaialbe",
# I.E. has the shocker been passed to the next player.
# Also add a pop-up, probably using pygame-gui, to tell the player
# to pass the shocker to the next person.
# NOTE: Each expansion is assigned multiple players, if an expansion has a tag
# such as "ONE_PERSON_ONLY", it means that when the game developer wants
# to go to the next player, it must self.notify() the players to pass the
# physical object around.
# NOTE: That tag IS NOT a part of NoPE-Lib or PDO-Lib. It simply acts as an
# identifier for games to use.
# TODO: Add player config editor.
class BaseTurnHandler:
def __init__(self, playersManager):
"""
The base turn handler metaclass, used to create round
handlers such as round table.
"""
self._pm = playersManager
# The current turn number (0 based)
self.turn = 0
# The maximum number of turns
self.turns = len(self._pm)
# The current round number (0 based)
self.round = 0
# Which players are playing in this turn
self.playing = []
def next(self):
raise NotImplementedError
class RoundTable(BaseTurnHandler):
"""
A simple "round table" round system.
"""
# TODO: Add some kind of implementation with NoPE
# to determain if we actually need to re-assign PDOs.
def __init__(self, teamSize: int, *args, **kwargs):
"""
"""
super().__init__(*args, **kwargs)
if teamSize > len(self._pm):
raise Exception('Too little players for specified team size.') # TODO: Custom exception
self.teamSize = teamSize
self.turns //= self.teamSize
self.turn -= 1
self.next()
def next(self):
"""
Oh god I hate it.
"""
self.turn += 1
if self.turn >= self.turns:
self.turn -= self.turns
self.round += 1
self.playing = []
playerNames = list(self._pm.keys())
for i in range(self.teamSize):
self.playing.append(self._pm[playerNames[(i+self.turn)%len(playerNames)]])
return self.playing

View File

@ -11,12 +11,11 @@ def centre(surface: pygame.Surface, size: tuple[int, int]) -> pygame.Surface:
Returns: Returns:
pygame.Surface pygame.Surface
""" """
surf = pygame.Surface(size) surf = pygame.Surface(size, pygame.SRCALPHA, 32)
offX = surf.size[0]//2 - surface.size[0]//2 offX = surf.size[0]//2 - surface.size[0]//2
offY = surf.size[1]//2 - surface.size[1]//2 offY = surf.size[1]//2 - surface.size[1]//2
surf.blit(surface, (offX, offY)) surf.blit(surface, (offX, offY))
return surf return surf