Skip to content

Commit

Permalink
NPC ammo quantity overhaul
Browse files Browse the repository at this point in the history
- ensure_npcs_ammo: count individual bullets instead of whole packs and calculate needed qty based on number of shots or bursts
- critter_loot: add chance to have 0 ammo instead of the given range (to avoid constantly having to unload like 3 bullets)
- critter_loot: reduce loot in deathanim2 instead of ondeath to account for some deaths dropping all loot on ground before it can be reduced!
  • Loading branch information
phobos2077 committed Jul 6, 2024
1 parent 7171873 commit 2666ab6
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 54 deletions.
13 changes: 7 additions & 6 deletions docs/todo.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
for 1.0.0:
- finish Big Guns run
- add warning message if wrong sfall version is used (save version to ini, if it changed and still wrong, show message again)

for 0.9.7:
- playtest
- add tie-ins to barter "demand" feature, have some trader explain it in dialog (and maybe also boost your barter skill)
- review usages of WEAPON_ACCURATE perk
- recheck economy of all craft involving ammo after pack size changes


for "future":
- investigate "damage_mod: Expected attacker and weapon differs", seems to be rare
- custom craft schematic art
- cavern encounters need rebalance!!
- take a look at 4/5 AP cost for pistols
- add tie-ins to barter "demand" feature, have some trader explain it in dialog (and maybe also boost your barter skill)


Considering:
- 4 AP cost for 14mm Pistol?
- add special sounds for Bozar AMR (& M2?)
- craft Combat Armor mk2 (what component? fuel is overused)
- hard to choose specific craft component when several are available: don't auto-search trunk? or provide UI to select component?
- installer switch to disable barter formula
Expand All @@ -27,10 +28,10 @@ Considering:

IDEAS:
- some way to lure more enemy types into traps than just Geckos and Rats (Scorpions, Deathclaws, Humans...)
- napalm/incendiary throwing grenade? (similar to 40mm IC but for throwing, higher tier alternative to Molotov)
- leather hides from dead brahmin?
- weapon destruction chance/% to depend on attack type/damage (bursts vs single, explosion vs punch, etc)
- Demolition Expert to buff all "explosion" attacks, not just traps?
- napalm/incendiary throwing grenade? (similar to 40mm IC but for throwing, higher tier alternative to Molotov)
- more powerful crafted grenade (3 home-made grenades bundled together?)
- NEW PERK: +1 to carry over AP
- NEW PERK: subsequent shots to the same target cost less AP (except bursts)
Expand Down
13 changes: 8 additions & 5 deletions root/mods/ecco/combat.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ bestweapon_ref_armor_pid=379 ; Leather mk2
; A fraction [0..1] of shots in a critical burst attack that will keep armor bypass and critical damage multiplier.
; For example, if set to 0.5, 10 bullets hit target and attack was a critical, first 5 bullets will retain the full damage and bypass (if any) of the attack. 5 remaining bullets will deal normal damage as if there was no critical.
; NOTE: first bullet always retains bypass and multiplier.
; To disable, set to 1.
; For vanilla behavior, set to 1.
burst_critical_fraction=0.5

; DT affected by ammo DR Adjust value. Set separate modes for positive (JHP) and negative (AP) values. Available modes:
Expand All @@ -45,7 +45,7 @@ knockback_div=14
knockback_perk_div=6

; Print detailed log of every damage calculation to debug log.
debug=1
debug=0


[ATTACK_MODES]
Expand Down Expand Up @@ -120,11 +120,14 @@ destroy_weapon_spawn_junk_chance_mult=5
destroy_weapon_list=5,6,8,9,10,11,12,13,15,16,18,22,23,24,28,94,115,116,118,122,143,160,233,235,242,268,283,287,296,299,300,313,332,350,351,352,353,354,355,385,387,388,389,391,392,394,395,396,397,398,399,400,401,402,403,404,405,406,407,500,522,617,629,630,634,638,639,640,643,644,646,647,648

