Skip to content

Commit

Permalink
Merchant loot reduction rework
Browse files Browse the repository at this point in the history
- Deterministic
- Minimizes exploit by keeping only up to a certain amount of items from restock list
- Related code refactoring
  • Loading branch information
phobos2077 committed Jul 16, 2024
1 parent 6c488ff commit d4d238c
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 111 deletions.
2 changes: 1 addition & 1 deletion scripts_src/_pbs_headers/ecco.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

#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 can_steal_from_critter_pid(pid) (not critter_proto_has_flag(pid, CFLG_NOSTEAL))
#define can_steal_from_critter_pid(pid) (not proto_critter_has_flag(pid, CFLG_NOSTEAL))
#define critter_facing_dir(crit) (has_trait(TRAIT_OBJECT,crit,OBJECT_CUR_ROT))

#define is_critter(obj) (obj_type(obj) == OBJ_TYPE_CRITTER)
Expand Down
10 changes: 7 additions & 3 deletions scripts_src/_pbs_headers/ecco_define.h
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
#ifndef ECCO_DEFINE_H
#define ECCO_DEFINE_H

#define obj_team(obj) (has_trait(TRAIT_OBJECT, obj, OBJECT_TEAM_NUM))
#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)

#define proto_critter_has_flag(pid, flg) ((get_proto_data(pid, PROTO_CR_FLAGS) bwand flg) != 0)
#define proto_item_is_hidden(pid) proto_has_ext_flag(pid, HIDDEN_ITEM)

#define proto_ammo_pack_size(pid) get_proto_data(pid, PROTO_AM_PACK_SIZE)
#define proto_weapon_mag_size(pid) get_proto_data(pid, PROTO_WP_MAG_SIZE)

#endif

75 changes: 75 additions & 0 deletions scripts_src/_pbs_headers/inven_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#ifndef PBS_INVEN_UTILS_H
#define PBS_INVEN_UTILS_H


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

#include "../_pbs_headers/ecco_define.h"
#include "../_pbs_headers/math_ext.h"
#include "../_pbs_headers/ecco_log.h"


procedure reduce_item_count(variable invenObj, variable item, variable newCount) begin
variable count := obj_is_carrying_obj(invenObj, item);
if (newCount >= count) then return;

count := rm_mult_objs_from_inven(invenObj, item, count - newCount);
destroy_object(item);
end

#define inven_ammo_qty_formula(invenQty, packSize, ammoCount) ((invenQty - 1) * packSize + ammoCount)
#define inven_ammo_qty_obj_w_pack_size(invenObj, ammoObj, packSize) inven_ammo_qty_formula(obj_is_carrying_obj(invenObj, ammoObj), packSize, get_weapon_ammo_count(ammoObj))
#define inven_ammo_qty_obj(invenObj, ammoObj) inven_ammo_qty_obj_w_pack_size(invenObj, ammoObj, proto_ammo_pack_size(obj_pid(ammoObj)))

procedure inven_ammo_qty_pid(variable invenObj, variable pid) begin
variable
i := 0,
numBullets := 0,
packSize := proto_ammo_pack_size(pid),
item := inven_ptr(invenObj, 0);

while (item) do begin
if (obj_pid(item) == pid) then begin
numBullets += inven_ammo_qty_obj_w_pack_size(invenObj, item, packSize);
end
i += 1;
item := inven_ptr(invenObj, i);
end
return numBullets;
end

procedure inven_set_ammo_qty_pid(variable invenObj, variable pid, variable qty) begin
variable
packSize := proto_ammo_pack_size(pid),
packsNeeded := ceil(1.0 * qty / packSize),
ammoObj;

call set_items_qty_pid(invenObj, pid, packsNeeded);
if (qty > 0) then begin
ammoObj := obj_carrying_pid_obj(invenObj, pid);
set_weapon_ammo_count(ammoObj, qty - (packsNeeded - 1) * packSize);
end
end

procedure inven_set_ammo_qty_obj(variable invenObj, variable ammoObj, variable qty) begin
variable
pid := obj_pid(ammoObj),
packSize := proto_ammo_pack_size(pid),
packsNeeded := ceil(1.0 * qty / packSize),
packsActual := obj_is_carrying_obj(invenObj, ammoObj);

if (packsNeeded > packsActual) then begin
ammoObj := add_items_pid(invenObj, pid, packsNeeded - packsActual);
end
if (qty > 0) then begin
set_weapon_ammo_count(ammoObj, qty - (packsNeeded - 1) * packSize);
end
if (packsNeeded < packsActual) then begin
ammoObj := rm_mult_objs_from_inven(invenObj, ammoObj, packsActual - packsNeeded);
destroy_object(ammoObj);
end
end


#endif
91 changes: 79 additions & 12 deletions scripts_src/_pbs_headers/loot_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@

