Implement i18n audit/localization cleanup and sync libstorm submodule
This commit is contained in:
278
docs/patterns.md
Normal file
278
docs/patterns.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Draugnorak Code Patterns Documentation
|
||||
|
||||
This document describes the standard patterns used throughout the Draugnorak codebase.
|
||||
|
||||
## Standard get_X_at(pos) Pattern
|
||||
|
||||
All world objects and creatures use a consistent lookup pattern for position-based queries.
|
||||
|
||||
### Pattern Template
|
||||
|
||||
```nvgt
|
||||
ClassName@ get_class_name_at(int pos) {
|
||||
for (uint i = 0; i < class_array.length(); i++) {
|
||||
if (class_array[i].position == pos) {
|
||||
return @class_array[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
- `Undead@ get_undead_at(int pos)` - Find undead creature at position
|
||||
- `Bandit@ get_bandit_at(int pos)` - Find bandit at position
|
||||
- `GroundGame@ get_ground_game_at(int pos)` - Find ground game animal at position
|
||||
- `FlyingCreature@ get_flying_creature_at(int pos)` - Find flying creature at position
|
||||
- `WorldFire@ get_fire_at(int pos)` - Find fire at position
|
||||
- `WorldSnare@ get_snare_at(int pos)` - Find snare at position
|
||||
- `WorldStream@ get_stream_at(int pos)` - Find stream at position
|
||||
- `WorldHerbGarden@ get_herb_garden_at_base()` - Special case for base buildings
|
||||
|
||||
### Usage Notes
|
||||
|
||||
- Always returns `null` if no object found at position
|
||||
- Use handle return type `@ClassName` for proper object reference
|
||||
- Position is always an `int` (tile coordinate)
|
||||
- Forward iteration is standard: `for (uint i = 0; i < array.length(); i++)`
|
||||
|
||||
## Creature Class Structure Pattern
|
||||
|
||||
All creature classes follow a consistent structure built on `Creature` base class.
|
||||
|
||||
### Base Class Structure
|
||||
|
||||
```nvgt
|
||||
class Creature {
|
||||
int position;
|
||||
int health;
|
||||
int max_health;
|
||||
int sound_handle;
|
||||
timer movement_timer;
|
||||
timer action_timer;
|
||||
|
||||
void update() { /* override me */ }
|
||||
void destroy() { /* cleanup */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Creature Type Classes
|
||||
|
||||
All creatures inherit from Creature and add type-specific behavior:
|
||||
|
||||
- `Undead` (zombies, vampires, ghosts) - `undead_type` field for variants
|
||||
- `Bandit` - Human enemy with "aggressive" and "wandering" states
|
||||
- `GroundGame` (game boars, mountain goats, rams) - `animal_type` field for variants
|
||||
- `FlyingCreature` (geese) - Config-driven with height, state fields
|
||||
|
||||
### Required Methods
|
||||
|
||||
Each creature class must implement:
|
||||
|
||||
```nvgt
|
||||
void update_creature(Creature@ creature) {
|
||||
// Movement logic
|
||||
// Attack logic
|
||||
// Sound management
|
||||
}
|
||||
|
||||
void clear_creatures() {
|
||||
for (uint i = 0; i < creatures.length(); i++) {
|
||||
if (creatures[i].sound_handle != -1) {
|
||||
p.destroy_sound(creatures[i].sound_handle);
|
||||
creatures[i].sound_handle = -1;
|
||||
}
|
||||
}
|
||||
creatures.resize(0);
|
||||
}
|
||||
```
|
||||
|
||||
### Update Loop Pattern
|
||||
|
||||
```nvgt
|
||||
void update_creatures() {
|
||||
for (uint i = 0; i < creatures.length(); i++) {
|
||||
update_creature(creatures[i]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sound Handle Lifecycle Management
|
||||
|
||||
Proper sound handle cleanup prevents memory leaks and ensures clean audio.
|
||||
|
||||
### Initialization
|
||||
|
||||
```nvgt
|
||||
class Creature {
|
||||
int sound_handle = -1; // Always initialize to -1 (invalid handle)
|
||||
|
||||
void play_sound() {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle); // Clean up existing sound first
|
||||
}
|
||||
sound_handle = p.play_1d("sounds/creature.ogg", position, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cleanup on Death/Destroy
|
||||
|
||||
```nvgt
|
||||
void destroy_creature(Creature@ creature) {
|
||||
if (creature.sound_handle != -1) {
|
||||
p.destroy_sound(creature.sound_handle);
|
||||
creature.sound_handle = -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Clear All Pattern
|
||||
|
||||
```nvgt
|
||||
void clear_creatures() {
|
||||
for (uint i = 0; i < creatures.length(); i++) {
|
||||
if (creatures[i].sound_handle != -1) {
|
||||
p.destroy_sound(creatures[i].sound_handle);
|
||||
creatures[i].sound_handle = -1;
|
||||
}
|
||||
}
|
||||
creatures.resize(0);
|
||||
}
|
||||
```
|
||||
|
||||
### Removal During Update Loop
|
||||
|
||||
When removing creatures during update iteration:
|
||||
|
||||
```nvgt
|
||||
void update_creatures() {
|
||||
for (uint i = 0; i < creatures.length(); i++) {
|
||||
update_creature(creatures[i]);
|
||||
|
||||
if (creatures[i].health <= 0) {
|
||||
if (creatures[i].sound_handle != -1) {
|
||||
p.destroy_sound(creatures[i].sound_handle);
|
||||
creatures[i].sound_handle = -1;
|
||||
}
|
||||
creatures.remove_at(i);
|
||||
i--; // Critical: decrement to skip the shifted element
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Rules
|
||||
|
||||
1. **Always initialize** sound_handle to -1
|
||||
2. **Check before destroying**: Always check `sound_handle != -1` before calling `p.destroy_sound()`
|
||||
3. **Reset after destroy**: Set `sound_handle = -1` after destroying
|
||||
4. **Clean before allocate**: If a sound is already playing, destroy it before creating a new one
|
||||
5. **Iterate carefully**: When removing elements during iteration, decrement the index after `remove_at(i)`
|
||||
|
||||
### Death Sound Pattern
|
||||
|
||||
```nvgt
|
||||
void play_creature_death_sound(string sound_path, int player_x, int creature_x, int volume_step) {
|
||||
int distance = abs(player_x - creature_x);
|
||||
if (distance <= volume_step) {
|
||||
p.play_stationary(sound_path, false);
|
||||
} else {
|
||||
// Too far to hear
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## File Organization Patterns
|
||||
|
||||
### Module Structure
|
||||
|
||||
Each major system is organized into:
|
||||
|
||||
1. **Orchestrator file** (in `src/`): Includes all sub-modules
|
||||
2. **Sub-module files** (in subdirectories): Focused functionality
|
||||
|
||||
Example:
|
||||
```
|
||||
src/
|
||||
inventory_menus.nvgt (orchestrator, ~45 lines)
|
||||
menus/
|
||||
menu_utils.nvgt (helpers)
|
||||
inventory_core.nvgt (personal inventory)
|
||||
storage_menu.nvgt (storage interactions)
|
||||
equipment_menu.nvgt (equipment)
|
||||
action_menu.nvgt (context actions)
|
||||
character_info.nvgt (stats display)
|
||||
base_info.nvgt (base status)
|
||||
altar_menu.nvgt (sacrifice)
|
||||
```
|
||||
|
||||
### Include Order
|
||||
|
||||
Orchestrator files include dependencies in order:
|
||||
|
||||
```nvgt
|
||||
// Utilities first (others depend on these)
|
||||
#include "base/module_utils.nvgt"
|
||||
|
||||
// Independent modules next
|
||||
#include "base/module_a.nvgt"
|
||||
#include "base/module_b.nvgt"
|
||||
|
||||
// Dependent modules last
|
||||
#include "base/module_c.nvgt" // depends on A and B
|
||||
```
|
||||
|
||||
## Menu System Patterns
|
||||
|
||||
### Menu Loop Structure
|
||||
|
||||
```nvgt
|
||||
void run_menu() {
|
||||
speak_with_history("Menu name.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {"Option 1", "Option 2"};
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick(); // Always call this first
|
||||
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
speak_with_history("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
// Handle selection
|
||||
break; // or rebuild options
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Menu Background Tick
|
||||
|
||||
All menus must call `menu_background_tick()` each iteration to:
|
||||
|
||||
- Update time
|
||||
- Update environment
|
||||
- Process game events (fires, enemies, blessings)
|
||||
- Check for fire damage
|
||||
- Handle healing
|
||||
- Check for death
|
||||
|
||||
This ensures the game continues running while menus are open.
|
||||
Reference in New Issue
Block a user