diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index e367074a..65c085e8 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -238,6 +238,11 @@ #define party_member_list_critters party_member_list(0) #define party_member_list_all party_member_list(1) +// fake perks/traits add mode flags +#define ADD_PERK_MODE_TRAIT (1) // add to the player's traits +#define ADD_PERK_MODE_PERK (2) // add to the player's perks +#define ADD_PERK_MODE_REMOVE (4) // remove from the list of selectable perks + // sfall_funcX macros #define add_iface_tag sfall_func0("add_iface_tag") #define art_cache_clear sfall_func0("art_cache_clear") @@ -264,6 +269,8 @@ #define get_object_data(obj, offset) sfall_func2("get_object_data", obj, offset) #define get_outline(obj) sfall_func1("get_outline", obj) #define get_string_pointer(text) sfall_func1("get_string_pointer", text) +#define has_fake_perk_npc(npc, perk) sfall_func2("has_fake_perk_npc", npc, perk) +#define has_fake_trait_npc(npc, trait) sfall_func2("has_fake_trait_npc", npc, trait) #define intface_hide sfall_func0("intface_hide") #define intface_is_hidden sfall_func0("intface_is_hidden") #define intface_redraw sfall_func0("intface_redraw") @@ -282,6 +289,8 @@ #define set_cursor_mode(mode) sfall_func1("set_cursor_mode", mode) #define set_drugs_data(type, pid, value) sfall_func3("set_drugs_data", type, pid, value) #define set_dude_obj(critter) sfall_func1("set_dude_obj", critter) +#define set_fake_perk_npc(npc, perk, level, image, desc) sfall_func5("set_fake_perk_npc", npc, perk, level, image, desc) +#define set_fake_trait_npc(npc, trait, active, image, desc) sfall_func5("set_fake_trait_npc", npc, trait, active, image, desc) #define set_flags(obj, flags) sfall_func2("set_flags", obj, flags) #define set_iface_tag_text(tag, text, color) sfall_func3("set_iface_tag_text", tag, text, color) #define set_ini_setting(setting, value) sfall_func2("set_ini_setting", setting, value) @@ -290,6 +299,7 @@ #define set_outline(obj, color) sfall_func2("set_outline", obj, color) #define set_rest_heal_time(time) sfall_func1("set_rest_heal_time", time) #define set_rest_mode(mode) sfall_func1("set_rest_mode", mode) +#define set_selectable_perk_npc(npc, perk, active, image, desc) sfall_func5("set_selectable_perk_npc", npc, perk, active, image, desc) #define set_unique_id(obj) sfall_func1("set_unique_id", obj) #define unset_unique_id(obj) sfall_func2("set_unique_id", obj, -1) #define set_unjam_locks_time(time) sfall_func1("set_unjam_locks_time", time) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 14e4de8e..7bc48402 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -32,7 +32,7 @@ set_shader_mode tells sfall when to use a shader. The parameter is a set of 32 f force_graphics_refresh forces the screen to redraw at times when it normally wouldn't. If you're using animated shader, turning this option on is recommended. -The mapper manual lists the functions 'world_map_x_pos' and 'world_map_y_pos', which supposedly return the players x and y positions on the world map. get_world_map_x/y_pos are included here anyway, because I was unable to get those original functions to work, or even to find any evidence that they existed in game. +The mapper manual lists the functions 'world_map_x_pos' and 'world_map_y_pos', which supposedly return the player's x and y positions on the world map. get_world_map_x/y_pos are included here anyway, because I was unable to get those original functions to work, or even to find any evidence that they existed in game. set_pipboy_available will only accept 0 or 1 as an argument. Using any other value will cause the function to have no effect. Use 0 to disable the pipboy, and 1 to enable it. @@ -42,13 +42,13 @@ The 'type' value in the weapon knockback functions can be 0 or 1. If 0, the valu The get/set_sfall_global functions require an 8 characters long case sensitive string for the variable name. The variables behave the same as normal Fallout globals, except that they don't have to be declared beforehand in vault13.gam. Trying to get a variable which hasn't been set will always return 0. These functions are intended for use when a patch to a mod requires the addition of a new global variable, a case which would otherwise require the player to start a new game. -set_pickpocket_max and set_hit_chance_max effect all critters rather than just the player. set_skill_max can't be used to increase the skill cap above 300. set_perk_level_mod sets a modifier between +25 and -25 that is added/subtracted from the players level for the purposes of deciding which perks can be chosen. +set_pickpocket_max and set_hit_chance_max effect all critters rather than just the player. set_skill_max can't be used to increase the skill cap above 300. set_perk_level_mod sets a modifier between +25 and -25 that is added/subtracted from the player's level for the purposes of deciding which perks can be chosen. set_fake_trait and set_fake_perk can be used to add additional traits and perks to the character screen. They will be saved correctly when the player saves and reloads games, but by themselves they will have no further effect on the character. For perks, the allowed range for levels is between 0 and 100; setting the level to 0 removes that perk. For traits, the level must be 0 or 1. The image is a numeric id that corresponds to an entry in skilldex.lst. The name is limited to 63 characters and the description to 255 characters by sfall, but internal Fallout limits may be lower. has_fake_trait and has_fake_perk return the number of levels the player has of the perks/traits with the given name or ID of extra perk. -perk_add_mode, set_selectable_perk, set_perkbox_title, hide_real_perks, show_real_perks and clear_selectable_perks control the behaviour of the select a perk box. set_selectable_perk can be used to add additional items by setting the 'active' parameter to 1, and to remove them again by setting it to 0. set_perkbox_title can be used to change the title of the box, or by using "" it will be set back to the default. hide and show_real_perks can be used to prevent the dialog from displaying any of the original 119 perks. perk_add_mode modifies what happens when a fake perk is selected from the perks dialog. It is treated as a set of flags - if bit 1 is set then it is added to the players traits, if bit 2 is set it is added to the players perks, and if bit 3 is set it is removed from the list of selectable perks. The default is 0x2. clear_selectable_perks restores the dialog to its default state. +perk_add_mode, set_selectable_perk, set_perkbox_title, hide_real_perks, show_real_perks and clear_selectable_perks control the behaviour of the select a perk box. set_selectable_perk can be used to add additional items by setting the 'active' parameter to 1, and to remove them again by setting it to 0. set_perkbox_title can be used to change the title of the box, or by using "" it will be set back to the default. hide and show_real_perks can be used to prevent the dialog from displaying any of the original 119 perks. perk_add_mode modifies what happens when a fake perk is selected from the perks dialog. It is treated as a set of flags - if bit 1 is set then it is added to the player's traits, if bit 2 is set it is added to the player's perks, and if bit 3 is set it is removed from the list of selectable perks. The default is 0x2. clear_selectable_perks restores the dialog to its default state. show_iface_tag, hide_iface_tag and is_iface_tag_active relate to the boxes that appear above the interface such as SNEAK and LEVEL. You can use 3 for LEVEL and 4 for ADDICT, or the range from 5 to (4 + the value of BoxBarCount in ddraw.ini) for custom boxes. Remember to add your messages to intrface.msg and set up the font colours in ddraw.ini if you're going to use custom boxes. Starting from sfall 4.1, is_iface_tag_active can also be used to check 0 for SNEAK, 1 for POISONED, and 2 for RADIATED. @@ -591,7 +591,7 @@ Some utility/math functions are available: > void sfall_func6("draw_image_scaled", string/int pathFile/artId, int frame, int x, int y, int width, int height) - displays the specified FRM image in the active window created by vanilla CreateWin or sfall's create_win script function - pathFile/artId: path to the FRM file (e.g. "art\\inven\\5mmap.frm"), or its FRM ID number (e.g. 117440550, see specification of the FID format) -Optional arguments: +optional arguments: - frame: frame number, the first frame starts from zero - x/y: offset relative to the top-left corner of the window - width/height: image size, used to scale the image when displaying it. Pass -1 to either width or height to keep the aspect ratio when scaling @@ -599,6 +599,13 @@ Optional arguments: - NOTE: to omit optional arguments starting from the right, call the functions with different sfall_funcX (e.g. sfall_func4("draw_image", pathFile, frame, x, y)) - if draw_image_scaled is called without x/y/width/height arguments, the image will be scaled to fit the window without transparent background +> void sfall_func5("set_fake_perk_npc", object npc, string namePerk, int level, int image, string desc) +> void sfall_func5("set_fake_trait_npc", object npc, string nameTrait, int active, int image, string desc) +> void sfall_func5("set_selectable_perk_npc", object npc, string namePerk, int active, int image, string desc) +> int sfall_func2("has_fake_perk_npc", object npc, string namePerk) +> int sfall_func2("has_fake_trait_npc", object npc, string nameTrait) +- functions are similar to has_fake_*/set_fake_*/set_selectable_perk opcodes, but work on the specified party member NPC (including dude_obj) + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index e39aad6f..74c9564e 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -641,7 +641,9 @@ enum BodyType : long Robotic = 2 }; -#define OBJFLAG_CAN_WEAR_ITEMS (0xf000000) +#define PLAYER_ID (18000) + +#define OBJFLAG_CAN_WEAR_ITEMS (0xF000000) #define OBJFLAG_HELD_IN_RIGHT (0x10000) #define OBJFLAG_HELD_IN_LEFT (0x20000) diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index f4804491..738ab498 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -21,6 +21,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "LoadGameHook.h" +#include "PartyControl.h" #include "Perks.h" @@ -75,12 +76,13 @@ struct FakePerk { int Image; char Name[maxNameLen]; char Desc[maxDescLen]; - char reserve[510]; // empty block + char reserve[506]; // empty block + int ownerId; // 0 = the player, or ID number of party member NPC short id; // perk id (use last bytes of the description under the ID value for compatibility) FakePerk() {} - FakePerk(char* _name, int _level, int _image, char* _desc, short _id = -1) : id(_id), Name {0}, Desc {0}, reserve {0} { + FakePerk(const char* _name, int _level, int _image, const char* _desc, int npcId, short _id = -1) : ownerId(npcId), id(_id), Name {0}, Desc {0}, reserve {0} { Level = _level; Image = _image; strncpy_s(this->Name, _name, _TRUNCATE); @@ -151,7 +153,7 @@ static long _stdcall LevelUp() { } } - int level = fo::var::Level_; // Get players level + int level = fo::var::Level_; // Get player's level if (!((level + 1) % eachLevel)) fo::var::free_perk++; // Increment the number of perks owed return level; } @@ -191,11 +193,21 @@ void _stdcall SetPerkboxTitle(char* name) { } } -void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { - if (active < 0 || active > 1) return; +static bool IsOwnedFake(int ownerId) { + return ((!ownerId && !PartyControl::IsNpcControlled()) || ownerId == fo::var::obj_dude->id); +} + +static bool IsNotOwnedFake(int ownerId1, int ownerId2) { + return (ownerId1 != ownerId2); +} + +void Perks::SetSelectablePerk(const char* name, int active, int image, const char* desc, int npcID) { + if (active < 0) return; + if (active > 1) active = 1; size_t size = fakeSelectablePerks.size(); if (active == 0) { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeSelectablePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks.erase(fakeSelectablePerks.begin() + i); return; @@ -203,6 +215,7 @@ void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { } } else { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeSelectablePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks[i].Level = active; fakeSelectablePerks[i].Image = image; @@ -212,19 +225,17 @@ void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { } } if (size == fakeSelectablePerks.capacity()) fakeSelectablePerks.reserve(size + 10); - fakeSelectablePerks.emplace_back(name, active, image, desc); + fakeSelectablePerks.emplace_back(name, active, image, desc, npcID); } } -void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { - if (level > 100) { - level = 100; - } else if (level < 0 ) { - return; - } +void Perks::SetFakePerk(const char* name, int level, int image, const char* desc, int npcID) { + if (level < 0) return; + if (level > 100) level = 100; size_t size = fakePerks.size(); if (level == 0) { // remove perk from fakePerks for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakePerks[i].Name)) { fakePerks.erase(fakePerks.begin() + i); return; @@ -232,6 +243,7 @@ void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { } } else { // add or change the existing fake perk in fakePerks for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakePerks[i].Name)) { fakePerks[i].Level = level; fakePerks[i].Image = image; @@ -241,19 +253,17 @@ void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { } } if (size == fakePerks.capacity()) fakePerks.reserve(size + 10); - fakePerks.emplace_back(name, level, image, desc); + fakePerks.emplace_back(name, level, image, desc, npcID); } } -void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { - if (active > 1) { - active = 1; - } else if (active < 0) { - return; - } +void Perks::SetFakeTrait(const char* name, int active, int image, const char* desc, int npcID) { + if (active < 0) return; + if (active > 1) active = 1; size_t size = fakeTraits.size(); if (active == 0) { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, npcID)) continue; if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits.erase(fakeTraits.begin() + i); return; @@ -261,6 +271,7 @@ void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { } } else { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, npcID)) continue; if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits[i].Level = active; fakeTraits[i].Image = image; @@ -270,7 +281,7 @@ void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { } } if (size == fakeTraits.capacity()) fakeTraits.reserve(size + 5); - fakeTraits.emplace_back(name, active, image, desc); + fakeTraits.emplace_back(name, active, image, desc, npcID); } } @@ -279,7 +290,8 @@ static DWORD _stdcall HaveFakePerks() { } static long __fastcall GetFakePerkLevel(int id) { - return fakePerks[id - PERK_count].Level; + int i = id - PERK_count; + return IsOwnedFake(fakePerks[i].ownerId) ? fakePerks[i].Level : 0; } static long __fastcall GetFakePerkImage(int id) { @@ -290,16 +302,24 @@ static FakePerk* __fastcall GetFakePerk(int id) { return &fakePerks[id - PERK_count]; } +// Get level of taken perk static DWORD __fastcall GetFakeSelectPerkLevel(int id) { if (id < startFakeID) { - for (DWORD i = 0; i < fakePerks.size(); i++) { - if (fakePerks[i].id == id) return fakePerks[i].Level; + if (!PartyControl::IsNpcControlled()) { // extra perks are not available for controlled NPC + for (DWORD i = 0; i < fakePerks.size(); i++) { + if (fakePerks[i].id == id) return fakePerks[i].Level; + } } return 0; } - char* name = fakeSelectablePerks[id - startFakeID].Name; + long n = id - startFakeID; + const char* name = fakeSelectablePerks[n].Name; + int ownerID = fakeSelectablePerks[n].ownerId; for (DWORD i = 0; i < fakePerks.size(); i++) { - if (!strcmp(name, fakePerks[i].Name)) return fakePerks[i].Level; + if (ownerID != fakePerks[i].ownerId) continue; + if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; + } } return 0; } @@ -321,8 +341,10 @@ static FakePerk* __fastcall GetFakeSelectPerk(int id) { return &fakeSelectablePerks[id - startFakeID]; } +// Print a list of fake traits static DWORD HandleFakeTraits(int isSelect) { for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (!IsOwnedFake(fakeTraits[i].ownerId)) continue; if (fo::func::folder_print_line(fakeTraits[i].Name) && !isSelect) { isSelect = 1; var::folder_card_fid = fakeTraits[i].Image; @@ -340,11 +362,13 @@ static long __fastcall PlayerHasPerk(int &isSelectPtr) { for (int i = 0; i < PERK_count; i++) { if (fo::func::perk_level(fo::var::obj_dude, i)) return 0x43438A; // print perks } - return (!fakePerks.empty()) ? 0x43438A : 0x434446; // skip print perks + return (!fakePerks.empty()) + ? 0x43438A // print perks + : 0x434446; // skip print perks } static DWORD __fastcall HaveFakeTraits(int &isSelectPtr) { - return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; + return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; // print traits } static void __declspec(naked) PlayerHasPerkHack() { @@ -362,7 +386,7 @@ static void __declspec(naked) PlayerHasTraitHook() { push ecx; // isSelect mov ecx, esp; // ptr to isSelect call HaveFakeTraits; - pop ecx; // value from HandleFakeTraits + pop ecx; // isSelect value from HandleFakeTraits jmp eax; } } @@ -445,16 +469,21 @@ cLoop: // Build a table of perks ID numbers available for selection, data buffer has limited size for 119 perks static DWORD _stdcall HandleExtraSelectablePerks(DWORD available, DWORD* data) { - size_t count = extPerks.size(); - for (size_t i = 0; i < count; i++) { - if (available >= 119) return available; // exit if the buffer is overfull - if (fo::func::perk_can_add(fo::var::obj_dude, extPerks[i].id)) data[available++] = extPerks[i].id; + size_t count; + if (!PartyControl::IsNpcControlled()) { // extra perks are not available for controlled NPC + count = extPerks.size(); + for (size_t i = 0; i < count; i++) { + if (available >= 119) return available; // exit if the buffer is overfull + if (fo::func::perk_can_add(fo::var::obj_dude, extPerks[i].id)) data[available++] = extPerks[i].id; + } } count = fakeSelectablePerks.size(); for (size_t i = 0; i < count; i++) { - if (available >= 119) break; - // for fake perks, their ID should start from 256 - data[available++] = startFakeID + i; + if (IsOwnedFake(fakeSelectablePerks[i].ownerId)) { + if (available >= 119) break; + // for fake perks, their ID should start from 256 + data[available++] = startFakeID + i; + } } return available; // total number of perks available for selection } @@ -579,10 +608,10 @@ static void ApplyPerkEffect(long index, fo::GameObject* critter, long type) { } // Adds the selected perk to the player -static void _stdcall AddFakePerk(DWORD perkID) { +static long _stdcall AddFakePerk(DWORD perkID) { size_t count; bool matched = false; - if (perkID < startFakeID) { + if (perkID < startFakeID) { // extra perk can only be added to the player count = fakePerks.size(); for (size_t d = 0; d < count; d++) { if (fakePerks[d].id == perkID) { @@ -593,18 +622,20 @@ static void _stdcall AddFakePerk(DWORD perkID) { } } if (!matched) { // add to fakePerks - RemovePerkID.push_back(count); // index of the added perk int index = PerkSearchID(perkID); - fakePerks.emplace_back(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc, extPerks[index].id); // id same as perkID + if (index < 0) return -1; + RemovePerkID.push_back(count); // index of the added perk + fakePerks.emplace_back(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc, 0, extPerks[index].id); // id same as perkID } fo::func::perk_add_effect(fo::var::obj_dude, perkID); - return; + return 0; } // behavior for fake perk/trait perkID -= startFakeID; if (addPerkMode & 1) { // add perk to trait count = fakeTraits.size(); for (size_t d = 0; d < count; d++) { + if (IsNotOwnedFake(fakeTraits[d].ownerId, fakeSelectablePerks[perkID].ownerId)) continue; if (!strcmp(fakeTraits[d].Name, fakeSelectablePerks[perkID].Name)) { matched = true; break; @@ -619,6 +650,7 @@ static void _stdcall AddFakePerk(DWORD perkID) { matched = false; count = fakePerks.size(); for (size_t d = 0; d < count; d++) { + if (IsNotOwnedFake(fakePerks[d].ownerId, fakeSelectablePerks[perkID].ownerId)) continue; if (!strcmp(fakePerks[d].Name, fakeSelectablePerks[perkID].Name)) { RemovePerkID.push_back(d); fakePerks[d].Level++; @@ -634,6 +666,7 @@ static void _stdcall AddFakePerk(DWORD perkID) { if (addPerkMode & 4) { // delete from selectable perks RemoveSelectableID.push_back(perkID); //fakeSelectablePerks.remove_at(perkID); } + return 0; } // Adds perk from selection window to player @@ -645,7 +678,6 @@ static void __declspec(naked) AddPerkHook() { push edx; call AddFakePerk; pop ecx; - xor eax, eax; retn; normalPerk: push edx; @@ -1287,23 +1319,43 @@ void _stdcall AddPerkMode(DWORD mode) { addPerkMode = mode; } -DWORD HasFakePerk(const char* name, long perkId) { - if (perkId < PERK_count && name[0] == 0) return 0; +DWORD Perks::HasFakePerk(const char* name, long perkId) { + if ((perkId < PERK_count && name[0] == 0) || (perkId && PartyControl::IsNpcControlled())) return 0; for (DWORD i = 0; i < fakePerks.size(); i++) { if (perkId) { - if (fakePerks[i].id == perkId) { - return fakePerks[i].Level; - } - } else if (!strcmp(name, fakePerks[i].Name)) { - return fakePerks[i].Level; // current perk level + if (fakePerks[i].id == perkId) return fakePerks[i].Level; // current perk level + } else if (IsOwnedFake(fakePerks[i].ownerId) && !strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; } } return 0; } -DWORD _stdcall HasFakeTrait(const char* name) { +DWORD Perks::HasFakeTrait(const char* name) { if (name[0] == 0) return 0; for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (IsOwnedFake(fakeTraits[i].ownerId) && !strcmp(name, fakeTraits[i].Name)) { + return 1; + } + } + return 0; +} + +DWORD Perks::HasFakePerkOwner(const char* name, long objId) { + if (name[0] == 0) return 0; + for (DWORD i = 0; i < fakePerks.size(); i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, objId)) continue; + if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; + } + } + return 0; +} + +DWORD Perks::HasFakeTraitOwner(const char* name, long objId) { + if (name[0] == 0) return 0; + for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, objId)) continue; if (!strcmp(name, fakeTraits[i].Name)) { return 1; } diff --git a/sfall/Modules/Perks.h b/sfall/Modules/Perks.h index 4af5f7f8..5b448286 100644 --- a/sfall/Modules/Perks.h +++ b/sfall/Modules/Perks.h @@ -30,6 +30,15 @@ public: static void save(HANDLE file); static bool load(HANDLE file); + + static void SetSelectablePerk(const char* name, int active, int image, const char* desc, int npcID = 0); + static void SetFakePerk(const char* name, int level, int image, const char* desc, int npcID = 0); + static void SetFakeTrait(const char* name, int active, int image, const char* desc, int npcID = 0); + + static DWORD HasFakePerk(const char* name, long perkId); + static DWORD HasFakeTrait(const char* name); + static DWORD HasFakePerkOwner(const char* name, long objId); + static DWORD HasFakeTraitOwner(const char* name, long objId); }; void PerksEnterCharScreen(); @@ -43,17 +52,12 @@ void _stdcall SetPerkName(int id, char* value); void _stdcall SetPerkDesc(int id, char* value); void _stdcall SetPerkboxTitle(char* title); -void _stdcall SetSelectablePerk(char* name, int level, int image, char* desc); -void _stdcall SetFakePerk(char* name, int level, int image, char* desc); -void _stdcall SetFakeTrait(char* name, int level, int image, char* desc); + void _stdcall IgnoreDefaultPerks(); void _stdcall RestoreDefaultPerks(); - void _stdcall AddPerkMode(DWORD mode); -DWORD HasFakePerk(const char* name, long id); -DWORD _stdcall HasFakeTrait(const char* name); -void _stdcall ClearSelectablePerks(); +void _stdcall ClearSelectablePerks(); void _stdcall SetPerkFreq(int i); } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 4f65b559..7ee89042 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -24,6 +24,7 @@ #include "Interface.h" #include "Misc.h" #include "Objects.h" +#include "Perks.h" #include "Utils.h" #include "Worldmap.h" @@ -83,6 +84,8 @@ static const SfallMetarule metarules[] = { {"get_object_data", sf_get_object_data, 2, 2, {ARG_OBJECT, ARG_INT}}, {"get_outline", sf_get_outline, 1, 1, {ARG_OBJECT}}, {"get_string_pointer", sf_get_string_pointer, 1, 1, {ARG_STRING}}, + {"has_fake_perk_npc", sf_has_fake_perk_npc, 2, 2, {ARG_OBJECT, ARG_STRING}}, + {"has_fake_trait_npc", sf_has_fake_trait_npc, 2, 2, {ARG_OBJECT, ARG_STRING}}, {"intface_hide", sf_intface_hide, 0, 0}, {"intface_is_hidden", sf_intface_is_hidden, 0, 0}, {"intface_redraw", sf_intface_redraw, 0, 0}, @@ -101,6 +104,8 @@ static const SfallMetarule metarules[] = { {"set_cursor_mode", sf_set_cursor_mode, 1, 1, {ARG_INT}}, {"set_drugs_data", sf_set_drugs_data, 3, 3, {ARG_INT, ARG_INT, ARG_INT}}, {"set_dude_obj", sf_set_dude_obj, 1, 1, {ARG_OBJECT}}, + {"set_fake_perk_npc", sf_set_fake_perk_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {"set_fake_trait_npc", sf_set_fake_trait_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"set_flags", sf_set_flags, 2, 2, {ARG_OBJECT, ARG_INT}}, {"set_iface_tag_text", sf_set_iface_tag_text, 3, 3, {ARG_INT, ARG_STRING, ARG_INT}}, {"set_ini_setting", sf_set_ini_setting, 2, 2, {ARG_STRING, ARG_INTSTR}}, @@ -109,6 +114,7 @@ static const SfallMetarule metarules[] = { {"set_outline", sf_set_outline, 2, 2, {ARG_OBJECT, ARG_INT}}, {"set_rest_heal_time", sf_set_rest_heal_time, 1, 1, {ARG_INT}}, {"set_rest_mode", sf_set_rest_mode, 1, 1, {ARG_INT}}, + {"set_selectable_perk_npc", sf_set_selectable_perk_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"set_unique_id", sf_set_unique_id, 1, 2, {ARG_OBJECT, ARG_INT}}, {"set_unjam_locks_time", sf_set_unjam_locks_time, 1, 1, {ARG_INT}}, {"spatial_radius", sf_spatial_radius, 1, 1, {ARG_OBJECT}}, diff --git a/sfall/Modules/Scripting/Handlers/Perks.cpp b/sfall/Modules/Scripting/Handlers/Perks.cpp index 04dd7e8c..a470bfa8 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.cpp +++ b/sfall/Modules/Scripting/Handlers/Perks.cpp @@ -27,7 +27,6 @@ namespace sfall { namespace script { -using namespace fo; void __declspec(naked) op_get_perk_owed() { __asm { @@ -183,218 +182,44 @@ end: } } -void __declspec(naked) op_set_selectable_perk() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; +void sf_set_selectable_perk(OpcodeContext& ctx) { + Perks::SetSelectablePerk(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); +} - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds:[esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds:[esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; +void sf_set_fake_perk(OpcodeContext& ctx) { + Perks::SetFakePerk(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); +} - call SetSelectablePerk; -fail: - add esp, 32; - popad; - retn; +void sf_set_fake_trait(OpcodeContext& ctx) { + Perks::SetFakeTrait(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); +} + +const char* notPartyMemberErr = "%s() - the object is not a party member."; + +void sf_set_selectable_perk_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetSelectablePerk(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } } -void __declspec(naked) op_set_fake_perk() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds:[esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds:[esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; - - call SetFakePerk; -fail: - add esp, 32; - popad; - retn; +void sf_set_fake_perk_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetFakePerk(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } } -void __declspec(naked) op_set_fake_trait() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - push esi; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds:[esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds:[esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; - - call SetFakeTrait; -fail: - add esp, 32; - pop esi; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; +void sf_set_fake_trait_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetFakeTrait(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } } @@ -402,28 +227,24 @@ void __declspec(naked) op_set_perkbox_title() { __asm { push ebx; push ecx; - push edx; - push edi; - mov edi, eax; + mov ecx, eax; call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; + mov edx, eax; + mov eax, ecx; call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; + cmp dx, VAR_TYPE_STR2; + jz next; + cmp dx, VAR_TYPE_STR; + jnz end; next: - mov ebx, eax; - mov eax, edi; + mov ebx, eax; + mov eax, ecx; call fo::funcoffs::interpretGetString_; push eax; call SetPerkboxTitle; end: - pop edi; - pop edx; - pop ecx; - pop ebx; + pop ecx; + pop ebx; retn; } } @@ -456,43 +277,33 @@ void __declspec(naked) op_clear_selectable_perks() { } void sf_has_fake_perk(OpcodeContext& ctx) { - ctx.setReturn(HasFakePerk(ctx.arg(0).asString(), ctx.arg(0).asInt())); + ctx.setReturn(Perks::HasFakePerk(ctx.arg(0).asString(), ctx.arg(0).asInt())); } -void __declspec(naked) op_has_fake_trait() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov edi, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push eax; - call HasFakeTrait; -end: - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov eax, edi; - mov edx, VAR_TYPE_INT; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; +void sf_has_fake_trait(OpcodeContext& ctx) { + ctx.setReturn(Perks::HasFakeTrait(ctx.arg(0).strValue())); +} + +void sf_has_fake_perk_npc(OpcodeContext& ctx) { + long result = 0; + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + result = Perks::HasFakePerkOwner(ctx.arg(1).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } + ctx.setReturn(result); +} + +void sf_has_fake_trait_npc(OpcodeContext& ctx) { + long result = 0; + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + result = Perks::HasFakeTraitOwner(ctx.arg(1).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); + } + ctx.setReturn(result); } void __declspec(naked) op_perk_add_mode() { diff --git a/sfall/Modules/Scripting/Handlers/Perks.h b/sfall/Modules/Scripting/Handlers/Perks.h index ef7a693a..1e2d6c84 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.h +++ b/sfall/Modules/Scripting/Handlers/Perks.h @@ -37,11 +37,17 @@ void __declspec() op_set_perk_desc(); void __declspec() op_set_perk_value(); -void __declspec() op_set_selectable_perk(); +void sf_set_selectable_perk(OpcodeContext&); -void __declspec() op_set_fake_perk(); +void sf_set_fake_perk(OpcodeContext&); -void __declspec() op_set_fake_trait(); +void sf_set_fake_trait(OpcodeContext&); + +void sf_set_selectable_perk_npc(OpcodeContext&); + +void sf_set_fake_perk_npc(OpcodeContext&); + +void sf_set_fake_trait_npc(OpcodeContext&); void __declspec() op_set_perkbox_title(); @@ -53,7 +59,11 @@ void __declspec() op_clear_selectable_perks(); void sf_has_fake_perk(OpcodeContext&); -void __declspec() op_has_fake_trait(); +void sf_has_fake_trait(OpcodeContext&); + +void sf_has_fake_perk_npc(OpcodeContext&); + +void sf_has_fake_trait_npc(OpcodeContext&); void __declspec() op_perk_add_mode(); diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index 22694036..67312313 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -86,7 +86,11 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x1a5, "inc_npc_level", sf_inc_npc_level, 1, false, {ARG_INTSTR}}, {0x1ac, "get_ini_setting", sf_get_ini_setting, 1, true, {ARG_STRING}}, + {0x1bb, "set_fake_perk", sf_set_fake_perk, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {0x1bc, "set_fake_trait", sf_set_fake_trait, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {0x1bd, "set_selectable_perk", sf_set_selectable_perk, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {0x1c1, "has_fake_perk", sf_has_fake_perk, 1, true, {ARG_INTSTR}}, + {0x1c2, "has_fake_trait", sf_has_fake_trait, 1, true, {ARG_STRING}}, {0x1dc, "show_iface_tag", sf_show_iface_tag, 1, false, {ARG_INT}}, {0x1dd, "hide_iface_tag", sf_hide_iface_tag, 1, false, {ARG_INT}}, @@ -330,13 +334,9 @@ void InitNewOpcodes() { opcodes[0x1b8] = op_set_pc_stat_min; opcodes[0x1b9] = op_set_npc_stat_max; opcodes[0x1ba] = op_set_npc_stat_min; - opcodes[0x1bb] = op_set_fake_perk; - opcodes[0x1bc] = op_set_fake_trait; - opcodes[0x1bd] = op_set_selectable_perk; opcodes[0x1be] = op_set_perkbox_title; opcodes[0x1bf] = op_hide_real_perks; opcodes[0x1c0] = op_show_real_perks; - opcodes[0x1c2] = op_has_fake_trait; opcodes[0x1c3] = op_perk_add_mode; opcodes[0x1c4] = op_clear_selectable_perks; opcodes[0x1c5] = op_set_critter_hit_chance_mod;