; set positive to reduce % of ammo (not including ammo loaded in guns) left in critters after death: <min_percent>,<max_percent>
; 100 means remove all, 50 mean roughly 50% of ammo will be deleted, etc.
reduce_ammo_percent=60,100
; 100 means remove all, 50 mean roughly 50% of ammo will be deleted, etc. Range: [0, 100].
reduce_ammo_percent=50,70

; if >0, a chance to skip reduce_ammo_percent and just remove ammo stack/magazine entirely
reduce_ammo_to_zero_chance=40

; set positive to reduce % of drugs left in critters after death
reduce_drugs_percent=50
reduce_drugs_percent=70

; comma-separated list of drug pids to be subject of reduce
; Currently: Stimpak, Radaway, Antidote, Mentats, Buffout, Rad-X, Psycho, Super Stimpak, Healing powder
Expand Down
2 changes: 1 addition & 1 deletion root/mods/ecco/misc.ini
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ unload_weapons=1
[ENCOUNTERS]
; Restore Sneak state after map change
remember_sneak=1
; Make sure all ranged NPC's have spare ammo
; Make sure all ranged NPC's have some spare ammo to remain in the fight for longer
ensure_npcs_ammo=1


Expand Down
34 changes: 25 additions & 9 deletions scripts_src/_pbs_headers/loot_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,38 @@ procedure reduce_item_count(variable invenObj, variable item, variable newCount)
destroy_object(item);
end

#define calc_reduced_ammo_range(count, percentRange) math_round_chance(count * (100 - random(percentRange[0], percentRange[1])) / 100.0)
procedure calc_reduced_ammo_range(variable count, variable percentRange, variable emptyChance := 0) begin
if (emptyChance > 0 and random(0, 99) < emptyChance) then
return 0;

/* variable
rnd := random(percentRange[0], percentRange[1]),
clamped := math_max(100 - rnd, 0),
multed := count * clamped / 100.0,
rounded := math_round_chance(multed),
allInOne := */

//debug_log_fmt("calc range from %d: rnd = %d, clamped = %d, multed = %.2f, rounded = %d, allInOne = %d", count, rnd, clamped, multed, rounded, allInOne);
return math_round_chance(count * math_max(100 - random(percentRange[0], percentRange[1]), 0) / 100.0);
end

/**
* Reduces number of individual bullets in an ammo stack by a percentage range.
* @arg {ObjectPtr} invenObj - Container or critter
* @arg {ObjectPtr} item - item stack object
* @arg {list} percentRange - [min, max] range of % to remove
* @arg {int} emptyChance - probabiltiy to remove all ammo
* @ret {string}
*/
procedure reduce_ammo_in_stack(variable invenObj, variable item, variable percentRange) begin
procedure reduce_ammo_in_stack(variable invenObj, variable item, variable percentRange, variable emptyChance := 0) begin
if (percentRange[1] <= 0) then return "";

variable
pid := obj_pid(item),
packSize := get_proto_data(pid, PROTO_AM_PACK_SIZE),
count := (obj_is_carrying_obj(invenObj, item) - 1) * packSize + get_weapon_ammo_count(item),
newCount := calc_reduced_ammo_range(count, percentRange);
newCount := calc_reduced_ammo_range(count, percentRange, emptyChance),
itemName := obj_name(item);

