Files
sfall/sfall/Modules/Scripting/Handlers/Objects.cpp
T
2021-05-26 06:58:18 +08:00

608 lines
18 KiB
C++

/*
* sfall
* Copyright (C) 2008-2016 The sfall team
*
* 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 "..\..\..\FalloutEngine\Fallout2.h"
#include "..\..\Combat.h"
#include "..\..\CritterStats.h"
#include "..\..\Drugs.h"
#include "..\..\Explosions.h"
#include "..\..\LoadGameHook.h"
#include "..\..\Objects.h"
#include "..\..\PartyControl.h"
#include "..\..\ScriptExtender.h"
#include "..\Arrays.h"
#include "Objects.h"
namespace sfall
{
namespace script
{
static const char* protoFailedLoad = "%s() - failed to load a prototype ID: %d";
static const char* nameNPCToInc;
static long pidNPCToInc;
static bool onceNpcLoop;
static void __cdecl IncNPCLevel(const char* fmt, const char* name) {
fo::GameObject* mObj;
__asm {
push edx;
mov eax, [ebp + 0x150 - 0x1C + 16]; // ebp <- esp
mov edx, [eax];
mov mObj, edx;
}
if ((pidNPCToInc && (mObj && mObj->protoId == pidNPCToInc)) || (!pidNPCToInc && !_stricmp(name, nameNPCToInc))) {
fo::func::debug_printf(fmt, name);
SafeWrite32(0x495C50, 0x01FB840F); // Want to keep this check intact. (restore)
SafeMemSet(0x495C77, CodeType::Nop, 6); // Check that the player is high enough for the npc to consider this level
//SafeMemSet(0x495C8C, CodeType::Nop, 6); // Check that the npc isn't already at its maximum level
SafeMemSet(0x495CEC, CodeType::Nop, 6); // Check that the npc hasn't already levelled up recently
if (!npcAutoLevelEnabled) {
SafeWrite8(0x495CFB, CodeType::JumpShort); // Disable random element
}
__asm mov [ebp + 0x150 - 0x28 + 16], 255; // set counter for exit loop
} else {
if (!onceNpcLoop) {
SafeWrite32(0x495C50, 0x01FCE9); // set goto next member
onceNpcLoop = true;
}
}
__asm pop edx;
}
void op_inc_npc_level(OpcodeContext& ctx) {
nameNPCToInc = ctx.arg(0).asString();
pidNPCToInc = ctx.arg(0).asInt(); // set to 0 if passing npc name
if (pidNPCToInc == 0 && nameNPCToInc[0] == 0) return;
MakeCall(0x495BF1, IncNPCLevel); // Replace the debug output
__asm call fo::funcoffs::partyMemberIncLevels_;
onceNpcLoop = false;
// restore code
SafeWrite32(0x495C50, 0x01FB840F);
__int64 data = 0x01D48C0F;
SafeWriteBytes(0x495C77, (BYTE*)&data, 6);
//SafeWrite16(0x495C8C, 0x8D0F);
//SafeWrite32(0x495C8E, 0x000001BF);
data = 0x0130850F;
SafeWriteBytes(0x495CEC, (BYTE*)&data, 6);
if (!npcAutoLevelEnabled) {
SafeWrite8(0x495CFB, CodeType::JumpZ);
}
}
void op_get_npc_level(OpcodeContext& ctx) {
int level = -1;
DWORD findPid = ctx.arg(0).asInt(); // set to 0 if passing npc name
const char *critterName, *name = ctx.arg(0).asString();
if (findPid || name[0] != 0) {
DWORD pid = 0;
auto members = fo::var::partyMemberList;
for (DWORD i = 0; i < fo::var::partyMemberCount; i++) {
if (!findPid) {
__asm {
mov eax, members;
mov eax, [eax];
call fo::funcoffs::critter_name_;
mov critterName, eax;
}
if (!_stricmp(name, critterName)) { // found npc
pid = members[i].object->protoId;
break;
}
} else {
DWORD _pid = members[i].object->protoId;
if (findPid == _pid) {
pid = _pid;
break;
}
}
}
if (pid) {
DWORD* pidList = fo::var::partyMemberPidList;
for (DWORD j = 0; j < fo::var::partyMemberMaxCount; j++) {
if (pidList[j] == pid) {
level = fo::var::partyMemberLevelUpInfoList[j * 3];
break;
}
}
}
}
ctx.setReturn(level);
}
void op_remove_script(OpcodeContext& ctx) {
auto object = ctx.arg(0).object();
if (object->scriptId != 0xFFFFFFFF) {
fo::func::scr_remove(object->scriptId);
object->scriptId = 0xFFFFFFFF;
}
}
#define exec_script_proc(script, proc) __asm { \
__asm mov eax, script \
__asm mov edx, proc \
__asm call fo::funcoffs::exec_script_proc_ \
}
void op_set_script(OpcodeContext& ctx) {
using fo::Scripts::start;
using fo::Scripts::map_enter_p_proc;
long scriptType;
auto object = ctx.arg(0).object();
unsigned long valArg = ctx.arg(1).rawValue();
long scriptIndex = valArg & ~0xF0000000;
if (scriptIndex == 0 || valArg > 0x8FFFFFFF) { // negative values are not allowed
ctx.printOpcodeError("%s() - the script index number is incorrect.", ctx.getOpcodeName());
return;
}
scriptIndex--;
if (object->scriptId != 0xFFFFFFFF) {
fo::func::scr_remove(object->scriptId);
object->scriptId = 0xFFFFFFFF;
}
if (object->IsCritter()) {
scriptType = fo::Scripts::ScriptTypes::SCRIPT_CRITTER;
} else {
scriptType = fo::Scripts::ScriptTypes::SCRIPT_ITEM;
}
fo::func::obj_new_sid_inst(object, scriptType, scriptIndex);
long scriptId = object->scriptId;
exec_script_proc(scriptId, start);
if ((valArg & 0x80000000) == 0) exec_script_proc(scriptId, map_enter_p_proc);
}
void op_create_spatial(OpcodeContext& ctx) {
using fo::Scripts::start;
DWORD scriptIndex = ctx.arg(0).rawValue(),
tile = ctx.arg(1).rawValue(),
elevation = ctx.arg(2).rawValue(),
radius = ctx.arg(3).rawValue();
long scriptId;
fo::ScriptInstance* scriptPtr;
if (fo::func::scr_new(&scriptId, fo::Scripts::ScriptTypes::SCRIPT_SPATIAL) == -1 || fo::func::scr_ptr(scriptId, &scriptPtr) == -1) return;
// set spatial script properties:
scriptPtr->scriptIdx = scriptIndex - 1;
scriptPtr->elevationAndTile = (elevation << 29) & 0xE0000000 | tile;
scriptPtr->spatialRadius = radius;
// this will load appropriate script program and link it to the script instance we just created:
exec_script_proc(scriptId, start);
ctx.setReturn(fo::func::scr_find_obj_from_program(scriptPtr->program));
}
#undef exec_script_proc
void mf_spatial_radius(OpcodeContext& ctx) {
auto spatialObj = ctx.arg(0).object();
fo::ScriptInstance* script;
if (fo::func::scr_ptr(spatialObj->scriptId, &script) != -1) {
ctx.setReturn(script->spatialRadius);
}
}
void op_get_script(OpcodeContext& ctx) {
auto scriptIndex = ctx.arg(0).object()->scriptIndex;
ctx.setReturn((scriptIndex >= 0) ? ++scriptIndex : 0);
}
void op_set_critter_burst_disable(OpcodeContext& ctx) {
SetNoBurstMode(ctx.arg(0).object(), ctx.arg(1).asBool());
}
void op_get_weapon_ammo_pid(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
long pid = -1;
fo::Proto* proto;
if (obj->IsItem() && GetProto(obj->protoId, &proto)) {
long type = proto->item.type;
if (type == fo::ItemType::item_type_weapon || type == fo::ItemType::item_type_misc_item) {
pid = obj->item.ammoPid;
}
}
ctx.setReturn(pid);
}
void op_set_weapon_ammo_pid(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
if (obj->IsNotItem()) return;
fo::Proto* proto;
if (GetProto(obj->protoId, &proto)) {
long type = proto->item.type;
if (type == fo::ItemType::item_type_weapon || type == fo::ItemType::item_type_misc_item) {
obj->item.ammoPid = ctx.arg(1).rawValue();
}
} else {
ctx.printOpcodeError(protoFailedLoad, ctx.getOpcodeName(), obj->protoId);
}
}
void op_get_weapon_ammo_count(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
ctx.setReturn((obj->IsItem()) ? obj->item.charges : 0);
}
void op_set_weapon_ammo_count(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
if (obj->IsItem()) obj->item.charges = ctx.arg(1).rawValue();
}
enum class BlockType {
BLOCKING_TYPE_BLOCK = 0,
BLOCKING_TYPE_SHOOT = 1,
BLOCKING_TYPE_AI = 2,
BLOCKING_TYPE_SIGHT = 3,
BLOCKING_TYPE_SCROLL = 4
};
static DWORD getBlockingFunc(BlockType type) {
switch (type) {
case BlockType::BLOCKING_TYPE_BLOCK: default:
return fo::funcoffs::obj_blocking_at_; // with calling hook
case BlockType::BLOCKING_TYPE_SHOOT:
return fo::funcoffs::obj_shoot_blocking_at_; // w/o calling hook
case BlockType::BLOCKING_TYPE_AI:
return fo::funcoffs::obj_ai_blocking_at_; // w/o calling hook
case BlockType::BLOCKING_TYPE_SIGHT:
return fo::funcoffs::obj_sight_blocking_at_; // w/o calling hook
//case 4:
// return obj_scroll_blocking_at_;
}
}
void op_make_straight_path(OpcodeContext& ctx) {
auto objFrom = ctx.arg(0).object();
DWORD tileTo = ctx.arg(1).rawValue();
BlockType type = (BlockType)ctx.arg(2).rawValue();
long flag = (type == BlockType::BLOCKING_TYPE_SHOOT) ? 32 : 0;
fo::GameObject* resultObj = nullptr;
fo::func::make_straight_path_func(objFrom, objFrom->tile, tileTo, 0, (DWORD*)&resultObj, flag, (void*)getBlockingFunc(type));
ctx.setReturn(resultObj);
}
void op_make_path(OpcodeContext& ctx) {
auto objFrom = ctx.arg(0).object();
auto tileTo = ctx.arg(1).rawValue();
auto func = getBlockingFunc((BlockType)ctx.arg(2).rawValue());
// if the object is not a critter, then there is no need to check tile (tileTo) for blocking
long checkFlag = (objFrom->IsCritter());
char pathData[800];
long pathLength = fo::func::make_path_func(objFrom, objFrom->tile, tileTo, pathData, checkFlag, (void*)func);
auto arrayId = CreateTempArray(pathLength, 0);
for (int i = 0; i < pathLength; i++) {
arrays[arrayId].val[i].set((long)pathData[i]);
}
ctx.setReturn(arrayId);
}
void op_obj_blocking_at(OpcodeContext& ctx) {
DWORD tile = ctx.arg(0).rawValue(),
elevation = ctx.arg(1).rawValue();
BlockType type = (BlockType)ctx.arg(2).rawValue();
fo::GameObject* resultObj = fo::func::obj_blocking_at_wrapper(0, tile, elevation, (void*)getBlockingFunc(type));
if (resultObj && type == BlockType::BLOCKING_TYPE_SHOOT && (resultObj->flags & fo::ObjectFlag::ShootThru)) { // don't know what this flag means, copy-pasted from the engine code
// this check was added because the engine always does exactly this when using shoot blocking checks
resultObj = nullptr;
}
ctx.setReturn(resultObj);
}
void op_tile_get_objects(OpcodeContext& ctx) {
DWORD tile = ctx.arg(0).rawValue(),
elevation = ctx.arg(1).rawValue();
DWORD arrayId = CreateTempArray(0, 4);
auto obj = fo::func::obj_find_first_at_tile(elevation, tile);
while (obj) {
arrays[arrayId].push_back(reinterpret_cast<long>(obj));
obj = fo::func::obj_find_next_at_tile();
}
ctx.setReturn(arrayId);
}
void op_get_party_members(OpcodeContext& ctx) {
auto includeHidden = ctx.arg(0).rawValue();
int actualCount = fo::var::partyMemberCount;
DWORD arrayId = CreateTempArray(0, 4);
for (int i = 0; i < actualCount; i++) {
fo::GameObject* obj = fo::var::partyMemberList[i].object;
if (includeHidden || (obj->IsCritter() && !fo::func::critter_is_dead(obj) && !(obj->flags & fo::ObjectFlag::Mouse_3d))) {
arrays[arrayId].push_back((long)obj);
}
}
ctx.setReturn(arrayId);
}
void mf_set_outline(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
int color = ctx.arg(1).rawValue();
obj->outline = color;
}
void mf_get_outline(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
ctx.setReturn(obj->outline);
}
void mf_set_flags(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
int flags = ctx.arg(1).rawValue();
obj->flags = flags;
}
void mf_get_flags(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
ctx.setReturn(obj->flags);
}
void mf_outlined_object(OpcodeContext& ctx) {
ctx.setReturn(fo::var::outlined_object);
}
void mf_set_dude_obj(OpcodeContext& ctx) {
auto obj = ctx.arg(0).object();
if (obj == nullptr || obj->IsCritter()) {
//if (!InCombat && obj && obj != PartyControl::RealDudeObject()) {
// ctx.printOpcodeError("%s() - controlling of the critter is only allowed in combat mode.", ctx.getMetaruleName());
//} else {
PartyControl::SwitchToCritter(obj);
//}
} else {
ctx.printOpcodeError("%s() - the object is not a critter.", ctx.getMetaruleName());
ctx.setReturn(-1);
}
}
void mf_real_dude_obj(OpcodeContext& ctx) {
ctx.setReturn(PartyControl::RealDudeObject());
}
void mf_car_gas_amount(OpcodeContext& ctx) {
ctx.setReturn(fo::var::carGasAmount);
}
void mf_lock_is_jammed(OpcodeContext& ctx) {
ctx.setReturn(fo::func::obj_lock_is_jammed(ctx.arg(0).object()));
}
void mf_unjam_lock(OpcodeContext& ctx) {
fo::func::obj_unjam_lock(ctx.arg(0).object());
}
void mf_set_unjam_locks_time(OpcodeContext& ctx) {
int time = ctx.arg(0).rawValue();
if (time < 0 || time > 127) {
ctx.printOpcodeError("%s() - time argument must be in the range of 0 to 127.", ctx.getMetaruleName());
ctx.setReturn(-1);
} else {
Objects::SetAutoUnjamLockTime(time);
}
}
void mf_item_make_explosive(OpcodeContext& ctx) {
DWORD pid = ctx.arg(0).rawValue();
DWORD pidActive = ctx.arg(1).rawValue();
DWORD min = ctx.arg(2).rawValue();
DWORD max = (ctx.numArgs() == 4) ? ctx.arg(3).rawValue() : min;
if (min > max) {
max = min;
ctx.printOpcodeError("%s() - Warning: value of max argument is less than the min argument.", ctx.getMetaruleName());
}
if (pid > 0 && pidActive > 0) {
Explosions::AddToExplosives(pid, pidActive, min, max);
} else {
ctx.printOpcodeError("%s() - invalid PID number, must be greater than 0.", ctx.getMetaruleName());
ctx.setReturn(-1);
}
}
void mf_get_dialog_object(OpcodeContext& ctx) {
ctx.setReturn(InDialog() ? fo::var::dialog_target : 0);
}
void mf_obj_under_cursor(OpcodeContext& ctx) {
ctx.setReturn(
fo::func::object_under_mouse(ctx.arg(0).asBool() ? 1 : -1, ctx.arg(1).rawValue(), fo::var::map_elevation)
);
}
void mf_get_loot_object(OpcodeContext& ctx) {
ctx.setReturn((GetLoopFlags() & INTFACELOOT) ? fo::var::target_stack[fo::var::target_curr_stack] : 0);
}
static bool protoMaxLimitPatch = false;
void op_get_proto_data(OpcodeContext& ctx) {
long result = -1;
fo::Proto* protoPtr;
int pid = ctx.arg(0).rawValue();
if (fo::CheckProtoID(pid) && fo::func::proto_ptr(pid, &protoPtr) != result) {
result = *(long*)((BYTE*)protoPtr + ctx.arg(1).rawValue());
} else {
ctx.printOpcodeError(protoFailedLoad, ctx.getOpcodeName(), pid);
}
ctx.setReturn(result);
}
void op_set_proto_data(OpcodeContext& ctx) {
int pid = ctx.arg(0).rawValue();
if (fo::CheckProtoID(pid) && CritterStats::SetProtoData(pid, ctx.arg(1).rawValue(), ctx.arg(2).rawValue()) != -1) {
if (!protoMaxLimitPatch) {
Objects::LoadProtoAutoMaxLimit();
protoMaxLimitPatch = true;
}
} else {
ctx.printOpcodeError(protoFailedLoad, ctx.getOpcodeName(), pid);
}
}
void mf_get_object_data(OpcodeContext& ctx) {
DWORD* object_ptr = (DWORD*)ctx.arg(0).rawValue();
ctx.setReturn(*(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue()));
}
void mf_set_object_data(OpcodeContext& ctx) {
DWORD* object_ptr = (DWORD*)ctx.arg(0).rawValue();
*(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue()) = ctx.arg(2).rawValue();
}
void mf_get_object_ai_data(OpcodeContext& ctx) {
fo::AIcap* cap = fo::func::ai_cap(ctx.arg(0).object());
DWORD arrayId, value = -1;
switch (ctx.arg(1).rawValue()) {
case 0:
value = cap->aggression;
break;
case 1:
value = cap->area_attack_mode;
break;
case 2:
value = cap->attack_who;
break;
case 3:
value = cap->best_weapon;
break;
case 4:
value = cap->chem_use;
break;
case 5:
value = cap->disposition;
break;
case 6:
value = cap->distance;
break;
case 7:
value = cap->max_dist;
break;
case 8:
value = cap->min_hp;
break;
case 9:
value = cap->min_to_hit;
break;
case 10:
value = cap->hurt_too_much; // DAM_BLIND/DAM_CRIP_* flags
break;
case 11:
value = cap->run_away_mode;
break;
case 12:
value = cap->secondary_freq;
break;
case 13:
value = cap->called_freq;
break;
case 14:
arrayId = CreateTempArray(3, 0);
arrays[arrayId].val[0].set(cap->chem_primary_desire);
arrays[arrayId].val[1].set(cap->chem_primary_desire1);
arrays[arrayId].val[2].set(cap->chem_primary_desire2);
value = arrayId;
break;
default:
ctx.printOpcodeError("%s() - invalid value for AI argument.", ctx.getMetaruleName());
}
ctx.setReturn(value);
}
void mf_set_drugs_data(OpcodeContext& ctx) {
int type = ctx.arg(0).rawValue();
int pid = ctx.arg(1).rawValue();
int val = ctx.arg(2).rawValue();
int result;
switch (type) {
case 0:
result = Drugs::SetDrugNumEffect(pid, val);
break;
case 1:
result = Drugs::SetDrugAddictTimeOff(pid, val);
break;
default:
ctx.printOpcodeError("%s() - invalid value for type argument.", ctx.getMetaruleName());
return;
}
if (result) {
ctx.printOpcodeError("%s() - drug PID not found in the configuration file.", ctx.getMetaruleName());
ctx.setReturn(-1);
}
}
void mf_set_unique_id(OpcodeContext& ctx) {
fo::GameObject* obj = ctx.arg(0).object();
long id;
if (ctx.arg(1).rawValue() == -1) {
id = fo::func::new_obj_id();
obj->id = id;
} else {
id = Objects::SetObjectUniqueID(obj);
}
ctx.setReturn(id);
}
void mf_objects_in_radius(OpcodeContext& ctx) {
long radius = ctx.arg(1).rawValue();
if (radius <= 0) radius = 1; else if (radius > 50) radius = 50;
long elev = ctx.arg(2).rawValue();
if (elev < 0) elev = 0; else if (elev > 2) elev = 2;
long type = (ctx.numArgs() > 3) ? ctx.arg(3).rawValue() : -1;
std::vector<fo::GameObject*> objects;
objects.reserve(25);
fo::GetObjectsTileRadius(objects, ctx.arg(0).rawValue(), radius, elev, type);
size_t sz = objects.size();
DWORD id = CreateTempArray(sz, 0);
for (size_t i = 0; i < sz; i++) {
arrays[id].val[i].set((long)objects[i]);
}
ctx.setReturn(id);
}
void mf_npc_engine_level_up(OpcodeContext& ctx) {
if (ctx.arg(0).asBool()) {
if (!npcEngineLevelUp) SafeWrite16(0x4AFC1C, 0x840F); // enable
npcEngineLevelUp = true;
} else {
if (npcEngineLevelUp) SafeWrite16(0x4AFC1C, 0xE990);
npcEngineLevelUp = false;
}
}
}
}