Metadata-Version: 2.4
Name: pygameP
Version: 1.0.11
Summary: Pygame Plus — advanced game framework: shaders, physics, scene management, input, CLI project management
Author: pygameP contributors
License: MIT
Project-URL: Homepage, https://github.com/pygameP/pygameP
Keywords: pygame,game,shader,physics,scene
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pygame>=2.0.0
Requires-Dist: PyOpenGL>=3.1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Provides-Extra: web
Requires-Dist: pywebview>=4.0; extra == "web"
Provides-Extra: video
Requires-Dist: opencv-python>=4.0; extra == "video"
Provides-Extra: build
Requires-Dist: pyinstaller>=6.0; extra == "build"
Dynamic: license-file

# pygameP

**Pygame Plus** — A comprehensive game development framework for Pygame with 44 modules: GLSL shaders, physics, scene management, lighting, dialogue, AI behavior trees, inventory, quests, achievements, CLI tools, and more.

## Installation

```bash
pip install pygameP
```

Dependencies are installed automatically:
- `pygame >= 2.0.0`
- `PyOpenGL >= 3.1.0` (for shader support)

## Features

| Module | Description |
|--------|-------------|
| **shaders** | GLSL shader system + built-in effects (grayscale, blur, invert, brightness, pulse, wave) |
| **performance** | Object pool, spatial hash, FPS monitor, batch renderer |
| **scene** | `.pgstage` JSON scene files with multi-scene switching and script bindings |
| **physics** | Simple rigid-body physics (gravity, collision, raycasting) |
| **input** | Unified input for keyboard, mouse, gamepad, and multi-touch |
| **camera** | 2D camera with smooth follow, screen shake, zoom, and bounds |
| **audio** | Audio manager with BGM fade, SFX pooling, and volume control |
| **audio_fx** | Audio effects (reverb, echo, pitch shift) + 2D positional audio |
| **animation** | SpriteSheet, Animation, AnimationController |
| **particles** | Particle emitters, lifecycle, color gradients |
| **tween** | 22+ easing functions, delay, callbacks |
| **sprites** | SpriteGroup batch update/draw, collision detection |
| **lighting** | 2D point lights, spotlights, ambient light, flicker effects |
| **dialogue** | Branching dialogue trees, typewriter effect, choices |
| **behaviortree** | AI behavior trees (Selector, Sequence, Action, Condition, decorators) |
| **statemachine** | Finite state machine for game states and AI |
| **events** | Publish/subscribe event bus |
| **pathfinding** | A* pathfinding with grid maps |
| **tilemap** | Orthographic + isometric tilemap rendering |
| **inventory** | Item system with stacking, equipment, serialization |
| **quest** | Quest tracking, objectives, prerequisites, rewards |
| **achievement** | Achievement unlock tracking, progress, notifications |
| **gui** | Button, Slider, TextInput, ProgressBar, Label |
| **gui_widgets** | Dropdown, Checkbox, RadioButton, Panel, ImageWidget |
| **scrollview** | ScrollView, TabPanel, Tooltip, ContextMenu |
| **layout** | VBox, HBox, GridLayout auto-arrangement |
| **dialog** | MessageBox, ConfirmDialog, InputDialog |
| **theme** | GUI theme system (dark/light/retro built-in) |
| **sprite_atlas** | Texture atlas packing + 9-slice/9-patch UI rendering |
| **render_target** | Off-screen rendering, post-processing (Bloom, Vignette, ColorGrade) |
| **noise** | Perlin noise, cave/maze/terrain generation |
| **i18n** | Multi-language support with string formatting |
| **parallax** | Multi-layer parallax scrolling backgrounds |
| **async_utils** | AsyncTimer, LoopRunner for non-blocking tasks |
| **assets** | Cached asset loader (images, sounds, fonts, JSON) |
| **save** | JSON save slots + config manager |
| **debug** | Debug overlay with FPS, hitboxes, custom stats |
| **console** | In-game debug console + screenshot capture |
| **virtual_joystick** | On-screen touch/mouse joystick |
| **tiled** | Tiled Map Editor (.tmx) importer |
| **script_engine** | Script bindings: file path or inline code (start/update/event hooks) |
| **webview** | Embed HTML content into Pygame (WebSurface) |
| **video** | Play MP4 videos in Pygame (VideoPlayer) |
| **cli** | CLI project management (new, run, build, pgstage, validate, merge, diff, etc.) |

