# 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.