//display_msg("count: "+count+", pack: "+packSize+", new: "+newCount+" ("+ceil(1.0*newCount / packSize)+")");
call reduce_item_count(invenObj, item, ceil(1.0 * newCount / packSize));
Expand All @@ -42,17 +57,18 @@ procedure reduce_ammo_in_stack(variable invenObj, variable item, variable percen
if (item and newCount % packSize > 0) then
set_weapon_ammo_count(item, newCount % packSize);

return obj_name(item)+" ("+count+" -> "+newCount+")";
return itemName+" ("+count+" -> "+newCount+"), ";
end


/**
* Reduces number of individual bullets in an ammo stack by a percentage range.
* @arg {ObjectPtr} item - Weapon item
* @arg {list} percentRange - [min, max] range of % to remove
* @arg {int} emptyChance - probabiltiy to remove all ammo
* @ret {string}
*/
procedure reduce_ammo_in_weapon(variable item, variable percentRange) begin
procedure reduce_ammo_in_weapon(variable item, variable percentRange, variable emptyChance := 0) begin
if (percentRange[1] <= 0) then return "";

variable
Expand All @@ -61,9 +77,9 @@ procedure reduce_ammo_in_weapon(variable item, variable percentRange) begin

if (count <= 0) then return "";

newCount := calc_reduced_ammo_range(count, percentRange);
newCount := calc_reduced_ammo_range(count, percentRange, emptyChance);
set_weapon_ammo_count(item, newCount);
return string_format("%s mag ammo (%d -> %d)", obj_name(item), count, newCount);
return string_format("%s mag ammo (%d -> %d), ", obj_name(item), count, newCount);
end

/**
Expand All @@ -83,7 +99,7 @@ procedure reduce_ammo_on_ground(variable item, variable percentRange) begin
else
destroy_object(item);

return string_format("%s (%d -> %d)", obj_name(item), count, newCount);
return string_format("%s (%d -> %d), ", obj_name(item), count, newCount);
end


Expand All @@ -110,7 +126,7 @@ procedure reduce_item_in_stack(variable invenObj, variable item, variable pidLis
if (newCount == count) then return "";

call reduce_item_count(invenObj, item, newCount);
return obj_name(item)+" ("+count+" -> "+newCount+")";
return obj_name(item)+" ("+count+" -> "+newCount+"), ";
end

/**
Expand Down
6 changes: 6 additions & 0 deletions scripts_src/_pbs_main/gl_ammocost_mod.ssl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ variable

procedure start;

export procedure ammocost_mod_get_cost(variable weaponPid);

procedure ammocost_mod_get_cost(variable weaponPid) begin
return ini_ammocost[weaponPid] or 1;
end

/*
HOOK_AMMOCOST
Runs when calculating ammo cost for a weapon. Doesn't affect damage, only how much ammo is spent. By default, weapons can make attacks when at least 1 ammo is left, regardless of ammo cost calculations. To add proper check for ammo before attacking and proper calculation of the number of burst rounds (hook type 1 and 2 in arg3), set CheckWeaponAmmoCost=1 in Misc section of ddraw.ini.
Expand Down
54 changes: 39 additions & 15 deletions scripts_src/_pbs_main/gl_pbs_critter_loot.ssl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ procedure start;

#define SCRIPT_REALNAME "gl_pbs_critter_loot"

#include "../headers/animcomd.h"
#include "../headers/scenepid.h"

#include "../sfall/define_lite.h"
#include "../sfall/command_lite.h"
#include "../sfall/lib.strings.h"
Expand All @@ -34,15 +36,16 @@ variable
ini_weapon_drop_dist,
ini_weapon_drop_dir,
ini_reduce_ammo_percent,
ini_reduce_ammo_to_zero_chance,
ini_reduce_drugs_percent,
ini_reduce_drugs_pids;


variable destroyed_stats;

procedure destroy_critter_weapon(variable critter);
procedure drop_weapons(variable critter);
procedure reduce_loot(variable critter);
procedure try_drop_weapons(variable critter);
procedure try_reduce_loot(variable critter);

#define INI_FILE INI_COMBAT
#define INI_SECTION "CRITTER_LOOT"
Expand All @@ -52,6 +55,25 @@ procedure reduce_loot(variable critter);
//#define load_ini_int_clamped(name, min, max) int_from_ini_file_clamped(name, INI_COMBAT, INI_SECTION, min, max)
//#define load_ini_int_list(name) int_list_from_ini_file(name, INI_COMBAT, INI_SECTION)


procedure deathanim2_handler begin
variable
critter := get_sfall_arg_at(2),
animId := get_sfall_arg_at(4);

// Reduce loot before all items drop to the ground due to violent death.
/*if (( animId == ANIM_electrified_to_nothing
or animId == ANIM_exploded_to_nothing
)
and is_critter(critter)
and not critter_proto_has_flag(obj_pid(critter), CFLG_NODROP)
and not critter_proto_has_flag(obj_pid(critter), CFLG_SPECIAL))
then begin
call try_reduce_loot(critter);
end*/
call try_reduce_loot(critter);
end

/*
HOOK_ONDEATH
Runs immediately after a critter dies for any reason. No return values; this is just a convenience for when you need to do something after death for a large number of different critters and don't want to have to script each and every one of them.
Expand All @@ -68,15 +90,11 @@ procedure ondeath_handler begin
if (not critter or obj_type(critter) != OBJ_TYPE_CRITTER) then
return;

if (not obj_in_party(critter)
and (ini_reduce_ammo_percent[1] > 0 or ini_reduce_drugs_percent > 0 or ini_destroy_weapon_percent > 0)) then begin
call reduce_loot(critter);
end
if (ini_weapon_drop_chance > 0 and random(1, 100) <= ini_weapon_drop_chance) then begin
call drop_weapons(critter);
end
//call try_reduce_loot(critter);
call try_drop_weapons(critter);
end


procedure start begin
if (game_loaded) then begin

Expand All @@ -89,9 +107,11 @@ procedure start begin
load_num_from_ini(weapon_drop_dir, 0, 0, 5);

load_range_from_ini(reduce_ammo_percent, 0, 0, 0, 100);
load_num_from_ini(reduce_ammo_to_zero_chance, 0, 0, 100);
load_num_from_ini(reduce_drugs_percent, 0, 0, 100);
load_int_list_from_ini(reduce_drugs_pids);

register_hook_proc(HOOK_DEATHANIM2, deathanim2_handler);
register_hook_proc(HOOK_ONDEATH, ondeath_handler);
end
end
Expand Down Expand Up @@ -152,7 +172,7 @@ end

#define is_unarmed_weapon_pid(pid) (weapon_attack_mode1(pid) == ATTACK_MODE_PUNCH)

procedure drop_weapons(variable critter) begin
procedure try_drop_weapons(variable critter) begin
variable
dist,
dir,
Expand All @@ -161,7 +181,9 @@ procedure drop_weapons(variable critter) begin
critter_flags,
i;

if (critter_proto_has_flag(obj_pid(critter), CFLG_NODROP)) then return;
if (ini_weapon_drop_chance <= 0
or critter_proto_has_flag(obj_pid(critter), CFLG_NODROP)
or random(1, 100) > ini_weapon_drop_chance) then return;

for (i := 1; i <= 2; i++) begin
weapon := critter_inven_obj(critter, i);
Expand Down Expand Up @@ -192,13 +214,15 @@ procedure drop_weapons(variable critter) begin
move_to(weapon, dropped_hex, elevation(critter));
end

procedure reduce_loot(variable critter) begin
procedure try_reduce_loot(variable critter) begin
variable
item,
list,
removeStats;

if (critter_proto_has_flag(obj_pid(critter), CFLG_NOSTEAL)) then return;
if ((ini_reduce_ammo_percent[1] <= 0 and ini_reduce_drugs_percent <= 0 and ini_destroy_weapon_percent <= 0)
or obj_in_party(critter)
or critter_proto_has_flag(obj_pid(critter), CFLG_NOSTEAL)) then return;

list := inven_as_array(critter);
removeStats := "";
Expand All @@ -207,7 +231,7 @@ procedure reduce_loot(variable critter) begin
continue;

if (obj_item_subtype(item) == item_type_ammo) then begin
removeStats += reduce_ammo_in_stack(critter, item, ini_reduce_ammo_percent);
removeStats += reduce_ammo_in_stack(critter, item, ini_reduce_ammo_percent, ini_reduce_ammo_to_zero_chance);
end
else if (obj_item_subtype(item) == item_type_drug) then begin
removeStats += reduce_item_in_stack(critter, item, ini_reduce_drugs_pids, ini_reduce_drugs_percent);
Expand All @@ -218,7 +242,7 @@ procedure reduce_loot(variable critter) begin
and try_destroy_weapon(critter, item)) then
removeStats += obj_name(item)+", ";
else
removeStats += reduce_ammo_in_weapon(item, ini_reduce_ammo_percent);
removeStats += reduce_ammo_in_weapon(item, ini_reduce_ammo_percent, ini_reduce_ammo_to_zero_chance);
end
end
if (removeStats != "") then
Expand Down
Loading

0 comments on commit 2666ab6

Please sign in to comment.