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.
Files changed (102) hide show
  1. {aether_engine-1.2.0/aether_engine.egg-info → aether_engine-1.3.0}/PKG-INFO +14 -7
  2. {aether_engine-1.2.0 → aether_engine-1.3.0}/README.md +9 -2
  3. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/__init__.py +10 -2
  4. aether_engine-1.3.0/aether/core/_identity.py +67 -0
  5. aether_engine-1.3.0/aether/core/boot_splash.py +141 -0
  6. aether_engine-1.3.0/aether/core/branding_guard.py +103 -0
  7. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/engine.py +53 -6
  8. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/text.py +36 -29
  9. aether_engine-1.3.0/aether/utils/fonts.py +56 -0
  10. {aether_engine-1.2.0 → aether_engine-1.3.0/aether_engine.egg-info}/PKG-INFO +14 -7
  11. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/SOURCES.txt +4 -0
  12. {aether_engine-1.2.0 → aether_engine-1.3.0}/setup.py +8 -2
  13. {aether_engine-1.2.0 → aether_engine-1.3.0}/LICENSE +0 -0
  14. {aether_engine-1.2.0 → aether_engine-1.3.0}/MANIFEST.in +0 -0
  15. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ai/behavior_tree.py +0 -0
  16. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ai/navmesh.py +0 -0
  17. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/animation/skeleton.py +0 -0
  18. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/assets/asset_pipeline.py +0 -0
  19. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/__init__.py +0 -0
  20. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/audio_manager.py +0 -0
  21. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/music.py +0 -0
  22. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/sfx.py +0 -0
  23. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/audio/sound.py +0 -0
  24. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/__init__.py +0 -0
  25. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/achievement_system.py +0 -0
  26. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/application.py +0 -0
  27. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/config.py +0 -0
  28. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/console.py +0 -0
  29. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/event_bus.py +0 -0
  30. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/input.py +0 -0
  31. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/plugin_system.py +0 -0
  32. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/save_system.py +0 -0
  33. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/time_manager.py +0 -0
  34. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/core/window.py +0 -0
  35. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/__init__.py +0 -0
  36. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/__main__.py +0 -0
  37. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/gizmo.py +0 -0
  38. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/imgui_integration.py +0 -0
  39. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/editor/model_editor.py +0 -0
  40. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/environment/environment_system.py +0 -0
  41. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/__init__.py +0 -0
  42. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/camera.py +0 -0
  43. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/first_person.py +0 -0
  44. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/framebuffer.py +0 -0
  45. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/light.py +0 -0
  46. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/lod_system.py +0 -0
  47. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/material.py +0 -0
  48. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/mesh.py +0 -0
  49. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/mesh_factory.py +0 -0
  50. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/particle_system.py +0 -0
  51. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/post_process.py +0 -0
  52. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/renderer.py +0 -0
  53. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/shader.py +0 -0
  54. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/shader_library.py +0 -0
  55. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/sky_atmosphere.py +0 -0
  56. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/terrain.py +0 -0
  57. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/texture.py +0 -0
  58. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics/water.py +0 -0
  59. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/graphics2d/__init__.py +0 -0
  60. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/network/network_manager.py +0 -0
  61. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/__init__.py +0 -0
  62. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/bullet_backend.py +0 -0
  63. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/collider.py +0 -0
  64. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/physics2d.py +0 -0
  65. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/physics3d.py +0 -0
  66. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/physics_world.py +0 -0
  67. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/raycast.py +0 -0
  68. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/physics/rigidbody.py +0 -0
  69. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/platform/steam_api.py +0 -0
  70. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/__init__.py +0 -0
  71. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/asset_database.py +0 -0
  72. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/loaders/model_loader.py +0 -0
  73. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/loaders/obj_loader.py +0 -0
  74. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/loaders/texture_loader.py +0 -0
  75. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/resource_manager.py +0 -0
  76. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/save_load.py +0 -0
  77. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/resources/serialization.py +0 -0
  78. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/__init__.py +0 -0
  79. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/component.py +0 -0
  80. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/entity.py +0 -0
  81. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/scene.py +0 -0
  82. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/scene_graph.py +0 -0
  83. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/scene/scene_manager.py +0 -0
  84. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/__init__.py +0 -0
  85. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/button.py +0 -0
  86. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/canvas.py +0 -0
  87. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/ui/hud.py +0 -0
  88. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/__init__.py +0 -0
  89. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/logger.py +0 -0
  90. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/math_utils.py +0 -0
  91. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/object_pool.py +0 -0
  92. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether/utils/profiler.py +0 -0
  93. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/dependency_links.txt +0 -0
  94. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/entry_points.txt +0 -0
  95. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/requires.txt +3 -3
  96. {aether_engine-1.2.0 → aether_engine-1.3.0}/aether_engine.egg-info/top_level.txt +0 -0
  97. {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/01_hello_cube.py +0 -0
  98. {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/02_sphere_scene.py +0 -0
  99. {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/03_first_person.py +0 -0
  100. {aether_engine-1.2.0 → aether_engine-1.3.0}/examples/04_physics2d.py +0 -0
  101. {aether_engine-1.2.0 → aether_engine-1.3.0}/pyproject.toml +0 -0
  102. {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.2.0
4
- Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, model editor, HUD
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.2.0 · **Лицензия:** MIT
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
- ## Showcase-игра (доказательство)
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.2.0 · **Лицензия:** MIT
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
- ## Showcase-игра (доказательство)
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.2.0"
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] Initializing Engine v1.2.0 by entiti937...")
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" # "3d" | "2d"
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 # overlay Renderer2D sprites on top of 3D
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
- """Рендеринг текста с использованием FreeType"""
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
- # Загружаем ASCII символы
47
+
48
+ # Latin + Cyrillic + punctuation
49
+ chars = set()
38
50
  for c in range(32, 127):
39
- char = chr(c)
40
- self._load_character(char, font)
41
-
42
- # Добавляем кириллицу
43
- for c in range(1040, 1104):
44
- char = chr(c)
45
- self._load_character(char, font)
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 ImportError:
57
- print("[Font] PIL not installed, using default 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 460 core
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 460 core
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
- vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
183
- color = textColor * sampled;
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 += (ch.advance >> 6) * scale
257
-
258
- total_width += (ch.advance >> 6) * scale
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 += (ch.advance >> 6) * scale
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.2.0
4
- Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, model editor, HUD
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.2.0 · **Лицензия:** MIT
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
- ## Showcase-игра (доказательство)
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.2.0",
57
+ version="1.3.0",
53
58
  author="entiti937",
54
- description="3D/2D game engine by entiti937 — OpenGL, physics, FPS, model editor, HUD",
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
@@ -7,11 +7,11 @@ Pillow>=10.0
7
7
  pygame-ce>=2.5
8
8
 
9
9
  [full]
10
- pyyaml>=6.0
11
- pygame-ce>=2.5
12
10
  trimesh>=3.21
13
- pymunk>=6.0
14
11
  freetype-py>=2.4
12
+ pygame-ce>=2.5
13
+ pyyaml>=6.0
14
+ pymunk>=6.0
15
15
  imageio>=2.31
16
16
 
17
17
  [models]
File without changes