├── .gitignore ├── .idea ├── .gitignore ├── ParticleOverdose.iml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── diamond.png ├── face │ ├── angry_eye.png │ ├── dead_eyes.png │ ├── fall_move_eyes.png │ ├── hoo_hoo.png │ ├── jump_eyes.png │ ├── love_eyes.png │ ├── silly_eyes.png │ ├── smile.png │ ├── smirk_0.png │ ├── smirk_1.png │ ├── super_angry_eyes.png │ ├── talk_0.png │ ├── talk_1.png │ ├── talk_2.png │ └── talk_3.png ├── gun │ ├── classic_baby.png │ ├── particulator.png │ ├── russian_roulette.png │ └── silent_killer.png ├── sword │ ├── blood_lust.png │ ├── blue_hawk.png │ ├── burning_desire.png │ ├── death_wish.png │ ├── evergreen_evil.png │ └── the_way_of_light.png └── tilesets │ ├── cave_stone.png │ └── tileset.png ├── core ├── __init__.py ├── assets.py ├── camera.py ├── common_functions.py ├── common_names.py ├── common_resources.py ├── constants.py ├── event_holder.py ├── face.py ├── game.py ├── inventory.py ├── jelly_cube.py ├── ldtk │ ├── __init__.py │ ├── common_functions.py │ ├── gun.py │ ├── tile_layer.py │ └── tileset.py ├── level.py ├── particle.py ├── player.py ├── pygame_ce │ ├── FRect.py │ └── __init__.py ├── sprite.py └── sword.py ├── levels └── test.json ├── main.py ├── main.spec ├── pt.py └── steps /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | core/__pycache__ 3 | core/ldtk/__pycache__ 4 | core/pygame_ce/__pycache__ 5 | build/ 6 | dist/ -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/ParticleOverdose.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Welcome to the contribution guidelines for 2 | our open-source game project! We are thrilled to 3 | have you interested in collaborating with us to 4 | create an exciting and engaging gaming experience. 5 | 6 | 7 | To contribute to our project, 8 | you will need to use the LDTK level editor to create maps for the game. 9 | LDTK is a requirement of our project, as it has proven to be an efficient 10 | and reliable tool for creating high-quality maps that integrate seamlessly 11 | with our game engine. 12 | 13 | 14 | We highly recommend using Pycharm as your editor of choice, 15 | as it has an exceptional conflict manager that will make your contributions 16 | smoother and more efficient. 17 | 18 | 19 | We welcome contributions at all stages of the project and encourage you to make a Pull Request 20 | if you have an idea or feature you would like to contribute. Upon review and agreement, 21 | we will happily add you to our list of collaborators. 22 | 23 | 24 | If you encounter any issues while contributing, please create an Issue in the dedicated section 25 | of our repository. This will allow us to quickly address any problems and continue improving our 26 | project. 27 | 28 | 29 | In case you need to reach out to me for any reason, 30 | please don't hesitate to contact us through my discord: 31 | Yolowex#5586 or email me at pistolparody@gmail.com. 32 | 33 | 34 | Thank you for considering contributing to our open-source game project. 35 | We appreciate your time and efforts, 36 | and look forward to working with you towards our shared vision. 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is license under Creative Commons NonCommercial License. 2 | 3 | 4 | You are free to: 5 | 6 | Share — copy and redistribute the material in any medium or format 7 | 8 | The licensor cannot revoke these freedoms as long as you follow the license terms. 9 | 10 | 11 | Under the following terms: 12 | 13 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 14 | 15 | NonCommercial — You may not use the material for commercial purposes. 16 | 17 | NoDerivatives — If you remix, transform, or build upon the material, you may not distribute the modified material. 18 | 19 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 20 | 21 | 22 | full details at: https://creativecommons.org/licenses/by-nc/4.0/ 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stylish Slasher: Speed Run Edition 2 | 3 | ## Intro 4 | Here is a game I originally made for Pygame Community Easter Jam 2023. But I fell in love with the Idea 5 | So I'm planning to expand the project further than this. 6 | 7 | ## Game Play 8 | 9 | Your goal in this game is to find as many diamonds as you can, as fast as you can. 10 | you can arm yourself up with 6 different swords with different abilities, 11 | you need some of them to gather certain diamonds. 12 | 13 | ``` 14 | 1 - use UP,DOWN,RIGHT and LEFT to move. 15 | 2 - use A,S,D to move the camera. 16 | 3 - use 1,2,3,4,5,6 to switch between swords. 17 | 4 - use F for normal attacks and V for special attacks. 18 | 5 - use Q,W,E,R,T,G as shortcuts for special attacks. 19 | ``` 20 | 21 | ## Showcase 22 | 23 | https://user-images.githubusercontent.com/122750743/232397527-249b0a73-04fb-4f5d-8fb6-60794e6dca2c.mp4 24 | 25 | ## Requirements 26 | 27 | You need `pygame-ce` same as or above version `2.2`. 28 | 29 | The minimum `Python` version is `3.10`, but `3.11` is recommended for performance. 30 | 31 | ## How to Play 32 | 33 | ### *From Source* 34 | 35 | you need to have `pygame-ce` (pygame community edition) installed. 36 | if you don't have it, first uninstall pygame, and then install `pygame-ce`, with pip. 37 | 38 | ```commandline 39 | pip uninstall pygame 40 | pip install pygame-ce 41 | ``` 42 | 43 | and the clone the project, use `--depth 1` to only download the head of the branch. 44 | 45 | ```commandline 46 | git clone https://github.com/mmdmoa/ParticleOverdose --depth 1 47 | ``` 48 | 49 | after that just enter the directory of the project and run the main file. 50 | 51 | ```commandline 52 | python ./main.py 53 | ``` 54 | 55 | ### *On the Browser* 56 | 57 | Just open the page of the game, at itch io. 58 | here is the link: 59 | https://pistolparody.itch.io/particle-overdose 60 | 61 | you'll need a desktop device though. it's not available 62 | on Phones yet. 63 | 64 | 65 | # Contribution 66 | 67 | Our open source project is licensed under the widely-used and permissive MIT License, 68 | granting users freedom to use, modify, and distribute the software without liability. 69 | 70 | We welcome contributions to our open source game project! Help us enhance the gameplay, 71 | add new features, fix bugs, and make it even better. 72 | See the [CONTRIBUTING.md](./CONTRIBUTING.md) file for more details. 73 | -------------------------------------------------------------------------------- /assets/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/diamond.png -------------------------------------------------------------------------------- /assets/face/angry_eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/angry_eye.png -------------------------------------------------------------------------------- /assets/face/dead_eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/dead_eyes.png -------------------------------------------------------------------------------- /assets/face/fall_move_eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/fall_move_eyes.png -------------------------------------------------------------------------------- /assets/face/hoo_hoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/hoo_hoo.png -------------------------------------------------------------------------------- /assets/face/jump_eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/jump_eyes.png -------------------------------------------------------------------------------- /assets/face/love_eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/love_eyes.png -------------------------------------------------------------------------------- /assets/face/silly_eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/silly_eyes.png -------------------------------------------------------------------------------- /assets/face/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/smile.png -------------------------------------------------------------------------------- /assets/face/smirk_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/smirk_0.png -------------------------------------------------------------------------------- /assets/face/smirk_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/smirk_1.png -------------------------------------------------------------------------------- /assets/face/super_angry_eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/super_angry_eyes.png -------------------------------------------------------------------------------- /assets/face/talk_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/talk_0.png -------------------------------------------------------------------------------- /assets/face/talk_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/talk_1.png -------------------------------------------------------------------------------- /assets/face/talk_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/talk_2.png -------------------------------------------------------------------------------- /assets/face/talk_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/face/talk_3.png -------------------------------------------------------------------------------- /assets/gun/classic_baby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/gun/classic_baby.png -------------------------------------------------------------------------------- /assets/gun/particulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/gun/particulator.png -------------------------------------------------------------------------------- /assets/gun/russian_roulette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/gun/russian_roulette.png -------------------------------------------------------------------------------- /assets/gun/silent_killer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/gun/silent_killer.png -------------------------------------------------------------------------------- /assets/sword/blood_lust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/sword/blood_lust.png -------------------------------------------------------------------------------- /assets/sword/blue_hawk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/sword/blue_hawk.png -------------------------------------------------------------------------------- /assets/sword/burning_desire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/sword/burning_desire.png -------------------------------------------------------------------------------- /assets/sword/death_wish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/sword/death_wish.png -------------------------------------------------------------------------------- /assets/sword/evergreen_evil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/sword/evergreen_evil.png -------------------------------------------------------------------------------- /assets/sword/the_way_of_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/sword/the_way_of_light.png -------------------------------------------------------------------------------- /assets/tilesets/cave_stone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/tilesets/cave_stone.png -------------------------------------------------------------------------------- /assets/tilesets/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/assets/tilesets/tileset.png -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/core/__init__.py -------------------------------------------------------------------------------- /core/assets.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | from core.sprite import Sprite 3 | from core.ldtk.tileset import Tileset 4 | from core.ldtk.common_functions import * 5 | 6 | face_root = "./assets/face/" 7 | sword_root = "./assets/sword/" 8 | 9 | eye_dict = { 10 | "angry":"angry_eye.png", 11 | "dead":"dead_eyes.png", 12 | "fall":"fall_move_eyes.png", 13 | "jump":"jump_eyes.png", 14 | "love":"love_eyes.png", 15 | "silly":"silly_eyes.png", 16 | "rage":"super_angry_eyes.png", 17 | } 18 | 19 | 20 | mouth_dict = { 21 | "hoo_hoo":"hoo_hoo.png", 22 | "talk_0":"talk_0.png", 23 | "talk_1":"talk_1.png", 24 | "talk_2":"talk_2.png", 25 | "talk_3":"talk_3.png", 26 | "smirk_0":"smirk_0.png", 27 | "smirk_1":"smirk_1.png", 28 | "smile":"smile.png", 29 | } 30 | 31 | sword_dict = { 32 | "blood":"blood_lust.png", 33 | "hawk":"blue_hawk.png", 34 | "desire":"burning_desire.png", 35 | "death":"death_wish.png", 36 | "evil":"evergreen_evil.png", 37 | "light":"the_way_of_light.png" 38 | } 39 | 40 | 41 | 42 | 43 | right_sword_dict = {key:Sprite(sword_root+path) for key,path in sword_dict.items()} 44 | left_sword_dict = {key:Sprite(sword_root+path) for key,path in sword_dict.items()} 45 | 46 | 47 | right_eye_sprite_dict = {key:Sprite(face_root+path) for key,path in eye_dict.items()} 48 | right_mouth_sprite_dict = {key:Sprite(face_root + path) for key,path in mouth_dict.items()} 49 | 50 | left_eye_sprite_dict = {key:sprite.copy()for key,sprite in right_eye_sprite_dict.items()} 51 | 52 | 53 | left_mouth_sprite_dict = {key:sprite.copy()for key,sprite in right_mouth_sprite_dict.items()} 54 | 55 | levels_root = './levels/' 56 | 57 | world = json.loads(open(levels_root+"test.json").read()) 58 | 59 | 60 | diamond = Sprite("./assets/diamond.png") 61 | 62 | -------------------------------------------------------------------------------- /core/camera.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | 3 | class Camera: 4 | def __init__(self,pos:Vector2): 5 | self.pos = pos 6 | 7 | @property 8 | def x(self): 9 | return self.pos.x 10 | 11 | @property 12 | def y( self ): 13 | return self.pos.y 14 | -------------------------------------------------------------------------------- /core/common_functions.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | 3 | 4 | def rect_convert_polygon( rect: FRect ) : 5 | return [Vector2(i) for i in 6 | [[rect.x, rect.y], [rect.x + rect.w, rect.y], [rect.x + rect.w, rect.y + rect.h], 7 | [rect.x , rect.y + rect.h],]] 8 | 9 | 10 | def move_points(points:list[Vector2],x=0,y=0): 11 | for point in points: 12 | point.x += x 13 | point.y += y 14 | 15 | 16 | def rotate_point(origin, point, angle): 17 | angle = math.radians(angle) 18 | ox, oy = origin 19 | px, py = point 20 | 21 | qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy) 22 | qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy) 23 | return Vector2(qx, qy) 24 | 25 | 26 | def percent(All,part): 27 | return (100/All) * part 28 | 29 | def random_color() -> Color: 30 | return Color([random.randint(0,255) for _ in range(3)]) 31 | 32 | def now(): 33 | return pg.time.get_ticks() / 1000 34 | 35 | 36 | def get_rotated_points(rect:FRect,angle:float) -> list[Vector2]: 37 | 38 | l_min_x = rect.x 39 | l_min_y = rect.y 40 | 41 | res = [] 42 | points = rect_convert_polygon(rect) 43 | c = Vector2(rect.center) 44 | 45 | for point in points: 46 | new_point = rotate_point(c,point,angle) 47 | res.append(new_point) 48 | 49 | min_x = min([i.x for i in res]) 50 | min_y = min([i.y for i in res]) 51 | 52 | for point in res: 53 | point.x += (l_min_x - min_x) 54 | point.y += (l_min_y - min_y) 55 | 56 | return res 57 | 58 | def polygon_to_rect(polygon:list[Vector2]): 59 | x_list = [i.x for i in polygon] 60 | y_list = [i.y for i in polygon] 61 | min_x = min(x_list) 62 | max_x = max(x_list) 63 | min_y = min(y_list) 64 | max_y = max(y_list) 65 | the_rect = FRect(min_x, min_y, max_x - min_x, max_y - min_y) 66 | return the_rect -------------------------------------------------------------------------------- /core/common_names.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | import json 4 | import pygame as pg 5 | from pygame.locals import * 6 | from typing import Optional 7 | 8 | from pygame import Vector2,Surface,Color 9 | if __import__("sys").platform == "emscripten": 10 | # PLATFORM = 'web' 11 | from core.pygame_ce.FRect import Rect as FRect 12 | else: 13 | from pygame import FRect 14 | -------------------------------------------------------------------------------- /core/common_resources.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | 3 | from core.event_holder import EventHolder 4 | from core.assets import * 5 | from core.camera import Camera 6 | 7 | screen: Optional[pg.Surface] = None 8 | 9 | 10 | event_holder: Optional[EventHolder] = None 11 | game = None 12 | camera = Camera(Vector2(0,0)) 13 | inner_box_list:list[Rect] = [] 14 | font: Optional[pg.Font] = None 15 | little_font: Optional[pg.Font] = None 16 | smallest_font: Optional[pg.Font] = None -------------------------------------------------------------------------------- /core/constants.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | from core.common_resources import * 3 | 4 | IS_WEB = __import__("sys").platform == "emscripten" 5 | 6 | WHITE = Color(195,195,195) 7 | BLACK = Color(15,15,15) 8 | GRAY = WHITE.lerp(BLACK,0.5) 9 | SKY = Color(150,150,220) 10 | RED = Color(195,15,15) 11 | 12 | RIGHT = 'right' 13 | LEFT = 'left' 14 | 15 | THROW = 'throw' 16 | SWING = 'swing' 17 | SWIRLING_THROW = 'swirling_throw' 18 | 19 | THROW_TYPES = [THROW,SWIRLING_THROW] 20 | 21 | ATTACK_NORMAL = 'normal' 22 | ATTACK_SPECIAL = 'special' -------------------------------------------------------------------------------- /core/event_holder.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | from pygame.locals import * 3 | from pygame.rect import FRect 4 | from pygame.math import Vector2 5 | 6 | class EventHolder : 7 | def __init__( self ) : 8 | self.pressed_keys = [] 9 | self.released_keys = [] 10 | self.held_keys = [] 11 | self.window_focus = True 12 | 13 | self.mouse_moved = False 14 | self.mouse_pos = Vector2(0, 0) 15 | self.mouse_pressed_keys = [False, False, False] 16 | self.mouse_released_keys = [False, False, False] 17 | self.mouse_held_keys = [False, False, False] 18 | self.mouse_focus = False 19 | self.should_render_debug = False 20 | self.should_quit = False 21 | self.determined_fps = 60 22 | self.final_fps = 0 23 | self.focus_gain_timer = -100 24 | self.mouse_focus_gain_timer = -100 25 | self.clock = pg.time.Clock() 26 | self.dt = 0 27 | 28 | @property 29 | def delta_time( self ): 30 | return self.dt 31 | # delta = 1 / (self.final_fps if self.final_fps!=0 else 60) 32 | # return delta 33 | 34 | @property 35 | def mouse_rect( self ) -> FRect: 36 | return FRect(self.mouse_pos.x - 1, self.mouse_pos.y - 1,2,2) 37 | 38 | def get_events( self ) : 39 | self.pressed_keys.clear() 40 | self.released_keys.clear() 41 | self.mouse_pressed_keys = [False, False, False] 42 | self.mouse_released_keys = [False, False, False] 43 | 44 | 45 | 46 | self.mouse_moved = False 47 | self.final_fps = self.clock.get_fps() 48 | self.dt = (self.clock.tick(self.determined_fps) / 1000) 49 | 50 | for i in pg.event.get() : 51 | if i.type == WINDOWFOCUSLOST: 52 | self.window_focus = False 53 | if i.type == WINDOWFOCUSGAINED: 54 | self.window_focus = True 55 | self.focus_gain_timer = pg.time.get_ticks() / 1000 56 | 57 | if i.type == WINDOWENTER: 58 | self.mouse_focus = True 59 | self.mouse_focus_gain_timer = pg.time.get_ticks() / 1000 60 | if i.type == WINDOWLEAVE: 61 | self.mouse_focus = False 62 | 63 | if i.type == WINDOWENTER or MOUSEMOTION : 64 | self.mouse_pos = Vector2(pg.mouse.get_pos()) 65 | 66 | if i.type == QUIT or i.type == KEYDOWN and i.key == K_ESCAPE: 67 | self.should_quit = True 68 | 69 | if i.type == MOUSEMOTION : 70 | self.mouse_moved = True 71 | 72 | if i.type == KEYDOWN : 73 | self.pressed_keys.append(i.key) 74 | if i.key not in self.held_keys : 75 | self.held_keys.append(i.key) 76 | 77 | if i.type == KEYUP : 78 | self.released_keys.append(i.key) 79 | if i.key in self.held_keys : 80 | self.held_keys.remove(i.key) 81 | 82 | if i.type == MOUSEBUTTONDOWN : 83 | self.mouse_pressed_keys = list(pg.mouse.get_pressed()) 84 | self.mouse_held_keys = list(pg.mouse.get_pressed()) 85 | 86 | if i.type == MOUSEBUTTONUP : 87 | self.mouse_released_keys = list(pg.mouse.get_pressed()) 88 | self.mouse_held_keys = list(pg.mouse.get_pressed()) -------------------------------------------------------------------------------- /core/face.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | from core.common_functions import * 3 | import core.common_resources as cr 4 | from core.sprite import Sprite 5 | from core.constants import * 6 | 7 | class Face: 8 | 9 | def __init__( self ): 10 | self.eye_right: Optional[Sprite] = None 11 | self.mouth_right: Optional[Sprite] = None 12 | self.eye_left: Optional[Sprite] = None 13 | self.mouth_left: Optional[Sprite] = None 14 | 15 | self.eye = "none" 16 | self.mouth = "none" 17 | 18 | 19 | def init( self ): 20 | eye = "angry" 21 | mouth = "smirk_0" 22 | self.update_face(eye,mouth) 23 | 24 | 25 | def update_face( self,new_eye=None,new_mouth=None ): 26 | 27 | if new_eye is not None and new_eye!=self.eye: 28 | self.eye = new_eye 29 | self.eye_right = cr.right_eye_sprite_dict[new_eye] 30 | self.eye_left = cr.left_eye_sprite_dict[new_eye] 31 | 32 | if new_mouth is not None and new_mouth!=self.mouth: 33 | self.mouth = new_mouth 34 | self.mouth_right = cr.right_mouth_sprite_dict[new_mouth] 35 | self.mouth_left = cr.left_mouth_sprite_dict[new_mouth] 36 | 37 | 38 | @property 39 | def eye_rect( self ): 40 | ptl,pbl = cr.game.player.points[0],cr.game.player.points[3] 41 | ptr,pbr = cr.game.player.points[1],cr.game.player.points[2] 42 | 43 | pt,pb = ptl,pbl 44 | if cr.game.player.facing == RIGHT: 45 | pt,pb = ptr,pbr 46 | 47 | pt,pb = pt.copy(),pb.copy() 48 | 49 | pm = pb.lerp(pt,0.9) 50 | 51 | rect = self.eye_left.transformed_surface.get_rect() 52 | 53 | if cr.game.player.facing == RIGHT : 54 | rect.x,rect.y = pm.x-rect.w,pm.y 55 | else: 56 | rect.x,rect.y = pm 57 | 58 | rect.x += cr.camera.x 59 | rect.y += cr.camera.y 60 | 61 | return rect 62 | 63 | 64 | @property 65 | def mouth_rect( self ) : 66 | ptl, pbl = cr.game.player.points[0], cr.game.player.points[3] 67 | ptr, pbr = cr.game.player.points[1], cr.game.player.points[2] 68 | 69 | pt, pb = ptl, pbl 70 | if cr.game.player.facing == RIGHT : 71 | pt, pb = ptr, pbr 72 | 73 | pt, pb = pt.copy(), pb.copy() 74 | 75 | pm = pb.lerp(pt, 0.3) 76 | 77 | rect = self.mouth_left.transformed_surface.get_rect() 78 | 79 | if cr.game.player.facing == RIGHT : 80 | rect.x, rect.y = pm.x - rect.w, pm.y 81 | else : 82 | rect.x, rect.y = pm 83 | 84 | rect.x += cr.camera.x 85 | rect.y += cr.camera.y 86 | 87 | return rect 88 | 89 | def check_events( self ): 90 | ... 91 | 92 | def render( self ): 93 | eye = self.eye_right 94 | mouth = self.mouth_right 95 | 96 | if cr.game.player.facing == LEFT: 97 | eye = self.eye_left 98 | mouth = self.mouth_left 99 | 100 | cr.screen.blit(eye.transformed_surface,self.eye_rect) 101 | cr.screen.blit(mouth.transformed_surface,self.mouth_rect) 102 | -------------------------------------------------------------------------------- /core/game.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | from core.player import Player 3 | import core.common_resources as cr 4 | from core.common_functions import * 5 | from core.constants import * 6 | from core.level import Level 7 | from core.inventory import Inventory 8 | 9 | 10 | class Game : 11 | 12 | def __init__( self ) : 13 | self.bg = SKY 14 | self.timer = now() 15 | self.camera_x_offset = 0 16 | self.camera_y_offset = 0 17 | 18 | s = cr.screen.get_size() 19 | # experimental 20 | 21 | self.level = Level() 22 | self.inventory = Inventory() 23 | 24 | p_rect = FRect(0, 0, 0, 0) 25 | p_rect.w, p_rect.h = self.level.player_size 26 | p_rect.center = Vector2(self.level.player_pos) 27 | self.player = Player(rect_convert_polygon(p_rect)) 28 | self.particles = [] 29 | self.maximum_particles = 1000 30 | if IS_WEB : 31 | self.maximum_particles = 1000 32 | self.gravity = 500 33 | 34 | p = self.player.center.copy() 35 | p.x = -int(p.x) + int(cr.screen.get_width() * 0.5) 36 | p.y = -int(p.y) + int(cr.screen.get_height() * 0.8) 37 | 38 | cr.camera.pos = p 39 | 40 | 41 | def reset_player( self, new_game: bool = False ) : 42 | l_locked = self.player.locked_swords_list 43 | l_diamonds = self.player.acquired_diamonds 44 | l_lives = self.player.lives 45 | 46 | p_rect = FRect(0, 0, 0, 0) 47 | p_rect.w, p_rect.h = self.level.player_size 48 | p_rect.center = Vector2(self.level.player_pos) 49 | self.player = Player(rect_convert_polygon(p_rect)) 50 | self.player.init() 51 | 52 | if not new_game : 53 | self.player.locked_swords_list = l_locked 54 | self.player.acquired_diamonds = l_diamonds 55 | self.player.lives = l_lives 56 | 57 | 58 | def init( self ) : 59 | self.player.init() 60 | self.level.init() 61 | r_mouth = list(cr.right_mouth_sprite_dict.values()) 62 | l_mouth = list(cr.left_mouth_sprite_dict.values()) 63 | mouth = r_mouth + l_mouth 64 | for sprite in mouth : 65 | sprite.transform_by_width(self.player.rect.w * 0.5) 66 | if sprite in l_mouth : 67 | sprite.flip(flip_x=True) 68 | 69 | r_eye = list(cr.right_eye_sprite_dict.values()) 70 | l_eye = list(cr.left_eye_sprite_dict.values()) 71 | eye = r_eye + l_eye 72 | for sprite in eye : 73 | sprite.transform_by_width(self.player.rect.w * 0.5) 74 | if sprite in l_eye : 75 | sprite.flip(flip_x=True) 76 | 77 | for name, sprite in list(cr.right_sword_dict.items()) + list(cr.left_sword_dict.items()) : 78 | m = 1.5 79 | if name == 'blood' : 80 | m = 1.75 81 | elif name == 'death' : 82 | m = 2.25 83 | 84 | sprite.transform_by_height(self.player.rect.h * m) 85 | 86 | for sprite in cr.right_sword_dict.values() : 87 | sprite.flip(True) 88 | 89 | 90 | def check_camera_and_peek( self ) : 91 | x_offset = 0.5 92 | y_offset = 0.8 93 | 94 | x = (self.player.sword.is_attacking and self.player.sword.name in ['light', 'evil']) 95 | 96 | go_center = self.player.is_jumping or \ 97 | (x and self.player.sword.attack_key == ATTACK_SPECIAL) or self.player.is_falling 98 | 99 | h_keys = cr.event_holder.held_keys 100 | 101 | cam_unit = cr.event_holder.delta_time * 0.5 102 | cam_release_unit = (1 - cr.event_holder.delta_time * 2) 103 | 104 | if cam_release_unit < 0 : 105 | cam_release_unit = 0 106 | if cam_release_unit > 1 : 107 | cam_release_unit = 1 108 | 109 | if K_a in h_keys : 110 | self.camera_x_offset += cam_unit 111 | if K_d in h_keys : 112 | self.camera_x_offset -= cam_unit 113 | 114 | if K_s in h_keys or go_center : 115 | self.camera_y_offset -= cam_unit 116 | 117 | if any(i in h_keys for i in [K_a, K_d, K_s]) : 118 | if self.camera_x_offset > 0.3 : 119 | self.camera_x_offset = 0.3 120 | elif self.camera_x_offset < -0.2 : 121 | self.camera_x_offset = -0.2 122 | if self.camera_y_offset < -0.6 : 123 | self.camera_y_offset = -0.6 124 | 125 | 126 | else : 127 | self.camera_x_offset *= cam_release_unit 128 | self.camera_y_offset *= cam_release_unit 129 | 130 | if abs(self.camera_x_offset) < 0.001 : 131 | self.camera_x_offset = 0 132 | 133 | if abs(self.camera_y_offset) < 0.001 : 134 | self.camera_y_offset = 0 135 | 136 | p = cr.game.player.center.copy() 137 | p.x = -int(p.x) + int(cr.screen.get_width() * (x_offset + self.camera_x_offset)) 138 | p.y = -int(p.y) + int(cr.screen.get_height() * (y_offset + self.camera_y_offset)) 139 | 140 | cr.camera.pos = p 141 | 142 | 143 | def check_particles( self ) : 144 | for particle, c in zip(self.particles[: :-1], range(len(self.particles))[: :-1]) : 145 | if particle.destroy_time is not None : 146 | if particle.destroy_time + particle.age < now() : 147 | self.particles.pop(c) 148 | 149 | elif particle.init_time + particle.absolute_age < now() : 150 | self.particles.pop(c) 151 | 152 | particle.check_events() 153 | 154 | 155 | @property 156 | def inner_box_list( self ) : 157 | return cr.inner_box_list 158 | 159 | 160 | @property 161 | def diamonds_text( self ) : 162 | return cr.font.render( 163 | f"Diamonds: {self.player.acquired_diamonds}/{self.level.total_diamonds}", False, 164 | "black") 165 | 166 | 167 | @property 168 | def time_text( self ) : 169 | return cr.font.render(f"{round(pg.time.get_ticks() / 1000 - self.timer, 1)}".zfill(5), 170 | False, (155, 0, 0)) 171 | 172 | 173 | @property 174 | def lives_text( self ) : 175 | return cr.font.render(f"lives: {self.player.lives}/{self.player.max_lives}", False, 176 | (0, 55, 0)) 177 | 178 | 179 | def check_dead_player( self ) : 180 | if K_p in cr.event_holder.released_keys or K_LCTRL in cr.event_holder.released_keys : 181 | self.reset_player() 182 | 183 | 184 | def check_events( self ) : 185 | cr.inner_box_list = self.level.inner_box_list 186 | gravity = self.gravity 187 | gravity *= cr.event_holder.delta_time 188 | self.level.check_events() 189 | self.player.gravity_request(gravity) 190 | 191 | if not self.player.is_dead : 192 | self.player.check_events() 193 | else : 194 | self.check_dead_player() 195 | 196 | self.inventory.check_events() 197 | self.check_particles() 198 | self.check_camera_and_peek() 199 | 200 | 201 | @property 202 | def dead_player_text( self ) : 203 | return cr.font.render("You are dead, press P to respawn!", True, "red") 204 | 205 | 206 | def render_dead_player_text( self ) : 207 | if self.player.lives == 0 : 208 | return 209 | 210 | surface = self.dead_player_text 211 | rect = surface.get_rect() 212 | rect.center = cr.screen.get_rect().center 213 | cr.screen.blit(surface, rect) 214 | 215 | 216 | def render( self ) : 217 | cr.screen.fill(self.bg) 218 | 219 | self.level.render() 220 | self.inventory.render() 221 | 222 | self.player.render() 223 | if self.player.is_dead : 224 | self.render_dead_player_text() 225 | 226 | text = self.diamonds_text 227 | rect = text.get_rect() 228 | rect.x = cr.screen.get_width() - rect.w 229 | cr.screen.blit(text, rect) 230 | 231 | time_text = self.time_text 232 | time_rect = time_text.get_rect() 233 | time_rect.x = cr.screen.get_width() - time_rect.w 234 | time_rect.y += rect.h * 1.3 235 | cr.screen.blit(time_text, time_rect) 236 | 237 | lives_text = self.lives_text 238 | lives_rect = lives_text.get_rect() 239 | lives_rect.x = cr.screen.get_width() - lives_rect.w 240 | lives_rect.y += time_rect.y + time_rect.h * 1.3 241 | cr.screen.blit(lives_text, lives_rect) 242 | -------------------------------------------------------------------------------- /core/inventory.py: -------------------------------------------------------------------------------- 1 | 2 | from core.constants import * 3 | from core.common_names import * 4 | import core.common_resources as cr 5 | 6 | 7 | class Inventory : 8 | 9 | def __init__( self ) : 10 | rect = FRect(0, 0, cr.screen.get_height() / 7, cr.screen.get_height() / 7) 11 | gap_unit = cr.screen.get_height() / 7 / 6 12 | self.inactive_color = "gray" 13 | self.active_color = Color(155, 30, 12) 14 | 15 | rect_0 = rect.copy() 16 | rect_0.y += rect.h * 0 + gap_unit * 0.0 17 | 18 | rect_1 = rect.copy() 19 | rect_1.y += rect.h * 1 + gap_unit * 1.0 20 | rect_2 = rect.copy() 21 | rect_2.y += rect.h * 2 + gap_unit * 2.0 22 | rect_3 = rect.copy() 23 | rect_3.y += rect.h * 3 + gap_unit * 3.0 24 | rect_4 = rect.copy() 25 | rect_4.y += rect.h * 4 + gap_unit * 4.0 26 | rect_5 = rect.copy() 27 | rect_5.y += rect.h * 5 + gap_unit * 5.0 28 | 29 | self.selected_surface = Surface((rect.w,rect.h)) 30 | self.selected_surface = self.selected_surface 31 | self.selected_surface.fill([180,150,150,125]) 32 | 33 | self.items = {"evil" : {"rect" : rect_0, "text" : "evergreen evil"}, 34 | "desire" : {"rect" : rect_1, "text" : "burning desire"}, 35 | "light" : {"rect" : rect_2, "text" : "the way of light"}, 36 | "hawk" : {"rect" : rect_3, "text" : "blue hawk"}, 37 | "blood" : {"rect" : rect_4, "text" : "blood lust"}, 38 | "death" : {"rect" : rect_5, "text" : "death wish"}} 39 | 40 | for key in self.items : 41 | item = self.items[key] 42 | item['sprite'] = Sprite(surface=cr.right_sword_dict[key].raw_surface) 43 | 44 | item['sprite'].transform_by_height(rect.h*0.8) 45 | item['locked_sprite'] = Sprite(surface=Surface((rect.w,rect.h))) 46 | 47 | gray_surface = pg.transform.grayscale(item['sprite'].transformed_surface) 48 | # gray_surface = item['sprite'].transformed_surface 49 | rect = item['locked_sprite'].raw_surface.get_rect() 50 | item['locked_sprite'].transform_by_height(rect.h) 51 | surface_rect = gray_surface.get_rect() 52 | surface_rect.center = rect.center 53 | 54 | 55 | item['locked_sprite'].transformed_surface.fill([0,0,0,150]) 56 | item['locked_sprite'].transformed_surface.blit(gray_surface,surface_rect) 57 | 58 | font = cr.smallest_font 59 | if IS_WEB: 60 | font = cr.little_font 61 | text = font.render(item['text'],False,"black",[155,133,168]) 62 | item['text_surface'] = text 63 | 64 | 65 | 66 | 67 | 68 | def check_events( self ) : 69 | ... 70 | 71 | 72 | def render( self ) : 73 | for key, value in self.items.items() : 74 | rect = value['rect'] 75 | color = self.inactive_color 76 | if cr.game.player.sword.name == key : 77 | color = self.active_color 78 | 79 | surface = value['sprite'].transformed_surface 80 | surface_rect = surface.get_rect() 81 | surface_rect.center = rect.center 82 | 83 | text_surface = value['text_surface'] 84 | text_rect = text_surface.get_rect() 85 | text_rect.x = 0 86 | text_rect.y = rect.y + rect.h 87 | cr.screen.blit(text_surface,text_rect) 88 | 89 | if not key in cr.game.player.locked_swords_list: 90 | cr.screen.blit(self.selected_surface,rect) 91 | cr.screen.blit(surface,surface_rect) 92 | else: 93 | cr.screen.blit(value['locked_sprite'].transformed_surface,rect) 94 | 95 | pg.draw.rect(cr.screen, color, rect, width=5) 96 | 97 | -------------------------------------------------------------------------------- /core/jelly_cube.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | import core.common_resources as cr 3 | from core.common_functions import * 4 | 5 | class JellyCube: 6 | def __init__(self,points:list[Vector2]): 7 | self.original_points = points 8 | 9 | self.min_height = self.o_rect.height * 0.5 10 | self.original_height = self.o_rect.height 11 | self.max_height = self.o_rect.height * 2 12 | self.angle_change_power = 0 13 | self.jelly_swing_scale = 1 14 | 15 | self.min_angle = -45 16 | self.max_angle = 45 17 | self.points = [i.copy() for i in points] 18 | self.top_points_angle = 0 19 | self.is_moving = False 20 | self.is_charging = False 21 | self.is_jumping = False 22 | self.is_falling = False 23 | self.is_shaking = False 24 | self.is_still = False 25 | 26 | @property 27 | def height_scale( self ): 28 | a = percent(self.max_height-self.min_height,self.o_rect.height-self.min_height) 29 | a*=0.01 30 | if a<0: 31 | a = 0 32 | if a>1: 33 | a = 1 34 | return a 35 | 36 | @property 37 | def angle_speed(self): 38 | return 60 * cr.event_holder.delta_time 39 | 40 | def check_events( self ): 41 | self.is_shaking = False 42 | 43 | self.check_angle_change() 44 | self.check_size_change() 45 | 46 | self.is_still = not self.is_moving and not self.is_shaking \ 47 | and not self.is_jumping and not self.is_falling 48 | 49 | def rotate_points( self): 50 | for point,o_point in zip(self.points[:2],self.original_points[:2]): 51 | new_point = rotate_point(self.o_rect.center,o_point,self.top_points_angle) 52 | point.x,point.y = new_point 53 | 54 | 55 | def check_size_change( self ): 56 | lys = [] 57 | 58 | if self.is_charging: 59 | m = 1 60 | elif self.is_jumping : 61 | m = -4 62 | elif self.is_falling : 63 | m = -2 64 | else: 65 | if self.o_rect.height > self.original_height: 66 | m = 4 67 | else: 68 | return 69 | 70 | 71 | for point in self.original_points[:2] : 72 | lys.append(point.copy()) 73 | 74 | point.y += 100 * cr.event_holder.delta_time * m 75 | self.rotate_points() 76 | 77 | any_collision = False 78 | for rect in cr.game.inner_box_list: 79 | if self.rect.colliderect(rect): 80 | any_collision = True 81 | break 82 | # for 83 | # revert 84 | if not self.min_height <= self.o_rect.height <= self.max_height \ 85 | or any_collision: 86 | for point, ly in zip(self.original_points[:2], lys) : 87 | point.x, point.y = ly.x, ly.y 88 | 89 | def check_angle_change( self ): 90 | 91 | if not self.is_moving: 92 | 93 | a = self.top_points_angle 94 | p = self.angle_change_power 95 | self.jelly_swing_scale -= cr.event_holder.delta_time * 0.7 96 | 97 | if a <= self.min_angle * self.jelly_swing_scale and p < 0: 98 | self.angle_change_power = abs(self.angle_change_power) 99 | 100 | if a >= self.max_angle * self.jelly_swing_scale and p > 0: 101 | self.angle_change_power = - abs(self.angle_change_power) 102 | 103 | if self.jelly_swing_scale < 0.01: 104 | self.angle_change_power = 0 105 | self.top_points_angle = 0 106 | self.rotate_points() 107 | 108 | if self.angle_change_power != 0: 109 | self.top_points_angle += self.angle_change_power 110 | self.is_shaking = True and not self.is_moving 111 | 112 | if self.top_points_angle < self.min_angle: 113 | self.top_points_angle = self.min_angle 114 | if self.top_points_angle > self.max_angle: 115 | self.top_points_angle = self.max_angle 116 | 117 | self.rotate_points() 118 | 119 | 120 | 121 | def move( self,value:Vector2 ): 122 | hs = self.height_scale 123 | if hs < 0.5: 124 | hs = 0.5 125 | 126 | self.is_moving = True 127 | if value.x > 0: 128 | self.angle_change_power = - self.angle_speed * 10 * hs 129 | self.jelly_swing_scale = hs 130 | if value.x < 0: 131 | self.angle_change_power = self.angle_speed * 10 * hs 132 | self.jelly_swing_scale = hs 133 | 134 | self.rotate_points() 135 | 136 | 137 | @property 138 | def rect( self ) : 139 | x_list = [i.x for i in self.points] 140 | y_list = [i.y for i in self.points] 141 | min_x = min(x_list) 142 | max_x = max(x_list) 143 | min_y = min(y_list) 144 | max_y = max(y_list) 145 | 146 | return FRect(min_x, min_y, max_x - min_x, max_y - min_y) 147 | 148 | 149 | @property 150 | def o_rect( self ) : 151 | x_list = [i.x for i in self.original_points] 152 | y_list = [i.y for i in self.original_points] 153 | min_x = min(x_list) 154 | max_x = max(x_list) 155 | min_y = min(y_list) 156 | max_y = max(y_list) 157 | 158 | return FRect(min_x, min_y, max_x - min_x, max_y - min_y) 159 | 160 | def sync_o_points_by_point( self,index ): 161 | point = self.points[index] 162 | target = self.original_points[index] 163 | px,py = point 164 | tx,ty = target 165 | dx,dy = tx-px,ty-py 166 | 167 | for point in self.original_points: 168 | point.x-=dx 169 | point.y-=dy 170 | 171 | 172 | 173 | @property 174 | def center( self ) : 175 | return Vector2(self.rect.center) 176 | 177 | 178 | @center.setter 179 | def center( self, new_center: Vector2 ) : 180 | last_center = Vector2(self.rect.center) 181 | diff = Vector2(new_center.x - last_center.x, new_center.y - last_center.y) 182 | 183 | for point in self.points : 184 | point.x += diff.x 185 | point.y += diff.y 186 | 187 | self.sync_o_points_by_point(2) 188 | 189 | -------------------------------------------------------------------------------- /core/ldtk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/core/ldtk/__init__.py -------------------------------------------------------------------------------- /core/ldtk/common_functions.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | 3 | def find_level(world:dict,name:str): 4 | for level in world['levels']: 5 | if level['identifier'] == name: 6 | return level 7 | 8 | def find_layer_instance(level:dict,name:str): 9 | for layer in level['layerInstances']: 10 | if layer['__identifier'] == name: 11 | return layer 12 | 13 | def find_entity(entities:dict,name:str): 14 | for entity in entities['entityInstances']: 15 | if entity['__identifier'] == name: 16 | return entity 17 | 18 | def find_in(list_:list,identifier:str): 19 | for item in list_: 20 | if item['__identifier'] == identifier: 21 | return item -------------------------------------------------------------------------------- /core/ldtk/gun.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/core/ldtk/gun.py -------------------------------------------------------------------------------- /core/ldtk/tile_layer.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/core/ldtk/tile_layer.py -------------------------------------------------------------------------------- /core/ldtk/tileset.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | from core.sprite import Sprite 3 | 4 | class Tileset: 5 | def __init__(self,path:str,grid_size:float,scale:float=1): 6 | self.path = path 7 | self.surface = pg.image.load(self.path) 8 | # self.surface.set_colorkey("black") 9 | self.grid_size = grid_size 10 | self.tiles:list[Sprite] = [] 11 | self.scale = scale 12 | self.create_tiles() 13 | 14 | def init( self ): 15 | for tile in self.tiles: 16 | tile.transformed_surface.set_colorkey("black") 17 | 18 | def create_tiles( self ): 19 | w = h = self.grid_size 20 | rect = FRect(0,0,w,h) 21 | 22 | w_index = 0 23 | h_index = 0 24 | base_tile = Surface((w,h)) 25 | counter = 0 26 | 27 | while True: 28 | rect.x = w_index * w 29 | rect.y = h_index * h 30 | tile = base_tile.copy() 31 | tile.blit(self.surface,[0,0],rect) 32 | sprite = Sprite(surface=tile) 33 | 34 | if not rect.x >= self.surface.get_rect().w: 35 | self.tiles.append(sprite) 36 | counter += 1 37 | 38 | w_index += 1 39 | if not self.surface.get_rect().contains(rect): 40 | w_index = 0 41 | h_index += 1 42 | 43 | if rect.y > self.surface.get_rect().h: 44 | break 45 | 46 | 47 | -------------------------------------------------------------------------------- /core/level.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | from core.ldtk.common_functions import * 3 | import core.common_resources as cr 4 | from core.constants import * 5 | from core.ldtk.tileset import Tileset 6 | 7 | 8 | class Level : 9 | 10 | def __init__( self, scale: float = 2 ) : 11 | test_entities = find_layer_instance(find_level(cr.world, 'Level_0'), 'Entities') 12 | test_level = find_layer_instance(find_level(cr.world, 'Level_0'), 'Tiles_grid') 13 | collidables = find_layer_instance(find_level(cr.world, 'Level_0'), 'Collidables') 14 | collision_boxs = find_layer_instance(find_level(cr.world, 'Level_0'), 'CollisionBoxs') 15 | water_boxs = find_layer_instance(find_level(cr.world, 'Level_0'), 'WaterBoxs') 16 | lava_boxs = find_layer_instance(find_level(cr.world, 'Level_0'), 'LavaBoxs') 17 | upgradables = find_layer_instance(find_level(cr.world, 'Level_0'), 'Upgradables') 18 | 19 | 20 | self.lowest_tile = None 21 | 22 | self.total_diamonds = 0 23 | self.grid_size = test_level['__gridSize'] 24 | tileset_path = cr.levels_root + test_level['__tilesetRelPath'] 25 | 26 | self.player = find_entity(test_entities, 'Player') 27 | self.player_pos = [self.player['px'][0] * scale, self.player['px'][1] * scale] 28 | self.player_size = self.player['width'] * scale * 0.8, self.player['height'] * scale * 0.8 29 | 30 | self.collision_box_map = {} 31 | 32 | self.tileset = Tileset(tileset_path, self.grid_size, scale) 33 | self.tiles = test_level['autoLayerTiles'] 34 | 35 | for tile in self.tiles : 36 | tile['px'][0] *= scale 37 | tile['px'][1] *= scale 38 | 39 | for sprite in self.tileset.tiles : 40 | sprite.transform_by_rel(scale, scale) 41 | 42 | self._inner_box_list = [] 43 | self._water_box_list = [] 44 | self._lava_box_list = [] 45 | 46 | for entity in collidables['entityInstances'] : 47 | if entity['__identifier'] != 'Collidable' : 48 | continue 49 | rect = FRect(entity['px'][0] * scale, entity['px'][1] * scale, entity['width'] * scale, 50 | entity['height'] * scale) 51 | 52 | if self.lowest_tile is None: 53 | self.lowest_tile = rect.y + rect.h 54 | 55 | if rect.y + rect.h > self.lowest_tile: 56 | self.lowest_tile = rect.y + rect.h 57 | 58 | 59 | self._inner_box_list.append(rect) 60 | 61 | for entity in collision_boxs['entityInstances'] : 62 | if entity['__identifier'] != 'CollisionBox' : 63 | continue 64 | rect = FRect(entity['px'][0] * scale, entity['px'][1] * scale, entity['width'] * scale, 65 | entity['height'] * scale) 66 | 67 | self.collision_box_map[len(self.collision_box_map)] = {"rect" : rect, 68 | "collidables" : [], "tiles" : [],"water_boxs":[],"lava_boxs":[]} 69 | 70 | 71 | for entity in water_boxs['entityInstances'] : 72 | if entity['__identifier'] != 'WaterBox' : 73 | continue 74 | rect = FRect(entity['px'][0] * scale, entity['px'][1] * scale, entity['width'] * scale, 75 | entity['height'] * scale) 76 | 77 | self._water_box_list.append(rect) 78 | 79 | for entity in lava_boxs['entityInstances'] : 80 | if entity['__identifier'] != 'LavaBox' : 81 | continue 82 | rect = FRect(entity['px'][0] * scale, entity['px'][1] * scale, entity['width'] * scale, 83 | entity['height'] * scale) 84 | 85 | self._lava_box_list.append(rect) 86 | 87 | self.diamonds = [] 88 | self.sword_upgradables = {} 89 | 90 | for entity in upgradables['entityInstances'] : 91 | if entity['__identifier'] != 'Upgradable' : 92 | continue 93 | 94 | name = find_in(entity['fieldInstances'],'String')['__value'] 95 | 96 | if name is None: 97 | continue 98 | 99 | rect = FRect(entity['px'][0] * scale, entity['px'][1] * scale, entity['width'] * scale, 100 | entity['height'] * scale) 101 | rect.x -= rect.w / 2 102 | rect.y -= rect.h / 2 103 | 104 | if name == "diamond": 105 | self.diamonds.append({"rect":rect, "is_taken":False, "angle":0}) 106 | else: 107 | self.sword_upgradables[name] = {"rect":rect, "is_taken":False, "angle":0} 108 | 109 | self.total_diamonds = len(self.diamonds) 110 | 111 | for key in self.collision_box_map: 112 | box = self.collision_box_map[key]['rect'] 113 | for tile,c in zip(self.tiles[::-1],range(len(self.tiles))[::-1]) : 114 | surface = self.tileset.tiles[tile['t']].transformed_surface 115 | px = list(tile['px']) 116 | size = surface.get_size() 117 | rect = FRect(px[0], px[1], size[0], size[1]) 118 | 119 | if box.colliderect(rect) : 120 | self.collision_box_map[key]['tiles'].append(tile) 121 | self.tiles.pop(c) 122 | continue 123 | 124 | for key in self.collision_box_map : 125 | box = self.collision_box_map[key]['rect'] 126 | for inner_box, c in zip(self._inner_box_list[: :-1], range(len(self._inner_box_list))[: :-1]) : 127 | if box.colliderect(inner_box) : 128 | self.collision_box_map[key]['collidables'].append(inner_box) 129 | continue 130 | 131 | for key in self.collision_box_map : 132 | box = self.collision_box_map[key]['rect'] 133 | for inner_box, c in zip(self._water_box_list[: :-1], range(len(self._water_box_list))[: :-1]) : 134 | if box.colliderect(inner_box) : 135 | self.collision_box_map[key]['water_boxs'].append(inner_box) 136 | continue 137 | 138 | for key in self.collision_box_map : 139 | box = self.collision_box_map[key]['rect'] 140 | for inner_box, c in zip(self._lava_box_list[: :-1], range(len(self._lava_box_list))[: :-1]) : 141 | if box.colliderect(inner_box) : 142 | self.collision_box_map[key]['lava_boxs'].append(inner_box) 143 | 144 | continue 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | def init( self ) : 153 | self.tileset.init() 154 | cr.diamond.transform_by_height(self.grid_size) 155 | 156 | def check_events( self ) : 157 | 158 | 159 | 160 | for name,values in self.sword_upgradables.items(): 161 | values['angle'] += cr.event_holder.delta_time * 45 162 | 163 | if name in cr.game.player.locked_swords_list: 164 | if cr.game.player.rect.colliderect(values['rect']): 165 | cr.game.player.locked_swords_list.remove(name) 166 | values['is_taken'] = True 167 | 168 | 169 | for values in self.diamonds: 170 | if values['is_taken']: 171 | continue 172 | 173 | values['angle'] += cr.event_holder.delta_time * - 65 174 | 175 | if cr.game.player.rect.colliderect(values['rect']) : 176 | cr.game.player.acquired_diamonds += 1 177 | values['is_taken'] = True 178 | 179 | 180 | # Bad usage of words, box means collidable here 181 | @property 182 | def inner_box_list( self ): 183 | cp = cr.camera 184 | scr_rect = FRect(cr.screen.get_rect()) 185 | scr_rect.x -= cp.x 186 | scr_rect.y -= cp.y 187 | 188 | inner_boxs = [] 189 | 190 | for box in self.collision_box_map: 191 | rect = self.collision_box_map[box]['rect'].copy() 192 | if rect.colliderect(scr_rect): 193 | inner_boxs.extend(self.collision_box_map[box]['collidables']) 194 | 195 | return inner_boxs 196 | 197 | @property 198 | def water_colliders_list( self ): 199 | cp = cr.camera 200 | scr_rect = FRect(cr.screen.get_rect()) 201 | scr_rect.x -= cp.x 202 | scr_rect.y -= cp.y 203 | 204 | water_colliders = [] 205 | 206 | for box in self.collision_box_map : 207 | rect = self.collision_box_map[box]['rect'].copy() 208 | if rect.colliderect(scr_rect) : 209 | water_colliders.extend(self.collision_box_map[box]['water_boxs']) 210 | 211 | return water_colliders 212 | 213 | 214 | @property 215 | def lava_colliders_list( self ) : 216 | cp = cr.camera 217 | scr_rect = FRect(cr.screen.get_rect()) 218 | scr_rect.x -= cp.x 219 | scr_rect.y -= cp.y 220 | 221 | lava_colliders = [] 222 | 223 | for box in self.collision_box_map : 224 | rect = self.collision_box_map[box]['rect'].copy() 225 | if rect.colliderect(scr_rect) : 226 | lava_colliders.extend(self.collision_box_map[box]['lava_boxs']) 227 | 228 | return lava_colliders 229 | 230 | def render_upgradables( self ): 231 | for name,values in self.sword_upgradables.items(): 232 | if name in cr.game.player.locked_swords_list or name == 'diamond': 233 | surface = cr.right_sword_dict[name].transformed_surface 234 | surface = pg.transform.rotate(surface,values['angle']) 235 | surface_rect = surface.get_rect() 236 | surface_rect.center = values['rect'].center 237 | surface_rect.x += cr.camera.x 238 | surface_rect.y += cr.camera.y 239 | rect = values['rect'].copy() 240 | rect.x += cr.camera.x 241 | rect.y += cr.camera.y 242 | 243 | 244 | pg.draw.rect(cr.screen,"gold",rect,width=5) 245 | 246 | cr.screen.blit(surface,surface_rect) 247 | 248 | for values in self.diamonds: 249 | if not values['is_taken']: 250 | surface = cr.diamond.transformed_surface 251 | surface = pg.transform.rotate(surface, values['angle']) 252 | surface_rect = surface.get_rect() 253 | surface_rect.center = values['rect'].center 254 | surface_rect.x += cr.camera.x 255 | surface_rect.y += cr.camera.y 256 | rect = values['rect'].copy() 257 | rect.x += cr.camera.x 258 | rect.y += cr.camera.y 259 | 260 | 261 | cr.screen.blit(surface, surface_rect) 262 | 263 | def render( self ) : 264 | cp = cr.camera.pos 265 | 266 | rendered_tiles = 0 267 | for box in self.collision_box_map : 268 | rect = self.collision_box_map[box]['rect'].copy() 269 | rect.x += cp.x 270 | rect.y += cp.y 271 | scr_rect = FRect(cr.screen.get_rect()) 272 | if scr_rect.colliderect(rect) : 273 | for tile in self.collision_box_map[box]['tiles']: 274 | surface = self.tileset.tiles[tile['t']].transformed_surface 275 | px = list(tile['px']) 276 | px = [cp.x + px[0], cp.y + px[1]] 277 | cr.screen.blit(surface, px) 278 | rendered_tiles += 1 279 | 280 | 281 | self.render_upgradables() 282 | -------------------------------------------------------------------------------- /core/particle.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | from core.common_functions import * 3 | import core.common_resources as cr 4 | from core.constants import * 5 | 6 | """ 7 | this needs to be optimized!! 8 | some optimization is done. 9 | it still needs to be faster 10 | my guess is that I should use numpy 11 | """ 12 | 13 | 14 | class Particle : 15 | 16 | def __init__( self, pos: Vector2, size: float, angle: int = 0, age: float = 5, 17 | color: Optional[Color] = None, anti_gravity=False,anti_collision=False ) : 18 | 19 | self.pos = pos 20 | self.size = size 21 | self.color = color 22 | if self.color is None : 23 | self.color = random_color() 24 | self.anti_gravity = anti_gravity 25 | self.anti_collision = anti_collision 26 | self.angle = angle 27 | self.gravity = 0 28 | self.power = 5 29 | self.power_decrease_scale = 0 30 | self.age = age 31 | self.init_time = now() 32 | self.absolute_age = age * 2.5 33 | self.destroy_time = None 34 | 35 | 36 | @property 37 | def top_pos( self ) : 38 | pos = self.pos.copy() 39 | x = 1000 - cr.event_holder.final_fps 40 | if x < 0 : 41 | x = 0 42 | 43 | x += 50 44 | 45 | pos.y -= x 46 | return pos 47 | 48 | 49 | 50 | @property 51 | def target_point( self ) : 52 | return rotate_point(self.pos, self.top_pos, self.angle) 53 | 54 | 55 | @property 56 | def rect( self ) : 57 | rect = FRect(0, 0, self.size, self.size) 58 | rect.center = self.pos 59 | return rect 60 | 61 | 62 | def check_events( self ) : 63 | if self.destroy_time is not None : 64 | return 65 | 66 | self.gravity_tick() 67 | self.move() 68 | 69 | 70 | def move( self ) : 71 | if self.power <= 0 : 72 | return 73 | last_center = self.pos.copy() 74 | 75 | delta_lerp = cr.event_holder.delta_time*0.1*self.power 76 | self.power -= cr.event_holder.delta_time*self.power_decrease_scale 77 | if delta_lerp>1: 78 | delta_lerp = 1 79 | 80 | self.pos = self.pos.lerp(self.target_point,delta_lerp) 81 | if self.anti_collision: 82 | return 83 | any_ = False 84 | for box in cr.game.inner_box_list : 85 | if box.colliderect(self.rect) : 86 | any_ = True 87 | break 88 | 89 | if any_ : 90 | self.pos = last_center 91 | self.angle -= random.randint(-50,50) 92 | 93 | 94 | def render( self ) : 95 | color = self.color 96 | if self.destroy_time is not None : 97 | color = GRAY 98 | 99 | rect = Rect(self.rect.x + cr.camera.x, self.rect.y + cr.camera.y, self.rect.w, self.rect.h) 100 | 101 | pg.draw.rect(cr.screen, color, rect) 102 | 103 | 104 | def gravity_tick( self ) : 105 | if self.anti_gravity: 106 | return 107 | 108 | last_center = self.pos 109 | center = last_center.copy() 110 | 111 | center.y += self.gravity 112 | self.gravity = 0 113 | self.pos = center 114 | if self.anti_collision: 115 | return 116 | 117 | any_ = False 118 | for box in cr.game.inner_box_list : 119 | if box.colliderect(self.rect) : 120 | any_ = True 121 | 122 | 123 | if any_ : 124 | if self.power <= 0 : 125 | self.destroy_time = now() 126 | self.pos = last_center 127 | 128 | 129 | def gravity_request( self, gravity: float ) : 130 | self.gravity = gravity 131 | -------------------------------------------------------------------------------- /core/player.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | import core.common_resources as cr 3 | from core.constants import * 4 | from core.jelly_cube import JellyCube 5 | from core.particle import Particle 6 | from core.face import Face 7 | from core.common_functions import * 8 | from core.sword import Sword 9 | 10 | 11 | class Player(JellyCube) : 12 | 13 | def __init__( self, points: list[Vector2] ) : 14 | self.init_points = points.copy() 15 | 16 | self.color = WHITE 17 | self.border_color = BLACK.lerp(WHITE, 0.5) 18 | self.lives = 5 19 | self.max_lives = 5 20 | self.border_size = 1 21 | if self.border_size < 1 : 22 | self.border_size = 1 23 | 24 | self.is_dead = False 25 | self.locked_swords_list = ['hawk','light','desire','death','evil'] 26 | self.acquired_diamonds = 0 27 | 28 | self.anti_gravity = False 29 | self.face = Face() 30 | self.sword = Sword() 31 | self.move_speed = 300 32 | self.gravity = 0 33 | self.jump_power = 0 34 | self.did_jump = False 35 | self.max_jump_power = -2500 36 | self.min_jump_power = -1000 37 | self.remaining_jump_power = 0 38 | self.is_wet = False 39 | self.on_fire = False 40 | 41 | # This is calculated only after a particle becomes inactive 42 | self.particles_age = 4 43 | self.facing = RIGHT 44 | self.movement_request = Vector2(0, 0) 45 | 46 | super(Player, self).__init__(points) 47 | 48 | @property 49 | def is_flying( self ): 50 | return self.sword.name == 'evil' and self.sword.is_attacking 51 | 52 | @property 53 | def particle_delta_count( self ) : 54 | delta = cr.event_holder.delta_time 55 | if delta <= 0.01 : 56 | delta = 0.01 57 | count = int(delta * 200) 58 | return count 59 | 60 | 61 | @property 62 | def maximum_particles( self ) : 63 | return cr.game.maximum_particles 64 | 65 | 66 | @property 67 | def particles( self ) : 68 | return cr.game.particles 69 | 70 | 71 | def init( self ) : 72 | self.face.init() 73 | self.sword.init() 74 | 75 | 76 | @property 77 | def jump_power_per_second( self ) : 78 | return self.min_jump_power * cr.event_holder.delta_time 79 | 80 | 81 | def gravity_tick( self ) : 82 | if self.anti_gravity : 83 | return 84 | 85 | water_m = 1 86 | if self.is_wet or self.is_flying: 87 | water_m = 0.4 88 | 89 | 90 | 91 | movement = Vector2(0, self.gravity * water_m) 92 | any_ = self.is_colliding(movement) 93 | if not any_ : 94 | self.center = Vector2(self.center.x + movement.x, self.center.y + movement.y) 95 | else : 96 | c = self.center 97 | # c.y = (any_.y - self.o_rect.h / 2 ) - 0 98 | self.center = c 99 | self.is_falling = False 100 | self.did_jump = False 101 | 102 | self.gravity = 0 103 | 104 | 105 | def check_lava( self ) -> bool: 106 | self.on_fire = False 107 | for lavabox in cr.game.level.lava_colliders_list : 108 | if self.rect.colliderect(lavabox) : 109 | if self.sword.name != 'desire': 110 | self.kill() 111 | self.face.update_face(new_eye="dead",new_mouth="talk_1") 112 | # Bad programming 113 | return True 114 | self.on_fire = True 115 | 116 | return False 117 | 118 | def gravity_request( self, gravity: float ) : 119 | self.gravity = gravity 120 | for particle in self.particles : 121 | particle.gravity_request(gravity) 122 | 123 | def kill( self ): 124 | self.lives -= 1 125 | self.is_dead = True 126 | 127 | def move( self, value: Vector2, anti_gravity: bool = False ) : 128 | self.anti_gravity = anti_gravity 129 | if self.anti_gravity : 130 | self.is_falling = False 131 | 132 | water_m = 1 133 | 134 | if self.sword.name == 'evil': 135 | water_m = 1.4 136 | if self.sword.name == 'desire': 137 | water_m = 1 138 | if self.is_flying: 139 | water_m = 1 140 | 141 | if value.y < 0: # above 142 | water_m *= 2 143 | if self.is_jumping: 144 | water_m = 0 145 | 146 | if value.y > 0: # below 147 | water_m *= 0.3 148 | 149 | 150 | 151 | throw = (self.sword.last_attack_type in THROW_TYPES and ( 152 | self.sword.is_attacking or self.sword.is_retrieving)) 153 | 154 | if value.x < 0 and not throw : 155 | self.facing = LEFT 156 | elif value.x > 0 and not throw : 157 | self.facing = RIGHT 158 | 159 | self.manage_movement_particles(value) 160 | movement = Vector2(value.x * water_m * cr.event_holder.delta_time, 161 | value.y * water_m * cr.event_holder.delta_time) 162 | any_ = self.is_colliding(movement) 163 | 164 | if any_ : 165 | return False 166 | else : 167 | c = self.center 168 | c.x += movement.x 169 | c.y += movement.y 170 | self.center = c 171 | 172 | self.is_moving = True 173 | super(Player, self).move(value) 174 | return True 175 | 176 | 177 | def jump_request( self ) : 178 | self.is_jumping = True 179 | self.remaining_jump_power = self.jump_power 180 | 181 | 182 | def check_color_update( self ) : 183 | if self.is_shaking : 184 | new_color = random_color() 185 | val = cr.event_holder.delta_time * 15 186 | if val > 0.10 : 187 | val = 0.10 188 | 189 | self.color = self.color.lerp(new_color, val) 190 | 191 | else : 192 | base_color = WHITE 193 | if self.sword.name == 'blood' : 194 | base_color = RED 195 | elif self.sword.name == 'light' : 196 | base_color = Color(255, 255, 0) 197 | elif self.sword.name == 'evil' : 198 | base_color = Color(0, 255, 0) 199 | elif self.sword.name == 'death' : 200 | base_color = Color("black") 201 | elif self.sword.name == 'desire' : 202 | base_color = Color(255, 155, 0) 203 | elif self.sword.name == 'hawk' : 204 | base_color = Color(0, 0, 255) 205 | 206 | lerp_value = cr.event_holder.delta_time * 2.5 207 | if lerp_value > 1 : 208 | lerp_value = 1 209 | self.color = self.color.lerp(base_color, lerp_value) 210 | 211 | 212 | def check_jump( self ) : 213 | water_m = 1 214 | if self.is_wet : 215 | water_m = 0.5 216 | 217 | if self.sword.name == 'evil' : 218 | water_m = 2 219 | 220 | last_center = self.center 221 | movement = Vector2(0, self.remaining_jump_power * water_m * cr.event_holder.delta_time) 222 | any_ = self.is_colliding(movement) 223 | 224 | if any_ : 225 | self.center = last_center 226 | self.remaining_jump_power = 0 227 | self.is_jumping = False 228 | self.is_falling = True 229 | else : 230 | c = self.center 231 | c.x += movement.x 232 | c.y += movement.y 233 | self.center = c 234 | self.remaining_jump_power -= ( 235 | self.remaining_jump_power * 7 * cr.event_holder.delta_time) 236 | 237 | if abs(self.remaining_jump_power) < abs(cr.game.gravity * 0.5) : 238 | self.remaining_jump_power = 0 239 | 240 | if abs(self.remaining_jump_power) < abs(cr.game.gravity * 1) : 241 | self.is_jumping = False 242 | self.is_falling = True 243 | 244 | 245 | def check_movements( self ) : 246 | h_keys = cr.event_holder.held_keys 247 | p_keys = cr.event_holder.pressed_keys 248 | r_keys = cr.event_holder.released_keys 249 | 250 | if self.anti_gravity : 251 | return 252 | 253 | if K_RIGHT in h_keys : 254 | self.move(Vector2(self.move_speed, 0)) 255 | 256 | if K_LEFT in h_keys : 257 | self.move(Vector2(-self.move_speed, 0)) 258 | 259 | if self.is_wet or self.on_fire or self.is_flying : 260 | if K_DOWN in h_keys : 261 | self.move(Vector2(0, self.move_speed)) 262 | if K_UP in h_keys : 263 | self.move(Vector2(0, -self.move_speed)) 264 | 265 | 266 | 267 | if not self.did_jump : 268 | if K_SPACE in p_keys : 269 | self.jump_power = self.min_jump_power 270 | self.is_charging = True 271 | 272 | if self.is_charging : 273 | if K_SPACE in h_keys and abs(self.jump_power) < abs(self.max_jump_power) : 274 | self.jump_power += self.jump_power_per_second 275 | if abs(self.jump_power) > abs(self.max_jump_power) : 276 | self.jump_power = self.max_jump_power 277 | 278 | if K_SPACE in r_keys : 279 | self.did_jump = True 280 | self.is_charging = False 281 | self.jump_request() 282 | 283 | 284 | def dev_sword_control( self ) : 285 | cant_do = self.sword.is_attacking or self.sword.is_retrieving 286 | 287 | p_keys = cr.event_holder.pressed_keys 288 | if not cant_do : 289 | if K_1 in p_keys and 'evil' not in self.locked_swords_list : 290 | self.sword.update_sword('evil') 291 | if K_2 in p_keys and 'desire' not in self.locked_swords_list : 292 | self.sword.update_sword('desire') 293 | if K_3 in p_keys and 'light' not in self.locked_swords_list : 294 | self.sword.update_sword('light') 295 | if K_4 in p_keys and 'hawk' not in self.locked_swords_list : 296 | self.sword.update_sword('hawk') 297 | if K_5 in p_keys and 'blood' not in self.locked_swords_list : 298 | self.sword.update_sword('blood') 299 | if K_6 in p_keys and 'death' not in self.locked_swords_list : 300 | self.sword.update_sword('death') 301 | 302 | 303 | def is_colliding( self, movement: Vector2 ) -> FRect or bool : 304 | n_rect = self.o_rect 305 | n_rect.x += movement.x 306 | n_rect.y += movement.y 307 | 308 | any_: Optional[FRect, bool] = False 309 | for box in cr.game.inner_box_list : 310 | if box.colliderect(n_rect) : 311 | any_ = box 312 | break 313 | 314 | return any_ 315 | 316 | @property 317 | def jump_power_percent( self ): 318 | return percent(abs(self.max_jump_power) - abs(self.min_jump_power), 319 | abs(self.jump_power) - abs(self.min_jump_power)) 320 | 321 | def check_events( self ) : 322 | if self.center.y > cr.game.level.lowest_tile + 500: 323 | self.kill() 324 | self.face.update_face(new_eye="dead", new_mouth="talk_1") 325 | return 326 | 327 | self.is_wet = False 328 | for waterbox in cr.game.level.water_colliders_list : 329 | if self.rect.colliderect(waterbox) : 330 | self.is_wet = True 331 | break 332 | 333 | if self.check_lava(): 334 | return 335 | 336 | self.check_movements() 337 | self.check_jump() 338 | self.gravity_tick() 339 | self.manage_shake_particles() 340 | self.manage_jump_particles() 341 | self.manage_fall_particles() 342 | self.update_face() 343 | self.dev_sword_control() 344 | self.check_color_update() 345 | self.anti_gravity = False 346 | self.sword.check_events() 347 | 348 | super(Player, self).check_events() 349 | if not self.is_still : 350 | self.sword.rotate_sword() 351 | 352 | self.is_moving = False 353 | 354 | 355 | def render_debug( self ) : 356 | rect = self.rect 357 | rect.x += cr.camera.x 358 | rect.y += cr.camera.y 359 | pg.draw.rect(cr.screen, "purple", rect) 360 | 361 | o_rect = self.o_rect 362 | o_rect.x += cr.camera.x 363 | o_rect.y += cr.camera.y 364 | pg.draw.rect(cr.screen, "yellow", o_rect) 365 | 366 | 367 | def render( self ) : 368 | if cr.event_holder.should_render_debug : 369 | self.render_debug() 370 | 371 | for particle in self.particles : 372 | particle.render() 373 | 374 | p = [i.copy() for i in self.points] 375 | move_points(p, cr.camera.x, cr.camera.y) 376 | 377 | pg.draw.polygon(cr.screen, self.color, p) 378 | # pg.draw.polygon(cr.screen, self.color.lerp("red",0.5), self.original_points) 379 | 380 | pg.draw.polygon(cr.screen, self.border_color, p, width=self.border_size) 381 | 382 | 383 | if self.is_charging: 384 | color = 'green' 385 | rect = self.rect 386 | rect.w = rect.w * 0.2 387 | rect.x -= rect.w * 1.3 388 | lh = rect.h 389 | rect.h = rect.h * self.jump_power_percent * 0.01 390 | rect.y += lh - rect.h 391 | 392 | rect.x += cr.camera.x 393 | rect.y += cr.camera.y 394 | 395 | if self.facing == LEFT: 396 | rect.x += self.rect.w * 1.3 + (rect.w) 397 | 398 | pg.draw.rect(cr.screen,color,rect) 399 | 400 | 401 | 402 | 403 | self.face.render() 404 | self.sword.render() 405 | 406 | 407 | def add_particle( self, source: Vector2, angle, size ) : 408 | # return 409 | 410 | if len(self.particles) > self.maximum_particles : 411 | return 412 | 413 | age = random.uniform(0, 1) 414 | 415 | anti_gravity = anti_collision = False 416 | if IS_WEB : 417 | anti_gravity = anti_collision = True 418 | 419 | particle = Particle(source, size, angle, age, None, anti_gravity, anti_collision) 420 | 421 | particle.power = 10 422 | if IS_WEB : 423 | particle.power = 3 # particle.power += random.uniform(particle.power*-0.8,particle.power) 424 | 425 | particle.power_decrease_scale = 2 426 | if self.is_wet : 427 | particle.power_decrease_scale *= 6 428 | 429 | self.particles.append(particle) 430 | 431 | 432 | def manage_movement_particles( self, value ) : 433 | for _ in range(self.particle_delta_count) : 434 | angle = random.randint(30, 60) 435 | 436 | if value.x > 0 : 437 | angle = - angle 438 | 439 | size = random.uniform(1, 4) 440 | 441 | self.add_particle(Vector2(self.center), angle, size) 442 | 443 | 444 | def manage_shake_particles( self ) : 445 | if not self.is_shaking : 446 | return 447 | for _ in range(self.particle_delta_count) : 448 | angle = random.randint(15, 60) 449 | if random.randint(0, 1) : 450 | angle = - angle 451 | 452 | size = random.uniform(1, 4) 453 | 454 | self.add_particle(Vector2(self.center), angle, size) 455 | 456 | 457 | def manage_jump_particles( self ) : 458 | if not self.is_jumping : 459 | return 460 | for _ in range(self.particle_delta_count) : 461 | angle = random.randint(135, 225) 462 | if random.randint(0, 1) : 463 | angle = - angle 464 | 465 | size = random.uniform(1, 4) 466 | 467 | self.add_particle(Vector2(self.center), angle, size) 468 | 469 | 470 | def teleport( self, to: Vector2 ) : 471 | l_center = self.center 472 | self.center = to 473 | if self.is_colliding(Vector2(0, 0)) : 474 | self.center = l_center 475 | print("Could not teleport, invalid location.") 476 | 477 | 478 | def manage_fall_particles( self ) : 479 | if not self.is_falling : 480 | return 481 | 482 | for _ in range(self.particle_delta_count) : 483 | angle = random.randint(-45, 45) 484 | if random.randint(0, 1) : 485 | angle = - angle 486 | 487 | size = random.uniform(1, 4) 488 | 489 | self.add_particle(Vector2(self.center), angle, size) 490 | 491 | 492 | def update_face( self ) : 493 | new_eye = None 494 | new_mouth = None 495 | # EYES 496 | if self.is_charging : 497 | new_eye = 'silly' 498 | elif self.is_falling : 499 | new_eye = "jump" 500 | elif self.is_moving : 501 | new_eye = "angry" 502 | elif self.is_shaking and not self.is_jumping : 503 | new_eye = "dead" 504 | elif not self.is_moving : 505 | new_eye = "angry" 506 | 507 | # MOUTH 508 | 509 | if self.is_charging : 510 | new_mouth = "talk_1" 511 | elif self.is_falling : 512 | new_mouth = "talk_3" 513 | elif self.is_moving : 514 | new_mouth = "smirk_1" 515 | elif self.is_shaking and not self.is_jumping : 516 | new_mouth = "hoo_hoo" 517 | elif not self.is_moving : 518 | new_mouth = "smirk_0" 519 | 520 | # SWORD 521 | 522 | if self.sword.is_active and not self.sword.is_attacking and self.sword.attack_key == ATTACK_SPECIAL and self.is_still : 523 | new_mouth = 'smile' 524 | 525 | if self.sword.is_attacking : 526 | if self.sword.name == 'light' : 527 | if self.sword.attack_key == ATTACK_SPECIAL : 528 | new_eye = 'love' 529 | new_mouth = 'hoo_hoo' 530 | 531 | if self.sword.name == 'blood' : 532 | new_eye = 'rage' 533 | 534 | if self.sword.name == 'desire' : 535 | if self.sword.attack_key == ATTACK_SPECIAL : 536 | new_eye = 'rage' 537 | new_mouth = 'smirk_0' 538 | 539 | if self.is_still or self.is_moving : 540 | if self.sword.name == 'blood' : 541 | new_eye = 'rage' 542 | 543 | if self.sword.was_thrown : 544 | new_mouth = 'smile' 545 | if self.sword.name == 'desire' : 546 | new_mouth = 'smirk_1' 547 | 548 | self.face.update_face(new_eye, new_mouth) 549 | -------------------------------------------------------------------------------- /core/pygame_ce/FRect.py: -------------------------------------------------------------------------------- 1 | # following code from Starbuck5: https://gist.github.com/Starbuck5/aa9a45dbab0952da3f22dadd826d9a5f 2 | 3 | # Alternative implementation of pygame.Rect that uses floats rather than integers 4 | # Don't question the implementation strategy, this is perfect 5 | # Repurposed from another project of mine 6 | 7 | 8 | # Constants needed for my direct translation of SDL_IntersectRectAndLine 9 | CODE_BOTTOM = 1 10 | CODE_TOP = 2 11 | CODE_LEFT = 4 12 | CODE_RIGHT = 8 13 | 14 | 15 | class Rect : 16 | 17 | def __init__( self, *args ) : 18 | if len(args) == 2 : 19 | if len(args[0]) == 2 and len(args[1]) == 2 : 20 | l = [*args[0], *args[1]] 21 | else : 22 | raise TypeError("Argument must be rect style object") 23 | elif len(args) == 4 : 24 | l = [*args] 25 | elif len(args) == 1 : 26 | if len(args[0]) == 2 : 27 | l = [*args[0][0], *args[0][1]] 28 | elif len(args[0]) == 4 : 29 | l = list(args[0]) 30 | else : 31 | raise TypeError(f"sequence argument takes 2 or 4 items ({len(args[0])} given)") 32 | 33 | else : 34 | raise TypeError("Argument must be rect style object") 35 | 36 | self.__dict__["_rect"] = l 37 | 38 | 39 | getattr_dict = {"x" : lambda x : x._rect[0], "y" : lambda x : x._rect[1], 40 | "top" : lambda x : x._rect[1], "left" : lambda x : x._rect[0], 41 | "bottom" : lambda x : x._rect[1] + x._rect[3], "right" : lambda x : x._rect[0] + x._rect[2], 42 | "topleft" : lambda x : (x._rect[0], x._rect[1]), 43 | "bottomleft" : lambda x : (x._rect[0], x._rect[1] + x._rect[3]), 44 | "topright" : lambda x : (x._rect[0] + x._rect[2], x._rect[1]), 45 | "bottomright" : lambda x : (x._rect[0] + x._rect[2], x._rect[1] + x._rect[3]), 46 | "midtop" : lambda x : (x._rect[0] + x._rect[2] / 2, x._rect[1]), 47 | "midleft" : lambda x : (x._rect[0], x._rect[1] + x._rect[3] / 2), 48 | "midbottom" : lambda x : (x._rect[0] + x._rect[2] / 2, x._rect[1] + x._rect[3]), 49 | "midright" : lambda x : (x._rect[0] + x._rect[2], x._rect[1] + x._rect[3] / 2), 50 | "center" : lambda x : (x._rect[0] + x._rect[2] / 2, x._rect[1] + x._rect[3] / 2), 51 | "centerx" : lambda x : x._rect[0] + x._rect[2] / 2, 52 | "centery" : lambda x : x._rect[1] + x._rect[3] / 2, 53 | "size" : lambda x : (x._rect[2], x._rect[3]), "width" : lambda x : x._rect[2], 54 | "height" : lambda x : x._rect[3], "w" : lambda x : x._rect[2], 55 | "h" : lambda x : x._rect[3], } 56 | 57 | 58 | def __getattr__( self, name ) : 59 | try : 60 | return Rect.getattr_dict[name](self) 61 | except KeyError : 62 | raise AttributeError(f"'{self.__class__.__name__}' object has no attribute 'name'") 63 | 64 | 65 | def __setattr__( self, name, value ) : 66 | if name == "x" : 67 | self._rect[0] = value 68 | return 69 | 70 | if name == "y" : 71 | self._rect[1] = value 72 | return 73 | 74 | if name == "top" : 75 | self._rect[1] = value 76 | return 77 | 78 | if name == "left" : 79 | self._rect[0] = value 80 | return 81 | 82 | if name == "bottom" : 83 | self._rect[1] += value - self.bottom 84 | return 85 | 86 | if name == "right" : 87 | self._rect[0] += value - self.right 88 | return 89 | 90 | if name == "topleft" : 91 | self._rect[0], self._rect[1] = value 92 | return 93 | 94 | if name == "bottomleft" : 95 | self._rect[0], self.bottom = value 96 | return 97 | 98 | if name == "topright" : 99 | self.right, self._rect[1] = value 100 | return 101 | 102 | if name == "bottomright" : 103 | self.right, self.bottom = value 104 | return 105 | 106 | if name == "midtop" : 107 | self.centerx, self._rect[1] = value 108 | return 109 | 110 | if name == "midleft" : 111 | self._rect[0], self.centery = value 112 | return 113 | 114 | if name == "midbottom" : 115 | self.centerx, self.bottom = value 116 | return 117 | 118 | if name == "midright" : 119 | self.right, self.centery = value 120 | return 121 | 122 | if name == "center" : 123 | self.centerx, self.centery = value 124 | return 125 | 126 | if name == "centerx" : 127 | self._rect[0] += value - self.centerx 128 | return 129 | 130 | if name == "centery" : 131 | self._rect[1] += value - self.centery 132 | return 133 | 134 | if name == "size" : 135 | self._rect[2], self._rect[3] = value 136 | return 137 | 138 | if name == "width" : 139 | self._rect[2] = value 140 | return 141 | 142 | if name == "height" : 143 | self._rect[3] = value 144 | return 145 | 146 | if name == "w" : 147 | self._rect[2] = value 148 | return 149 | 150 | if name == "h" : 151 | self._rect[3] = value 152 | return 153 | 154 | self.__dict__[name] = value 155 | 156 | 157 | def __getitem__( self, index ) : 158 | return self._rect[index] 159 | 160 | 161 | def __setitem__( self, index, value ) : 162 | self._rect[index] = value 163 | 164 | 165 | def __len__( self ) : 166 | return 4 167 | 168 | 169 | def __str__( self ) : 170 | return f"" 171 | 172 | 173 | def __repr__( self ) : 174 | return self.__str__() 175 | 176 | 177 | def __eq__( self, other ) : 178 | try : 179 | return self._rect == self.__class__(other)._rect 180 | except : 181 | return False 182 | 183 | 184 | def __bool__( self ) : 185 | return self._rect[2] != 0 and self._rect[3] != 0 186 | 187 | 188 | def copy( self ) : 189 | return self.__class__(self._rect) 190 | 191 | 192 | def move( self, x, y ) : 193 | c = self.copy() 194 | c.move_ip(x, y) 195 | return c 196 | 197 | 198 | def move_ip( self, x, y ) : 199 | self._rect[0] += x 200 | self._rect[1] += y 201 | 202 | 203 | def inflate( self, x, y ) : 204 | c = self.copy() 205 | c.inflate_ip(x, y) 206 | return c 207 | 208 | 209 | def inflate_ip( self, x, y ) : 210 | self._rect[0] -= x / 2 211 | self._rect[2] += x 212 | 213 | self._rect[1] -= y / 2 214 | self._rect[3] += y 215 | 216 | 217 | def update( self, *args ) : 218 | self.__init__(*args) 219 | 220 | 221 | def clamp( self, argrect ) : 222 | c = self.copy() 223 | c.clamp_ip(argrect) 224 | return c 225 | 226 | 227 | def clamp_ip( self, argrect ) : 228 | try : 229 | argrect = Rect(argrect) 230 | except : 231 | raise TypeError("Argument must be rect style object") 232 | 233 | if self._rect[2] >= argrect.w : 234 | x = argrect.x + argrect.w / 2 - self._rect[2] / 2 235 | elif self._rect[0] < argrect.x : 236 | x = argrect.x 237 | elif self._rect[0] + self._rect[2] > argrect.x + argrect.w : 238 | x = argrect.x + argrect.w - self._rect[2] 239 | else : 240 | x = self._rect[0] 241 | 242 | if self._rect[3] >= argrect.h : 243 | y = argrect.y + argrect.h / 2 - self._rect[3] / 2 244 | elif self._rect[1] < argrect.y : 245 | y = argrect.y 246 | elif self._rect[1] + self._rect[3] > argrect.y + argrect.h : 247 | y = argrect.y + argrect.h - self._rect[3] 248 | else : 249 | y = self._rect[1] 250 | 251 | self._rect[0] = x 252 | self._rect[1] = y 253 | 254 | 255 | def clip( self, argrect ) : 256 | try : 257 | argrect = Rect(argrect) 258 | except : 259 | raise TypeError("Argument must be rect style object") 260 | 261 | # left 262 | if self.x >= argrect.x and self.x < argrect.x + argrect.w : 263 | x = self.x 264 | elif argrect.x >= self.x and argrect.x < self.x + self.w : 265 | x = argrect.x 266 | else : 267 | return self.__class__(self.x, self.y, 0, 0) 268 | 269 | # right 270 | if self.x + self.w > argrect.x and self.x + self.w <= argrect.x + argrect.w : 271 | w = self.x + self.w - x 272 | elif (argrect.x + argrect.w > self.x and argrect.x + argrect.w <= self.x + self.w) : 273 | w = argrect.x + argrect.w - x 274 | else : 275 | return self.__class__(self.x, self.y, 0, 0) 276 | 277 | # top 278 | if self.y >= argrect.y and self.y < argrect.y + argrect.h : 279 | y = self.y 280 | elif argrect.y >= self.y and argrect.y < self.y + self.h : 281 | y = argrect.y 282 | else : 283 | return self.__class__(self.x, self.y, 0, 0) 284 | 285 | # bottom 286 | if self.y + self.h > argrect.y and self.y + self.h <= argrect.y + argrect.h : 287 | h = self.y + self.h - y 288 | elif (argrect.y + argrect.h > self.y and argrect.y + argrect.h <= self.y + self.h) : 289 | h = argrect.y + argrect.h - y 290 | else : 291 | return self.__class__(self.x, self.y, 0, 0) 292 | 293 | return self.__class__(x, y, w, h) 294 | 295 | 296 | def clipline( self, *args ) : 297 | # arg1 = arg2 = arg3 = arg4 = None 298 | x1 = y1 = x2 = y2 = 0 299 | 300 | if len(args) == 1 : 301 | if len(args[0]) == 2 : 302 | x1, y1 = args[0][0] 303 | x2, y2 = args[0][1] 304 | elif len(args[0]) == 4 : 305 | x1, y1, x2, y2 = args[0] 306 | else : 307 | raise TypeError(f"sequence argument takes 2 or 4 items ({len(args[0])} given)") 308 | 309 | elif len(args) == 2 : 310 | x1, y1 = args[0] 311 | x2, y2 = args[1] 312 | 313 | elif len(args) == 4 : 314 | x1, y1, x2, y2 = args 315 | 316 | else : 317 | raise TypeError(f"clipline() takes 1, 2, or 4 arguments ({len(args)}) given") 318 | 319 | rect = self.__class__(self.__dict__["_rect"].copy()) 320 | rect.normalize() 321 | 322 | # fun fact, I went into SDl's source code to write this 323 | rectx1 = rect.x 324 | recty1 = rect.y 325 | rectx2 = rect.right - 1 326 | recty2 = rect.bottom - 1 327 | 328 | # Check to see if entire line is inside rect 329 | if ( 330 | rectx1 <= x1 <= rectx2 and rectx1 <= x2 <= rectx2 and recty1 <= y1 <= recty2 and recty1 <= y2 <= recty2) : 331 | return ((x1, y1), (x2, y2)) 332 | 333 | # Check to see if entire line is to one side of rect 334 | if ((x1 < rectx1 and x2 < rectx1) or (x1 > rectx2 and x2 > rectx2) or ( 335 | y1 < recty1 and y2 < recty1) or (y1 > recty2 and y2 > recty2)) : 336 | return () 337 | 338 | if y1 == y2 : 339 | # Horizontal line, easy to clip 340 | if x1 < rectx1 : 341 | x1 = rectx1 342 | elif x1 > rectx2 : 343 | x1 = rectx2 344 | if x2 < rectx1 : 345 | x2 = rectx1 346 | elif x2 > rectx2 : 347 | x2 = rectx2 348 | return ((x1, y1), (x2, y2)) 349 | 350 | if x1 == x2 : 351 | # Vertical line, easy to clip 352 | if y1 < recty1 : 353 | y1 = recty1 354 | elif y1 > recty2 : 355 | y1 = recty2 356 | if y2 < recty1 : 357 | y2 = recty1 358 | elif y2 > recty2 : 359 | y2 = recty2 360 | return ((x1, y1), (x2, y2)) 361 | 362 | # More complicated Cohen-Sutherland algorithm 363 | outcode1 = self._compute_outcode(rect, x1, y1) 364 | outcode2 = self._compute_outcode(rect, x2, y2) 365 | while outcode1 or outcode2 : 366 | if outcode1 & outcode2 : 367 | return () 368 | 369 | if outcode1 : 370 | if outcode1 & CODE_TOP : 371 | y = recty1 372 | x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1) 373 | elif outcode1 & CODE_BOTTOM : 374 | y = recty2 375 | x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1) 376 | elif outcode1 & CODE_LEFT : 377 | x = rectx1 378 | y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1) 379 | elif outcode1 & CODE_RIGHT : 380 | x = rectx2 381 | y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1) 382 | x1 = x 383 | y1 = y 384 | outcode1 = self._compute_outcode(rect, x, y) 385 | else : 386 | if outcode2 & CODE_TOP : 387 | y = recty1 388 | x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1) 389 | elif outcode2 & CODE_BOTTOM : 390 | y = recty2 391 | x = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1) 392 | elif outcode2 & CODE_LEFT : 393 | assert x2 != x1 # if equal: division by zero. 394 | x = rectx1 395 | y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1) 396 | elif outcode2 & CODE_RIGHT : 397 | assert x2 != x1 # if equal: division by zero. 398 | x = rectx2 399 | y = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1) 400 | x2 = x 401 | y2 = y 402 | outcode2 = self._compute_outcode(rect, x, y) 403 | 404 | return ((x1, y1), (x2, y2)) 405 | 406 | 407 | def _compute_outcode( self, rect, x, y ) : 408 | code = 0 409 | if y < rect.y : 410 | code |= CODE_TOP 411 | elif y >= rect.y + rect.h : 412 | code |= CODE_BOTTOM 413 | if x < rect.x : 414 | code |= CODE_LEFT 415 | elif x >= rect.x + rect.w : 416 | code |= CODE_RIGHT 417 | return code 418 | 419 | 420 | def union( self, argrect ) : 421 | c = self.copy() 422 | c.union_ip(argrect) 423 | return c 424 | 425 | 426 | def union_ip( self, argrect ) : 427 | try : 428 | argrect = Rect(argrect) 429 | except : 430 | raise TypeError("Argument must be rect style object") 431 | 432 | x = min(self.x, argrect.x) 433 | y = min(self.y, argrect.y) 434 | w = max(self.x + self.w, argrect.x + argrect.w) - x 435 | h = max(self.y + self.h, argrect.y + argrect.h) - y 436 | 437 | self._rect = [x, y, w, h] 438 | 439 | 440 | def unionall( self, argrects ) : 441 | c = self.copy() 442 | c.unionall_ip(argrects) 443 | return c 444 | 445 | 446 | def unionall_ip( self, argrects ) : 447 | for i, argrect in enumerate(argrects) : 448 | try : 449 | argrects[i] = Rect(argrect) 450 | except : 451 | raise TypeError("Argument must be rect style object") 452 | 453 | x = min([self.x] + [r.x for r in argrects]) 454 | y = min([self.y] + [r.y for r in argrects]) 455 | w = max([self.right] + [r.right for r in argrects]) - x 456 | h = max([self.bottom] + [r.bottom for r in argrects]) - y 457 | 458 | self._rect = [x, y, w, h] 459 | 460 | 461 | def fit( self, argrect ) : 462 | try : 463 | argrect = Rect(argrect) 464 | except : 465 | raise TypeError("Argument must be rect style object") 466 | 467 | xratio = (self.w / argrect.w) if argrect.w != 0 else float('inf') 468 | yratio = (self.h / argrect.h) if argrect.h != 0 else float('inf') 469 | maxratio = max(xratio, yratio) 470 | 471 | w = self.w / maxratio 472 | h = self.h / maxratio 473 | 474 | x = argrect.x + (argrect.w - w) / 2 475 | y = argrect.y + (argrect.h - h) / 2 476 | 477 | return Rect(x, y, w, h) 478 | 479 | 480 | def normalize( self ) : 481 | if self._rect[2] < 0 : 482 | self._rect[0] += self._rect[2] 483 | self._rect[2] = -self._rect[2] 484 | 485 | if self._rect[3] < 0 : 486 | self._rect[1] += self._rect[3] 487 | self._rect[3] = -self._rect[3] 488 | 489 | 490 | def contains( self, argrect ) : 491 | try : 492 | argrect = Rect(argrect) 493 | except : 494 | raise TypeError("Argument must be rect style object") 495 | 496 | if self._rect[0] <= argrect[0] and argrect[0] + argrect[2] <= self.right : 497 | if self._rect[1] <= argrect[1] and argrect[1] + argrect[3] <= self.bottom : 498 | return True 499 | return False 500 | 501 | 502 | def collidepoint( self, *args ) : 503 | if len(args) == 1 : 504 | point = args[0] 505 | elif len(args) == 2 : 506 | point = tuple(args) 507 | else : 508 | raise TypeError("argument must contain two numbers") 509 | 510 | # conforms with no collision on right / bottom edge behavior of pygame Rects 511 | if self._rect[0] <= point[0] < self.right : 512 | if self._rect[1] <= point[1] < self.bottom : 513 | return True 514 | return False 515 | 516 | 517 | def colliderect( self, argrect ) : 518 | try : 519 | argrect = Rect(argrect) 520 | except : 521 | raise TypeError("Argument must be rect style object") 522 | 523 | if any(0 == d for d in [self.w, self.h, argrect.w, argrect.h]) : 524 | return False 525 | 526 | return (min(self.x, self.x + self.w) < max(argrect.x, argrect.x + argrect.w) and min(self.y, 527 | self.y + self.h) < max(argrect.y, argrect.y + argrect.h) and max(self.x, 528 | self.x + self.w) > min(argrect.x, argrect.x + argrect.w) and max(self.y, 529 | self.y + self.h) > min(argrect.y, argrect.y + argrect.h)) 530 | 531 | 532 | def collidelist( self, argrects ) : 533 | for i, argrect in enumerate(argrects) : 534 | if self.colliderect(argrect) : 535 | return i 536 | 537 | return -1 538 | 539 | 540 | def collidelistall( self, argrects ) : 541 | out = [] 542 | 543 | for i, argrect in enumerate(argrects) : 544 | if self.colliderect(argrect) : 545 | out.append(i) 546 | 547 | return out 548 | 549 | 550 | def collidedict( self, rects_dict, use_values=0 ) : 551 | for key in rects_dict : 552 | if use_values == 0 : 553 | argrect = key 554 | else : 555 | argrect = rects_dict[key] 556 | 557 | if self.colliderect(argrect) : 558 | return (key, rects_dict[key]) 559 | 560 | return None # explicit rather than implicit 561 | 562 | 563 | def collidedictall( self, rects_dict, use_values=0 ) : 564 | out = [] 565 | 566 | for key in rects_dict : 567 | if use_values == 0 : 568 | argrect = key 569 | else : 570 | argrect = rects_dict[key] 571 | 572 | if self.colliderect(argrect) : 573 | out.append((key, rects_dict[key])) 574 | 575 | return out -------------------------------------------------------------------------------- /core/pygame_ce/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yolowex/ParticleOverdose/7d5a1e474794b313550de5cee9a9e8cf2630e926/core/pygame_ce/__init__.py -------------------------------------------------------------------------------- /core/sprite.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | 3 | class Sprite: 4 | 5 | def __init__(self,path:str=None,data=None,surface=None): 6 | if surface is not None: 7 | self.raw_surface = surface.convert_alpha() 8 | self.transformed_surface = self.raw_surface.copy() 9 | return 10 | 11 | if path is None: 12 | self.raw_surface = data[0] 13 | self.transformed_surface = data[1] 14 | return 15 | 16 | self.raw_surface = pg.image.load(path) 17 | # self.raw_surface.set_colorkey("black") 18 | self.transformed_surface = self.raw_surface.copy() 19 | 20 | def get_diff( self ): 21 | a = self.raw_surface.get_size() 22 | b = self.transformed_surface.get_size() 23 | 24 | return [b[0]/a[0],b[1]/a[1]] 25 | 26 | 27 | def copy( self): 28 | return Sprite(data=[self.raw_surface,self.transformed_surface]) 29 | 30 | 31 | def transform( self,new_w,new_h ): 32 | self.transformed_surface = pg.transform.scale(self.raw_surface,(new_w,new_h)) 33 | 34 | def transform_by_height( self,new_h ): 35 | current_h = self.raw_surface.get_height() 36 | new_w = self.raw_surface.get_width() * (new_h / current_h) 37 | 38 | self.transformed_surface = pg.transform.scale(self.raw_surface,(new_w,new_h)) 39 | 40 | def transform_by_width( self,new_w ): 41 | current_w = self.raw_surface.get_width() 42 | new_h = self.raw_surface.get_height() * (new_w / current_w) 43 | 44 | self.transformed_surface = pg.transform.scale(self.raw_surface,(new_w,new_h)) 45 | 46 | def transform_by_rel( self,rel_x,rel_y ): 47 | new_w,new_h = self.raw_surface.get_size() 48 | new_w *= rel_x 49 | new_h *= rel_y 50 | 51 | self.transformed_surface = pg.transform.scale(self.raw_surface,(new_w,new_h)) 52 | 53 | def flip( self,flip_x=False,flip_y=False ): 54 | self.transformed_surface = pg.transform.flip(self.transformed_surface,flip_x, flip_y) 55 | return self -------------------------------------------------------------------------------- /core/sword.py: -------------------------------------------------------------------------------- 1 | from core.common_names import * 2 | import core.common_resources as cr 3 | from core.sprite import Sprite 4 | from core.constants import * 5 | from core.common_functions import * 6 | from core.particle import Particle 7 | 8 | 9 | class Sword : 10 | 11 | def __init__( self ) : 12 | self.sword_left: Optional[Sprite] = None 13 | self.sword_right: Optional[Sprite] = None 14 | self.rotated_sword_left: Optional[Surface] = None 15 | self.rotated_sword_right: Optional[Surface] = None 16 | self.rotated_points_left: list[Vector2] = [] 17 | self.rotated_points_right: list[Vector2] = [] 18 | self.angle = 0.0 19 | self.name = "none'" 20 | self.angle_power = 0.0 21 | self.is_attacking = False 22 | self.attack_key = None 23 | self.is_retrieving = False 24 | self.is_active = False 25 | self.last_attack_type = None 26 | self.was_thrown = False 27 | self.timer = None 28 | self.move_timer = None 29 | self.idle_duration = 1.5 30 | self.original_distance = 0.7 31 | self.distance = self.original_distance 32 | self.cancel_throw = False 33 | 34 | self.max_special_combo = 2 35 | self.total_combo = 0 36 | self.cooldown = 3 37 | self.cooldown_timer = -100 38 | 39 | 40 | def init( self ) : 41 | name = 'blood' 42 | self.update_sword(name) 43 | self.rotate_sword() 44 | 45 | 46 | @property 47 | def particle_delta_count( self ) : 48 | delta = cr.event_holder.delta_time 49 | if delta <= 0.01 : 50 | delta = 0.01 51 | 52 | count = int(delta * 100) 53 | if IS_WEB : 54 | count *= 6 55 | 56 | return count 57 | 58 | 59 | # @property 60 | # def is_active( self ): 61 | # return self.distance > 0.25 62 | 63 | def reset_sword( self ) : 64 | self.is_attacking = False 65 | self.is_retrieving = False 66 | self.angle = 0 67 | self.distance = 0.7 68 | self.move_timer = None 69 | self.timer = None 70 | self.rotate_sword() 71 | 72 | 73 | def rotate_sword( self ) : 74 | swords = [i.transformed_surface for i in [self.sword_right, self.sword_left]] 75 | 76 | r = 1 77 | for sword, drc in zip(swords, 'rl') : 78 | if drc == 'l' : 79 | r = -1 80 | 81 | new_sword = pg.transform.rotate(sword, -self.angle * r) 82 | # This code doesn't work for the opposite direction of the player because of this 83 | gp = self.get_grab_point(drc) 84 | 85 | r_points = get_rotated_points(FRect(sword.get_rect()), self.angle * r) 86 | 87 | ptl, pbl = r_points[0], r_points[3] 88 | ptr, pbr = r_points[1], r_points[2] 89 | 90 | b = pbr.lerp(pbl, 0.5) 91 | t = ptr.lerp(ptl, 0.5) 92 | lerp_val = 0.2 93 | 94 | c = b.lerp(t, lerp_val) 95 | 96 | diff = gp.x - c.x, gp.y - c.y 97 | 98 | for point in r_points : 99 | point.x += diff[0] 100 | point.y += diff[1] 101 | 102 | if drc == 'r' : 103 | self.rotated_sword_right = new_sword 104 | self.rotated_points_right = r_points 105 | else : 106 | self.rotated_sword_left = new_sword 107 | self.rotated_points_left = r_points 108 | 109 | 110 | @property 111 | def grab_point( self ) : 112 | return self.get_grab_point(cr.game.player.facing) 113 | 114 | 115 | def get_grab_point( self, direction: str ) : 116 | if direction == 'r' : 117 | direction = RIGHT 118 | elif direction == 'l' : 119 | direction = LEFT 120 | 121 | player = cr.game.player 122 | ptl, pbl = player.points[0], player.points[3] 123 | ptr, pbr = player.points[1], player.points[2] 124 | 125 | b = pbr.lerp(pbl, 0.5) 126 | t = ptr.lerp(ptl, 0.5) 127 | lerp_value = 0.3 128 | if self.name == 'death' : 129 | lerp_value = 0.7 130 | c = b.lerp(t, lerp_value) 131 | 132 | m = 1 133 | if self.name == 'blood' : 134 | m = 1.5 135 | 136 | if direction == RIGHT : 137 | c.x += player.rect.w * self.distance * m 138 | else : 139 | c.x -= player.rect.w * self.distance * m 140 | 141 | return c 142 | 143 | 144 | def update_sword( self, name: str ) : 145 | if self.name != name : 146 | self.name = name 147 | self.sword_left = cr.left_sword_dict[self.name] 148 | self.sword_right = cr.right_sword_dict[self.name] 149 | self.distance = self.original_distance 150 | self.is_attacking = False 151 | self.was_thrown = False 152 | self.is_retrieving = False 153 | self.angle = 0 154 | self.rotate_sword() 155 | self.timer = now() 156 | self.is_active = True 157 | 158 | 159 | def check_events( self ) : 160 | self.check_attack() 161 | self.angle += self.angle_power 162 | if K_f in cr.event_holder.pressed_keys : 163 | self.attack(ATTACK_NORMAL) 164 | 165 | in_cooldown = now() < self.cooldown + self.cooldown_timer 166 | 167 | if K_v in cr.event_holder.pressed_keys : 168 | if not self.is_attacking and not self.is_retrieving and not in_cooldown: 169 | self.attack(ATTACK_SPECIAL) 170 | self.cooldown_timer = now() 171 | elif not self.was_thrown : 172 | self.reset_sword() 173 | self.timer = now() 174 | 175 | if not in_cooldown and self.total_combo == self.max_special_combo : 176 | self.total_combo = 0 177 | 178 | if not self.was_thrown : 179 | name = 'none' 180 | if K_q in cr.event_holder.pressed_keys and 'evil' not in cr.game.player.locked_swords_list : 181 | name = 'evil' 182 | elif K_w in cr.event_holder.pressed_keys and 'desire' not in cr.game.player.locked_swords_list : 183 | name = 'desire' 184 | elif K_e in cr.event_holder.pressed_keys and 'light' not in cr.game.player.locked_swords_list : 185 | name = 'light' 186 | elif K_r in cr.event_holder.pressed_keys and 'hawk' not in cr.game.player.locked_swords_list : 187 | name = 'hawk' 188 | elif K_t in cr.event_holder.pressed_keys and 'blood' not in cr.game.player.locked_swords_list : 189 | name = 'blood' 190 | elif K_g in cr.event_holder.pressed_keys and 'death' not in cr.game.player.locked_swords_list : 191 | name = 'death' 192 | 193 | if name is not 'none' and not in_cooldown : 194 | self.reset_sword() 195 | reset = (self.is_attacking or self.is_retrieving) 196 | self.update_sword(name) 197 | 198 | if not reset : 199 | self.attack(ATTACK_SPECIAL) 200 | self.total_combo += 1 201 | if self.total_combo == self.max_special_combo : 202 | self.cooldown_timer = now() 203 | 204 | self.check_activeness() 205 | 206 | 207 | @property 208 | def angle_speed( self ) : 209 | return 2000 * cr.event_holder.delta_time 210 | 211 | 212 | def swing_attack( self, swing_speed: float = 1, release_speed: float = 0.5, 213 | swing_amount: float = 135 ) : 214 | angle_m = 1 215 | points = self.rotated_points_right 216 | if cr.game.player.facing == LEFT : 217 | points = self.rotated_points_left 218 | angle_m = -1 219 | 220 | web_m = 1 221 | if IS_WEB : 222 | web_m = 3 223 | 224 | for _ in range(random.randint(1, self.particle_delta_count * 5)) : 225 | b = points[2].lerp(points[3], 0.5) 226 | t = points[0].lerp(points[1], 0.5) 227 | 228 | shoot_point = b.lerp(t, random.uniform(0.2, 0.8)) 229 | 230 | shoot_angle = (self.angle * angle_m) + random.randint(-10 * web_m, 10 * web_m) 231 | size = random.uniform(1, 4.5) 232 | self.add_particle(shoot_point, shoot_angle, size, ) 233 | 234 | self.last_attack_type = SWING 235 | m = swing_speed 236 | if self.is_retrieving : 237 | m = -release_speed 238 | 239 | self.angle += self.angle_speed * m 240 | 241 | if self.angle >= swing_amount : 242 | self.angle = swing_amount 243 | self.angle = self.angle % 360 244 | self.is_retrieving = True 245 | self.is_attacking = False 246 | 247 | elif self.is_retrieving and self.angle <= 0 : 248 | self.angle = 0 249 | self.is_retrieving = False 250 | self.timer = now() 251 | 252 | self.rotate_sword() 253 | 254 | 255 | def throw_attack( self ) : 256 | self.last_attack_type = THROW 257 | throw_angle = 90 258 | m = 0.5 259 | throw_speed = 20 260 | max_distance = 20 261 | 262 | points = self.rotated_points_right 263 | if cr.game.player.facing == LEFT : 264 | points = self.rotated_points_left 265 | 266 | rect = polygon_to_rect(points) 267 | 268 | do_particles = False 269 | reverse = 1 270 | 271 | # retrieve 272 | if (self.was_thrown and self.distance != self.original_distance) or self.cancel_throw : 273 | self.is_attacking = False 274 | self.is_retrieving = True 275 | do_particles = True 276 | reverse *= -1 277 | self.distance -= cr.event_holder.delta_time * throw_speed 278 | 279 | if self.cancel_throw or self.distance < self.original_distance : 280 | self.distance = self.original_distance 281 | self.cancel_throw = False 282 | 283 | # rotate in 284 | elif self.was_thrown and self.distance == self.original_distance : 285 | self.angle -= self.angle_speed * m 286 | if self.angle < 0 : 287 | self.angle = 0 288 | self.is_retrieving = False 289 | self.is_attacking = False 290 | self.was_thrown = False 291 | self.timer = now() 292 | 293 | else : 294 | # rotate out 295 | if self.angle < throw_angle : 296 | self.is_attacking = True 297 | self.angle += self.angle_speed * m 298 | if self.angle > throw_angle : 299 | self.angle = throw_angle 300 | 301 | # throw 302 | elif self.angle == throw_angle : 303 | do_particles = True 304 | self.distance += cr.event_holder.delta_time * throw_speed 305 | if self.distance > max_distance : 306 | self.distance = max_distance 307 | self.was_thrown = True 308 | 309 | if self.is_colliding() : 310 | self.cancel_throw = True 311 | self.was_thrown = True 312 | cr.game.player.teleport(Vector2(rect.center)) 313 | 314 | if do_particles : 315 | angle_m = 1 316 | points = self.rotated_points_right 317 | if cr.game.player.facing == LEFT : 318 | points = self.rotated_points_left 319 | angle_m = -1 320 | 321 | for _ in range(random.randint(1, self.particle_delta_count * 5)) : 322 | bias = random.uniform(0, 1) 323 | b = points[2].lerp(points[3], bias) 324 | t = points[0].lerp(points[1], bias) 325 | 326 | shoot_point = b.lerp(t, random.uniform(0.2, 0.8)) 327 | 328 | shoot_angle = (self.angle * angle_m) + random.randint(-7, 7) 329 | shoot_angle = shoot_angle * -1 * reverse 330 | 331 | size = random.uniform(1, 4.5) 332 | self.add_particle(shoot_point, shoot_angle, size, ) 333 | 334 | self.rotate_sword() 335 | 336 | 337 | def swirling_throw_attack( self ) : 338 | angle_m = 1 339 | points = self.rotated_points_right 340 | if cr.game.player.facing == LEFT : 341 | points = self.rotated_points_left 342 | angle_m = -1 343 | 344 | web_m = 1 345 | if IS_WEB : 346 | web_m = 3 347 | 348 | for _ in range(random.randint(1, self.particle_delta_count * 5)) : 349 | b = points[2].lerp(points[3], 0.5) 350 | t = points[0].lerp(points[1], 0.5) 351 | 352 | shoot_point = b.lerp(t, random.uniform(0.2, 0.8)) 353 | 354 | shoot_angle = (self.angle * angle_m) + random.randint(-10 * web_m, 10 * web_m) 355 | size = random.uniform(1, 4.5) 356 | self.add_particle(shoot_point, shoot_angle, size, ) 357 | 358 | self.last_attack_type = SWIRLING_THROW 359 | throw_angle = 90 360 | rotate_in_out = 0.5 361 | throw_m = 0.5 362 | retrieve_m = 0.5 363 | throw_speed = 15 364 | retrieve_speed = 15 365 | max_distance = 8 366 | 367 | 368 | # It's messy but I'm in a hurry 369 | def current_distance_scale() : 370 | x = 1 - (self.distance / max_distance) 371 | x *= 2 372 | if x >= 1 : 373 | x = 1 374 | if x <= 0.2 : 375 | x = 0.2 376 | return x 377 | 378 | 379 | # retrieve 380 | if self.was_thrown and self.distance != self.original_distance : 381 | self.angle += self.angle_speed * retrieve_m 382 | self.is_attacking = False 383 | self.is_retrieving = True 384 | self.distance -= cr.event_holder.delta_time * retrieve_speed * current_distance_scale() 385 | if self.distance < self.original_distance : 386 | self.distance = self.original_distance 387 | self.angle = abs(self.angle) 388 | self.angle = self.angle % 360 389 | 390 | # rotate out 391 | elif self.was_thrown and self.distance == self.original_distance : 392 | dirc = -1 393 | if self.angle > 360 - self.angle : 394 | dirc = 1 395 | 396 | self.angle += self.angle_speed * rotate_in_out * dirc 397 | if self.angle < 0 or self.angle > 360 : 398 | self.angle = 0 399 | self.is_retrieving = False 400 | self.is_attacking = False 401 | self.was_thrown = False 402 | self.timer = now() 403 | else : 404 | # rotate in 405 | if self.angle < throw_angle : 406 | self.is_attacking = True 407 | self.angle += self.angle_speed * rotate_in_out 408 | if self.angle >= throw_angle : 409 | self.angle = throw_angle 410 | # throw 411 | elif self.angle >= throw_angle : 412 | self.angle += self.angle_speed * throw_m 413 | self.distance += cr.event_holder.delta_time * throw_speed * current_distance_scale() 414 | if self.distance > max_distance : 415 | self.distance = max_distance 416 | self.was_thrown = True 417 | 418 | self.rotate_sword() 419 | 420 | 421 | def advance_attack( self, duration: float = 0.5 ) : 422 | angle_m = 1 423 | points = self.rotated_points_right 424 | if cr.game.player.facing == LEFT : 425 | points = self.rotated_points_left 426 | angle_m = -1 427 | 428 | for _ in range(random.randint(0, self.particle_delta_count * 5)) : 429 | bias = random.uniform(0, 1) 430 | b = points[2].lerp(points[3], bias) 431 | t = points[0].lerp(points[1], bias) 432 | 433 | shoot_point = b.lerp(t, random.uniform(0.2, 0.8)) 434 | 435 | shoot_angle = (self.angle * angle_m) + random.randint(-5, 5) 436 | shoot_angle = shoot_angle * -1 437 | 438 | size = random.uniform(1, 4.5) 439 | self.add_particle(shoot_point, shoot_angle, size, ) 440 | 441 | dirc = cr.game.player.move_speed * 2.8 442 | if cr.game.player.facing == LEFT : 443 | dirc *= -1 444 | 445 | self.angle = 90 446 | self.rotate_sword() 447 | halt = False 448 | if not cr.game.player.move(Vector2(dirc, 0), True) : 449 | halt = True 450 | 451 | if self.move_timer is None : 452 | self.move_timer = now() 453 | 454 | elif self.move_timer + duration < now() or halt : 455 | self.angle = 0 456 | self.rotate_sword() 457 | self.move_timer = None 458 | self.is_attacking = False 459 | self.timer = now() 460 | return 461 | 462 | 463 | def fly_attack( self, duration: float = 0.5 ) : 464 | points = self.rotated_points_right 465 | dirc = cr.game.player.move_speed * 3 466 | if cr.game.player.facing == LEFT : 467 | dirc *= -1 468 | points = self.rotated_points_left 469 | 470 | self.angle = 0 471 | self.rotate_sword() 472 | 473 | angle_m = 1 474 | points = self.rotated_points_right 475 | if cr.game.player.facing == LEFT : 476 | points = self.rotated_points_left 477 | angle_m = -1 478 | 479 | for _ in range(random.randint(0, self.particle_delta_count * 5)) : 480 | bias = random.uniform(0, 1) 481 | b = points[2].lerp(points[3], bias) 482 | t = points[0].lerp(points[1], bias) 483 | 484 | shoot_point = b.lerp(t, random.uniform(0.2, 0.8)) 485 | shoot_angle = (self.angle * angle_m) + random.randint(-5, 5) 486 | shoot_angle += 180 487 | size = random.uniform(1, 4.5) 488 | self.add_particle(shoot_point, shoot_angle, size, ) 489 | 490 | halt = False 491 | if not cr.game.player.move(Vector2(0, -(abs(dirc))), True) : 492 | halt = True 493 | 494 | if self.move_timer is None : 495 | self.move_timer = now() 496 | 497 | elif self.move_timer + duration < now() or halt : 498 | self.angle = 0 499 | self.rotate_sword() 500 | self.move_timer = None 501 | self.is_attacking = False 502 | self.timer = now() 503 | return 504 | 505 | 506 | def check_attack( self ) : 507 | if not self.is_attacking and not self.is_retrieving : 508 | return 509 | 510 | if self.attack_key == ATTACK_NORMAL : 511 | self.swing_attack(swing_amount=90) 512 | else : 513 | if self.name == 'death' : 514 | self.throw_attack() 515 | 516 | if self.name == 'blood' : 517 | self.swing_attack(1, 0.1, 360 * 3.1) 518 | 519 | if self.name == 'desire' : 520 | self.swirling_throw_attack() 521 | 522 | if self.name == 'evil' : 523 | self.swing_attack(2, 4, 360 * 10.8) 524 | 525 | if self.name == 'light' : 526 | self.advance_attack(0.5) 527 | 528 | if self.name == 'hawk' : 529 | self.fly_attack(0.5) 530 | 531 | 532 | def add_particle( self, source: Vector2, angle, size, color: Optional[Color] = None ) : 533 | if len(cr.game.particles) > cr.game.maximum_particles : 534 | return 535 | 536 | power_scale = 0 537 | spec = self.attack_key == ATTACK_SPECIAL 538 | 539 | color = color 540 | if self.name == 'death' : 541 | color = BLACK 542 | if spec : 543 | power_scale = random.uniform(0, 1) 544 | elif self.name == 'blood' : 545 | power_scale = 1 546 | color = Color(random.randint(200, 255), random.randint(0, 75), random.randint(0, 75)) 547 | elif self.name == 'hawk' : 548 | power_scale = 5 549 | color = Color(random.randint(0, 75), random.randint(0, 75), random.randint(200, 255)) 550 | elif self.name == 'evil' : 551 | power_scale = 10 552 | color = Color(random.randint(0, 75), random.randint(200, 255), random.randint(0, 75)) 553 | elif self.name == 'desire' : 554 | color = Color(random.randint(200, 255), random.randint(50, 80), random.randint(0, 50)) 555 | elif self.name == 'light' : 556 | power_scale = 5 557 | color = Color(random.randint(200, 255), 0, random.randint(0, 255)) 558 | color.g = color.r 559 | 560 | age = random.uniform(0, 1) 561 | particle = Particle(source, size, angle, age, color, True, True) 562 | # particle.power = random.uniform(4,5) 563 | particle.power = 5 564 | if IS_WEB : 565 | particle.power = 3.8 566 | particle.power_decrease_scale = power_scale 567 | cr.game.particles.append(particle) 568 | 569 | 570 | def check_activeness( self ) : 571 | if self.is_attacking or self.is_retrieving : 572 | return 573 | 574 | if self.timer is not None : 575 | if now() > self.timer + self.idle_duration : 576 | self.timer = None 577 | self.is_active = False 578 | 579 | 580 | def attack( self, attack_key ) : 581 | if self.is_attacking or self.is_retrieving : 582 | return 583 | 584 | self.attack_key = attack_key 585 | self.is_attacking = True 586 | self.is_active = True 587 | 588 | 589 | def is_colliding( self ) -> bool : 590 | points = self.rotated_points_right 591 | if cr.game.player.facing == LEFT : 592 | points = self.rotated_points_left 593 | 594 | rect = polygon_to_rect(points) 595 | 596 | for box in cr.game.inner_box_list : 597 | if rect.colliderect(box) : 598 | return True 599 | 600 | return False 601 | 602 | 603 | @property 604 | def cooldown_text( self ) : 605 | return cr.little_font.render( 606 | f"Cooldown... {str(round(abs(now() - (self.cooldown_timer + self.cooldown)), 2)).zfill(5)}", 607 | False, Color(155,0,0)) 608 | 609 | 610 | def render_debug( self, points ) : 611 | pg.draw.polygon(cr.screen, 'red', points, width=2) 612 | 613 | 614 | def render( self ) : 615 | if not self.is_active : 616 | return 617 | 618 | points = self.rotated_points_right 619 | sword = self.rotated_sword_right 620 | if cr.game.player.facing == LEFT : 621 | points = self.rotated_points_left 622 | sword = self.rotated_sword_left 623 | 624 | points = [i.copy() for i in points] 625 | move_points(points, cr.camera.x, cr.camera.y) 626 | the_rect = polygon_to_rect(points) 627 | 628 | cr.screen.blit(sword, the_rect) 629 | 630 | in_cooldown = now() < self.cooldown + self.cooldown_timer 631 | if in_cooldown : 632 | surface = self.cooldown_text 633 | rect = surface.get_rect() 634 | rect.center = cr.screen.get_rect().center 635 | rect.y = cr.screen.get_height() - rect.h 636 | cr.screen.blit(surface, rect) 637 | 638 | if cr.event_holder.should_render_debug : 639 | self.render_debug(points) 640 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | from pygame.locals import * 3 | 4 | from core.event_holder import EventHolder 5 | import core.common_resources as cr 6 | from core.game import Game 7 | from core.constants import * 8 | from core.common_functions import * 9 | import core.constants as const 10 | import asyncio 11 | 12 | pic = "./pic.png" 13 | res = "./pic_res.png" 14 | 15 | pg.init() 16 | 17 | cr.font = pg.font.SysFont('monospace', 20) 18 | cr.little_font = pg.font.SysFont('Arial', 15) 19 | cr.smallest_font = pg.font.SysFont('monospace', 10) 20 | 21 | font = cr.font 22 | last_time = 0 23 | 24 | 25 | def game_over_text() : 26 | return cr.little_font.render( 27 | f"You found {last_diamonds} Diamonds in {last_time} seconds! Good Job! press X to replay", True, 28 | "red") 29 | 30 | 31 | def win_text() : 32 | return cr.little_font.render( 33 | f"You gathered all Diamonds in {last_time} seconds! Very nice! press X to replay", True, "red") 34 | 35 | 36 | pg.mouse.set_visible(False) 37 | 38 | just_lost = False 39 | just_won = False 40 | 41 | last_diamonds = 0 42 | async def main() : 43 | global last_time, just_lost, just_won 44 | global last_diamonds 45 | 46 | # cr.screen = pg.display.set_mode([900, 640], SCALED | FULLSCREEN) 47 | if IS_WEB : # web only, scales automatically 48 | cr.screen = pg.display.set_mode([900 * 0.8, 640 * 0.8]) 49 | else : 50 | cr.screen = pg.display.set_mode([900 * 0.8, 640 * 0.8], SCALED | FULLSCREEN) 51 | 52 | start_playing = False 53 | 54 | cr.event_holder = EventHolder() 55 | cr.event_holder.should_render_debug = False 56 | cr.event_holder.determined_fps = 1000 57 | 58 | start_playing_text = font.render("press P to start Playing!", True, "red") 59 | 60 | 61 | def reset_game() : 62 | start_playing = False 63 | cr.world = json.loads(open(levels_root + "test.json").read()) 64 | cr.game = Game() 65 | cr.game.init() 66 | 67 | 68 | reset_game() 69 | 70 | this_font = cr.smallest_font 71 | if IS_WEB: 72 | this_font = cr.little_font 73 | 74 | fps_text = lambda : this_font.render(f"FPS :{int(cr.event_holder.final_fps)}" 75 | f" PARTICLES: {cr.game.player.particles.__len__()}", 76 | True, "white") 77 | 78 | wait_text = this_font.render("Please wait while loading...",True,"red") 79 | 80 | # I F**king love OOP :heart: 81 | while not cr.event_holder.should_quit : 82 | if cr.game.player.lives == 0 and not just_lost : 83 | last_time = round(now() - cr.game.timer, 2) 84 | last_diamonds = cr.game.player.acquired_diamonds 85 | reset_game() 86 | just_lost = True 87 | 88 | if cr.game.player.acquired_diamonds == cr.game.level.total_diamonds and not just_won : 89 | last_diamonds = cr.game.player.acquired_diamonds 90 | last_time = round(now() - cr.game.timer, 2) 91 | 92 | reset_game() 93 | just_won = True 94 | 95 | if K_F3 in cr.event_holder.pressed_keys : 96 | cr.event_holder.should_render_debug = not cr.event_holder.should_render_debug 97 | 98 | if K_x in cr.event_holder.released_keys : 99 | if just_won or just_lost : 100 | just_lost = False 101 | just_won = False 102 | 103 | if K_F12 in cr.event_holder.released_keys: 104 | just_lost = False 105 | just_won = False 106 | 107 | cr.event_holder.get_events() 108 | if IS_WEB and cr.event_holder.should_quit: 109 | cr.event_holder.should_quit = False 110 | 111 | 112 | 113 | win_focus = cr.event_holder.window_focus and cr.event_holder.focus_gain_timer + 1 < now() 114 | mouse_focus = cr.event_holder.mouse_focus and cr.event_holder.mouse_focus_gain_timer + 1 < now() 115 | 116 | should = start_playing and not (just_lost or just_won) and (win_focus and mouse_focus) 117 | 118 | 119 | if not should: 120 | cr.screen.fill([0,0,0,0]) 121 | if should: 122 | cr.game.check_events() 123 | cr.game.render() 124 | 125 | if not should and not just_lost and not just_won: 126 | rect = wait_text.get_rect() 127 | rect.center = cr.screen.get_rect().center 128 | rect.y = cr.screen.get_height() * 0.7 129 | cr.screen.blit(wait_text,rect) 130 | 131 | if not start_playing : 132 | text_rect = start_playing_text.get_rect() 133 | text_rect.center = cr.screen.get_rect().center 134 | cr.screen.blit(start_playing_text, text_rect) 135 | if K_p in cr.event_holder.released_keys or K_LCTRL in cr.event_holder.released_keys : 136 | start_playing = True 137 | just_lost = False 138 | cr.game.timer = now() 139 | 140 | text = fps_text() 141 | cr.screen.blit(fps_text(), 142 | (cr.screen.get_width() - text.get_width(), cr.screen.get_height() - text.get_height())) 143 | 144 | if just_lost : 145 | surface = game_over_text() 146 | rect = surface.get_rect() 147 | rect.center = cr.screen.get_rect().center 148 | cr.screen.blit(surface, rect) 149 | 150 | if just_won : 151 | surface = win_text() 152 | rect = surface.get_rect() 153 | rect.center = cr.screen.get_rect().center 154 | cr.screen.blit(surface, rect) 155 | 156 | pg.display.update() 157 | 158 | # pg.image.save(cr.screen, "./dump.jpg") 159 | 160 | await asyncio.sleep(0) 161 | 162 | 163 | if __name__ == '__main__' : 164 | asyncio.run(main()) 165 | -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | block_cipher = None 5 | 6 | 7 | a = Analysis( 8 | ['main.py'], 9 | pathex=[], 10 | binaries=[], 11 | datas=[], 12 | hiddenimports=[], 13 | hookspath=[], 14 | hooksconfig={}, 15 | runtime_hooks=[], 16 | excludes=[], 17 | win_no_prefer_redirects=False, 18 | win_private_assemblies=False, 19 | cipher=block_cipher, 20 | noarchive=False, 21 | ) 22 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 23 | 24 | exe = EXE( 25 | pyz, 26 | a.scripts, 27 | [], 28 | exclude_binaries=True, 29 | name='main', 30 | debug=False, 31 | bootloader_ignore_signals=False, 32 | strip=False, 33 | upx=True, 34 | console=True, 35 | disable_windowed_traceback=False, 36 | argv_emulation=False, 37 | target_arch=None, 38 | codesign_identity=None, 39 | entitlements_file=None, 40 | ) 41 | coll = COLLECT( 42 | exe, 43 | a.binaries, 44 | a.zipfiles, 45 | a.datas, 46 | strip=False, 47 | upx=True, 48 | upx_exclude=[], 49 | name='main', 50 | ) 51 | -------------------------------------------------------------------------------- /pt.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import random 3 | import sys 4 | from PIL import Image 5 | 6 | def find_coeffs(pa, pb): 7 | matrix = [] 8 | for p1, p2 in zip(pa, pb): 9 | matrix.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0]*p1[0], -p2[0]*p1[1]]) 10 | matrix.append([0, 0, 0, p1[0], p1[1], 1, -p2[1]*p1[0], -p2[1]*p1[1]]) 11 | 12 | A = numpy.matrix(matrix, dtype=float) 13 | B = numpy.array(pb).reshape(8) 14 | 15 | res = numpy.dot(numpy.linalg.inv(A.T * A) * A.T, B) 16 | return numpy.array(res).reshape(8) 17 | 18 | pic = "./pic.png" 19 | res = "./pic_res.png" 20 | 21 | import sys 22 | from PIL import Image 23 | 24 | img = Image.open(pic) 25 | width, height = img.size 26 | m = -0.5 27 | xshift = abs(m) * width 28 | new_width = width + int(round(xshift)) 29 | 30 | coeffs = find_coeffs( 31 | [(0, 0), (256, 0), (256, 256), (0, 256)], 32 | [(0, 0), (256, 0), (new_width, height), (xshift, height)]) 33 | 34 | img.transform((width, height), Image.PERSPECTIVE, coeffs, 35 | Image.BICUBIC).save(res) 36 | 37 | 38 | 39 | exit() -------------------------------------------------------------------------------- /steps: -------------------------------------------------------------------------------- 1 | add face assets Done 2 | improve colors Done 3 | add collisions boxs Done 4 | add sword Done 5 | add gun Canceled 6 | add player particles Done 7 | add wall jump 8 | add enemies 9 | 10 | add particles Done 11 | 12 | create maps with ldtk Done 13 | load maps in game Done 14 | 15 | 16 | enemy ideas: 17 | canon 18 | bombers --------------------------------------------------------------------------------