fallout2-ce/src/proto_instance.cc

2266 lines
60 KiB
C++
Raw Normal View History

2022-05-19 01:51:26 -07:00
#include "proto_instance.h"
#include "animation.h"
2022-06-19 03:32:42 -07:00
#include "art.h"
2022-05-19 01:51:26 -07:00
#include "color.h"
#include "combat.h"
#include "critter.h"
#include "debug.h"
#include "display_monitor.h"
#include "game.h"
#include "game_dialog.h"
2022-05-19 01:51:26 -07:00
#include "game_sound.h"
#include "geometry.h"
#include "interface.h"
#include "item.h"
#include "map.h"
2022-06-19 04:32:00 -07:00
#include "message.h"
2022-05-19 01:51:26 -07:00
#include "object.h"
#include "palette.h"
#include "perk.h"
#include "proto.h"
#include "queue.h"
#include "random.h"
#include "scripts.h"
#include "skill.h"
#include "stat.h"
#include "tile.h"
#include "world_map.h"
#include <assert.h>
#include <stdio.h>
2022-06-19 00:41:25 -07:00
#include <stdlib.h>
2022-05-19 01:51:26 -07:00
#include <string.h>
2022-06-19 04:32:00 -07:00
static int _obj_remove_from_inven(Object* critter, Object* item);
static int _obj_use_book(Object* item_obj);
static int _obj_use_flare(Object* critter_obj, Object* item_obj);
static int _obj_use_radio(Object* item_obj);
static int _obj_use_explosive(Object* explosive);
static int _obj_use_power_on_car(Object* ammo);
static int _obj_use_misc_item(Object* item_obj);
static int _protinstTestDroppedExplosive(Object* a1);
static int _protinst_default_use_item(Object* a1, Object* a2, Object* item);
static int useLadderDown(Object* a1, Object* ladder, int a3);
static int useLadderUp(Object* a1, Object* ladder, int a3);
static int useStairs(Object* a1, Object* stairs, int a3);
static int _set_door_state_open(Object* a1, Object* a2);
static int _set_door_state_closed(Object* a1, Object* a2);
static int _check_door_state(Object* a1, Object* a2);
2022-08-06 10:33:53 -07:00
static bool _obj_is_portal(Object* obj);
2022-06-19 04:32:00 -07:00
static bool _obj_is_lockable(Object* obj);
static bool _obj_is_openable(Object* obj);
static int objectOpenClose(Object* obj);
static bool objectIsJammed(Object* obj);
2022-05-19 01:51:26 -07:00
// 0x49A990
2022-06-19 04:32:00 -07:00
static MessageListItem stru_49A990;
2022-05-19 01:51:26 -07:00
// 0x49A9A0
int _obj_sid(Object* object, int* sidPtr)
{
*sidPtr = object->sid;
if (*sidPtr == -1) {
return -1;
}
return 0;
}
// 0x49A9B4
int _obj_new_sid(Object* object, int* sidPtr)
{
*sidPtr = -1;
Proto* proto;
if (protoGetProto(object->pid, &proto) == -1) {
return -1;
}
int sid;
int objectType = PID_TYPE(object->pid);
2022-05-19 01:51:26 -07:00
if (objectType < OBJ_TYPE_TILE) {
sid = proto->sid;
} else if (objectType == OBJ_TYPE_TILE) {
sid = proto->tile.sid;
} else if (objectType == OBJ_TYPE_MISC) {
sid = -1;
} else {
assert(false && "Should be unreachable");
}
if (sid == -1) {
return -1;
}
int scriptType = SID_TYPE(sid);
2022-05-19 01:51:26 -07:00
if (scriptAdd(sidPtr, scriptType) == -1) {
return -1;
}
Script* script;
if (scriptGetScript(*sidPtr, &script) == -1) {
return -1;
}
script->field_14 = sid & 0xFFFFFF;
if (objectType == OBJ_TYPE_CRITTER) {
object->field_80 = script->field_14;
}
if (scriptType == SCRIPT_TYPE_SPATIAL) {
script->sp.built_tile = builtTileCreate(object->tile, object->elevation);
2022-05-19 01:51:26 -07:00
script->sp.radius = 3;
}
if (object->id == -1) {
object->id = scriptsNewObjectId();
}
script->field_1C = object->id;
script->owner = object;
_scr_find_str_run_info(sid & 0xFFFFFF, &(script->field_50), *sidPtr);
return 0;
}
// 0x49AAC0
int _obj_new_sid_inst(Object* obj, int scriptType, int a3)
{
if (a3 == -1) {
return -1;
}
int sid;
if (scriptAdd(&sid, scriptType) == -1) {
return -1;
}
Script* script;
if (scriptGetScript(sid, &script) == -1) {
return -1;
}
script->field_14 = a3;
if (scriptType == SCRIPT_TYPE_SPATIAL) {
script->sp.built_tile = builtTileCreate(obj->tile, obj->elevation);
2022-05-19 01:51:26 -07:00
script->sp.radius = 3;
}
obj->sid = sid;
obj->id = scriptsNewObjectId();
script->field_1C = obj->id;
script->owner = obj;
_scr_find_str_run_info(a3 & 0xFFFFFF, &(script->field_50), sid);
if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {
2022-05-19 01:51:26 -07:00
obj->field_80 = script->field_14;
}
return 0;
}
// 0x49AC3C
int _obj_look_at(Object* a1, Object* a2)
{
return _obj_look_at_func(a1, a2, displayMonitorAddMessage);
}
// 0x49AC4C
int _obj_look_at_func(Object* a1, Object* a2, void (*a3)(char* string))
{
if (critterIsDead(a1)) {
return -1;
}
if (FID_TYPE(a2->fid) == OBJ_TYPE_TILE) {
2022-05-19 01:51:26 -07:00
return -1;
}
Proto* proto;
if (protoGetProto(a2->pid, &proto) == -1) {
return -1;
}
bool scriptOverrides = false;
if (a2->sid != -1) {
scriptSetObjects(a2->sid, a1, a2);
scriptExecProc(a2->sid, SCRIPT_PROC_LOOK_AT);
Script* script;
if (scriptGetScript(a2->sid, &script) == -1) {
return -1;
}
scriptOverrides = script->scriptOverrides;
}
if (!scriptOverrides) {
MessageListItem messageListItem;
if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER && critterIsDead(a2)) {
2022-05-19 01:51:26 -07:00
messageListItem.num = 491 + randomBetween(0, 1);
} else {
messageListItem.num = 490;
}
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
const char* objectName = objectGetName(a2);
char formattedText[260];
sprintf(formattedText, messageListItem.text, objectName);
a3(formattedText);
}
}
return -1;
}
// 0x49AD78
int _obj_examine(Object* a1, Object* a2)
{
return _obj_examine_func(a1, a2, displayMonitorAddMessage);
}
// Performs examine (reading description) action and passes resulting text
// to given callback.
//
// [critter] is a critter who's performing an action. Can be NULL.
// [fn] can be called up to three times when [a2] is an ammo.
//
// 0x49AD88
int _obj_examine_func(Object* critter, Object* target, void (*fn)(char* string))
{
if (critterIsDead(critter)) {
return -1;
}
if (FID_TYPE(target->fid) == OBJ_TYPE_TILE) {
2022-05-19 01:51:26 -07:00
return -1;
}
bool scriptOverrides = false;
if (target->sid != -1) {
scriptSetObjects(target->sid, critter, target);
scriptExecProc(target->sid, SCRIPT_PROC_DESCRIPTION);
Script* script;
if (scriptGetScript(target->sid, &script) == -1) {
return -1;
}
scriptOverrides = script->scriptOverrides;
}
if (!scriptOverrides) {
char* description = objectGetDescription(target);
if (description != NULL && strcmp(description, _proto_none_str) == 0) {
description = NULL;
}
if (description == NULL || *description == '\0') {
MessageListItem messageListItem;
messageListItem.num = 493;
if (!messageListGetItem(&gProtoMessageList, &messageListItem)) {
debugPrint("\nError: Can't find msg num!");
}
fn(messageListItem.text);
} else {
if (PID_TYPE(target->pid) != OBJ_TYPE_CRITTER || !critterIsDead(target)) {
2022-05-19 01:51:26 -07:00
fn(description);
}
}
}
if (critter == NULL || critter != gDude) {
return 0;
}
char formattedText[260];
int type = PID_TYPE(target->pid);
2022-05-19 01:51:26 -07:00
if (type == OBJ_TYPE_CRITTER) {
if (target != gDude && perkGetRank(gDude, PERK_AWARENESS) && !critterIsDead(target)) {
MessageListItem hpMessageListItem;
if (critterGetBodyType(target) != BODY_TYPE_BIPED) {
// It has %d/%d hps
hpMessageListItem.num = 537;
} else {
// 535: He has %d/%d hps
// 536: She has %d/%d hps
hpMessageListItem.num = 535 + critterGetStat(target, STAT_GENDER);
}
Object* item2 = critterGetItem2(target);
if (item2 != NULL && itemGetType(item2) != ITEM_TYPE_WEAPON) {
item2 = NULL;
}
if (!messageListGetItem(&gProtoMessageList, &hpMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
if (item2 != NULL) {
MessageListItem weaponMessageListItem;
if (ammoGetCaliber(item2) != 0) {
weaponMessageListItem.num = 547; // and is wielding a %s with %d/%d shots of %s.
} else {
weaponMessageListItem.num = 546; // and is wielding a %s.
}
if (!messageListGetItem(&gProtoMessageList, &weaponMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
char format[80];
sprintf(format, "%s%s", hpMessageListItem.text, weaponMessageListItem.text);
if (ammoGetCaliber(item2) != 0) {
const int ammoTypePid = weaponGetAmmoTypePid(item2);
const char* ammoName = protoGetName(ammoTypePid);
const int ammoCapacity = ammoGetCapacity(item2);
const int ammoQuantity = ammoGetQuantity(item2);
const char* weaponName = objectGetName(item2);
const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);
const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);
sprintf(formattedText,
format,
currentHitPoints,
maxiumHitPoints,
weaponName,
ammoQuantity,
ammoCapacity,
ammoName);
} else {
const char* weaponName = objectGetName(item2);
const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);
const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);
sprintf(formattedText,
format,
currentHitPoints,
maxiumHitPoints,
weaponName);
}
} else {
MessageListItem endingMessageListItem;
if (critterIsCrippled(target)) {
endingMessageListItem.num = 544; // ,
} else {
endingMessageListItem.num = 545; // .
}
if (!messageListGetItem(&gProtoMessageList, &endingMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);
const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);
sprintf(formattedText, hpMessageListItem.text, currentHitPoints, maxiumHitPoints);
strcat(formattedText, endingMessageListItem.text);
}
} else {
int v12 = 0;
if (critterIsCrippled(target)) {
v12 -= 2;
}
int v16;
const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);
const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);
if (currentHitPoints <= 0 || critterIsDead(target)) {
v16 = 0;
} else if (currentHitPoints == maxiumHitPoints) {
v16 = 4;
} else {
v16 = (currentHitPoints * 3) / maxiumHitPoints + 1;
}
MessageListItem hpMessageListItem;
hpMessageListItem.num = 500 + v16;
if (!messageListGetItem(&gProtoMessageList, &hpMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
if (v16 > 4) {
// Error: lookup_val out of range
hpMessageListItem.num = 550;
if (!messageListGetItem(&gProtoMessageList, &hpMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
debugPrint(hpMessageListItem.text);
return 0;
}
MessageListItem v66;
if (target == gDude) {
// You look %s
v66.num = 520 + v12;
if (!messageListGetItem(&gProtoMessageList, &v66)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
sprintf(formattedText, v66.text, hpMessageListItem.text);
} else {
// %s %s
v66.num = 521 + v12;
if (!messageListGetItem(&gProtoMessageList, &v66)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
MessageListItem v63;
v63.num = 522 + critterGetStat(target, STAT_GENDER);
if (!messageListGetItem(&gProtoMessageList, &v63)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
sprintf(formattedText, v63.text, hpMessageListItem.text);
}
}
if (critterIsCrippled(target)) {
const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);
const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);
MessageListItem v63;
v63.num = maxiumHitPoints >= currentHitPoints ? 531 : 530;
if (target == gDude) {
v63.num += 2;
}
if (!messageListGetItem(&gProtoMessageList, &v63)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
strcat(formattedText, v63.text);
}
fn(formattedText);
} else if (type == OBJ_TYPE_SCENERY) {
if (target->pid == PROTO_ID_CAR) {
MessageListItem carMessageListItem;
carMessageListItem.num = 549; // The car is running at %d%% power.
int car = gameGetGlobalVar(GVAR_PLAYER_GOT_CAR);
if (car == 0) {
carMessageListItem.num = 548; // The car doesn't look like it's working right now.
}
if (!messageListGetItem(&gProtoMessageList, &carMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
if (car != 0) {
sprintf(formattedText, carMessageListItem.text, 100 * carGetFuel() / 80000);
} else {
strcpy(formattedText, carMessageListItem.text);
}
fn(formattedText);
}
} else if (type == OBJ_TYPE_ITEM) {
int itemType = itemGetType(target);
if (itemType == ITEM_TYPE_WEAPON) {
if (ammoGetCaliber(target) != 0) {
MessageListItem weaponMessageListItem;
weaponMessageListItem.num = 526;
if (!messageListGetItem(&gProtoMessageList, &weaponMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
int ammoTypePid = weaponGetAmmoTypePid(target);
const char* ammoName = protoGetName(ammoTypePid);
int ammoCapacity = ammoGetCapacity(target);
int ammoQuantity = ammoGetQuantity(target);
sprintf(formattedText, weaponMessageListItem.text, ammoQuantity, ammoCapacity, ammoName);
fn(formattedText);
}
} else if (itemType == ITEM_TYPE_AMMO) {
// SFALL: Fix ammo details when examining in barter screen.
// CE: Underlying `gameDialogRenderSupplementaryMessage` cannot
// accumulate strings like `inventoryRenderItemDescription` does.
char ammoFormattedText[260 * 3];
ammoFormattedText[0] = '\0';
2022-05-19 01:51:26 -07:00
MessageListItem ammoMessageListItem;
ammoMessageListItem.num = 510;
if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
sprintf(formattedText,
ammoMessageListItem.text,
ammoGetArmorClassModifier(target));
if (fn == gameDialogRenderSupplementaryMessage) {
strcat(ammoFormattedText, formattedText);
} else {
fn(formattedText);
}
2022-05-19 01:51:26 -07:00
ammoMessageListItem.num++;
if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
sprintf(formattedText,
ammoMessageListItem.text,
ammoGetDamageResistanceModifier(target));
if (fn == gameDialogRenderSupplementaryMessage) {
strcat(ammoFormattedText, ", ");
strcat(ammoFormattedText, formattedText);
} else {
fn(formattedText);
}
2022-05-19 01:51:26 -07:00
ammoMessageListItem.num++;
if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) {
debugPrint("\nError: Can't find msg num!");
exit(1);
}
sprintf(formattedText,
ammoMessageListItem.text,
ammoGetDamageMultiplier(target),
ammoGetDamageDivisor(target));
if (fn == gameDialogRenderSupplementaryMessage) {
strcat(ammoFormattedText, ", ");
strcat(ammoFormattedText, formattedText);
strcat(ammoFormattedText, ".");
fn(ammoFormattedText);
} else {
fn(formattedText);
}
2022-05-19 01:51:26 -07:00
}
}
return 0;
}
// 0x49B650
int _obj_pickup(Object* critter, Object* item)
{
bool overriden = false;
if (item->sid != -1) {
scriptSetObjects(item->sid, critter, item);
scriptExecProc(item->sid, SCRIPT_PROC_PICKUP);
Script* script;
if (scriptGetScript(item->sid, &script) == -1) {
return -1;
}
overriden = script->scriptOverrides;
}
if (!overriden) {
int rc;
if (item->pid == PROTO_ID_MONEY) {
int amount = itemGetMoney(item);
if (amount <= 0) {
amount = 1;
}
rc = itemAttemptAdd(critter, item, amount);
if (rc == 0) {
itemSetMoney(item, 0);
}
} else {
rc = itemAttemptAdd(critter, item, 1);
}
if (rc == 0) {
Rect rect;
_obj_disconnect(item, &rect);
tileWindowRefreshRect(&rect, item->elevation);
} else {
MessageListItem messageListItem;
// You cannot pick up that item. You are at your maximum weight capacity.
messageListItem.num = 905;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
}
return 0;
}
// 0x49B73C
2022-06-19 04:32:00 -07:00
static int _obj_remove_from_inven(Object* critter, Object* item)
2022-05-19 01:51:26 -07:00
{
Rect updatedRect;
int fid;
int v11 = 0;
if (critterGetItem2(critter) == item) {
if (critter != gDude || interfaceGetCurrentHand()) {
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, FID_ANIM_TYPE(critter->fid), 0, critter->rotation);
2022-05-19 01:51:26 -07:00
objectSetFid(critter, fid, &updatedRect);
v11 = 2;
} else {
v11 = 1;
}
} else if (critterGetItem1(critter) == item) {
if (critter == gDude && !interfaceGetCurrentHand()) {
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, FID_ANIM_TYPE(critter->fid), 0, critter->rotation);
2022-05-19 01:51:26 -07:00
objectSetFid(critter, fid, &updatedRect);
v11 = 2;
} else {
v11 = 1;
}
} else if (critterGetArmor(critter) == item) {
if (critter == gDude) {
int v5 = 1;
Proto* proto;
if (protoGetProto(0x1000000, &proto) != -1) {
v5 = proto->fid;
}
fid = buildFid(OBJ_TYPE_CRITTER, v5, FID_ANIM_TYPE(critter->fid), (critter->fid & 0xF000) >> 12, critter->rotation);
2022-05-19 01:51:26 -07:00
objectSetFid(critter, fid, &updatedRect);
v11 = 3;
}
}
int rc = itemRemove(critter, item, 1);
if (v11 >= 2) {
tileWindowRefreshRect(&updatedRect, critter->elevation);
}
if (v11 <= 2 && critter == gDude) {
interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);
}
return rc;
}
// 0x49B8B0
int _obj_drop(Object* a1, Object* a2)
{
if (a2 == NULL) {
return -1;
}
bool scriptOverrides = false;
if (a1->sid != -1) {
scriptSetObjects(a1->sid, a2, NULL);
scriptExecProc(a1->sid, SCRIPT_PROC_IS_DROPPING);
Script* scr;
if (scriptGetScript(a1->sid, &scr) == -1) {
return -1;
}
scriptOverrides = scr->scriptOverrides;
}
if (scriptOverrides) {
return 0;
}
if (a2->sid != -1) {
scriptSetObjects(a2->sid, a1, a2);
scriptExecProc(a2->sid, SCRIPT_PROC_DROP);
Script* scr;
if (scriptGetScript(a2->sid, &scr) == -1) {
return -1;
}
scriptOverrides = scr->scriptOverrides;
}
if (scriptOverrides) {
return 0;
}
if (_obj_remove_from_inven(a1, a2) == 0) {
Object* owner = objectGetOwner(a1);
if (owner == NULL) {
owner = a1;
}
Rect updatedRect;
_obj_connect(a2, owner->tile, owner->elevation, &updatedRect);
tileWindowRefreshRect(&updatedRect, owner->elevation);
}
return 0;
}
// 0x49B9A0
int _obj_destroy(Object* obj)
{
if (obj == NULL) {
return -1;
}
int elev;
Object* owner = obj->owner;
if (owner != NULL) {
_obj_remove_from_inven(owner, obj);
} else {
elev = obj->elevation;
}
queueRemoveEvents(obj);
Rect rect;
objectDestroy(obj, &rect);
if (owner == NULL) {
tileWindowRefreshRect(&rect, elev);
}
return 0;
}
// Read a book.
//
// 0x49B9F0
2022-06-19 04:32:00 -07:00
static int _obj_use_book(Object* book)
2022-05-19 01:51:26 -07:00
{
MessageListItem messageListItem;
int messageId = -1;
int skill;
2022-08-02 11:52:32 -07:00
// SFALL
if (!booksGetInfo(book->pid, &messageId, &skill)) {
2022-05-19 01:51:26 -07:00
return -1;
}
if (isInCombat()) {
// You cannot do that in combat.
messageListItem.num = 902;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
return 0;
}
int increase = (100 - skillGetValue(gDude, skill)) / 10;
if (increase <= 0) {
messageId = 801;
} else {
if (perkGetRank(gDude, PERK_COMPREHENSION)) {
increase = 150 * increase / 100;
}
for (int i = 0; i < increase; i++) {
skillAddForce(gDude, skill);
}
}
paletteFadeTo(gPaletteBlack);
int intelligence = critterGetStat(gDude, STAT_INTELLIGENCE);
gameTimeAddSeconds(3600 * (11 - intelligence));
scriptsExecMapUpdateProc();
paletteFadeTo(_cmap);
// You read the book.
messageListItem.num = 800;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
messageListItem.num = messageId;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
return 1;
}
// Light a flare.
//
// 0x49BBA8
2022-06-19 04:32:00 -07:00
static int _obj_use_flare(Object* critter_obj, Object* flare)
2022-05-19 01:51:26 -07:00
{
MessageListItem messageListItem;
if (flare->pid != PROTO_ID_FLARE) {
return -1;
}
if ((flare->flags & OBJECT_USED) != 0) {
2022-05-19 01:51:26 -07:00
if (critter_obj == gDude) {
// The flare is already lit.
messageListItem.num = 588;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
} else {
if (critter_obj == gDude) {
// You light the flare.
messageListItem.num = 588;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
flare->pid = PROTO_ID_LIT_FLARE;
objectSetLight(flare, 8, 0x10000, NULL);
queueAddEvent(72000, flare, NULL, EVENT_TYPE_FLARE);
}
return 0;
}
// 0x49BC60
2022-06-19 04:32:00 -07:00
static int _obj_use_radio(Object* item)
2022-05-19 01:51:26 -07:00
{
Script* scr;
if (item->sid == -1) {
return -1;
}
scriptSetObjects(item->sid, gDude, item);
scriptExecProc(item->sid, SCRIPT_PROC_USE);
if (scriptGetScript(item->sid, &scr) == -1) {
return -1;
}
return 0;
}
// 0x49BCB4
2022-06-19 04:32:00 -07:00
static int _obj_use_explosive(Object* explosive)
2022-05-19 01:51:26 -07:00
{
MessageListItem messageListItem;
int pid = explosive->pid;
2022-08-05 07:43:00 -07:00
// SFALL
if (!explosiveIsExplosive(pid)) {
2022-05-19 01:51:26 -07:00
return -1;
}
if ((explosive->flags & OBJECT_USED) != 0) {
2022-05-19 01:51:26 -07:00
// The timer is already ticking.
messageListItem.num = 590;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
} else {
int seconds = _inven_set_timer(explosive);
if (seconds != -1) {
// You set the timer.
messageListItem.num = 589;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
2022-08-05 07:43:00 -07:00
// SFALL
explosiveActivate(&(explosive->pid));
2022-05-19 01:51:26 -07:00
int delay = 10 * seconds;
int roll;
if (perkHasRank(gDude, PERK_DEMOLITION_EXPERT)) {
roll = ROLL_SUCCESS;
} else {
roll = skillRoll(gDude, SKILL_TRAPS, 0, NULL);
}
int eventType;
switch (roll) {
case ROLL_CRITICAL_FAILURE:
delay = 0;
eventType = EVENT_TYPE_EXPLOSION_FAILURE;
break;
case ROLL_FAILURE:
eventType = EVENT_TYPE_EXPLOSION_FAILURE;
delay /= 2;
break;
default:
eventType = EVENT_TYPE_EXPLOSION;
break;
}
queueAddEvent(delay, explosive, NULL, eventType);
}
}
return 2;
}
// Recharge car with given item
// Returns -1 when car cannot be recharged with given item.
// Returns 1 when car is recharged.
//
// 0x49BDE8
2022-06-19 04:32:00 -07:00
static int _obj_use_power_on_car(Object* item)
2022-05-19 01:51:26 -07:00
{
MessageListItem messageListItem;
int messageNum;
memcpy(&messageListItem, &stru_49A990, sizeof(messageListItem));
bool isEnergy = false;
int energyDensity;
switch (item->pid) {
case PROTO_ID_SMALL_ENERGY_CELL:
energyDensity = 16000;
isEnergy = true;
break;
case PROTO_ID_MICRO_FUSION_CELL:
energyDensity = 40000;
isEnergy = true;
break;
}
if (!isEnergy) {
return -1;
}
2022-08-06 07:49:24 -07:00
// SFALL: Fix for cells getting consumed even when the car is already fully
// charged.
int rc;
2022-05-19 01:51:26 -07:00
if (carGetFuel() < CAR_FUEL_MAX) {
int energy = ammoGetQuantity(item) * energyDensity;
int capacity = ammoGetCapacity(item);
// NOTE: that function will never return -1
if (carAddFuel(energy / capacity) == -1) {
return -1;
}
// You charge the car with more power.
messageNum = 595;
2022-08-06 07:49:24 -07:00
rc = 1;
2022-05-19 01:51:26 -07:00
} else {
// The car is already full of power.
messageNum = 596;
2022-08-06 07:49:24 -07:00
rc = 0;
2022-05-19 01:51:26 -07:00
}
char* text = getmsg(&gProtoMessageList, &messageListItem, messageNum);
displayMonitorAddMessage(text);
2022-08-06 07:49:24 -07:00
return rc;
2022-05-19 01:51:26 -07:00
}
// 0x49BE88
2022-06-19 04:32:00 -07:00
static int _obj_use_misc_item(Object* item)
2022-05-19 01:51:26 -07:00
{
if (item == NULL) {
return -1;
}
switch (item->pid) {
case PROTO_ID_RAMIREZ_BOX_CLOSED:
case PROTO_ID_RAIDERS_MAP:
case PROTO_ID_CATS_PAW_ISSUE_5:
case PROTO_ID_PIP_BOY_LINGUAL_ENHANCER:
case PROTO_ID_SURVEY_MAP:
case PROTO_ID_PIP_BOY_MEDICAL_ENHANCER:
if (item->sid == -1) {
return 1;
}
scriptSetObjects(item->sid, gDude, item);
scriptExecProc(item->sid, SCRIPT_PROC_USE);
Script* scr;
if (scriptGetScript(item->sid, &scr) == -1) {
return -1;
}
return 1;
}
return -1;
}
// 0x49BF38
int _protinst_use_item(Object* critter, Object* item)
{
int rc;
MessageListItem messageListItem;
switch (itemGetType(item)) {
case ITEM_TYPE_DRUG:
rc = -1;
break;
case ITEM_TYPE_WEAPON:
case ITEM_TYPE_MISC:
rc = _obj_use_book(item);
if (rc != -1) {
break;
}
rc = _obj_use_flare(critter, item);
if (rc == 0) {
break;
}
rc = _obj_use_misc_item(item);
if (rc != -1) {
break;
}
rc = _obj_use_radio(item);
if (rc == 0) {
break;
}
rc = _obj_use_explosive(item);
if (rc == 0 || rc == 2) {
break;
}
// TODO: Not sure about these two conditions.
if (miscItemIsConsumable(item)) {
rc = _item_m_use_charged_item(critter, item);
if (rc == 0) {
break;
}
}
// FALLTHROUGH
default:
// That does nothing
messageListItem.num = 582;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
rc = -1;
}
return rc;
}
// 0x49BFE8
2022-06-19 04:32:00 -07:00
static int _protinstTestDroppedExplosive(Object* a1)
2022-05-19 01:51:26 -07:00
{
2022-08-05 07:43:00 -07:00
// SFALL
if (explosiveIsActiveExplosive(a1->pid)) {
2022-05-19 01:51:26 -07:00
Attack attack;
attackInit(&attack, gDude, 0, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);
attack.attackerFlags = DAM_HIT;
attack.tile = gDude->tile;
_compute_explosion_on_extras(&attack, 0, 0, 1);
int team = gDude->data.critter.combat.team;
Object* v2 = NULL;
for (int index = 0; index < attack.extrasLength; index++) {
Object* v5 = attack.extras[index];
if (v5 != gDude
&& v5->data.critter.combat.team != team
&& statRoll(v5, STAT_PERCEPTION, 0, NULL) >= 2) {
_critter_set_who_hit_me(v5, gDude);
if (v2 == NULL) {
v2 = v5;
}
}
}
if (v2 != NULL && !isInCombat()) {
STRUCT_664980 attack;
attack.attacker = v2;
attack.defender = gDude;
attack.actionPointsBonus = 0;
attack.accuracyBonus = 0;
attack.minDamage = 0;
attack.maxDamage = 99999;
attack.field_1C = 0;
scriptsRequestCombat(&attack);
}
}
return 0;
}
// 0x49C124
int _obj_use_item(Object* a1, Object* a2)
{
int rc = _protinst_use_item(a1, a2);
if (rc == 1 || rc == 2) {
Object* root = objectGetOwner(a2);
if (root != NULL) {
int flags = a2->flags & OBJECT_IN_ANY_HAND;
itemRemove(root, a2, 1);
Object* v8 = _item_replace(root, a2, flags);
if (root == gDude) {
int leftItemAction;
int rightItemAction;
interfaceGetItemActions(&leftItemAction, &rightItemAction);
if (v8 == NULL) {
if ((flags & OBJECT_IN_LEFT_HAND) != 0) {
leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
} else if ((flags & OBJECT_IN_RIGHT_HAND) != 0) {
rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
} else {
leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
}
}
interfaceUpdateItems(false, leftItemAction, rightItemAction);
}
}
if (rc == 1) {
_obj_destroy(a2);
} else if (rc == 2 && root != NULL) {
Rect updatedRect;
_obj_connect(a2, root->tile, root->elevation, &updatedRect);
tileWindowRefreshRect(&updatedRect, root->elevation);
_protinstTestDroppedExplosive(a2);
}
rc = 0;
}
scriptsExecMapUpdateProc();
return rc;
}
// 0x49C240
2022-06-19 04:32:00 -07:00
static int _protinst_default_use_item(Object* a1, Object* a2, Object* item)
2022-05-19 01:51:26 -07:00
{
char formattedText[90];
MessageListItem messageListItem;
int rc;
switch (itemGetType(item)) {
case ITEM_TYPE_DRUG:
if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {
2022-05-19 01:51:26 -07:00
if (a1 == gDude) {
// That does nothing
messageListItem.num = 582;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
return -1;
}
if (critterIsDead(a2)) {
// 583: To your dismay, you realize that it is already dead.
// 584: As you reach down, you realize that it is already dead.
// 585: Alas, you are too late.
// 586: That won't work on the dead.
messageListItem.num = 583 + randomBetween(0, 3);
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
return -1;
}
rc = _item_d_take_drug(a2, item);
if (a1 == gDude && a2 != gDude) {
// TODO: Looks like there is bug in this branch, message 580 will never be shown,
// as we can only be here when target is not dude.
// 580: You use the %s.
// 581: You use the %s on %s.
messageListItem.num = 580 + (a2 != gDude);
if (!messageListGetItem(&gProtoMessageList, &messageListItem)) {
return -1;
}
sprintf(formattedText, messageListItem.text, objectGetName(item), objectGetName(a2));
displayMonitorAddMessage(formattedText);
}
if (a2 == gDude) {
interfaceRenderHitPoints(true);
}
return rc;
case ITEM_TYPE_AMMO:
2022-08-06 07:49:24 -07:00
// SFALL: Fix for being able to charge the car by using cells on other
// scenery/critters.
if (a2->pid == PROTO_ID_CAR || a2->pid == PROTO_ID_CAR_TRUNK) {
rc = _obj_use_power_on_car(item);
if (rc == 1) {
return 1;
} else if (rc == 0) {
return -1;
}
2022-05-19 01:51:26 -07:00
}
break;
case ITEM_TYPE_WEAPON:
case ITEM_TYPE_MISC:
rc = _obj_use_flare(a1, item);
if (rc == 0) {
return 0;
}
break;
}
messageListItem.num = 582;
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
2022-08-11 03:41:06 -07:00
sprintf(formattedText, "%s", messageListItem.text);
2022-05-19 01:51:26 -07:00
displayMonitorAddMessage(formattedText);
}
return -1;
}
// 0x49C3CC
int _protinst_use_item_on(Object* a1, Object* a2, Object* item)
{
int messageId = -1;
int criticalChanceModifier = 0;
int skill = -1;
switch (item->pid) {
case PROTO_ID_DOCTORS_BAG:
// The supplies in the Doctor's Bag run out.
messageId = 900;
criticalChanceModifier = 20;
skill = SKILL_DOCTOR;
break;
case PROTO_ID_FIRST_AID_KIT:
// The supplies in the First Aid Kit run out.
messageId = 901;
criticalChanceModifier = 20;
skill = SKILL_FIRST_AID;
break;
case PROTO_ID_PARAMEDICS_BAG:
// The supplies in the Paramedic's Bag run out.
messageId = 910;
criticalChanceModifier = 40;
skill = SKILL_DOCTOR;
break;
case PROTO_ID_FIELD_MEDIC_FIRST_AID_KIT:
// The supplies in the Field Medic First Aid Kit run out.
messageId = 911;
criticalChanceModifier = 40;
skill = SKILL_FIRST_AID;
break;
}
if (skill == -1) {
Script* script;
if (item->sid == -1) {
if (a2->sid == -1) {
return _protinst_default_use_item(a1, a2, item);
}
scriptSetObjects(a2->sid, a1, item);
scriptExecProc(a2->sid, SCRIPT_PROC_USE_OBJ_ON);
if (scriptGetScript(a2->sid, &script) == -1) {
return -1;
}
if (!script->scriptOverrides) {
return _protinst_default_use_item(a1, a2, item);
}
} else {
scriptSetObjects(item->sid, a1, a2);
scriptExecProc(item->sid, SCRIPT_PROC_USE_OBJ_ON);
if (scriptGetScript(item->sid, &script) == -1) {
return -1;
}
if (script->field_28 == 0) {
if (a2->sid == -1) {
return _protinst_default_use_item(a1, a2, item);
}
scriptSetObjects(a2->sid, a1, item);
scriptExecProc(a2->sid, SCRIPT_PROC_USE_OBJ_ON);
Script* script;
if (scriptGetScript(a2->sid, &script) == -1) {
return -1;
}
if (!script->scriptOverrides) {
return _protinst_default_use_item(a1, a2, item);
}
}
}
return script->field_28;
}
if (isInCombat()) {
MessageListItem messageListItem;
// You cannot do that in combat.
messageListItem.num = 902;
if (a1 == gDude) {
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
return -1;
}
if (skillUse(a1, a2, skill, criticalChanceModifier) != 0) {
return 0;
}
if (randomBetween(1, 10) != 1) {
return 0;
}
MessageListItem messageListItem;
messageListItem.num = messageId;
if (a1 == gDude) {
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
return 1;
}
// 0x49C5FC
int _obj_use_item_on(Object* a1, Object* a2, Object* a3)
{
int rc = _protinst_use_item_on(a1, a2, a3);
if (rc == 1) {
if (a1 != NULL) {
int flags = a3->flags & OBJECT_IN_ANY_HAND;
itemRemove(a1, a3, 1);
Object* v7 = _item_replace(a1, a3, flags);
int leftItemAction;
int rightItemAction;
if (a1 == gDude) {
interfaceGetItemActions(&leftItemAction, &rightItemAction);
}
if (v7 == NULL) {
if ((flags & OBJECT_IN_LEFT_HAND) != 0) {
leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
} else if ((flags & OBJECT_IN_RIGHT_HAND) != 0) {
rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
} else {
leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
}
}
interfaceUpdateItems(false, leftItemAction, rightItemAction);
}
_obj_destroy(a3);
rc = 0;
}
scriptsExecMapUpdateProc();
return rc;
}
// 0x49C6BC
int _check_scenery_ap_cost(Object* obj, Object* a2)
{
if (!isInCombat()) {
return 0;
}
int actionPoints = obj->data.critter.combat.ap;
if (actionPoints >= 3) {
obj->data.critter.combat.ap = actionPoints - 3;
if (obj == gDude) {
interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move);
}
return 0;
}
MessageListItem messageListItem;
// You don't have enough action points.
messageListItem.num = 700;
if (obj == gDude) {
if (messageListGetItem(&gProtoMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
return -1;
}
// 0x49C740
int _obj_use(Object* a1, Object* a2)
{
int type = FID_TYPE(a2->fid);
2022-05-19 01:51:26 -07:00
if (a1 == gDude) {
if (type != OBJ_TYPE_SCENERY) {
return -1;
}
} else {
if (type != OBJ_TYPE_SCENERY) {
return 0;
}
}
Proto* sceneryProto;
if (protoGetProto(a2->pid, &sceneryProto) == -1) {
return -1;
}
if (PID_TYPE(a2->pid) == OBJ_TYPE_SCENERY && sceneryProto->scenery.type == SCENERY_TYPE_DOOR) {
2022-05-19 01:51:26 -07:00
return _obj_use_door(a1, a2, 0);
}
bool scriptOverrides = false;
if (a2->sid != -1) {
scriptSetObjects(a2->sid, a1, a2);
scriptExecProc(a2->sid, SCRIPT_PROC_USE);
Script* script;
if (scriptGetScript(a2->sid, &script) == -1) {
return -1;
}
scriptOverrides = script->scriptOverrides;
}
if (!scriptOverrides) {
if (PID_TYPE(a2->pid) == OBJ_TYPE_SCENERY) {
2022-05-19 01:51:26 -07:00
if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_DOWN) {
if (useLadderDown(a1, a2, 0) == 0) {
scriptOverrides = true;
}
} else if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_UP) {
if (useLadderUp(a1, a2, 0) == 0) {
scriptOverrides = true;
}
} else if (sceneryProto->scenery.type == SCENERY_TYPE_STAIRS) {
if (useStairs(a1, a2, 0) == 0) {
scriptOverrides = true;
}
}
}
}
if (!scriptOverrides) {
if (a1 == gDude) {
// You see: %s
MessageListItem messageListItem;
messageListItem.num = 480;
if (!messageListGetItem(&gProtoMessageList, &messageListItem)) {
return -1;
}
char formattedText[260];
const char* name = objectGetName(a2);
sprintf(formattedText, messageListItem.text, name);
displayMonitorAddMessage(formattedText);
}
}
scriptsExecMapUpdateProc();
return 0;
}
// 0x49C900
2022-06-19 04:32:00 -07:00
static int useLadderDown(Object* a1, Object* ladder, int a3)
2022-05-19 01:51:26 -07:00
{
int builtTile = ladder->data.scenery.ladder.destinationBuiltTile;
2022-05-19 01:51:26 -07:00
if (builtTile == -1) {
return -1;
}
int tile = builtTileGetTile(builtTile);
int elevation = builtTileGetElevation(builtTile);
if (ladder->data.scenery.ladder.destinationMap != 0) {
2022-05-19 01:51:26 -07:00
MapTransition transition;
memset(&transition, 0, sizeof(transition));
transition.map = ladder->data.scenery.ladder.destinationMap;
2022-05-19 01:51:26 -07:00
transition.elevation = elevation;
transition.tile = tile;
transition.rotation = builtTileGetRotation(builtTile);
2022-05-19 01:51:26 -07:00
mapSetTransition(&transition);
_wmMapMarkMapEntranceState(transition.map, elevation, 1);
} else {
Rect updatedRect;
if (objectSetLocation(a1, tile, elevation, &updatedRect) == -1) {
return -1;
}
tileWindowRefreshRect(&updatedRect, gElevation);
}
return 0;
}
// 0x49C9A4
2022-06-19 04:32:00 -07:00
static int useLadderUp(Object* a1, Object* ladder, int a3)
2022-05-19 01:51:26 -07:00
{
int builtTile = ladder->data.scenery.ladder.destinationBuiltTile;
2022-05-19 01:51:26 -07:00
if (builtTile == -1) {
return -1;
}
int tile = builtTileGetTile(builtTile);
int elevation = builtTileGetElevation(builtTile);
if (ladder->data.scenery.ladder.destinationMap != 0) {
2022-05-19 01:51:26 -07:00
MapTransition transition;
memset(&transition, 0, sizeof(transition));
transition.map = ladder->data.scenery.ladder.destinationMap;
2022-05-19 01:51:26 -07:00
transition.elevation = elevation;
transition.tile = tile;
transition.rotation = builtTileGetRotation(builtTile);
2022-05-19 01:51:26 -07:00
mapSetTransition(&transition);
_wmMapMarkMapEntranceState(transition.map, elevation, 1);
} else {
Rect updatedRect;
if (objectSetLocation(a1, tile, elevation, &updatedRect) == -1) {
return -1;
}
tileWindowRefreshRect(&updatedRect, gElevation);
}
return 0;
}
// 0x49CA48
2022-06-19 04:32:00 -07:00
static int useStairs(Object* a1, Object* stairs, int a3)
2022-05-19 01:51:26 -07:00
{
int builtTile = stairs->data.scenery.stairs.destinationBuiltTile;
2022-05-19 01:51:26 -07:00
if (builtTile == -1) {
return -1;
}
int tile = builtTileGetTile(builtTile);
int elevation = builtTileGetElevation(builtTile);
if (stairs->data.scenery.stairs.destinationMap > 0) {
2022-05-19 01:51:26 -07:00
MapTransition transition;
memset(&transition, 0, sizeof(transition));
transition.map = stairs->data.scenery.stairs.destinationMap;
2022-05-19 01:51:26 -07:00
transition.elevation = elevation;
transition.tile = tile;
transition.rotation = builtTileGetRotation(builtTile);
2022-05-19 01:51:26 -07:00
mapSetTransition(&transition);
_wmMapMarkMapEntranceState(transition.map, elevation, 1);
} else {
Rect updatedRect;
if (objectSetLocation(a1, tile, elevation, &updatedRect) == -1) {
return -1;
}
tileWindowRefreshRect(&updatedRect, gElevation);
}
return 0;
}
// 0x49CAF4
2022-06-19 04:32:00 -07:00
static int _set_door_state_open(Object* a1, Object* a2)
2022-05-19 01:51:26 -07:00
{
a1->data.scenery.door.openFlags |= 0x01;
return 0;
}
// 0x49CB04
2022-06-19 04:32:00 -07:00
static int _set_door_state_closed(Object* a1, Object* a2)
2022-05-19 01:51:26 -07:00
{
a1->data.scenery.door.openFlags &= ~0x01;
return 0;
}
// 0x49CB14
2022-06-19 04:32:00 -07:00
static int _check_door_state(Object* a1, Object* a2)
2022-05-19 01:51:26 -07:00
{
if ((a1->data.scenery.door.openFlags & 0x01) == 0) {
2022-08-06 10:33:53 -07:00
// SFALL: Fix flags on non-door objects.
if (_obj_is_portal(a1)) {
a1->flags &= ~OBJECT_OPEN_DOOR;
}
2022-05-19 01:51:26 -07:00
_obj_rebuild_all_light();
tileWindowRefresh();
if (a1->frame == 0) {
return 0;
}
CacheEntry* artHandle;
Art* art = artLock(a1->fid, &artHandle);
if (art == NULL) {
return -1;
}
Rect dirty;
Rect temp;
objectGetRect(a1, &dirty);
for (int frame = a1->frame - 1; frame >= 0; frame--) {
int x;
int y;
artGetFrameOffsets(art, frame, a1->rotation, &x, &y);
_obj_offset(a1, -x, -y, &temp);
}
objectSetFrame(a1, 0, &temp);
rectUnion(&dirty, &temp, &dirty);
tileWindowRefreshRect(&dirty, gElevation);
artUnlock(artHandle);
return 0;
} else {
2022-08-06 10:33:53 -07:00
// SFALL: Fix flags on non-door objects.
if (_obj_is_portal(a1)) {
a1->flags |= OBJECT_OPEN_DOOR;
}
2022-05-19 01:51:26 -07:00
_obj_rebuild_all_light();
tileWindowRefresh();
CacheEntry* artHandle;
Art* art = artLock(a1->fid, &artHandle);
if (art == NULL) {
return -1;
}
int frameCount = artGetFrameCount(art);
if (a1->frame == frameCount - 1) {
artUnlock(artHandle);
return 0;
}
Rect dirty;
Rect temp;
objectGetRect(a1, &dirty);
for (int frame = a1->frame + 1; frame < frameCount; frame++) {
int x;
int y;
artGetFrameOffsets(art, frame, a1->rotation, &x, &y);
_obj_offset(a1, x, y, &temp);
}
objectSetFrame(a1, frameCount - 1, &temp);
rectUnion(&dirty, &temp, &dirty);
tileWindowRefreshRect(&dirty, gElevation);
artUnlock(artHandle);
return 0;
}
}
// 0x49CCB8
int _obj_use_door(Object* a1, Object* a2, int a3)
{
if (objectIsLocked(a2)) {
const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_LOCKED);
soundPlayFile(sfx);
}
bool scriptOverrides = false;
if (a2->sid != -1) {
scriptSetObjects(a2->sid, a1, a2);
scriptExecProc(a2->sid, SCRIPT_PROC_USE);
Script* script;
if (scriptGetScript(a2->sid, &script) == -1) {
return -1;
}
scriptOverrides = script->scriptOverrides;
}
if (!scriptOverrides) {
int start;
int end;
int step;
if (a2->frame != 0) {
if (_obj_blocking_at(NULL, a2->tile, a2->elevation) != 0) {
MessageListItem messageListItem;
char* text = getmsg(&gProtoMessageList, &messageListItem, 597);
displayMonitorAddMessage(text);
return -1;
}
start = 1;
end = (a3 == 0) - 1;
step = -1;
} else {
if (a2->data.scenery.door.openFlags & 0x01) {
return -1;
}
start = 0;
end = (a3 != 0) + 1;
step = 1;
}
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
2022-05-19 01:51:26 -07:00
for (int i = start; i != end; i += step) {
if (i != 0) {
if (a3 == 0) {
animationRegisterCallback(a2, a2, (AnimationCallback*)_set_door_state_closed, -1);
2022-05-19 01:51:26 -07:00
}
const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_CLOSED);
animationRegisterPlaySoundEffect(a2, sfx, -1);
2022-05-19 01:51:26 -07:00
animationRegisterAnimateReversed(a2, ANIM_STAND, 0);
2022-05-19 01:51:26 -07:00
} else {
if (a3 == 0) {
animationRegisterCallback(a2, a2, (AnimationCallback*)_set_door_state_open, -1);
2022-05-19 01:51:26 -07:00
}
const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_CLOSED);
animationRegisterPlaySoundEffect(a2, sfx, -1);
2022-05-19 01:51:26 -07:00
animationRegisterAnimate(a2, ANIM_STAND, 0);
2022-05-19 01:51:26 -07:00
}
}
animationRegisterCallbackForced(a2, a2, (AnimationCallback*)_check_door_state, -1);
2022-05-19 01:51:26 -07:00
reg_anim_end();
}
return 0;
}
// 0x49CE7C
int _obj_use_container(Object* critter, Object* item)
{
if (FID_TYPE(item->fid) != OBJ_TYPE_ITEM) {
2022-05-19 01:51:26 -07:00
return -1;
}
Proto* itemProto;
if (protoGetProto(item->pid, &itemProto) == -1) {
return -1;
}
if (itemProto->item.type != ITEM_TYPE_CONTAINER) {
return -1;
}
if (objectIsLocked(item)) {
const char* sfx = sfxBuildOpenName(item, SCENERY_SOUND_EFFECT_LOCKED);
soundPlayFile(sfx);
if (critter == gDude) {
MessageListItem messageListItem;
// It is locked.
messageListItem.num = 487;
if (!messageListGetItem(&gProtoMessageList, &messageListItem)) {
return -1;
}
displayMonitorAddMessage(messageListItem.text);
}
return -1;
}
bool overriden = false;
if (item->sid != -1) {
scriptSetObjects(item->sid, critter, item);
scriptExecProc(item->sid, SCRIPT_PROC_USE);
Script* script;
if (scriptGetScript(item->sid, &script) == -1) {
return -1;
}
overriden = script->scriptOverrides;
}
if (overriden) {
return -1;
}
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
2022-05-19 01:51:26 -07:00
if (item->frame == 0) {
const char* sfx = sfxBuildOpenName(item, SCENERY_SOUND_EFFECT_OPEN);
animationRegisterPlaySoundEffect(item, sfx, 0);
animationRegisterAnimate(item, ANIM_STAND, 0);
2022-05-19 01:51:26 -07:00
} else {
const char* sfx = sfxBuildOpenName(item, SCENERY_SOUND_EFFECT_CLOSED);
animationRegisterPlaySoundEffect(item, sfx, 0);
animationRegisterAnimateReversed(item, ANIM_STAND, 0);
2022-05-19 01:51:26 -07:00
}
reg_anim_end();
if (critter == gDude) {
MessageListItem messageListItem;
messageListItem.num = item->frame != 0
? 486 // You search the %s.
: 485; // You close the %s.
if (!messageListGetItem(&gProtoMessageList, &messageListItem)) {
return -1;
}
char formattedText[260];
const char* objectName = objectGetName(item);
sprintf(formattedText, messageListItem.text, objectName);
displayMonitorAddMessage(formattedText);
}
return 0;
}
// 0x49D078
int _obj_use_skill_on(Object* source, Object* target, int skill)
{
if (objectIsJammed(target)) {
if (source == gDude) {
MessageListItem messageListItem;
messageListItem.num = 2001;
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
displayMonitorAddMessage(messageListItem.text);
}
}
return -1;
}
Proto* proto;
if (protoGetProto(target->pid, &proto) == -1) {
return -1;
}
bool scriptOverrides = false;
if (target->sid != -1) {
scriptSetObjects(target->sid, source, target);
scriptSetActionBeingUsed(target->sid, skill);
scriptExecProc(target->sid, SCRIPT_PROC_USE_SKILL_ON);
Script* script;
if (scriptGetScript(target->sid, &script) == -1) {
return -1;
}
scriptOverrides = script->scriptOverrides;
}
if (!scriptOverrides) {
skillUse(source, target, skill, 0);
}
return 0;
}
2022-08-06 10:33:53 -07:00
// 0x49D140
static bool _obj_is_portal(Object* obj)
{
if (obj == NULL) {
return false;
}
Proto* proto;
if (protoGetProto(obj->pid, &proto) == -1) {
return false;
}
return proto->scenery.type == SCENERY_TYPE_DOOR;
}
2022-05-19 01:51:26 -07:00
// 0x49D178
2022-06-19 04:32:00 -07:00
static bool _obj_is_lockable(Object* obj)
2022-05-19 01:51:26 -07:00
{
Proto* proto;
if (obj == NULL) {
return false;
}
if (protoGetProto(obj->pid, &proto) == -1) {
return false;
}
switch (PID_TYPE(obj->pid)) {
2022-05-19 01:51:26 -07:00
case OBJ_TYPE_ITEM:
if (proto->item.type == ITEM_TYPE_CONTAINER) {
return true;
}
break;
case OBJ_TYPE_SCENERY:
if (proto->scenery.type == SCENERY_TYPE_DOOR) {
return true;
}
break;
}
return false;
}
// 0x49D1C8
bool objectIsLocked(Object* obj)
{
if (obj == NULL) {
return false;
}
ObjectData* data = &(obj->data);
switch (PID_TYPE(obj->pid)) {
2022-05-19 01:51:26 -07:00
case OBJ_TYPE_ITEM:
return data->flags & CONTAINER_FLAG_LOCKED;
case OBJ_TYPE_SCENERY:
return data->scenery.door.openFlags & DOOR_FLAG_LOCKED;
}
return false;
}
// 0x49D20C
int objectLock(Object* object)
{
if (object == NULL) {
return -1;
}
switch (PID_TYPE(object->pid)) {
2022-05-19 01:51:26 -07:00
case OBJ_TYPE_ITEM:
object->data.flags |= OBJ_LOCKED;
break;
case OBJ_TYPE_SCENERY:
object->data.scenery.door.openFlags |= OBJ_LOCKED;
break;
default:
return -1;
}
return 0;
}
// 0x49D250
int objectUnlock(Object* object)
{
if (object == NULL) {
return -1;
}
switch (PID_TYPE(object->pid)) {
2022-05-19 01:51:26 -07:00
case OBJ_TYPE_ITEM:
object->data.flags &= ~OBJ_LOCKED;
return 0;
case OBJ_TYPE_SCENERY:
object->data.scenery.door.openFlags &= ~OBJ_LOCKED;
return 0;
}
return -1;
}
// 0x49D294
2022-06-19 04:32:00 -07:00
static bool _obj_is_openable(Object* obj)
2022-05-19 01:51:26 -07:00
{
Proto* proto;
if (obj == NULL) {
return false;
}
if (protoGetProto(obj->pid, &proto) == -1) {
return false;
}
switch (PID_TYPE(obj->pid)) {
2022-05-19 01:51:26 -07:00
case OBJ_TYPE_ITEM:
if (proto->item.type == ITEM_TYPE_CONTAINER) {
return true;
}
break;
case OBJ_TYPE_SCENERY:
if (proto->scenery.type == SCENERY_TYPE_DOOR) {
return true;
}
break;
}
return false;
}
// 0x49D2E4
int objectIsOpen(Object* object)
{
return object->frame != 0;
}
// 0x49D2F4
2022-06-19 04:32:00 -07:00
static int objectOpenClose(Object* obj)
2022-05-19 01:51:26 -07:00
{
if (obj == NULL) {
return -1;
}
if (!_obj_is_openable(obj)) {
return -1;
}
if (objectIsLocked(obj)) {
return -1;
}
objectUnjamLock(obj);
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
2022-05-19 01:51:26 -07:00
if (obj->frame != 0) {
animationRegisterCallbackForced(obj, obj, (AnimationCallback*)_set_door_state_closed, -1);
2022-05-19 01:51:26 -07:00
const char* sfx = sfxBuildOpenName(obj, SCENERY_SOUND_EFFECT_CLOSED);
animationRegisterPlaySoundEffect(obj, sfx, -1);
2022-05-19 01:51:26 -07:00
animationRegisterAnimateReversed(obj, ANIM_STAND, 0);
2022-05-19 01:51:26 -07:00
} else {
animationRegisterCallbackForced(obj, obj, (AnimationCallback*)_set_door_state_open, -1);
2022-05-19 01:51:26 -07:00
const char* sfx = sfxBuildOpenName(obj, SCENERY_SOUND_EFFECT_OPEN);
animationRegisterPlaySoundEffect(obj, sfx, -1);
animationRegisterAnimate(obj, ANIM_STAND, 0);
2022-05-19 01:51:26 -07:00
}
animationRegisterCallbackForced(obj, obj, (AnimationCallback*)_check_door_state, -1);
2022-05-19 01:51:26 -07:00
reg_anim_end();
return 0;
}
// 0x49D3D8
int objectOpen(Object* obj)
{
if (obj->frame == 0) {
objectOpenClose(obj);
}
return 0;
}
// 0x49D3F4
int objectClose(Object* obj)
{
if (obj->frame != 0) {
objectOpenClose(obj);
}
return 0;
}
// 0x49D410
2022-06-19 04:32:00 -07:00
static bool objectIsJammed(Object* obj)
2022-05-19 01:51:26 -07:00
{
if (!_obj_is_lockable(obj)) {
return false;
}
if (PID_TYPE(obj->pid) == OBJ_TYPE_SCENERY) {
2022-05-19 01:51:26 -07:00
if ((obj->data.scenery.door.openFlags & OBJ_JAMMED) != 0) {
return true;
}
} else {
if ((obj->data.flags & OBJ_JAMMED) != 0) {
return true;
}
}
return false;
}
// jam_lock
// 0x49D448
int objectJamLock(Object* obj)
{
if (!_obj_is_lockable(obj)) {
return -1;
}
ObjectData* data = &(obj->data);
switch (PID_TYPE(obj->pid)) {
2022-05-19 01:51:26 -07:00
case OBJ_TYPE_ITEM:
data->flags |= CONTAINER_FLAG_JAMMED;
break;
case OBJ_TYPE_SCENERY:
data->scenery.door.openFlags |= DOOR_FLAG_JAMMGED;
break;
}
return 0;
}
// 0x49D480
int objectUnjamLock(Object* obj)
{
if (!_obj_is_lockable(obj)) {
return -1;
}
ObjectData* data = &(obj->data);
switch (PID_TYPE(obj->pid)) {
2022-05-19 01:51:26 -07:00
case OBJ_TYPE_ITEM:
data->flags &= ~CONTAINER_FLAG_JAMMED;
break;
case OBJ_TYPE_SCENERY:
data->scenery.door.openFlags &= ~DOOR_FLAG_JAMMGED;
break;
}
return 0;
}
// 0x49D4B8
int objectUnjamAll()
{
Object* obj = objectFindFirst();
while (obj != NULL) {
objectUnjamLock(obj);
obj = objectFindNext();
}
return 0;
}
// critter_attempt_placement
// 0x49D4D4
int _obj_attempt_placement(Object* obj, int tile, int elevation, int a4)
{
if (tile == -1) {
return -1;
}
int newTile = tile;
if (_obj_blocking_at(NULL, tile, elevation) != NULL) {
int v6 = a4;
if (a4 < 1) {
v6 = 1;
}
int attempts = 0;
while (v6 < 7) {
attempts++;
if (attempts >= 100) {
break;
}
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
newTile = tileGetTileInDirection(tile, rotation, v6);
if (_obj_blocking_at(NULL, newTile, elevation) == NULL && v6 > 1 && _make_path(gDude, gDude->tile, newTile, NULL, 0) != 0) {
break;
}
}
v6++;
}
if (a4 != 1 && v6 > a4 + 2) {
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
int candidate = tileGetTileInDirection(tile, rotation, 1);
if (_obj_blocking_at(NULL, candidate, elevation) == NULL) {
newTile = candidate;
break;
}
}
}
}
Rect updatedRect;
objectShow(obj, &updatedRect);
Rect temp;
if (objectSetLocation(obj, newTile, elevation, &temp) != -1) {
rectUnion(&updatedRect, &temp, &updatedRect);
if (elevation == gElevation) {
tileWindowRefreshRect(&updatedRect, elevation);
}
}
return 0;
}
// 0x49D628
int _objPMAttemptPlacement(Object* obj, int tile, int elevation)
{
if (obj == NULL) {
return -1;
}
if (tile == -1) {
return -1;
}
int v9 = tile;
int v7 = 0;
if (!_wmEvalTileNumForPlacement(tile)) {
v9 = gDude->tile;
for (int v4 = 1; v4 <= 100; v4++) {
// TODO: Check.
v7++;
v9 = tileGetTileInDirection(v9, v7 % ROTATION_COUNT, 1);
2022-05-19 01:51:26 -07:00
if (_wmEvalTileNumForPlacement(v9) != 0) {
break;
}
if (tileDistanceBetween(gDude->tile, v9) > 8) {
v9 = tile;
break;
}
}
}
objectShow(obj, NULL);
objectSetLocation(obj, v9, elevation, NULL);
return 0;
}