---

## Quick Start

### 1. GLSL Shader Effects

Load shaders from `.glsl` files or inline code strings. Apply to the entire screen or individual sprites.

```python
from pygameP import Shader, ShaderEffect, BuiltInEffects

# Use built-in effects
grayscale = BuiltInEffects.grayscale()
blur = BuiltInEffects.blur(radius=3.0)
invert = BuiltInEffects.invert()
pulse = BuiltInEffects.pulse(speed=2.0)
wave = BuiltInEffects.wave(amplitude=0.05, frequency=10.0)

# Apply to the entire screen
grayscale.apply(screen)

# Apply to a single sprite
pulse.apply(my_sprite)

# Custom GLSL code
my_shader = Shader(fragment_source="""
#version 330 core
in vec2 v_texcoord;
out vec4 frag_color;
uniform sampler2D u_texture;
uniform float u_time;
void main() {
    vec4 c = texture(u_texture, v_texcoord);
    frag_color = vec4(c.r, c.g * abs(sin(u_time)), c.b, c.a);
}
""")

# Load from file
custom = Shader(
    vertex_file="assets/shaders/default.vert",
    fragment_file="assets/shaders/plasma.frag"
)
```

### 2. Performance Optimization

```python
from pygameP import ObjectPool, SpatialHash, FPSMonitor, BatchRenderer

# Object pool — reduce allocation overhead for bullets/particles
bullet_pool = ObjectPool(lambda: Bullet(), initial_size=200)
bullet = bullet_pool.acquire()   # grab from pool
# ... use it ...
bullet_pool.release(bullet)      # return to pool

# Spatial hash — fast collision detection
spatial = SpatialHash(cell_size=64)
spatial.insert(enemy, enemy.rect)
nearby = spatial.query(player.rect)  # only check nearby objects

# FPS monitor + adaptive quality
fps_monitor = FPSMonitor(target_fps=60)
while running:
    dt = fps_monitor.tick()
    if fps_monitor.should_reduce_quality():
        reduce_particle_count()

# Batch rendering — merge draw calls
batch = BatchRenderer(screen)
for sprite in sprites:
    batch.blit(sprite.image, sprite.rect.topleft)
batch.render()  # execute all at once
```

### 3. Scene Management (.pgstage)

`.pgstage` is pygameP's custom scene file format, based on JSON.

