Files
sfall/sfall/Modules/Inventory.cpp
T
NovaRain 475503d632 Added a check for OutlineColor in gl_highlighting, in case player set it to negative values.
Split the push/pop instructions in ItemCountFix() into individual lines.
2017-05-07 21:20:17 +08:00

767 lines
18 KiB
C++

/*
* sfall
* Copyright (C) 2011 Timeslip
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
#include "..\InputFuncs.h"
#include "PartyControl.h"
#include "HookScripts.h"
#include "LoadGameHook.h"
#include "Inventory.h"
namespace sfall
{
static Delegate<DWORD> onAdjustFid;
static DWORD mode;
static DWORD maxItemSize;
static DWORD reloadWeaponKey = 0;
long& GetActiveItemMode() {
return fo::var::itemButtonItems[fo::var::itemCurrentItem].mode;
}
fo::GameObject* GetActiveItem() {
return fo::var::itemButtonItems[fo::var::itemCurrentItem].item;
}
void InventoryKeyPressedHook(DWORD dxKey, bool pressed, DWORD vKey) {
// TODO: move this out into a script
if (pressed && reloadWeaponKey && dxKey == reloadWeaponKey && IsMapLoaded() && (GetLoopFlags() & ~(COMBAT | PCOMBAT)) == 0) {
DWORD maxAmmo, curAmmo;
fo::GameObject* item = GetActiveItem();
maxAmmo = fo::func::item_w_max_ammo(item);
curAmmo = fo::func::item_w_curr_ammo(item);
if (maxAmmo != curAmmo) {
long &currentMode = GetActiveItemMode();
long previusMode = currentMode;
currentMode = 5; // reload mode
fo::func::intface_use_item();
if (previusMode != 5) {
// return to previous active item mode (if it wasn't "reload")
currentMode = previusMode - 1;
if (currentMode < 0) {
currentMode = 4;
}
fo::func::intface_toggle_item_state();
}
}
}
}
//TODO: Do we actually want to include this in the limit anyway?
static __declspec(naked) DWORD item_total_size(void* critter) {
__asm {
push ebx;
push ecx;
push edx;
push esi;
push edi;
push ebp;
mov ebp, eax;
test eax, eax;
jz loc_477F33;
lea edi, [eax+2Ch];
xor edx, edx;
mov ebx, [edi];
xor esi, esi;
test ebx, ebx;
jle loc_477ED3;
xor ebx, ebx;
loc_477EB7:
mov ecx, [edi+8];
mov eax, [ecx+ebx];
call fo::funcoffs::item_size_
imul eax, [ecx+ebx+4];
add ebx, 8;
inc edx;
mov ecx, [edi];
add esi, eax;
cmp edx, ecx;
jl loc_477EB7;
loc_477ED3:
mov eax, [ebp+20h];
and eax, 0F000000h;
sar eax, 18h;
cmp eax, 1;
jnz loc_477F31;
mov eax, ebp;
call fo::funcoffs::inven_right_hand_
mov edx, eax;
test eax, eax;
jz loc_477EFD;
test byte ptr [eax+27h], 2;
jnz loc_477EFD;
call fo::funcoffs::item_size_
add esi, eax;
loc_477EFD:
mov eax, ebp;
call fo::funcoffs::inven_left_hand_
test eax, eax;
jz loc_477F19;
cmp edx, eax;
jz loc_477F19;
test byte ptr [eax+27h], 1;
jnz loc_477F19;
call fo::funcoffs::item_size_
add esi, eax;
loc_477F19:
mov eax, ebp;
call fo::funcoffs::inven_worn_
test eax, eax;
jz loc_477F31;
test byte ptr [eax+27h], 4;
jnz loc_477F31;
call fo::funcoffs::item_size_
add esi, eax;
loc_477F31:
mov eax, esi;
loc_477F33:
pop ebp;
pop edi;
pop esi;
pop edx;
pop ecx;
pop ebx;
retn;
}
}
/*static const DWORD ObjPickupFail=0x49B70D;
static const DWORD ObjPickupEnd=0x49B6F8;
static const DWORD size_limit;
static __declspec(naked) void ObjPickupHook() {
__asm {
cmp edi, ds:[FO_VAR_obj_dude];
jnz end;
end:
lea edx, [esp+0x10];
mov eax, ecx;
jmp ObjPickupEnd;
}
}*/
static __declspec(naked) int CritterCheck() {
__asm {
push ebx;
push edx;
sub esp, 4;
mov ebx, eax;
cmp eax, dword ptr ds:[FO_VAR_obj_dude];
je single;
test mode, 3;
jnz run;
test mode, 2;
jz fail;
call fo::funcoffs::isPartyMember_;
test eax, eax;
jz end;
run:
test mode, 8;
jz single;
mov edx, esp;
mov eax, ebx;
call fo::funcoffs::proto_ptr_;
mov eax, [esp];
mov eax, [eax + 0xB0 + 40]; //The unused stat in the extra block
jmp end;
single:
mov eax, maxItemSize;
jmp end;
fail:
xor eax, eax;
end:
add esp, 4;
pop edx;
pop ebx;
retn;
}
}
static const DWORD IsOverloadedEnd = 0x42E68D;
static __declspec(naked) void CritterIsOverloadedHook() {
__asm {
and eax, 0xff;
jnz end;
mov eax, ebx;
call CritterCheck;
test eax, eax;
jz end;
xchg eax, ebx;
call item_total_size;
cmp eax, ebx;
setg al;
and eax, 0xff;
end:
jmp IsOverloadedEnd;
}
}
static const DWORD ItemAddMultiRet = 0x4772A6;
static const DWORD ItemAddMultiFail = 0x4771C7;
static __declspec(naked) void ItemAddMultiHook1() {
__asm {
push ebp;
mov eax, ecx;
call CritterCheck;
test eax, eax;
jz end;
mov ebp, eax;
mov eax, esi;
call fo::funcoffs::item_size_
mov edx, eax;
imul edx, ebx;
mov eax, ecx;
call item_total_size;
add edx, eax;
cmp edx, ebp;
jle end;
mov eax, -6; //TODO: Switch this to a lower number, and add custom error messages.
pop ebp;
jmp ItemAddMultiFail;
end:
pop ebp;
jmp ItemAddMultiRet;
}
}
static __declspec(naked) void ItemAddMultiHook2() {
__asm {
cmp eax, edi;
jl fail;
jmp ItemAddMultiHook1;
fail:
mov eax, -6;
jmp ItemAddMultiFail;
}
}
static const DWORD BarterAttemptTransactionHook1Fail = 0x474C81;
static const DWORD BarterAttemptTransactionHook1End = 0x474CA8;
static __declspec(naked) void BarterAttemptTransactionHook1() {
__asm {
cmp eax, edx;
jg fail;
mov eax, edi;
call CritterCheck;
test eax, eax;
jz end;
mov edx, eax;
mov eax, edi;
call item_total_size;
sub edx, eax;
mov eax, ebp;
call item_total_size;
cmp eax, edx;
jle end;
fail:
mov esi, 0x1f;
jmp BarterAttemptTransactionHook1Fail;
end:
jmp BarterAttemptTransactionHook1End;
}
}
static const DWORD BarterAttemptTransactionHook2Fail = 0x474CD8;
static const DWORD BarterAttemptTransactionHook2End = 0x474D01;
static __declspec(naked) void BarterAttemptTransactionHook2() {
__asm {
cmp eax, edx;
jg fail;
mov eax, ebx;
call CritterCheck;
test eax, eax;
jz end;
mov edx, eax;
mov eax, ebx;
call item_total_size;
sub edx, eax;
mov eax, esi;
call item_total_size;
cmp eax, edx;
jle end;
fail:
mov ecx, 0x20;
jmp BarterAttemptTransactionHook2Fail;
end:
jmp BarterAttemptTransactionHook2End;
}
}
static char SizeStr[16];
static char InvenFmt[32];
static const char* InvenFmt1 = "%s %d/%d %s %d/%d";
static const char* InvenFmt2 = "%s %d/%d";
static const char* _stdcall GetInvenMsg() {
const char* tmp = fo::MessageSearch(&fo::var::inventry_message_file, 35);
if (!tmp) return "S:";
else return tmp;
}
static void _stdcall strcpy_wrapper(char* buf, const char* str) {
strcpy(buf, str);
}
static const DWORD DisplayStatsEnd = 0x4725E5;
static __declspec(naked) void DisplayStatsHook() {
__asm {
call CritterCheck;
jz nolimit;
push eax;
mov eax, ds:[FO_VAR_stack];
push ecx;
push InvenFmt1;
push offset InvenFmt;
call strcpy_wrapper;
pop ecx;
mov eax, ds:[FO_VAR_stack];
call item_total_size;
push eax;
push ecx;
call GetInvenMsg;
pop ecx;
push eax;
jmp end;
nolimit:
push ecx;
push InvenFmt2;
push offset InvenFmt;
call strcpy_wrapper;
pop ecx;
push eax;
push eax;
push eax;
end:
mov eax, ds:[FO_VAR_stack];
mov edx, 0xc;
jmp DisplayStatsEnd;
}
}
static char SizeMsgBuf[32];
static const char* _stdcall FmtSizeMsg(int size) {
if(size==1) {
const char* tmp = fo::MessageSearch(&fo::var::proto_main_msg_file, 543);
if(!tmp) strcpy(SizeMsgBuf, "It occupies 1 unit.");
else sprintf(SizeMsgBuf, tmp, size);
} else {
const char* tmp = fo::MessageSearch(&fo::var::proto_main_msg_file, 542);
if(!tmp) sprintf(SizeMsgBuf, "It occupies %d units.", size);
else sprintf(SizeMsgBuf, tmp, size);
}
return SizeMsgBuf;
}
static __declspec(naked) void InvenObjExamineFuncHook() {
__asm {
call fo::funcoffs::inven_display_msg_
push edx;
push ecx;
mov eax, esi;
call fo::funcoffs::item_size_
push eax;
call FmtSizeMsg;
pop ecx;
pop edx;
call fo::funcoffs::inven_display_msg_
retn;
}
}
static std::string superStimMsg;
static int _stdcall SuperStimFix2(fo::GameObject* item, fo::GameObject* target) {
if (!item || !target) return 0;
DWORD itm_pid = item->protoId, target_pid = target->protoId;
if ((target_pid & 0xff000000) != 0x01000000) return 0;
if ((itm_pid & 0xff000000) != 0) return 0;
if ((itm_pid & 0xffffff) != 144) return 0;
DWORD curr_hp, max_hp;
curr_hp = fo::func::stat_level(target, fo::STAT_current_hp);
max_hp = fo::func::stat_level(target, fo::STAT_max_hit_points);
if (curr_hp < max_hp) return 0;
fo::func::display_print(superStimMsg.c_str());
return 1;
}
static const DWORD UseItemHookRet = 0x49C3D3;
static void __declspec(naked) SuperStimFix() {
__asm {
push eax;
push ecx;
push edx;
push edx;
push ebx;
call SuperStimFix2;
pop edx;
pop ecx;
test eax, eax;
jz end;
pop eax;
xor eax, eax;
retn;
end:
pop eax;
push ecx;
push esi;
push edi;
push ebp;
sub esp, 0x14;
jmp UseItemHookRet;
}
}
static int invenapcost;
static char invenapqpreduction;
void _stdcall SetInvenApCost(int a) {
invenapcost = a;
}
static const DWORD inven_ap_cost_hook_ret = 0x46E816;
static void __declspec(naked) inven_ap_cost_hook() {
_asm {
movzx ebx, byte ptr invenapqpreduction;
mul bl;
mov edx, invenapcost;
sub edx, eax;
mov eax, edx;
jmp inven_ap_cost_hook_ret;
}
}
static const DWORD add_check_for_item_ammo_cost_back = 0x4266EE;
// adds check for weapons which require more than 1 ammo for single shot (super cattle prod & mega power fist)
static void __declspec(naked) add_check_for_item_ammo_cost() {
__asm {
push edx
push ebx
sub esp, 4
call fo::funcoffs::item_w_curr_ammo_
mov ebx, eax
mov eax, ecx // weapon
mov edx, esp
mov dword ptr [esp], 1
pushad
push 1 // hook type
call AmmoCostHookWrapper
add esp, 4
popad
mov eax, [esp]
cmp eax, ebx
jle enoughammo
xor eax, eax // this will force "Out of ammo"
jmp end
enoughammo:
mov eax, 1 // this will force success
end:
add esp, 4
pop ebx
pop edx
jmp add_check_for_item_ammo_cost_back; // jump back
}
}
static const DWORD divide_burst_rounds_by_ammo_cost_back = 0x4234B9;
static void __declspec(naked) divide_burst_rounds_by_ammo_cost() {
__asm {
// ecx - current ammo, eax - burst rounds; need to set ebp
push edx
sub esp, 4
mov ebp, eax
mov eax, edx // weapon
mov dword ptr [esp], 1
mov edx, esp // *rounds
pushad
push 2
call AmmoCostHookWrapper
add esp, 4
popad
mov edx, 0
mov eax, ebp // rounds in burst
imul dword ptr [esp] // so much ammo is required for this burst
cmp eax, ecx
jle skip
mov eax, ecx // if more than current ammo, set it to current
skip:
idiv dword ptr [esp] // divide back to get proper number of rounds for damage calculations
mov ebp, eax
add esp, 4
pop edx
// end overwriten code
jmp divide_burst_rounds_by_ammo_cost_back; // jump back
}
}
static void __declspec(naked) SetDefaultAmmo() {
using namespace fo;
__asm {
push eax
push ebx
push edx
xchg eax, edx
mov ebx, eax
call fo::funcoffs::item_get_type_
cmp eax, item_type_weapon // is it item_type_weapon?
jne end // no
cmp dword ptr [ebx+0x3C], 0 // is there any ammo in the weapon?
jne end // yes
sub esp, 4
mov edx, esp
mov eax, [ebx+0x64] // eax = weapon pid
call fo::funcoffs::proto_ptr_
mov edx, [esp]
mov eax, [edx+0x5C] // eax = default ammo pid
mov [ebx+0x40], eax // set current ammo proto
add esp, 4
end:
pop edx
pop ebx
pop eax
retn
}
}
static const DWORD inven_action_cursor_hack_End = 0x4736CB;
static void __declspec(naked) inven_action_cursor_hack() {
__asm {
mov edx, [esp+0x1C]
call SetDefaultAmmo
cmp dword ptr [esp+0x18], 0
jmp inven_action_cursor_hack_End
}
}
static void __declspec(naked) item_add_mult_hook() {
__asm {
call SetDefaultAmmo
jmp fo::funcoffs::item_add_force_
}
}
static void __declspec(naked) inven_pickup_hook() {
__asm {
mov eax, ds:[FO_VAR_i_wid]
call fo::funcoffs::GNW_find_
mov ebx, [eax+0x8+0x0] // ebx = _i_wid.rect.x
mov ecx, [eax+0x8+0x4] // ecx = _i_wid.rect.y
mov eax, 176
add eax, ebx // x_start
add ebx, 176+60 // x_end
mov edx, 37
add edx, ecx // y_start
add ecx, 37+100 // y_end
call fo::funcoffs::mouse_click_in_
test eax, eax
jz end
mov edx, ds:[FO_VAR_curr_stack]
test edx, edx
jnz end
cmp edi, 1006 // Hands?
jae skip // Yes
skip:
xor eax, eax
end:
retn
}
}
int __stdcall ItemCountFixStdcall(fo::GameObject* who, fo::GameObject* item) {
int count = 0;
for (int i = 0; i < who->invenSize; i++) {
auto tableItem = &who->invenTable[i];
if (tableItem->object == item) {
count += tableItem->count;
} else if (fo::func::item_get_type(tableItem->object) == fo::item_type_container) {
count += ItemCountFixStdcall(tableItem->object, item);
}
}
return count;
}
void __declspec(naked) ItemCountFix() {
__asm {
push ebx;
push ecx;
push edx; // save state
push edx; // item
push eax; // container-object
call ItemCountFixStdcall;
pop edx;
pop ecx;
pop ebx; // restore
retn;
}
}
// reimplementation of adjust_fid engine function
// Differences from vanilla:
// - doesn't use art_vault_guy_num as default art, uses current critter FID instead
// - invokes onAdjustFid delegate that allows to hook into FID calculation
DWORD __stdcall adjust_fid_replacement2() {
using namespace fo;
DWORD fid;
if ((var::inven_dude->artFid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) {
DWORD frameNum;
DWORD weaponAnimCode = 0;
if (PartyControl::IsNpcControlled()) {
// if NPC is under control, use current FID of critter
frameNum = var::inven_dude->artFid & 0xFFF;
} else {
// vanilla logic:
frameNum = var::art_vault_guy_num;
auto critterPro = GetProto(var::inven_pid);
if (critterPro != nullptr) {
frameNum = critterPro->fid & 0xFFF;
}
if (var::i_worn != nullptr) {
auto armorPro = GetProto(var::i_worn->protoId);
DWORD armorFrameNum = func::stat_level(var::inven_dude, STAT_gender) == GENDER_FEMALE
? armorPro->item.armor.femaleFrameNum
: armorPro->item.armor.maleFrameNum;
if (armorFrameNum != -1) {
frameNum = armorFrameNum;
}
}
}
auto itemInHand = func::intface_is_item_right_hand()
? var::i_rhand
: var::i_lhand;
if (itemInHand != nullptr) {
auto itemPro = GetProto(itemInHand->protoId);
if (itemPro->item.type == item_type_weapon) {
weaponAnimCode = itemPro->item.weapon.animationCode;
}
}
fid = func::art_id(OBJ_TYPE_CRITTER, frameNum, 0, weaponAnimCode, 0);
} else {
fid = var::inven_dude->artFid;
}
var::i_fid = fid;
onAdjustFid.invoke(fid);
return var::i_fid;
}
void __declspec(naked) adjust_fid_replacement() {
__asm {
pushad;
call adjust_fid_replacement2;
popad;
mov eax, [FO_VAR_i_fid];
retn;
}
}
void InventoryReset() {
invenapcost = GetConfigInt("Misc", "InventoryApCost", 4);
}
void Inventory::init() {
OnKeyPressed() += InventoryKeyPressedHook;
LoadGameHook::OnGameReset() += InventoryReset;
MakeJump(fo::funcoffs::adjust_fid_, adjust_fid_replacement);
mode = GetConfigInt("Misc", "CritterInvSizeLimitMode", 0);
invenapcost = GetConfigInt("Misc", "InventoryApCost", 4);
invenapqpreduction = GetConfigInt("Misc", "QuickPocketsApCostReduction", 2);
MakeJump(0x46E80B, inven_ap_cost_hook);
if (mode > 7) {
mode = 0;
}
if (mode >= 4) {
mode -= 4;
SafeWrite8(0x477EB3, 0xeb);
}
if (mode) {
maxItemSize = GetConfigInt("Misc", "CritterInvSizeLimit", 100);
//Check item_add_multi (picking stuff from the floor, etc.)
HookCall(0x4771BD, &ItemAddMultiHook1);
MakeJump(0x47726D, ItemAddMultiHook2);
MakeJump(0x42E688, CritterIsOverloadedHook);
//Check capacity of player and barteree when bartering
MakeJump(0x474C78, BarterAttemptTransactionHook1);
MakeJump(0x474CCF, BarterAttemptTransactionHook2);
//Display total weight on the inventory screen
SafeWrite32(0x4725FF, (DWORD)&InvenFmt);
MakeJump(0x4725E0, DisplayStatsHook);
SafeWrite8(0x47260F, 0x20);
SafeWrite32(0x4725F9, 0x9c + 0xc);
SafeWrite8(0x472606, 0x10 + 0xc);
SafeWrite32(0x472632, 150);
SafeWrite8(0x472638, 0);
//Display item weight when examining
HookCall(0x472FFE, &InvenObjExamineFuncHook);
}
if (GetConfigInt("Misc", "SuperStimExploitFix", 0)) {
superStimMsg = Translate("sfall", "SuperStimExploitMsg", "You cannot use a super stim on someone who is not injured!");
MakeJump(0x49C3CC, SuperStimFix);
}
if (GetConfigInt("Misc", "CheckWeaponAmmoCost", 0)) {
MakeJump(0x4266E9, add_check_for_item_ammo_cost);
MakeJump(0x4234B3, divide_burst_rounds_by_ammo_cost);
}
reloadWeaponKey = GetConfigInt("Input", "ReloadWeaponKey", 0);
if (GetConfigInt("Misc", "StackEmptyWeapons", 0)) {
MakeJump(0x4736C6, inven_action_cursor_hack);
HookCall(0x4772AA, &item_add_mult_hook);
}
// Do not call the 'Move Items' window when using drap and drop to reload weapons in the inventory
int ReloadReserve = GetConfigInt("Misc", "ReloadReserve", 1);
if (ReloadReserve >= 0) {
SafeWrite32(0x47655F, ReloadReserve); // mov eax, ReloadReserve
SafeWrite32(0x476563, 0x097EC139); // cmp ecx, eax; jle 0x476570
SafeWrite16(0x476567, 0xC129); // sub ecx, eax
SafeWrite8(0x476569, 0x91); // xchg ecx, eax
};
// Move items out of bag/backpack and back into the main inventory list by dragging them to character's image
// (similar to Fallout 1 behavior)
HookCall(0x471457, &inven_pickup_hook);
// Move items to player's main inventory instead of the opened bag/backpack when confirming a trade
SafeWrite32(0x475CF2, FO_VAR_stack);
// Fix item_count function returning incorrect value when there is a container-item inside
MakeJump(0x47808C, ItemCountFix); // replacing item_count_ function
}
Delegate<DWORD>& Inventory::OnAdjustFid() {
return onAdjustFid;
}
}