A bit of repository cleanup.

This commit is contained in:
Storm Dragon
2026-02-27 23:58:21 -05:00
parent 91ef310e14
commit 874512e3b2
2 changed files with 0 additions and 452 deletions
-229
View File
@@ -1,229 +0,0 @@
# Draugnorak Project Guidelines
## NVGT Workflow
- Use the `nvgt-engine-dev` skill for NVGT API behavior, include/module decisions, and engine-source verification.
- If API behavior is uncertain, verify against primary sources in `nvgt/` (engine source) and `docs/nvgt/` before coding.
- Reuse shared helpers in `libstorm-nvgt/` before creating project-local replacements.
## libstorm-nvgt Workflow
- Treat `~/git/libstorm-nvgt` as the canonical repository for shared library changes.
- Do not make canonical library changes directly inside this repo's `libstorm-nvgt/` copy first.
- Hard stop: do not implement shared-library edits in this repo's `libstorm-nvgt/` submodule copy. Make the change in `~/git/libstorm-nvgt`, commit it there, then update the submodule pointer here.
- When shared helper behavior must change:
- Implement and commit the change in `~/git/libstorm-nvgt`.
- Update this repo to the new `libstorm-nvgt` submodule commit.
- Verify Draugnorak still compiles with `./nvgt -c draugnorak.nvgt` after updating the pointer.
## AGENTS.md Maintenance
- Keep this file aligned with the real repository layout and active workflows.
- Update `AGENTS.md` in the same PR/commit when any of the following change:
- Core directories/modules (for example new systems under `src/`, `scripts/`, or `docs/`).
- Required build/test/validation commands.
- Localization workflow expectations.
- Canonical engine/library source locations or skill usage expectations.
- If a section is uncertain or outdated, replace it with a short verified statement instead of leaving stale instructions.
## Project Structure
Key paths (focused, not exhaustive):
- `draugnorak.nvgt` - Main game entry point.
- `src/` - Core gameplay systems.
- `constants.nvgt`, `player.nvgt`, `time_system.nvgt` - Global state and timing.
- `world_state.nvgt`, `src/world/` - World object orchestration and world systems.
- `src/enemies/`, `combat.nvgt`, `creature_*` - Enemy and combat behavior.
- `inventory.nvgt`, `inventory_items.nvgt`, `inventory_menus.nvgt`, `item_registry.nvgt` - Inventory/equipment/items.
- `crafting.nvgt`, `src/crafting/` - Crafting orchestration and recipe categories.
- `quest_system.nvgt`, `src/quests/`, `src/bosses/` - Quests, adventures, and boss content.
- `base_system.nvgt`, `environment.nvgt`, `weather.nvgt`, `fishing.nvgt`, `pet_system.nvgt`, `fylgja_system.nvgt` - Survival systems.
- `i18n.nvgt` - Translation loading and lookup wrappers.
- `save_system.nvgt` - Save/load behavior.
- `lang/` - Localization catalogs (`en.ini`, `en.template.ini`, and per-language files such as `es.ini`).
- `scripts/` - Build and i18n tooling (`generate_i18n_catalog.py`, `validate_i18n_catalog.py`, `audit_untranslated_strings.py`).
- `docs/` - Implementation references (`patterns.md`, `rune_system.md`, `localization.md`).
- `libstorm-nvgt/` - Shared NVGT helpers reused by this project.
- `sounds/` - Game audio assets.
## Game Mechanics
### Inventory Limits
- Personal inventory stacks are capped at 9
- Skin pouches add +2 stack capacity, backpacks add +9
### Map Layout
- Base area: x 0-4 (wood terrain)
- Grass area: x 5-19 (grass terrain, contains trees)
- Gravel area: x 20-34 (gravel terrain, contains stones)
### Healing System
- Base area provides passive healing when player health < max
- Without herb garden: 1 HP every 2.5 minutes (150000ms)
- With herb garden: 1 HP every 30 seconds (30000ms)
- Herb garden can only be built in base area (x <= 4)
- Only one herb garden allowed per base
### Fire System
- Fires require a firepit to build
- New fires start with 12 minutes (720000ms) of fuel
- Warning at 30 seconds remaining
- Fire goes out when fuel depletes
- Fire sound plays within 3 tiles distance
- Jumping over fire prevents damage
- Feeding fire is done from the Action menu (A)
### Snare System
- Snares become active when player moves away
- Check every hour for catching/escaping small game
- Catch chance starts at 5%, increases by 5% per hour (max 75%)
- Escape chance: 0% for first hour after catch, then increases by 2% per hour (max 95%)
- Snare sound plays within 2 tiles distance
- Residents can retrieve game from snares (daytime only, requires meat in storage)
### Tree System
- Trees regenerate fully after ~5 minutes (minute-by-minute refill logic)
- Tree ambient sound plays within 4 tiles distance
- Trees only play sound when not chopped
- Climb down only when in a tree (Down arrow does nothing during jumps or when not in a tree)
- Trees have height; falling damage is applied when falling from height
### Notification System
- Important events use `notify()` function (plays sounds/notify.ogg + speaks message)
- Last 10 notifications stored in history
- Navigation keys:
- `\` - Repeat most recent notification
- `]` - Next (newer) notification
- `[` (Shift+Comma) - Previous (older) notification
- Navigating history speaks message without notification sound
- Speech history (screen_reader_speak wrapper) uses `,` and `.` for previous/next message
### Search System
- Hold Shift for 1 second to search
- Search completes after 1-second delay
### Undead System
- At night, zombies spawn (5 max) and wander outside the base
- Zombies cannot enter the base while barricade health > 0
- Zombies attack the barricade for 4-6 damage when they reach it; play `sounds/enemies/zombie_hit.ogg`
- Zombies vanish at daybreak
- Player can hit zombies with spear (1-tile range) or sling (8-tile range)
### Barricade System
- Base starts with 100 barricade health, capped at 500
- In base, press B to report barricade health
- Crafting menu includes Barricade category for reinforcement
- Reinforcement costs and health: 3 sticks (+10), 5 vines (+15), 1 log (+30), 5 stones (+20)
- Barricade health does not reset during gameplay
### Mountains, Rope Climbing, Falling
- Mountain ranges can appear during area expansion with elevation-based terrain
- Steep slopes require a rope; the player is prompted to press Up/Down to climb
- Moving left/right during rope climb cancels and causes a fall
- Falling damage is applied beyond a safe height (10 feet)
### World Expansion & Invasions
- After day 2, bandit invasions can trigger and expand the map to the east
- Expansion is either a 30-tile biome strip or a 60-tile mountain range
- Invasions last 1 hour and spawn bandits in the expanded area during daytime
### Weather & Ambience
- Weather transitions between clear, windy, rainy, stormy with wind/rain/thunder audio
- Day/night ambience crossfades on hour changes
### Inventory + Equipment
- Item definitions live in `src/item_registry.nvgt` (personal + storage inventories)
- Equipment includes spear, axe, sling, bow, clothing, pouches, backpacks
- Combat logic currently supports spear/axe/sling; bow attacks are not implemented yet
- Quivers gate arrow capacity (12 arrows per quiver)
- Quick slots (keys 1-0) bind equipment from the Equipment menu
### Buildings
- Firepit, Fire, Herb Garden, Storage, Pasture, Stable, Altar
- Storage enables base inventory menus and increases resident recruitment chance
### Residents and Base Automation
- Residents consume meat daily, can repair the barricade, defend with stored weapons, and collect resources
- Residents use spears or slings (requires stones) for defense
- Daily weapon breakage occurs based on resident count
### Favor, Altars, Incense, Blessings
- Altar sacrifices (S key, base only) convert items to Favor
- Burning incense (A key action) grants Favor per hour while active
- Blessings can trigger (heal, speed boost, barricade repair) and consume Favor
### Quests (Mini-Games)
- Quest menu (Q, base only) appears after altar/favor requirements
- Max 4 active quests; rewards grant Favor and resources
### Adventures & Bosses
- Adventure menu (Tab) available outside base; limited to once per day
- Mountain adventure: Unicorn boss; victory grants Favor and unlocks Rune of Swiftness
### Runes
- Rune system in `src/runes/` (data + effects)
- Runes are crafted in Crafting > Runes after unlock (requires knife + clay + favor)
- Rune of Swiftness grants move speed and gathering bonuses; runed items are tracked separately
## Menu Structure
### Crafting Menu (C key, base only)
Organized into categories:
- **Weapons**: Spear, Sling
- **Tools**: Knife, Snare, Stone Axe, Fishing Pole, Rope, Reed Basket, Clay Pot
- **Materials**: Butcher Game, Incense
- **Clothing**: Skin gear, Moccasins, Pouch, Backpack
- **Buildings**: Firepit, Fire, Herb Garden, Storage, Pasture, Stable, Altar
- **Barricade**: Reinforcement options
- **Runes**: Available after rune unlocks
### Equipment Menu (E key)
- Only shows items player actually has
- Shows equipped status
- Says "Nothing to equip" if inventory is empty
### Inventory Menu (I key)
- Base + storage built: root menu for personal vs storage
- No storage: personal inventory only
### Action Menu (A key)
- Place snares, feed fires, burn incense (context-sensitive)
- Tab in menu performs “max” action for the selected option
### Base Info (B key)
- Barricade health, residents, storage totals, base buildings
### Quest Menu (Q key)
- Base-only quest selection for mini-games
### Adventure Menu (Tab key)
- Terrain-based adventures outside the base (once per day)
## Testing
- Always run `./nvgt -c draugnorak.nvgt` after code changes
- This compiles without opening the game window and prevents it from taking over the terminal
- If user-facing text changed, also run:
- `python3 scripts/generate_i18n_catalog.py`
- `python3 scripts/validate_i18n_catalog.py`
- `python3 scripts/audit_untranslated_strings.py`
## Localization Workflow
- Any new or changed user-facing string must be localizable; avoid hardcoded UI/gameplay text in NVGT files.
- Update the English catalogs in the same change as text changes by running `python3 scripts/generate_i18n_catalog.py`.
- Keep `lang/en.ini` and `lang/en.template.ini` in sync with code changes.
- Validate all translation catalogs with `python3 scripts/validate_i18n_catalog.py` and fix missing keys/placeholders before merging.
- Run `python3 scripts/audit_untranslated_strings.py` after text-heavy changes; keep `scripts/i18n_audit_allowlist.txt` small and commented.
- Preserve placeholders exactly as written (for example `{arg1}`, `{language}`) and keep escapes such as `\n` and `\t` intact.
- For new translations, copy `lang/en.template.ini` to `lang/<code>.ini`, then update `[meta]` (`code`, `name`, `native_name`) to match the file.
- Translator-facing instructions live in `translate.md`; developer-level localization details live in `docs/localization.md`.
## Code Standards
- Use `notify()` for important game events that should be reviewable
- Use `screen_reader_speak()` for immediate feedback that doesn't need to be stored
- All menus must call `menu_background_tick()` each loop to keep game state updating
- **Time references**: When discussing time (hours, minutes, days), assume game time unless explicitly stated as "real time". Game time runs at 60x speed: 1 real minute = 1 game hour, 24 real minutes = 1 game day.
- See `docs/patterns.md` for module and creature patterns
- See `docs/rune_system.md` for rune data/effects/save requirements
-223
View File
@@ -1,223 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# create Draugnorak package for all platforms.
tmpAndroidToolsDir=""
tmpAndroidConfigBackup=""
createdAndroidConfig="0"
tmpAndroidWrapperSource=""
tmpAndroidPackFile=""
tmpAndroidPackBuilder=""
trap 'rm -fv README.html; if [[ -n "${tmpAndroidToolsDir}" && -d "${tmpAndroidToolsDir}" ]]; then rm -rf "${tmpAndroidToolsDir}"; fi; if [[ -n "${tmpAndroidConfigBackup}" && -f "${tmpAndroidConfigBackup}" ]]; then mv -f "${tmpAndroidConfigBackup}" draugnorak.properties; elif [[ "${createdAndroidConfig}" == "1" ]]; then rm -f draugnorak.properties; fi; if [[ -n "${tmpAndroidWrapperSource}" && -f "${tmpAndroidWrapperSource}" ]]; then rm -f "${tmpAndroidWrapperSource}"; fi; if [[ -n "${tmpAndroidPackBuilder}" && -f "${tmpAndroidPackBuilder}" ]]; then rm -f "${tmpAndroidPackBuilder}"; fi; if [[ -n "${tmpAndroidPackFile}" && -f "${tmpAndroidPackFile}" ]]; then rm -f "${tmpAndroidPackFile}"; fi' EXIT
escape_nvgt_string() {
local inputValue="$1"
inputValue="${inputValue//\\/\\\\}"
inputValue="${inputValue//\"/\\\"}"
printf '%s\n' "${inputValue}"
}
find_android_sdk_root() {
local sdkRoot
for sdkRoot in "${ANDROID_HOME:-}" "${ANDROID_SDK_HOME:-}" "/opt/android-sdk" "${HOME}/Android/Sdk" "${HOME}/Android/sdk"; do
if [[ -n "${sdkRoot}" && -d "${sdkRoot}" ]]; then
printf '%s\n' "${sdkRoot}"
return 0
fi
done
return 1
}
find_latest_build_tools_dir() {
local sdkRoot="$1"
local buildToolsDir=""
if [[ ! -d "${sdkRoot}/build-tools" ]]; then
return 1
fi
buildToolsDir="$(find "${sdkRoot}/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n 1)"
if [[ -z "${buildToolsDir}" ]]; then
return 1
fi
printf '%s\n' "${buildToolsDir}"
}
find_latest_android_jar() {
local sdkRoot="$1"
local androidJar=""
if [[ ! -d "${sdkRoot}/platforms" ]]; then
return 1
fi
androidJar="$(find "${sdkRoot}/platforms" -mindepth 2 -maxdepth 2 -type f -name android.jar | sort -V | tail -n 1)"
if [[ -z "${androidJar}" ]]; then
return 1
fi
printf '%s\n' "${androidJar}"
}
setup_android_tools_workaround() {
local sdkRoot="$1"
local buildToolsDir="$2"
local androidJar="$3"
local javaBin=""
local keytoolBin=""
local adbBin=""
tmpAndroidToolsDir="$(mktemp -d)"
ln -sf "${buildToolsDir}/zipalign" "${tmpAndroidToolsDir}/zipalign"
ln -sf "${buildToolsDir}/lib/apksigner.jar" "${tmpAndroidToolsDir}/apksigner.jar"
ln -sf "${androidJar}" "${tmpAndroidToolsDir}/android.jar"
if command -v java >/dev/null 2>&1; then
javaBin="$(command -v java)"
ln -sf "${javaBin}" "${tmpAndroidToolsDir}/java"
fi
if command -v keytool >/dev/null 2>&1; then
keytoolBin="$(command -v keytool)"
ln -sf "${keytoolBin}" "${tmpAndroidToolsDir}/keytool"
fi
if command -v adb >/dev/null 2>&1; then
adbBin="$(command -v adb)"
ln -sf "${adbBin}" "${tmpAndroidToolsDir}/adb"
fi
cat > "${tmpAndroidToolsDir}/aapt2" <<EOF
#!/usr/bin/env bash
set -euo pipefail
filteredArgs=()
for arg in "\$@"; do
if [[ -n "\${arg}" ]]; then
filteredArgs+=("\${arg}")
fi
done
exec "${buildToolsDir}/aapt2" "\${filteredArgs[@]}"
EOF
chmod +x "${tmpAndroidToolsDir}/aapt2"
echo "Using Android SDK at: ${sdkRoot}"
echo "Using build-tools: ${buildToolsDir}"
echo "Using android.jar: ${androidJar}"
}
build_android_sound_pack() {
local outputPackPath="$1"
local builderScriptPath="$2"
local sourcePath=""
local escapedSourcePath=""
local escapedOutputPath=""
escapedOutputPath="$(escape_nvgt_string "${outputPackPath}")"
cat > "${builderScriptPath}" <<EOF
void main() {
pack soundPack;
if (!soundPack.open("${escapedOutputPath}", PACK_OPEN_MODE_CREATE)) return;
EOF
while IFS= read -r sourcePath; do
escapedSourcePath="$(escape_nvgt_string "${sourcePath}")"
printf ' if (!soundPack.add_file("%s", "%s")) return;\n' "${escapedSourcePath}" "${escapedSourcePath}" >> "${builderScriptPath}"
done < <(find sounds -type f | sort)
cat >> "${builderScriptPath}" <<'EOF'
soundPack.close();
}
EOF
./nvgt "${builderScriptPath}"
if [[ ! -f "${outputPackPath}" ]]; then
echo "Failed to build Android sound pack: ${outputPackPath}"
exit 1
fi
}
# Generate documentation
pandoc -s files/instructions.md -o README.html
# Create packages
for platformName in linux mac windows android; do
echo "Packaging Draugnorak for ${platformName^}..."
if [[ "${platformName}" == "android" ]]; then
androidKeystorePath="${DRAUGNORAK_ANDROID_KEYSTORE:-${PWD}/.nvgt_android.keystore}"
sdkRoot="$(find_android_sdk_root || true)"
if [[ -z "${sdkRoot}" ]]; then
echo "Android SDK not found. Set ANDROID_HOME or ANDROID_SDK_HOME."
exit 1
fi
buildToolsDir="$(find_latest_build_tools_dir "${sdkRoot}" || true)"
if [[ -z "${buildToolsDir}" || ! -x "${buildToolsDir}/aapt2" || ! -x "${buildToolsDir}/zipalign" || ! -f "${buildToolsDir}/lib/apksigner.jar" ]]; then
echo "Missing required Android build tools in ${sdkRoot}."
exit 1
fi
androidJar="$(find_latest_android_jar "${sdkRoot}" || true)"
if [[ -z "${androidJar}" ]]; then
echo "No android.jar found in ${sdkRoot}/platforms."
exit 1
fi
setup_android_tools_workaround "${sdkRoot}" "${buildToolsDir}" "${androidJar}"
if [[ -f draugnorak.properties ]]; then
tmpAndroidConfigBackup="$(mktemp)"
cp draugnorak.properties "${tmpAndroidConfigBackup}"
cat "${tmpAndroidConfigBackup}" > draugnorak.properties
else
createdAndroidConfig="1"
fi
{
printf '%s\n' "build.android_install = 0"
printf '%s\n' "build.android_signature_cert = ${androidKeystorePath}"
printf '%s\n' "build.output_basename = draugnorak"
} >> draugnorak.properties
tmpAndroidPackFile=".draugnorak_android_sounds.dat"
tmpAndroidPackBuilder=".draugnorak_make_android_pack.nvgt"
build_android_sound_pack "${tmpAndroidPackFile}" "${tmpAndroidPackBuilder}"
tmpAndroidWrapperSource=".draugnorak_android_bundle_pragmas.nvgt"
cat > "${tmpAndroidWrapperSource}" <<'EOF'
#pragma embed ".draugnorak_android_sounds.dat"
#pragma asset files
#pragma document "README.html"
pack@ androidSoundPack;
bool preglobals() {
@androidSoundPack = pack();
if (androidSoundPack.open("*.draugnorak_android_sounds.dat", PACK_OPEN_MODE_READ)) {
@sound_default_pack = @androidSoundPack;
}
return true;
}
EOF
PATH="${tmpAndroidToolsDir}:${PATH}" ./nvgt -c --platform "${platformName}" --include "${tmpAndroidWrapperSource}" draugnorak.nvgt
else
./nvgt -c --platform "${platformName}" draugnorak.nvgt
fi
case "${platformName}" in
"linux"|"windows")
zip -r draugnorak.zip files/ sounds/ README.html
mv draugnorak.zip "draugnorak-${platformName}.zip"
;;
"mac")
mkdir -p draugnorak.app/Contents/Resources
cp -r files draugnorak.app/Contents/Resources
cp -r sounds draugnorak.app/Contents/Resources
cp README.html draugnorak.app/Contents/Resources
zip -r draugnorak.app.zip draugnorak.app fix_mac.sh
rm -rfv draugnorak.app/
;;
"android")
zip -r draugnorak-android.zip draugnorak.apk files/ sounds/ README.html
;;
esac
done
echo "Packaging complete!"
exit 0