> **Scene Editor:** `.pgstage` files are created and managed by **[pgstage Editor](https://tomlct2015.github.io/pygameP/editor/pgstage-editor)**, a visual scene editor. The pygameP library only loads and runs scenes.
> Online Editor: https://tomlct2015.github.io/pygameP/editor/pgstage-editor

```json
{
  "name": "ExampleLevel",
  "width": 1600,
  "height": 1200,
  "background_color": [20, 25, 40],
  "camera_x": 0,
  "camera_y": 0,
  "properties": {
    "music": "assets/music/level1.ogg",
    "difficulty": "normal"
  },
  "entities": [
    {
      "type": "Player",
      "x": 100,
      "y": 800,
      "layer": 5,
      "hp": 100,
      "tag": "player"
    },
    {
      "type": "Enemy",
      "x": 500,
      "y": 800,
      "layer": 4,
      "hp": 50,
      "ai": "patrol"
    }
  ]
}
```

Usage in Python:

```python
from pygameP import Scene, SceneManager, SceneEntity

# Load a scene from .pgstage file
scene = Scene.load("levels/level1.pgstage")

# Multi-scene management (with transition)
manager = SceneManager()
manager.load("menu", "scenes/menu.pgstage")
manager.load("game", "levels/level1.pgstage")
manager.switch_to("game", transition=0.5)

# Game loop
while running:
    dt = clock.tick(60) / 1000.0
    manager.update(dt)
    manager.draw(screen)
```

### 4. Simple Physics Engine

```python
from pygameP import RigidBody, PhysicsWorld, BoxCollider, CircleCollider

# Create physics world
world = PhysicsWorld(gravity=980.0)

# Dynamic rigid body (affected by gravity)
player = RigidBody(x=100, y=0, mass=1.0, collider=BoxCollider(32, 64))
player.restitution = 0.3  # bounciness
player.friction = 0.2     # friction
world.add_body(player)

# Static rigid body (ground, platforms)
ground = RigidBody(x=0, y=600, mass=0, collider=BoxCollider(800, 40))
world.add_body(ground)

# Apply force or impulse
player.apply_force(500, 0)        # continuous force
player.apply_impulse(0, -300)     # jump (instant)

# Update each frame
world.update(dt)

# Collision callback
def on_hit(body1, body2):
    print("Collision!")
world.on_collision = on_hit

# Raycasting
hit = world.raycast((100, 300), (1, 0), max_distance=500)
if hit:
    body, distance, point = hit
    print(f"Hit {body} at distance {distance}")
```

### 5. Extended Input Devices

```python
from pygameP import InputManager

input_mgr = InputManager()

# Map actions to multiple input sources
input_mgr.map_action("jump", "keyboard", pygame.K_SPACE)
input_mgr.map_action("jump", "gamepad", (0, 0))  # gamepad 0, button 0

input_mgr.map_action("shoot", "mouse", 1)  # left mouse button
input_mgr.map_action("move_right", "gamepad", (0, "axis_0"))

# Game loop
while running:
    events = pygame.event.get()
    input_mgr.update(events)

    if input_mgr.is_action_just_pressed("jump"):
        player.jump()
    if input_mgr.is_action_pressed("move_right"):
        player.move_right()

    # Analog input (gamepad stick)
    move_x = input_mgr.get_action_value("move_right")

    # Direct gamepad access
    pad = input_mgr.get_gamepad(0)
    if pad:
        left_stick = pad.get_left_stick()
        pad.rumble(0.5, 0.5, 100)  # vibration

    # Touch input
    if input_mgr.touch.is_touching():
        pos = input_mgr.touch.get_touch_position()
```

Supported input devices:
- **Keyboard** — key press/release/just-pressed
- **Mouse** — position, relative motion, scroll wheel, button state
- **Gamepad** — buttons, stick axes, D-pad, rumble/vibration
- **Touch** — multi-touch, touch start/end/move

### 6. Async Timer & Loop Runner

Non-blocking scheduled tasks for game loops.

```python
from pygameP import AsyncTimer, LoopRunner

timer = AsyncTimer()

# Infinite loop — run callback every N seconds
timer.loop(0.1, update_ai)

# One-shot — run once after delay
timer.after(3.0, spawn_boss)

# Repeating — run every N seconds (stoppable)
task = timer.every(1.0, save_checkpoint)
task.stop()  # cancel later

# Per-frame loop runner
runner = LoopRunner()
runner.add("physics", update_physics)
runner.add("ai", update_ai)

# In game loop:
timer.update(dt)
runner.update(dt)
```

### 7. Asset Manager

Global resource loader with automatic caching.

```python
from pygameP import AssetManager

assets = AssetManager("assets")

# Load (cached after first load)
img = assets.image("sprites/player.png")
snd = assets.sound("sfx/jump.ogg")
fnt = assets.font("fonts/arial.ttf", 24)
data = assets.json("data/level1.json")

# Preload all at startup
assets.preload_images(["bg.png", "player.png", "enemy.png"])
assets.preload_sounds(["jump.ogg", "hit.ogg"])

# Background music
assets.music("music/theme.ogg", fade_ms=1000)
assets.stop_music(fade_ms=500)

# Stats
print(assets.stats())  # {'images': 3, 'sounds': 2, ...}

# Cleanup
assets.unload("sprites/player.png")
assets.clear()  # clear all caches
```

### 8. Save System & Config

JSON-based save slots and game configuration.

```python
from pygameP import SaveManager, Config

# Save game
save = SaveManager("saves")
save.save("slot1", {"level": 5, "hp": 100, "items": ["sword", "shield"]})
data = save.load("slot1")
save.exists("slot1")  # True
save.list_slots()     # ["slot1"]
save.delete("slot1")

# Config with defaults
cfg = Config("settings.json", defaults={
    "volume": 0.8,
    "resolution": [1280, 720],
    "fullscreen": False,
})
vol = cfg.get("volume")      # 0.8
cfg.set("volume", 0.5)
cfg["fullscreen"] = True     # bracket access
cfg.save()                   # write to disk
cfg.reset()                  # reset to defaults
```

### 9. Camera System

2D camera with smooth follow, screen shake, zoom, and world bounds.

```python
from pygameP import Camera

cam = Camera(1280, 720, world_width=3200, world_height=1800)

# Smooth follow player
cam.follow(player.x, player.y, speed=5.0, dead_zone=20)

# Instant snap to position
cam.look_at(500, 300)

# Screen shake on hit
cam.shake(intensity=8, duration=0.3)

# Zoom
cam.zoom_to(1.5, speed=3.0)

# In game loop:
cam.update(dt)
offset = cam.offset  # (dx, dy) to subtract from entity positions

# Coordinate conversion
sx, sy = cam.world_to_screen(world_x, world_y)
wx, wy = cam.screen_to_world(mouse_x, mouse_y)

# Visibility check
if cam.visible(entity.x, entity.y, 32, 32):
    entity.draw(screen)
```

### 10. Audio Manager

BGM fade, SFX pooling, and volume control.

```python
from pygameP import AudioManager

audio = AudioManager(sfx_channels=16)

# Background music with fade
audio.play_bgm("music/theme.ogg", fade_ms=1000)
audio.set_bgm_volume(0.7)
audio.stop_bgm(fade_ms=500)
audio.pause_bgm()
audio.resume_bgm()

# Sound effects (auto-cached)
audio.play_sfx("sfx/jump.ogg")
audio.play_sfx("sfx/explosion.ogg", volume=0.5)

# Volume groups
audio.set_master_volume(0.8)  # affects everything
audio.set_sfx_volume(0.6)     # affects SFX only

# Preload
audio.preload_sfx(["jump.ogg", "hit.ogg", "coin.ogg"])
```

### 11. GUI Widgets

Button, Slider, TextInput, ProgressBar, Label.

```python
from pygameP import Button, Slider, TextInput, ProgressBar, Label

btn = Button(100, 200, 200, 50, "Start Game", callback=start_game)
slider = Slider(100, 300, 200, min_val=0.0, max_val=1.0, value=0.8)
text = TextInput(100, 400, 200, 40, placeholder="Enter name...")
bar = ProgressBar(100, 500, 200, 20, value=0.75)
label = Label(100, 550, "Score: 0", font_size=24)

# In game loop:
for event in pygame.event.get():
    btn.handle_event(event)
    slider.handle_event(event)
    text.handle_event(event)

text.update(dt)  # cursor blink

btn.draw(screen)
slider.draw(screen)
text.draw(screen)
bar.draw(screen)
label.draw(screen)

# Access values
print(slider.value)   # 0.0 to 1.0
print(text.text)      # typed string
bar.value = 0.9       # auto-clamped to 0.0-1.0
```

### 12. Debug Overlay

Press F1 to toggle. Shows FPS, entity count, hitboxes, and custom stats.

```python
from pygameP import DebugOverlay

dbg = DebugOverlay()
dbg.show_hitboxes = True
dbg.show_fps = True
dbg.show_entity_count = True
dbg.hotkey = pygame.K_F1  # configurable

# In game loop:
dbg.handle_event(event)
dbg.update(dt, entity_count=len(entities), score=score, ammo=ammo)
dbg.draw(screen, entities)  # draws hitboxes + overlay

# Custom stats
dbg.set_stat("health", player.hp)
dbg.set_stat("mode", "survival")
```

### 13. Tiled Map Import

Load Tiled Map Editor (.tmx) files.

```python
from pygameP import TiledLoader

loader = TiledLoader()
tilemap = loader.load("levels/level1.tmx")

# Access layers
for layer in tilemap.layers:
    print(layer.name, layer.width, layer.height)

# Get tile at position
tile_id = tilemap.get_tile("Ground", x=5, y=3)

# Access objects
for obj in tilemap.objects:
    print(obj.name, obj.type, obj.x, obj.y)

# Find objects by type
enemies = tilemap.get_object_layer("Enemies").get_objects_by_type("Enemy")
spawn = tilemap.get_object_layer("Spawns").get_object_by_name("PlayerStart")

# Tileset info
for ts in tilemap.tilesets:
    print(ts.name, ts.first_gid, ts.image_source)

# Coordinate conversion
px, py = tilemap.tile_to_pixel(5, 3)
tx, ty = tilemap.pixel_to_pixel(mouse_x, mouse_y)

# Check visibility
if tilemap.visible(entity.rect):
    entity.draw(screen)
```

---

## API Reference

### `Shader`
- `Shader(vertex_source, fragment_source, vertex_file, fragment_file)` — create shader
- `shader.use()` / `shader.stop()` — enable/disable
- `shader.set_uniform(name, value)` — set uniform (float, int, vec2, vec3, vec4)
- `shader.apply_to_surface(surface)` — apply to entire screen
- `shader.apply_to_sprite(sprite)` — apply to single sprite

### `BuiltInEffects`
- `grayscale()` — grayscale filter
- `blur(radius)` — blur effect
- `invert()` — color inversion
- `brightness(amount)` — brightness adjustment
- `pulse(speed)` — pulsing glow
- `wave(amplitude, frequency)` — wave distortion

### `ObjectPool`
- `acquire()` — get object from pool
- `release(obj)` — return object
- `release_all()` — return all objects
- `resize(n)` — adjust pool size

### `SpatialHash`
- `insert(obj, rect)` — insert object
- `query(rect)` — query objects in area
- `query_nearby(obj, rect)` — query nearby (excludes self)
- `remove(obj)` / `clear()`

### `Scene` / `SceneManager`
- `Scene.load(path)` — load `.pgstage` file
- `SceneManager.load(name, path)` — load and register scene
- `SceneManager.switch_to(name, transition)` — switch scenes

### `PhysicsWorld`
- `add_body(body)` / `remove_body(body)`
- `update(dt)` — step physics (handles gravity and collisions)
- `raycast(start, direction, max_distance)` — ray cast query

### `InputManager`
- `update(events)` — update all input devices
- `is_action_pressed(action)` — is action held down
- `is_action_just_pressed(action)` — was action just pressed this frame
- `get_action_value(action)` — get analog value (-1 to 1)
- `map_action(action, device, binding)` — map action to input

### `AsyncTimer`
- `loop(interval, callback)` — run callback every N seconds (infinite)
- `every(interval, callback)` — alias for loop()
- `after(delay, callback)` — run once after delay
- `update(dt)` — advance all tasks
- `cancel_all()` — cancel all tasks

### `LoopRunner`
- `add(name, callback)` — register per-frame callback
- `remove(name)` — remove callback
- `update(dt)` — call all callbacks with dt

### `AssetManager`
- `image(path)` — load/cache image
- `sound(path)` — load/cache sound
- `font(path, size)` — load/cache font
- `json(path)` — load/cache JSON data
- `music(path, fade_ms)` — play BGM
- `preload_images(paths)` — bulk preload
- `stats()` — cache statistics
- `clear()` — clear all caches

### `SaveManager`
- `save(slot, data)` — save to slot
- `load(slot)` — load from slot
- `exists(slot)` — check if slot exists
- `delete(slot)` — delete slot
- `list_slots()` — list all slots

### `Config`
- `get(key, default)` — get value with fallback
- `set(key, value)` — set value
- `save()` — write to disk
- `reset()` — reset to defaults
- `config[key]` — bracket access

### `Camera`
- `follow(x, y, speed, dead_zone)` — smooth follow
- `look_at(x, y)` — instant snap
- `shake(intensity, duration)` — screen shake
- `zoom_to(value, speed)` — smooth zoom
- `update(dt)` — update camera
- `offset` — (dx, dy) for rendering
- `world_to_screen(x, y)` — coordinate conversion
- `screen_to_world(x, y)` — reverse conversion
- `visible(rect)` — visibility check

### `AudioManager`
- `play_bgm(path, fade_ms)` — play background music
- `stop_bgm(fade_ms)` — stop BGM with fade
- `play_sfx(path, volume)` — play sound effect
- `set_master_volume(v)` — global volume
- `set_bgm_volume(v)` — BGM volume
- `set_sfx_volume(v)` — SFX volume
- `preload_sfx(paths)` — bulk preload

### GUI Widgets
- `Button(x, y, w, h, text, callback)` — clickable button
- `Slider(x, y, w, min, max, value)` — horizontal slider
- `TextInput(x, y, w, h, placeholder)` — text input field
- `ProgressBar(x, y, w, h, value)` — progress bar
- `Label(x, y, text, font_size)` — static text
- All widgets: `handle_event(event)`, `update(dt)`, `draw(surface)`

### `DebugOverlay`
- `handle_event(event)` — toggle on F1
- `update(dt, entity_count, **custom)` — update stats
- `draw(surface, entities)` — render overlay + hitboxes
- `set_stat(key, value)` — add custom stat

### `TiledLoader`
- `load(path)` — load .tmx file
- Returns `TileMap` with:
  - `layers` — list of `TileLayer`
  - `object_layers` — list of `ObjectLayer`
  - `tilesets` — list of `Tileset`
  - `get_tile(layer_name, x, y)` — get tile ID
  - `tile_to_pixel(tx, ty)` — coordinate conversion

---

## Full Example

```python
import pygame
from pygameP import (
    SceneManager, Scene, SceneEntity,
    PhysicsWorld, RigidBody, BoxCollider,
    InputManager, FPSMonitor, BuiltInEffects
)

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Scene
manager = SceneManager()
game = Scene({"name": "Game", "width": 800, "height": 600,
              "background_color": [30, 40, 60]})
manager.add("game", game)
manager.switch_to("game")

# Physics
world = PhysicsWorld(gravity=980.0)
player = RigidBody(400, 100, mass=1.0, collider=BoxCollider(32, 32))
world.add_body(player)
ground = RigidBody(0, 550, mass=0, collider=BoxCollider(800, 50))
world.add_body(ground)

# Input
input_mgr = InputManager()
input_mgr.map_action("jump", "keyboard", pygame.K_SPACE)
input_mgr.map_action("left", "keyboard", pygame.K_LEFT)
input_mgr.map_action("right", "keyboard", pygame.K_RIGHT)

# FPS monitor
fps = FPSMonitor(target_fps=60)

# Shader effect
effect = BuiltInEffects.pulse(speed=2.0)

running = True
while running:
    dt = clock.tick(60) / 1000.0
    fps.tick()

    events = pygame.event.get()
    input_mgr.update(events)

    for event in events:
        if event.type == pygame.QUIT:
            running = False

    # Input
    if input_mgr.is_action_just_pressed("jump") and player.grounded:
        player.apply_impulse(0, -500)
    if input_mgr.is_action_pressed("left"):
        player.apply_force(-300, 0)
    if input_mgr.is_action_pressed("right"):
        player.apply_force(300, 0)

    # Update
    world.update(dt)
    manager.update(dt)

    # Draw
    manager.draw(screen)
    pygame.draw.rect(screen, (255, 200, 100),
                     (player.x, player.y, 32, 32))
    pygame.draw.rect(screen, (100, 100, 100),
                     (ground.x, ground.y, 800, 50))

    pygame.display.flip()

pygame.quit()
```

---

## New in v1.0.11

### 14. Lighting System

```python
from pygameP import Light2D, LightingSystem

lighting = LightingSystem(ambient=(20, 20, 40))
torch = Light2D(x=400, y=300, radius=200, color=(255, 200, 100))
torch.flicker(speed=5.0, amount=0.15)
lighting.add(torch)

spot = Light2D(x=100, y=100, radius=300, angle=45, spread=30)
lighting.add(spot)

# Game loop
lighting.update(dt)
lighting.render(screen, scene_surface)
```

### 15. Dialogue System

```python
from pygameP import DialogueTree, DialogueBox

tree = DialogueTree()
tree.add_node("start", speaker="Elder", text="Welcome, hero!")
tree.add_node("quest", speaker="Elder", text="Will you help?")
tree.add_choice("start", "quest", "Yes!")
tree.add_choice("start", "decline", "No.")
tree.add_node("decline", speaker="Elder", text="Very well...")

box = DialogueBox(tree)
box.start("start")

# Game loop
box.update(dt)
box.handle_event(event)
box.draw(screen)
```

### 16. AI Behavior Tree

```python
from pygameP import BehaviorTree, Selector, Sequence, Action, Condition

patrol = Action("patrol", lambda bt: bt.blackboard["move_patrol"]())
chase = Action("chase", lambda bt: bt.blackboard["move_chase"]())
attack = Action("attack", lambda bt: bt.blackboard["do_attack"]())
visible = Condition("visible?", lambda bt: bt.blackboard["can_see_player"]())
close = Condition("close?", lambda bt: bt.blackboard["dist"]() < 50)

root = Selector("root", [
    Sequence("engage", [visible, Selector("combat", [
        Sequence("fight", [close, attack]),
        chase
    ])]),
    patrol
])

tree = BehaviorTree(root)
tree.tick(dt)
```

### 17. Inventory System

```python
from pygameP import Inventory, Item

inv = Inventory(capacity=20)
potion = Item("potion", "Health Potion", stackable=True, max_stack=10)
sword = Item("sword", "Iron Sword", value=100)

inv.register_item(potion)
inv.add(potion, quantity=5)
inv.add(sword)
inv.use_item(0, player)
inv.has("potion", 3)  # True
```

### 18. Quest System

```python
from pygameP import QuestManager, Quest, QuestObjective

qm = QuestManager()
quest = Quest("slay", "Slay the Dragon", "Defeat 3 dragons")
quest.add_objective(QuestObjective("kill", "Kill dragons", required=3))
quest.add_reward("gold", 500)
qm.add_quest(quest)
qm.accept("slay")
qm.progress("slay", "kill", amount=1)
```

### 19. Achievement System

```python
from pygameP import AchievementManager, Achievement

am = AchievementManager()
am.add(Achievement("first_kill", "First Blood", "Defeat your first enemy"))
am.add(Achievement("explorer", "Explorer", "Visit all areas", progress_max=10))

am.unlock("first_kill")
am.progress("explorer", amount=1)
```

### 20. Theme System

```python
from pygameP import ThemeManager, Theme, DARK_THEME, LIGHT_THEME, RETRO_THEME

ThemeManager.set_theme(DARK_THEME)
ThemeManager.switch("retro")

# Custom theme
custom = Theme("neon", {"accent": (0, 255, 200), "bg": (10, 10, 20)})
ThemeManager.register(custom)
ThemeManager.switch("neon")
```

### 21. ScrollView, TabPanel, Tooltip, ContextMenu

```python
from pygameP import ScrollView, TabPanel, Tooltip, ContextMenu

scroll = ScrollView(x=50, y=50, width=300, height=400)
scroll.add_content(widget)

tabs = TabPanel(x=50, y=50, width=400, height=300)
tabs.add_tab("Settings", widgets)
tabs.add_tab("Items", item_widgets)

tooltip = Tooltip("Hover text")
tooltip.attach(widget.rect)

ctx = ContextMenu()
ctx.add_item("Copy", on_copy)
ctx.add_item("Delete", on_delete)
ctx.show(mouse_x, mouse_y)
```

### 22. SpriteAtlas & NineSlice

```python
from pygameP import SpriteAtlas, NineSlice

atlas = SpriteAtlas("assets/atlas.png", tile_size=32)
frame = atlas.get_frame(0)
atlas.define_region("sword", 64, 0, 32, 32)

panel = NineSlice("assets/panel.png", border=12)
panel.draw(screen, pygame.Rect(50, 50, 300, 200))
```

### 23. RenderTarget & Post-Processing

```python
from pygameP import RenderTarget, PostProcessChain, Bloom, Vignette

rt = RenderTarget(800, 600)
rt.begin()
# draw scene...
rt.end()

chain = PostProcessChain()
chain.add(Bloom(threshold=0.8, intensity=0.5))
chain.add(Vignette(intensity=0.3))
result = chain.process(rt.surface)
screen.blit(result, (0, 0))
```

### 24. In-Game Console

```python
from pygameP import GameConsole, Screenshot

console = GameConsole()
console.register("spawn", lambda args: spawn(args[0]))
console.register("godmode", lambda args: setattr(player, 'invincible', True))

# Toggle with backtick
console.handle_event(event)
console.update(dt)
console.draw(screen)

Screenshot.capture(screen)  # saves to screenshots/
```

### 25. Virtual Joystick

```python
from pygameP import VirtualJoystick

joy = VirtualJoystick(x=120, y=480, radius=60)
joy.handle_event(event)
dx, dy = joy.get_direction()  # -1.0 to 1.0
joy.draw(screen)
```

### 26. Audio Effects & Positional Audio

```python
from pygameP import AudioFX, PositionalAudio

fx = AudioFX()
reverb_snd = fx.reverb(original, decay=0.5)
echo_snd = fx.echo(original, repeats=3, delay=0.3)
fast_snd = fx.pitch_shift(original, factor=1.5)

pos = PositionalAudio(listener_x=400, listener_y=300)
pos.add_source("torch", x=200, y=150, sound=snd, max_dist=300)
pos.update(player.x, player.y)  # auto volume + stereo pan
```

### 27. Script Engine (File + Inline Code)

```python
# In .pgstage JSON:
{
  "script": {
    "start": "scripts/init.py",
    "update": {"code": "entity.x += 100 * dt"},
    "event": {"file": "scripts/events.py"}
  }
}
```

### 28. CLI Commands

```bash
pygameP new MyGame        # Create project
pygameP run               # Run game
pygameP build             # Build exe
pygameP pgstage scene.pgstage  # Open in editor
pygameP info              # Project stats
pygameP validate          # Validate .pgstage files
pygameP lint              # Check script references
pygameP export            # Export as zip
pygameP merge a.pgstage b.pgstage -o out.pgstage
pygameP diff a.pgstage b.pgstage
pygameP deps --install    # Install missing deps
pygameP test              # Run tests
pygameP clean             # Clean build artifacts
pygameP serve             # Dev server with hot reload
pygameP watch             # Monitor file changes
```

## License

MIT License
