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
+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() {