diff --git a/README.md b/README.md index 00ae0e1..f1b01c0 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,18 @@ Skład zespołu: - Michał Czekański - Marcin Kostrzewski -Poruszanie się: *WASD* -Podnoszenie obiektów: *SPACJA* \ No newline at end of file +## Wymagania +``` +Python 3.x +pygame: 1.9.x +``` +## Uruchomienie +``` +$ python Run.py +``` +## Konfiguracja +Plik z konfiguracją znajduje w ```data/config/mainConfig.json```. + +## Sterowanie +* Poruszanie się: *WASD* +* Interakcja: *SPACJA* \ No newline at end of file diff --git a/Run.py b/Run.py new file mode 100644 index 0000000..07e0abb --- /dev/null +++ b/Run.py @@ -0,0 +1,3 @@ +from src.game.Game import Game + +game = Game() diff --git a/data/images/entities/firepit.png b/data/images/entities/firepit.png new file mode 100644 index 0000000..c505c72 Binary files /dev/null and b/data/images/entities/firepit.png differ diff --git a/data/images/entities/gravestone.png b/data/images/entities/gravestone.png new file mode 100644 index 0000000..97075d3 Binary files /dev/null and b/data/images/entities/gravestone.png differ diff --git a/data/images/entities/player.png b/data/images/entities/player.png index f9da2de..2e02234 100644 Binary files a/data/images/entities/player.png and b/data/images/entities/player.png differ diff --git a/data/images/entities/purewater.png b/data/images/entities/purewater.png new file mode 100644 index 0000000..ea19dfe Binary files /dev/null and b/data/images/entities/purewater.png differ diff --git a/data/images/entities/tree.png b/data/images/entities/tree.png new file mode 100644 index 0000000..c103cd7 Binary files /dev/null and b/data/images/entities/tree.png differ diff --git a/data/mapdata/mapEntities.json b/data/mapdata/mapEntities.json index 8df079c..af3325e 100644 --- a/data/mapdata/mapEntities.json +++ b/data/mapdata/mapEntities.json @@ -74,5 +74,416 @@ "thirst": 0, "hunger": -25 } + }, + { + "name" : "firepit", + "position" : { + "x": 10, + "y": 7 + }, + "isPickupable": false, + "effect" : { + "hp": 10, + "stamina": 15, + "thirst": 0, + "hunger": 0 + } + }, + { + "name" : "purewater", + "position" : { + "x": 3, + "y": 5 + }, + "isPickupable": false, + "effect" : { + "hp": 2, + "stamina": 2, + "thirst": -40, + "hunger": 0 + } + }, + { + "name" : "purewater", + "position" : { + "x": 3, + "y": 6 + }, + "isPickupable": false, + "effect" : { + "hp": 2, + "stamina": 2, + "thirst": -40, + "hunger": 0 + } + }, + { + "name" : "purewater", + "position" : { + "x": 3, + "y": 7 + }, + "isPickupable": false, + "effect" : { + "hp": 2, + "stamina": 2, + "thirst": -40, + "hunger": 0 + } + }, + { + "name" : "purewater", + "position" : { + "x": 2, + "y": 7 + }, + "isPickupable": false, + "effect" : { + "hp": 2, + "stamina": 2, + "thirst": -40, + "hunger": 0 + } + }, + { + "name" : "purewater", + "position" : { + "x": 2, + "y": 8 + }, + "isPickupable": false, + "effect" : { + "hp": 2, + "stamina": 2, + "thirst": -40, + "hunger": 0 + } + }, + { + "name" : "purewater", + "position" : { + "x": 1, + "y": 8 + }, + "isPickupable": false, + "effect" : { + "hp": 2, + "stamina": 2, + "thirst": -40, + "hunger": 0 + } + }, + { + "name" : "rock", + "position" : { + "x": 3, + "y": 4 + }, + "isPickupable" : false + }, + { + "name" : "rock", + "position" : { + "x": 2, + "y": 5 + }, + "isPickupable" : false + }, + { + "name" : "rock", + "position" : { + "x": 4, + "y": 5 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 2, + "y": 2 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 2, + "y": 3 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 2, + "y": 4 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 3, + "y": 2 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 3, + "y": 3 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 13, + "y": 8 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 13, + "y": 7 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 12, + "y": 17 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 12, + "y": 14 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 13, + "y": 14 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 15, + "y": 14 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 13, + "y": 15 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 13, + "y": 16 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 10, + "y": 16 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 10, + "y": 17 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 11, + "y": 17 + }, + "isPickupable" : false + }, + { + "name" : "cranberry", + "position" : { + "x": 12, + "y": 15 + }, + "isPickupable" : true, + "effect" : { + "hp" : 0, + "stamina" : 5, + "thirst" : -2, + "hunger" : -10 + } + }, + { + "name" : "berry", + "position" : { + "x": 11, + "y": 16 + }, + "isPickupable" : true, + "effect" : { + "hp" : -10, + "stamina" : 5, + "thirst" : 0, + "hunger" : -20 + } + }, + { + "name" : "cranberry", + "position" : { + "x": 5, + "y": 12 + }, + "isPickupable" : true, + "effect" : { + "hp" : 0, + "stamina" : 5, + "thirst" : -2, + "hunger" : -10 + } + }, + { + "name" : "cranberry", + "position" : { + "x": 5, + "y": 13 + }, + "isPickupable" : true, + "effect" : { + "hp" : 0, + "stamina" : 5, + "thirst" : -2, + "hunger" : -10 + } + }, + { + "name" : "tree", + "position" : { + "x": 6, + "y": 13 + }, + "isPickupable" : false + }, + { + "name" : "rock", + "position" : { + "x": 7, + "y": 15 + }, + "isPickupable" : false + }, + { + "name" : "tree", + "position" : { + "x": 6, + "y": 15 + }, + "isPickupable" : false + } + , + { + "name" : "tree", + "position" : { + "x": 6, + "y": 14 + }, + "isPickupable" : false + }, + { + "name" : "rabbit", + "position" : { + "x": 17, + "y": 6 + }, + "isPickupable" : true, + "effect" : { + "hp": 30, + "stamina": -5, + "thirst": -5, + "hunger": -40 + } + }, + { + "name" : "rabbit", + "position" : { + "x": 3, + "y": 14 + }, + "isPickupable" : true, + "effect" : { + "hp": 30, + "stamina": -5, + "thirst": -5, + "hunger": -40 + } + }, + { + "name" : "berry", + "position" : { + "x": 5, + "y": 16 + }, + "isPickupable" : true, + "effect" : { + "hp" : -10, + "stamina" : 5, + "thirst" : 0, + "hunger" : -20 + } + }, + { + "name" : "berry", + "position" : { + "x": 7, + "y": 13 + }, + "isPickupable" : true, + "effect" : { + "hp" : -10, + "stamina" : 5, + "thirst" : 0, + "hunger" : -20 + } + }, + { + "name" : "bush", + "position" : { + "x": 7, + "y": 5 + }, + "isPickupable" : false + }, + { + "name" : "bush", + "position" : { + "x": 10, + "y": 15 + }, + "isPickupable" : false } ] \ No newline at end of file diff --git a/src/Run.py b/src/Run.py deleted file mode 100644 index a3e220a..0000000 --- a/src/Run.py +++ /dev/null @@ -1,3 +0,0 @@ -from game.Game import Game - -game = Game() diff --git a/src/entities/Collidable.py b/src/entities/Collidable.py deleted file mode 100644 index 9ecf2eb..0000000 --- a/src/entities/Collidable.py +++ /dev/null @@ -1,31 +0,0 @@ -from src.entities.Entity import Entity - - -class Collidable(Entity): - - def __init__(self, texture, pos, id): - super().__init__(texture, pos, id) - - def check_for_collision(self, x_pos, y_pos): - if self.pos[0] == x_pos: - if self.pos[1] == y_pos: - return True - - return False - - -#col = Collidable(1, 1, 1) - - -""" - def interact_with_hp(self, hp, Statistics): - Statistics.set_hp(hp) - - def interact_with_hunger(self, hunger, Statistics): - Statistics.set_hunger(hunger) - - def interact_with_thirst(self, thirst, Statistics): - Statistics.set_thirst(thirst) - - def interact_with_stamina(self, stamina, Statistics): - Statistics.set_stamina(stamina)""" \ No newline at end of file diff --git a/src/entities/Interactable.py b/src/entities/Interactable.py index a3f3fb9..ed12634 100644 --- a/src/entities/Interactable.py +++ b/src/entities/Interactable.py @@ -2,23 +2,13 @@ from src.entities.Entity import Entity class Interactable(Entity): + def __init__(self, texture, size, pos, Statistics): + super().__init__(texture, size, pos) + self.Statistics = Statistics - def __init__(self, texture, pos, id): - super().__init__(texture, pos, id) - - @staticmethod - def interact_with_hp(hp, Statistics): - Statistics.set_hp(hp) - - @staticmethod - def interact_with_hunger(hunger, Statistics): - Statistics.set_hunger(hunger) - - @staticmethod - def interact_with_thirst(thirst, Statistics): - Statistics.set_thirst(thirst) - - @staticmethod - def interact_with_stamina(stamina, Statistics): - Statistics.set_stamina(stamina) + def on_interaction(self, Player): + Player.statistics.set_hp(self.Statistics.hp) + Player.statistics.set_stamina(self.Statistics.stamina) + Player.statistics.set_thirst(self.Statistics.thirst) + Player.statistics.set_hunger(self.Statistics.hunger) diff --git a/src/entities/Pickupable.py b/src/entities/Pickupable.py index cae4981..bf4cc5e 100644 --- a/src/entities/Pickupable.py +++ b/src/entities/Pickupable.py @@ -1,16 +1,8 @@ -from src.entities import Statistics, Player from src.entities.Interactable import Interactable class Pickupable(Interactable): def __init__(self, texture, size, pos, Statistics): - super().__init__(texture, size, pos) + super().__init__(texture, size, pos, Statistics) self.is_pickupable = True - self.Statistics = Statistics - - def on_pickup(self, Player): - Player.statistics.set_hp(self.Statistics.hp) - Player.statistics.set_stamina(self.Statistics.stamina) - Player.statistics.set_thirst(self.Statistics.thirst) - Player.statistics.set_hunger(self.Statistics.hunger) diff --git a/src/entities/Player.py b/src/entities/Player.py index bc0d713..1a2d160 100644 --- a/src/entities/Player.py +++ b/src/entities/Player.py @@ -1,4 +1,5 @@ from enum import Enum +import random from src.entities.Entity import Entity from src.entities.Statistics import Statistics @@ -6,8 +7,6 @@ import pygame class Player(Entity): - statistics: Statistics - def __init__(self, spawnpoint, size): super().__init__("player.png", size, (spawnpoint[0] * size, spawnpoint[1] * size)) # Where the player is facing, 0 - north, 1 @@ -15,31 +14,51 @@ class Player(Entity): self.statistics = Statistics(100, 0, 0, 100) # How many steps has the player taken through its lifetime self.movePoints = 0 + # Tracks how much time has passed since the player is alive + self.timer = pygame.time.Clock() + self.timeAlive = 1 + # Used to determine fatigue + self.fatigueTimeout = 0 + self.alive = True + # If a player dies, the death reason is stored here + self.deathReason = None # Move in a desired direction def move(self, rotation): self.movePoints += 1 - # Player gets tired aswell! - self.applyFatigue() - if rotation.value == Rotations.NORTH.value: - self.rect.y -= self.rect.w - elif rotation.value == Rotations.EAST.value: - self.rect.x += self.rect.w - elif rotation.value == Rotations.SOUTH.value: - self.rect.y += self.rect.w - elif rotation.value == Rotations.WEST.value: - self.rect.x -= self.rect.w + # You can only move if you have enough stamina + if self.statistics.stamina > 1: + self.applyWalkingFatigue() + if rotation.value == Rotations.NORTH.value: + self.rect.y -= self.rect.w + elif rotation.value == Rotations.EAST.value: + self.rect.x += self.rect.w + elif rotation.value == Rotations.SOUTH.value: + self.rect.y += self.rect.w + elif rotation.value == Rotations.WEST.value: + self.rect.x -= self.rect.w - def applyFatigue(self): + def applyWalkingFatigue(self): # looses hunger every 10 steps taken if self.movePoints % 10 == 0: - self.statistics.set_hunger(10) + self.statistics.set_hunger(5) # gets more thirsty every 5 steps if self.movePoints % 5 == 0: - self.statistics.set_thirst(10) + self.statistics.set_thirst(6) # gets tired every step self.statistics.set_stamina(-2) + def applyTimeFatigue(self, tickTime): + self.fatigueTimeout += tickTime + if self.fatigueTimeout >= 700: + self.statistics.set_thirst(5) + self.statistics.set_hunger(3) + # A player can randomly regenerate stamina + if random.randrange(5) == 0: + self.statistics.set_stamina(2) + self.fatigueTimeout = 0 + + def getFacingCoord(self): if self.rotation == Rotations.NORTH: return self.rect.x, self.rect.y - (self.rect.h) @@ -71,6 +90,31 @@ class Player(Entity): self.image = pygame.transform.rotate(self.image, ((self.rotation.value - rotation.value) * 90)) self.rotation = rotation + # Updates self.alive if any of the statistic reaches critical value + def determineLife(self): + if self.statistics.hunger == 100: + self.alive = False + self.deathReason = StatisticNames.HUNGER + elif self.statistics.thirst == 100: + self.alive = False + self.deathReason = StatisticNames.THIRST + elif self.statistics.hp == 0: + self.alive = False + self.deathReason = StatisticNames.HP + + # Change texture after dying + if not self.alive: + self.image, null = self.getTexture("gravestone.png", self.rect.h) + + # Called every frame + def update(self): + if self.alive: + self.timeAlive += self.timer.get_time() + # Player gets tired every once in a while + self.applyTimeFatigue(self.timer.get_time()) + self.timer.tick() + self.determineLife() + class Rotations(Enum): NORTH = 0 diff --git a/src/game/EventManager.py b/src/game/EventManager.py index fffb38a..7975494 100644 --- a/src/game/EventManager.py +++ b/src/game/EventManager.py @@ -1,5 +1,6 @@ import pygame +from src.entities.Interactable import Interactable from src.entities.Pickupable import Pickupable from src.entities.Player import Rotations @@ -39,27 +40,35 @@ class EventManager: def handlePlayerControls(self, keys): # Key names are temporary # TODO: Load key bindings from JSON - if keys[pygame.K_SPACE]: - object = self.game.map.getEntityOnCoord(self.player.getFacingCoord()) - if type(object) is Pickupable: - object.on_pickup(self.player) - self.game.map.removeSpriteFromMap(object) - if keys[pygame.K_w]: - self.player.rotate(Rotations.NORTH) - if not self.game.map.collision(self.player.rect.x, self.player.rect.y - self.player.rect.w): - self.player.move(Rotations.NORTH) - if keys[pygame.K_s]: - self.player.rotate(Rotations.SOUTH) - if not self.game.map.collision(self.player.rect.x, self.player.rect.y + self.player.rect.w): - self.player.move(Rotations.SOUTH) - if keys[pygame.K_d]: - self.player.rotate(Rotations.EAST) - if not self.game.map.collision(self.player.rect.x + self.player.rect.w, self.player.rect.y): - self.player.move(Rotations.EAST) - if keys[pygame.K_a]: - self.player.rotate(Rotations.WEST) - if not self.game.map.collision(self.player.rect.x - self.player.rect.w, self.player.rect.y): - self.player.move(Rotations.WEST) + + if self.player.alive: + # Picking up items + if keys[pygame.K_SPACE]: + object = self.game.map.getEntityOnCoord(self.player.getFacingCoord()) + # Picked up item gets removed from the map + if type(object) is Pickupable: + object.on_interaction(self.player) + self.game.map.removeSpriteFromMap(object) + elif type(object) is Interactable: + object.on_interaction(self.player) + + # Movement + if keys[pygame.K_w]: + self.player.rotate(Rotations.NORTH) + if not self.game.map.collision(self.player.rect.x, self.player.rect.y - self.player.rect.w): + self.player.move(Rotations.NORTH) + if keys[pygame.K_s]: + self.player.rotate(Rotations.SOUTH) + if not self.game.map.collision(self.player.rect.x, self.player.rect.y + self.player.rect.w): + self.player.move(Rotations.SOUTH) + if keys[pygame.K_d]: + self.player.rotate(Rotations.EAST) + if not self.game.map.collision(self.player.rect.x + self.player.rect.w, self.player.rect.y): + self.player.move(Rotations.EAST) + if keys[pygame.K_a]: + self.player.rotate(Rotations.WEST) + if not self.game.map.collision(self.player.rect.x - self.player.rect.w, self.player.rect.y): + self.player.move(Rotations.WEST) diff --git a/src/game/Game.py b/src/game/Game.py index ddc760a..ba6b8ad 100644 --- a/src/game/Game.py +++ b/src/game/Game.py @@ -3,13 +3,11 @@ import json from pathlib import Path from os import path -from game.EventManager import EventManager -from game.Screen import Screen -from game.Map import Map +from src.game.EventManager import EventManager +from src.game.Screen import Screen +from src.game.Map import Map -from src.entities.Pickupable import Pickupable from src.entities.Player import Player -from src.entities.Statistics import Statistics from src.game.Timer import Timer @@ -61,6 +59,7 @@ class Game: while self.running: # Update ingame clock self.ingameTimer.updateTime(self.pgTimer.tick()) + self.spritesList.update() self.eventManager.handleEvents() self.spritesList.draw(self.screen.pygameScreen) pygame.display.flip() diff --git a/src/game/Map.py b/src/game/Map.py index 7ed08bf..33ce59a 100644 --- a/src/game/Map.py +++ b/src/game/Map.py @@ -1,6 +1,8 @@ import json import pygame + +from src.entities.Interactable import Interactable from src.game.TerrainTile import TerrainTile from src.game.Screen import Locations @@ -41,6 +43,7 @@ class Map: entityListJson = json.loads(file.read()) for entity in entityListJson: try: + # Creates a pickupable object if entity["isPickupable"]: actualEntities.append(Pickupable(entity["name"] + ".png", self.tileSize, @@ -49,6 +52,17 @@ class Map: entity["effect"]["hunger"], entity["effect"]["thirst"], entity["effect"]["stamina"]))) + # Creates an interactable object + elif "effect" in entity: + actualEntities.append(Interactable(entity["name"] + ".png", + self.tileSize, + (entity["position"]["x"] * self.tileSize, + entity["position"]["y"] * self.tileSize), + Statistics(entity["effect"]["hp"], + entity["effect"]["hunger"], + entity["effect"]["thirst"], + entity["effect"]["stamina"]))) + # Creates plain entity else: actualEntities.append(Entity(entity["name"] + ".png", self.tileSize, diff --git a/src/ui/Ui.py b/src/ui/Ui.py index ccb64ad..dcdfd31 100644 --- a/src/ui/Ui.py +++ b/src/ui/Ui.py @@ -78,7 +78,7 @@ class Ui(): if self.thirstBar.value != statistics.thirst: self.thirstBar.updateFill(statistics.thirst) - consoleLines.append(self.timer.getPrettyTime() + " - Stamina: " + str(statistics.thirst)) + consoleLines.append(self.timer.getPrettyTime() + " - Thirst: " + str(statistics.thirst)) if len(consoleLines) > 0: self.console.addLinesToConsoleAndScrollToDisplayThem(consoleLines)