#include "../sfall/lib.arrays.h"
#include "../sfall/lib.inven.h"
#include "../sfall/lib.math.h"

#include "../_pbs_headers/math_ext.h"
#include "../_pbs_headers/ecco_log.h"
#include "math_ext.h"
#include "ecco_define.h"
#include "ecco_log.h"

#include "inven_utils.h"

procedure reduce_item_count(variable invenObj, variable item, variable newCount) begin
variable count := obj_is_carrying_obj(invenObj, item);
if (newCount >= count) then return;

count := rm_mult_objs_from_inven(invenObj, item, count - newCount);
destroy_object(item);
end

procedure calc_reduced_ammo_range(variable count, variable percentRange, variable emptyChance := 0) begin
if (emptyChance > 0 and random(0, 99) < emptyChance) then
Expand Down Expand Up @@ -113,17 +109,17 @@ end
*/
procedure reduce_item_in_stack(variable invenObj, variable item, variable pidList, variable percent) begin
if (percent <= 0
or (pidList and not is_in_array(obj_pid(item), pidList))) then
or (pidList andAlso not is_in_array(obj_pid(item), pidList))) then
return "";

// reduce with probability formula
variable
count := obj_is_carrying_obj(invenObj, item),
newCount := count * (100 - percent) / 100.0;

//debug_log_fmt("reducing %d -> %04f", count, newCount);
//debug_log_fmt("reducing %s: %d -> %04f", obj_name(item), count, newCount);
newCount := floor(newCount) + (random(0, 99) < (newCount - floor(newCount))*100);
if (newCount == count) then return "";
if (newCount >= count) then return "";

