Skip to content

Commit

Permalink
AI best weapon tweaks and apply armor stats fix to the whole party
Browse files Browse the repository at this point in the history
  • Loading branch information
phobos2077 committed Jun 27, 2024
1 parent 4b3532e commit 25298c9
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 109 deletions.
1 change: 1 addition & 0 deletions docs/ecco_changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ v0.9.7:
- added more MFC to stores for car refueling, as well as more healing kits and certain types of ammo
- added bonus damage for fire-based traps with Pyromaniac perk
- fixed incorrect car charging amount when using Cells on trunk
- tweaked AI weapon selection logic to avoid some illogical choices


v0.9.6:
Expand Down
4 changes: 2 additions & 2 deletions root/mods/ecco/combat.ini
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ weapon_drop_dist=2
weapon_drop_dir=0

; % of times critter's weapon will be destroyed on death
destroy_weapon_percent=30
destroy_weapon_percent=35

; pid of junk item to spawn in place of destroyed weapon
destroy_weapon_spawn_junk_pid=98

; if weapon is destroyed, it's weight will be multiplied by this value and used as probability to spawn Junk in it's place
destroy_weapon_spawn_junk_chance_mult=4
destroy_weapon_spawn_junk_chance_mult=5

; comma-separated list of weapon PIDs that may be destroyed by "destroy_weapon_chance"
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
Expand Down
12 changes: 1 addition & 11 deletions scripts_src/_pbs_headers/ecco.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
#include "../sfall/command_lite.h"
#include "../sfall/define_extra.h"

#include "ecco_define.h"
#include "ecco_ids.h"
#include "ecco_ini.h"
#include "ecco_log.h"
#include "ecco_msg.h"


#define dude_skill(x) (has_skill(dude_obj, x))

#define last_deathanim_critter (get_sfall_global_int(SGVAR_LAST_DEATHANIM_CRITTER))
#define last_deathanim (get_sfall_global_int(SGVAR_LAST_DEATHANIM))

Expand All @@ -39,15 +38,8 @@
#define get_proto_dmg_min(pid) (get_proto_data(pid, PROTO_WP_DMG_MIN))
#define get_proto_dmg_max(pid) (get_proto_data(pid, PROTO_WP_DMG_MAX))

#define is_unarmed_weapon_pid(pid) (weapon_attack_mode1(pid) == ATTACK_MODE_PUNCH)
#define is_hidden_item(pid) ((get_proto_data(pid, PROTO_FLAG_EXT) bwand HIDDEN_ITEM) != 0)

#define critter_dt_by_dmg_type(crit, type) (get_critter_stat(crit, STAT_dmg_thresh + type))
#define critter_dr_by_dmg_type(crit, type) (get_critter_stat(crit, STAT_dmg_resist + type))
#define critter_max_hp(crit) (get_critter_stat(crit, STAT_max_hp))
#define exp_for_kill_critter_pid(pid) (get_proto_data(pid, PROTO_CR_KILL_EXP))
#define critter_flags_by_pid(pid) (get_proto_data(pid, PROTO_CR_FLAGS))
#define critter_proto_has_flag(pid, flg) ((get_proto_data(pid, PROTO_CR_FLAGS) bwand flg) != 0)
#define can_steal_from_critter_pid(pid) (not critter_proto_has_flag(pid, CFLG_NOSTEAL))
#define critter_facing_dir(crit) (has_trait(TRAIT_OBJECT,crit,OBJECT_CUR_ROT))

Expand Down Expand Up @@ -137,8 +129,6 @@
#define is_humanoid(crit) (proto_data(obj_pid(crit), cr_body_type) == CR_BODY_BIPED)
#define is_weapon_pid(pid) (proto_data(pid, it_type) == item_type_weapon)

#define obj_team(obj) (has_trait(TRAIT_OBJECT, obj, OBJECT_TEAM_NUM))

#define actual_ammo_count(crit, obj) ((obj_is_carrying_obj(crit, obj) - 1)*get_proto_data(obj_pid(obj), PROTO_AM_PACK_SIZE) + get_weapon_ammo_count(obj))

