aether-engine 1.2.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.2.0/aether_engine.egg-info → aether_engine-1.3.0}/PKG-INFO +14 -7
- {aether_engine-1.2.0 → aether_engine-1.3.0}/README.md +9 -2
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/__init__.py +10 -2
- 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.2.0 → aether_engine-1.3.0}/aether/core/engine.py +53 -6
- {aether_engine-1.2.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.2.0 → aether_engine-1.3.0/aether_engine.egg-info}/PKG-INFO +14 -7
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/SOURCES.txt +4 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/setup.py +8 -2
- {aether_engine-1.2.0 → aether_engine-1.3.0}/LICENSE +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/MANIFEST.in +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ai/behavior_tree.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ai/navmesh.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/animation/skeleton.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/assets/asset_pipeline.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/audio_manager.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/music.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/sfx.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/sound.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/achievement_system.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/application.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/config.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/console.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/event_bus.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/input.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/plugin_system.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/save_system.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/time_manager.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/window.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/__main__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/gizmo.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/imgui_integration.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/model_editor.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/environment/environment_system.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/camera.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/first_person.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/framebuffer.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/light.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/lod_system.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/material.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/mesh.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/mesh_factory.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/particle_system.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/post_process.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/renderer.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/shader.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/shader_library.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/sky_atmosphere.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/terrain.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/texture.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/water.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics2d/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/network/network_manager.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/bullet_backend.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/collider.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/physics2d.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/physics3d.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/physics_world.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/raycast.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/rigidbody.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/platform/steam_api.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/asset_database.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/loaders/model_loader.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/loaders/obj_loader.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/loaders/texture_loader.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/resource_manager.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/save_load.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/serialization.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/component.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/entity.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/scene.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/scene_graph.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/scene_manager.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/button.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/canvas.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/hud.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/__init__.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/logger.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/math_utils.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/object_pool.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/profiler.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/dependency_links.txt +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/entry_points.txt +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/requires.txt +3 -3
- {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/top_level.txt +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/01_hello_cube.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/02_sphere_scene.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/03_first_person.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/04_physics2d.py +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/pyproject.toml +0 -0
- {aether_engine-1.2.0 → aether_engine-1.3.0}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aether-engine
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS,
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, mandatory branding splash
|
|
5
5
|
Home-page: https://pypi.org/project/aether-engine/
|
|
6
6
|
Author: entiti937
|
|
7
7
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -29,11 +29,11 @@ Requires-Dist: pymunk>=6.0; extra == "physics"
|
|
|
29
29
|
Provides-Extra: models
|
|
30
30
|
Requires-Dist: trimesh>=3.21; extra == "models"
|
|
31
31
|
Provides-Extra: full
|
|
32
|
-
Requires-Dist: pyyaml>=6.0; extra == "full"
|
|
33
|
-
Requires-Dist: pygame-ce>=2.5; extra == "full"
|
|
34
32
|
Requires-Dist: trimesh>=3.21; extra == "full"
|
|
35
|
-
Requires-Dist: pymunk>=6.0; extra == "full"
|
|
36
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
37
|
Requires-Dist: imageio>=2.31; extra == "full"
|
|
38
38
|
Dynamic: author
|
|
39
39
|
Dynamic: classifier
|
|
@@ -49,7 +49,7 @@ Dynamic: summary
|
|
|
49
49
|
# Aether Engine
|
|
50
50
|
|
|
51
51
|
**3D / 2D игровой движок на Python + OpenGL**
|
|
52
|
-
**Разработчик:** entiti937 · **Версия:** 1.
|
|
52
|
+
**Разработчик:** entiti937 · **Версия:** 1.3.0 · **Лицензия:** MIT
|
|
53
53
|
**PyPI:** https://pypi.org/project/aether-engine/
|
|
54
54
|
|
|
55
55
|
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
@@ -62,7 +62,14 @@ Dynamic: summary
|
|
|
62
62
|
pip install -U "aether-engine[full]"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## Что нового в 1.3.0
|
|
66
|
+
|
|
67
|
+
- **Обязательный splash** при старте любой игры (чёрный экран, белый текст, entiti937)
|
|
68
|
+
- **Водяной знак** в каждом кадре + защита от удаления брендинга
|
|
69
|
+
- **Кириллица** — системные шрифты, UTF-8 консоль, полный Unicode в UI
|
|
70
|
+
- Компиляция защищённых модулей при сборке пакета
|
|
71
|
+
|
|
72
|
+
## Showcase-игра
|
|
66
73
|
|
|
67
74
|
Minecraft-like sandbox **на этом же движке**:
|
|
68
75
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Aether Engine
|
|
2
2
|
|
|
3
3
|
**3D / 2D игровой движок на Python + OpenGL**
|
|
4
|
-
**Разработчик:** entiti937 · **Версия:** 1.
|
|
4
|
+
**Разработчик:** entiti937 · **Версия:** 1.3.0 · **Лицензия:** MIT
|
|
5
5
|
**PyPI:** https://pypi.org/project/aether-engine/
|
|
6
6
|
|
|
7
7
|
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
@@ -14,7 +14,14 @@
|
|
|
14
14
|
pip install -U "aether-engine[full]"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Что нового в 1.3.0
|
|
18
|
+
|
|
19
|
+
- **Обязательный splash** при старте любой игры (чёрный экран, белый текст, entiti937)
|
|
20
|
+
- **Водяной знак** в каждом кадре + защита от удаления брендинга
|
|
21
|
+
- **Кириллица** — системные шрифты, UTF-8 консоль, полный Unicode в UI
|
|
22
|
+
- Компиляция защищённых модулей при сборке пакета
|
|
23
|
+
|
|
24
|
+
## Showcase-игра
|
|
18
25
|
|
|
19
26
|
Minecraft-like sandbox **на этом же движке**:
|
|
20
27
|
|
|
@@ -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
|
|
@@ -18,12 +25,13 @@ from aether.graphics.first_person import FirstPersonController
|
|
|
18
25
|
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
|
|
28
|
+
from aether.editor import ModelEditor
|
|
21
29
|
from aether.ui.hud import Hud, HudBar
|
|
22
30
|
from aether.audio.sfx import SfxBank
|
|
23
31
|
from aether.resources.save_load import save_json, load_json
|
|
24
32
|
from aether.physics.raycast import Raycaster
|
|
25
33
|
|
|
26
|
-
__version__ = "1.
|
|
34
|
+
__version__ = "1.3.0"
|
|
27
35
|
|
|
28
36
|
__all__ = [
|
|
29
37
|
"Engine",
|
|
@@ -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
|
|
@@ -24,6 +24,10 @@ from aether.resources.resource_manager import ResourceManager
|
|
|
24
24
|
from aether.resources.asset_database import AssetDatabase
|
|
25
25
|
from aether.graphics.shader_library import ShaderLibrary
|
|
26
26
|
from aether.graphics2d import Renderer2D
|
|
27
|
+
from aether.core._identity import verify_token, signature
|
|
28
|
+
from aether.core.branding_guard import BrandingGuard
|
|
29
|
+
from aether.core.boot_splash import BootSplash
|
|
30
|
+
from aether.utils.fonts import configure_stdio_utf8
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
class Engine:
|
|
@@ -47,6 +51,10 @@ class Engine:
|
|
|
47
51
|
return
|
|
48
52
|
self._initialized = True
|
|
49
53
|
|
|
54
|
+
if not verify_token():
|
|
55
|
+
raise RuntimeError("Aether Engine: integrity verification failed")
|
|
56
|
+
|
|
57
|
+
configure_stdio_utf8()
|
|
50
58
|
self._physics_accumulator = 0.0
|
|
51
59
|
|
|
52
60
|
self.config = config or EngineConfig()
|
|
@@ -54,7 +62,7 @@ class Engine:
|
|
|
54
62
|
self.config.graphics.width = width
|
|
55
63
|
self.config.graphics.height = height
|
|
56
64
|
|
|
57
|
-
print("[Aether]
|
|
65
|
+
print(f"[Aether] {signature()} — entiti937")
|
|
58
66
|
|
|
59
67
|
self.window = Window(
|
|
60
68
|
self.config.graphics.width,
|
|
@@ -64,35 +72,48 @@ class Engine:
|
|
|
64
72
|
self.config.graphics.msaa_samples,
|
|
65
73
|
)
|
|
66
74
|
|
|
75
|
+
splash = BootSplash(self.window)
|
|
76
|
+
splash.step(0.04, "Инициализация окна...")
|
|
77
|
+
|
|
67
78
|
self.input = Input(self.window)
|
|
79
|
+
splash.step(0.10, "Система ввода...")
|
|
80
|
+
|
|
68
81
|
self.time = Time()
|
|
69
82
|
self.event_bus = EventBus()
|
|
83
|
+
splash.step(0.16, "Время и события...")
|
|
84
|
+
|
|
70
85
|
self.resource_manager = ResourceManager()
|
|
71
86
|
self.asset_database = AssetDatabase()
|
|
87
|
+
splash.step(0.24, "Ресурсы...")
|
|
72
88
|
|
|
73
89
|
self.shader_library = ShaderLibrary()
|
|
74
90
|
self.shader_library.preload_all()
|
|
91
|
+
splash.step(0.40, "Шейдеры OpenGL...")
|
|
75
92
|
|
|
76
|
-
self.mode = "3d"
|
|
93
|
+
self.mode = "3d"
|
|
77
94
|
self.renderer = Renderer(
|
|
78
95
|
self.config.graphics.width,
|
|
79
|
-
self.config.graphics.height
|
|
96
|
+
self.config.graphics.height,
|
|
80
97
|
)
|
|
81
98
|
self.renderer2d = Renderer2D(
|
|
82
99
|
self.config.graphics.width,
|
|
83
|
-
self.config.graphics.height
|
|
100
|
+
self.config.graphics.height,
|
|
84
101
|
)
|
|
102
|
+
splash.step(0.58, "Графика 3D / 2D...")
|
|
85
103
|
|
|
86
104
|
self.physics = PhysicsWorld(self.config.physics.gravity)
|
|
87
105
|
self.physics3d = PhysicsWorld3D(self.config.physics.gravity)
|
|
88
106
|
self.physics2d = PhysicsWorld2D(gravity=(0.0, -980.0))
|
|
107
|
+
splash.step(0.72, "Физика...")
|
|
89
108
|
|
|
90
109
|
self.audio = AudioManager()
|
|
91
110
|
self.audio.master_volume = self.config.audio.master_volume
|
|
111
|
+
splash.step(0.82, "Аудио...")
|
|
92
112
|
|
|
93
113
|
self.plugin_manager = PluginManager()
|
|
94
114
|
self.plugin_manager.discover_plugins()
|
|
95
115
|
self.plugin_manager.initialize_all(self)
|
|
116
|
+
splash.step(0.92, "Плагины...")
|
|
96
117
|
|
|
97
118
|
self.scene: Optional[Scene] = None
|
|
98
119
|
self._running = False
|
|
@@ -101,12 +122,16 @@ class Engine:
|
|
|
101
122
|
self.free_camera = True
|
|
102
123
|
self.fps_controller: Optional[FirstPersonController] = None
|
|
103
124
|
self._update_callbacks = []
|
|
104
|
-
self.draw_hud = False
|
|
125
|
+
self.draw_hud = False
|
|
126
|
+
self._branding = BrandingGuard(self)
|
|
105
127
|
|
|
106
128
|
self.event_bus.subscribe("window_resize", self._on_window_resize)
|
|
107
129
|
self.event_bus.subscribe("engine_pause", self._on_pause)
|
|
108
130
|
self.event_bus.subscribe("engine_quit", self._on_quit)
|
|
109
131
|
|
|
132
|
+
splash.step(0.98, "Запуск...")
|
|
133
|
+
splash.finish()
|
|
134
|
+
|
|
110
135
|
print("[Aether] Engine initialized successfully!")
|
|
111
136
|
try:
|
|
112
137
|
print(f"[Aether] OpenGL {glGetString(GL_VERSION).decode()}")
|
|
@@ -140,6 +165,8 @@ class Engine:
|
|
|
140
165
|
print(f"[Aether] Scene loaded: {scene.name}")
|
|
141
166
|
|
|
142
167
|
def run(self):
|
|
168
|
+
if not verify_token():
|
|
169
|
+
raise RuntimeError("Aether Engine: branding integrity check failed")
|
|
143
170
|
print("[Aether] Starting game loop...")
|
|
144
171
|
self._running = True
|
|
145
172
|
|
|
@@ -246,9 +273,11 @@ class Engine:
|
|
|
246
273
|
try:
|
|
247
274
|
self._update_title()
|
|
248
275
|
except Exception:
|
|
249
|
-
print("[Aether] ERROR while updating title")
|
|
250
276
|
traceback.print_exc()
|
|
251
277
|
|
|
278
|
+
if self._frame_count % 180 == 0 and not BrandingGuard.assert_integrity(self):
|
|
279
|
+
self._branding._force_overlay = True
|
|
280
|
+
|
|
252
281
|
profiler.end_frame()
|
|
253
282
|
self._frame_count += 1
|
|
254
283
|
|
|
@@ -331,6 +360,7 @@ class Engine:
|
|
|
331
360
|
self.renderer2d.begin_frame(clear=True)
|
|
332
361
|
self.renderer2d.render()
|
|
333
362
|
self.renderer2d.end_frame()
|
|
363
|
+
self._render_branding()
|
|
334
364
|
return
|
|
335
365
|
|
|
336
366
|
self.renderer.begin_frame()
|
|
@@ -350,6 +380,23 @@ class Engine:
|
|
|
350
380
|
self.renderer2d.render()
|
|
351
381
|
self.renderer2d.end_frame()
|
|
352
382
|
|
|
383
|
+
self._render_branding()
|
|
384
|
+
|
|
385
|
+
def _render_branding(self):
|
|
386
|
+
"""Mandatory watermark — always drawn."""
|
|
387
|
+
if not hasattr(self, "_branding") or self._branding is None:
|
|
388
|
+
return
|
|
389
|
+
try:
|
|
390
|
+
from OpenGL.GL import glDisable, glEnable, GL_BLEND, GL_DEPTH_TEST
|
|
391
|
+
|
|
392
|
+
glDisable(GL_DEPTH_TEST)
|
|
393
|
+
glEnable(GL_BLEND)
|
|
394
|
+
self._branding.draw_watermark()
|
|
395
|
+
self._branding.tick()
|
|
396
|
+
glEnable(GL_DEPTH_TEST)
|
|
397
|
+
except Exception:
|
|
398
|
+
pass
|
|
399
|
+
|
|
353
400
|
def on_update(self, callback):
|
|
354
401
|
"""Регистрирует callback(dt), вызываемый каждый кадр."""
|
|
355
402
|
self._update_callbacks.append(callback)
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
"""Рендеринг текста с использованием
|
|
1
|
+
"""Рендеринг текста с использованием Pillow (кириллица + латиница)."""
|
|
2
|
+
import ctypes
|
|
2
3
|
import numpy as np
|
|
3
4
|
from OpenGL.GL import *
|
|
4
5
|
import os
|
|
6
|
+
import sys
|
|
5
7
|
from typing import Dict, Tuple
|
|
6
8
|
|
|
9
|
+
from aether.utils.fonts import resolve_system_font
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
class Character:
|
|
9
13
|
"""Глиф символа"""
|
|
@@ -31,35 +35,38 @@ class Font:
|
|
|
31
35
|
"""Загружает шрифт"""
|
|
32
36
|
try:
|
|
33
37
|
from PIL import Image, ImageDraw, ImageFont
|
|
34
|
-
|
|
38
|
+
|
|
39
|
+
if not path or not os.path.isfile(path):
|
|
40
|
+
path = resolve_system_font()
|
|
41
|
+
if not path:
|
|
42
|
+
print("[Font] System TTF not found, using bitmap fallback")
|
|
43
|
+
self._create_default_font()
|
|
44
|
+
return
|
|
45
|
+
|
|
35
46
|
font = ImageFont.truetype(path, size)
|
|
36
|
-
|
|
37
|
-
#
|
|
47
|
+
|
|
48
|
+
# Latin + Cyrillic + punctuation
|
|
49
|
+
chars = set()
|
|
38
50
|
for c in range(32, 127):
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# Дополнительные символы
|
|
48
|
-
for char in "—«»…№":
|
|
51
|
+
chars.add(chr(c))
|
|
52
|
+
for c in range(0x0400, 0x04FF):
|
|
53
|
+
chars.add(chr(c))
|
|
54
|
+
for ch in "—«»…№:.,!?-+()":
|
|
55
|
+
chars.add(ch)
|
|
56
|
+
|
|
57
|
+
for char in sorted(chars):
|
|
49
58
|
self._load_character(char, font)
|
|
50
|
-
|
|
51
|
-
# Высота строки
|
|
59
|
+
|
|
52
60
|
self.line_height = size + 10
|
|
53
|
-
|
|
54
61
|
print(f"[Font] Loaded: {path} ({size}px, {len(self.characters)} chars)")
|
|
55
|
-
|
|
56
|
-
except
|
|
57
|
-
print("[Font]
|
|
62
|
+
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
print(f"[Font] Load failed ({exc}), using default font")
|
|
58
65
|
self._create_default_font()
|
|
59
66
|
|
|
60
67
|
def _load_character(self, char: str, font):
|
|
61
68
|
"""Загружает один символ"""
|
|
62
|
-
from PIL import Image
|
|
69
|
+
from PIL import Image, ImageDraw
|
|
63
70
|
|
|
64
71
|
# Получаем размеры символа
|
|
65
72
|
bbox = font.getbbox(char)
|
|
@@ -157,7 +164,7 @@ class TextRenderer:
|
|
|
157
164
|
def _setup_shader(self):
|
|
158
165
|
"""Настройка шейдера для текста"""
|
|
159
166
|
vertex_shader = """
|
|
160
|
-
#version
|
|
167
|
+
#version 330 core
|
|
161
168
|
layout(location = 0) in vec4 vertex; // xy: pos, zw: uv
|
|
162
169
|
|
|
163
170
|
out vec2 TexCoords;
|
|
@@ -171,7 +178,7 @@ class TextRenderer:
|
|
|
171
178
|
"""
|
|
172
179
|
|
|
173
180
|
fragment_shader = """
|
|
174
|
-
#version
|
|
181
|
+
#version 330 core
|
|
175
182
|
in vec2 TexCoords;
|
|
176
183
|
out vec4 color;
|
|
177
184
|
|
|
@@ -179,8 +186,8 @@ class TextRenderer:
|
|
|
179
186
|
uniform vec4 textColor;
|
|
180
187
|
|
|
181
188
|
void main() {
|
|
182
|
-
|
|
183
|
-
color = textColor *
|
|
189
|
+
float a = texture(text, TexCoords).a;
|
|
190
|
+
color = vec4(textColor.rgb, textColor.a * a);
|
|
184
191
|
}
|
|
185
192
|
"""
|
|
186
193
|
|
|
@@ -253,9 +260,9 @@ class TextRenderer:
|
|
|
253
260
|
glDrawArrays(GL_TRIANGLES, 0, 6)
|
|
254
261
|
|
|
255
262
|
# Сдвигаем позицию для следующего символа
|
|
256
|
-
x +=
|
|
257
|
-
|
|
258
|
-
total_width +=
|
|
263
|
+
x += ch.advance * scale
|
|
264
|
+
|
|
265
|
+
total_width += ch.advance * scale
|
|
259
266
|
total_height = max(total_height, ch.size[1] * scale)
|
|
260
267
|
|
|
261
268
|
glBindVertexArray(0)
|
|
@@ -280,7 +287,7 @@ class TextRenderer:
|
|
|
280
287
|
for char in text:
|
|
281
288
|
if char in font.characters:
|
|
282
289
|
ch = font.characters[char]
|
|
283
|
-
width +=
|
|
290
|
+
width += ch.advance * scale
|
|
284
291
|
height = max(height, ch.size[1] * scale)
|
|
285
292
|
|
|
286
293
|
return (width, height)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""System font resolver with Cyrillic / Latin support."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _candidates() -> list:
|
|
10
|
+
paths = []
|
|
11
|
+
if sys.platform == "win32":
|
|
12
|
+
windir = os.environ.get("WINDIR", r"C:\Windows")
|
|
13
|
+
fonts = Path(windir) / "Fonts"
|
|
14
|
+
paths += [
|
|
15
|
+
fonts / "segoeui.ttf",
|
|
16
|
+
fonts / "arial.ttf",
|
|
17
|
+
fonts / "calibri.ttf",
|
|
18
|
+
fonts / "tahoma.ttf",
|
|
19
|
+
]
|
|
20
|
+
elif sys.platform == "darwin":
|
|
21
|
+
paths += [
|
|
22
|
+
Path("/System/Library/Fonts/Supplemental/Arial.ttf"),
|
|
23
|
+
Path("/Library/Fonts/Arial Unicode.ttf"),
|
|
24
|
+
Path("/System/Library/Fonts/Supplemental/Arial Unicode.ttf"),
|
|
25
|
+
]
|
|
26
|
+
else:
|
|
27
|
+
paths += [
|
|
28
|
+
Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
|
|
29
|
+
Path("/usr/share/fonts/TTF/DejaVuSans.ttf"),
|
|
30
|
+
Path("/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"),
|
|
31
|
+
Path("/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf"),
|
|
32
|
+
]
|
|
33
|
+
return paths
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resolve_system_font() -> str:
|
|
37
|
+
"""Return path to a TTF that supports Cyrillic, or empty string."""
|
|
38
|
+
for p in _candidates():
|
|
39
|
+
if p.exists():
|
|
40
|
+
return str(p)
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def configure_stdio_utf8():
|
|
45
|
+
"""Fix console mojibake for Cyrillic on Windows."""
|
|
46
|
+
for stream in (sys.stdout, sys.stderr):
|
|
47
|
+
try:
|
|
48
|
+
if hasattr(stream, "reconfigure"):
|
|
49
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
if sys.platform == "win32":
|
|
53
|
+
try:
|
|
54
|
+
os.system("chcp 65001 >nul 2>&1")
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aether-engine
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS,
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, mandatory branding splash
|
|
5
5
|
Home-page: https://pypi.org/project/aether-engine/
|
|
6
6
|
Author: entiti937
|
|
7
7
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -29,11 +29,11 @@ Requires-Dist: pymunk>=6.0; extra == "physics"
|
|
|
29
29
|
Provides-Extra: models
|
|
30
30
|
Requires-Dist: trimesh>=3.21; extra == "models"
|
|
31
31
|
Provides-Extra: full
|
|
32
|
-
Requires-Dist: pyyaml>=6.0; extra == "full"
|
|
33
|
-
Requires-Dist: pygame-ce>=2.5; extra == "full"
|
|
34
32
|
Requires-Dist: trimesh>=3.21; extra == "full"
|
|
35
|
-
Requires-Dist: pymunk>=6.0; extra == "full"
|
|
36
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
37
|
Requires-Dist: imageio>=2.31; extra == "full"
|
|
38
38
|
Dynamic: author
|
|
39
39
|
Dynamic: classifier
|
|
@@ -49,7 +49,7 @@ Dynamic: summary
|
|
|
49
49
|
# Aether Engine
|
|
50
50
|
|
|
51
51
|
**3D / 2D игровой движок на Python + OpenGL**
|
|
52
|
-
**Разработчик:** entiti937 · **Версия:** 1.
|
|
52
|
+
**Разработчик:** entiti937 · **Версия:** 1.3.0 · **Лицензия:** MIT
|
|
53
53
|
**PyPI:** https://pypi.org/project/aether-engine/
|
|
54
54
|
|
|
55
55
|
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
@@ -62,7 +62,14 @@ Dynamic: summary
|
|
|
62
62
|
pip install -U "aether-engine[full]"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## Что нового в 1.3.0
|
|
66
|
+
|
|
67
|
+
- **Обязательный splash** при старте любой игры (чёрный экран, белый текст, entiti937)
|
|
68
|
+
- **Водяной знак** в каждом кадре + защита от удаления брендинга
|
|
69
|
+
- **Кириллица** — системные шрифты, UTF-8 консоль, полный Unicode в UI
|
|
70
|
+
- Компиляция защищённых модулей при сборке пакета
|
|
71
|
+
|
|
72
|
+
## Showcase-игра
|
|
66
73
|
|
|
67
74
|
Minecraft-like sandbox **на этом же движке**:
|
|
68
75
|
|
|
@@ -14,8 +14,11 @@ aether/audio/music.py
|
|
|
14
14
|
aether/audio/sfx.py
|
|
15
15
|
aether/audio/sound.py
|
|
16
16
|
aether/core/__init__.py
|
|
17
|
+
aether/core/_identity.py
|
|
17
18
|
aether/core/achievement_system.py
|
|
18
19
|
aether/core/application.py
|
|
20
|
+
aether/core/boot_splash.py
|
|
21
|
+
aether/core/branding_guard.py
|
|
19
22
|
aether/core/config.py
|
|
20
23
|
aether/core/console.py
|
|
21
24
|
aether/core/engine.py
|
|
@@ -80,6 +83,7 @@ aether/ui/canvas.py
|
|
|
80
83
|
aether/ui/hud.py
|
|
81
84
|
aether/ui/text.py
|
|
82
85
|
aether/utils/__init__.py
|
|
86
|
+
aether/utils/fonts.py
|
|
83
87
|
aether/utils/logger.py
|
|
84
88
|
aether/utils/math_utils.py
|
|
85
89
|
aether/utils/object_pool.py
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
from setuptools import setup, find_packages
|
|
3
3
|
import sys
|
|
4
4
|
import importlib.util
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Add scripts for custom build
|
|
8
|
+
sys.path.insert(0, str(Path(__file__).parent / "scripts"))
|
|
9
|
+
from build_protected import BuildPyProtected
|
|
5
10
|
|
|
6
11
|
with open("README.md", "r", encoding="utf-8") as f:
|
|
7
12
|
long_description = f.read()
|
|
@@ -49,9 +54,9 @@ extras_require = {
|
|
|
49
54
|
|
|
50
55
|
setup(
|
|
51
56
|
name="aether-engine",
|
|
52
|
-
version="1.
|
|
57
|
+
version="1.3.0",
|
|
53
58
|
author="entiti937",
|
|
54
|
-
description="3D/2D game engine by entiti937 — OpenGL, physics, FPS,
|
|
59
|
+
description="3D/2D game engine by entiti937 — OpenGL, physics, FPS, mandatory branding splash",
|
|
55
60
|
long_description=long_description,
|
|
56
61
|
long_description_content_type="text/markdown",
|
|
57
62
|
url="https://pypi.org/project/aether-engine/",
|
|
@@ -73,6 +78,7 @@ setup(
|
|
|
73
78
|
"aether-editor=aether.editor.model_editor:main",
|
|
74
79
|
],
|
|
75
80
|
},
|
|
81
|
+
cmdclass={"build_py": BuildPyProtected},
|
|
76
82
|
classifiers=[
|
|
77
83
|
"Development Status :: 4 - Beta",
|
|
78
84
|
"Intended Audience :: Developers",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|