Bow mechanics added, documentation updated.
This commit is contained in:
26
README.md
26
README.md
@@ -1,10 +1,10 @@
|
||||
# Draugnorak
|
||||
|
||||
A survival audio game built with NVGT. Explore, gather, craft, and defend your base as day and night cycle on.
|
||||
A survival audio game built with NVGT. Explore, gather, craft, and defend your base as day and night cycle.
|
||||
|
||||
## Installation
|
||||
- Unzip the file for your operating system.
|
||||
- On Linux and Mac make sure the dragnorak executable is marked executable
|
||||
- On Linux and Mac, make sure the draugnorak executable is marked executable.
|
||||
``` bash
|
||||
chmod +x draugnorak
|
||||
```
|
||||
@@ -16,14 +16,12 @@ chmod +x draugnorak
|
||||
- Set snares and build fires to survive.
|
||||
|
||||
Some of the first things you will want are a stone knife, spear, stone axe, and firepit.
|
||||
|
||||
|
||||
## Controls
|
||||
- **Left/Right**: Move.
|
||||
- **Up**: Jump, climb trees, start rope climbs when prompted.
|
||||
- **Down**: Climb down trees or descend rope climbs when prompted.
|
||||
- **Shift (hold)**: Search area (1-second hold, 1-second delay).
|
||||
- **Control (hold/release)**: Attack with equipped weapon. Sling uses a charge window on release.
|
||||
- **Control (hold/release)**: Attack with equipped weapon. Sling uses a charge window.
|
||||
- **A**: Action menu (place snare, feed fire, burn incense).
|
||||
- **C**: Crafting menu (base only).
|
||||
- **I**: Inventory.
|
||||
@@ -66,9 +64,9 @@ Some of the first things you will want are a stone knife, spear, stone axe, and
|
||||
Crafting is only available in the base (C).
|
||||
|
||||
### Categories
|
||||
- **Weapons**: Spear, Sling
|
||||
- **Weapons**: Spear, Sling, Bow
|
||||
- **Tools**: Knife, Snare, Stone Axe, Fishing Pole, Rope, Quiver, Canoe, Reed Basket, Clay Pot
|
||||
- **Materials**: Arrows, Butcher Game, Incense
|
||||
- **Materials**: Butcher Game, Smoke Fish, Bowstring, Arrows, Incense
|
||||
- **Clothing**: Skin gear, Moccasins, Pouch, Backpack
|
||||
- **Buildings**: Firepit, Fire, Herb Garden, Storage, Pasture, Stable, Altar
|
||||
- **Barricade**: Reinforcement options
|
||||
@@ -79,8 +77,10 @@ Crafting is only available in the base (C).
|
||||
- **Spear**: 1-tile range.
|
||||
- **Axe**: Melee; also chops trees.
|
||||
- **Sling**: Ranged (uses stones). Hold Control to charge; release on the “in-range” audio cue.
|
||||
- **Bow**: Ranged (uses arrows). Hold Control to draw; release to fire. Damage scales with draw time (0–10).
|
||||
- Enemies include zombies (night), bandits (invasions), and boars (daytime).
|
||||
- Bow combat is planned; arrows are stockpiled for future bow use.
|
||||
- **25% chance** to recover a fired arrow as a world drop.
|
||||
- If your base has an altar, each undead kill grants **+0.2 favor**.
|
||||
|
||||
## Base and Survival
|
||||
- **Passive healing** in the base when below max health.
|
||||
@@ -94,7 +94,7 @@ Crafting is only available in the base (C).
|
||||
Residents can automate survival tasks, defend your base, and process resources. To get the most out of them, ensure you have the necessary tools and facilities in your base storage.
|
||||
|
||||
### Recruitment and Care
|
||||
- **Recruitment**: Residents join automatically after invasions. If you have **Storage** built the likelihood for survivors to become residents increases. You can have a maximum of 4 residents.
|
||||
- **Recruitment**: Residents join automatically after invasions. If you have **Storage** built, the likelihood for survivors to become residents increases. **1–2 survivors** can join at a time, up to a maximum of **4 residents**.
|
||||
- **Food**: Each resident consumes **1 unit of food every 8 hours** (3 per day).
|
||||
- Priority: Meat > Smoked Fish > Basket of Fruits and Nuts.
|
||||
- If no food is available, residents will go hungry and stop working.
|
||||
@@ -103,10 +103,11 @@ Residents can automate survival tasks, defend your base, and process resources.
|
||||
### Activities and Requirements
|
||||
Residents automatically perform these tasks if the requirements are met in your base storage.
|
||||
|
||||
| Activity | Requires (in Storage) | details |
|
||||
| Activity | Requires (in Storage) | Details |
|
||||
| :--- | :--- | :--- |
|
||||
| **Defense (Melee)** | **Spear** | Deals damage to attackers. Spears may break. |
|
||||
| **Defense (Ranged)** | **Sling** + **Stones** | Attacks enemies from a distance. Consumes stones. Slings have a chance to break. |
|
||||
| **Defense (Ranged - Bow)** | **Bow** + **Arrows** | Preferred ranged defense. Consumes arrows. Bows have a chance to break. |
|
||||
| **Defense (Ranged - Sling)** | **Sling** + **Stones** | Attacks enemies from a distance. Consumes stones. Slings have a chance to break. |
|
||||
| **Repairs** | N/A | Repairs the **Barricade** during the day. |
|
||||
| **Gathering** | **Reed Basket** | Collects Sticks, Vines, Stones, and Logs. |
|
||||
| **Foraging** | **Reed Basket** | Occurs at 6 AM and 12 PM. Produces **Basket of Fruits and Nuts**. |
|
||||
@@ -115,7 +116,7 @@ Residents automatically perform these tasks if the requirements are met in your
|
||||
| **Butchering** | **Knife** + **Game** | Requires a **burning fire**. Processes Small Game or Boar Carcasses into Meat, Skins, etc. |
|
||||
| **Snare Check** | **Placed Snares** | Checks active snares, retrieves **Small Game**, and resets the snare. |
|
||||
|
||||
> **Note**: Tools (Spears, Slings, Baskets, Knives, Fishing Poles) have a chance to break when used by residents. Keep a stock of spares!
|
||||
> **Note**: Tools (Spears, Slings, Bows, Baskets, Knives, Fishing Poles) have a chance to break when used by residents. Keep a stock of spares!
|
||||
|
||||
## Time, Weather, and Events
|
||||
- **1 real minute = 1 in-game hour**.
|
||||
@@ -141,6 +142,7 @@ Residents automatically perform these tasks if the requirements are met in your
|
||||
- Personal inventory stacks are capped at 9.
|
||||
- Skin pouches add +2 stack capacity, backpacks add +9.
|
||||
- Arrows require quivers. Each quiver adds capacity for 12 arrows.
|
||||
- Bowstrings are crafted from **sinew at a fire** and used to craft bows.
|
||||
- Storage is separate and accessed in the base (Inventory menu when storage exists).
|
||||
|
||||
## Saving and Loading
|
||||
|
||||
@@ -407,9 +407,33 @@ void main()
|
||||
}
|
||||
|
||||
update_fishing();
|
||||
update_bow_shot();
|
||||
|
||||
bool ctrl_down = (key_down(KEY_LCTRL) || key_down(KEY_RCTRL));
|
||||
|
||||
// Bow draw detection
|
||||
if (bow_equipped) {
|
||||
if (ctrl_down && !bow_drawing) {
|
||||
if (get_personal_count(ITEM_ARROWS) > 0) {
|
||||
bow_drawing = true;
|
||||
bow_draw_timer.restart();
|
||||
p.play_stationary("sounds/weapons/bow_draw.ogg", false);
|
||||
} else {
|
||||
speak_ammo_blocked("No arrows.");
|
||||
}
|
||||
}
|
||||
|
||||
if (bow_drawing && !ctrl_down) {
|
||||
release_bow_attack(x);
|
||||
bow_drawing = false;
|
||||
}
|
||||
}
|
||||
if (!bow_equipped && bow_drawing) {
|
||||
bow_drawing = false;
|
||||
}
|
||||
|
||||
// Sling charge detection
|
||||
if (sling_equipped && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && !sling_charging) {
|
||||
if (!bow_equipped && sling_equipped && ctrl_down && !sling_charging) {
|
||||
if (get_personal_count(ITEM_STONES) > 0) {
|
||||
sling_charging = true;
|
||||
sling_charge_timer.restart();
|
||||
@@ -421,12 +445,12 @@ void main()
|
||||
}
|
||||
|
||||
// Update sling charge state while holding
|
||||
if (sling_charging && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL))) {
|
||||
if (sling_charging && ctrl_down) {
|
||||
update_sling_charge();
|
||||
}
|
||||
|
||||
// Sling release detection
|
||||
if (sling_charging && (!key_down(KEY_LCTRL) && !key_down(KEY_RCTRL))) {
|
||||
if (sling_charging && !ctrl_down) {
|
||||
release_sling_attack(x);
|
||||
sling_charging = false;
|
||||
if (sling_sound_handle != -1) {
|
||||
@@ -436,20 +460,12 @@ void main()
|
||||
}
|
||||
|
||||
// Non-sling weapon attacks (existing pattern)
|
||||
if (!sling_equipped && !sling_charging) {
|
||||
if (!bow_equipped && !bow_drawing && !sling_equipped && !sling_charging) {
|
||||
int attack_cooldown = 1000;
|
||||
if (spear_equipped) attack_cooldown = 800;
|
||||
if (axe_equipped) attack_cooldown = 1600;
|
||||
|
||||
bool ctrl_down = (key_down(KEY_LCTRL) || key_down(KEY_RCTRL));
|
||||
if (bow_equipped && ctrl_down && attack_timer.elapsed > attack_cooldown) {
|
||||
if (get_personal_count(ITEM_ARROWS) <= 0) {
|
||||
speak_ammo_blocked("No arrows.");
|
||||
} else {
|
||||
attack_timer.restart();
|
||||
perform_attack(x);
|
||||
}
|
||||
} else if (!bow_equipped && !fishing_pole_equipped && ctrl_down && attack_timer.elapsed > attack_cooldown) {
|
||||
if (!fishing_pole_equipped && ctrl_down && attack_timer.elapsed > attack_cooldown) {
|
||||
attack_timer.restart();
|
||||
perform_attack(x);
|
||||
}
|
||||
|
||||
BIN
sounds/weapons/arrow_flies.ogg
LFS
Normal file
BIN
sounds/weapons/arrow_flies.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/weapons/arrow_hit.ogg
LFS
Normal file
BIN
sounds/weapons/arrow_hit.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/weapons/bow_draw.ogg
LFS
Normal file
BIN
sounds/weapons/bow_draw.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/weapons/bow_fire.ogg
LFS
Normal file
BIN
sounds/weapons/bow_fire.ogg
LFS
Normal file
Binary file not shown.
@@ -198,12 +198,20 @@ void keep_base_fires_fed() {
|
||||
}
|
||||
|
||||
// Resident defense functions
|
||||
const int RESIDENT_WEAPON_SPEAR = 0;
|
||||
const int RESIDENT_WEAPON_SLING = 1;
|
||||
const int RESIDENT_WEAPON_BOW = 2;
|
||||
|
||||
int get_available_defense_weapons() {
|
||||
int count = get_storage_count(ITEM_SPEARS);
|
||||
// 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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -212,33 +220,42 @@ bool can_residents_defend() {
|
||||
return get_available_defense_weapons() > 0;
|
||||
}
|
||||
|
||||
bool choose_defense_weapon() {
|
||||
// Returns true for spear, false for sling
|
||||
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)
|
||||
: 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 total = spearCount + slingCount;
|
||||
|
||||
if (total == 0) return true;
|
||||
if (slingCount == 0) return true;
|
||||
if (spearCount == 0) return false;
|
||||
if (total == 0) return RESIDENT_WEAPON_SPEAR;
|
||||
if (slingCount == 0) return RESIDENT_WEAPON_SPEAR;
|
||||
if (spearCount == 0) return RESIDENT_WEAPON_SLING;
|
||||
|
||||
int roll = random(1, total);
|
||||
return roll <= spearCount;
|
||||
return (roll <= spearCount) ? RESIDENT_WEAPON_SPEAR : RESIDENT_WEAPON_SLING;
|
||||
}
|
||||
|
||||
int perform_resident_defense() {
|
||||
if (!can_residents_defend()) return 0;
|
||||
|
||||
// Choose weapon type randomly weighted by availability
|
||||
bool useSpear = choose_defense_weapon();
|
||||
// Choose weapon type (bows preferred, otherwise weighted by availability)
|
||||
int weapon_type = choose_defense_weapon_type();
|
||||
|
||||
int damage = 0;
|
||||
if (useSpear && get_storage_count(ITEM_SPEARS) > 0) {
|
||||
if (weapon_type == RESIDENT_WEAPON_BOW && get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 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) {
|
||||
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 (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) {
|
||||
} else if (weapon_type == RESIDENT_WEAPON_SLING && get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 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);
|
||||
@@ -248,28 +265,31 @@ int perform_resident_defense() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
// Proactive resident sling defense
|
||||
timer resident_sling_timer;
|
||||
// Proactive resident ranged defense
|
||||
timer resident_ranged_timer;
|
||||
|
||||
void attempt_resident_sling_defense() {
|
||||
// Only if residents exist and have slings with stones
|
||||
void attempt_resident_ranged_defense() {
|
||||
// Only if residents exist and have ranged weapons
|
||||
if (residents_count <= 0) return;
|
||||
if (get_storage_count(ITEM_SLINGS) <= 0 || get_storage_count(ITEM_STONES) <= 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);
|
||||
if (!has_bow && !has_sling) return;
|
||||
|
||||
// Cooldown between shots
|
||||
if (resident_sling_timer.elapsed < get_resident_cooldown(RESIDENT_SLING_COOLDOWN)) return;
|
||||
if (resident_ranged_timer.elapsed < get_resident_cooldown(RESIDENT_SLING_COOLDOWN)) return;
|
||||
|
||||
// Find nearest enemy within sling range
|
||||
int nearestDistance = SLING_RANGE + 1;
|
||||
int range = has_bow ? BOW_RANGE : SLING_RANGE;
|
||||
// Find nearest enemy within range
|
||||
int nearestDistance = range + 1;
|
||||
int targetPos = -1;
|
||||
bool targetIsBandit = false;
|
||||
|
||||
int sling_origin = BASE_END;
|
||||
int ranged_origin = BASE_END;
|
||||
|
||||
// Check zombies
|
||||
for (uint i = 0; i < zombies.length(); i++) {
|
||||
int dist = abs(zombies[i].position - sling_origin);
|
||||
if (dist > 0 && dist <= SLING_RANGE && dist < nearestDistance) {
|
||||
int dist = abs(zombies[i].position - ranged_origin);
|
||||
if (dist > 0 && dist <= range && dist < nearestDistance) {
|
||||
nearestDistance = dist;
|
||||
targetPos = zombies[i].position;
|
||||
targetIsBandit = false;
|
||||
@@ -278,8 +298,8 @@ void attempt_resident_sling_defense() {
|
||||
|
||||
// Check bandits
|
||||
for (uint i = 0; i < bandits.length(); i++) {
|
||||
int dist = abs(bandits[i].position - sling_origin);
|
||||
if (dist > 0 && dist <= SLING_RANGE && dist < nearestDistance) {
|
||||
int dist = abs(bandits[i].position - ranged_origin);
|
||||
if (dist > 0 && dist <= range && dist < nearestDistance) {
|
||||
nearestDistance = dist;
|
||||
targetPos = bandits[i].position;
|
||||
targetIsBandit = true;
|
||||
@@ -290,11 +310,16 @@ void attempt_resident_sling_defense() {
|
||||
if (targetPos == -1) return;
|
||||
|
||||
// Shoot!
|
||||
resident_sling_timer.restart();
|
||||
add_storage_count(ITEM_STONES, -1);
|
||||
|
||||
resident_ranged_timer.restart();
|
||||
int damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
|
||||
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||
if (has_bow) {
|
||||
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);
|
||||
play_1d_with_volume_step("sounds/weapons/arrow_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||
} else {
|
||||
add_storage_count(ITEM_STONES, -1);
|
||||
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||
}
|
||||
|
||||
if (targetIsBandit) {
|
||||
damage_bandit_at(targetPos, damage);
|
||||
@@ -313,7 +338,7 @@ void attempt_resident_sling_defense() {
|
||||
void process_daily_weapon_breakage() {
|
||||
if (residents_count <= 0) return;
|
||||
|
||||
int totalWeapons = get_storage_count(ITEM_SPEARS) + get_storage_count(ITEM_SLINGS);
|
||||
int totalWeapons = get_storage_count(ITEM_SPEARS) + get_storage_count(ITEM_SLINGS) + get_storage_count(ITEM_BOWS);
|
||||
if (totalWeapons == 0) return;
|
||||
|
||||
// Number of breakage checks = min(residents, weapons)
|
||||
@@ -322,17 +347,21 @@ void process_daily_weapon_breakage() {
|
||||
// Distribute checks among available weapons
|
||||
int spearChecks = 0;
|
||||
int slingChecks = 0;
|
||||
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 remaining = remainingSpears + remainingSlings;
|
||||
int remainingBows = get_storage_count(ITEM_BOWS) - bowChecks;
|
||||
int remaining = remainingSpears + remainingSlings + remainingBows;
|
||||
|
||||
if (remaining <= 0) break;
|
||||
|
||||
int roll = random(1, remaining);
|
||||
if (roll <= remainingSpears && remainingSpears > 0) {
|
||||
spearChecks++;
|
||||
} else if (roll <= remainingSpears + remainingBows && remainingBows > 0) {
|
||||
bowChecks++;
|
||||
} else if (remainingSlings > 0) {
|
||||
slingChecks++;
|
||||
}
|
||||
@@ -341,6 +370,7 @@ void process_daily_weapon_breakage() {
|
||||
// Perform breakage checks
|
||||
int spearsBroken = 0;
|
||||
int slingsBroken = 0;
|
||||
int bowsBroken = 0;
|
||||
int break_chance = get_resident_break_chance(RESIDENT_WEAPON_BREAK_CHANCE);
|
||||
|
||||
for (int i = 0; i < spearChecks; i++) {
|
||||
@@ -355,6 +385,12 @@ void process_daily_weapon_breakage() {
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bowChecks; i++) {
|
||||
if (random(1, 100) <= break_chance) {
|
||||
bowsBroken++;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply breakage
|
||||
if (spearsBroken > 0) {
|
||||
add_storage_count(ITEM_SPEARS, -spearsBroken);
|
||||
@@ -373,6 +409,15 @@ void process_daily_weapon_breakage() {
|
||||
: slingsBroken + " slings broke from wear.";
|
||||
notify(msg);
|
||||
}
|
||||
|
||||
if (bowsBroken > 0) {
|
||||
add_storage_count(ITEM_BOWS, -bowsBroken);
|
||||
if (get_storage_count(ITEM_BOWS) < 0) set_storage_count(ITEM_BOWS, 0);
|
||||
string msg = (bowsBroken == 1)
|
||||
? "A resident's bow broke from wear."
|
||||
: bowsBroken + " bows broke from wear.";
|
||||
notify(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Resident snare retrieval
|
||||
|
||||
221
src/combat.nvgt
221
src/combat.nvgt
@@ -14,6 +14,11 @@ void perform_attack(int current_x) {
|
||||
timer ammoBlockTimer;
|
||||
string lastAmmoBlockMessage = "";
|
||||
const int AMMO_BLOCK_COOLDOWN_MS = 3000;
|
||||
const int BOW_HIT_NONE = 0;
|
||||
const int BOW_HIT_BANDIT = 1;
|
||||
const int BOW_HIT_BOAR = 2;
|
||||
const int BOW_HIT_ZOMBIE = 3;
|
||||
const int BOW_HIT_FLYING = 4;
|
||||
|
||||
void speak_ammo_blocked(string message) {
|
||||
if (message == lastAmmoBlockMessage && ammoBlockTimer.elapsed < AMMO_BLOCK_COOLDOWN_MS) {
|
||||
@@ -25,6 +30,44 @@ void speak_ammo_blocked(string message) {
|
||||
speak_with_history(message, true);
|
||||
}
|
||||
|
||||
int find_ranged_enemy(int player_x, int range, int direction, bool allow_flying, bool &out hit_bandit, bool &out hit_boar, bool &out hit_flying_creature) {
|
||||
hit_bandit = false;
|
||||
hit_boar = false;
|
||||
hit_flying_creature = false;
|
||||
|
||||
for (int dist = 1; dist <= range; dist++) {
|
||||
int check_x = player_x + (dist * direction);
|
||||
if (check_x < 0 || check_x >= MAP_SIZE) break;
|
||||
|
||||
Bandit@ bandit = get_bandit_at(check_x);
|
||||
if (bandit != null) {
|
||||
hit_bandit = true;
|
||||
return check_x;
|
||||
}
|
||||
|
||||
GroundGame@ boar = get_boar_at(check_x);
|
||||
if (boar != null) {
|
||||
hit_boar = true;
|
||||
return check_x;
|
||||
}
|
||||
|
||||
Undead@ undead = get_zombie_at(check_x);
|
||||
if (undead != null) {
|
||||
return check_x;
|
||||
}
|
||||
|
||||
if (allow_flying) {
|
||||
FlyingCreature@ creature = get_flying_creature_at(check_x);
|
||||
if (creature != null && creature.state == "flying") {
|
||||
hit_flying_creature = true;
|
||||
return check_x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int attack_enemy_ranged(int start_x, int end_x, int damage) {
|
||||
for (int check_x = start_x; check_x <= end_x; check_x++) {
|
||||
// Check for bandits first (priority during daytime)
|
||||
@@ -137,6 +180,145 @@ void update_sling_charge() {
|
||||
}
|
||||
}
|
||||
|
||||
int get_bow_draw_damage(int elapsed_ms) {
|
||||
int clamped = elapsed_ms;
|
||||
if (clamped < 0) clamped = 0;
|
||||
if (clamped > BOW_DRAW_TIME_MS) clamped = BOW_DRAW_TIME_MS;
|
||||
|
||||
float ratio = float(clamped) / float(BOW_DRAW_TIME_MS);
|
||||
int damage = int(ratio * BOW_DAMAGE_MAX);
|
||||
if (damage < BOW_DAMAGE_MIN) damage = BOW_DAMAGE_MIN;
|
||||
if (damage > BOW_DAMAGE_MAX) damage = BOW_DAMAGE_MAX;
|
||||
return damage;
|
||||
}
|
||||
|
||||
void stop_bow_shot_audio() {
|
||||
safe_destroy_sound(bow_shot_sound_handle);
|
||||
bow_shot_active = false;
|
||||
bow_shot_duration_ms = 0;
|
||||
bow_shot_hit_x = -1;
|
||||
bow_shot_hit_type = BOW_HIT_NONE;
|
||||
}
|
||||
|
||||
void start_bow_shot_audio(int start_x, int end_x, int hit_x, int hit_type, int duration_ms) {
|
||||
stop_bow_shot_audio();
|
||||
bow_shot_active = true;
|
||||
bow_shot_timer.restart();
|
||||
bow_shot_start_x = start_x;
|
||||
bow_shot_end_x = end_x;
|
||||
bow_shot_hit_x = hit_x;
|
||||
bow_shot_hit_type = hit_type;
|
||||
bow_shot_duration_ms = duration_ms;
|
||||
bow_shot_drop_pending = false;
|
||||
bow_shot_drop_pos = -1;
|
||||
if (bow_shot_duration_ms < 1) bow_shot_duration_ms = 1;
|
||||
|
||||
bow_shot_sound_handle = play_1d_with_volume_step(
|
||||
"sounds/weapons/arrow_flies.ogg",
|
||||
x,
|
||||
bow_shot_start_x,
|
||||
false,
|
||||
PLAYER_WEAPON_SOUND_VOLUME_STEP
|
||||
);
|
||||
}
|
||||
|
||||
void update_bow_shot() {
|
||||
if (!bow_shot_active) return;
|
||||
if (bow_shot_duration_ms < 1) bow_shot_duration_ms = 1;
|
||||
|
||||
int elapsed = bow_shot_timer.elapsed;
|
||||
float progress = float(elapsed) / float(bow_shot_duration_ms);
|
||||
if (progress > 1.0f) progress = 1.0f;
|
||||
|
||||
int travel = int(float(bow_shot_end_x - bow_shot_start_x) * progress);
|
||||
int current_pos = bow_shot_start_x + travel;
|
||||
if (bow_shot_sound_handle != -1) {
|
||||
p.update_sound_1d(bow_shot_sound_handle, current_pos);
|
||||
}
|
||||
|
||||
if (elapsed >= bow_shot_duration_ms) {
|
||||
int hit_x = bow_shot_hit_x;
|
||||
int hit_type = bow_shot_hit_type;
|
||||
bool drop_pending = bow_shot_drop_pending;
|
||||
int drop_pos = bow_shot_drop_pos;
|
||||
stop_bow_shot_audio();
|
||||
if (hit_x >= 0) {
|
||||
play_1d_with_volume_step(
|
||||
"sounds/weapons/arrow_hit.ogg",
|
||||
x,
|
||||
hit_x,
|
||||
false,
|
||||
PLAYER_WEAPON_SOUND_VOLUME_STEP
|
||||
);
|
||||
if (hit_type == BOW_HIT_BANDIT) {
|
||||
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_x, BANDIT_SOUND_VOLUME_STEP);
|
||||
} else if (hit_type == BOW_HIT_BOAR) {
|
||||
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_x, BOAR_SOUND_VOLUME_STEP);
|
||||
} else if (hit_type == BOW_HIT_ZOMBIE) {
|
||||
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_x, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
}
|
||||
}
|
||||
if (drop_pending && drop_pos >= 0) {
|
||||
if (get_drop_at(drop_pos) == null) {
|
||||
add_world_drop(drop_pos, "arrow");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void release_bow_attack(int player_x) {
|
||||
if (get_personal_count(ITEM_ARROWS) <= 0) {
|
||||
speak_ammo_blocked("No arrows.");
|
||||
return;
|
||||
}
|
||||
|
||||
int damage = get_bow_draw_damage(bow_draw_timer.elapsed);
|
||||
add_personal_count(ITEM_ARROWS, -1);
|
||||
p.play_stationary("sounds/weapons/bow_fire.ogg", false);
|
||||
|
||||
int search_direction = (facing == 1) ? 1 : -1;
|
||||
bool hit_bandit = false;
|
||||
bool hit_flying_creature = false;
|
||||
bool hit_boar = false;
|
||||
int target_x = find_ranged_enemy(player_x, BOW_RANGE, search_direction, true, hit_bandit, hit_boar, hit_flying_creature);
|
||||
|
||||
int hit_type = BOW_HIT_NONE;
|
||||
if (target_x != -1) {
|
||||
if (hit_bandit) {
|
||||
damage_bandit_at(target_x, damage);
|
||||
hit_type = BOW_HIT_BANDIT;
|
||||
} else if (hit_boar) {
|
||||
damage_boar_at(target_x, damage);
|
||||
hit_type = BOW_HIT_BOAR;
|
||||
} else if (hit_flying_creature) {
|
||||
damage_flying_creature_at(target_x, damage);
|
||||
hit_type = BOW_HIT_FLYING;
|
||||
} else {
|
||||
damage_zombie_at(target_x, damage);
|
||||
hit_type = BOW_HIT_ZOMBIE;
|
||||
}
|
||||
}
|
||||
|
||||
int end_x = (target_x != -1)
|
||||
? target_x
|
||||
: (player_x + (search_direction * (BOW_RANGE + BOW_MISS_EXTRA_TILES)));
|
||||
|
||||
int duration_ms = ARROW_FLIES_DURATION_MS;
|
||||
if (target_x != -1) {
|
||||
int distance = abs(target_x - player_x);
|
||||
if (distance < 1) distance = 1;
|
||||
duration_ms = int(float(ARROW_FLIES_DURATION_MS) * (float(distance) / float(BOW_RANGE)));
|
||||
if (duration_ms < 1) duration_ms = 1;
|
||||
}
|
||||
|
||||
int hit_x = (target_x != -1) ? target_x : -1;
|
||||
start_bow_shot_audio(player_x, end_x, hit_x, hit_type, duration_ms);
|
||||
if (random(1, 100) <= 25) {
|
||||
bow_shot_drop_pending = true;
|
||||
bow_shot_drop_pos = (target_x != -1) ? target_x : end_x;
|
||||
}
|
||||
}
|
||||
|
||||
void release_sling_attack(int player_x) {
|
||||
// Consume stone
|
||||
add_personal_count(ITEM_STONES, -1);
|
||||
@@ -158,44 +340,7 @@ void release_sling_attack(int player_x) {
|
||||
bool hit_bandit = false;
|
||||
bool hit_flying_creature = false;
|
||||
bool hit_boar = false;
|
||||
|
||||
// Priority: Find nearest enemy (bandit or zombie) first
|
||||
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;
|
||||
|
||||
// Check for bandit first
|
||||
Bandit@ bandit = get_bandit_at(check_x);
|
||||
if (bandit != null) {
|
||||
target_x = check_x;
|
||||
hit_bandit = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Then check for boar
|
||||
GroundGame@ boar = get_boar_at(check_x);
|
||||
if (boar != null) {
|
||||
target_x = check_x;
|
||||
hit_boar = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Then check for undead
|
||||
Undead@ undead = get_zombie_at(check_x);
|
||||
if (undead != null) {
|
||||
target_x = check_x;
|
||||
hit_bandit = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Then check for flying creature (only if flying)
|
||||
FlyingCreature@ creature = get_flying_creature_at(check_x);
|
||||
if (creature != null && creature.state == "flying") {
|
||||
target_x = check_x;
|
||||
hit_flying_creature = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
target_x = find_ranged_enemy(player_x, SLING_RANGE, search_direction, true, hit_bandit, hit_boar, hit_flying_creature);
|
||||
|
||||
// If no enemy found, check for trees (but don't damage them)
|
||||
if (target_x == -1) {
|
||||
|
||||
@@ -37,9 +37,12 @@ const int SLING_DAMAGE_MAX = 8;
|
||||
const int SLING_RANGE = 8;
|
||||
|
||||
// Bow settings
|
||||
const int BOW_DAMAGE_MIN = 6;
|
||||
const int BOW_DAMAGE_MAX = 9;
|
||||
const int BOW_DAMAGE_MIN = 0;
|
||||
const int BOW_DAMAGE_MAX = 10;
|
||||
const int BOW_RANGE = 12;
|
||||
const int BOW_DRAW_TIME_MS = 1000;
|
||||
const int ARROW_FLIES_DURATION_MS = 230;
|
||||
const int BOW_MISS_EXTRA_TILES = 3;
|
||||
const int ARROWS_PER_CRAFT = 12;
|
||||
const int ARROW_CAPACITY_PER_QUIVER = 12;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ void run_materials_menu() {
|
||||
"Butcher Game [Requires Game, Knife, Fire nearby]",
|
||||
"Smoke Fish (1 Fish, 1 Stick) [Requires Fire nearby]",
|
||||
"Arrows (2 Sticks, 4 Feathers, 2 Stones) [Requires Quiver]",
|
||||
"Bowstring (3 Sinew) [Requires Fire nearby]",
|
||||
"Incense (6 Sticks, 2 Vines, 1 Reed) [Requires Altar]"
|
||||
};
|
||||
|
||||
@@ -34,7 +35,8 @@ void run_materials_menu() {
|
||||
if (selection == 0) butcher_small_game();
|
||||
else if (selection == 1) craft_smoke_fish();
|
||||
else if (selection == 2) craft_arrows();
|
||||
else if (selection == 3) craft_incense();
|
||||
else if (selection == 3) craft_bowstring();
|
||||
else if (selection == 4) craft_incense();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -42,7 +44,8 @@ void run_materials_menu() {
|
||||
if (selection == 0) butcher_small_game_max();
|
||||
else if (selection == 1) craft_smoke_fish_max();
|
||||
else if (selection == 2) craft_arrows_max();
|
||||
else if (selection == 3) craft_incense_max();
|
||||
else if (selection == 3) craft_bowstring_max();
|
||||
else if (selection == 4) craft_incense_max();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -119,6 +122,61 @@ void craft_arrows_max() {
|
||||
speak_with_history("Crafted " + (ARROWS_PER_CRAFT * maxCraft) + " arrows.", true);
|
||||
}
|
||||
|
||||
void craft_bowstring() {
|
||||
WorldFire@ fire = get_fire_within_range(x, 3);
|
||||
if (fire == null) {
|
||||
speak_with_history("You need a fire within 3 tiles to make bowstring.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
string missing = "";
|
||||
if (get_personal_count(ITEM_SINEW) < 3) missing += "3 sinew ";
|
||||
|
||||
if (missing == "") {
|
||||
if (get_personal_count(ITEM_BOWSTRINGS) >= get_personal_stack_limit()) {
|
||||
speak_with_history("You can't carry any more bowstrings.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting(3);
|
||||
add_personal_count(ITEM_SINEW, -3);
|
||||
add_personal_count(ITEM_BOWSTRINGS, 1);
|
||||
speak_with_history("Crafted a bowstring.", true);
|
||||
} else {
|
||||
speak_with_history("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_bowstring_max() {
|
||||
WorldFire@ fire = get_fire_within_range(x, 3);
|
||||
if (fire == null) {
|
||||
speak_with_history("You need a fire within 3 tiles to make bowstring.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_personal_count(ITEM_BOWSTRINGS) >= get_personal_stack_limit()) {
|
||||
speak_with_history("You can't carry any more bowstrings.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
int max_by_sinew = get_personal_count(ITEM_SINEW) / 3;
|
||||
int max_craft = max_by_sinew;
|
||||
|
||||
int space = get_personal_stack_limit() - get_personal_count(ITEM_BOWSTRINGS);
|
||||
if (max_craft > space) max_craft = space;
|
||||
|
||||
if (max_craft <= 0) {
|
||||
speak_with_history("Missing: 3 sinew", true);
|
||||
return;
|
||||
}
|
||||
|
||||
int total_cost = max_craft * 3; // 3 sinew per bowstring
|
||||
int craft_time = (total_cost < max_craft * 4) ? max_craft * 4 : total_cost;
|
||||
simulate_crafting(craft_time);
|
||||
add_personal_count(ITEM_SINEW, -(max_craft * 3));
|
||||
add_personal_count(ITEM_BOWSTRINGS, max_craft);
|
||||
speak_with_history("Crafted " + max_craft + " Bowstrings.", true);
|
||||
}
|
||||
|
||||
void craft_incense() {
|
||||
if (world_altars.length() == 0) {
|
||||
speak_with_history("You need an altar to craft incense.", true);
|
||||
|
||||
@@ -5,7 +5,8 @@ void run_weapons_menu() {
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Spear (1 Stick, 1 Vine, 1 Stone) [Requires Knife]",
|
||||
"Sling (1 Skin, 2 Vines)"
|
||||
"Sling (1 Skin, 2 Vines)",
|
||||
"Bow (1 Stick, 1 Bowstring)"
|
||||
};
|
||||
|
||||
while(true) {
|
||||
@@ -31,12 +32,14 @@ void run_weapons_menu() {
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
if (selection == 0) craft_spear();
|
||||
else if (selection == 1) craft_sling();
|
||||
else if (selection == 2) craft_bow();
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_TAB)) {
|
||||
if (selection == 0) craft_spear_max();
|
||||
else if (selection == 1) craft_sling_max();
|
||||
else if (selection == 2) craft_bow_max();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -155,6 +158,57 @@ void craft_sling_max() {
|
||||
speak_with_history("Crafted " + max_craft + " Slings.", true);
|
||||
}
|
||||
|
||||
void craft_bow() {
|
||||
string missing = "";
|
||||
if (get_personal_count(ITEM_STICKS) < 1) missing += "1 stick ";
|
||||
if (get_personal_count(ITEM_BOWSTRINGS) < 1) missing += "1 bowstring ";
|
||||
|
||||
if (missing == "") {
|
||||
if (get_personal_count(ITEM_BOWS) >= get_personal_stack_limit()) {
|
||||
speak_with_history("You can't carry any more bows.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting(3);
|
||||
add_personal_count(ITEM_STICKS, -1);
|
||||
add_personal_count(ITEM_BOWSTRINGS, -1);
|
||||
add_personal_count(ITEM_BOWS, 1);
|
||||
speak_with_history("Crafted a Bow.", true);
|
||||
} else {
|
||||
speak_with_history("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_bow_max() {
|
||||
if (get_personal_count(ITEM_BOWS) >= get_personal_stack_limit()) {
|
||||
speak_with_history("You can't carry any more bows.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
int max_by_sticks = get_personal_count(ITEM_STICKS);
|
||||
int max_by_bowstrings = get_personal_count(ITEM_BOWSTRINGS);
|
||||
int max_craft = max_by_sticks;
|
||||
if (max_by_bowstrings < max_craft) max_craft = max_by_bowstrings;
|
||||
|
||||
int space = get_personal_stack_limit() - get_personal_count(ITEM_BOWS);
|
||||
if (max_craft > space) max_craft = space;
|
||||
|
||||
if (max_craft <= 0) {
|
||||
string missing = "";
|
||||
if (get_personal_count(ITEM_STICKS) < 1) missing += "1 stick ";
|
||||
if (get_personal_count(ITEM_BOWSTRINGS) < 1) missing += "1 bowstring ";
|
||||
speak_with_history("Missing: " + missing, true);
|
||||
return;
|
||||
}
|
||||
|
||||
int total_cost = max_craft * 2; // 1 stick + 1 bowstring per bow
|
||||
int craft_time = (total_cost < max_craft * 4) ? max_craft * 4 : total_cost;
|
||||
simulate_crafting(craft_time);
|
||||
add_personal_count(ITEM_STICKS, -max_craft);
|
||||
add_personal_count(ITEM_BOWSTRINGS, -max_craft);
|
||||
add_personal_count(ITEM_BOWS, max_craft);
|
||||
speak_with_history("Crafted " + max_craft + " Bows.", true);
|
||||
}
|
||||
|
||||
void craft_axe() {
|
||||
string missing = "";
|
||||
if (get_personal_count(ITEM_KNIVES) < 1) missing += "Stone Knife ";
|
||||
|
||||
@@ -239,6 +239,9 @@ bool damage_undead_at(int pos, int damage) {
|
||||
if (undeads[i].position == pos) {
|
||||
undeads[i].health -= damage;
|
||||
if (undeads[i].health <= 0) {
|
||||
if (world_altars.length() > 0) {
|
||||
favor += 0.2;
|
||||
}
|
||||
if (undeads[i].sound_handle != -1) {
|
||||
p.destroy_sound(undeads[i].sound_handle);
|
||||
undeads[i].sound_handle = -1;
|
||||
|
||||
@@ -35,6 +35,20 @@ timer sling_charge_timer;
|
||||
int sling_sound_handle = -1;
|
||||
int last_sling_stage = -1; // Track which stage we're in to avoid duplicate sounds
|
||||
|
||||
// Bow state
|
||||
bool bow_drawing = false;
|
||||
timer bow_draw_timer;
|
||||
bool bow_shot_active = false;
|
||||
timer bow_shot_timer;
|
||||
int bow_shot_start_x = 0;
|
||||
int bow_shot_end_x = 0;
|
||||
int bow_shot_hit_x = -1;
|
||||
int bow_shot_hit_type = 0;
|
||||
int bow_shot_sound_handle = -1;
|
||||
int bow_shot_duration_ms = 0;
|
||||
bool bow_shot_drop_pending = false;
|
||||
int bow_shot_drop_pos = -1;
|
||||
|
||||
// Fishing state
|
||||
bool is_casting = false; // Holding control, cast_strength playing
|
||||
bool line_in_water = false; // Cast successful, waiting for fish
|
||||
@@ -88,6 +102,8 @@ void restart_all_timers() {
|
||||
fire_damage_timer.restart();
|
||||
healing_timer.restart();
|
||||
sling_charge_timer.restart();
|
||||
bow_draw_timer.restart();
|
||||
bow_shot_timer.restart();
|
||||
|
||||
// Fire fuel timers
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
|
||||
@@ -110,6 +110,10 @@ void stop_active_sounds() {
|
||||
p.destroy_sound(sling_sound_handle);
|
||||
sling_sound_handle = -1;
|
||||
}
|
||||
if (bow_shot_sound_handle != -1) {
|
||||
p.destroy_sound(bow_shot_sound_handle);
|
||||
bow_shot_sound_handle = -1;
|
||||
}
|
||||
stop_all_weather_sounds();
|
||||
}
|
||||
|
||||
@@ -164,6 +168,16 @@ void reset_game_state() {
|
||||
climb_target_y = 0;
|
||||
fall_start_y = 0;
|
||||
sling_charging = false;
|
||||
bow_drawing = false;
|
||||
bow_shot_active = false;
|
||||
bow_shot_start_x = 0;
|
||||
bow_shot_end_x = 0;
|
||||
bow_shot_hit_x = -1;
|
||||
bow_shot_hit_type = 0;
|
||||
bow_shot_duration_ms = 0;
|
||||
bow_shot_sound_handle = -1;
|
||||
bow_shot_drop_pending = false;
|
||||
bow_shot_drop_pos = -1;
|
||||
searching = false;
|
||||
rope_climbing = false;
|
||||
rope_climb_up = true;
|
||||
@@ -256,6 +270,8 @@ void reset_game_state() {
|
||||
fire_damage_timer.restart();
|
||||
healing_timer.restart();
|
||||
sling_charge_timer.restart();
|
||||
bow_draw_timer.restart();
|
||||
bow_shot_timer.restart();
|
||||
fall_timer.restart();
|
||||
climb_timer.restart();
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ void attempt_resident_recruitment() {
|
||||
return;
|
||||
}
|
||||
|
||||
int added = random(1, 3);
|
||||
int added = random(1, 2);
|
||||
// Don't exceed cap
|
||||
if (residents_count + added > MAX_RESIDENTS) {
|
||||
added = MAX_RESIDENTS - residents_count;
|
||||
@@ -483,8 +483,8 @@ void update_time() {
|
||||
|
||||
ensure_ambience_running();
|
||||
|
||||
// Proactive resident defense with slings
|
||||
attempt_resident_sling_defense();
|
||||
// Proactive resident ranged defense
|
||||
attempt_resident_ranged_defense();
|
||||
|
||||
// Manage invasion enemies during active invasion
|
||||
manage_bandits_during_invasion();
|
||||
|
||||
@@ -104,6 +104,20 @@ bool try_pickup_world_drop(WorldDrop@ drop) {
|
||||
if (get_flying_creature_config_by_drop_type(drop.type) !is null) {
|
||||
return try_pickup_small_game(drop.type);
|
||||
}
|
||||
if (drop.type == "arrow") {
|
||||
int max_arrows = get_arrow_limit();
|
||||
if (max_arrows <= 0) {
|
||||
speak_with_history("You need a quiver to carry arrows.", true);
|
||||
return false;
|
||||
}
|
||||
if (get_personal_count(ITEM_ARROWS) >= max_arrows) {
|
||||
speak_with_history("You can't carry any more arrows.", true);
|
||||
return false;
|
||||
}
|
||||
add_personal_count(ITEM_ARROWS, 1);
|
||||
speak_with_history("Picked up arrow.", true);
|
||||
return true;
|
||||
}
|
||||
if (drop.type == "boar carcass") {
|
||||
if (get_personal_count(ITEM_BOAR_CARCASSES) >= get_personal_stack_limit()) {
|
||||
speak_with_history("You can't carry any more boar carcasses.", true);
|
||||
|
||||
Reference in New Issue
Block a user