Item breaking now happens for players too. Bandit adventured tweaked a bit. Rune of destruction added. Can be put anywhere only affective on weapons.

This commit is contained in:
Storm Dragon
2026-02-04 20:07:32 -05:00
parent 78a6156656
commit c6db6d49de
18 changed files with 488 additions and 69 deletions
Binary file not shown.
Binary file not shown.
+95 -25
View File
@@ -202,17 +202,70 @@ const int RESIDENT_WEAPON_SPEAR = 0;
const int RESIDENT_WEAPON_SLING = 1;
const int RESIDENT_WEAPON_BOW = 2;
int get_stored_runed_weapon_count(int equipType) {
int total = 0;
int[] runeTypes;
get_all_rune_types(runeTypes);
for (uint i = 0; i < runeTypes.length(); i++) {
total += get_stored_runed_item_count(equipType, runeTypes[i]);
}
return total;
}
int get_total_stored_weapon_count(int equipType, int itemType) {
return get_storage_count(itemType) + get_stored_runed_weapon_count(equipType);
}
bool remove_random_stored_runed_weapon(int equipType) {
int[] runeTypes;
get_all_rune_types(runeTypes);
int total = 0;
for (uint i = 0; i < runeTypes.length(); i++) {
total += get_stored_runed_item_count(equipType, runeTypes[i]);
}
if (total <= 0) return false;
int roll = random(1, total);
int running = 0;
for (uint i = 0; i < runeTypes.length(); i++) {
int count = get_stored_runed_item_count(equipType, runeTypes[i]);
if (count <= 0) continue;
running += count;
if (roll <= running) {
remove_stored_runed_item(equipType, runeTypes[i]);
return true;
}
}
return false;
}
bool remove_random_stored_weapon(int equipType, int itemType) {
int unrunedCount = get_storage_count(itemType);
int runedCount = get_stored_runed_weapon_count(equipType);
int total = unrunedCount + runedCount;
if (total <= 0) return false;
int roll = random(1, total);
if (roll <= unrunedCount) {
if (unrunedCount > 0) add_storage_count(itemType, -1);
return true;
}
return remove_random_stored_runed_weapon(equipType);
}
int get_available_defense_weapons() {
int count = get_storage_count(ITEM_SPEARS);
int spearCount = get_total_stored_weapon_count(EQUIP_SPEAR, ITEM_SPEARS);
int slingCount = 0;
int bowCount = 0;
// Slings only count if stones are available
if (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) {
count += get_storage_count(ITEM_SLINGS);
if (get_storage_count(ITEM_STONES) > 0) {
slingCount = get_total_stored_weapon_count(EQUIP_SLING, ITEM_SLINGS);
}
// Bows only count if arrows are available
if (get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0) {
count += get_storage_count(ITEM_BOWS);
if (get_storage_count(ITEM_ARROWS) > 0) {
bowCount = get_total_stored_weapon_count(EQUIP_BOW, ITEM_BOWS);
}
return count;
return spearCount + slingCount + bowCount;
}
bool can_residents_defend() {
@@ -222,13 +275,15 @@ bool can_residents_defend() {
int choose_defense_weapon_type() {
// Prefer bows if available
int bowCount = (get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0)
? get_storage_count(ITEM_BOWS)
int bowCount = (get_storage_count(ITEM_ARROWS) > 0)
? get_total_stored_weapon_count(EQUIP_BOW, ITEM_BOWS)
: 0;
if (bowCount > 0) return RESIDENT_WEAPON_BOW;
int spearCount = get_storage_count(ITEM_SPEARS);
int slingCount = (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) ? get_storage_count(ITEM_SLINGS) : 0;
int spearCount = get_total_stored_weapon_count(EQUIP_SPEAR, ITEM_SPEARS);
int slingCount = (get_storage_count(ITEM_STONES) > 0)
? get_total_stored_weapon_count(EQUIP_SLING, ITEM_SLINGS)
: 0;
int total = spearCount + slingCount;
if (total == 0) return RESIDENT_WEAPON_SPEAR;
@@ -244,18 +299,25 @@ int perform_resident_defense() {
// Choose weapon type (bows preferred, otherwise weighted by availability)
int weapon_type = choose_defense_weapon_type();
int bowCount = (get_storage_count(ITEM_ARROWS) > 0)
? get_total_stored_weapon_count(EQUIP_BOW, ITEM_BOWS)
: 0;
int spearCount = get_total_stored_weapon_count(EQUIP_SPEAR, ITEM_SPEARS);
int slingCount = (get_storage_count(ITEM_STONES) > 0)
? get_total_stored_weapon_count(EQUIP_SLING, ITEM_SLINGS)
: 0;
int damage = 0;
if (weapon_type == RESIDENT_WEAPON_BOW && get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0) {
if (weapon_type == RESIDENT_WEAPON_BOW && bowCount > 0) {
damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
add_storage_count(ITEM_ARROWS, -1);
play_1d_with_volume_step("sounds/weapons/bow_fire.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
} else if (weapon_type == RESIDENT_WEAPON_SPEAR && get_storage_count(ITEM_SPEARS) > 0) {
} else if (weapon_type == RESIDENT_WEAPON_SPEAR && spearCount > 0) {
damage = apply_resident_damage_bonus(RESIDENT_SPEAR_DAMAGE);
// Weapons don't get consumed on use - they break via daily breakage check
// Just play the sound
play_1d_with_volume_step("sounds/weapons/spear_swing.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
} else if (weapon_type == RESIDENT_WEAPON_SLING && get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) {
} else if (weapon_type == RESIDENT_WEAPON_SLING && slingCount > 0) {
damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
// Slings use stones as ammo, so consume a stone
add_storage_count(ITEM_STONES, -1);
@@ -271,8 +333,10 @@ timer resident_ranged_timer;
void attempt_resident_ranged_defense() {
// Only if residents exist and have ranged weapons
if (residents_count <= 0) return;
bool has_bow = (get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0);
bool has_sling = (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0);
int bowCount = get_total_stored_weapon_count(EQUIP_BOW, ITEM_BOWS);
int slingCount = get_total_stored_weapon_count(EQUIP_SLING, ITEM_SLINGS);
bool has_bow = (bowCount > 0 && get_storage_count(ITEM_ARROWS) > 0);
bool has_sling = (slingCount > 0 && get_storage_count(ITEM_STONES) > 0);
if (!has_bow && !has_sling) return;
// Cooldown between shots
@@ -338,7 +402,10 @@ void attempt_resident_ranged_defense() {
void process_daily_weapon_breakage() {
if (residents_count <= 0) return;
int totalWeapons = get_storage_count(ITEM_SPEARS) + get_storage_count(ITEM_SLINGS) + get_storage_count(ITEM_BOWS);
int spearTotal = get_total_stored_weapon_count(EQUIP_SPEAR, ITEM_SPEARS);
int slingTotal = get_total_stored_weapon_count(EQUIP_SLING, ITEM_SLINGS);
int bowTotal = get_total_stored_weapon_count(EQUIP_BOW, ITEM_BOWS);
int totalWeapons = spearTotal + slingTotal + bowTotal;
if (totalWeapons == 0) return;
// Number of breakage checks = min(residents, weapons)
@@ -350,9 +417,9 @@ void process_daily_weapon_breakage() {
int bowChecks = 0;
for (int i = 0; i < checksToPerform; i++) {
int remainingSpears = get_storage_count(ITEM_SPEARS) - spearChecks;
int remainingSlings = get_storage_count(ITEM_SLINGS) - slingChecks;
int remainingBows = get_storage_count(ITEM_BOWS) - bowChecks;
int remainingSpears = spearTotal - spearChecks;
int remainingSlings = slingTotal - slingChecks;
int remainingBows = bowTotal - bowChecks;
int remaining = remainingSpears + remainingSlings + remainingBows;
if (remaining <= 0) break;
@@ -393,8 +460,9 @@ void process_daily_weapon_breakage() {
// Apply breakage
if (spearsBroken > 0) {
add_storage_count(ITEM_SPEARS, -spearsBroken);
if (get_storage_count(ITEM_SPEARS) < 0) set_storage_count(ITEM_SPEARS, 0);
for (int i = 0; i < spearsBroken; i++) {
remove_random_stored_weapon(EQUIP_SPEAR, ITEM_SPEARS);
}
string msg = (spearsBroken == 1)
? "A resident's spear broke from wear."
: spearsBroken + " spears broke from wear.";
@@ -402,8 +470,9 @@ void process_daily_weapon_breakage() {
}
if (slingsBroken > 0) {
add_storage_count(ITEM_SLINGS, -slingsBroken);
if (get_storage_count(ITEM_SLINGS) < 0) set_storage_count(ITEM_SLINGS, 0);
for (int i = 0; i < slingsBroken; i++) {
remove_random_stored_weapon(EQUIP_SLING, ITEM_SLINGS);
}
string msg = (slingsBroken == 1)
? "A resident's sling broke from wear."
: slingsBroken + " slings broke from wear.";
@@ -411,8 +480,9 @@ void process_daily_weapon_breakage() {
}
if (bowsBroken > 0) {
add_storage_count(ITEM_BOWS, -bowsBroken);
if (get_storage_count(ITEM_BOWS) < 0) set_storage_count(ITEM_BOWS, 0);
for (int i = 0; i < bowsBroken; i++) {
remove_random_stored_weapon(EQUIP_BOW, ITEM_BOWS);
}
string msg = (bowsBroken == 1)
? "A resident's bow broke from wear."
: bowsBroken + " bows broke from wear.";
+2
View File
@@ -89,6 +89,7 @@ void adventure_release_bow_attack(int player_x, int player_facing, AdventureRang
}
int damage = get_bow_draw_damage(bow_draw_timer.elapsed);
damage = apply_weapon_rune_damage(damage);
add_personal_count(ITEM_ARROWS, -1);
p.play_stationary("sounds/weapons/bow_fire.ogg", false);
@@ -130,6 +131,7 @@ void adventure_release_sling_attack(int player_x, int player_facing, AdventureRa
int search_direction = (player_facing == 1) ? 1 : -1;
int target_x = -1;
int damage = random(SLING_DAMAGE_MIN, SLING_DAMAGE_MAX);
damage = apply_weapon_rune_damage(damage);
if (@ranged_callback !is null) {
target_x = ranged_callback(player_x, search_direction, SLING_RANGE, ADVENTURE_WEAPON_SLING, damage);
+27 -3
View File
@@ -272,7 +272,7 @@ void run_bandit_hideout_adventure() {
if (hideoutBarricadeHealth <= 0) {
cleanup_bandit_hideout_adventure();
p.play_stationary("sounds/actions/break_snare.ogg", false);
p.play_stationary("sounds/bosses/bandit/base_destroyed.ogg", false);
give_bandit_hideout_rewards();
return;
}
@@ -446,7 +446,8 @@ void play_hideout_melee_hit(int weaponType) {
bool hideout_melee_hit(int weaponType) {
int range = (weaponType == ADVENTURE_WEAPON_SPEAR) ? 1 : 0;
int damage = (weaponType == ADVENTURE_WEAPON_SPEAR) ? SPEAR_DAMAGE : AXE_DAMAGE;
int baseDamage = (weaponType == ADVENTURE_WEAPON_SPEAR) ? SPEAR_DAMAGE : AXE_DAMAGE;
int damage = apply_weapon_rune_damage(baseDamage);
for (int offset = -range; offset <= range; offset++) {
int targetX = hideoutPlayerX + offset;
@@ -653,7 +654,7 @@ void give_bandit_hideout_rewards() {
rewards.insert_last("Storage rewards:");
bool anyItems = false;
for (int itemType = 0; itemType < ITEM_COUNT; itemType++) {
int roll = random(0, 9);
int roll = random(0, 5);
if (roll <= 0) continue;
int addedAmount = add_hideout_storage_item(itemType, roll);
if (addedAmount <= 0) continue;
@@ -665,5 +666,28 @@ void give_bandit_hideout_rewards() {
}
}
bool newRune = !rune_destruction_unlocked;
rune_destruction_unlocked = true;
rewards.insert_last("");
if (newRune) {
rewards.insert_last("Learned Rune of Destruction!");
rewards.insert_last("You can now engrave weapons with this rune at the crafting menu.");
} else {
rewards.insert_last("You have already mastered the Rune of Destruction.");
}
if (residents_count < MAX_RESIDENTS) {
int survivorRoll = random(1, 100);
if (survivorRoll <= 50) {
residents_count += 1;
if (residents_count > MAX_RESIDENTS) residents_count = MAX_RESIDENTS;
rewards.insert_last("");
rewards.insert_last("A survivor joins your base.");
} else {
rewards.insert_last("");
rewards.insert_last("No survivors were found.");
}
}
text_reader_lines(rewards, "Bandit's Hideout", true);
}
+4 -2
View File
@@ -372,7 +372,8 @@ bool unicorn_melee_hit(int weapon_type) {
if (target_support != -1 && bridge_supports_health[target_support] > 0) {
if (weapon_type == ADVENTURE_WEAPON_AXE) {
bridge_supports_health[target_support] -= AXE_DAMAGE;
int damage = apply_weapon_rune_damage(AXE_DAMAGE);
bridge_supports_health[target_support] -= damage;
if (bridge_supports_health[target_support] <= 0) {
check_bridge_collapse();
}
@@ -381,7 +382,8 @@ bool unicorn_melee_hit(int weapon_type) {
}
if (abs(player_arena_x - unicorn.x) <= 1) {
int damage = (weapon_type == ADVENTURE_WEAPON_SPEAR) ? SPEAR_DAMAGE : AXE_DAMAGE;
int baseDamage = (weapon_type == ADVENTURE_WEAPON_SPEAR) ? SPEAR_DAMAGE : AXE_DAMAGE;
int damage = apply_weapon_rune_damage(baseDamage);
apply_unicorn_damage(damage);
return true;
}
+7 -3
View File
@@ -102,7 +102,8 @@ bool attack_enemy(int target_x, int damage) {
void perform_spear_attack(int current_x) {
p.play_stationary("sounds/weapons/spear_swing.ogg", false);
int hit_pos = attack_enemy_ranged(current_x - 1, current_x + 1, SPEAR_DAMAGE);
int damage = apply_weapon_rune_damage(SPEAR_DAMAGE);
int hit_pos = attack_enemy_ranged(current_x - 1, current_x + 1, damage);
if (hit_pos != -1) {
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
// Play hit sound based on enemy type (both use same hit sound for now)
@@ -123,7 +124,8 @@ void perform_spear_attack(int current_x) {
void perform_axe_attack(int current_x) {
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
if (attack_enemy(current_x, AXE_DAMAGE)) {
int damage = apply_weapon_rune_damage(AXE_DAMAGE);
if (attack_enemy(current_x, damage)) {
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
// Play hit sound based on enemy type
if (get_bandit_at(current_x) != null) {
@@ -140,7 +142,7 @@ void perform_axe_attack(int current_x) {
// Range: current_x (0 distance)
// Damage: 4
// Target: Trees
damage_tree(current_x, 4);
damage_tree(current_x, damage);
}
void perform_sling_attack(int current_x) {
@@ -273,6 +275,7 @@ void release_bow_attack(int player_x) {
}
int damage = get_bow_draw_damage(bow_draw_timer.elapsed);
damage = apply_weapon_rune_damage(damage);
add_personal_count(ITEM_ARROWS, -1);
p.play_stationary("sounds/weapons/bow_fire.ogg", false);
@@ -379,6 +382,7 @@ void release_sling_attack(int player_x) {
}
int damage = random(SLING_DAMAGE_MIN, SLING_DAMAGE_MAX);
damage = apply_weapon_rune_damage(damage);
// Damage the correct enemy type
if (hit_bandit) {
+4
View File
@@ -204,6 +204,10 @@ const int RESIDENT_SNARE_CHECK_CHANCE = 15; // 15% chance per hour to check sna
const int RESIDENT_FISHING_CHANCE = 6; // 6% chance per resident per hour to catch a fish
const int RESIDENT_SMOKE_FISH_CHANCE = 10; // 10% chance per hour to smoke a stored fish
const int RESIDENT_TOOL_BREAK_CHANCE = 2; // 2% chance tools break during resident use (fishing poles, knives, baskets)
const int PLAYER_ITEM_BREAK_CHANCE_MIN = 1;
const int PLAYER_ITEM_BREAK_CHANCE_MAX = 100;
const int PLAYER_ITEM_BREAK_CHANCE_INCREMENT = 1;
const int PLAYER_ITEM_BREAKS_PER_DAY = 2;
// Goose settings
const int GOOSE_HEALTH = 1;
+17 -5
View File
@@ -3,7 +3,11 @@
// Get total pouches available (unruned + runed)
int get_total_pouch_count() {
int total = get_personal_count(ITEM_SKIN_POUCHES);
total += get_runed_item_count(EQUIP_POUCH, RUNE_SWIFTNESS);
int[] runeTypes;
get_all_rune_types(runeTypes);
for (uint i = 0; i < runeTypes.length(); i++) {
total += get_runed_item_count(EQUIP_POUCH, runeTypes[i]);
}
return total;
}
@@ -21,10 +25,18 @@ void consume_pouches(int amount) {
// Then consume runed pouches if needed
while (remaining > 0) {
if (get_runed_item_count(EQUIP_POUCH, RUNE_SWIFTNESS) > 0) {
remove_runed_item(EQUIP_POUCH, RUNE_SWIFTNESS);
remaining--;
} else {
bool removed = false;
int[] runeTypes;
get_all_rune_types(runeTypes);
for (uint i = 0; i < runeTypes.length(); i++) {
if (get_runed_item_count(EQUIP_POUCH, runeTypes[i]) > 0) {
remove_runed_item(EQUIP_POUCH, runeTypes[i]);
remaining--;
removed = true;
break;
}
}
if (!removed) {
break;
}
}
+8 -5
View File
@@ -64,10 +64,13 @@ void run_runes_menu() {
// Build list of unlocked runes
string[] rune_options;
int[] rune_types;
if (rune_swiftness_unlocked) {
rune_options.insert_last("Rune of Swiftness (1 Clay, 1 Favor) [Requires Knife]");
rune_types.insert_last(RUNE_SWIFTNESS);
int[] unlocked_runes;
get_unlocked_rune_types(unlocked_runes);
for (uint i = 0; i < unlocked_runes.length(); i++) {
int rune_type = unlocked_runes[i];
string label = get_rune_name(rune_type) + " (1 Clay, 1 Favor) [Requires Knife]";
rune_options.insert_last(label);
rune_types.insert_last(rune_type);
}
if (rune_options.length() == 0) {
@@ -112,7 +115,7 @@ void run_rune_equipment_menu(int rune_type) {
string[] equipment_options;
int[] equipment_types;
int[] runeable = get_runeable_equipment_types();
int[] runeable = get_runeable_equipment_types_for_rune(rune_type);
for (uint i = 0; i < runeable.length(); i++) {
int equip_type = runeable[i];
int unruned_count = get_unruned_equipment_count(equip_type);
+92 -3
View File
@@ -261,6 +261,95 @@ string get_item_count_binding_name(int item_type) {
return get_item_display_name(item_type);
}
string get_runed_item_display_name(int equip_type, int rune_type) {
return "Runed " + get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type);
}
string get_equipment_display_name_with_rune(int equip_type, int rune_type) {
if (rune_type != RUNE_NONE) {
return get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type);
}
return get_equipment_name(equip_type);
}
bool is_breakable_item_type(int itemType) {
if (is_runed_item_type(itemType)) return true;
if (itemType == ITEM_SPEARS) return true;
if (itemType == ITEM_SLINGS) return true;
if (itemType == ITEM_AXES) return true;
if (itemType == ITEM_SNARES) return true;
if (itemType == ITEM_KNIVES) return true;
if (itemType == ITEM_FISHING_POLES) return true;
if (itemType == ITEM_SKIN_HATS) return true;
if (itemType == ITEM_SKIN_GLOVES) return true;
if (itemType == ITEM_SKIN_PANTS) return true;
if (itemType == ITEM_SKIN_TUNICS) return true;
if (itemType == ITEM_MOCCASINS) return true;
if (itemType == ITEM_SKIN_POUCHES) return true;
if (itemType == ITEM_ROPES) return true;
if (itemType == ITEM_REED_BASKETS) return true;
if (itemType == ITEM_CLAY_POTS) return true;
if (itemType == ITEM_BOWS) return true;
if (itemType == ITEM_QUIVERS) return true;
if (itemType == ITEM_BACKPACKS) return true;
if (itemType == ITEM_CANOES) return true;
return false;
}
string get_breakable_item_name(int itemType) {
if (is_runed_item_type(itemType)) {
int equipType = 0;
int runeType = 0;
decode_runed_item_type(itemType, equipType, runeType);
return "Runed " + get_base_equipment_name(equipType) + " of " + get_rune_effect_name(runeType);
}
return get_item_label_singular(itemType);
}
void get_breakable_personal_item_types(int[]@ items) {
if (@items == null) return;
items.resize(0);
for (int itemType = 0; itemType < ITEM_COUNT; itemType++) {
if (!is_breakable_item_type(itemType)) continue;
if (get_personal_count(itemType) <= 0) continue;
items.insert_last(itemType);
}
int[] runeable = get_runeable_equipment_types();
for (uint i = 0; i < runeable.length(); i++) {
int equipType = runeable[i];
int[] runeTypes;
get_all_rune_types(runeTypes);
for (uint j = 0; j < runeTypes.length(); j++) {
int runeType = runeTypes[j];
int runedCount = get_runed_item_count(equipType, runeType);
if (runedCount <= 0) continue;
int encoded = encode_runed_item_type(equipType, runeType);
items.insert_last(encoded);
}
}
}
bool remove_breakable_personal_item(int itemType) {
if (is_runed_item_type(itemType)) {
int equipType = 0;
int runeType = 0;
decode_runed_item_type(itemType, equipType, runeType);
int current = get_runed_item_count(equipType, runeType);
if (current <= 0) return false;
remove_runed_item(equipType, runeType);
if (get_runed_item_count(equipType, runeType) <= 0 && get_equipped_rune_for_slot(equipType) == runeType) {
clear_equipped_rune_for_slot(equipType);
}
return true;
}
if (!is_breakable_item_type(itemType)) return false;
if (get_personal_count(itemType) <= 0) return false;
add_personal_count(itemType, -1);
return true;
}
bool try_consume_heal_scroll() {
if (player_health > 0) return false;
if (get_personal_count(ITEM_HEAL_SCROLL) <= 0) return false;
@@ -304,13 +393,13 @@ void activate_quick_slot(int slot_index) {
}
set_equipped_rune_for_slot(equip_type, rune_type);
update_max_health_from_equipment();
speak_with_history(get_equipment_name(equip_type) + " equipped.", true);
speak_with_history(get_equipment_display_name_with_rune(equip_type, rune_type) + " equipped.", true);
return;
}
unequip_equipment_type(equip_type);
clear_equipped_rune_for_slot(equip_type);
update_max_health_from_equipment();
speak_with_history(get_equipment_name(equip_type) + " unequipped.", true);
speak_with_history(get_equipment_display_name_with_rune(equip_type, rune_type) + " unequipped.", true);
return;
}
@@ -327,7 +416,7 @@ void activate_quick_slot(int slot_index) {
equip_equipment_type(equip_type);
set_equipped_rune_for_slot(equip_type, rune_type);
update_max_health_from_equipment();
speak_with_history(get_equipment_name(equip_type) + " equipped.", true);
speak_with_history(get_equipment_display_name_with_rune(equip_type, rune_type) + " equipped.", true);
}
void check_quick_slot_keys() {
+17 -9
View File
@@ -16,9 +16,13 @@ bool has_any_equipment() {
// Check runed items
int[] runeable = get_runeable_equipment_types();
int[] rune_types;
get_all_rune_types(rune_types);
for (uint i = 0; i < runeable.length(); i++) {
if (get_runed_item_count(runeable[i], RUNE_SWIFTNESS) > 0) {
return true;
for (uint j = 0; j < rune_types.length(); j++) {
if (get_runed_item_count(runeable[i], rune_types[j]) > 0) {
return true;
}
}
}
@@ -39,7 +43,7 @@ void check_equipment_menu() {
string get_full_equipment_name(int equip_type, int rune_type) {
string base_name = get_base_equipment_name(equip_type);
if (rune_type != RUNE_NONE) {
return "Runed " + base_name + " of " + get_rune_effect_name(rune_type);
return get_runed_item_display_name(equip_type, rune_type);
}
return base_name;
}
@@ -132,17 +136,21 @@ void run_equipment_menu() {
rune_types.insert_last(RUNE_NONE);
}
// Add runed items (currently only swiftness rune)
// Add runed items
int[] runeable = get_runeable_equipment_types();
int[] all_rune_types;
get_all_rune_types(all_rune_types);
for (uint i = 0; i < runeable.length(); i++) {
int equip_type = runeable[i];
int count = get_runed_item_count(equip_type, RUNE_SWIFTNESS);
if (count > 0) {
string name = get_full_equipment_name(equip_type, RUNE_SWIFTNESS);
string status = is_runed_item_equipped(equip_type, RUNE_SWIFTNESS) ? " (equipped)" : "";
for (uint j = 0; j < all_rune_types.length(); j++) {
int rune_type = all_rune_types[j];
int count = get_runed_item_count(equip_type, rune_type);
if (count <= 0) continue;
string name = get_full_equipment_name(equip_type, rune_type);
string status = is_runed_item_equipped(equip_type, rune_type) ? " (equipped)" : "";
options.insert_last(name + status);
equipment_types.insert_last(equip_type);
rune_types.insert_last(RUNE_SWIFTNESS);
rune_types.insert_last(rune_type);
}
}
string filter_text = "";
+16 -7
View File
@@ -16,13 +16,17 @@ void build_personal_inventory_options(string[]@ options, int[]@ item_types) {
// Add runed items
int[] runeable = get_runeable_equipment_types();
int[] rune_types;
get_all_rune_types(rune_types);
for (uint i = 0; i < runeable.length(); i++) {
int equip_type = runeable[i];
int count = get_runed_item_count(equip_type, RUNE_SWIFTNESS);
if (count > 0) {
string name = "Runed " + get_base_equipment_name(equip_type) + " of Quickness";
for (uint j = 0; j < rune_types.length(); j++) {
int rune_type = rune_types[j];
int count = get_runed_item_count(equip_type, rune_type);
if (count <= 0) continue;
string name = get_runed_item_display_name(equip_type, rune_type);
options.insert_last(name + ": " + count);
item_types.insert_last(encode_runed_item_type(equip_type, RUNE_SWIFTNESS));
item_types.insert_last(encode_runed_item_type(equip_type, rune_type));
}
}
}
@@ -49,11 +53,16 @@ void show_inventory() {
// Add runed items summary
string runed_info = "";
int[] runeable = get_runeable_equipment_types();
int[] rune_types;
get_all_rune_types(rune_types);
for (uint i = 0; i < runeable.length(); i++) {
int count = get_runed_item_count(runeable[i], RUNE_SWIFTNESS);
if (count > 0) {
int equip_type = runeable[i];
for (uint j = 0; j < rune_types.length(); j++) {
int rune_type = rune_types[j];
int count = get_runed_item_count(equip_type, rune_type);
if (count <= 0) continue;
if (runed_info.length() > 0) runed_info += ", ";
runed_info += count + " Runed " + get_base_equipment_name(runeable[i]) + " of Quickness";
runed_info += count + " " + get_runed_item_display_name(equip_type, rune_type);
}
}
if (runed_info.length() > 0) {
+8 -4
View File
@@ -271,13 +271,17 @@ void build_storage_inventory_options(string[]@ options, int[]@ item_types) {
// Add stored runed items
int[] runeable = get_runeable_equipment_types();
int[] rune_types;
get_all_rune_types(rune_types);
for (uint i = 0; i < runeable.length(); i++) {
int equip_type = runeable[i];
int count = get_stored_runed_item_count(equip_type, RUNE_SWIFTNESS);
if (count > 0) {
string name = "Runed " + get_base_equipment_name(equip_type) + " of Quickness";
for (uint j = 0; j < rune_types.length(); j++) {
int rune_type = rune_types[j];
int count = get_stored_runed_item_count(equip_type, rune_type);
if (count <= 0) continue;
string name = get_runed_item_display_name(equip_type, rune_type);
options.insert_last(name + ": " + count);
item_types.insert_last(encode_runed_item_type(equip_type, RUNE_SWIFTNESS));
item_types.insert_last(encode_runed_item_type(equip_type, rune_type));
}
}
}
+38 -3
View File
@@ -4,9 +4,11 @@
// Rune type constants
const int RUNE_NONE = -1;
const int RUNE_SWIFTNESS = 0;
const int RUNE_DESTRUCTION = 1;
// Rune unlock tracking (persisted in save)
bool rune_swiftness_unlocked = false;
bool rune_destruction_unlocked = false;
// Boss defeat tracking
bool unicorn_boss_defeated = false;
@@ -37,26 +39,43 @@ int equipped_weapon_rune = RUNE_NONE;
// Get display name for a rune type
string get_rune_name(int rune_type) {
if (rune_type == RUNE_SWIFTNESS) return "Rune of Swiftness";
if (rune_type == RUNE_DESTRUCTION) return "Rune of Destruction";
return "Unknown Rune";
}
// Get the effect suffix for runed item names (e.g., "of Quickness")
string get_rune_effect_name(int rune_type) {
if (rune_type == RUNE_SWIFTNESS) return "Quickness";
if (rune_type == RUNE_DESTRUCTION) return "Destruction";
return "Unknown";
}
// Check if any rune has been unlocked
bool any_rune_unlocked() {
return rune_swiftness_unlocked;
return rune_swiftness_unlocked || rune_destruction_unlocked;
}
// Check if a specific rune is unlocked
bool is_rune_unlocked(int rune_type) {
if (rune_type == RUNE_SWIFTNESS) return rune_swiftness_unlocked;
if (rune_type == RUNE_DESTRUCTION) return rune_destruction_unlocked;
return false;
}
void get_all_rune_types(int[]@ runeTypes) {
if (@runeTypes == null) return;
runeTypes.resize(0);
runeTypes.insert_last(RUNE_SWIFTNESS);
runeTypes.insert_last(RUNE_DESTRUCTION);
}
void get_unlocked_rune_types(int[]@ runeTypes) {
if (@runeTypes == null) return;
runeTypes.resize(0);
if (rune_swiftness_unlocked) runeTypes.insert_last(RUNE_SWIFTNESS);
if (rune_destruction_unlocked) runeTypes.insert_last(RUNE_DESTRUCTION);
}
// Create dictionary key for runed item storage
string make_runed_key(int equip_type, int rune_type) {
return "" + equip_type + ":" + rune_type;
@@ -89,8 +108,11 @@ void remove_runed_item(int equip_type, int rune_type) {
// Check if player has any runed version of an equipment type
bool has_any_runed_version(int equip_type) {
// Check all rune types
if (get_runed_item_count(equip_type, RUNE_SWIFTNESS) > 0) return true;
int[] runeTypes;
get_all_rune_types(runeTypes);
for (uint i = 0; i < runeTypes.length(); i++) {
if (get_runed_item_count(equip_type, runeTypes[i]) > 0) return true;
}
return false;
}
@@ -191,9 +213,22 @@ int[] get_runeable_equipment_types() {
return types;
}
int[] get_runeable_equipment_types_for_rune(int rune_type) {
if (rune_type == RUNE_DESTRUCTION) {
int[] types;
types.insert_last(EQUIP_SPEAR);
types.insert_last(EQUIP_AXE);
types.insert_last(EQUIP_SLING);
types.insert_last(EQUIP_BOW);
return types;
}
return get_runeable_equipment_types();
}
// Reset all rune data for new game
void reset_rune_data() {
rune_swiftness_unlocked = false;
rune_destruction_unlocked = false;
unicorn_boss_defeated = false;
runed_items.delete_all();
stored_runed_items.delete_all();
+10
View File
@@ -1,6 +1,8 @@
// Rune Effects - Calculate bonuses from equipped runed items
// This file handles the actual gameplay effects of runes
const int RUNE_DESTRUCTION_DAMAGE_MULTIPLIER = 2;
// Calculate total walking speed bonus from all equipped runed items
int get_total_rune_walk_speed_bonus() {
int bonus = 0;
@@ -79,3 +81,11 @@ string get_rune_speed_description() {
}
return desc;
}
int apply_weapon_rune_damage(int baseDamage) {
if (baseDamage <= 0) return baseDamage;
if (equipped_weapon_rune == RUNE_DESTRUCTION) {
return baseDamage * RUNE_DESTRUCTION_DAMAGE_MULTIPLIER;
}
return baseDamage;
}
+38
View File
@@ -625,6 +625,13 @@ void reset_game_state() {
invasion_scheduled_hour = -1;
quest_roll_done_today = false;
quest_queue.resize(0);
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
playerItemBreaksToday = 0;
playerItemBreakPending = false;
playerItemBreakPendingType = -1;
playerItemBreakMessage = "";
playerItemBreakSoundHandle = -1;
playerItemBreakSoundPending = false;
walktimer.restart();
jumptimer.restart();
@@ -823,6 +830,7 @@ bool save_game_state() {
// Rune system data
saveData.set("rune_swiftness_unlocked", rune_swiftness_unlocked);
saveData.set("rune_destruction_unlocked", rune_destruction_unlocked);
saveData.set("unicorn_boss_defeated", unicorn_boss_defeated);
saveData.set("equipped_head_rune", equipped_head_rune);
saveData.set("equipped_torso_rune", equipped_torso_rune);
@@ -869,6 +877,10 @@ bool save_game_state() {
saveData.set("time_invasion_roll_done_today", invasion_roll_done_today);
saveData.set("time_invasion_scheduled_hour", invasion_scheduled_hour);
saveData.set("time_invasion_enemy_type", invasion_enemy_type);
saveData.set("player_item_break_chance", playerItemBreakChance);
saveData.set("player_item_breaks_today", playerItemBreaksToday);
saveData.set("player_item_break_pending", playerItemBreakPending);
saveData.set("player_item_break_pending_type", playerItemBreakPendingType);
saveData.set("quest_roll_done_today", quest_roll_done_today);
saveData.set("wight_spawn_chance", wight_spawn_chance);
saveData.set("wight_spawned_this_night", wight_spawned_this_night);
@@ -1186,6 +1198,7 @@ bool load_game_state_from_file(const string&in filename) {
// Load rune system data
rune_swiftness_unlocked = get_bool(saveData, "rune_swiftness_unlocked", false);
rune_destruction_unlocked = get_bool(saveData, "rune_destruction_unlocked", false);
unicorn_boss_defeated = get_bool(saveData, "unicorn_boss_defeated", false);
equipped_head_rune = int(get_number(saveData, "equipped_head_rune", RUNE_NONE));
equipped_torso_rune = int(get_number(saveData, "equipped_torso_rune", RUNE_NONE));
@@ -1287,6 +1300,31 @@ bool load_game_state_from_file(const string&in filename) {
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;
playerItemBreakChance = int(get_number(saveData, "player_item_break_chance", PLAYER_ITEM_BREAK_CHANCE_MIN));
playerItemBreaksToday = int(get_number(saveData, "player_item_breaks_today", 0));
playerItemBreakPending = get_bool(saveData, "player_item_break_pending", false);
playerItemBreakPendingType = int(get_number(saveData, "player_item_break_pending_type", -1));
if (playerItemBreakChance < PLAYER_ITEM_BREAK_CHANCE_MIN) {
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
}
if (playerItemBreakChance > PLAYER_ITEM_BREAK_CHANCE_MAX) {
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MAX;
}
if (playerItemBreaksToday < 0) playerItemBreaksToday = 0;
if (playerItemBreaksToday > PLAYER_ITEM_BREAKS_PER_DAY) {
playerItemBreaksToday = PLAYER_ITEM_BREAKS_PER_DAY;
}
if (playerItemBreakPending) {
bool validPending = (playerItemBreakPendingType >= 0 && playerItemBreakPendingType < ITEM_COUNT)
|| is_runed_item_type(playerItemBreakPendingType);
if (!validPending) {
playerItemBreakPending = false;
playerItemBreakPendingType = -1;
}
} else {
playerItemBreakPendingType = -1;
}
reset_player_item_break_audio_state();
quest_roll_done_today = get_bool(saveData, "quest_roll_done_today", false);
wight_spawn_chance = int(get_number(saveData, "wight_spawn_chance", WIGHT_SPAWN_CHANCE_START));
wight_spawned_this_night = get_bool(saveData, "wight_spawned_this_night", false);
+99
View File
@@ -12,6 +12,14 @@ bool is_daytime = true;
bool sun_setting_warned = false;
bool sunrise_warned = false;
int playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
int playerItemBreaksToday = 0;
bool playerItemBreakPending = false;
int playerItemBreakPendingType = -1;
string playerItemBreakMessage = "";
int playerItemBreakSoundHandle = -1;
bool playerItemBreakSoundPending = false;
// Crossfade state
bool crossfade_active = false;
bool crossfade_to_night = false; // true = fading to night, false = fading to day
@@ -53,9 +61,95 @@ void init_time() {
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
invasion_enemy_type = "bandit";
reset_player_item_break_state();
update_ambience(true); // Force start
}
void reset_player_item_break_audio_state() {
playerItemBreakMessage = "";
playerItemBreakSoundHandle = -1;
playerItemBreakSoundPending = false;
}
void reset_player_item_break_state() {
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
playerItemBreaksToday = 0;
playerItemBreakPending = false;
playerItemBreakPendingType = -1;
reset_player_item_break_audio_state();
}
void queue_player_item_break_message(const string&in message) {
if (message.length() == 0) return;
playerItemBreakMessage = message;
playerItemBreakSoundHandle = p.play_stationary("sounds/items/item_breaks.ogg", false);
playerItemBreakSoundPending = true;
}
void update_player_item_break_message() {
if (!playerItemBreakSoundPending) return;
if (playerItemBreakSoundHandle != -1 && p.sound_is_active(playerItemBreakSoundHandle)) {
return;
}
playerItemBreakSoundHandle = -1;
playerItemBreakSoundPending = false;
if (playerItemBreakMessage.length() > 0) {
speak_with_history(playerItemBreakMessage, true);
playerItemBreakMessage = "";
}
}
void resolve_pending_player_item_break() {
if (!playerItemBreakPending) return;
if (x > BASE_END) return;
if (playerItemBreakPendingType == -1) {
playerItemBreakPending = false;
return;
}
if (remove_breakable_personal_item(playerItemBreakPendingType)) {
cleanup_equipment_after_inventory_change();
string itemName = get_breakable_item_name(playerItemBreakPendingType);
queue_player_item_break_message(itemName + " is worn out so you discard it.");
}
playerItemBreakPending = false;
playerItemBreakPendingType = -1;
}
void attempt_player_item_break_check() {
if (playerItemBreakPending) return;
if (playerItemBreaksToday >= PLAYER_ITEM_BREAKS_PER_DAY) return;
int[] breakableItems;
get_breakable_personal_item_types(breakableItems);
if (breakableItems.length() == 0) return;
int roll = random(1, 100);
if (roll <= playerItemBreakChance) {
int pickIndex = random(0, int(breakableItems.length()) - 1);
int itemType = breakableItems[pickIndex];
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
playerItemBreaksToday++;
if (x <= BASE_END) {
if (remove_breakable_personal_item(itemType)) {
cleanup_equipment_after_inventory_change();
string itemName = get_breakable_item_name(itemType);
queue_player_item_break_message(itemName + " is worn out so you discard it.");
}
} else {
playerItemBreakPending = true;
playerItemBreakPendingType = itemType;
}
} else {
if (playerItemBreakChance < PLAYER_ITEM_BREAK_CHANCE_MAX) {
playerItemBreakChance += PLAYER_ITEM_BREAK_CHANCE_INCREMENT;
}
if (playerItemBreakChance > PLAYER_ITEM_BREAK_CHANCE_MAX) {
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MAX;
}
}
}
string get_invasion_enemy_type_for_terrain(const string&in terrain_type) {
for (uint i = 0; i < invasion_terrain_enemy_map.length(); i++) {
string entry = invasion_terrain_enemy_map[i];
@@ -452,6 +546,9 @@ bool should_attempt_resident_foraging(int hour) {
}
void update_time() {
update_player_item_break_message();
resolve_pending_player_item_break();
if (hour_timer.elapsed >= MS_PER_HOUR) {
hour_timer.restart();
current_hour++;
@@ -464,6 +561,7 @@ void update_time() {
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
quest_roll_done_today = false;
playerItemBreaksToday = 0;
}
// Residents consume food every 8 hours (at midnight, 8am, 4pm)
@@ -530,6 +628,7 @@ void update_time() {
}
attempt_daily_invasion();
keep_base_fires_fed();
attempt_player_item_break_check();
update_incense_burning();
attempt_hourly_flying_creature_spawn();
attempt_hourly_boar_spawn();