#define is_using_ammo_pid(crit, pid) (get_weapon_ammo_pid(critter_inven_obj(crit, 1)) == pid or get_weapon_ammo_pid(critter_inven_obj(crit, 2)) == pid)
Expand Down
15 changes: 15 additions & 0 deletions scripts_src/_pbs_headers/ecco_define.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef ECCO_DEFINE_H
#define ECCO_DEFINE_H

#define obj_team(obj) (has_trait(TRAIT_OBJECT, obj, OBJECT_TEAM_NUM))

#define critter_dt_by_dmg_type(crit, type) (get_critter_stat(crit, STAT_dmg_thresh + type))
#define critter_dr_by_dmg_type(crit, type) (get_critter_stat(crit, STAT_dmg_resist + type))
#define critter_max_hp(crit) (get_critter_stat(crit, STAT_max_hp))
#define critter_proto_has_flag(pid, flg) ((get_proto_data(pid, PROTO_CR_FLAGS) bwand flg) != 0)

#define proto_has_ext_flag(pid, flg) ((get_proto_data(pid, PROTO_FLAG_EXT) bwand flg) != 0)
#define item_proto_is_hidden(pid) proto_has_ext_flag(pid, HIDDEN_ITEM)

#endif

Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
#include "..\sfall\command_lite.h"
#include "..\sfall\lib.arrays.h"

#define SCRIPT_REALNAME "party_armor_stats_fix"
#include "..\_pbs_headers\ecco_log.h"

#define MAX_PID (1000)
#define STATS_ARR_NAME(pid) ("armor_stats_"+pid)
#define STATS_AC (0)
#define STATS_DR (1)
#define STATS_DT (1 + 7)
#define STATS_SIZE (1 + 7*2)

#define debug_log(msg) debug_msg("pc_armor_stats_fix: "+msg)
#define debug_error(msg) debug_msg("! ERROR ! pc_armor_stats_fix: "+msg)


procedure save_armor_stats(variable armorPid) begin
variable stats, i;
Expand All @@ -46,40 +46,45 @@ procedure save_all_armor_stats begin
debug_log("Saved armor stats for "+pids);
end

procedure apply_armor_stats_difference begin
variable stats, armorPid, diff, i;
armorPid := obj_pid(dude_armor) if dude_armor else 0;
if (not armorPid) then return;
#define armor_pid_str(pid) (proto_data(pid, it_name) + "[" + pid + "]")

procedure apply_armor_stats_difference(variable critter) begin
variable stats, armor, armorPid, diff, i;
armor := get_armor(critter);
if (not armor) then return;

armorPid := obj_pid(armor);
stats := load_array(STATS_ARR_NAME(armorPid));
if (not stats) then begin
debug_error("Dude has armor but no saved armor stats! (possibly loaded an old save)");
debug_err(obj_name(critter)+" has armor but no saved armor stats! (possibly loaded an old save)");
return;
end

diff := get_proto_data(armorPid, PROTO_AR_AC) - stats[STATS_AC];
if (diff != 0) then begin
set_critter_extra_stat(dude_obj, STAT_ac, get_critter_extra_stat(dude_obj, STAT_ac) + diff);
debug_log("Armor "+armorPid+" AC changed by "+diff+", applying difference.");
set_critter_extra_stat(critter, STAT_ac, get_critter_extra_stat(critter, STAT_ac) + diff);
debug_log(armor_pid_str(armorPid)+" AC changed by "+diff+", applying difference for "+obj_name(critter));
intface_redraw;
end
for (i := 0; i < 7; i++) begin
diff := get_proto_data(armorPid, PROTO_AR_DR_NORMAL + i*4) - stats[STATS_DR + i];
if (diff != 0) then begin
set_critter_extra_stat(dude_obj, STAT_dmg_resist + i, get_critter_extra_stat(dude_obj, STAT_dmg_resist + i) + diff);
debug_log("Armor "+armorPid+" DR ("+i+") changed by "+diff+", applying difference.");
set_critter_extra_stat(critter, STAT_dmg_resist + i, get_critter_extra_stat(critter, STAT_dmg_resist + i) + diff);
debug_log(armor_pid_str(armorPid)+" DR ("+i+") changed by "+diff+", applying difference for "+obj_name(critter));
end
diff := get_proto_data(armorPid, PROTO_AR_DT_NORMAL + i*4) - stats[STATS_DT + i];
if (diff != 0) then begin
set_critter_extra_stat(dude_obj, STAT_dmg_thresh + i, get_critter_extra_stat(dude_obj, STAT_dmg_thresh + i) + diff);
debug_log("Armor "+armorPid+" DT ("+i+") changed by "+diff+", applying difference.");
set_critter_extra_stat(critter, STAT_dmg_thresh + i, get_critter_extra_stat(critter, STAT_dmg_thresh + i) + diff);
debug_log(armor_pid_str(armorPid)+" DT ("+i+") changed by "+diff+", applying difference for "+obj_name(critter));
end
end
end