call reduce_item_count(invenObj, item, newCount);
return obj_name(item)+" ("+count+" -> "+newCount+"), ";
Expand All @@ -145,4 +141,75 @@ procedure reduce_item_on_ground(variable item, variable pidList, variable percen
return obj_name(item) + " (removed)";
end

procedure unload_weapon(variable item) begin
variable count := get_weapon_ammo_count(item);
if (count <= 0) then return "";

set_weapon_ammo_count(item, 0);
return string_format("%s mag ammo (%d -> 0)", obj_name(item), count);
end


procedure loot_trim_inventory(variable invenObj, variable pidToQty) begin
variable item, pid, list, n, qtyMax, qtyActual, rmvd, removeStats;
removeStats := "";

list := inven_as_array(invenObj);
foreach item in list begin
pid := obj_pid(item);
qtyActual := obj_is_carrying_obj(invenObj, item);
qtyMax := pidToQty[pid];
if (qtyActual > qtyMax) then begin
call inven_unwield_item(invenObj, item);
if (qtyMax > 0 and obj_item_subtype(item) == item_type_weapon) then
call unload_weapon(item);
removeStats += string_format("%s (%d -> %d); ", obj_name(item), qtyActual, qtyMax);
n := rm_mult_objs_from_inven(invenObj, item, qtyActual - qtyMax);
if (n != qtyActual - qtyMax) then
debug_err_fmt("Expected to remove %d of %s, but actually removed: %d", qtyActual - qtyMax, obj_name(item), n);
destroy_object(item);
end
end
if (removeStats != "") then
debug_log_fmt("Removed merchant loot in %s: %s", obj_name(invenObj), removeStats);
end

/*
procedure merchant_stock_cleanup(variable invenObj, variable percentMoney, variable percentAmmo, variable pidsToKeep) begin
variable item, pid, list, n, prob, qtyMax, qtyActual, packSize, removeStats;
removeStats := "";
item_caps_adjust(invenObj, -(item_caps_total(invenObj) * percentMoney / 100));
list := inven_as_array(invenObj);
foreach item in list begin
pid := obj_pid(item);
if (proto_item_is_hidden(pid) or pid == PID_BOTTLE_CAPS) then continue;
qtyMax := pidsToKeep[pid];
if (qtyMax == 0) then begin
removeStats += string_format("%s -> 0", obj_name(item));
call remove_item_obj(invenObj, item);
end else if (obj_item_subtype(item) == item_type_ammo) then begin
packSize := proto_ammo_pack_size(pid);
qtyMax := qtyMax * packSize * percentAmmo / 100;
qtyActual := inven_ammo_qty_obj_w_pack_size(invenObj, item, packSize);
if (qtyActual > qtyMax) then begin
removeStats += string_format("%s (%d -> %d)", obj_name(item), qtyActual, qtyMax);
call inven_set_ammo_qty_obj(invenObj, item, qtyAmmo * percentAmmo / 100);
end
end else begin
qtyActual := obj_is_carrying_obj(invenObj, item);
if (qtyActual > qtyMax) then begin
removeStats += string_format("%s (%d -> %d)", obj_name(item), qtyActual, qtyMax);
n := rm_mult_objs_from_inven(invenObj, item, qtyActual - qtyMax);
destroy_object(item);
end
end
end
if (removeStats != "") then
debug_log_fmt("Removed merchant loot in %s: %s", obj_name(invenObj), removeStats);
end
*/


#endif
14 changes: 14 additions & 0 deletions scripts_src/_pbs_headers/merchant_loot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef PBS_MERCHANT_LOOT_H
#define PBS_MERCHANT_LOOT_H

#include "loot_utils.h"

#ifdef MERCHANT_CRITICAL_ITEMS
procedure merchant_loot_trim(variable invenObj, variable pidToQty) begin
return loot_trim_inventory(invenObj, array_concat(pidToQty, array_to_set(MERCHANT_CRITICAL_ITEMS, 100)));
end
#else
#define merchant_loot_trim(invenObj, pidToQty) __ERROR__NO__MERCHANT_CRITICAL_ITEMS
#endif

#endif
58 changes: 58 additions & 0 deletions scripts_src/_pbs_headers/rpu_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef GAME_RPU_H
#define GAME_RPU_H

procedure critical_items_list_rpu begin
return [
PID_ACCOUNT_BOOK,
PID_ANNA_GOLD_LOCKET,
PID_BECKY_BOOK,
PID_BISHOPS_HOLODISK,
PID_BLUE_PASS_KEY,
//PID_CAR_FUEL_CELL,
PID_CAR_FUEL_CELL_CONTROLLER,
PID_CAR_FUEL_INJECTION,
PID_CELL_DOOR_KEY,
PID_COMPUTER_VOICE_MODULE,
PID_CORNELIUS_GOLD_WATCH,
PID_DAY_PASS,
PID_DR_HENRY_PAPERS,
//PID_ECON_HOLODISK,
PID_ENLIGHTENED_ONE_LETTER,
PID_EXCAVATOR_CHIP,
PID_FAKE_CITIZENSHIP,
PID_GECK,
PID_GECKO_DATA_DISK,
PID_GOLD_LOCKET,
PID_HY_MAG_PART,
PID_K9_MOTIVATOR,
PID_LYNETTE_HOLO,
PID_MOORE_BAD_BRIEFCASE,
PID_MOORE_GOOD_BRIEFCASE,
PID_NAV_COMPUTER_PARTS,
PID_PLASMA_TRANSFORMER,
PID_PRES_ACCESS_KEY,
PID_PRESIDENTIAL_PASS,
PID_RAMIREZ_BOX_CLOSED,
PID_RAMIREZ_BOX_OPEN,
PID_REACTOR_DATA_DISK,
PID_RED_PASS_KEY,
PID_RED_REACTOR_KEYCARD,
PID_SMITTY_MEAL,
PID_SPY_HOLO,
PID_TANKER_FOB,
PID_TRAPPER_TOWN_KEY,
PID_V15_COMPUTER_PART,
PID_VAULT_13_SHACK_KEY,
PID_VERTIBIRD_PLANS,
PID_VIC_RADIO,
PID_VIC_WATER_FLASK,
PID_WESTIN_HOLO,
PID_YELLOW_PASS_KEY,
PID_YELLOW_REACTOR_KEYCARD
];
end

#define MERCHANT_CRITICAL_ITEMS critical_items_list_rpu


#endif
12 changes: 6 additions & 6 deletions scripts_src/_pbs_main/gl_pbs_critter_loot.ssl
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ procedure deathanim2_handler begin
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))
and not proto_critter_has_flag(obj_pid(critter), CFLG_NODROP)
and not proto_critter_has_flag(obj_pid(critter), CFLG_SPECIAL))
then begin
call try_reduce_loot(critter);
end*/
Expand Down Expand Up @@ -182,14 +182,14 @@ procedure try_drop_weapons(variable critter) begin
i;

if (ini_weapon_drop_chance <= 0
or critter_proto_has_flag(obj_pid(critter), CFLG_NODROP)
or proto_critter_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);
if (weapon and obj_item_subtype(weapon) == item_type_weapon
and not is_unarmed_weapon_pid(obj_pid(weapon))
and not item_proto_is_hidden(obj_pid(weapon))) then
and not proto_item_is_hidden(obj_pid(weapon))) then
break;
else
weapon := 0;
Expand Down Expand Up @@ -222,12 +222,12 @@ procedure try_reduce_loot(variable critter) begin

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;
or proto_critter_has_flag(obj_pid(critter), CFLG_NOSTEAL)) then return;

list := inven_as_array(critter);
removeStats := "";
foreach item in list begin
if (item == 0 or item_proto_is_hidden(obj_pid(item))) then
if (item == 0 or proto_item_is_hidden(obj_pid(item))) then
continue;

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

0 comments on commit d4d238c

Please sign in to comment.