aether-engine 1.1.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aether_engine-1.3.0/PKG-INFO +129 -0
- aether_engine-1.3.0/README.md +81 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/__init__.py +19 -2
- aether_engine-1.3.0/aether/audio/sfx.py +85 -0
- aether_engine-1.3.0/aether/core/_identity.py +67 -0
- aether_engine-1.3.0/aether/core/boot_splash.py +141 -0
- aether_engine-1.3.0/aether/core/branding_guard.py +103 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/engine.py +66 -6
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics2d/__init__.py +4 -3
- aether_engine-1.3.0/aether/physics/raycast.py +102 -0
- aether_engine-1.3.0/aether/resources/save_load.py +20 -0
- aether_engine-1.3.0/aether/ui/__init__.py +5 -0
- aether_engine-1.3.0/aether/ui/hud.py +65 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/ui/text.py +36 -29
- aether_engine-1.3.0/aether/utils/fonts.py +56 -0
- aether_engine-1.3.0/aether_engine.egg-info/PKG-INFO +129 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether_engine.egg-info/SOURCES.txt +7 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/setup.py +8 -2
- aether_engine-1.1.0/PKG-INFO +0 -155
- aether_engine-1.1.0/README.md +0 -107
- aether_engine-1.1.0/aether/physics/raycast.py +0 -25
- aether_engine-1.1.0/aether/utils/__init__.py +0 -0
- aether_engine-1.1.0/aether_engine.egg-info/PKG-INFO +0 -155
- {aether_engine-1.1.0 → aether_engine-1.3.0}/LICENSE +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/MANIFEST.in +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/ai/behavior_tree.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/ai/navmesh.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/animation/skeleton.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/assets/asset_pipeline.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/audio/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/audio/audio_manager.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/audio/music.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/audio/sound.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/achievement_system.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/application.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/config.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/console.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/event_bus.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/input.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/plugin_system.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/save_system.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/time_manager.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/core/window.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/editor/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/editor/__main__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/editor/gizmo.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/editor/imgui_integration.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/editor/model_editor.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/environment/environment_system.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/camera.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/first_person.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/framebuffer.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/light.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/lod_system.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/material.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/mesh.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/mesh_factory.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/particle_system.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/post_process.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/renderer.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/shader.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/shader_library.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/sky_atmosphere.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/terrain.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/texture.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/graphics/water.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/network/network_manager.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/physics/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/physics/bullet_backend.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/physics/collider.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/physics/physics2d.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/physics/physics3d.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/physics/physics_world.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/physics/rigidbody.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/platform/steam_api.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/resources/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/resources/asset_database.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/resources/loaders/model_loader.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/resources/loaders/obj_loader.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/resources/loaders/texture_loader.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/resources/resource_manager.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/resources/serialization.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/scene/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/scene/component.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/scene/entity.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/scene/scene.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/scene/scene_graph.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/scene/scene_manager.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/ui/button.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/ui/canvas.py +0 -0
- {aether_engine-1.1.0/aether/ui → aether_engine-1.3.0/aether/utils}/__init__.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/utils/logger.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/utils/math_utils.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/utils/object_pool.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether/utils/profiler.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether_engine.egg-info/dependency_links.txt +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether_engine.egg-info/entry_points.txt +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether_engine.egg-info/requires.txt +3 -3
- {aether_engine-1.1.0 → aether_engine-1.3.0}/aether_engine.egg-info/top_level.txt +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/examples/01_hello_cube.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/examples/02_sphere_scene.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/examples/03_first_person.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/examples/04_physics2d.py +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/pyproject.toml +0 -0
- {aether_engine-1.1.0 → aether_engine-1.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aether-engine
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, mandatory branding splash
|
|
5
|
+
Home-page: https://pypi.org/project/aether-engine/
|
|
6
|
+
Author: entiti937
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Games/Entertainment
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: numpy>=1.24
|
|
22
|
+
Requires-Dist: glfw>=2.6
|
|
23
|
+
Requires-Dist: PyOpenGL>=3.1.7
|
|
24
|
+
Requires-Dist: Pillow>=10.0
|
|
25
|
+
Provides-Extra: audio
|
|
26
|
+
Requires-Dist: pygame-ce>=2.5; extra == "audio"
|
|
27
|
+
Provides-Extra: physics
|
|
28
|
+
Requires-Dist: pymunk>=6.0; extra == "physics"
|
|
29
|
+
Provides-Extra: models
|
|
30
|
+
Requires-Dist: trimesh>=3.21; extra == "models"
|
|
31
|
+
Provides-Extra: full
|
|
32
|
+
Requires-Dist: trimesh>=3.21; extra == "full"
|
|
33
|
+
Requires-Dist: freetype-py>=2.4; extra == "full"
|
|
34
|
+
Requires-Dist: pygame-ce>=2.5; extra == "full"
|
|
35
|
+
Requires-Dist: pyyaml>=6.0; extra == "full"
|
|
36
|
+
Requires-Dist: pymunk>=6.0; extra == "full"
|
|
37
|
+
Requires-Dist: imageio>=2.31; extra == "full"
|
|
38
|
+
Dynamic: author
|
|
39
|
+
Dynamic: classifier
|
|
40
|
+
Dynamic: description
|
|
41
|
+
Dynamic: description-content-type
|
|
42
|
+
Dynamic: home-page
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
Dynamic: provides-extra
|
|
45
|
+
Dynamic: requires-dist
|
|
46
|
+
Dynamic: requires-python
|
|
47
|
+
Dynamic: summary
|
|
48
|
+
|
|
49
|
+
# Aether Engine
|
|
50
|
+
|
|
51
|
+
**3D / 2D игровой движок на Python + OpenGL**
|
|
52
|
+
**Разработчик:** entiti937 · **Версия:** 1.3.0 · **Лицензия:** MIT
|
|
53
|
+
**PyPI:** https://pypi.org/project/aether-engine/
|
|
54
|
+
|
|
55
|
+
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Установка
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install -U "aether-engine[full]"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Что нового в 1.3.0
|
|
66
|
+
|
|
67
|
+
- **Обязательный splash** при старте любой игры (чёрный экран, белый текст, entiti937)
|
|
68
|
+
- **Водяной знак** в каждом кадре + защита от удаления брендинга
|
|
69
|
+
- **Кириллица** — системные шрифты, UTF-8 консоль, полный Unicode в UI
|
|
70
|
+
- Компиляция защищённых модулей при сборке пакета
|
|
71
|
+
|
|
72
|
+
## Showcase-игра
|
|
73
|
+
|
|
74
|
+
Minecraft-like sandbox **на этом же движке**:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python games/aether_craft.py
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Там есть: воксельный мир, вид от 1 лица, ломать/ставить блоки, HUD, звуки, день/ночь, сейвы (F5/F9), урон и респавн.
|
|
81
|
+
|
|
82
|
+
## Другие запуски
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python -m aether.editor
|
|
86
|
+
python games/orb_rush.py
|
|
87
|
+
python examples/03_first_person.py
|
|
88
|
+
python examples/04_physics2d.py
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Минимальный код
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from aether import Engine, Scene, Entity, MeshFactory, Material, Camera
|
|
95
|
+
from aether.scene.component import Transform, MeshRenderer
|
|
96
|
+
|
|
97
|
+
engine = Engine(1280, 720, "Game by you — engine by entiti937")
|
|
98
|
+
scene = Scene("Main")
|
|
99
|
+
camera = Camera(position=(0, 2, 6), yaw=-90)
|
|
100
|
+
scene.set_main_camera(camera)
|
|
101
|
+
|
|
102
|
+
cube = Entity("Cube")
|
|
103
|
+
cube.add_component(Transform(position=(0, 1, 0)))
|
|
104
|
+
mat = Material(); mat.albedo[:] = (0.2, 0.85, 1.0)
|
|
105
|
+
cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
|
|
106
|
+
scene.spawn(cube)
|
|
107
|
+
|
|
108
|
+
engine.load_scene(scene)
|
|
109
|
+
engine.enable_first_person(camera)
|
|
110
|
+
engine.run()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Возможности 1.2
|
|
114
|
+
|
|
115
|
+
- PBR 3D + sprites 2D + HUD overlay
|
|
116
|
+
- FPS (`enable_first_person`) и free-look камера
|
|
117
|
+
- Физика 3D/2D + raycast
|
|
118
|
+
- `ModelLoader` (OBJ/GLB/glTF/STL/FBX/…)
|
|
119
|
+
- Встроенный voxel model editor
|
|
120
|
+
- Procedural SFX, JSON save/load
|
|
121
|
+
- Примеры и две игры в репозитории
|
|
122
|
+
|
|
123
|
+
## Документация
|
|
124
|
+
|
|
125
|
+
Все команды, API и туториалы: **[docs/GUIDE.md](docs/GUIDE.md)**
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
Aether Engine © **entiti937**
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Aether Engine
|
|
2
|
+
|
|
3
|
+
**3D / 2D игровой движок на Python + OpenGL**
|
|
4
|
+
**Разработчик:** entiti937 · **Версия:** 1.3.0 · **Лицензия:** MIT
|
|
5
|
+
**PyPI:** https://pypi.org/project/aether-engine/
|
|
6
|
+
|
|
7
|
+
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Установка
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -U "aether-engine[full]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Что нового в 1.3.0
|
|
18
|
+
|
|
19
|
+
- **Обязательный splash** при старте любой игры (чёрный экран, белый текст, entiti937)
|
|
20
|
+
- **Водяной знак** в каждом кадре + защита от удаления брендинга
|
|
21
|
+
- **Кириллица** — системные шрифты, UTF-8 консоль, полный Unicode в UI
|
|
22
|
+
- Компиляция защищённых модулей при сборке пакета
|
|
23
|
+
|
|
24
|
+
## Showcase-игра
|
|
25
|
+
|
|
26
|
+
Minecraft-like sandbox **на этом же движке**:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python games/aether_craft.py
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Там есть: воксельный мир, вид от 1 лица, ломать/ставить блоки, HUD, звуки, день/ночь, сейвы (F5/F9), урон и респавн.
|
|
33
|
+
|
|
34
|
+
## Другие запуски
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python -m aether.editor
|
|
38
|
+
python games/orb_rush.py
|
|
39
|
+
python examples/03_first_person.py
|
|
40
|
+
python examples/04_physics2d.py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Минимальный код
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from aether import Engine, Scene, Entity, MeshFactory, Material, Camera
|
|
47
|
+
from aether.scene.component import Transform, MeshRenderer
|
|
48
|
+
|
|
49
|
+
engine = Engine(1280, 720, "Game by you — engine by entiti937")
|
|
50
|
+
scene = Scene("Main")
|
|
51
|
+
camera = Camera(position=(0, 2, 6), yaw=-90)
|
|
52
|
+
scene.set_main_camera(camera)
|
|
53
|
+
|
|
54
|
+
cube = Entity("Cube")
|
|
55
|
+
cube.add_component(Transform(position=(0, 1, 0)))
|
|
56
|
+
mat = Material(); mat.albedo[:] = (0.2, 0.85, 1.0)
|
|
57
|
+
cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
|
|
58
|
+
scene.spawn(cube)
|
|
59
|
+
|
|
60
|
+
engine.load_scene(scene)
|
|
61
|
+
engine.enable_first_person(camera)
|
|
62
|
+
engine.run()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Возможности 1.2
|
|
66
|
+
|
|
67
|
+
- PBR 3D + sprites 2D + HUD overlay
|
|
68
|
+
- FPS (`enable_first_person`) и free-look камера
|
|
69
|
+
- Физика 3D/2D + raycast
|
|
70
|
+
- `ModelLoader` (OBJ/GLB/glTF/STL/FBX/…)
|
|
71
|
+
- Встроенный voxel model editor
|
|
72
|
+
- Procedural SFX, JSON save/load
|
|
73
|
+
- Примеры и две игры в репозитории
|
|
74
|
+
|
|
75
|
+
## Документация
|
|
76
|
+
|
|
77
|
+
Все команды, API и туториалы: **[docs/GUIDE.md](docs/GUIDE.md)**
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
Aether Engine © **entiti937**
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
"""Aether Engine — 3D / 2D game engine"""
|
|
1
|
+
"""Aether Engine — 3D / 2D game engine by entiti937"""
|
|
2
|
+
|
|
3
|
+
from aether.core._identity import verify_token, engine_name, creator
|
|
4
|
+
|
|
5
|
+
if not verify_token():
|
|
6
|
+
raise ImportError(
|
|
7
|
+
f"{engine_name()}: integrity check failed. Reinstall: pip install aether-engine"
|
|
8
|
+
)
|
|
2
9
|
|
|
3
10
|
from aether.core.engine import Engine
|
|
4
11
|
from aether.scene.scene import Scene
|
|
@@ -19,8 +26,12 @@ from aether.resources.loaders.model_loader import ModelLoader
|
|
|
19
26
|
from aether.physics import PhysicsWorld2D, PhysicsWorld3D, BoxCollider, SphereCollider
|
|
20
27
|
from aether.graphics2d import Renderer2D, Sprite, Camera2D
|
|
21
28
|
from aether.editor import ModelEditor
|
|
29
|
+
from aether.ui.hud import Hud, HudBar
|
|
30
|
+
from aether.audio.sfx import SfxBank
|
|
31
|
+
from aether.resources.save_load import save_json, load_json
|
|
32
|
+
from aether.physics.raycast import Raycaster
|
|
22
33
|
|
|
23
|
-
__version__ = "1.
|
|
34
|
+
__version__ = "1.3.0"
|
|
24
35
|
|
|
25
36
|
__all__ = [
|
|
26
37
|
"Engine",
|
|
@@ -45,5 +56,11 @@ __all__ = [
|
|
|
45
56
|
"Sprite",
|
|
46
57
|
"Camera2D",
|
|
47
58
|
"ModelEditor",
|
|
59
|
+
"Hud",
|
|
60
|
+
"HudBar",
|
|
61
|
+
"SfxBank",
|
|
62
|
+
"save_json",
|
|
63
|
+
"load_json",
|
|
64
|
+
"Raycaster",
|
|
48
65
|
"__version__",
|
|
49
66
|
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Procedural sound helpers (no asset files required)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import struct
|
|
6
|
+
import tempfile
|
|
7
|
+
import wave
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _write_wav(path: Path, samples, rate: int = 22050):
|
|
13
|
+
with wave.open(str(path), "w") as wf:
|
|
14
|
+
wf.setnchannels(1)
|
|
15
|
+
wf.setsampwidth(2)
|
|
16
|
+
wf.setframerate(rate)
|
|
17
|
+
frames = b"".join(struct.pack("<h", int(max(-1, min(1, s)) * 32767)) for s in samples)
|
|
18
|
+
wf.writeframes(frames)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_tone(
|
|
22
|
+
frequency: float = 440.0,
|
|
23
|
+
duration: float = 0.08,
|
|
24
|
+
volume: float = 0.35,
|
|
25
|
+
kind: str = "square",
|
|
26
|
+
rate: int = 22050,
|
|
27
|
+
) -> Path:
|
|
28
|
+
"""Generate a short WAV tone and return its path."""
|
|
29
|
+
n = int(rate * duration)
|
|
30
|
+
samples = []
|
|
31
|
+
for i in range(n):
|
|
32
|
+
t = i / rate
|
|
33
|
+
env = 1.0
|
|
34
|
+
if i < rate * 0.01:
|
|
35
|
+
env = i / (rate * 0.01)
|
|
36
|
+
elif i > n - rate * 0.02:
|
|
37
|
+
env = max(0.0, (n - i) / (rate * 0.02))
|
|
38
|
+
phase = 2 * math.pi * frequency * t
|
|
39
|
+
if kind == "sine":
|
|
40
|
+
val = math.sin(phase)
|
|
41
|
+
elif kind == "noise":
|
|
42
|
+
# deterministic pseudo-noise
|
|
43
|
+
val = math.sin(phase * 12.9898) * 2.0 % 2.0 - 1.0
|
|
44
|
+
else:
|
|
45
|
+
val = 1.0 if math.sin(phase) >= 0 else -1.0
|
|
46
|
+
samples.append(val * volume * env)
|
|
47
|
+
|
|
48
|
+
path = Path(tempfile.gettempdir()) / f"aether_tone_{int(frequency)}_{kind}.wav"
|
|
49
|
+
_write_wav(path, samples, rate)
|
|
50
|
+
return path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SfxBank:
|
|
54
|
+
"""Lazy bank of procedural SFX played through Engine.audio."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, audio_manager):
|
|
57
|
+
self.audio = audio_manager
|
|
58
|
+
self._cache = {}
|
|
59
|
+
|
|
60
|
+
def _get(self, key: str, factory):
|
|
61
|
+
if key not in self._cache:
|
|
62
|
+
path = factory()
|
|
63
|
+
# AudioManager.load_sound(name, path)
|
|
64
|
+
self._cache[key] = self.audio.load_sound(key, str(path))
|
|
65
|
+
return self._cache[key]
|
|
66
|
+
|
|
67
|
+
def play_break(self):
|
|
68
|
+
snd = self._get("break", lambda: make_tone(180, 0.07, 0.4, "noise"))
|
|
69
|
+
if snd:
|
|
70
|
+
self.audio.play_sound("break", volume=0.45)
|
|
71
|
+
|
|
72
|
+
def play_place(self):
|
|
73
|
+
snd = self._get("place", lambda: make_tone(520, 0.06, 0.3, "square"))
|
|
74
|
+
if snd:
|
|
75
|
+
self.audio.play_sound("place", volume=0.4)
|
|
76
|
+
|
|
77
|
+
def play_select(self):
|
|
78
|
+
snd = self._get("select", lambda: make_tone(760, 0.04, 0.25, "sine"))
|
|
79
|
+
if snd:
|
|
80
|
+
self.audio.play_sound("select", volume=0.35)
|
|
81
|
+
|
|
82
|
+
def play_hurt(self):
|
|
83
|
+
snd = self._get("hurt", lambda: make_tone(120, 0.12, 0.45, "square"))
|
|
84
|
+
if snd:
|
|
85
|
+
self.audio.play_sound("hurt", volume=0.5)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Encoded engine identity (integrity-protected, UTF-8 XOR)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
_K = 0xA7
|
|
5
|
+
|
|
6
|
+
_B = {
|
|
7
|
+
"n": bytes([230, 194, 211, 207, 194, 213, 135, 226, 201, 192, 206, 201, 194]),
|
|
8
|
+
"c": bytes([194, 201, 211, 206, 211, 206, 158, 148, 144]),
|
|
9
|
+
"l1": bytes([
|
|
10
|
+
119, 57, 118, 38, 119, 26, 119, 25, 119, 21, 119, 23, 119, 26, 119, 25,
|
|
11
|
+
135, 119, 26, 119, 23, 135, 119, 22, 119, 23, 119, 16, 119, 18, 135,
|
|
12
|
+
230, 194, 211, 207, 194, 213, 135, 226, 201, 192, 206, 201, 194,
|
|
13
|
+
]),
|
|
14
|
+
"l2": bytes([
|
|
15
|
+
119, 6, 119, 25, 119, 16, 119, 19, 119, 23, 118, 37, 119, 18, 119, 28,
|
|
16
|
+
118, 43, 157, 135, 194, 201, 211, 206, 211, 206, 158, 148, 144,
|
|
17
|
+
]),
|
|
18
|
+
"w": bytes([
|
|
19
|
+
230, 194, 211, 207, 194, 213, 135, 226, 201, 192, 206, 201, 194,
|
|
20
|
+
135, 219, 135, 194, 201, 211, 206, 211, 206, 158, 148, 144,
|
|
21
|
+
]),
|
|
22
|
+
"sig": bytes([
|
|
23
|
+
230, 194, 211, 207, 194, 213, 135, 226, 201, 192, 206, 201, 194,
|
|
24
|
+
135, 150, 137, 148, 137, 151,
|
|
25
|
+
]),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_EXPECTED = 0xAC3B2627
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _decode(blob: bytes) -> str:
|
|
32
|
+
return bytes(b ^ _K for b in blob).decode("utf-8")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def engine_name() -> str:
|
|
36
|
+
return _decode(_B["n"])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def creator() -> str:
|
|
40
|
+
return _decode(_B["c"])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def splash_line1() -> str:
|
|
44
|
+
return _decode(_B["l1"])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def splash_line2() -> str:
|
|
48
|
+
return _decode(_B["l2"])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def watermark() -> str:
|
|
52
|
+
return _decode(_B["w"])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def signature() -> str:
|
|
56
|
+
return _decode(_B["sig"])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def verify_token() -> bool:
|
|
60
|
+
try:
|
|
61
|
+
payload = "|".join([engine_name(), creator(), splash_line1(), splash_line2()])
|
|
62
|
+
h = 0
|
|
63
|
+
for ch in payload:
|
|
64
|
+
h = ((h * 131) + ord(ch)) & 0xFFFFFFFF
|
|
65
|
+
return h == _EXPECTED
|
|
66
|
+
except Exception:
|
|
67
|
+
return False
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Mandatory boot splash — white text on black, shown during engine preload."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import glfw
|
|
8
|
+
from OpenGL.GL import *
|
|
9
|
+
|
|
10
|
+
from aether.core._identity import creator, engine_name, splash_line1, splash_line2, verify_token
|
|
11
|
+
from aether.utils.fonts import resolve_system_font
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BootSplash:
|
|
15
|
+
"""Pre-load screen integrated into Engine startup (cannot be skipped)."""
|
|
16
|
+
|
|
17
|
+
MIN_SHOW_SEC = 2.2
|
|
18
|
+
|
|
19
|
+
def __init__(self, window):
|
|
20
|
+
self.window = window
|
|
21
|
+
self.width = window.width
|
|
22
|
+
self.height = window.height
|
|
23
|
+
self._text = None
|
|
24
|
+
self._font = None
|
|
25
|
+
self._started = time.monotonic()
|
|
26
|
+
self._progress = 0.0
|
|
27
|
+
self._ready = False
|
|
28
|
+
self._init_renderer()
|
|
29
|
+
self.draw(0.0, "Загрузка...")
|
|
30
|
+
|
|
31
|
+
def _init_renderer(self):
|
|
32
|
+
try:
|
|
33
|
+
from aether.ui.text import Font, TextRenderer
|
|
34
|
+
|
|
35
|
+
self._text = TextRenderer()
|
|
36
|
+
font_path = resolve_system_font()
|
|
37
|
+
if font_path:
|
|
38
|
+
self._font = Font(font_path, 34)
|
|
39
|
+
self._text.default_font = self._font
|
|
40
|
+
else:
|
|
41
|
+
self._font = None
|
|
42
|
+
except Exception as exc:
|
|
43
|
+
print(f"[BootSplash] Font init: {exc}")
|
|
44
|
+
self._text = None
|
|
45
|
+
|
|
46
|
+
def _draw_bar(self, progress: float):
|
|
47
|
+
"""Progress bar drawn via text (core-profile safe)."""
|
|
48
|
+
if not self._text or not self._font:
|
|
49
|
+
return
|
|
50
|
+
p = max(0.0, min(1.0, progress))
|
|
51
|
+
filled = int(p * 24)
|
|
52
|
+
bar = "[" + "#" * filled + "-" * (24 - filled) + f"] {int(p * 100)}%"
|
|
53
|
+
tw, _ = self._text.measure_text(bar, 0.75, self._font)
|
|
54
|
+
self._text.render_text(
|
|
55
|
+
bar,
|
|
56
|
+
self.width * 0.5 - tw * 0.5,
|
|
57
|
+
self.height * 0.62,
|
|
58
|
+
scale=0.75,
|
|
59
|
+
color=(0.85, 0.85, 0.85, 1.0),
|
|
60
|
+
font=self._font,
|
|
61
|
+
screen_width=self.width,
|
|
62
|
+
screen_height=self.height,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def draw(self, progress: float, status: str = ""):
|
|
66
|
+
if not verify_token():
|
|
67
|
+
raise RuntimeError("Aether Engine identity verification failed")
|
|
68
|
+
|
|
69
|
+
self._progress = max(self._progress, progress)
|
|
70
|
+
glViewport(0, 0, self.width, self.height)
|
|
71
|
+
glClearColor(0.0, 0.0, 0.0, 1.0)
|
|
72
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
73
|
+
|
|
74
|
+
line1 = splash_line1()
|
|
75
|
+
line2 = splash_line2()
|
|
76
|
+
|
|
77
|
+
if self._text and self._font:
|
|
78
|
+
scale = 1.0
|
|
79
|
+
tw1, _ = self._text.measure_text(line1, scale, self._font)
|
|
80
|
+
tw2, th2 = self._text.measure_text(line2, scale * 0.85, self._font)
|
|
81
|
+
cx = self.width * 0.5
|
|
82
|
+
cy = self.height * 0.42
|
|
83
|
+
self._text.render_text(
|
|
84
|
+
line1,
|
|
85
|
+
cx - tw1 * 0.5,
|
|
86
|
+
cy - 40,
|
|
87
|
+
scale=scale,
|
|
88
|
+
color=(1.0, 1.0, 1.0, 1.0),
|
|
89
|
+
font=self._font,
|
|
90
|
+
screen_width=self.width,
|
|
91
|
+
screen_height=self.height,
|
|
92
|
+
)
|
|
93
|
+
self._text.render_text(
|
|
94
|
+
line2,
|
|
95
|
+
cx - tw2 * 0.5,
|
|
96
|
+
cy + 8,
|
|
97
|
+
scale=0.85,
|
|
98
|
+
color=(1.0, 1.0, 1.0, 0.92),
|
|
99
|
+
font=self._font,
|
|
100
|
+
screen_width=self.width,
|
|
101
|
+
screen_height=self.height,
|
|
102
|
+
)
|
|
103
|
+
if status:
|
|
104
|
+
ts, _ = self._text.measure_text(status, 0.7, self._font)
|
|
105
|
+
self._text.render_text(
|
|
106
|
+
status,
|
|
107
|
+
cx - ts * 0.5,
|
|
108
|
+
cy + th2 + 36,
|
|
109
|
+
scale=0.7,
|
|
110
|
+
color=(0.75, 0.75, 0.75, 1.0),
|
|
111
|
+
font=self._font,
|
|
112
|
+
screen_width=self.width,
|
|
113
|
+
screen_height=self.height,
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
# ASCII fallback if font missing
|
|
117
|
+
print(f"[BootSplash] {line1}")
|
|
118
|
+
print(f"[BootSplash] {line2}")
|
|
119
|
+
if status:
|
|
120
|
+
print(f"[BootSplash] {status}")
|
|
121
|
+
|
|
122
|
+
self._draw_bar(self._progress)
|
|
123
|
+
self.window.swap_buffers()
|
|
124
|
+
glfw.poll_events()
|
|
125
|
+
|
|
126
|
+
def step(self, progress: float, status: str):
|
|
127
|
+
self.draw(progress, status)
|
|
128
|
+
time.sleep(0.02)
|
|
129
|
+
|
|
130
|
+
def finish(self):
|
|
131
|
+
elapsed = time.monotonic() - self._started
|
|
132
|
+
if elapsed < self.MIN_SHOW_SEC:
|
|
133
|
+
remaining = self.MIN_SHOW_SEC - elapsed
|
|
134
|
+
steps = max(1, int(remaining / 0.05))
|
|
135
|
+
for i in range(steps):
|
|
136
|
+
p = self._progress + (1.0 - self._progress) * ((i + 1) / steps)
|
|
137
|
+
self.draw(p, "Готово")
|
|
138
|
+
time.sleep(remaining / steps)
|
|
139
|
+
else:
|
|
140
|
+
self.draw(1.0, "Готово")
|
|
141
|
+
self._ready = True
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Runtime branding guard — watermark + integrity checks (mandatory)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from aether.core._identity import creator, engine_name, verify_token, watermark
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BrandingGuard:
|
|
8
|
+
"""Ensures engine attribution is always visible."""
|
|
9
|
+
|
|
10
|
+
_instances = 0
|
|
11
|
+
_tamper_count = 0
|
|
12
|
+
|
|
13
|
+
def __init__(self, engine):
|
|
14
|
+
self.engine = engine
|
|
15
|
+
self._frame = 0
|
|
16
|
+
self._font = None
|
|
17
|
+
self._text = None
|
|
18
|
+
self._force_overlay = False
|
|
19
|
+
BrandingGuard._instances += 1
|
|
20
|
+
self._init_text()
|
|
21
|
+
|
|
22
|
+
def _init_text(self):
|
|
23
|
+
try:
|
|
24
|
+
from aether.ui.text import Font, TextRenderer
|
|
25
|
+
from aether.utils.fonts import resolve_system_font
|
|
26
|
+
|
|
27
|
+
path = resolve_system_font()
|
|
28
|
+
if path:
|
|
29
|
+
self._text = TextRenderer()
|
|
30
|
+
self._font = Font(path, 16)
|
|
31
|
+
self._text.default_font = self._font
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def assert_integrity(cls, engine) -> bool:
|
|
37
|
+
if not verify_token():
|
|
38
|
+
cls._tamper_count += 1
|
|
39
|
+
return False
|
|
40
|
+
if getattr(engine, "_brand_bypass", False):
|
|
41
|
+
cls._tamper_count += 1
|
|
42
|
+
engine._brand_bypass = False
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def draw_watermark(self):
|
|
47
|
+
"""Subtle persistent watermark (bottom-right)."""
|
|
48
|
+
if not BrandingGuard.assert_integrity(self.engine):
|
|
49
|
+
self._force_overlay = True
|
|
50
|
+
|
|
51
|
+
if not self._text or not self._font:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
from OpenGL.GL import glDisable, glEnable, GL_DEPTH_TEST
|
|
55
|
+
|
|
56
|
+
w = self.engine.config.graphics.width
|
|
57
|
+
h = self.engine.config.graphics.height
|
|
58
|
+
label = watermark()
|
|
59
|
+
tw, _ = self._text.measure_text(label, 1.0, self._font)
|
|
60
|
+
alpha = 0.55 if not self._force_overlay else 0.95
|
|
61
|
+
self._text.render_text(
|
|
62
|
+
label,
|
|
63
|
+
w - tw - 12,
|
|
64
|
+
h - 22,
|
|
65
|
+
scale=1.0,
|
|
66
|
+
color=(1.0, 1.0, 1.0, alpha),
|
|
67
|
+
font=self._font,
|
|
68
|
+
screen_width=w,
|
|
69
|
+
screen_height=h,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if self._force_overlay:
|
|
73
|
+
msg1 = f"Основано на базе {engine_name()}"
|
|
74
|
+
msg2 = f"Создатель: {creator()}"
|
|
75
|
+
self._text.render_text(
|
|
76
|
+
msg1,
|
|
77
|
+
w * 0.5 - 180,
|
|
78
|
+
h * 0.5 - 20,
|
|
79
|
+
scale=1.2,
|
|
80
|
+
color=(1, 1, 1, 0.9),
|
|
81
|
+
font=self._font,
|
|
82
|
+
screen_width=w,
|
|
83
|
+
screen_height=h,
|
|
84
|
+
)
|
|
85
|
+
self._text.render_text(
|
|
86
|
+
msg2,
|
|
87
|
+
w * 0.5 - 120,
|
|
88
|
+
h * 0.5 + 18,
|
|
89
|
+
scale=1.0,
|
|
90
|
+
color=(1, 1, 1, 0.85),
|
|
91
|
+
font=self._font,
|
|
92
|
+
screen_width=w,
|
|
93
|
+
screen_height=h,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
glDisable(GL_DEPTH_TEST)
|
|
97
|
+
glEnable(GL_DEPTH_TEST)
|
|
98
|
+
|
|
99
|
+
def tick(self):
|
|
100
|
+
self._frame += 1
|
|
101
|
+
if self._frame % 90 == 0:
|
|
102
|
+
if not verify_token():
|
|
103
|
+
self._force_overlay = True
|