procedure start begin
if (game_loaded) then begin
call apply_armor_stats_difference;
foreach (variable crit in party_member_list_critters) begin
call apply_armor_stats_difference(crit);
end
call save_all_armor_stats;
end
end
102 changes: 97 additions & 5 deletions scripts_src/_pbs_main/gl_pbs_ai.ssl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
AI improvements:
- Configure certain parameters of called shot conditions, so it happens more consistently and frequently
- Improved best weapon selection logic (better expected damage estimation, range/distance factors, etc)
*/


Expand All @@ -10,8 +11,10 @@
#include "../sfall/command_lite.h"

#include "../sfall/lib.arrays.h"
#include "../sfall/lib.obj.h"

#include "../_pbs_headers/engine_funcs.h"
#include "../_pbs_headers/ecco_define.h"
#include "../_pbs_headers/ecco_ini.h"
#include "../_pbs_headers/ecco_log.h"

Expand Down Expand Up @@ -188,6 +191,94 @@ procedure combatturn_handler begin
end
end


procedure weapon_rank(variable critter, variable weapon, variable target) begin
// TODO: vanilla uses combat_safety_invalidate_weapon to account for potential friendly fire, this code does not
variable
pid := obj_pid(weapon),
avgDmg := (get_proto_data(pid, PROTO_WP_DMG_MIN) + get_proto_data(pid, PROTO_WP_DMG_MAX)) / 2,
burstSize := get_proto_data(pid, PROTO_WP_BURST) if (weapon_attack_mode1(pid) == ATTACK_MODE_BURST or weapon_attack_mode2(pid) == ATTACK_MODE_BURST) else 1,
avgBurstSize := (0.66 * burstSize) if burstSize > 1 else 1,
ammoPid := get_weapon_ammo_pid(weapon),
ammoDmgMult := (1.0 * get_proto_data(ammoPid, PROTO_AM_DMG_MULT) / get_proto_data(ammoPid, PROTO_AM_DMG_DIV) if (ammoPid > 0) else 1),
dmgType := get_proto_data(pid, PROTO_WP_DMG_TYPE),
perk := get_proto_data(pid, PROTO_WP_PERK),
targetDT := get_critter_stat(target, STAT_dmg_thresh + dmgType) if target != 0 else 0,
targetDR := get_critter_stat(target, STAT_dmg_resist + dmgType) if target != 0 else 0,
effectiveDT := (targetDT * 20 / 100) if perk == PERK_weapon_penetrate else targetDT,
expectedDmgPerAttack := (ammoDmgMult * avgDmg * avgBurstSize - effectiveDT) * (100 - targetDR) / 100,
attackAP := get_proto_data(pid, PROTO_WP_APCOST_2) if (weapon_attack_mode2(pid) == ATTACK_MODE_BURST) else get_proto_data(pid, PROTO_WP_APCOST_1),
expectedDmgPerAP := expectedDmgPerAttack / attackAP,
range := get_proto_data(pid, PROTO_WP_RANGE_1),
dmgFactor := expectedDmgPerAP * 10,
rangeFactor;

if (target != 0) then begin
variable dist := tile_distance_objs(critter, target);
// Account for scope hit chance penalty
if (perk == PERK_weapon_scope_range and dist < 6) then
rangeFactor := -10;
else // Reduce range factor depending on actual distance to target
rangeFactor := range * 2 * math_min(dist, 50) / 50;
end else
rangeFactor := range;

// TODO: get calculated damage from gl_pbs_damage_mod via exported proc?
return round(dmgFactor + rangeFactor);
end

procedure weapon_type(variable weapon) begin
variable attackMode, type := WEAPON_TYPE_UNARMED;

