2026-05-16 21:28:22 +02:00
|
|
|
# Puzzle Quest
|
|
|
|
|
|
|
|
|
|
Hidden-object game built with **Godot 4.6** (single renderer: Forward+).
|
|
|
|
|
Branched from a Godot 3.3 codebase — migration is recent, expect leftover
|
|
|
|
|
Godot-3-isms to occasionally surface.
|
|
|
|
|
|
|
|
|
|
## Run
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
godot --editor # open project in the editor
|
|
|
|
|
godot # run the main scene (scenes/Main.tscn)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The system package `godot` (Arch `extra/godot`) is the project's Godot. There
|
|
|
|
|
is no in-repo Godot binary.
|
|
|
|
|
|
|
|
|
|
## Architecture
|
|
|
|
|
|
|
|
|
|
**Autoloads** (declared in `project.godot [autoload]`, loaded in this order):
|
|
|
|
|
|
|
|
|
|
| Name | Source | Role |
|
|
|
|
|
|------------------|---------------------------------------|-------------------------------------------------------------------|
|
|
|
|
|
| `Loading` | `scenes/UI/loading/Loading.tscn` | Full-screen transition overlay (BG + animated border + progress) |
|
|
|
|
|
| `Global` | `scripts/Global.gd` | Async scene loader (`goto_scene(path)`) + `database` reference |
|
|
|
|
|
| `Setting` | `scripts/Setting.gd` | Reads/writes the settings table; applies locale/resolution/fullscreen |
|
|
|
|
|
| `Event` | `scripts/Event.gd` | Static handlers connected to scene buttons (Warcraft / Home / reset / back) |
|
|
|
|
|
| `GlobalAnimation`| `scripts/Animation.gd` | Tween-based dissolve + HUD slide/warning animations |
|
|
|
|
|
|
|
|
|
|
**Database (`scripts/Database.gd`)**
|
|
|
|
|
|
|
|
|
|
- Plain JSON at `db/ahog.json`. Three tables: `settings`, `levels`, `scenes`.
|
|
|
|
|
- On Android the file is copied once to `user://database.json` and written
|
|
|
|
|
there (game saves modify lock state per item).
|
|
|
|
|
- `Global.database` is a `Database.DB` instance exposing
|
|
|
|
|
`get_table_by_name(name)` and `save_db()`. Each `Table` exposes
|
|
|
|
|
`get_data_at_row_idx(row_id)`, `edit_data(prop_id, row_id, value)`,
|
|
|
|
|
`m_rows_count`, `get_data_by_prop_name_and_data(prop_name, value)`,
|
|
|
|
|
`get_dictionary_by_prop_name_and_data(prop_name, value)`.
|
|
|
|
|
- `db/M*.gd` are typed accessors (MBase / MScene / MLevel / MSetting) that
|
|
|
|
|
read rows via the API above — extend these, not the JSON shape directly.
|
|
|
|
|
|
|
|
|
|
**Scene transitions** (`Global.goto_scene(path)`)
|
|
|
|
|
|
|
|
|
|
Uses `ResourceLoader.load_threaded_request` + `load_threaded_get_status` +
|
|
|
|
|
`load_threaded_get` (Godot 4 API; the original `load_interactive`/`poll()`
|
|
|
|
|
was removed). The Loading overlay (autoload) plays `BorderAnim` while the
|
|
|
|
|
new scene loads in a background thread; `Event._loading_is_started/finished`
|
|
|
|
|
flip `Global.loaded` so the poll only runs once the entry animation has set
|
|
|
|
|
the boolean. Don't call `current_scene.queue_free()` outside this function
|
|
|
|
|
— it will leak the resource and break the loader state.
|
|
|
|
|
|
|
|
|
|
**Levels.tscn / level scenes**
|
|
|
|
|
|
|
|
|
|
`scenes/levels/Levels.tscn` is the shared shell; concrete levels
|
|
|
|
|
(`warcraft/WarCraft.tscn`, `home/Home.tscn`) inherit from it. Each scene
|
|
|
|
|
has a `HiddenObjectsItems/<Item>` `MeshInstance3D` with an `Area3D`
|
|
|
|
|
collision shape; clicking it triggers `Levels._check_collider` →
|
|
|
|
|
`_start_dissolve(name)` → `GlobalAnimation.start_dissolve(mesh, material)`
|
|
|
|
|
which runs the dissolve shader on `material.dissolve_amount`.
|
|
|
|
|
|
|
|
|
|
The runtime hidden-object list lives under
|
|
|
|
|
`scenes/levels/parts/ListObjects.tscn` and is populated dynamically from
|
|
|
|
|
the `scenes` table for the current `Global.current_scene_int`.
|
|
|
|
|
|
|
|
|
|
## Migration notes (Godot 3 → 4)
|
|
|
|
|
|
|
|
|
|
The conversion is mostly done but a few classes of mistake keep surfacing
|
|
|
|
|
because `godot --convert-3to4` does not handle them:
|
|
|
|
|
|
|
|
|
|
- **Label**: `align/valign` → `horizontal_alignment/vertical_alignment`.
|
|
|
|
|
- **Button**: `pressed` (property) → `button_pressed`; `set_pressed()` →
|
|
|
|
|
assignment to `button_pressed`. The `pressed` *signal* still exists.
|
|
|
|
|
- **ScrollContainer**: `scroll_horizontal_enabled = false` →
|
|
|
|
|
`horizontal_scroll_mode = 0` (and same for vertical). ScrollContainer
|
|
|
|
|
also ships a non-empty default panel style in Godot 4 — set
|
|
|
|
|
`theme_override_styles/panel = StyleBoxEmpty` if you need transparency.
|
|
|
|
|
- **TextureRect / TextureButton**: `expand = true` → `expand_mode = 1`.
|
|
|
|
|
`stretch_mode` enum values **shifted down by 1** between 3 and 4 (the
|
|
|
|
|
deprecated "Scale on Expand" at index 0 was removed): G3's Tile (2) is
|
|
|
|
|
G4's Tile (1), G3's Keep (3) is G4's Keep (2), etc.
|
|
|
|
|
- **Environment**: `background_mode` enum changed (G3 3=Sky → G4 2=Sky;
|
|
|
|
|
G4 3 is Canvas which renders black). `background_sky` → `sky`;
|
|
|
|
|
`background_energy` → `background_energy_multiplier`;
|
|
|
|
|
`fog_color` → `fog_light_color`; `fog_height_min` → `fog_height`;
|
|
|
|
|
`fog_mode = 1` (Depth) needed to keep `fog_depth_*` semantics, otherwise
|
|
|
|
|
Godot 4 falls back to dense `fog_density` exponential fog.
|
|
|
|
|
- **AnimationPlayer**: `add_animation(name, anim)` removed — go through
|
|
|
|
|
`get_animation_library("")` (create one with `add_animation_library("")`
|
|
|
|
|
if it returns null) and call `lib.add_animation(name, anim)`.
|
|
|
|
|
- **Tween**: no longer a Node — call `create_tween()` from any Node and use
|
|
|
|
|
`tween.tween_method(...)`. Delete any `[node type="Tween"]` from older
|
|
|
|
|
`.tscn` files (the loader will crash otherwise).
|
|
|
|
|
- **PhysicsRayQuery**: `space_state.intersect_ray(from, to, ...)` →
|
|
|
|
|
`PhysicsRayQueryParameters3D.create(from, to)`, set fields, then
|
|
|
|
|
`space_state.intersect_ray(query)`.
|
|
|
|
|
- **PackedScene.instance()** → `instantiate()`.
|
|
|
|
|
- **String / cast operators**: `String(x)` constructor doesn't exist — use
|
|
|
|
|
`str(x)`. `x as int/String/bool` does **not** parse strings — use
|
|
|
|
|
`int(s)`, `str(x)`, `bool(int(s))`.
|
|
|
|
|
- **Internationalization**: project setting is `[internationalization]
|
|
|
|
|
locale/translations=...`, not `[locale] translations=...`. Locale codes
|
|
|
|
|
must match `.po` filenames exactly (`fr.po` → `"fr"`; G3-style `fr_FR`
|
|
|
|
|
no longer auto-falls-back).
|
|
|
|
|
- **Inherited scenes**: nodes from an instanced child scene need
|
|
|
|
|
`layout_mode = 1` + `anchors_preset = 15` explicitly set in the parent
|
|
|
|
|
scene file, otherwise Godot 4 treats them as Position-mode and zeroes
|
|
|
|
|
the anchors. Watch for stale per-Label `layout_mode = 0` /
|
|
|
|
|
`anchor_right = 0` overrides in Main.tscn-style parent scenes.
|
|
|
|
|
|
|
|
|
|
## Plugins
|
|
|
|
|
|
|
|
|
|
- `addons/lod/` — Calinou's Level-of-Detail plugin (Spatial / OmniLight /
|
|
|
|
|
SpotLight / Particles → 3D variants). Class declarations use the Godot 4
|
|
|
|
|
form (`@icon("...") class_name X` + `extends Node3D` separately).
|
|
|
|
|
- `addons/godot_db_manager/` — **removed during migration**. Don't restore
|
|
|
|
|
it; it relies on `WindowDialog`/`Tabs`/`PopupPanel` which were dropped in
|
|
|
|
|
Godot 4. The replacement is `scripts/Database.gd` (see above).
|
|
|
|
|
|
|
|
|
|
## Custom shaders / materials
|
|
|
|
|
|
|
|
|
|
Drop-in shaders live in `.material` resources (binary `RSCC` format) next
|
|
|
|
|
to each prop. `scripts/migrate_shaders.gd` is a one-shot tool that walks
|
|
|
|
|
`assets/` and rewrites the embedded shader code (Godot 3 → 4 keyword
|
|
|
|
|
renames: `depth_draw_alpha_prepass`, `hint_color`, `NORMALMAP`). Re-run it
|
|
|
|
|
with `godot --headless --script scripts/migrate_shaders.gd` after adding
|
|
|
|
|
new materials authored in Godot 3.
|
|
|
|
|
|
|
|
|
|
`assets/fonts/text_outline.material` is a VisualShader graph from Godot 3
|
|
|
|
|
that doesn't connect cleanly in 4 (out-of-bounds `p_from_port` errors,
|
|
|
|
|
COLOR never written). It has been detached from the Summary menu labels;
|
|
|
|
|
re-author it with Godot 4's built-in `theme_override_constants/outline_size`
|
|
|
|
|
+ `theme_override_colors/font_outline_color` instead.
|
|
|
|
|
|
|
|
|
|
## Known visual issues to revisit
|
|
|
|
|
|
|
|
|
|
- **Baked lightmap is gone.** `scenes/levels/warcraft/WarCraft.lmbake` is
|
|
|
|
|
in Godot 3's binary format (incompatible). The `LightmapGI` node still
|
|
|
|
|
exists in `WarCraft.tscn` but its `light_data` reference is cleared.
|
|
|
|
|
Open the scene in the editor and re-bake (Scene → Bake Lightmaps).
|
|
|
|
|
- **`assets/ui/themes/tab_select/UI-level-btn-shadow.png`** is 83% opaque
|
|
|
|
|
black with feathered edges (a drop-shadow texture). In Godot 4 it
|
|
|
|
|
renders stretched and opaque, which looked like a black square below
|
|
|
|
|
each level tile, so its `BackgroundTile` TextureRect is currently
|
|
|
|
|
`visible = false`. Replace with a proper 9-patch / shader if the shadow
|
|
|
|
|
effect is wanted.
|
|
|
|
|
- `developers/aurelien/` is a sandbox — files there (`ui_scrolls.tscn`,
|
|
|
|
|
`ui_tile.tscn`, `CheckLightmap.tscn`) still use Godot-3 property names
|
|
|
|
|
in places. Not on the main flow, low priority.
|
|
|
|
|
|
|
|
|
|
## CI
|
|
|
|
|
|
2026-05-17 12:36:34 +02:00
|
|
|
Gitea Actions workflow at `.gitea/workflows/build.yml`, documented in
|
|
|
|
|
`.gitea/workflows/README.md`. Three jobs: GDScript validation
|
|
|
|
|
(`godot --headless --import` + error grep), desktop matrix
|
|
|
|
|
(Windows / Linux / macOS), and Android. Build only — no Butler / itch.io
|
|
|
|
|
deploy currently wired (channels used historically were
|
|
|
|
|
`dev-crea/ahog:windows|android|linux|mac`). Drone pipeline removed.
|
2026-05-16 21:28:22 +02:00
|
|
|
|
|
|
|
|
Branches: default `dev`, releases from `main`. Long-running migration work
|
|
|
|
|
on `feature/godot-migration`.
|
|
|
|
|
|
|
|
|
|
## Conventions
|
|
|
|
|
|
|
|
|
|
- Don't commit `db/ahog.json` runtime mutations (lock progress saved
|
|
|
|
|
during play). The README documents using `git update-index --skip-worktree`
|
|
|
|
|
but the cleaner alternative is just to `git checkout db/ahog.json`
|
|
|
|
|
before staging.
|
|
|
|
|
- `android/`, `.godot/`, and `releases/` are in `.gitignore`. The
|
|
|
|
|
`.import` sidecars next to assets **are** committed (they carry the
|
|
|
|
|
stable UIDs Godot 4 generates).
|
|
|
|
|
- Commit messages: imperative, English, explain the *why* (especially for
|
|
|
|
|
migration commits — the next person reading the diff won't have the
|
|
|
|
|
Godot 3 context).
|