A ton of changes including bug fixes, new craftable items, altar with favor system added. Info for player and base added.

This commit is contained in:
Storm Dragon
2026-01-18 22:25:38 -05:00
parent 95a3f4a96c
commit 489408a181
40 changed files with 3910 additions and 1046 deletions
+3
View File
@@ -10,9 +10,12 @@ A survival audio game built with NVGT.
- **E**: Open Equipment Menu.
- **A**: Open Action Menu (Place objects, feed fire).
- **I**: Check Inventory.
- **P**: Character info.
- **Q**: Quests menu (base only).
- **H**: Check Health.
- **T**: Check Time.
- **X**: Check Coordinates.
- **B**: Base info menu (base only).
- **Escape**: Exit game.
## Gameplay
+23 -12
View File
@@ -8,10 +8,13 @@ sound_pool p(100);
#include "src/constants.nvgt"
#include "src/player.nvgt"
#include "src/world_state.nvgt"
#include "src/ui.nvgt"
#include "src/inventory.nvgt"
#include "src/quest_system.nvgt"
#include "src/environment.nvgt"
#include "src/combat.nvgt"
#include "src/save_system.nvgt"
#include "src/base_system.nvgt"
#include "src/time_system.nvgt"
#include "src/audio_utils.nvgt"
#include "src/notify.nvgt"
@@ -70,9 +73,11 @@ void main()
game_started = true;
} else {
if (has_save_game()) {
screen_reader_speak("Unable to load save.", true);
string message = last_save_error;
if (message == "") message = "Unable to load save.";
ui_info_box("Draugnorak", "Load Game", message);
} else {
screen_reader_speak("No save found.", true);
ui_info_box("Draugnorak", "Load Game", "No save found.");
}
}
} else {
@@ -86,22 +91,23 @@ void main()
if(key_pressed(KEY_ESCAPE))
{
int really_exit = virtual_question("Draugnorak", "Really exit?");
int really_exit = ui_question("Draugnorak", "Really exit?");
if (really_exit == 1) {
exit();
}
// Restore focus to the game window
show_window("Draugnorak");
}
// Time & Environment updates
update_time();
update_crossfade();
update_environment();
update_snares();
update_streams();
update_fires();
update_zombies();
update_bandits();
update_blessings();
update_notifications();
// Fire damage check (only if not jumping)
WorldFire@ fire_on_tile = get_fire_at(x);
@@ -135,6 +141,8 @@ void main()
check_action_menu(x);
check_crafting_menu(x, BASE_END);
check_equipment_menu();
check_quest_menu();
check_quick_slot_keys();
check_time_input();
check_notification_keys();
@@ -149,13 +157,9 @@ void main()
screen_reader_speak(direction_label + ", x " + x + ", y " + y, true);
}
// Barricade Key (base only)
// Base Info Key (base only)
if (key_pressed(KEY_B)) {
if (x <= BASE_END) {
screen_reader_speak("Barricade health " + barricade_health + " of " + BARRICADE_MAX_HEALTH, true);
} else {
screen_reader_speak("You are not in the base.", true);
}
run_base_info_menu();
}
// Climbing and Falling Updates
@@ -259,7 +263,14 @@ void main()
}
// Searching Logic
if((key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT)) && search_timer.elapsed > 2000 && !searching)
bool shift_down = (key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT));
if (!shift_down) {
if (searching) {
searching = false;
}
search_timer.restart();
}
if (shift_down && search_timer.elapsed > 2000 && !searching)
{
searching = true;
search_delay_timer.restart();
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+39
View File
@@ -0,0 +1,39 @@
// Base automation helpers
int get_daily_food_requirement() {
if (residents_count <= 0) return 0;
return (residents_count + 1) / 2;
}
void consume_food_for_residents() {
int needed = get_daily_food_requirement();
if (needed <= 0) return;
if (storage_meat >= needed) {
storage_meat -= needed;
} else {
storage_meat = 0;
if (x <= BASE_END) {
notify("No food, residents are hungry.");
}
}
}
void keep_base_fires_fed() {
if (residents_count <= 0) return;
if (storage_meat <= 0) return;
if (storage_sticks <= 0 && storage_logs <= 0) return;
for (uint i = 0; i < world_fires.length(); i++) {
if (world_fires[i].position > BASE_END) continue;
if (!world_fires[i].is_burning()) continue;
if (world_fires[i].fuel_remaining > 300000) continue;
if (storage_sticks > 0) {
storage_sticks--;
world_fires[i].add_fuel(300000); // 5 minutes
} else if (storage_logs > 0) {
storage_logs--;
world_fires[i].add_fuel(720000); // 12 minutes
}
break;
}
}
+3 -3
View File
@@ -126,13 +126,13 @@ void release_sling_attack(int player_x) {
return;
}
// Find target in facing direction (5 tiles)
// Find target in facing direction
int search_direction = (facing == 1) ? 1 : -1;
int target_x = -1;
bool hit_bandit = false;
// Priority: Find nearest enemy (bandit or zombie) first
for (int dist = 1; dist <= 5; dist++) {
for (int dist = 1; dist <= SLING_RANGE; dist++) {
int check_x = player_x + (dist * search_direction);
if (check_x < 0 || check_x >= MAP_SIZE) break;
@@ -155,7 +155,7 @@ void release_sling_attack(int player_x) {
// If no enemy found, check for trees (but don't damage them)
if (target_x == -1) {
for (int dist = 1; dist <= 5; dist++) {
for (int dist = 1; dist <= SLING_RANGE; dist++) {
int check_x = player_x + (dist * search_direction);
if (check_x < 0 || check_x >= MAP_SIZE) break;
Tree@ tree = get_tree_at(check_x);
+37 -1
View File
@@ -12,15 +12,25 @@ int expanded_area_end = -1;
// Movement configuration
int movetime = 400; // Time between steps/movements
int walk_speed = 400;
const int BASE_WALK_SPEED = 400;
const int MOCCASINS_WALK_SPEED = 360;
int walk_speed = BASE_WALK_SPEED;
int jump_speed = 170;
const int MAX_ITEM_STACK = 9;
const int POUCH_STACK_BONUS = 2;
const int BASE_STORAGE_MAX = 50;
const int BLESSING_HEAL_AMOUNT = 3;
const int BLESSING_BARRICADE_REPAIR = 20;
const int BLESSING_SPEED_DURATION = 300000;
const int BLESSING_TRIGGER_CHANCE = 10;
const int BLESSING_WALK_SPEED = 320;
// Weapon damage
const int SPEAR_DAMAGE = 3;
const int AXE_DAMAGE = 4;
const int SLING_DAMAGE_MIN = 5;
const int SLING_DAMAGE_MAX = 8;
const int SLING_RANGE = 7;
// Zombie settings
const int ZOMBIE_HEALTH = 12;
@@ -47,6 +57,18 @@ const int BARRICADE_LOG_HEALTH = 30;
const int BARRICADE_STONE_COST = 5;
const int BARRICADE_STONE_HEALTH = 20;
// Building costs
const int STORAGE_LOG_COST = 6;
const int STORAGE_STONE_COST = 9;
const int STORAGE_VINE_COST = 8;
const int PASTURE_LOG_COST = 8;
const int PASTURE_VINE_COST = 20;
const int STABLE_LOG_COST = 10;
const int STABLE_STONE_COST = 15;
const int STABLE_VINE_COST = 10;
const int ALTAR_STONE_COST = 9;
const int ALTAR_STICK_COST = 3;
// Bandit settings
const int BANDIT_HEALTH = 4;
const int BANDIT_MAX_COUNT = 3;
@@ -61,3 +83,17 @@ const int BANDIT_FOOTSTEP_MAX_DISTANCE = 7;
const float BANDIT_SOUND_VOLUME_STEP = 3.0;
const int BANDIT_ATTACK_MAX_HEIGHT = 6;
const int INVASION_DURATION_HOURS = 1;
const int BANDIT_DETECTION_RADIUS = 5;
const int BANDIT_WANDER_DIRECTION_CHANGE_MIN = 3000;
const int BANDIT_WANDER_DIRECTION_CHANGE_MAX = 8000;
// Stream audio
const int STREAM_SOUND_RANGE = 5;
const float STREAM_SOUND_VOLUME_STEP = 0.6;
const int QUEST_MAX_ACTIVE = 4;
const int QUEST_CHANCE_PER_FAVOR = 10;
const int QUEST_MIN_CHANCE = 5;
const double QUEST_FAVOR_PER_POINT = 0.05;
const int QUEST_STONE_SCORE = 6;
const int QUEST_LOG_SCORE = 10;
const int QUEST_SKIN_SCORE = 14;
+897
View File
@@ -0,0 +1,897 @@
// Crafting menus and recipes
void check_crafting_menu(int x, int base_end_tile) {
if (x <= base_end_tile) {
if (key_pressed(KEY_C)) {
run_crafting_menu();
}
}
}
void run_crafting_menu() {
screen_reader_speak("Crafting menu.", true);
int selection = 0;
string[] categories = {"Weapons", "Tools", "Clothing", "Buildings", "Barricade"};
int[] category_types = {0, 1, 2, 3, 4};
if (world_altars.length() > 0) {
categories.insert_last("Altar");
category_types.insert_last(5);
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= categories.length()) selection = 0;
screen_reader_speak(categories[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = categories.length() - 1;
screen_reader_speak(categories[selection], true);
}
if (key_pressed(KEY_RETURN)) {
int category = category_types[selection];
if (category == 0) run_weapons_menu();
else if (category == 1) run_tools_menu();
else if (category == 2) run_clothing_menu();
else if (category == 3) run_buildings_menu();
else if (category == 4) run_barricade_menu();
else if (category == 5) run_altar_menu();
break;
}
}
}
void run_weapons_menu() {
screen_reader_speak("Weapons.", true);
int selection = 0;
string[] options = {
"Spear (1 Stick, 1 Vine, 1 Stone) [Requires Knife]",
"Sling (1 Skin, 2 Vines)"
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_spear();
else if (selection == 1) craft_sling();
break;
}
}
}
void run_tools_menu() {
screen_reader_speak("Tools.", true);
int selection = 0;
string[] options = {
"Stone Knife (2 Stones)",
"Snare (1 Stick, 2 Vines)",
"Stone Axe (1 Stick, 1 Vine, 2 Stones) [Requires Knife]",
"Fishing Pole (1 Stick, 2 Vines)",
"Rope (3 Vines)",
"Reed Basket (3 Reeds)",
"Clay Pot (3 Clay)",
"Butcher Small Game (1 Small Game) [Requires Knife and Fire nearby]"
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_knife();
else if (selection == 1) craft_snare();
else if (selection == 2) craft_axe();
else if (selection == 3) craft_fishing_pole();
else if (selection == 4) craft_rope();
else if (selection == 5) craft_reed_basket();
else if (selection == 6) craft_clay_pot();
else if (selection == 7) butcher_small_game();
break;
}
}
}
void run_clothing_menu() {
screen_reader_speak("Clothing.", true);
int selection = 0;
string[] options = {
"Skin Hat (1 Skin, 1 Vine)",
"Skin Gloves (1 Skin, 1 Vine)",
"Skin Pants (6 Skins, 3 Vines)",
"Skin Tunic (4 Skins, 2 Vines)",
"Moccasins (2 Skins, 1 Vine)",
"Skin Pouch (2 Skins, 1 Vine)"
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_skin_hat();
else if (selection == 1) craft_skin_gloves();
else if (selection == 2) craft_skin_pants();
else if (selection == 3) craft_skin_tunic();
else if (selection == 4) craft_moccasins();
else if (selection == 5) craft_skin_pouch();
break;
}
}
}
void run_buildings_menu() {
screen_reader_speak("Buildings.", true);
int selection = 0;
string[] options = {
"Firepit (9 Stones)",
"Fire (2 Sticks, 1 Log) [Requires Firepit]",
"Herb Garden (9 Stones, 3 Vines, 2 Logs) [Base Only]",
"Storage (6 Logs, 9 Stones, 8 Vines) [Base Only]",
"Pasture (8 Logs, 20 Vines) [Base Only]",
"Stable (10 Logs, 15 Stones, 10 Vines) [Base Only]",
"Altar (9 Stones, 3 Sticks) [Base Only]"
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_firepit();
else if (selection == 1) craft_campfire();
else if (selection == 2) craft_herb_garden();
else if (selection == 3) craft_storage();
else if (selection == 4) craft_pasture();
else if (selection == 5) craft_stable();
else if (selection == 6) craft_altar();
break;
}
}
}
void run_barricade_menu() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
screen_reader_speak("Barricade.", true);
int selection = 0;
string[] options;
int[] action_types; // 0 = sticks, 1 = vines, 2 = log, 3 = stones
if (inv_sticks >= BARRICADE_STICK_COST) {
options.insert_last("Reinforce with sticks (" + BARRICADE_STICK_COST + " sticks, +" + BARRICADE_STICK_HEALTH + " health)");
action_types.insert_last(0);
}
if (inv_vines >= BARRICADE_VINE_COST) {
options.insert_last("Reinforce with vines (" + BARRICADE_VINE_COST + " vines, +" + BARRICADE_VINE_HEALTH + " health)");
action_types.insert_last(1);
}
if (inv_logs >= BARRICADE_LOG_COST) {
options.insert_last("Reinforce with log (" + BARRICADE_LOG_COST + " log, +" + BARRICADE_LOG_HEALTH + " health)");
action_types.insert_last(2);
}
if (inv_stones >= BARRICADE_STONE_COST) {
options.insert_last("Reinforce with stones (" + BARRICADE_STONE_COST + " stones, +" + BARRICADE_STONE_HEALTH + " health)");
action_types.insert_last(3);
}
if (options.length() == 0) {
screen_reader_speak("No materials to reinforce the barricade.", true);
return;
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
int action = action_types[selection];
if (action == 0) reinforce_barricade_with_sticks();
else if (action == 1) reinforce_barricade_with_vines();
else if (action == 2) reinforce_barricade_with_log();
else if (action == 3) reinforce_barricade_with_stones();
break;
}
}
}
void simulate_crafting() {
screen_reader_speak("Crafting...", true);
timer t;
int duration = 4000;
int next_sound = 0;
while(t.elapsed < duration) {
if(t.elapsed > next_sound) {
float pitch = random(85, 115);
p.play_stationary_extended("sounds/crafting.ogg", false, 0, 0, 0, pitch);
next_sound = t.elapsed + 800;
}
wait(5);
menu_background_tick();
}
p.play_stationary("sounds/crafting_complete.ogg", false);
}
void craft_knife() {
string missing = "";
if (inv_stones < 2) missing += "2 stones ";
if (missing == "") {
if (inv_knives >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more stone knives.", true);
return;
}
simulate_crafting();
inv_stones -= 2;
inv_knives++;
screen_reader_speak("Crafted a Stone Knife.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_spear() {
string missing = "";
if (inv_knives < 1) missing += "Stone Knife ";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 1) missing += "1 vine ";
if (inv_stones < 1) missing += "1 stone ";
if (missing == "") {
if (inv_spears >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more spears.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines--;
inv_stones--;
inv_spears++;
screen_reader_speak("Crafted a Spear.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_sling() {
string missing = "";
if (inv_skins < 1) missing += "1 skin ";
if (inv_vines < 2) missing += "2 vines ";
if (missing == "") {
if (inv_slings >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more slings.", true);
return;
}
simulate_crafting();
inv_skins--;
inv_vines -= 2;
inv_slings++;
screen_reader_speak("Crafted a Sling.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_skin_hat() {
string missing = "";
if (inv_skins < 1) missing += "1 skin ";
if (inv_vines < 1) missing += "1 vine ";
if (missing == "") {
if (inv_skin_hats >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more skin hats.", true);
return;
}
simulate_crafting();
inv_skins--;
inv_vines--;
inv_skin_hats++;
screen_reader_speak("Crafted a Skin Hat.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_skin_gloves() {
string missing = "";
if (inv_skins < 1) missing += "1 skin ";
if (inv_vines < 1) missing += "1 vine ";
if (missing == "") {
if (inv_skin_gloves >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more skin gloves.", true);
return;
}
simulate_crafting();
inv_skins--;
inv_vines--;
inv_skin_gloves++;
screen_reader_speak("Crafted Skin Gloves.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_skin_pants() {
string missing = "";
if (inv_skins < 6) missing += "6 skins ";
if (inv_vines < 3) missing += "3 vines ";
if (missing == "") {
if (inv_skin_pants >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more skin pants.", true);
return;
}
simulate_crafting();
inv_skins -= 6;
inv_vines -= 3;
inv_skin_pants++;
screen_reader_speak("Crafted Skin Pants.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_skin_tunic() {
string missing = "";
if (inv_skins < 4) missing += "4 skins ";
if (inv_vines < 2) missing += "2 vines ";
if (missing == "") {
if (inv_skin_tunics >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more skin tunics.", true);
return;
}
simulate_crafting();
inv_skins -= 4;
inv_vines -= 2;
inv_skin_tunics++;
screen_reader_speak("Crafted a Skin Tunic.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_moccasins() {
string missing = "";
if (inv_skins < 2) missing += "2 skins ";
if (inv_vines < 1) missing += "1 vine ";
if (missing == "") {
if (inv_moccasins >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more moccasins.", true);
return;
}
simulate_crafting();
inv_skins -= 2;
inv_vines--;
inv_moccasins++;
screen_reader_speak("Crafted moccasins.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_skin_pouch() {
string missing = "";
if (inv_skins < 2) missing += "2 skins ";
if (inv_vines < 1) missing += "1 vine ";
if (missing == "") {
if (inv_skin_pouches >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more skin pouches.", true);
return;
}
simulate_crafting();
inv_skins -= 2;
inv_vines--;
inv_skin_pouches++;
screen_reader_speak("Crafted a Skin Pouch.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_snare() {
string missing = "";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 2) missing += "2 vines ";
if (missing == "") {
if (inv_snares >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more snares.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines -= 2;
inv_snares++;
screen_reader_speak("Crafted a Snare.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_axe() {
string missing = "";
if (inv_knives < 1) missing += "Stone Knife ";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 1) missing += "1 vine ";
if (inv_stones < 2) missing += "2 stones ";
if (missing == "") {
if (inv_axes >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more stone axes.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines--;
inv_stones -= 2;
inv_axes++;
screen_reader_speak("Crafted a Stone Axe.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_firepit() {
// Check if there's already a firepit here
if (get_firepit_at(x) != null) {
screen_reader_speak("There is already a firepit here.", true);
return;
}
string missing = "";
if (inv_stones < 9) missing += "9 stones ";
if (missing == "") {
simulate_crafting();
inv_stones -= 9;
add_world_firepit(x);
screen_reader_speak("Firepit built here.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_campfire() {
// Check if there's a firepit within 2 tiles
WorldFirepit@ firepit = get_firepit_near(x, 2);
if (firepit == null) {
screen_reader_speak("You need a firepit within 2 tiles to build a fire.", true);
return;
}
string missing = "";
if (inv_logs < 1) missing += "1 log ";
if (inv_sticks < 2) missing += "2 sticks ";
if (missing == "") {
simulate_crafting();
inv_logs--;
inv_sticks -= 2;
// Build the fire at the firepit location, not player location
add_world_fire(firepit.position);
screen_reader_speak("Fire built at firepit.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_herb_garden() {
// Can only build in base area
if (x > BASE_END) {
screen_reader_speak("Herb garden can only be built in the base area.", true);
return;
}
// Check if there's already an herb garden in the base
if (get_herb_garden_at_base() != null) {
screen_reader_speak("There is already an herb garden in the base.", true);
return;
}
string missing = "";
if (inv_stones < 9) missing += "9 stones ";
if (inv_vines < 3) missing += "3 vines ";
if (inv_logs < 2) missing += "2 logs ";
if (missing == "") {
simulate_crafting();
inv_stones -= 9;
inv_vines -= 3;
inv_logs -= 2;
add_world_herb_garden(x);
screen_reader_speak("Herb garden built. The base now heals faster.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_storage() {
if (x > BASE_END) {
screen_reader_speak("Storage must be built in the base.", true);
return;
}
if (world_storages.length() > 0) {
screen_reader_speak("Storage already built.", true);
return;
}
string missing = "";
if (inv_logs < STORAGE_LOG_COST) missing += STORAGE_LOG_COST + " logs ";
if (inv_stones < STORAGE_STONE_COST) missing += STORAGE_STONE_COST + " stones ";
if (inv_vines < STORAGE_VINE_COST) missing += STORAGE_VINE_COST + " vines ";
if (missing == "") {
simulate_crafting();
inv_logs -= STORAGE_LOG_COST;
inv_stones -= STORAGE_STONE_COST;
inv_vines -= STORAGE_VINE_COST;
add_world_storage(x);
screen_reader_speak("Storage built.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_pasture() {
if (x > BASE_END) {
screen_reader_speak("Pasture must be built in the base.", true);
return;
}
if (world_pastures.length() > 0) {
screen_reader_speak("Pasture already built.", true);
return;
}
string missing = "";
if (inv_logs < PASTURE_LOG_COST) missing += PASTURE_LOG_COST + " logs ";
if (inv_vines < PASTURE_VINE_COST) missing += PASTURE_VINE_COST + " vines ";
if (missing == "") {
simulate_crafting();
inv_logs -= PASTURE_LOG_COST;
inv_vines -= PASTURE_VINE_COST;
add_world_pasture(x);
screen_reader_speak("Pasture built.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_stable() {
if (x > BASE_END) {
screen_reader_speak("Stable must be built in the base.", true);
return;
}
if (world_stables.length() > 0) {
screen_reader_speak("Stable already built.", true);
return;
}
string missing = "";
if (inv_logs < STABLE_LOG_COST) missing += STABLE_LOG_COST + " logs ";
if (inv_stones < STABLE_STONE_COST) missing += STABLE_STONE_COST + " stones ";
if (inv_vines < STABLE_VINE_COST) missing += STABLE_VINE_COST + " vines ";
if (missing == "") {
simulate_crafting();
inv_logs -= STABLE_LOG_COST;
inv_stones -= STABLE_STONE_COST;
inv_vines -= STABLE_VINE_COST;
add_world_stable(x);
screen_reader_speak("Stable built.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_altar() {
if (x > BASE_END) {
screen_reader_speak("Altar must be built in the base.", true);
return;
}
if (world_altars.length() > 0) {
screen_reader_speak("Altar already built.", true);
return;
}
string missing = "";
if (inv_stones < ALTAR_STONE_COST) missing += ALTAR_STONE_COST + " stones ";
if (inv_sticks < ALTAR_STICK_COST) missing += ALTAR_STICK_COST + " sticks ";
if (missing == "") {
simulate_crafting();
inv_stones -= ALTAR_STONE_COST;
inv_sticks -= ALTAR_STICK_COST;
add_world_altar(x);
screen_reader_speak("Altar built.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void reinforce_barricade_with_sticks() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_sticks < BARRICADE_STICK_COST) {
screen_reader_speak("Not enough sticks.", true);
return;
}
simulate_crafting();
inv_sticks -= BARRICADE_STICK_COST;
int gained = add_barricade_health(BARRICADE_STICK_HEALTH);
screen_reader_speak("Reinforced barricade with sticks. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void reinforce_barricade_with_vines() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_vines < BARRICADE_VINE_COST) {
screen_reader_speak("Not enough vines.", true);
return;
}
simulate_crafting();
inv_vines -= BARRICADE_VINE_COST;
int gained = add_barricade_health(BARRICADE_VINE_HEALTH);
screen_reader_speak("Reinforced barricade with vines. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void reinforce_barricade_with_log() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_logs < BARRICADE_LOG_COST) {
screen_reader_speak("Not enough logs.", true);
return;
}
simulate_crafting();
inv_logs -= BARRICADE_LOG_COST;
int gained = add_barricade_health(BARRICADE_LOG_HEALTH);
screen_reader_speak("Reinforced barricade with log. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void reinforce_barricade_with_stones() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_stones < BARRICADE_STONE_COST) {
screen_reader_speak("Not enough stones.", true);
return;
}
simulate_crafting();
inv_stones -= BARRICADE_STONE_COST;
int gained = add_barricade_health(BARRICADE_STONE_HEALTH);
screen_reader_speak("Reinforced barricade with stones. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void craft_fishing_pole() {
string missing = "";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 2) missing += "2 vines ";
if (missing == "") {
if (inv_fishing_poles >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more fishing poles.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines -= 2;
inv_fishing_poles++;
screen_reader_speak("Crafted a Fishing Pole.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_rope() {
string missing = "";
if (inv_vines < 3) missing += "3 vines ";
if (missing == "") {
if (inv_ropes >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more rope.", true);
return;
}
simulate_crafting();
inv_vines -= 3;
inv_ropes++;
screen_reader_speak("Crafted rope.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_reed_basket() {
string missing = "";
if (inv_reeds < 3) missing += "3 reeds ";
if (missing == "") {
if (inv_reed_baskets >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more reed baskets.", true);
return;
}
simulate_crafting();
inv_reeds -= 3;
inv_reed_baskets++;
screen_reader_speak("Crafted a reed basket.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_clay_pot() {
string missing = "";
if (inv_clay < 3) missing += "3 clay ";
// Check for fire within 3 tiles (can hear it)
WorldFire@ fire = get_fire_within_range(x, 3);
if (fire == null) {
screen_reader_speak("You need a fire within 3 tiles to craft a clay pot.", true);
return;
}
if (missing == "") {
if (inv_clay_pots >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more clay pots.", true);
return;
}
simulate_crafting();
inv_clay -= 3;
inv_clay_pots++;
screen_reader_speak("Crafted a clay pot.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void butcher_small_game() {
string missing = "";
// Check for knife
if (inv_knives < 1) missing += "Stone Knife ";
// Check for small game
if (inv_small_game < 1) missing += "Small Game ";
// Check for fire within 3 tiles (can hear it)
WorldFire@ fire = get_fire_within_range(x, 3);
if (fire == null) {
screen_reader_speak("You need a fire within 3 tiles to butcher.", true);
return;
}
if (missing == "") {
if (inv_meat >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more meat.", true);
return;
}
if (inv_skins >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more skins.", true);
return;
}
simulate_crafting();
// Get the type of game we're butchering (first in the list)
string game_type = inv_small_game_types[0];
inv_small_game_types.remove_at(0);
inv_small_game--;
inv_meat++;
inv_skins++;
screen_reader_speak("Butchered " + game_type + ". Got 1 meat and 1 skin.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
+92 -27
View File
@@ -132,10 +132,28 @@ class Tree {
}
Tree@[] trees;
bool tree_too_close(int pos) {
for (uint i = 0; i < trees.length(); i++) {
int distance = trees[i].position - pos;
if (distance < 0) distance = -distance;
if (distance <= 5) {
return true;
}
}
return false;
}
void spawn_trees(int grass_start, int grass_end) {
int pos = random(grass_start, grass_end);
Tree@ t = Tree(pos);
trees.insert_last(t);
int attempts = 10;
for (int i = 0; i < attempts; i++) {
int pos = random(grass_start, grass_end);
if (tree_too_close(pos)) {
continue;
}
Tree@ t = Tree(pos);
trees.insert_last(t);
return;
}
}
void update_environment() {
@@ -225,11 +243,11 @@ void perform_search(int current_x)
WorldSnare@ s = get_snare_at(check_x);
if (s != null) {
if (s.has_catch) {
if (inv_small_game >= MAX_ITEM_STACK) {
if (inv_small_game >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more small game.", true);
return;
}
if (inv_snares >= MAX_ITEM_STACK) {
if (inv_snares >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more snares.", true);
return;
}
@@ -238,7 +256,7 @@ void perform_search(int current_x)
inv_snares++; // Recover snare
screen_reader_speak("Collected " + s.catch_type + " and snare.", true);
} else {
if (inv_snares >= MAX_ITEM_STACK) {
if (inv_snares >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more snares.", true);
return;
}
@@ -251,7 +269,12 @@ void perform_search(int current_x)
}
}
// Stream banks - Clay (within stream sound range, but not in water)
if (random(1, 100) <= 10) {
screen_reader_speak("Found nothing.", true);
return;
}
// Stream banks - Clay or reeds (within 3 tiles of stream, but not in water)
bool near_stream_bank = false;
for (uint i = 0; i < world_streams.length(); i++) {
if (world_streams[i].contains_position(current_x)) {
@@ -267,10 +290,33 @@ void perform_search(int current_x)
}
if (near_stream_bank) {
if (inv_clay < MAX_ITEM_STACK) {
bool found_reed = random(1, 100) <= 30;
if (found_reed) {
if (inv_reeds < get_personal_stack_limit()) {
inv_reeds++;
p.play_stationary("sounds/items/stick.ogg", false);
screen_reader_speak("Found a reed.", true);
return;
}
} else {
if (inv_clay < get_personal_stack_limit()) {
inv_clay++;
p.play_stationary("sounds/items/clay.ogg", false);
screen_reader_speak("Found clay.", true);
return;
}
}
if (!found_reed && inv_reeds < get_personal_stack_limit()) {
inv_reeds++;
p.play_stationary("sounds/items/stick.ogg", false);
screen_reader_speak("Found a reed.", true);
} else if (found_reed && inv_clay < get_personal_stack_limit()) {
inv_clay++;
p.play_stationary("sounds/items/clay.ogg", false);
screen_reader_speak("Found clay.", true);
} else if (found_reed) {
screen_reader_speak("You can't carry any more reeds.", true);
} else {
screen_reader_speak("You can't carry any more clay.", true);
}
@@ -309,28 +355,47 @@ void perform_search(int current_x)
if(nearest.sticks > 0 || nearest.vines > 0)
{
bool find_stick = (nearest.vines <= 0) || (nearest.sticks > 0 && random(0, 1) == 0);
if(find_stick)
{
if (inv_sticks >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more sticks.", true);
return;
bool took_item = false;
if (find_stick) {
if (nearest.sticks > 0 && inv_sticks < get_personal_stack_limit()) {
nearest.sticks--;
inv_sticks++;
p.play_stationary("sounds/items/stick.ogg", false);
screen_reader_speak("Found a stick.", true);
took_item = true;
} else if (nearest.vines > 0 && inv_vines < get_personal_stack_limit()) {
nearest.vines--;
inv_vines++;
p.play_stationary("sounds/items/vine.ogg", false);
screen_reader_speak("Found a vine.", true);
took_item = true;
}
} else {
if (nearest.vines > 0 && inv_vines < get_personal_stack_limit()) {
nearest.vines--;
inv_vines++;
p.play_stationary("sounds/items/vine.ogg", false);
screen_reader_speak("Found a vine.", true);
took_item = true;
} else if (nearest.sticks > 0 && inv_sticks < get_personal_stack_limit()) {
nearest.sticks--;
inv_sticks++;
p.play_stationary("sounds/items/stick.ogg", false);
screen_reader_speak("Found a stick.", true);
took_item = true;
}
nearest.sticks--;
inv_sticks++;
p.play_stationary("sounds/items/stick.ogg", false);
screen_reader_speak("Found a stick.", true);
}
else
{
if (inv_vines >= MAX_ITEM_STACK) {
if (!took_item) {
if (nearest.sticks > 0 && nearest.vines > 0) {
screen_reader_speak("You can't carry any more sticks or vines.", true);
} else if (nearest.sticks > 0) {
screen_reader_speak("You can't carry any more sticks.", true);
} else {
screen_reader_speak("You can't carry any more vines.", true);
return;
}
nearest.vines--;
inv_vines++;
p.play_stationary("sounds/items/vine.ogg", false);
screen_reader_speak("Found a vine.", true);
return;
}
if(nearest.sticks == 0 && nearest.vines == 0) {
@@ -349,7 +414,7 @@ void perform_search(int current_x)
// Gravel Area - Stones (20-34)
if (current_x >= 20 && current_x <= 34)
{
if (inv_stones < MAX_ITEM_STACK)
if (inv_stones < get_personal_stack_limit())
{
inv_stones++;
p.play_stationary("sounds/items/stone.ogg", false);
+4 -889
View File
@@ -1,889 +1,4 @@
// Inventory
int inv_stones = 0;
int inv_sticks = 0;
int inv_vines = 0;
int inv_logs = 0;
int inv_clay = 0;
int inv_small_game = 0; // Total small game caught (any type)
string[] inv_small_game_types; // Array to track what types of small game we have
int inv_meat = 0;
int inv_skins = 0;
int inv_spears = 0;
int inv_snares = 0;
int inv_axes = 0;
int inv_knives = 0;
int inv_fishing_poles = 0;
int inv_slings = 0;
bool spear_equipped = false;
bool axe_equipped = false;
bool sling_equipped = false;
int add_to_stack(int current, int amount) {
if (amount <= 0) return 0;
int space = MAX_ITEM_STACK - current;
if (space <= 0) return 0;
if (amount > space) return space;
return amount;
}
void check_crafting_menu(int x, int base_end_tile) {
if (x <= base_end_tile) {
if (key_pressed(KEY_C)) {
run_crafting_menu();
}
}
}
void check_inventory_keys(int x) {
if (key_pressed(KEY_I)) {
run_inventory_menu();
}
}
void check_action_menu(int x) {
if (key_pressed(KEY_A)) {
run_action_menu(x);
}
}
void menu_background_tick() {
update_time();
update_environment();
update_snares();
update_fires();
update_zombies();
// Fire damage check (only if not jumping)
WorldFire@ fire_on_tile = get_fire_at(x);
if (fire_on_tile != null && !jumping && fire_damage_timer.elapsed > 1000) {
player_health--;
fire_damage_timer.restart();
screen_reader_speak("Burning! " + player_health + " health remaining.", true);
}
// Healing in base area
if (x <= BASE_END && player_health < max_health) {
WorldHerbGarden@ herb_garden = get_herb_garden_at_base();
int heal_interval = (herb_garden != null) ? 30000 : 150000; // 30 seconds with garden, 2.5 minutes without
if (healing_timer.elapsed > heal_interval) {
player_health++;
healing_timer.restart();
screen_reader_speak(player_health + " health.", true);
}
}
// Death check
if (player_health <= 0) {
screen_reader_speak("You have died.", true);
wait(2000);
exit();
}
}
void show_inventory() {
string info = "Inventory: ";
info += inv_sticks + " sticks, ";
info += inv_vines + " vines, ";
info += inv_stones + " stones, ";
info += inv_logs + " logs, ";
info += inv_clay + " clay, ";
info += inv_small_game + " small game, ";
info += inv_meat + " meat, ";
info += inv_skins + " skins. ";
info += "Tools: " + inv_spears + " spears, " + inv_slings + " slings, " + inv_axes + " axes, " + inv_snares + " snares, " + inv_knives + " knives, " + inv_fishing_poles + " fishing poles.";
screen_reader_speak(info, true);
}
void run_inventory_menu() {
screen_reader_speak("Inventory menu.", true);
int selection = 0;
string[] options = {
"Sticks: " + inv_sticks,
"Vines: " + inv_vines,
"Stones: " + inv_stones,
"Logs: " + inv_logs,
"Clay: " + inv_clay,
"Small Game: " + inv_small_game,
"Meat: " + inv_meat,
"Skins: " + inv_skins,
"Spears: " + inv_spears,
"Slings: " + inv_slings,
"Axes: " + inv_axes,
"Snares: " + inv_snares,
"Knives: " + inv_knives,
"Fishing Poles: " + inv_fishing_poles
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
}
}
void try_place_snare(int x) {
if (inv_snares > 0) {
// Prevent placing if one already exists here
if (get_snare_at(x) != null) {
screen_reader_speak("There is already a snare here.", true);
return;
}
inv_snares--;
add_world_snare(x);
screen_reader_speak("Snare set.", true);
} else {
screen_reader_speak("No snares to place.", true);
}
}
void try_feed_fire_stick(WorldFire@ fire) {
if (inv_sticks > 0 && fire != null) {
inv_sticks--;
fire.add_fuel(300000); // 5 minutes
screen_reader_speak("You dump an arm load of sticks into the fire.", true);
p.play_stationary("sounds/actions/fed_fire.ogg", false);
}
}
void try_feed_fire_vine(WorldFire@ fire) {
if (inv_vines > 0 && fire != null) {
inv_vines--;
fire.add_fuel(60000); // 1 minute
screen_reader_speak("You toss a fiew vines and leaves into the fire.", true);
p.play_stationary("sounds/actions/fed_fire.ogg", false);
}
}
void try_feed_fire_log(WorldFire@ fire) {
if (inv_logs > 0 && fire != null) {
inv_logs--;
fire.add_fuel(720000); // 12 minutes
screen_reader_speak("You heave a log into the fire.", true);
p.play_stationary("sounds/actions/fed_fire.ogg", false);
}
}
void check_equipment_menu() {
if (key_pressed(KEY_E)) {
// Check if player has any equipment
if (inv_spears == 0 && inv_axes == 0 && inv_slings == 0) {
screen_reader_speak("Nothing to equip.", true);
} else {
run_equipment_menu();
}
}
}
void run_action_menu(int x) {
screen_reader_speak("Action menu.", true);
int selection = 0;
string[] options;
int[] action_types; // Track what action each option corresponds to
// Check if fire is nearby
WorldFire@ nearby_fire = get_fire_near(x);
bool can_feed_fire = nearby_fire != null;
// Build menu options dynamically
options.insert_last("Place Snare");
action_types.insert_last(0);
if (can_feed_fire) {
if (inv_sticks > 0) {
options.insert_last("Feed fire with stick");
action_types.insert_last(1);
}
if (inv_vines > 0) {
options.insert_last("Feed fire with vine");
action_types.insert_last(2);
}
if (inv_logs > 0) {
options.insert_last("Feed fire with log");
action_types.insert_last(3);
}
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
int action = action_types[selection];
if (action == 0) {
try_place_snare(x);
} else if (action == 1) {
try_feed_fire_stick(nearby_fire);
} else if (action == 2) {
try_feed_fire_vine(nearby_fire);
} else if (action == 3) {
try_feed_fire_log(nearby_fire);
}
break;
}
}
}
void run_crafting_menu() {
screen_reader_speak("Crafting menu.", true);
int selection = 0;
string[] categories = {"Weapons", "Tools", "Buildings", "Barricade"};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= categories.length()) selection = 0;
screen_reader_speak(categories[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = categories.length() - 1;
screen_reader_speak(categories[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) run_weapons_menu();
else if (selection == 1) run_tools_menu();
else if (selection == 2) run_buildings_menu();
else if (selection == 3) run_barricade_menu();
break;
}
}
}
void run_weapons_menu() {
screen_reader_speak("Weapons.", true);
int selection = 0;
string[] options = {
"Spear (1 Stick, 1 Vine, 1 Stone) [Requires Knife]",
"Sling (1 Skin, 2 Vines)"
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_spear();
else if (selection == 1) craft_sling();
break;
}
}
}
void run_tools_menu() {
screen_reader_speak("Tools.", true);
int selection = 0;
string[] options = {
"Stone Knife (2 Stones)",
"Snare (1 Stick, 2 Vines)",
"Stone Axe (1 Stick, 1 Vine, 2 Stones) [Requires Knife]",
"Fishing Pole (1 Stick, 2 Vines)",
"Butcher Small Game (1 Small Game) [Requires Knife and Fire nearby]"
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_knife();
else if (selection == 1) craft_snare();
else if (selection == 2) craft_axe();
else if (selection == 3) craft_fishing_pole();
else if (selection == 4) butcher_small_game();
break;
}
}
}
void run_buildings_menu() {
screen_reader_speak("Buildings.", true);
int selection = 0;
string[] options = {
"Firepit (9 Stones)",
"Fire (2 Sticks, 1 Log) [Requires Firepit]",
"Herb Garden (9 Stones, 3 Vines, 2 Logs) [Base Only]"
};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_firepit();
else if (selection == 1) craft_campfire();
else if (selection == 2) craft_herb_garden();
break;
}
}
}
void run_barricade_menu() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
screen_reader_speak("Barricade.", true);
int selection = 0;
string[] options;
int[] action_types; // 0 = sticks, 1 = vines, 2 = log, 3 = stones
if (inv_sticks >= BARRICADE_STICK_COST) {
options.insert_last("Reinforce with sticks (" + BARRICADE_STICK_COST + " sticks, +" + BARRICADE_STICK_HEALTH + " health)");
action_types.insert_last(0);
}
if (inv_vines >= BARRICADE_VINE_COST) {
options.insert_last("Reinforce with vines (" + BARRICADE_VINE_COST + " vines, +" + BARRICADE_VINE_HEALTH + " health)");
action_types.insert_last(1);
}
if (inv_logs >= BARRICADE_LOG_COST) {
options.insert_last("Reinforce with log (" + BARRICADE_LOG_COST + " log, +" + BARRICADE_LOG_HEALTH + " health)");
action_types.insert_last(2);
}
if (inv_stones >= BARRICADE_STONE_COST) {
options.insert_last("Reinforce with stones (" + BARRICADE_STONE_COST + " stones, +" + BARRICADE_STONE_HEALTH + " health)");
action_types.insert_last(3);
}
if (options.length() == 0) {
screen_reader_speak("No materials to reinforce the barricade.", true);
return;
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
int action = action_types[selection];
if (action == 0) reinforce_barricade_with_sticks();
else if (action == 1) reinforce_barricade_with_vines();
else if (action == 2) reinforce_barricade_with_log();
else if (action == 3) reinforce_barricade_with_stones();
break;
}
}
}
void run_equipment_menu() {
screen_reader_speak("Equipment menu.", true);
int selection = 0;
string[] options;
int[] equipment_types; // 0 = spear, 1 = axe, 2 = sling
// Build menu dynamically based on what player has
if (inv_spears > 0) {
string status = spear_equipped ? " (equipped)" : "";
options.insert_last("Spear" + status);
equipment_types.insert_last(0);
}
if (inv_slings > 0) {
string status = sling_equipped ? " (equipped)" : "";
options.insert_last("Sling" + status);
equipment_types.insert_last(2);
}
if (inv_axes > 0) {
string status = axe_equipped ? " (equipped)" : "";
options.insert_last("Stone Axe" + status);
equipment_types.insert_last(1);
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
int equip_type = equipment_types[selection];
if (equip_type == 0) {
// Spear
if (!spear_equipped) {
spear_equipped = true;
axe_equipped = false;
sling_equipped = false;
screen_reader_speak("Spear equipped.", true);
} else {
spear_equipped = false;
screen_reader_speak("Spear unequipped.", true);
}
} else if (equip_type == 1) {
// Axe
if (!axe_equipped) {
axe_equipped = true;
spear_equipped = false;
sling_equipped = false;
screen_reader_speak("Stone Axe equipped.", true);
} else {
axe_equipped = false;
screen_reader_speak("Stone Axe unequipped.", true);
}
} else if (equip_type == 2) {
// Sling
if (!sling_equipped) {
sling_equipped = true;
spear_equipped = false;
axe_equipped = false;
screen_reader_speak("Sling equipped.", true);
} else {
sling_equipped = false;
screen_reader_speak("Sling unequipped.", true);
}
}
break;
}
}
}
void simulate_crafting() {
screen_reader_speak("Crafting...", true);
timer t;
int duration = 4000;
int next_sound = 0;
while(t.elapsed < duration) {
if(t.elapsed > next_sound) {
float pitch = random(85, 115);
p.play_stationary_extended("sounds/crafting.ogg", false, 0, 0, 0, pitch);
next_sound = t.elapsed + 800;
}
wait(5);
menu_background_tick();
}
p.play_stationary("sounds/crafting_complete.ogg", false);
}
void craft_knife() {
string missing = "";
if (inv_stones < 2) missing += "2 stones ";
if (missing == "") {
if (inv_knives >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more stone knives.", true);
return;
}
simulate_crafting();
inv_stones -= 2;
inv_knives++;
screen_reader_speak("Crafted a Stone Knife.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_spear() {
string missing = "";
if (inv_knives < 1) missing += "Stone Knife ";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 1) missing += "1 vine ";
if (inv_stones < 1) missing += "1 stone ";
if (missing == "") {
if (inv_spears >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more spears.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines--;
inv_stones--;
inv_spears++;
screen_reader_speak("Crafted a Spear.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_sling() {
string missing = "";
if (inv_skins < 1) missing += "1 skin ";
if (inv_vines < 2) missing += "2 vines ";
if (missing == "") {
if (inv_slings >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more slings.", true);
return;
}
simulate_crafting();
inv_skins--;
inv_vines -= 2;
inv_slings++;
screen_reader_speak("Crafted a Sling.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_snare() {
string missing = "";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 2) missing += "2 vines ";
if (missing == "") {
if (inv_snares >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more snares.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines -= 2;
inv_snares++;
screen_reader_speak("Crafted a Snare.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_axe() {
string missing = "";
if (inv_knives < 1) missing += "Stone Knife ";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 1) missing += "1 vine ";
if (inv_stones < 2) missing += "2 stones ";
if (missing == "") {
if (inv_axes >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more stone axes.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines--;
inv_stones -= 2;
inv_axes++;
screen_reader_speak("Crafted a Stone Axe.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_firepit() {
// Check if there's already a firepit here
if (get_firepit_at(x) != null) {
screen_reader_speak("There is already a firepit here.", true);
return;
}
string missing = "";
if (inv_stones < 9) missing += "9 stones ";
if (missing == "") {
simulate_crafting();
inv_stones -= 9;
add_world_firepit(x);
screen_reader_speak("Firepit built here.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_campfire() {
// Check if there's a firepit within 2 tiles
WorldFirepit@ firepit = get_firepit_near(x, 2);
if (firepit == null) {
screen_reader_speak("You need a firepit within 2 tiles to build a fire.", true);
return;
}
string missing = "";
if (inv_logs < 1) missing += "1 log ";
if (inv_sticks < 2) missing += "2 sticks ";
if (missing == "") {
simulate_crafting();
inv_logs--;
inv_sticks -= 2;
// Build the fire at the firepit location, not player location
add_world_fire(firepit.position);
screen_reader_speak("Fire built at firepit.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void craft_herb_garden() {
// Can only build in base area
if (x > BASE_END) {
screen_reader_speak("Herb garden can only be built in the base area.", true);
return;
}
// Check if there's already an herb garden in the base
if (get_herb_garden_at_base() != null) {
screen_reader_speak("There is already an herb garden in the base.", true);
return;
}
string missing = "";
if (inv_stones < 9) missing += "9 stones ";
if (inv_vines < 3) missing += "3 vines ";
if (inv_logs < 2) missing += "2 logs ";
if (missing == "") {
simulate_crafting();
inv_stones -= 9;
inv_vines -= 3;
inv_logs -= 2;
add_world_herb_garden(x);
screen_reader_speak("Herb garden built. The base now heals faster.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void reinforce_barricade_with_sticks() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_sticks < BARRICADE_STICK_COST) {
screen_reader_speak("Not enough sticks.", true);
return;
}
simulate_crafting();
inv_sticks -= BARRICADE_STICK_COST;
int gained = add_barricade_health(BARRICADE_STICK_HEALTH);
screen_reader_speak("Reinforced barricade with sticks. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void reinforce_barricade_with_vines() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_vines < BARRICADE_VINE_COST) {
screen_reader_speak("Not enough vines.", true);
return;
}
simulate_crafting();
inv_vines -= BARRICADE_VINE_COST;
int gained = add_barricade_health(BARRICADE_VINE_HEALTH);
screen_reader_speak("Reinforced barricade with vines. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void reinforce_barricade_with_log() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_logs < BARRICADE_LOG_COST) {
screen_reader_speak("Not enough logs.", true);
return;
}
simulate_crafting();
inv_logs -= BARRICADE_LOG_COST;
int gained = add_barricade_health(BARRICADE_LOG_HEALTH);
screen_reader_speak("Reinforced barricade with log. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void reinforce_barricade_with_stones() {
if (barricade_health >= BARRICADE_MAX_HEALTH) {
screen_reader_speak("Barricade is already at full health.", true);
return;
}
if (inv_stones < BARRICADE_STONE_COST) {
screen_reader_speak("Not enough stones.", true);
return;
}
simulate_crafting();
inv_stones -= BARRICADE_STONE_COST;
int gained = add_barricade_health(BARRICADE_STONE_HEALTH);
screen_reader_speak("Reinforced barricade with stones. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
}
void craft_fishing_pole() {
string missing = "";
if (inv_sticks < 1) missing += "1 stick ";
if (inv_vines < 2) missing += "2 vines ";
if (missing == "") {
if (inv_fishing_poles >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more fishing poles.", true);
return;
}
simulate_crafting();
inv_sticks--;
inv_vines -= 2;
inv_fishing_poles++;
screen_reader_speak("Crafted a Fishing Pole.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
void butcher_small_game() {
string missing = "";
// Check for knife
if (inv_knives < 1) missing += "Stone Knife ";
// Check for small game
if (inv_small_game < 1) missing += "Small Game ";
// Check for fire within 3 tiles (can hear it)
WorldFire@ fire = get_fire_within_range(x, 3);
if (fire == null) {
screen_reader_speak("You need a fire within 3 tiles to butcher.", true);
return;
}
if (missing == "") {
if (inv_meat >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more meat.", true);
return;
}
if (inv_skins >= MAX_ITEM_STACK) {
screen_reader_speak("You can't carry any more skins.", true);
return;
}
simulate_crafting();
// Get the type of game we're butchering (first in the list)
string game_type = inv_small_game_types[0];
inv_small_game_types.remove_at(0);
inv_small_game--;
inv_meat++;
inv_skins++;
screen_reader_speak("Butchered " + game_type + ". Got 1 meat and 1 skin.", true);
} else {
screen_reader_speak("Missing: " + missing, true);
}
}
// Inventory module includes
#include "src/inventory_items.nvgt"
#include "src/inventory_menus.nvgt"
#include "src/crafting.nvgt"
+439
View File
@@ -0,0 +1,439 @@
// Inventory items and equipment
int inv_stones = 0;
int inv_sticks = 0;
int inv_vines = 0;
int inv_reeds = 0;
int inv_logs = 0;
int inv_clay = 0;
int inv_small_game = 0; // Total small game caught (any type)
string[] inv_small_game_types; // Array to track what types of small game we have
int inv_meat = 0;
int inv_skins = 0;
int inv_spears = 0;
int inv_snares = 0;
int inv_axes = 0;
int inv_knives = 0;
int inv_fishing_poles = 0;
int inv_slings = 0;
int inv_ropes = 0;
int inv_reed_baskets = 0;
int inv_clay_pots = 0;
int inv_skin_hats = 0;
int inv_skin_gloves = 0;
int inv_skin_pants = 0;
int inv_skin_tunics = 0;
int inv_moccasins = 0;
int inv_skin_pouches = 0;
int storage_stones = 0;
int storage_sticks = 0;
int storage_vines = 0;
int storage_reeds = 0;
int storage_logs = 0;
int storage_clay = 0;
int storage_small_game = 0;
string[] storage_small_game_types;
int storage_meat = 0;
int storage_skins = 0;
int storage_spears = 0;
int storage_snares = 0;
int storage_axes = 0;
int storage_knives = 0;
int storage_fishing_poles = 0;
int storage_slings = 0;
int storage_ropes = 0;
int storage_reed_baskets = 0;
int storage_clay_pots = 0;
int storage_skin_hats = 0;
int storage_skin_gloves = 0;
int storage_skin_pants = 0;
int storage_skin_tunics = 0;
int storage_moccasins = 0;
int storage_skin_pouches = 0;
bool spear_equipped = false;
bool axe_equipped = false;
bool sling_equipped = false;
int[] quick_slots;
const int EQUIP_NONE = -1;
const int EQUIP_SPEAR = 0;
const int EQUIP_AXE = 1;
const int EQUIP_SLING = 2;
const int EQUIP_HAT = 3;
const int EQUIP_GLOVES = 4;
const int EQUIP_PANTS = 5;
const int EQUIP_TUNIC = 6;
const int EQUIP_MOCCASINS = 7;
const int EQUIP_POUCH = 8;
const int ITEM_STICKS = 0;
const int ITEM_VINES = 1;
const int ITEM_REEDS = 2;
const int ITEM_STONES = 3;
const int ITEM_LOGS = 4;
const int ITEM_CLAY = 5;
const int ITEM_SMALL_GAME = 6;
const int ITEM_MEAT = 7;
const int ITEM_SKINS = 8;
const int ITEM_SPEARS = 9;
const int ITEM_SLINGS = 10;
const int ITEM_AXES = 11;
const int ITEM_SNARES = 12;
const int ITEM_KNIVES = 13;
const int ITEM_FISHING_POLES = 14;
const int ITEM_SKIN_HATS = 15;
const int ITEM_SKIN_GLOVES = 16;
const int ITEM_SKIN_PANTS = 17;
const int ITEM_SKIN_TUNICS = 18;
const int ITEM_MOCCASINS = 19;
const int ITEM_SKIN_POUCHES = 20;
const int ITEM_ROPES = 21;
const int ITEM_REED_BASKETS = 22;
const int ITEM_CLAY_POTS = 23;
const int HAT_MAX_HEALTH_BONUS = 1;
const int GLOVES_MAX_HEALTH_BONUS = 1;
const int PANTS_MAX_HEALTH_BONUS = 3;
const int TUNIC_MAX_HEALTH_BONUS = 4;
const int MOCCASINS_MAX_HEALTH_BONUS = 2;
int equipped_head = EQUIP_NONE;
int equipped_torso = EQUIP_NONE;
int equipped_arms = EQUIP_NONE;
int equipped_hands = EQUIP_NONE;
int equipped_legs = EQUIP_NONE;
int equipped_feet = EQUIP_NONE;
void reset_quick_slots() {
quick_slots.resize(10);
for (uint i = 0; i < quick_slots.length(); i++) {
quick_slots[i] = -1;
}
}
int get_personal_stack_limit() {
int limit = MAX_ITEM_STACK;
if (equipped_arms == EQUIP_POUCH) {
limit += POUCH_STACK_BONUS;
}
return limit;
}
string get_equipment_name(int equip_type) {
if (equip_type == EQUIP_SPEAR) return "Spear";
if (equip_type == EQUIP_AXE) return "Stone Axe";
if (equip_type == EQUIP_SLING) return "Sling";
if (equip_type == EQUIP_HAT) return "Skin Hat";
if (equip_type == EQUIP_GLOVES) return "Skin Gloves";
if (equip_type == EQUIP_PANTS) return "Skin Pants";
if (equip_type == EQUIP_TUNIC) return "Skin Tunic";
if (equip_type == EQUIP_MOCCASINS) return "Moccasins";
if (equip_type == EQUIP_POUCH) return "Skin Pouch";
return "Unknown";
}
bool equipment_available(int equip_type) {
if (equip_type == EQUIP_SPEAR) return inv_spears > 0;
if (equip_type == EQUIP_AXE) return inv_axes > 0;
if (equip_type == EQUIP_SLING) return inv_slings > 0;
if (equip_type == EQUIP_HAT) return inv_skin_hats > 0;
if (equip_type == EQUIP_GLOVES) return inv_skin_gloves > 0;
if (equip_type == EQUIP_PANTS) return inv_skin_pants > 0;
if (equip_type == EQUIP_TUNIC) return inv_skin_tunics > 0;
if (equip_type == EQUIP_MOCCASINS) return inv_moccasins > 0;
if (equip_type == EQUIP_POUCH) return inv_skin_pouches > 0;
return false;
}
void equip_equipment_type(int equip_type) {
if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING) {
spear_equipped = (equip_type == EQUIP_SPEAR);
axe_equipped = (equip_type == EQUIP_AXE);
sling_equipped = (equip_type == EQUIP_SLING);
return;
}
if (equip_type == EQUIP_HAT) equipped_head = EQUIP_HAT;
else if (equip_type == EQUIP_TUNIC) equipped_torso = EQUIP_TUNIC;
else if (equip_type == EQUIP_GLOVES) equipped_hands = EQUIP_GLOVES;
else if (equip_type == EQUIP_PANTS) equipped_legs = EQUIP_PANTS;
else if (equip_type == EQUIP_MOCCASINS) equipped_feet = EQUIP_MOCCASINS;
else if (equip_type == EQUIP_POUCH) equipped_arms = EQUIP_POUCH;
}
bool equipment_is_equipped(int equip_type) {
if (equip_type == EQUIP_SPEAR) return spear_equipped;
if (equip_type == EQUIP_AXE) return axe_equipped;
if (equip_type == EQUIP_SLING) return sling_equipped;
if (equip_type == EQUIP_HAT) return equipped_head == EQUIP_HAT;
if (equip_type == EQUIP_TUNIC) return equipped_torso == EQUIP_TUNIC;
if (equip_type == EQUIP_GLOVES) return equipped_hands == EQUIP_GLOVES;
if (equip_type == EQUIP_PANTS) return equipped_legs == EQUIP_PANTS;
if (equip_type == EQUIP_MOCCASINS) return equipped_feet == EQUIP_MOCCASINS;
if (equip_type == EQUIP_POUCH) return equipped_arms == EQUIP_POUCH;
return false;
}
void unequip_equipment_type(int equip_type) {
if (equip_type == EQUIP_SPEAR) {
spear_equipped = false;
} else if (equip_type == EQUIP_AXE) {
axe_equipped = false;
} else if (equip_type == EQUIP_SLING) {
sling_equipped = false;
} else if (equip_type == EQUIP_HAT && equipped_head == EQUIP_HAT) {
equipped_head = EQUIP_NONE;
} else if (equip_type == EQUIP_TUNIC && equipped_torso == EQUIP_TUNIC) {
equipped_torso = EQUIP_NONE;
} else if (equip_type == EQUIP_GLOVES && equipped_hands == EQUIP_GLOVES) {
equipped_hands = EQUIP_NONE;
} else if (equip_type == EQUIP_PANTS && equipped_legs == EQUIP_PANTS) {
equipped_legs = EQUIP_NONE;
} else if (equip_type == EQUIP_MOCCASINS && equipped_feet == EQUIP_MOCCASINS) {
equipped_feet = EQUIP_NONE;
} else if (equip_type == EQUIP_POUCH && equipped_arms == EQUIP_POUCH) {
equipped_arms = EQUIP_NONE;
}
}
void update_max_health_from_equipment() {
int bonus = 0;
if (equipped_head == EQUIP_HAT) bonus += HAT_MAX_HEALTH_BONUS;
if (equipped_hands == EQUIP_GLOVES) bonus += GLOVES_MAX_HEALTH_BONUS;
if (equipped_legs == EQUIP_PANTS) bonus += PANTS_MAX_HEALTH_BONUS;
if (equipped_torso == EQUIP_TUNIC) bonus += TUNIC_MAX_HEALTH_BONUS;
if (equipped_feet == EQUIP_MOCCASINS) bonus += MOCCASINS_MAX_HEALTH_BONUS;
max_health = base_max_health + bonus;
if (player_health > max_health) {
player_health = max_health;
}
int base_speed = (equipped_feet == EQUIP_MOCCASINS) ? MOCCASINS_WALK_SPEED : BASE_WALK_SPEED;
walk_speed = base_speed;
if (blessing_speed_active && BLESSING_WALK_SPEED < walk_speed) {
walk_speed = BLESSING_WALK_SPEED;
}
}
int get_quick_slot_key() {
if (key_pressed(KEY_1)) return 1;
if (key_pressed(KEY_2)) return 2;
if (key_pressed(KEY_3)) return 3;
if (key_pressed(KEY_4)) return 4;
if (key_pressed(KEY_5)) return 5;
if (key_pressed(KEY_6)) return 6;
if (key_pressed(KEY_7)) return 7;
if (key_pressed(KEY_8)) return 8;
if (key_pressed(KEY_9)) return 9;
if (key_pressed(KEY_0)) return 0;
return -1;
}
void activate_quick_slot(int slot_index) {
if (slot_index < 0 || slot_index >= int(quick_slots.length())) {
return;
}
int equip_type = quick_slots[slot_index];
if (equip_type < 0) {
screen_reader_speak("No item bound to slot " + slot_index + ".", true);
return;
}
if (!equipment_available(equip_type)) {
screen_reader_speak("Item not available.", true);
return;
}
equip_equipment_type(equip_type);
update_max_health_from_equipment();
screen_reader_speak(get_equipment_name(equip_type) + " equipped.", true);
}
void check_quick_slot_keys() {
int slot_index = get_quick_slot_key();
if (slot_index != -1) {
activate_quick_slot(slot_index);
}
}
int add_to_stack(int current, int amount) {
if (amount <= 0) return 0;
int space = get_personal_stack_limit() - current;
if (space <= 0) return 0;
if (amount > space) return space;
return amount;
}
int get_personal_count(int item_type) {
if (item_type == ITEM_STICKS) return inv_sticks;
if (item_type == ITEM_VINES) return inv_vines;
if (item_type == ITEM_REEDS) return inv_reeds;
if (item_type == ITEM_STONES) return inv_stones;
if (item_type == ITEM_LOGS) return inv_logs;
if (item_type == ITEM_CLAY) return inv_clay;
if (item_type == ITEM_SMALL_GAME) return inv_small_game;
if (item_type == ITEM_MEAT) return inv_meat;
if (item_type == ITEM_SKINS) return inv_skins;
if (item_type == ITEM_SPEARS) return inv_spears;
if (item_type == ITEM_SLINGS) return inv_slings;
if (item_type == ITEM_AXES) return inv_axes;
if (item_type == ITEM_SNARES) return inv_snares;
if (item_type == ITEM_KNIVES) return inv_knives;
if (item_type == ITEM_FISHING_POLES) return inv_fishing_poles;
if (item_type == ITEM_ROPES) return inv_ropes;
if (item_type == ITEM_REED_BASKETS) return inv_reed_baskets;
if (item_type == ITEM_CLAY_POTS) return inv_clay_pots;
if (item_type == ITEM_SKIN_HATS) return inv_skin_hats;
if (item_type == ITEM_SKIN_GLOVES) return inv_skin_gloves;
if (item_type == ITEM_SKIN_PANTS) return inv_skin_pants;
if (item_type == ITEM_SKIN_TUNICS) return inv_skin_tunics;
if (item_type == ITEM_MOCCASINS) return inv_moccasins;
if (item_type == ITEM_SKIN_POUCHES) return inv_skin_pouches;
return 0;
}
int get_storage_count(int item_type) {
if (item_type == ITEM_STICKS) return storage_sticks;
if (item_type == ITEM_VINES) return storage_vines;
if (item_type == ITEM_REEDS) return storage_reeds;
if (item_type == ITEM_STONES) return storage_stones;
if (item_type == ITEM_LOGS) return storage_logs;
if (item_type == ITEM_CLAY) return storage_clay;
if (item_type == ITEM_SMALL_GAME) return storage_small_game;
if (item_type == ITEM_MEAT) return storage_meat;
if (item_type == ITEM_SKINS) return storage_skins;
if (item_type == ITEM_SPEARS) return storage_spears;
if (item_type == ITEM_SLINGS) return storage_slings;
if (item_type == ITEM_AXES) return storage_axes;
if (item_type == ITEM_SNARES) return storage_snares;
if (item_type == ITEM_KNIVES) return storage_knives;
if (item_type == ITEM_FISHING_POLES) return storage_fishing_poles;
if (item_type == ITEM_ROPES) return storage_ropes;
if (item_type == ITEM_REED_BASKETS) return storage_reed_baskets;
if (item_type == ITEM_CLAY_POTS) return storage_clay_pots;
if (item_type == ITEM_SKIN_HATS) return storage_skin_hats;
if (item_type == ITEM_SKIN_GLOVES) return storage_skin_gloves;
if (item_type == ITEM_SKIN_PANTS) return storage_skin_pants;
if (item_type == ITEM_SKIN_TUNICS) return storage_skin_tunics;
if (item_type == ITEM_MOCCASINS) return storage_moccasins;
if (item_type == ITEM_SKIN_POUCHES) return storage_skin_pouches;
return 0;
}
string get_item_label(int item_type) {
if (item_type == ITEM_STICKS) return "sticks";
if (item_type == ITEM_VINES) return "vines";
if (item_type == ITEM_REEDS) return "reeds";
if (item_type == ITEM_STONES) return "stones";
if (item_type == ITEM_LOGS) return "logs";
if (item_type == ITEM_CLAY) return "clay";
if (item_type == ITEM_SMALL_GAME) return "small game";
if (item_type == ITEM_MEAT) return "meat";
if (item_type == ITEM_SKINS) return "skins";
if (item_type == ITEM_SPEARS) return "spears";
if (item_type == ITEM_SLINGS) return "slings";
if (item_type == ITEM_AXES) return "axes";
if (item_type == ITEM_SNARES) return "snares";
if (item_type == ITEM_KNIVES) return "knives";
if (item_type == ITEM_FISHING_POLES) return "fishing poles";
if (item_type == ITEM_ROPES) return "ropes";
if (item_type == ITEM_REED_BASKETS) return "reed baskets";
if (item_type == ITEM_CLAY_POTS) return "clay pots";
if (item_type == ITEM_SKIN_HATS) return "skin hats";
if (item_type == ITEM_SKIN_GLOVES) return "skin gloves";
if (item_type == ITEM_SKIN_PANTS) return "skin pants";
if (item_type == ITEM_SKIN_TUNICS) return "skin tunics";
if (item_type == ITEM_MOCCASINS) return "moccasins";
if (item_type == ITEM_SKIN_POUCHES) return "skin pouches";
return "items";
}
string format_favor(double value) {
if (value < 0) value = 0;
int scaled = int(value * 100 + 0.5);
int whole = scaled / 100;
int frac = scaled % 100;
string frac_text = (frac < 10) ? "0" + frac : "" + frac;
return "" + whole + "." + frac_text;
}
string get_item_label_singular(int item_type) {
if (item_type == ITEM_STICKS) return "stick";
if (item_type == ITEM_VINES) return "vine";
if (item_type == ITEM_REEDS) return "reed";
if (item_type == ITEM_STONES) return "stone";
if (item_type == ITEM_LOGS) return "log";
if (item_type == ITEM_CLAY) return "clay";
if (item_type == ITEM_SMALL_GAME) return "small game";
if (item_type == ITEM_MEAT) return "meat";
if (item_type == ITEM_SKINS) return "skin";
if (item_type == ITEM_SPEARS) return "spear";
if (item_type == ITEM_SLINGS) return "sling";
if (item_type == ITEM_AXES) return "axe";
if (item_type == ITEM_SNARES) return "snare";
if (item_type == ITEM_KNIVES) return "knife";
if (item_type == ITEM_FISHING_POLES) return "fishing pole";
if (item_type == ITEM_ROPES) return "rope";
if (item_type == ITEM_REED_BASKETS) return "reed basket";
if (item_type == ITEM_CLAY_POTS) return "clay pot";
if (item_type == ITEM_SKIN_HATS) return "skin hat";
if (item_type == ITEM_SKIN_GLOVES) return "skin glove";
if (item_type == ITEM_SKIN_PANTS) return "skin pants";
if (item_type == ITEM_SKIN_TUNICS) return "skin tunic";
if (item_type == ITEM_MOCCASINS) return "moccasin";
if (item_type == ITEM_SKIN_POUCHES) return "skin pouch";
return "item";
}
double get_item_favor_value(int item_type) {
if (item_type == ITEM_STICKS) return 0.01;
if (item_type == ITEM_VINES) return 0.01;
if (item_type == ITEM_REEDS) return 0.01;
if (item_type == ITEM_STONES) return 0.02;
if (item_type == ITEM_LOGS) return 0.05;
if (item_type == ITEM_CLAY) return 0.02;
if (item_type == ITEM_SMALL_GAME) return 0.20;
if (item_type == ITEM_MEAT) return 0.15;
if (item_type == ITEM_SKINS) return 0.15;
if (item_type == ITEM_SPEARS) return 1.00;
if (item_type == ITEM_SLINGS) return 2.00;
if (item_type == ITEM_AXES) return 1.50;
if (item_type == ITEM_SNARES) return 0.50;
if (item_type == ITEM_KNIVES) return 0.80;
if (item_type == ITEM_FISHING_POLES) return 0.80;
if (item_type == ITEM_ROPES) return 0.40;
if (item_type == ITEM_REED_BASKETS) return 0.60;
if (item_type == ITEM_CLAY_POTS) return 0.70;
if (item_type == ITEM_SKIN_HATS) return 0.60;
if (item_type == ITEM_SKIN_GLOVES) return 0.60;
if (item_type == ITEM_SKIN_PANTS) return 1.20;
if (item_type == ITEM_SKIN_TUNICS) return 1.20;
if (item_type == ITEM_MOCCASINS) return 0.80;
if (item_type == ITEM_SKIN_POUCHES) return 0.80;
return 0.01;
}
string get_equipped_weapon_name() {
if (spear_equipped) return "Spear";
if (axe_equipped) return "Stone Axe";
if (sling_equipped) return "Sling";
return "None";
}
string get_speed_status() {
if (blessing_speed_active) return "blessed";
if (equipped_feet == EQUIP_MOCCASINS) return "boosted by moccasins";
return "normal";
}
void cleanup_equipment_after_inventory_change() {
if (inv_spears <= 0) spear_equipped = false;
if (inv_axes <= 0) axe_equipped = false;
if (inv_slings <= 0) sling_equipped = false;
if (inv_skin_hats <= 0) equipped_head = EQUIP_NONE;
if (inv_skin_gloves <= 0) equipped_hands = EQUIP_NONE;
if (inv_skin_pants <= 0) equipped_legs = EQUIP_NONE;
if (inv_skin_tunics <= 0) equipped_torso = EQUIP_NONE;
if (inv_moccasins <= 0) equipped_feet = EQUIP_NONE;
if (inv_skin_pouches <= 0) equipped_arms = EQUIP_NONE;
update_max_health_from_equipment();
}
+811
View File
@@ -0,0 +1,811 @@
// Inventory and base menus
void check_inventory_keys(int x) {
if (key_pressed(KEY_P)) {
show_character_info();
return;
}
if (key_pressed(KEY_I)) {
bool in_base = x <= BASE_END;
if (in_base && world_storages.length() > 0) {
run_inventory_root_menu();
} else {
if (in_base && world_storages.length() == 0) {
screen_reader_speak("No storage built.", true);
}
run_inventory_menu(false);
}
}
}
void check_action_menu(int x) {
if (key_pressed(KEY_A)) {
run_action_menu(x);
}
}
void menu_background_tick() {
update_time();
update_environment();
update_snares();
update_fires();
update_zombies();
update_blessings();
update_notifications();
// Fire damage check (only if not jumping)
WorldFire@ fire_on_tile = get_fire_at(x);
if (fire_on_tile != null && !jumping && fire_damage_timer.elapsed > 1000) {
player_health--;
fire_damage_timer.restart();
screen_reader_speak("Burning! " + player_health + " health remaining.", true);
}
// Healing in base area
if (x <= BASE_END && player_health < max_health) {
WorldHerbGarden@ herb_garden = get_herb_garden_at_base();
int heal_interval = (herb_garden != null) ? 30000 : 150000; // 30 seconds with garden, 2.5 minutes without
if (healing_timer.elapsed > heal_interval) {
player_health++;
healing_timer.restart();
screen_reader_speak(player_health + " health.", true);
}
}
// Death check
if (player_health <= 0) {
screen_reader_speak("You have died.", true);
wait(2000);
exit();
}
}
int get_storage_total_items() {
int total = 0;
total += storage_stones;
total += storage_sticks;
total += storage_vines;
total += storage_reeds;
total += storage_logs;
total += storage_clay;
total += storage_small_game;
total += storage_meat;
total += storage_skins;
total += storage_spears;
total += storage_slings;
total += storage_axes;
total += storage_snares;
total += storage_knives;
total += storage_fishing_poles;
total += storage_ropes;
total += storage_reed_baskets;
total += storage_clay_pots;
total += storage_skin_hats;
total += storage_skin_gloves;
total += storage_skin_pants;
total += storage_skin_tunics;
total += storage_moccasins;
total += storage_skin_pouches;
return total;
}
string get_base_fire_status() {
int total = 0;
int burning = 0;
for (uint i = 0; i < world_fires.length(); i++) {
if (world_fires[i].position <= BASE_END) {
total++;
if (world_fires[i].is_burning()) {
burning++;
}
}
}
if (total == 0) return "No fires in base";
return "Fires in base: " + burning + " burning, " + total + " total";
}
void run_base_info_menu() {
if (x > BASE_END) {
screen_reader_speak("You are not in the base.", true);
return;
}
screen_reader_speak("Base info.", true);
int selection = 0;
string[] options;
options.insert_last("Barricade health " + barricade_health + " of " + BARRICADE_MAX_HEALTH);
options.insert_last("Residents " + residents_count);
if (world_storages.length() > 0) {
options.insert_last("Storage built. Total items " + get_storage_total_items());
int daily_food = get_daily_food_requirement();
options.insert_last("Food in storage " + storage_meat + " meat. Daily use " + daily_food);
} else {
options.insert_last("Storage not built");
}
options.insert_last(get_base_fire_status());
if (world_altars.length() > 0) options.insert_last("Altar built");
else options.insert_last("Altar not built");
if (get_herb_garden_at_base() != null) options.insert_last("Herb garden built");
else options.insert_last("Herb garden not built");
if (world_pastures.length() > 0) options.insert_last("Pasture built");
else options.insert_last("Pasture not built");
if (world_stables.length() > 0) options.insert_last("Stable built");
else options.insert_last("Stable not built");
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
}
}
string join_string_list(const string[]@ items) {
if (@items == null || items.length() == 0) return "";
string result = items[0];
for (uint i = 1; i < items.length(); i++) {
result += ", " + items[i];
}
return result;
}
void show_character_info() {
string[] equipped_clothing;
string[] missing_slots;
if (equipped_head == EQUIP_HAT) equipped_clothing.insert_last("Skin Hat");
else missing_slots.insert_last("head");
if (equipped_torso == EQUIP_TUNIC) equipped_clothing.insert_last("Skin Tunic");
else missing_slots.insert_last("torso");
if (equipped_arms == EQUIP_POUCH) equipped_clothing.insert_last("Skin Pouch");
else missing_slots.insert_last("arms");
if (equipped_hands == EQUIP_GLOVES) equipped_clothing.insert_last("Skin Gloves");
else missing_slots.insert_last("hands");
if (equipped_legs == EQUIP_PANTS) equipped_clothing.insert_last("Skin Pants");
else missing_slots.insert_last("legs");
if (equipped_feet == EQUIP_MOCCASINS) equipped_clothing.insert_last("Moccasins");
else missing_slots.insert_last("feet");
string info = "Character info. ";
info += "Health " + player_health + " of " + max_health + ". ";
info += "Weapon " + get_equipped_weapon_name() + ". ";
if (equipped_clothing.length() > 0) {
info += "Clothing equipped: " + join_string_list(equipped_clothing) + ". ";
} else {
info += "No clothing equipped. ";
}
if (missing_slots.length() > 0) {
info += "Missing " + join_string_list(missing_slots) + ". ";
}
info += "Favor " + format_favor(favor) + ". ";
info += "Speed " + get_speed_status() + ".";
screen_reader_speak(info, true);
}
int prompt_transfer_amount(const string prompt, int max_amount) {
string input = ui_input_box("Inventory", prompt + " (max " + max_amount + ")", "");
int amount = parse_int(input);
if (amount <= 0) return 0;
if (amount > max_amount) amount = max_amount;
return amount;
}
void move_small_game_to_storage(int amount) {
for (int i = 0; i < amount; i++) {
string game_type = "small game";
if (inv_small_game_types.length() > 0) {
game_type = inv_small_game_types[0];
inv_small_game_types.remove_at(0);
}
storage_small_game_types.insert_last(game_type);
}
}
void move_small_game_to_personal(int amount) {
for (int i = 0; i < amount; i++) {
string game_type = "small game";
if (storage_small_game_types.length() > 0) {
game_type = storage_small_game_types[0];
storage_small_game_types.remove_at(0);
}
inv_small_game_types.insert_last(game_type);
}
}
void deposit_item(int item_type) {
int available = get_personal_count(item_type);
if (available <= 0) {
screen_reader_speak("Nothing to deposit.", true);
return;
}
int capacity = BASE_STORAGE_MAX - get_storage_count(item_type);
if (capacity <= 0) {
screen_reader_speak("Storage for that item is full.", true);
return;
}
int max_transfer = (available < capacity) ? available : capacity;
int amount = prompt_transfer_amount("Deposit how many?", max_transfer);
if (amount <= 0) return;
if (item_type == ITEM_STICKS) { inv_sticks -= amount; storage_sticks += amount; }
else if (item_type == ITEM_VINES) { inv_vines -= amount; storage_vines += amount; }
else if (item_type == ITEM_REEDS) { inv_reeds -= amount; storage_reeds += amount; }
else if (item_type == ITEM_STONES) { inv_stones -= amount; storage_stones += amount; }
else if (item_type == ITEM_LOGS) { inv_logs -= amount; storage_logs += amount; }
else if (item_type == ITEM_CLAY) { inv_clay -= amount; storage_clay += amount; }
else if (item_type == ITEM_SMALL_GAME) { inv_small_game -= amount; storage_small_game += amount; move_small_game_to_storage(amount); }
else if (item_type == ITEM_MEAT) { inv_meat -= amount; storage_meat += amount; }
else if (item_type == ITEM_SKINS) { inv_skins -= amount; storage_skins += amount; }
else if (item_type == ITEM_SPEARS) { inv_spears -= amount; storage_spears += amount; }
else if (item_type == ITEM_SLINGS) { inv_slings -= amount; storage_slings += amount; }
else if (item_type == ITEM_AXES) { inv_axes -= amount; storage_axes += amount; }
else if (item_type == ITEM_SNARES) { inv_snares -= amount; storage_snares += amount; }
else if (item_type == ITEM_KNIVES) { inv_knives -= amount; storage_knives += amount; }
else if (item_type == ITEM_FISHING_POLES) { inv_fishing_poles -= amount; storage_fishing_poles += amount; }
else if (item_type == ITEM_ROPES) { inv_ropes -= amount; storage_ropes += amount; }
else if (item_type == ITEM_REED_BASKETS) { inv_reed_baskets -= amount; storage_reed_baskets += amount; }
else if (item_type == ITEM_CLAY_POTS) { inv_clay_pots -= amount; storage_clay_pots += amount; }
else if (item_type == ITEM_SKIN_HATS) { inv_skin_hats -= amount; storage_skin_hats += amount; }
else if (item_type == ITEM_SKIN_GLOVES) { inv_skin_gloves -= amount; storage_skin_gloves += amount; }
else if (item_type == ITEM_SKIN_PANTS) { inv_skin_pants -= amount; storage_skin_pants += amount; }
else if (item_type == ITEM_SKIN_TUNICS) { inv_skin_tunics -= amount; storage_skin_tunics += amount; }
else if (item_type == ITEM_MOCCASINS) { inv_moccasins -= amount; storage_moccasins += amount; }
else if (item_type == ITEM_SKIN_POUCHES) { inv_skin_pouches -= amount; storage_skin_pouches += amount; }
cleanup_equipment_after_inventory_change();
screen_reader_speak("Deposited " + amount + " " + get_item_label(item_type) + ".", true);
}
void withdraw_item(int item_type) {
int available = get_storage_count(item_type);
if (available <= 0) {
screen_reader_speak("Nothing to withdraw.", true);
return;
}
int capacity = get_personal_stack_limit() - get_personal_count(item_type);
if (capacity <= 0) {
screen_reader_speak("You can't carry any more " + get_item_label(item_type) + ".", true);
return;
}
int max_transfer = (available < capacity) ? available : capacity;
int amount = prompt_transfer_amount("Withdraw how many?", max_transfer);
if (amount <= 0) return;
if (item_type == ITEM_STICKS) { storage_sticks -= amount; inv_sticks += amount; }
else if (item_type == ITEM_VINES) { storage_vines -= amount; inv_vines += amount; }
else if (item_type == ITEM_REEDS) { storage_reeds -= amount; inv_reeds += amount; }
else if (item_type == ITEM_STONES) { storage_stones -= amount; inv_stones += amount; }
else if (item_type == ITEM_LOGS) { storage_logs -= amount; inv_logs += amount; }
else if (item_type == ITEM_CLAY) { storage_clay -= amount; inv_clay += amount; }
else if (item_type == ITEM_SMALL_GAME) { storage_small_game -= amount; inv_small_game += amount; move_small_game_to_personal(amount); }
else if (item_type == ITEM_MEAT) { storage_meat -= amount; inv_meat += amount; }
else if (item_type == ITEM_SKINS) { storage_skins -= amount; inv_skins += amount; }
else if (item_type == ITEM_SPEARS) { storage_spears -= amount; inv_spears += amount; }
else if (item_type == ITEM_SLINGS) { storage_slings -= amount; inv_slings += amount; }
else if (item_type == ITEM_AXES) { storage_axes -= amount; inv_axes += amount; }
else if (item_type == ITEM_SNARES) { storage_snares -= amount; inv_snares += amount; }
else if (item_type == ITEM_KNIVES) { storage_knives -= amount; inv_knives += amount; }
else if (item_type == ITEM_FISHING_POLES) { storage_fishing_poles -= amount; inv_fishing_poles += amount; }
else if (item_type == ITEM_ROPES) { storage_ropes -= amount; inv_ropes += amount; }
else if (item_type == ITEM_REED_BASKETS) { storage_reed_baskets -= amount; inv_reed_baskets += amount; }
else if (item_type == ITEM_CLAY_POTS) { storage_clay_pots -= amount; inv_clay_pots += amount; }
else if (item_type == ITEM_SKIN_HATS) { storage_skin_hats -= amount; inv_skin_hats += amount; }
else if (item_type == ITEM_SKIN_GLOVES) { storage_skin_gloves -= amount; inv_skin_gloves += amount; }
else if (item_type == ITEM_SKIN_PANTS) { storage_skin_pants -= amount; inv_skin_pants += amount; }
else if (item_type == ITEM_SKIN_TUNICS) { storage_skin_tunics -= amount; inv_skin_tunics += amount; }
else if (item_type == ITEM_MOCCASINS) { storage_moccasins -= amount; inv_moccasins += amount; }
else if (item_type == ITEM_SKIN_POUCHES) { storage_skin_pouches -= amount; inv_skin_pouches += amount; }
screen_reader_speak("Withdrew " + amount + " " + get_item_label(item_type) + ".", true);
}
void sacrifice_item(int item_type) {
int available = get_personal_count(item_type);
if (available <= 0) {
screen_reader_speak("Nothing to sacrifice.", true);
return;
}
if (item_type == ITEM_STICKS) inv_sticks--;
else if (item_type == ITEM_VINES) inv_vines--;
else if (item_type == ITEM_REEDS) inv_reeds--;
else if (item_type == ITEM_STONES) inv_stones--;
else if (item_type == ITEM_LOGS) inv_logs--;
else if (item_type == ITEM_CLAY) inv_clay--;
else if (item_type == ITEM_SMALL_GAME) {
inv_small_game--;
if (inv_small_game_types.length() > 0) {
inv_small_game_types.remove_at(0);
}
}
else if (item_type == ITEM_MEAT) inv_meat--;
else if (item_type == ITEM_SKINS) inv_skins--;
else if (item_type == ITEM_SPEARS) inv_spears--;
else if (item_type == ITEM_SLINGS) inv_slings--;
else if (item_type == ITEM_AXES) inv_axes--;
else if (item_type == ITEM_SNARES) inv_snares--;
else if (item_type == ITEM_KNIVES) inv_knives--;
else if (item_type == ITEM_FISHING_POLES) inv_fishing_poles--;
else if (item_type == ITEM_ROPES) inv_ropes--;
else if (item_type == ITEM_REED_BASKETS) inv_reed_baskets--;
else if (item_type == ITEM_CLAY_POTS) inv_clay_pots--;
else if (item_type == ITEM_SKIN_HATS) inv_skin_hats--;
else if (item_type == ITEM_SKIN_GLOVES) inv_skin_gloves--;
else if (item_type == ITEM_SKIN_PANTS) inv_skin_pants--;
else if (item_type == ITEM_SKIN_TUNICS) inv_skin_tunics--;
else if (item_type == ITEM_MOCCASINS) inv_moccasins--;
else if (item_type == ITEM_SKIN_POUCHES) inv_skin_pouches--;
cleanup_equipment_after_inventory_change();
double favor_gain = get_item_favor_value(item_type);
favor += favor_gain;
screen_reader_speak("Sacrificed 1 " + get_item_label_singular(item_type) + ". Favor +" + format_favor(favor_gain) + ". Total " + format_favor(favor) + ".", true);
}
void build_personal_inventory_options(string[]@ options, int[]@ item_types) {
options.resize(0);
item_types.resize(0);
options.insert_last("Sticks: " + inv_sticks); item_types.insert_last(ITEM_STICKS);
options.insert_last("Vines: " + inv_vines); item_types.insert_last(ITEM_VINES);
options.insert_last("Reeds: " + inv_reeds); item_types.insert_last(ITEM_REEDS);
options.insert_last("Stones: " + inv_stones); item_types.insert_last(ITEM_STONES);
options.insert_last("Logs: " + inv_logs); item_types.insert_last(ITEM_LOGS);
options.insert_last("Clay: " + inv_clay); item_types.insert_last(ITEM_CLAY);
options.insert_last("Small Game: " + inv_small_game); item_types.insert_last(ITEM_SMALL_GAME);
options.insert_last("Meat: " + inv_meat); item_types.insert_last(ITEM_MEAT);
options.insert_last("Skins: " + inv_skins); item_types.insert_last(ITEM_SKINS);
options.insert_last("Spears: " + inv_spears); item_types.insert_last(ITEM_SPEARS);
options.insert_last("Slings: " + inv_slings); item_types.insert_last(ITEM_SLINGS);
options.insert_last("Axes: " + inv_axes); item_types.insert_last(ITEM_AXES);
options.insert_last("Snares: " + inv_snares); item_types.insert_last(ITEM_SNARES);
options.insert_last("Knives: " + inv_knives); item_types.insert_last(ITEM_KNIVES);
options.insert_last("Fishing Poles: " + inv_fishing_poles); item_types.insert_last(ITEM_FISHING_POLES);
options.insert_last("Ropes: " + inv_ropes); item_types.insert_last(ITEM_ROPES);
options.insert_last("Reed Baskets: " + inv_reed_baskets); item_types.insert_last(ITEM_REED_BASKETS);
options.insert_last("Clay Pots: " + inv_clay_pots); item_types.insert_last(ITEM_CLAY_POTS);
options.insert_last("Skin Hats: " + inv_skin_hats); item_types.insert_last(ITEM_SKIN_HATS);
options.insert_last("Skin Gloves: " + inv_skin_gloves); item_types.insert_last(ITEM_SKIN_GLOVES);
options.insert_last("Skin Pants: " + inv_skin_pants); item_types.insert_last(ITEM_SKIN_PANTS);
options.insert_last("Skin Tunics: " + inv_skin_tunics); item_types.insert_last(ITEM_SKIN_TUNICS);
options.insert_last("Moccasins: " + inv_moccasins); item_types.insert_last(ITEM_MOCCASINS);
options.insert_last("Skin Pouches: " + inv_skin_pouches); item_types.insert_last(ITEM_SKIN_POUCHES);
}
void build_storage_inventory_options(string[]@ options, int[]@ item_types) {
options.resize(0);
item_types.resize(0);
options.insert_last("Sticks: " + storage_sticks); item_types.insert_last(ITEM_STICKS);
options.insert_last("Vines: " + storage_vines); item_types.insert_last(ITEM_VINES);
options.insert_last("Reeds: " + storage_reeds); item_types.insert_last(ITEM_REEDS);
options.insert_last("Stones: " + storage_stones); item_types.insert_last(ITEM_STONES);
options.insert_last("Logs: " + storage_logs); item_types.insert_last(ITEM_LOGS);
options.insert_last("Clay: " + storage_clay); item_types.insert_last(ITEM_CLAY);
options.insert_last("Small Game: " + storage_small_game); item_types.insert_last(ITEM_SMALL_GAME);
options.insert_last("Meat: " + storage_meat); item_types.insert_last(ITEM_MEAT);
options.insert_last("Skins: " + storage_skins); item_types.insert_last(ITEM_SKINS);
options.insert_last("Spears: " + storage_spears); item_types.insert_last(ITEM_SPEARS);
options.insert_last("Slings: " + storage_slings); item_types.insert_last(ITEM_SLINGS);
options.insert_last("Axes: " + storage_axes); item_types.insert_last(ITEM_AXES);
options.insert_last("Snares: " + storage_snares); item_types.insert_last(ITEM_SNARES);
options.insert_last("Knives: " + storage_knives); item_types.insert_last(ITEM_KNIVES);
options.insert_last("Fishing Poles: " + storage_fishing_poles); item_types.insert_last(ITEM_FISHING_POLES);
options.insert_last("Ropes: " + storage_ropes); item_types.insert_last(ITEM_ROPES);
options.insert_last("Reed Baskets: " + storage_reed_baskets); item_types.insert_last(ITEM_REED_BASKETS);
options.insert_last("Clay Pots: " + storage_clay_pots); item_types.insert_last(ITEM_CLAY_POTS);
options.insert_last("Skin Hats: " + storage_skin_hats); item_types.insert_last(ITEM_SKIN_HATS);
options.insert_last("Skin Gloves: " + storage_skin_gloves); item_types.insert_last(ITEM_SKIN_GLOVES);
options.insert_last("Skin Pants: " + storage_skin_pants); item_types.insert_last(ITEM_SKIN_PANTS);
options.insert_last("Skin Tunics: " + storage_skin_tunics); item_types.insert_last(ITEM_SKIN_TUNICS);
options.insert_last("Moccasins: " + storage_moccasins); item_types.insert_last(ITEM_MOCCASINS);
options.insert_last("Skin Pouches: " + storage_skin_pouches); item_types.insert_last(ITEM_SKIN_POUCHES);
}
void show_inventory() {
string info = "Inventory: ";
info += inv_sticks + " sticks, ";
info += inv_vines + " vines, ";
info += inv_reeds + " reeds, ";
info += inv_stones + " stones, ";
info += inv_logs + " logs, ";
info += inv_clay + " clay, ";
info += inv_small_game + " small game, ";
info += inv_meat + " meat, ";
info += inv_skins + " skins. ";
info += "Tools: " + inv_spears + " spears, " + inv_slings + " slings, " + inv_axes + " axes, " + inv_snares + " snares, " + inv_knives + " knives, " + inv_fishing_poles + " fishing poles, " + inv_ropes + " ropes, " + inv_reed_baskets + " reed baskets, " + inv_clay_pots + " clay pots. ";
info += "Clothing: " + inv_skin_hats + " hats, " + inv_skin_gloves + " gloves, " + inv_skin_pants + " pants, " + inv_skin_tunics + " tunics, " + inv_moccasins + " moccasins, " + inv_skin_pouches + " skin pouches.";
screen_reader_speak(info, true);
}
void run_inventory_root_menu() {
screen_reader_speak("Inventory menu.", true);
int selection = 0;
string[] options = {"Personal inventory", "Base storage"};
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
if (selection == 0) run_inventory_menu(true);
else run_storage_menu();
break;
}
}
}
void run_inventory_menu(bool allow_deposit) {
screen_reader_speak("Inventory menu.", true);
int selection = 0;
string[] options;
int[] item_types;
build_personal_inventory_options(options, item_types);
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (allow_deposit && key_pressed(KEY_RETURN)) {
deposit_item(item_types[selection]);
build_personal_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
}
}
void run_storage_menu() {
if (world_storages.length() == 0) {
screen_reader_speak("No storage built.", true);
return;
}
screen_reader_speak("Base storage.", true);
int selection = 0;
string[] options;
int[] item_types;
build_storage_inventory_options(options, item_types);
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
withdraw_item(item_types[selection]);
build_storage_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
}
}
void run_altar_menu() {
if (world_altars.length() == 0) {
screen_reader_speak("No altar built.", true);
return;
}
screen_reader_speak("Altar. Favor " + format_favor(favor) + ".", true);
int selection = 0;
string[] options;
int[] item_types;
build_personal_inventory_options(options, item_types);
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
sacrifice_item(item_types[selection]);
build_personal_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
}
}
void try_place_snare(int x) {
if (inv_snares > 0) {
// Prevent placing if one already exists here
if (get_snare_at(x) != null) {
screen_reader_speak("There is already a snare here.", true);
return;
}
inv_snares--;
add_world_snare(x);
screen_reader_speak("Snare set.", true);
} else {
screen_reader_speak("No snares to place.", true);
}
}
void try_feed_fire_stick(WorldFire@ fire) {
if (inv_sticks > 0 && fire != null) {
inv_sticks--;
fire.add_fuel(300000); // 5 minutes
screen_reader_speak("You dump an arm load of sticks into the fire.", true);
p.play_stationary("sounds/actions/feed_fire.ogg", false);
}
}
void try_feed_fire_vine(WorldFire@ fire) {
if (inv_vines > 0 && fire != null) {
inv_vines--;
fire.add_fuel(60000); // 1 minute
screen_reader_speak("You toss a fiew vines and leaves into the fire.", true);
p.play_stationary("sounds/actions/feed_fire.ogg", false);
}
}
void try_feed_fire_log(WorldFire@ fire) {
if (inv_logs > 0 && fire != null) {
inv_logs--;
fire.add_fuel(720000); // 12 minutes
screen_reader_speak("You heave a log into the fire.", true);
p.play_stationary("sounds/actions/feed_fire.ogg", false);
}
}
void check_equipment_menu() {
if (key_pressed(KEY_E)) {
// Check if player has any equipment
if (inv_spears == 0 && inv_axes == 0 && inv_slings == 0 &&
inv_skin_hats == 0 && inv_skin_gloves == 0 && inv_skin_pants == 0 &&
inv_skin_tunics == 0 && inv_moccasins == 0 && inv_skin_pouches == 0) {
screen_reader_speak("Nothing to equip.", true);
} else {
run_equipment_menu();
}
}
}
void run_action_menu(int x) {
screen_reader_speak("Action menu.", true);
int selection = 0;
string[] options;
int[] action_types; // Track what action each option corresponds to
// Check if fire is nearby
WorldFire@ nearby_fire = get_fire_near(x);
bool can_feed_fire = nearby_fire != null;
// Build menu options dynamically
options.insert_last("Place Snare");
action_types.insert_last(0);
if (can_feed_fire) {
if (inv_sticks > 0) {
options.insert_last("Feed fire with stick");
action_types.insert_last(1);
}
if (inv_vines > 0) {
options.insert_last("Feed fire with vine");
action_types.insert_last(2);
}
if (inv_logs > 0) {
options.insert_last("Feed fire with log");
action_types.insert_last(3);
}
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
int action = action_types[selection];
if (action == 0) {
try_place_snare(x);
} else if (action == 1) {
try_feed_fire_stick(nearby_fire);
} else if (action == 2) {
try_feed_fire_vine(nearby_fire);
} else if (action == 3) {
try_feed_fire_log(nearby_fire);
}
break;
}
}
}
void run_equipment_menu() {
screen_reader_speak("Equipment menu.", true);
int selection = 0;
string[] options;
int[] equipment_types;
// Build menu dynamically based on what player has
if (inv_spears > 0) {
string status = equipment_is_equipped(EQUIP_SPEAR) ? " (equipped)" : "";
options.insert_last("Spear" + status);
equipment_types.insert_last(EQUIP_SPEAR);
}
if (inv_slings > 0) {
string status = equipment_is_equipped(EQUIP_SLING) ? " (equipped)" : "";
options.insert_last("Sling" + status);
equipment_types.insert_last(EQUIP_SLING);
}
if (inv_axes > 0) {
string status = equipment_is_equipped(EQUIP_AXE) ? " (equipped)" : "";
options.insert_last("Stone Axe" + status);
equipment_types.insert_last(EQUIP_AXE);
}
if (inv_skin_hats > 0) {
string status = equipment_is_equipped(EQUIP_HAT) ? " (equipped)" : "";
options.insert_last("Skin Hat" + status);
equipment_types.insert_last(EQUIP_HAT);
}
if (inv_skin_gloves > 0) {
string status = equipment_is_equipped(EQUIP_GLOVES) ? " (equipped)" : "";
options.insert_last("Skin Gloves" + status);
equipment_types.insert_last(EQUIP_GLOVES);
}
if (inv_skin_pants > 0) {
string status = equipment_is_equipped(EQUIP_PANTS) ? " (equipped)" : "";
options.insert_last("Skin Pants" + status);
equipment_types.insert_last(EQUIP_PANTS);
}
if (inv_skin_tunics > 0) {
string status = equipment_is_equipped(EQUIP_TUNIC) ? " (equipped)" : "";
options.insert_last("Skin Tunic" + status);
equipment_types.insert_last(EQUIP_TUNIC);
}
if (inv_moccasins > 0) {
string status = equipment_is_equipped(EQUIP_MOCCASINS) ? " (equipped)" : "";
options.insert_last("Moccasins" + status);
equipment_types.insert_last(EQUIP_MOCCASINS);
}
if (inv_skin_pouches > 0) {
string status = equipment_is_equipped(EQUIP_POUCH) ? " (equipped)" : "";
options.insert_last("Skin Pouch" + status);
equipment_types.insert_last(EQUIP_POUCH);
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
int slot_index = get_quick_slot_key();
if (slot_index != -1) {
int equip_type = equipment_types[selection];
quick_slots[slot_index] = equip_type;
screen_reader_speak(get_equipment_name(equip_type) + " set to slot " + slot_index + ".", true);
}
if (key_pressed(KEY_RETURN)) {
int equip_type = equipment_types[selection];
if (equipment_is_equipped(equip_type)) {
unequip_equipment_type(equip_type);
screen_reader_speak(get_equipment_name(equip_type) + " unequipped.", true);
} else {
equip_equipment_type(equip_type);
screen_reader_speak(get_equipment_name(equip_type) + " equipped.", true);
}
update_max_health_from_equipment();
break;
}
}
}
+20 -5
View File
@@ -2,13 +2,14 @@
string[] notification_history;
const int MAX_NOTIFICATIONS = 10;
int current_notification_index = -1;
string[] notification_queue;
int[] notification_sound_handles;
void notify(string message) {
// Play notification sound
p.play_stationary("sounds/notify.ogg", false);
// Speak the message
screen_reader_speak(message, true);
int sound_handle = p.play_stationary("sounds/notify.ogg", false);
notification_queue.insert_last(message);
notification_sound_handles.insert_last(sound_handle);
// Add to history
notification_history.insert_last(message);
@@ -22,6 +23,21 @@ void notify(string message) {
current_notification_index = notification_history.length() - 1;
}
void update_notifications() {
if (notification_queue.length() == 0) {
return;
}
int sound_handle = notification_sound_handles[0];
if (sound_handle != -1 && p.sound_is_playing(sound_handle)) {
return;
}
screen_reader_speak(notification_queue[0], true);
notification_queue.remove_at(0);
notification_sound_handles.remove_at(0);
}
void check_notification_keys() {
// [ for previous notification (older) with position
if (key_pressed(KEY_LBRACKET)) {
@@ -69,4 +85,3 @@ void check_notification_keys() {
screen_reader_speak(notification_history[current_notification_index], true);
}
}
+6
View File
@@ -13,6 +13,7 @@ timer climb_timer; // For climb speed
// Health System
int player_health = 10;
int base_max_health = 10;
int max_health = 10;
timer fire_damage_timer;
timer healing_timer;
@@ -23,6 +24,11 @@ timer sling_charge_timer;
int sling_sound_handle = -1;
int last_sling_stage = -1; // Track which stage we're in to avoid duplicate sounds
// Favor system
double favor = 0.0;
bool blessing_speed_active = false;
timer blessing_speed_timer;
// Timers
timer walktimer;
timer jumptimer;
+151
View File
@@ -0,0 +1,151 @@
// Quest system
#include "src/quests/bat_invasion_game.nvgt"
#include "src/quests/enchanted_melody_game.nvgt"
#include "src/quests/escape_from_hel_game.nvgt"
const int QUEST_BAT_INVASION = 0;
const int QUEST_ENCHANTED_MELODY = 1;
const int QUEST_ESCAPE_FROM_HEL = 2;
const int QUEST_TYPE_COUNT = 3;
int[] quest_queue;
bool quest_roll_done_today = false;
string get_quest_name(int quest_type) {
if (quest_type == QUEST_BAT_INVASION) return "Bat Invasion";
if (quest_type == QUEST_ENCHANTED_MELODY) return "Enchanted Melody";
if (quest_type == QUEST_ESCAPE_FROM_HEL) return "Escape from Hel";
return "Unknown Quest";
}
string get_quest_description(int quest_type) {
if (quest_type == QUEST_BAT_INVASION) {
return "Bat Invasion. Giant killer bats are attacking. Press space to throw when the bat is centered.";
}
if (quest_type == QUEST_ENCHANTED_MELODY) {
return "Enchanted Melody. Repeat the pattern using E R D F or U I J K. Lowest to highest pitch.";
}
if (quest_type == QUEST_ESCAPE_FROM_HEL) {
return "Escape from Hel. Press space to jump over open graves. The pace quickens.";
}
return "Unknown quest.";
}
int get_quest_chance_from_favor() {
int chance = int(favor * QUEST_CHANCE_PER_FAVOR);
if (chance < QUEST_MIN_CHANCE && favor > 1.0) chance = QUEST_MIN_CHANCE;
if (chance > 100) chance = 100;
return chance;
}
void add_quest(int quest_type) {
if (quest_queue.length() >= QUEST_MAX_ACTIVE) return;
quest_queue.insert_last(quest_type);
notify("A new quest is available: " + get_quest_name(quest_type) + ".");
}
void attempt_daily_quest() {
if (quest_roll_done_today) return;
if (favor <= 1.0) return;
if (world_altars.length() == 0) return;
if (quest_queue.length() >= QUEST_MAX_ACTIVE) return;
quest_roll_done_today = true;
int chance = get_quest_chance_from_favor();
int roll = random(1, 100);
if (roll > chance) return;
int quest_type = random(0, QUEST_TYPE_COUNT - 1);
add_quest(quest_type);
}
void check_quest_menu() {
if (key_pressed(KEY_Q)) {
if (x > BASE_END) {
screen_reader_speak("You are not in the base.", true);
return;
}
if (quest_queue.length() == 0) {
screen_reader_speak("No quests available.", true);
return;
}
run_quest_menu();
}
}
void apply_quest_reward(int score) {
if (score <= 0) {
screen_reader_speak("No reward earned.", true);
return;
}
double favor_gain = score * QUEST_FAVOR_PER_POINT;
favor += favor_gain;
int stones_gain = (score >= QUEST_STONE_SCORE) ? 1 : 0;
int logs_gain = (score >= QUEST_LOG_SCORE) ? 1 : 0;
int skins_gain = (score >= QUEST_SKIN_SCORE) ? 1 : 0;
int stones_added = add_to_stack(inv_stones, stones_gain);
inv_stones += stones_added;
int logs_added = add_to_stack(inv_logs, logs_gain);
inv_logs += logs_added;
int skins_added = add_to_stack(inv_skins, skins_gain);
inv_skins += skins_added;
string message = "Quest reward: favor +" + format_favor(favor_gain) + ".";
if (stones_gain > 0) message += " Stones +" + stones_added + ".";
if (logs_gain > 0) message += " Logs +" + logs_added + ".";
if (skins_gain > 0) message += " Skins +" + skins_added + ".";
screen_reader_speak(message, true);
}
void run_quest(int quest_type) {
screen_reader_speak(get_quest_description(quest_type), true);
wait(800);
p.pause_all();
int score = 0;
if (quest_type == QUEST_BAT_INVASION) score = run_bat_invasion();
else if (quest_type == QUEST_ENCHANTED_MELODY) score = run_enchanted_melody();
else if (quest_type == QUEST_ESCAPE_FROM_HEL) score = run_escape_from_hel();
apply_quest_reward(score);
p.resume_all();
}
void run_quest_menu() {
screen_reader_speak("Quest menu.", true);
int selection = 0;
string[] options;
for (uint i = 0; i < quest_queue.length(); i++) {
options.insert_last(get_quest_name(quest_queue[i]));
}
while(true) {
wait(5);
menu_background_tick();
if (key_pressed(KEY_ESCAPE)) {
screen_reader_speak("Closed.", true);
break;
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
screen_reader_speak(options[selection], true);
}
if (key_pressed(KEY_RETURN)) {
int quest_type = quest_queue[selection];
quest_queue.remove_at(selection);
run_quest(quest_type);
break;
}
}
}
+99
View File
@@ -0,0 +1,99 @@
// Bat Invasion quest game
string[] bat_sounds = {"sounds/quests/bat1.ogg", "sounds/quests/bat2.ogg"};
int run_bat_invasion() {
screen_reader_speak("Bat Invasion. Bats will fly past from left or right. Press space to throw your spear when the bat is centered. Press enter to continue.", true);
// Wait for enter
while (!key_pressed(KEY_RETURN)) {
wait(5);
}
screen_reader_speak("Starting.", true);
wait(500);
int turns = 10;
int score = 0;
// Position configuration
const int BAT_START_DISTANCE = 15; // How far left/right the bat starts
const int CENTER_TOLERANCE = 2; // How close to center counts as a hit
const int LISTENER_POS = 0; // Player is at center
for (int i = 0; i < turns; i++) {
// Pick random bat sound
string bat_sound = bat_sounds[random(0, bat_sounds.length() - 1)];
// Randomly start from left or right
int direction = (random(0, 1) == 0) ? 1 : -1; // 1 = left to right, -1 = right to left
int start_pos = -BAT_START_DISTANCE * direction;
int end_pos = BAT_START_DISTANCE * direction;
// Random flight speed (slower range with variation)
int flight_time = random(2200, 3400);
int update_interval = 30; // Update position every 30ms
// Start bat sound at starting position
int bat_handle = p.play_1d(bat_sound, LISTENER_POS, start_pos, true);
timer flight_timer;
flight_timer.restart();
bool threw = false;
bool hit = false;
while (flight_timer.elapsed < flight_time) {
wait(update_interval);
// Calculate current position based on elapsed time
float progress = float(flight_timer.elapsed) / float(flight_time);
int current_pos = int(start_pos + (progress * (end_pos - start_pos)));
// Update bat sound position
if (bat_handle != -1) {
p.update_sound_1d(bat_handle, current_pos);
}
// Check for throw
if (!threw && key_pressed(KEY_SPACE)) {
threw = true;
p.play_stationary("sounds/quests/spear_throw.ogg", false);
// Check if bat is centered (within tolerance)
int distance_from_center = current_pos;
if (distance_from_center < 0) distance_from_center = -distance_from_center;
if (distance_from_center <= CENTER_TOLERANCE) {
hit = true;
// Stop bat sound and play hit
if (bat_handle != -1) {
p.destroy_sound(bat_handle);
bat_handle = -1;
}
wait(150);
p.play_stationary("sounds/quests/spear_hit.ogg", false);
score += 2;
break;
} else {
wait(150);
p.play_stationary("sounds/quests/spear_miss.ogg", false);
}
}
}
// Clean up bat sound if still playing
if (bat_handle != -1) {
p.destroy_sound(bat_handle);
bat_handle = -1;
}
// If didn't throw at all, count as miss
if (!threw) {
p.play_stationary("sounds/quests/spear_miss.ogg", false);
}
wait(400);
}
screen_reader_speak("Bat invasion complete. Score " + score + ".", true);
return score;
}
+87
View File
@@ -0,0 +1,87 @@
// Enchanted Melody quest game
string[] quest_notes = {
"sounds/quests/bone1.ogg",
"sounds/quests/bone2.ogg",
"sounds/quests/bone3.ogg",
"sounds/quests/bone4.ogg"
};
void play_note(int note_index) {
if (note_index < 0 || note_index >= int(quest_notes.length())) return;
p.play_stationary(quest_notes[note_index], false);
}
int get_note_from_key() {
if (key_pressed(KEY_F) || key_pressed(KEY_K)) return 0;
if (key_pressed(KEY_D) || key_pressed(KEY_J)) return 1;
if (key_pressed(KEY_R) || key_pressed(KEY_I)) return 2;
if (key_pressed(KEY_E) || key_pressed(KEY_U)) return 3;
return -1;
}
void run_practice_mode() {
screen_reader_speak("Enchanted Melody. Repeat the magical pattern using F D R E or K J I U, from lowest to highest pitch. Practice the notes now, then press enter to begin, or escape to cancel.", true);
while (true) {
wait(5);
// Check for practice note input
int note = get_note_from_key();
if (note != -1) {
play_note(note);
}
// Enter to start the game
if (key_pressed(KEY_RETURN)) {
return;
}
// Escape to cancel
if (key_pressed(KEY_ESCAPE)) {
return;
}
}
}
int run_enchanted_melody() {
// Practice mode first
run_practice_mode();
// Check if player pressed escape during practice
if (key_down(KEY_ESCAPE)) {
return 0;
}
screen_reader_speak("Starting. Repeat the pattern.", true);
wait(500);
int[] pattern;
pattern.insert_last(random(0, 3));
int rounds = 0;
while (true) {
for (uint i = 0; i < pattern.length(); i++) {
play_note(pattern[i]);
wait(600);
}
uint index = 0;
while (index < pattern.length()) {
wait(5);
int note = get_note_from_key();
if (note == -1) continue;
play_note(note);
if (note != pattern[index]) {
int score = rounds * 2;
screen_reader_speak("You matched " + rounds + " notes. Score " + score + ".", true);
return score;
}
index++;
}
rounds++;
pattern.insert_last(random(0, 3));
wait(500);
}
return 0;
}
+117
View File
@@ -0,0 +1,117 @@
// Escape from Hel quest game
int run_escape_from_hel() {
screen_reader_speak("Escape from Hel. You are running from the Draugr, good luck. Press space to jump over open graves as you hear them approach. Press enter to continue.", true);
// Wait for enter
while (!key_pressed(KEY_RETURN)) {
wait(5);
}
screen_reader_speak("Starting.", true);
wait(500);
int score = 0;
int steps_until_pit = 10; // First pit after exactly 10 steps
int steps_taken = 0;
int total_steps = 0;
int base_step_time = 900;
string step_sound = "sounds/quests/footstep.ogg";
// Pit audio configuration
int pit_fade_start = 6; // Start pit sound 6 steps before the pit
float start_volume = -35.0; // Start very quiet
float max_volume = 0.0; // Full volume at pit
int pit_handle = -1;
// Jump state
bool jumping = false;
timer jump_timer;
const int JUMP_DURATION = 400; // How long a jump lasts
while (true) {
// Speed increases indefinitely until player can't keep up
int step_time = base_step_time - (total_steps * 8);
if (step_time < 50) step_time = 50; // Minimum to prevent audio/timing issues
// Check if jump finished
if (jumping && jump_timer.elapsed >= JUMP_DURATION) {
jumping = false;
}
// Process step with input checking
timer step_timer;
step_timer.restart();
// Play footstep at start of step
if (!jumping) {
p.play_stationary(step_sound, false);
}
// Wait for step duration, checking for jump input
while (step_timer.elapsed < step_time) {
wait(5);
// Allow jump anytime when not already jumping
if (!jumping && key_pressed(KEY_SPACE)) {
p.play_stationary("sounds/quests/jump.ogg", false);
jumping = true;
jump_timer.restart();
}
// Update jump state during wait
if (jumping && jump_timer.elapsed >= JUMP_DURATION) {
jumping = false;
}
}
steps_taken++;
total_steps++;
// Calculate steps remaining until pit
int steps_remaining = steps_until_pit - steps_taken;
// Start pit sound when approaching
if (steps_remaining <= pit_fade_start && steps_remaining >= 0 && pit_handle == -1) {
pit_handle = p.play_stationary("sounds/quests/pit.ogg", true);
if (pit_handle != -1) {
p.update_sound_start_values(pit_handle, 0.0, start_volume, 1.0);
}
}
// Update pit volume as we get closer
if (pit_handle != -1 && steps_remaining >= 0) {
float progress = float(pit_fade_start - steps_remaining) / float(pit_fade_start);
if (progress > 1.0) progress = 1.0;
float current_volume = start_volume + (progress * (max_volume - start_volume));
p.update_sound_start_values(pit_handle, 0.0, current_volume, 1.0);
}
// Reached the pit
if (steps_taken >= steps_until_pit) {
// Must be jumping to clear the pit
if (!jumping) {
// Fell in
if (pit_handle != -1) {
p.destroy_sound(pit_handle);
pit_handle = -1;
}
p.play_stationary("sounds/quests/fall.ogg", false);
screen_reader_speak("You fell in. Score " + score + ".", true);
return score;
}
// Successfully jumped over
if (pit_handle != -1) {
p.destroy_sound(pit_handle);
pit_handle = -1;
}
score += 2;
// Reset for next pit - random spacing between 5-15 steps
steps_taken = 0;
steps_until_pit = random(5, 15);
}
}
return score;
}
+542 -25
View File
@@ -3,6 +3,7 @@
const string SAVE_FILE_PATH = "save.dat";
const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1";
const int SAVE_VERSION = 1;
string last_save_error = "";
bool has_save_game() {
return file_exists(SAVE_FILE_PATH);
@@ -47,15 +48,42 @@ bool read_file_string(const string&in filename, string&out data) {
double get_number(dictionary@ data, const string&in key, double defaultValue) {
double value;
if (@data == null) return defaultValue;
if (!data.get(key, value)) return defaultValue;
return value;
if (data.get(key, value)) return value;
int value_int;
if (data.get(key, value_int)) return value_int;
string value_str;
if (data.get(key, value_str)) {
return parse_int(value_str);
}
return defaultValue;
}
bool get_bool(dictionary@ data, const string&in key, bool defaultValue) {
bool value;
if (@data == null) return defaultValue;
if (!data.get(key, value)) return defaultValue;
return value;
if (data.get(key, value)) return value;
int value_int;
if (data.get(key, value_int)) return value_int != 0;
string value_str;
if (data.get(key, value_str)) return value_str == "1" || value_str == "true";
return defaultValue;
}
bool dictionary_has_keys(dictionary@ data) {
if (@data == null) return false;
string[]@ keys = data.get_keys();
return keys.length() > 0;
}
bool has_number_key(dictionary@ data, const string&in key) {
double value;
if (@data == null) return false;
if (data.get(key, value)) return true;
int value_int;
if (data.get(key, value_int)) return true;
string value_str;
if (data.get(key, value_str)) return value_str.length() > 0;
return false;
}
string[] get_string_list(dictionary@ data, const string&in key) {
@@ -102,6 +130,10 @@ void clear_world_objects() {
world_firepits.resize(0);
world_herb_gardens.resize(0);
world_storages.resize(0);
world_pastures.resize(0);
world_stables.resize(0);
world_altars.resize(0);
for (uint i = 0; i < trees.length(); i++) {
if (trees[i].sound_handle != -1) {
@@ -131,11 +163,15 @@ void reset_game_state() {
searching = false;
player_health = 10;
base_max_health = 10;
max_health = 10;
favor = 0.0;
blessing_speed_active = false;
inv_stones = 0;
inv_sticks = 0;
inv_vines = 0;
inv_reeds = 0;
inv_logs = 0;
inv_clay = 0;
inv_small_game = 0;
@@ -148,10 +184,52 @@ void reset_game_state() {
inv_knives = 0;
inv_fishing_poles = 0;
inv_slings = 0;
inv_ropes = 0;
inv_reed_baskets = 0;
inv_clay_pots = 0;
inv_skin_hats = 0;
inv_skin_gloves = 0;
inv_skin_pants = 0;
inv_skin_tunics = 0;
inv_moccasins = 0;
inv_skin_pouches = 0;
storage_stones = 0;
storage_sticks = 0;
storage_vines = 0;
storage_reeds = 0;
storage_logs = 0;
storage_clay = 0;
storage_small_game = 0;
storage_small_game_types.resize(0);
storage_meat = 0;
storage_skins = 0;
storage_spears = 0;
storage_snares = 0;
storage_axes = 0;
storage_knives = 0;
storage_fishing_poles = 0;
storage_slings = 0;
storage_ropes = 0;
storage_reed_baskets = 0;
storage_clay_pots = 0;
storage_skin_hats = 0;
storage_skin_gloves = 0;
storage_skin_pants = 0;
storage_skin_tunics = 0;
storage_moccasins = 0;
storage_skin_pouches = 0;
spear_equipped = false;
axe_equipped = false;
sling_equipped = false;
equipped_head = EQUIP_NONE;
equipped_torso = EQUIP_NONE;
equipped_arms = EQUIP_NONE;
equipped_hands = EQUIP_NONE;
equipped_legs = EQUIP_NONE;
equipped_feet = EQUIP_NONE;
reset_quick_slots();
update_max_health_from_equipment();
MAP_SIZE = 35;
expanded_area_start = -1;
@@ -160,15 +238,24 @@ void reset_game_state() {
barricade_health = 0;
barricade_initialized = false;
residents_count = 0;
current_hour = 8;
current_day = 1;
is_daytime = true;
sun_setting_warned = false;
sunrise_warned = false;
crossfade_active = false;
crossfade_to_night = false;
area_expanded_today = false;
invasion_active = false;
invasion_start_hour = -1;
invasion_chance = 25;
invasion_triggered_today = false;
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
quest_roll_done_today = false;
quest_queue.resize(0);
walktimer.restart();
jumptimer.restart();
@@ -188,6 +275,7 @@ void start_new_game() {
spawn_trees(5, 19);
init_barricade();
init_time();
save_game_state();
}
string serialize_bool(bool value) {
@@ -210,6 +298,181 @@ string serialize_stream(WorldStream@ stream) {
return stream.start_position + "|" + stream.get_width();
}
string serialize_bandit(Bandit@ bandit) {
return bandit.position + "|" + bandit.health + "|" + bandit.weapon_type + "|" + bandit.behavior_state + "|" + bandit.wander_direction + "|" + bandit.move_interval;
}
string join_string_array(const string[]@ arr) {
if (@arr == null || arr.length() == 0) return "";
string result = arr[0];
for (uint i = 1; i < arr.length(); i++) {
result += "\n" + arr[i];
}
return result;
}
string[] split_string_array(const string&in data) {
string[] result;
if (data.length() == 0) return result;
result = data.split("\n");
return result;
}
string[] get_string_list_or_split(dictionary@ data, const string&in key) {
string[] result = get_string_list(data, key);
if (result.length() > 0) return result;
string value;
if (@data != null && data.get(key, value)) {
return split_string_array(value);
}
return result;
}
int get_byte_at(const string&in data, int index) {
string single = data.substr(index, 1);
return character_to_ascii(single);
}
bool find_raw_key(const string&in rawData, const string&in key, int &out pos_after_key) {
int key_len = key.length();
if (key_len <= 0 || key_len > 65535) return false;
int low = key_len & 0xFF;
int high = (key_len >> 8) & 0xFF;
int limit = rawData.length() - key_len - 2;
for (int i = 0; i <= limit; i++) {
if (get_byte_at(rawData, i) != low) continue;
if (get_byte_at(rawData, i + 1) != high) continue;
if (rawData.substr(i + 2, key_len) == key) {
pos_after_key = i + 2 + key_len;
return true;
}
}
return false;
}
bool get_raw_number(const string&in rawData, const string&in key, int &out value) {
int pos;
if (!find_raw_key(rawData, key, pos)) return false;
if (pos >= rawData.length()) return false;
int type = get_byte_at(rawData, pos);
if (type != 2) return false;
if (pos + 1 + 8 > rawData.length()) return false;
double result = 0;
double multiplier = 1;
for (int i = 0; i < 8; i++) {
result += get_byte_at(rawData, pos + 1 + i) * multiplier;
multiplier *= 256;
}
value = int(result);
return true;
}
bool get_raw_bool(const string&in rawData, const string&in key, bool &out value) {
int pos;
if (!find_raw_key(rawData, key, pos)) return false;
if (pos >= rawData.length()) return false;
int type = get_byte_at(rawData, pos);
if (type != 1) return false;
if (pos + 1 >= rawData.length()) return false;
value = (get_byte_at(rawData, pos + 1) != 0);
return true;
}
bool load_game_state_from_raw(const string&in rawData) {
reset_game_state();
int value;
bool bool_value;
if (!get_raw_number(rawData, "player_x", value)) return false;
x = value;
if (!get_raw_number(rawData, "player_health", value)) return false;
player_health = value;
if (!get_raw_number(rawData, "time_current_day", value)) return false;
current_day = value;
if (get_raw_number(rawData, "player_y", value)) y = value;
if (get_raw_number(rawData, "player_facing", value)) facing = value;
if (get_raw_number(rawData, "player_base_health", value)) base_max_health = value;
if (get_raw_number(rawData, "player_max_health", value)) max_health = value;
if (get_raw_number(rawData, "player_favor", value)) favor = value;
if (get_raw_number(rawData, "time_current_hour", value)) current_hour = value;
if (get_raw_number(rawData, "world_map_size", value)) MAP_SIZE = value;
if (get_raw_number(rawData, "world_expanded_area_start", value)) expanded_area_start = value;
if (get_raw_number(rawData, "world_expanded_area_end", value)) expanded_area_end = value;
if (get_raw_number(rawData, "world_barricade_health", value)) barricade_health = value;
if (get_raw_number(rawData, "world_residents_count", value)) residents_count = value;
if (get_raw_bool(rawData, "world_barricade_initialized", bool_value)) barricade_initialized = bool_value;
if (get_raw_bool(rawData, "time_sun_setting_warned", bool_value)) sun_setting_warned = bool_value;
if (get_raw_bool(rawData, "time_sunrise_warned", bool_value)) sunrise_warned = bool_value;
if (get_raw_bool(rawData, "time_area_expanded_today", bool_value)) area_expanded_today = bool_value;
if (get_raw_bool(rawData, "time_invasion_active", bool_value)) invasion_active = bool_value;
if (get_raw_bool(rawData, "time_invasion_triggered_today", bool_value)) invasion_triggered_today = bool_value;
if (get_raw_bool(rawData, "time_invasion_roll_done_today", bool_value)) invasion_roll_done_today = bool_value;
if (get_raw_bool(rawData, "quest_roll_done_today", bool_value)) quest_roll_done_today = bool_value;
if (get_raw_number(rawData, "time_invasion_start_hour", value)) invasion_start_hour = value;
if (get_raw_number(rawData, "time_invasion_chance", value)) invasion_chance = value;
if (get_raw_number(rawData, "time_invasion_scheduled_hour", value)) invasion_scheduled_hour = value;
if (get_raw_number(rawData, "inventory_stones", value)) inv_stones = value;
if (get_raw_number(rawData, "inventory_sticks", value)) inv_sticks = value;
if (get_raw_number(rawData, "inventory_vines", value)) inv_vines = value;
if (get_raw_number(rawData, "inventory_reeds", value)) inv_reeds = value;
if (get_raw_number(rawData, "inventory_logs", value)) inv_logs = value;
if (get_raw_number(rawData, "inventory_clay", value)) inv_clay = value;
if (get_raw_number(rawData, "inventory_small_game", value)) inv_small_game = value;
if (get_raw_number(rawData, "inventory_meat", value)) inv_meat = value;
if (get_raw_number(rawData, "inventory_skins", value)) inv_skins = value;
if (get_raw_number(rawData, "inventory_spears", value)) inv_spears = value;
if (get_raw_number(rawData, "inventory_snares", value)) inv_snares = value;
if (get_raw_number(rawData, "inventory_axes", value)) inv_axes = value;
if (get_raw_number(rawData, "inventory_knives", value)) inv_knives = value;
if (get_raw_number(rawData, "inventory_fishing_poles", value)) inv_fishing_poles = value;
if (get_raw_number(rawData, "inventory_slings", value)) inv_slings = value;
if (get_raw_number(rawData, "inventory_ropes", value)) inv_ropes = value;
if (get_raw_number(rawData, "inventory_reed_baskets", value)) inv_reed_baskets = value;
if (get_raw_number(rawData, "inventory_clay_pots", value)) inv_clay_pots = value;
if (get_raw_number(rawData, "inventory_skin_hats", value)) inv_skin_hats = value;
if (get_raw_number(rawData, "inventory_skin_gloves", value)) inv_skin_gloves = value;
if (get_raw_number(rawData, "inventory_skin_pants", value)) inv_skin_pants = value;
if (get_raw_number(rawData, "inventory_skin_tunics", value)) inv_skin_tunics = value;
if (get_raw_number(rawData, "inventory_moccasins", value)) inv_moccasins = value;
if (get_raw_number(rawData, "inventory_skin_pouches", value)) inv_skin_pouches = value;
if (get_raw_bool(rawData, "equipment_spear_equipped", bool_value)) spear_equipped = bool_value;
if (get_raw_bool(rawData, "equipment_axe_equipped", bool_value)) axe_equipped = bool_value;
if (get_raw_bool(rawData, "equipment_sling_equipped", bool_value)) sling_equipped = bool_value;
if (get_raw_number(rawData, "equipment_head", value)) equipped_head = value;
if (get_raw_number(rawData, "equipment_torso", value)) equipped_torso = value;
if (get_raw_number(rawData, "equipment_hands", value)) equipped_hands = value;
if (get_raw_number(rawData, "equipment_legs", value)) equipped_legs = value;
if (get_raw_number(rawData, "equipment_feet", value)) equipped_feet = value;
if (get_raw_number(rawData, "equipment_arms", value)) equipped_arms = value;
if (equipped_arms != EQUIP_POUCH) equipped_arms = EQUIP_NONE;
if (equipped_arms == EQUIP_POUCH && inv_skin_pouches <= 0) equipped_arms = EQUIP_NONE;
if (inv_small_game_types.length() == 0 && inv_small_game > 0) {
for (int i = 0; i < inv_small_game; i++) {
inv_small_game_types.insert_last("small game");
}
}
if (!barricade_initialized) {
init_barricade();
} else {
if (barricade_health < 0) barricade_health = 0;
if (barricade_health > BARRICADE_MAX_HEALTH) barricade_health = BARRICADE_MAX_HEALTH;
}
spawn_trees(5, 19);
is_daytime = (current_hour >= 6 && current_hour < 19);
hour_timer.restart();
update_max_health_from_equipment();
return true;
}
bool save_game_state() {
dictionary saveData;
@@ -218,11 +481,14 @@ bool save_game_state() {
saveData.set("player_y", y);
saveData.set("player_facing", facing);
saveData.set("player_health", player_health);
saveData.set("player_base_health", base_max_health);
saveData.set("player_max_health", max_health);
saveData.set("player_favor", favor);
saveData.set("inventory_stones", inv_stones);
saveData.set("inventory_sticks", inv_sticks);
saveData.set("inventory_vines", inv_vines);
saveData.set("inventory_reeds", inv_reeds);
saveData.set("inventory_logs", inv_logs);
saveData.set("inventory_clay", inv_clay);
saveData.set("inventory_small_game", inv_small_game);
@@ -234,11 +500,57 @@ bool save_game_state() {
saveData.set("inventory_knives", inv_knives);
saveData.set("inventory_fishing_poles", inv_fishing_poles);
saveData.set("inventory_slings", inv_slings);
saveData.set("inventory_small_game_types", inv_small_game_types);
saveData.set("inventory_ropes", inv_ropes);
saveData.set("inventory_reed_baskets", inv_reed_baskets);
saveData.set("inventory_clay_pots", inv_clay_pots);
saveData.set("inventory_skin_hats", inv_skin_hats);
saveData.set("inventory_skin_gloves", inv_skin_gloves);
saveData.set("inventory_skin_pants", inv_skin_pants);
saveData.set("inventory_skin_tunics", inv_skin_tunics);
saveData.set("inventory_moccasins", inv_moccasins);
saveData.set("inventory_skin_pouches", inv_skin_pouches);
saveData.set("inventory_small_game_types", join_string_array(inv_small_game_types));
saveData.set("storage_stones", storage_stones);
saveData.set("storage_sticks", storage_sticks);
saveData.set("storage_vines", storage_vines);
saveData.set("storage_reeds", storage_reeds);
saveData.set("storage_logs", storage_logs);
saveData.set("storage_clay", storage_clay);
saveData.set("storage_small_game", storage_small_game);
saveData.set("storage_meat", storage_meat);
saveData.set("storage_skins", storage_skins);
saveData.set("storage_spears", storage_spears);
saveData.set("storage_snares", storage_snares);
saveData.set("storage_axes", storage_axes);
saveData.set("storage_knives", storage_knives);
saveData.set("storage_fishing_poles", storage_fishing_poles);
saveData.set("storage_slings", storage_slings);
saveData.set("storage_ropes", storage_ropes);
saveData.set("storage_reed_baskets", storage_reed_baskets);
saveData.set("storage_clay_pots", storage_clay_pots);
saveData.set("storage_skin_hats", storage_skin_hats);
saveData.set("storage_skin_gloves", storage_skin_gloves);
saveData.set("storage_skin_pants", storage_skin_pants);
saveData.set("storage_skin_tunics", storage_skin_tunics);
saveData.set("storage_moccasins", storage_moccasins);
saveData.set("storage_skin_pouches", storage_skin_pouches);
saveData.set("storage_small_game_types", join_string_array(storage_small_game_types));
saveData.set("equipment_spear_equipped", spear_equipped);
saveData.set("equipment_axe_equipped", axe_equipped);
saveData.set("equipment_sling_equipped", sling_equipped);
saveData.set("equipment_head", equipped_head);
saveData.set("equipment_torso", equipped_torso);
saveData.set("equipment_arms", equipped_arms);
saveData.set("equipment_hands", equipped_hands);
saveData.set("equipment_legs", equipped_legs);
saveData.set("equipment_feet", equipped_feet);
string[] quickSlotData;
for (uint i = 0; i < quick_slots.length(); i++) {
quickSlotData.insert_last("" + quick_slots[i]);
}
saveData.set("equipment_quick_slots", join_string_array(quickSlotData));
saveData.set("time_current_hour", current_hour);
saveData.set("time_current_day", current_day);
@@ -248,49 +560,90 @@ bool save_game_state() {
saveData.set("time_area_expanded_today", area_expanded_today);
saveData.set("time_invasion_active", invasion_active);
saveData.set("time_invasion_start_hour", invasion_start_hour);
saveData.set("time_invasion_chance", invasion_chance);
saveData.set("time_invasion_triggered_today", invasion_triggered_today);
saveData.set("time_invasion_roll_done_today", invasion_roll_done_today);
saveData.set("time_invasion_scheduled_hour", invasion_scheduled_hour);
saveData.set("quest_roll_done_today", quest_roll_done_today);
string[] questData;
for (uint i = 0; i < quest_queue.length(); i++) {
questData.insert_last("" + quest_queue[i]);
}
saveData.set("quest_queue", join_string_array(questData));
saveData.set("world_map_size", MAP_SIZE);
saveData.set("world_expanded_area_start", expanded_area_start);
saveData.set("world_expanded_area_end", expanded_area_end);
saveData.set("world_barricade_initialized", barricade_initialized);
saveData.set("world_barricade_health", barricade_health);
saveData.set("world_expanded_terrain_types", expanded_terrain_types);
saveData.set("world_residents_count", residents_count);
saveData.set("world_expanded_terrain_types", join_string_array(expanded_terrain_types));
string[] treeData;
for (uint i = 0; i < trees.length(); i++) {
treeData.insert_last(serialize_tree(trees[i]));
}
saveData.set("trees_data", treeData);
saveData.set("trees_data", join_string_array(treeData));
string[] snareData;
for (uint i = 0; i < world_snares.length(); i++) {
snareData.insert_last(serialize_snare(world_snares[i]));
}
saveData.set("snares_data", snareData);
saveData.set("snares_data", join_string_array(snareData));
string[] fireData;
for (uint i = 0; i < world_fires.length(); i++) {
fireData.insert_last(serialize_fire(world_fires[i]));
}
saveData.set("fires_data", fireData);
saveData.set("fires_data", join_string_array(fireData));
string[] firepitPositions;
for (uint i = 0; i < world_firepits.length(); i++) {
firepitPositions.insert_last("" + world_firepits[i].position);
}
saveData.set("firepits_positions", firepitPositions);
saveData.set("firepits_positions", join_string_array(firepitPositions));
string[] herbPositions;
for (uint i = 0; i < world_herb_gardens.length(); i++) {
herbPositions.insert_last("" + world_herb_gardens[i].position);
}
saveData.set("herb_gardens_positions", herbPositions);
saveData.set("herb_gardens_positions", join_string_array(herbPositions));
string[] storagePositions;
for (uint i = 0; i < world_storages.length(); i++) {
storagePositions.insert_last("" + world_storages[i].position);
}
saveData.set("storages_positions", join_string_array(storagePositions));
string[] pasturePositions;
for (uint i = 0; i < world_pastures.length(); i++) {
pasturePositions.insert_last("" + world_pastures[i].position);
}
saveData.set("pastures_positions", join_string_array(pasturePositions));
string[] stablePositions;
for (uint i = 0; i < world_stables.length(); i++) {
stablePositions.insert_last("" + world_stables[i].position);
}
saveData.set("stables_positions", join_string_array(stablePositions));
string[] altarPositions;
for (uint i = 0; i < world_altars.length(); i++) {
altarPositions.insert_last("" + world_altars[i].position);
}
saveData.set("altars_positions", join_string_array(altarPositions));
string[] streamData;
for (uint i = 0; i < world_streams.length(); i++) {
streamData.insert_last(serialize_stream(world_streams[i]));
}
saveData.set("streams_data", streamData);
saveData.set("streams_data", join_string_array(streamData));
string[] banditData;
for (uint i = 0; i < bandits.length(); i++) {
banditData.insert_last(serialize_bandit(bandits[i]));
}
saveData.set("bandits_data", join_string_array(banditData));
string rawData = saveData.serialize();
string encryptedData = encrypt_save_data(rawData);
@@ -298,23 +651,38 @@ bool save_game_state() {
}
bool load_game_state() {
last_save_error = "";
if (!file_exists(SAVE_FILE_PATH)) {
last_save_error = "No save file found.";
return false;
}
string encryptedData;
if (!read_file_string(SAVE_FILE_PATH, encryptedData)) {
last_save_error = "Unable to read save file.";
return false;
}
string rawData = decrypt_save_data(encryptedData);
if (rawData.length() == 0) {
dictionary@ saveData = deserialize(rawData);
if (@saveData == null || !dictionary_has_keys(saveData)) {
saveData = deserialize(encryptedData);
}
if (@saveData == null || !dictionary_has_keys(saveData)) {
if (load_game_state_from_raw(rawData)) {
return true;
}
last_save_error = "Save file is corrupted or unreadable.";
return false;
}
dictionary@ saveData = deserialize(rawData);
if (@saveData == null) {
return false;
double version;
bool has_version = saveData.get("version", version);
if (!has_version) {
if (!has_number_key(saveData, "player_x") || !has_number_key(saveData, "player_health") || !has_number_key(saveData, "time_current_day")) {
last_save_error = "Save file is missing required data.";
return false;
}
version = 0;
}
reset_game_state();
@@ -323,7 +691,7 @@ bool load_game_state() {
expanded_area_start = int(get_number(saveData, "world_expanded_area_start", -1));
expanded_area_end = int(get_number(saveData, "world_expanded_area_end", -1));
string[] loadedTerrain = get_string_list(saveData, "world_expanded_terrain_types");
string[] loadedTerrain = get_string_list_or_split(saveData, "world_expanded_terrain_types");
expanded_terrain_types.resize(0);
for (uint i = 0; i < loadedTerrain.length(); i++) {
expanded_terrain_types.insert_last(loadedTerrain[i]);
@@ -331,6 +699,8 @@ bool load_game_state() {
barricade_initialized = get_bool(saveData, "world_barricade_initialized", true);
barricade_health = int(get_number(saveData, "world_barricade_health", BARRICADE_BASE_HEALTH));
residents_count = int(get_number(saveData, "world_residents_count", 0));
if (residents_count < 0) residents_count = 0;
if (!barricade_initialized) {
init_barricade();
} else {
@@ -343,6 +713,8 @@ bool load_game_state() {
facing = int(get_number(saveData, "player_facing", 1));
player_health = int(get_number(saveData, "player_health", 10));
max_health = int(get_number(saveData, "player_max_health", 10));
base_max_health = int(get_number(saveData, "player_base_health", max_health));
favor = get_number(saveData, "player_favor", 0.0);
if (x < 0) x = 0;
if (x >= MAP_SIZE) x = MAP_SIZE - 1;
@@ -352,6 +724,7 @@ bool load_game_state() {
inv_stones = int(get_number(saveData, "inventory_stones", 0));
inv_sticks = int(get_number(saveData, "inventory_sticks", 0));
inv_vines = int(get_number(saveData, "inventory_vines", 0));
inv_reeds = int(get_number(saveData, "inventory_reeds", 0));
inv_logs = int(get_number(saveData, "inventory_logs", 0));
inv_clay = int(get_number(saveData, "inventory_clay", 0));
inv_small_game = int(get_number(saveData, "inventory_small_game", 0));
@@ -363,8 +736,17 @@ bool load_game_state() {
inv_knives = int(get_number(saveData, "inventory_knives", 0));
inv_fishing_poles = int(get_number(saveData, "inventory_fishing_poles", 0));
inv_slings = int(get_number(saveData, "inventory_slings", 0));
inv_ropes = int(get_number(saveData, "inventory_ropes", 0));
inv_reed_baskets = int(get_number(saveData, "inventory_reed_baskets", 0));
inv_clay_pots = int(get_number(saveData, "inventory_clay_pots", 0));
inv_skin_hats = int(get_number(saveData, "inventory_skin_hats", 0));
inv_skin_gloves = int(get_number(saveData, "inventory_skin_gloves", 0));
inv_skin_pants = int(get_number(saveData, "inventory_skin_pants", 0));
inv_skin_tunics = int(get_number(saveData, "inventory_skin_tunics", 0));
inv_moccasins = int(get_number(saveData, "inventory_moccasins", 0));
inv_skin_pouches = int(get_number(saveData, "inventory_skin_pouches", 0));
string[] loadedSmallGameTypes = get_string_list(saveData, "inventory_small_game_types");
string[] loadedSmallGameTypes = get_string_list_or_split(saveData, "inventory_small_game_types");
inv_small_game_types.resize(0);
for (uint i = 0; i < loadedSmallGameTypes.length(); i++) {
inv_small_game_types.insert_last(loadedSmallGameTypes[i]);
@@ -378,9 +760,71 @@ bool load_game_state() {
inv_small_game = inv_small_game_types.length();
}
storage_stones = int(get_number(saveData, "storage_stones", 0));
storage_sticks = int(get_number(saveData, "storage_sticks", 0));
storage_vines = int(get_number(saveData, "storage_vines", 0));
storage_reeds = int(get_number(saveData, "storage_reeds", 0));
storage_logs = int(get_number(saveData, "storage_logs", 0));
storage_clay = int(get_number(saveData, "storage_clay", 0));
storage_small_game = int(get_number(saveData, "storage_small_game", 0));
storage_meat = int(get_number(saveData, "storage_meat", 0));
storage_skins = int(get_number(saveData, "storage_skins", 0));
storage_spears = int(get_number(saveData, "storage_spears", 0));
storage_snares = int(get_number(saveData, "storage_snares", 0));
storage_axes = int(get_number(saveData, "storage_axes", 0));
storage_knives = int(get_number(saveData, "storage_knives", 0));
storage_fishing_poles = int(get_number(saveData, "storage_fishing_poles", 0));
storage_slings = int(get_number(saveData, "storage_slings", 0));
storage_ropes = int(get_number(saveData, "storage_ropes", 0));
storage_reed_baskets = int(get_number(saveData, "storage_reed_baskets", 0));
storage_clay_pots = int(get_number(saveData, "storage_clay_pots", 0));
storage_skin_hats = int(get_number(saveData, "storage_skin_hats", 0));
storage_skin_gloves = int(get_number(saveData, "storage_skin_gloves", 0));
storage_skin_pants = int(get_number(saveData, "storage_skin_pants", 0));
storage_skin_tunics = int(get_number(saveData, "storage_skin_tunics", 0));
storage_moccasins = int(get_number(saveData, "storage_moccasins", 0));
storage_skin_pouches = int(get_number(saveData, "storage_skin_pouches", 0));
string[] loadedStorageSmallGameTypes = get_string_list_or_split(saveData, "storage_small_game_types");
storage_small_game_types.resize(0);
for (uint i = 0; i < loadedStorageSmallGameTypes.length(); i++) {
storage_small_game_types.insert_last(loadedStorageSmallGameTypes[i]);
}
if (storage_small_game_types.length() == 0 && storage_small_game > 0) {
for (int i = 0; i < storage_small_game; i++) {
storage_small_game_types.insert_last("small game");
}
} else {
storage_small_game = storage_small_game_types.length();
}
spear_equipped = get_bool(saveData, "equipment_spear_equipped", false);
axe_equipped = get_bool(saveData, "equipment_axe_equipped", false);
sling_equipped = get_bool(saveData, "equipment_sling_equipped", false);
equipped_head = int(get_number(saveData, "equipment_head", EQUIP_NONE));
equipped_torso = int(get_number(saveData, "equipment_torso", EQUIP_NONE));
equipped_arms = int(get_number(saveData, "equipment_arms", EQUIP_NONE));
equipped_hands = int(get_number(saveData, "equipment_hands", EQUIP_NONE));
equipped_legs = int(get_number(saveData, "equipment_legs", EQUIP_NONE));
equipped_feet = int(get_number(saveData, "equipment_feet", EQUIP_NONE));
if (equipped_head != EQUIP_HAT) equipped_head = EQUIP_NONE;
if (equipped_torso != EQUIP_TUNIC) equipped_torso = EQUIP_NONE;
if (equipped_hands != EQUIP_GLOVES) equipped_hands = EQUIP_NONE;
if (equipped_legs != EQUIP_PANTS) equipped_legs = EQUIP_NONE;
if (equipped_feet != EQUIP_MOCCASINS) equipped_feet = EQUIP_NONE;
if (equipped_arms != EQUIP_POUCH) equipped_arms = EQUIP_NONE;
if (equipped_arms == EQUIP_POUCH && inv_skin_pouches <= 0) equipped_arms = EQUIP_NONE;
reset_quick_slots();
string[] loadedQuickSlots = get_string_list_or_split(saveData, "equipment_quick_slots");
uint slot_count = loadedQuickSlots.length();
if (slot_count > quick_slots.length()) slot_count = quick_slots.length();
for (uint i = 0; i < slot_count; i++) {
int slot_value = parse_int(loadedQuickSlots[i]);
if (slot_value >= EQUIP_NONE && slot_value <= EQUIP_POUCH) {
quick_slots[i] = slot_value;
}
}
update_max_health_from_equipment();
current_hour = int(get_number(saveData, "time_current_hour", 8));
current_day = int(get_number(saveData, "time_current_day", 1));
@@ -389,6 +833,25 @@ bool load_game_state() {
area_expanded_today = get_bool(saveData, "time_area_expanded_today", false);
invasion_active = get_bool(saveData, "time_invasion_active", false);
invasion_start_hour = int(get_number(saveData, "time_invasion_start_hour", -1));
invasion_chance = int(get_number(saveData, "time_invasion_chance", 25));
invasion_triggered_today = get_bool(saveData, "time_invasion_triggered_today", false);
invasion_roll_done_today = get_bool(saveData, "time_invasion_roll_done_today", false);
invasion_scheduled_hour = int(get_number(saveData, "time_invasion_scheduled_hour", -1));
if (invasion_chance < 0) invasion_chance = 0;
if (invasion_chance > 100) invasion_chance = 100;
if (invasion_scheduled_hour < -1) invasion_scheduled_hour = -1;
if (invasion_scheduled_hour > 23) invasion_scheduled_hour = -1;
quest_roll_done_today = get_bool(saveData, "quest_roll_done_today", false);
quest_queue.resize(0);
string[] loadedQuests = get_string_list_or_split(saveData, "quest_queue");
for (uint i = 0; i < loadedQuests.length(); i++) {
int quest_type = parse_int(loadedQuests[i]);
if (quest_type >= 0 && quest_type < QUEST_TYPE_COUNT) {
quest_queue.insert_last(quest_type);
if (quest_queue.length() >= QUEST_MAX_ACTIVE) break;
}
}
if (current_hour < 0) current_hour = 0;
if (current_hour > 23) current_hour = 23;
@@ -397,7 +860,7 @@ bool load_game_state() {
is_daytime = (current_hour >= 6 && current_hour < 19);
hour_timer.restart();
string[] treeData = get_string_list(saveData, "trees_data");
string[] treeData = get_string_list_or_split(saveData, "trees_data");
for (uint i = 0; i < treeData.length(); i++) {
string[]@ parts = treeData[i].split("|");
if (parts.length() < 8) continue;
@@ -415,7 +878,7 @@ bool load_game_state() {
trees.insert_last(tree);
}
string[] snareData = get_string_list(saveData, "snares_data");
string[] snareData = get_string_list_or_split(saveData, "snares_data");
for (uint i = 0; i < snareData.length(); i++) {
string[]@ parts = snareData[i].split("|");
if (parts.length() < 6) continue;
@@ -431,7 +894,7 @@ bool load_game_state() {
world_snares.insert_last(snare);
}
string[] fireData = get_string_list(saveData, "fires_data");
string[] fireData = get_string_list_or_split(saveData, "fires_data");
for (uint i = 0; i < fireData.length(); i++) {
string[]@ parts = fireData[i].split("|");
if (parts.length() < 3) continue;
@@ -444,17 +907,37 @@ bool load_game_state() {
world_fires.insert_last(fire);
}
string[] firepitPositions = get_string_list(saveData, "firepits_positions");
string[] firepitPositions = get_string_list_or_split(saveData, "firepits_positions");
for (uint i = 0; i < firepitPositions.length(); i++) {
add_world_firepit(parse_int(firepitPositions[i]));
}
string[] herbPositions = get_string_list(saveData, "herb_gardens_positions");
string[] herbPositions = get_string_list_or_split(saveData, "herb_gardens_positions");
for (uint i = 0; i < herbPositions.length(); i++) {
add_world_herb_garden(parse_int(herbPositions[i]));
}
string[] streamData = get_string_list(saveData, "streams_data");
string[] storagePositions = get_string_list_or_split(saveData, "storages_positions");
for (uint i = 0; i < storagePositions.length(); i++) {
add_world_storage(parse_int(storagePositions[i]));
}
string[] pasturePositions = get_string_list_or_split(saveData, "pastures_positions");
for (uint i = 0; i < pasturePositions.length(); i++) {
add_world_pasture(parse_int(pasturePositions[i]));
}
string[] stablePositions = get_string_list_or_split(saveData, "stables_positions");
for (uint i = 0; i < stablePositions.length(); i++) {
add_world_stable(parse_int(stablePositions[i]));
}
string[] altarPositions = get_string_list_or_split(saveData, "altars_positions");
for (uint i = 0; i < altarPositions.length(); i++) {
add_world_altar(parse_int(altarPositions[i]));
}
string[] streamData = get_string_list_or_split(saveData, "streams_data");
for (uint i = 0; i < streamData.length(); i++) {
string[]@ parts = streamData[i].split("|");
if (parts.length() < 2) continue;
@@ -465,6 +948,40 @@ bool load_game_state() {
add_world_stream(startPos, width);
}
string[] banditData = get_string_list_or_split(saveData, "bandits_data");
for (uint i = 0; i < banditData.length(); i++) {
string[]@ parts = banditData[i].split("|");
if (parts.length() < 6) continue;
int pos = parse_int(parts[0]);
int health = parse_int(parts[1]);
string weapon = parts[2];
string state = parts[3];
int wander_dir = parse_int(parts[4]);
int move_int = parse_int(parts[5]);
// Create bandit with dummy expansion area (position will be overridden)
Bandit@ b = Bandit(pos, pos, pos);
b.position = pos;
b.health = health;
b.weapon_type = weapon;
b.behavior_state = state;
b.wander_direction = wander_dir;
b.move_interval = move_int;
b.wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
b.wander_direction_timer.restart();
b.move_timer.restart();
b.alert_timer.restart();
b.attack_timer.restart();
// Restore alert sound based on weapon type
int sound_index = random(0, bandit_sounds.length() - 1);
b.alert_sound = bandit_sounds[sound_index];
b.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
bandits.insert_last(b);
}
update_ambience(true);
return true;
}
+269 -45
View File
@@ -12,11 +12,23 @@ bool is_daytime = true;
bool sun_setting_warned = false;
bool sunrise_warned = false;
// Crossfade state
bool crossfade_active = false;
bool crossfade_to_night = false; // true = fading to night, false = fading to day
timer crossfade_timer;
const int CROSSFADE_DURATION = 60000; // 1 minute (1 game hour)
const float CROSSFADE_MIN_VOLUME = -40.0; // dB, effectively silent but not extreme
const float CROSSFADE_MAX_VOLUME = 0.0; // dB, full volume
// Expansion and invasion tracking
bool area_expanded_today = false;
bool invasion_active = false;
int invasion_start_hour = -1;
string[] expanded_terrain_types;
int invasion_chance = 25;
bool invasion_triggered_today = false;
bool invasion_roll_done_today = false;
int invasion_scheduled_hour = -1;
void init_time() {
current_hour = 8;
@@ -25,23 +37,29 @@ void init_time() {
is_daytime = true;
sun_setting_warned = false;
sunrise_warned = false;
crossfade_active = false;
crossfade_to_night = false;
area_expanded_today = false;
invasion_active = false;
invasion_start_hour = -1;
invasion_chance = 25;
invasion_triggered_today = false;
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
update_ambience(true); // Force start
}
void expand_area() {
if (expanded_area_start != -1) {
return; // Already expanded
}
// Play invasion sound
p.play_stationary("sounds/enemies/invasion.ogg", false);
// Calculate new area
expanded_area_start = MAP_SIZE;
expanded_area_end = MAP_SIZE + EXPANSION_SIZE - 1;
int new_start = MAP_SIZE;
int new_end = MAP_SIZE + EXPANSION_SIZE - 1;
if (expanded_area_start == -1) {
expanded_area_start = new_start;
}
expanded_area_end = new_end;
MAP_SIZE += EXPANSION_SIZE;
// Generate a single terrain type for the entire new area
@@ -55,9 +73,8 @@ void expand_area() {
terrain_type = "snow";
}
expanded_terrain_types.resize(EXPANSION_SIZE);
for (int i = 0; i < EXPANSION_SIZE; i++) {
expanded_terrain_types[i] = terrain_type;
expanded_terrain_types.insert_last(terrain_type);
}
// Place exactly one feature: either a stream or a tree
@@ -66,7 +83,7 @@ void expand_area() {
if (place_stream) {
int stream_width = random(1, 5);
int stream_start = random(0, EXPANSION_SIZE - stream_width);
int actual_start = expanded_area_start + stream_start;
int actual_start = new_start + stream_start;
add_world_stream(actual_start, stream_width);
string width_desc = "very small";
@@ -77,7 +94,7 @@ void expand_area() {
notify("A " + width_desc + " stream flows through the new area at x " + actual_start + ".");
} else {
int tree_pos = random(expanded_area_start, expanded_area_end);
int tree_pos = random(new_start, new_end);
Tree@ t = Tree(tree_pos);
trees.insert_last(t);
}
@@ -87,16 +104,97 @@ void expand_area() {
}
void start_invasion() {
expand_area();
invasion_active = true;
invasion_start_hour = current_hour;
notify("Bandits are invading from the new area!");
}
void update_invasion_chance_for_new_day() {
if (current_day == 2) {
invasion_chance = 100;
return;
}
if (current_day > 2) {
if (invasion_triggered_today) {
invasion_chance = 25;
} else {
invasion_chance += 25;
if (invasion_chance > 100) invasion_chance = 100;
}
}
}
int get_random_invasion_hour(int min_hour) {
if (min_hour < 6) min_hour = 6;
if (min_hour > 11) return -1;
return random(min_hour, 11);
}
void schedule_invasion() {
if (invasion_scheduled_hour != -1) return;
int hour = get_random_invasion_hour(current_hour);
if (hour == -1) return;
invasion_scheduled_hour = hour;
}
void check_scheduled_invasion() {
if (invasion_active || invasion_triggered_today) return;
if (invasion_scheduled_hour == -1) return;
if (current_hour == invasion_scheduled_hour) {
invasion_scheduled_hour = -1;
invasion_triggered_today = true;
start_invasion();
} else if (current_hour > 11) {
invasion_scheduled_hour = -1;
}
}
void attempt_daily_invasion() {
if (current_day < 2) return;
if (invasion_triggered_today || invasion_active) return;
if (invasion_roll_done_today) return;
invasion_roll_done_today = true;
int roll = random(1, 100);
if (roll > invasion_chance) return;
schedule_invasion();
check_scheduled_invasion();
}
void attempt_resident_recruitment() {
if (barricade_health <= 0) {
return;
}
int chance = random(25, 35);
int roll = random(1, 100);
if (roll > chance) {
return;
}
int added = random(1, 3);
residents_count += added;
string join_message = (added == 1) ? "A survivor joins your base." : "" + added + " survivors join your base.";
notify(join_message);
}
void end_invasion() {
invasion_active = false;
invasion_start_hour = -1;
clear_bandits();
transition_bandits_to_wandering();
notify("The bandit invasion has ended.");
attempt_resident_recruitment();
}
void transition_bandits_to_wandering() {
for (uint i = 0; i < bandits.length(); i++) {
bandits[i].behavior_state = "wandering";
bandits[i].wander_direction = random(-1, 1);
bandits[i].wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
bandits[i].wander_direction_timer.restart();
}
}
void check_invasion_status() {
@@ -114,18 +212,62 @@ void check_invasion_status() {
}
void manage_bandits_during_invasion() {
if (!invasion_active) return;
if (expanded_area_start == -1) return;
// Bandits only appear during daytime (6 AM to 7 PM)
// Clear ALL bandits at nighttime (undead eat them)
if (!is_daytime) {
clear_bandits();
return;
}
// Maintain BANDIT_MAX_COUNT bandits during invasion
while (bandits.length() < BANDIT_MAX_COUNT) {
spawn_bandit(expanded_area_start, expanded_area_end);
// During daytime: if invasion is active, maintain bandit count
if (invasion_active && expanded_area_start != -1) {
while (bandits.length() < BANDIT_MAX_COUNT) {
spawn_bandit(expanded_area_start, expanded_area_end);
}
}
// If invasion not active, wandering bandits persist during daytime
}
void update_blessings() {
if (blessing_speed_active && blessing_speed_timer.elapsed >= BLESSING_SPEED_DURATION) {
blessing_speed_active = false;
update_max_health_from_equipment();
screen_reader_speak("The speed blessing fades.", true);
}
}
void attempt_blessing() {
if (favor < 1.0) return;
int roll = random(1, 100);
if (roll > BLESSING_TRIGGER_CHANCE) return;
int[] options;
if (player_health < max_health) options.insert_last(0);
if (!blessing_speed_active) options.insert_last(1);
if (barricade_health < BARRICADE_MAX_HEALTH) options.insert_last(2);
if (options.length() == 0) return;
int choice = options[random(0, options.length() - 1)];
favor -= 1.0;
if (favor < 0) favor = 0;
if (choice == 0) {
int before = player_health;
player_health += BLESSING_HEAL_AMOUNT;
if (player_health > max_health) player_health = max_health;
int healed = player_health - before;
string bonus = (healed > 0) ? "You feel restored. +" + healed + " health." : "You feel restored.";
notify("The gods' favor shines upon you. " + bonus);
} else if (choice == 1) {
blessing_speed_active = true;
blessing_speed_timer.restart();
update_max_health_from_equipment();
notify("The gods' favor shines upon you. You feel swift for a while.");
} else if (choice == 2) {
int gained = add_barricade_health(BLESSING_BARRICADE_REPAIR);
string bonus = (gained > 0)
? "A divine force repairs the barricade. +" + gained + " health."
: "A divine force surrounds the barricade.";
notify("The gods' favor shines upon you. " + bonus);
}
}
@@ -137,6 +279,12 @@ void update_time() {
current_hour = 0;
current_day++;
area_expanded_today = false; // Reset for new day
update_invasion_chance_for_new_day();
invasion_triggered_today = false;
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
quest_roll_done_today = false;
consume_food_for_residents();
}
if (current_hour == 18 && !sun_setting_warned) {
@@ -152,24 +300,32 @@ void update_time() {
sunrise_warned = false;
}
// Check for area expansion (day 2+, daytime morning before noon, not yet expanded today)
if (current_day >= 2 && current_hour >= 6 && current_hour < 12 && !area_expanded_today && expanded_area_start == -1) {
int roll = random(1, 100);
if (roll <= EXPANSION_CHANCE) {
expand_area();
// Start invasion immediately after expansion (morning, during daytime)
start_invasion();
}
}
// Check invasion status
check_invasion_status();
check_ambience_transition();
// TODO: add resident defense using stored weapons once storage exists.
if (is_daytime && residents_count > 0 && barricade_health < BARRICADE_MAX_HEALTH && current_hour % 4 == 0) {
if (storage_meat > 0) {
int gained = add_barricade_health(residents_count);
if (gained > 0 && x <= BASE_END) {
screen_reader_speak("Residents repaired the barricade. +" + gained + " health.", true);
}
}
}
if (current_hour == 6) {
save_game_state();
}
if (current_hour == 6) {
attempt_daily_invasion();
attempt_daily_quest();
}
keep_base_fires_fed();
check_scheduled_invasion();
attempt_blessing();
}
// Manage bandits during active invasion
@@ -199,40 +355,108 @@ string get_time_string() {
}
void check_ambience_transition() {
// Definition of Day: 6 AM to 7 PM (19:00) ?
// Let's say Day is 6 (6AM) to 19 (7PM). Night is 20 (8PM) to 5 (5AM).
// Or simpler: 6 to 18 (6PM).
bool now_day = (current_hour >= 6 && current_hour < 19);
if (now_day != is_daytime) {
is_daytime = now_day;
update_ambience(false);
// Day is 6 (6AM) to 18 (6PM inclusive, so transition starts at hour 18)
// Night is 19 (7PM) to 5 (5AM inclusive, so transition starts at hour 5)
// Crossfade begins at hour 18 (sunset) and hour 5 (sunrise)
// Start crossfade to night at hour 18
if (current_hour == 18 && is_daytime && !crossfade_active) {
start_crossfade(true); // Fade to night
}
// Start crossfade to day at hour 5
else if (current_hour == 5 && !is_daytime && !crossfade_active) {
start_crossfade(false); // Fade to day
}
}
void start_crossfade(bool to_night) {
crossfade_active = true;
crossfade_to_night = to_night;
crossfade_timer.restart();
// Start the incoming sound at minimum volume (silent)
if (to_night) {
// Starting night sound
if (night_sound_handle == -1 || !p.sound_is_active(night_sound_handle)) {
night_sound_handle = p.play_stationary("sounds/nature/night.ogg", true);
}
p.update_sound_start_values(night_sound_handle, 0.0, CROSSFADE_MIN_VOLUME, 1.0);
} else {
// Starting day sound
if (day_sound_handle == -1 || !p.sound_is_active(day_sound_handle)) {
day_sound_handle = p.play_stationary("sounds/nature/day.ogg", true);
}
p.update_sound_start_values(day_sound_handle, 0.0, CROSSFADE_MIN_VOLUME, 1.0);
}
}
void update_crossfade() {
if (!crossfade_active) return;
float progress = float(crossfade_timer.elapsed) / float(CROSSFADE_DURATION);
if (progress > 1.0) progress = 1.0;
// Volume interpolation: fade out goes 0 -> -40, fade in goes -40 -> 0
float volume_range = CROSSFADE_MAX_VOLUME - CROSSFADE_MIN_VOLUME; // 40 dB range
float fade_out_vol = CROSSFADE_MAX_VOLUME - (volume_range * progress); // 0 -> -40
float fade_in_vol = CROSSFADE_MIN_VOLUME + (volume_range * progress); // -40 -> 0
if (crossfade_to_night) {
// Fading day out, night in
if (day_sound_handle != -1) p.update_sound_start_values(day_sound_handle, 0.0, fade_out_vol, 1.0);
if (night_sound_handle != -1) p.update_sound_start_values(night_sound_handle, 0.0, fade_in_vol, 1.0);
} else {
// Fading night out, day in
if (night_sound_handle != -1) p.update_sound_start_values(night_sound_handle, 0.0, fade_out_vol, 1.0);
if (day_sound_handle != -1) p.update_sound_start_values(day_sound_handle, 0.0, fade_in_vol, 1.0);
}
// Complete the crossfade
if (progress >= 1.0) {
complete_crossfade();
}
}
void complete_crossfade() {
crossfade_active = false;
if (crossfade_to_night) {
// Destroy day sound, ensure night is at full volume
if (day_sound_handle != -1) {
p.destroy_sound(day_sound_handle);
day_sound_handle = -1;
}
if (night_sound_handle != -1) p.update_sound_start_values(night_sound_handle, 0.0, 0.0, 1.0);
is_daytime = false;
} else {
// Destroy night sound, ensure day is at full volume
if (night_sound_handle != -1) {
p.destroy_sound(night_sound_handle);
night_sound_handle = -1;
}
if (day_sound_handle != -1) p.update_sound_start_values(day_sound_handle, 0.0, 0.0, 1.0);
is_daytime = true;
}
}
void update_ambience(bool force_restart) {
if (is_daytime) {
// Transition to Day
// Start day ambience
if (night_sound_handle != -1) {
p.destroy_sound(night_sound_handle);
night_sound_handle = -1;
}
if (day_sound_handle == -1 || !p.sound_is_active(day_sound_handle)) {
// Play looped, stationary (or relative to player x?)
// Usually ambience is 2D/Global. play_stationary_extended allows looping.
// Or play_1d at player position if we want panning?
// "sounds/nature/day.ogg"
day_sound_handle = p.play_stationary("sounds/nature/day.ogg", true);
day_sound_handle = p.play_stationary("sounds/nature/day.ogg", true);
}
} else {
// Transition to Night
// Start night ambience
if (day_sound_handle != -1) {
p.destroy_sound(day_sound_handle);
day_sound_handle = -1;
}
if (night_sound_handle == -1 || !p.sound_is_active(night_sound_handle)) {
night_sound_handle = p.play_stationary("sounds/nature/night.ogg", true);
night_sound_handle = p.play_stationary("sounds/nature/night.ogg", true);
}
}
}
+17
View File
@@ -0,0 +1,17 @@
// UI helpers
string ui_input_box(const string title, const string prompt, const string default_value) {
string result = virtual_input_box(title, prompt, default_value);
show_window("Draugnorak");
return result;
}
int ui_question(const string title, const string prompt) {
int result = virtual_question(title, prompt);
show_window("Draugnorak");
return result;
}
void ui_info_box(const string title, const string heading, const string message) {
virtual_info_box(title, heading, message);
show_window("Draugnorak");
}
+196 -35
View File
@@ -5,6 +5,7 @@ string[] small_game_types = {"rabbit", "squirrel", "raccoon", "opossum", "ground
int barricade_health = 0;
bool barricade_initialized = false;
int residents_count = 0;
string[] zombie_sounds = {"sounds/enemies/zombie1.ogg"};
string[] bandit_sounds = {"sounds/enemies/bandit1.ogg", "sounds/enemies/bandit2.ogg"};
@@ -13,6 +14,7 @@ class Zombie {
int position;
int health;
string voice_sound;
int sound_handle;
timer move_timer;
timer groan_timer;
timer attack_timer;
@@ -23,6 +25,7 @@ class Zombie {
health = ZOMBIE_HEALTH;
int sound_index = random(0, zombie_sounds.length() - 1);
voice_sound = zombie_sounds[sound_index];
sound_handle = -1;
move_timer.restart();
groan_timer.restart();
attack_timer.restart();
@@ -42,6 +45,12 @@ class Bandit {
int next_alert_delay;
int move_interval;
// Wandering behavior properties
string behavior_state; // "aggressive" or "wandering"
int wander_direction; // -1, 0, or 1
timer wander_direction_timer;
int wander_direction_change_interval;
Bandit(int pos, int expansion_start, int expansion_end) {
// Spawn somewhere in the expanded area
position = random(expansion_start, expansion_end);
@@ -61,6 +70,12 @@ class Bandit {
alert_timer.restart();
attack_timer.restart();
next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
// Initialize wandering behavior (start aggressive during invasion)
behavior_state = "aggressive";
wander_direction = 0;
wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
wander_direction_timer.restart();
}
}
Bandit@[] bandits;
@@ -122,10 +137,9 @@ class WorldSnare {
int roll = random(1, 100);
if (roll <= escape_chance) {
// Animal escaped!
has_catch = false;
notify("A " + catch_type + " escaped from your snare at " + position + "!");
catch_type = "";
catch_chance = 5;
notify("A " + catch_type + " escaped from your snare at x " + position + " y 0!");
remove_snare_at(position);
return;
}
} else {
// Trying to catch small game
@@ -243,6 +257,42 @@ class WorldHerbGarden {
}
WorldHerbGarden@[] world_herb_gardens;
class WorldStorage {
int position;
WorldStorage(int pos) {
position = pos;
}
}
WorldStorage@[] world_storages;
class WorldPasture {
int position;
WorldPasture(int pos) {
position = pos;
}
}
WorldPasture@[] world_pastures;
class WorldStable {
int position;
WorldStable(int pos) {
position = pos;
}
}
WorldStable@[] world_stables;
class WorldAltar {
int position;
WorldAltar(int pos) {
position = pos;
}
}
WorldAltar@[] world_altars;
class WorldStream {
int start_position;
int end_position;
@@ -269,10 +319,14 @@ class WorldStream {
void update() {
int center = get_center_position();
// Play stream sound within 3 tiles distance from center
if (abs(x - center) <= 3) {
// Play stream sound within range of center
if (abs(x - center) <= STREAM_SOUND_RANGE) {
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
sound_handle = p.play_1d("sounds/terrain/stream.ogg", x, center, true);
if (sound_handle != -1) {
p.update_sound_positioning_values(sound_handle, -1.0, STREAM_SOUND_VOLUME_STEP, true);
p.update_sound_range_1d(sound_handle, STREAM_SOUND_RANGE, STREAM_SOUND_RANGE);
}
}
} else {
if (sound_handle != -1) {
@@ -306,6 +360,62 @@ void add_world_firepit(int pos) {
world_firepits.insert_last(fp);
}
void add_world_storage(int pos) {
WorldStorage@ s = WorldStorage(pos);
world_storages.insert_last(s);
}
void add_world_pasture(int pos) {
WorldPasture@ p = WorldPasture(pos);
world_pastures.insert_last(p);
}
void add_world_stable(int pos) {
WorldStable@ s = WorldStable(pos);
world_stables.insert_last(s);
}
void add_world_altar(int pos) {
WorldAltar@ a = WorldAltar(pos);
world_altars.insert_last(a);
}
WorldStorage@ get_storage_at(int pos) {
for (uint i = 0; i < world_storages.length(); i++) {
if (world_storages[i].position == pos) {
return @world_storages[i];
}
}
return null;
}
WorldPasture@ get_pasture_at(int pos) {
for (uint i = 0; i < world_pastures.length(); i++) {
if (world_pastures[i].position == pos) {
return @world_pastures[i];
}
}
return null;
}
WorldStable@ get_stable_at(int pos) {
for (uint i = 0; i < world_stables.length(); i++) {
if (world_stables[i].position == pos) {
return @world_stables[i];
}
}
return null;
}
WorldAltar@ get_altar_at(int pos) {
for (uint i = 0; i < world_altars.length(); i++) {
if (world_altars[i].position == pos) {
return @world_altars[i];
}
}
return null;
}
void update_world_objects() {
for (uint i = 0; i < world_snares.length(); i++) {
world_snares[i].update();
@@ -352,7 +462,7 @@ void check_snare_collision(int player_x) {
}
void update_snares() {
for (uint i = 0; i < world_snares.length(); i++) {
for (int i = int(world_snares.length()) - 1; i >= 0; i--) {
world_snares[i].update();
}
}
@@ -461,6 +571,12 @@ int add_barricade_health(int amount) {
void clear_zombies() {
if (zombies.length() == 0) return;
for (uint i = 0; i < zombies.length(); i++) {
if (zombies[i].sound_handle != -1) {
p.destroy_sound(zombies[i].sound_handle);
zombies[i].sound_handle = -1;
}
}
zombies.resize(0);
}
@@ -488,7 +604,7 @@ void spawn_zombie() {
Zombie@ z = Zombie(spawn_x);
zombies.insert_last(z);
play_1d_with_volume_step(z.voice_sound, x, spawn_x, false, ZOMBIE_SOUND_VOLUME_STEP);
z.sound_handle = play_1d_with_volume_step(z.voice_sound, x, spawn_x, false, ZOMBIE_SOUND_VOLUME_STEP);
}
void try_attack_barricade(Zombie@ zombie) {
@@ -546,7 +662,7 @@ void update_zombie(Zombie@ zombie) {
if (zombie.groan_timer.elapsed > zombie.next_groan_delay) {
zombie.groan_timer.restart();
zombie.next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY);
play_1d_with_volume_step(zombie.voice_sound, x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP);
zombie.sound_handle = play_1d_with_volume_step(zombie.voice_sound, x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP);
}
if (try_attack_player(zombie)) {
@@ -607,9 +723,12 @@ bool damage_zombie_at(int pos, int damage) {
if (zombies[i].position == pos) {
zombies[i].health -= damage;
if (zombies[i].health <= 0) {
if (zombies[i].sound_handle != -1) {
p.destroy_sound(zombies[i].sound_handle);
zombies[i].sound_handle = -1;
}
play_1d_with_volume_step("sounds/enemies/enemy_falls.ogg", x, pos, false, ZOMBIE_SOUND_VOLUME_STEP);
zombies.remove_at(i);
} else {
}
return true;
}
@@ -766,37 +885,79 @@ void update_bandit(Bandit@ bandit) {
return;
}
// Move toward player
int direction = 0;
if (x > BASE_END) {
// Player is outside base, move toward them
if (x > bandit.position) {
direction = 1;
} else if (x < bandit.position) {
direction = -1;
// State-based behavior
if (bandit.behavior_state == "wandering") {
// Check if player is within detection radius
int distance = abs(bandit.position - x);
if (distance <= BANDIT_DETECTION_RADIUS) {
// Player detected! Switch to aggressive
bandit.behavior_state = "aggressive";
} else {
// Continue wandering
if (bandit.wander_direction_timer.elapsed > bandit.wander_direction_change_interval) {
// Time to change direction
bandit.wander_direction = random(-1, 1);
bandit.wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
bandit.wander_direction_timer.restart();
}
// Move in wander direction (if not 0)
if (bandit.wander_direction != 0) {
int target_x = bandit.position + bandit.wander_direction;
// Check bounds
if (target_x >= 0 && target_x < MAP_SIZE) {
// Don't wander into base if barricade is up
if (target_x <= BASE_END && barricade_health > 0) {
// Change direction instead
bandit.wander_direction = -bandit.wander_direction;
} else {
bandit.position = target_x;
play_positional_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
}
} else {
// Hit map boundary, reverse direction
bandit.wander_direction = -bandit.wander_direction;
}
}
return;
}
} else {
// Player is in base, move toward base edge
if (bandit.position > BASE_END + 1) {
direction = -1;
}
// Aggressive behavior (original logic)
if (bandit.behavior_state == "aggressive") {
// Move toward player
int direction = 0;
if (x > BASE_END) {
// Player is outside base, move toward them
if (x > bandit.position) {
direction = 1;
} else if (x < bandit.position) {
direction = -1;
} else {
return;
}
} else {
return; // Already at base edge
// Player is in base, move toward base edge
if (bandit.position > BASE_END + 1) {
direction = -1;
} else {
return; // Already at base edge
}
}
int target_x = bandit.position + direction;
if (target_x < 0 || target_x >= MAP_SIZE) return;
// Don't enter base if barricade is up
if (target_x <= BASE_END && barricade_health > 0) {
try_attack_barricade_bandit(bandit);
return;
}
bandit.position = target_x;
play_positional_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
}
int target_x = bandit.position + direction;
if (target_x < 0 || target_x >= MAP_SIZE) return;
// Don't enter base if barricade is up
if (target_x <= BASE_END && barricade_health > 0) {
try_attack_barricade_bandit(bandit);
return;
}
bandit.position = target_x;
play_positional_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
}
void update_bandits() {