if weapon then begin
attackMode := weapon_attack_mode(obj_pid(weapon), ATKTYPE_RWEP1);
if (attackMode >= ATTACK_MODE_SINGLE) then
type := WEAPON_TYPE_RANGED;
else if (attackMode == ATTACK_MODE_THROW) then
type := WEAPON_TYPE_THROWN;
else if (attackMode >= ATTACK_MODE_SWING) then
type := WEAPON_TYPE_MELEE;
else if (attackMode == ATTACK_MODE_NONE) then
type := WEAPON_TYPE_NONE;
end

return type;
end

/*
HOOK_BESTWEAPON
Runs when the AI decides which weapon is the best while searching the inventory for a weapon to equip in combat. This also runs when the player presses the "Use Best Weapon" button on the party member control panel.

Critter arg0 - the critter searching for a weapon
Item arg1 - the best weapon chosen from two items
Item arg2 - the first choice of weapon
Item arg3 - the second choice of weapon
Critter arg4 - the target of the critter (can be 0)

Item ret0 - overrides the chosen best weapon
*/
procedure bestweapon_handler begin
variable
critter := get_sfall_arg,
weaponChosen := get_sfall_arg,
weapon1 := get_sfall_arg,
weapon2 := get_sfall_arg,
target := get_sfall_arg;

//display_msg(string_format("bestweapon for %s: %s vs %s -> %s (against %s)", obj_name_safe(critter), obj_name_safe(weapon1), obj_name_safe(weapon2), obj_name_safe(weaponChosen), obj_name_safe(target)));

if (weapon1 == 0 or weapon2 == 0
or weapon_type(weapon1) != weapon_type(weapon2)
or item_proto_is_hidden(obj_pid(weapon1)) or item_proto_is_hidden(obj_pid(weapon2))) then return;

variable
weapon1rank := weapon_rank(critter, weapon1, target),
weapon2rank := weapon_rank(critter, weapon2, target);

debug_log_fmt("%s's bestweapon: %s (%d) vs %s (%d)", obj_name_safe(critter), obj_name_safe(weapon1), weapon1rank, obj_name_safe(weapon2), weapon2rank);
set_sfall_return(weapon1 if weapon1rank > weapon2rank else weapon2);
end

#define INI_FILE INI_COMBAT
#define INI_SECTION "AI"

Expand All @@ -209,10 +300,11 @@ end

procedure start begin
if not game_loaded then return;
if (get_ini_value_def(INI_FILE, INI_SECTION, "called_tweaks", 0) == 0) then return;

call load_called_shot_settings;
if (get_ini_value_def(INI_FILE, INI_SECTION, "called_tweaks", 0)) then begin
call load_called_shot_settings;
register_hook_proc(HOOK_TOHIT, tohit_handler);
register_hook_proc(HOOK_COMBATTURN, combatturn_handler);
end

register_hook_proc(HOOK_TOHIT, tohit_handler);
register_hook_proc(HOOK_COMBATTURN, combatturn_handler);
register_hook_proc(HOOK_BESTWEAPON, bestweapon_handler);
end
6 changes: 4 additions & 2 deletions scripts_src/_pbs_main/gl_pbs_critter_loot.ssl
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ procedure try_destroy_weapon(variable critter, variable weapon) begin
return removed;
end

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

procedure drop_weapons(variable critter) begin
variable
dist,
Expand All @@ -165,7 +167,7 @@ procedure drop_weapons(variable critter) begin
weapon := critter_inven_obj(critter, i);
if (weapon and obj_item_subtype(weapon) == item_type_weapon
and not is_unarmed_weapon_pid(obj_pid(weapon))
and not is_hidden_item(obj_pid(weapon))) then
and not item_proto_is_hidden(obj_pid(weapon))) then
break;
else
weapon := 0;
Expand Down Expand Up @@ -201,7 +203,7 @@ procedure reduce_loot(variable critter) begin
list := inven_as_array(critter);
removeStats := "";
foreach item in list begin
if (item == 0 or is_hidden_item(obj_pid(item))) then
if (item == 0 or item_proto_is_hidden(obj_pid(item))) then
continue;

if (obj_item_subtype(item) == item_type_ammo) then begin
Expand Down
Loading

0 comments on commit 25298c9

Please sign in to comment.