├── .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 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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
--------------------------------------------------------------------------------