577 lines
15 KiB
C++
577 lines
15 KiB
C++
#include "queue.h"
|
|
|
|
#include "actions.h"
|
|
#include "critter.h"
|
|
#include "display_monitor.h"
|
|
#include "game.h"
|
|
#include "game_sound.h"
|
|
#include "item.h"
|
|
#include "map.h"
|
|
#include "memory.h"
|
|
#include "message.h"
|
|
#include "object.h"
|
|
#include "perk.h"
|
|
#include "proto.h"
|
|
#include "proto_instance.h"
|
|
#include "scripts.h"
|
|
|
|
namespace fallout {
|
|
|
|
typedef struct QueueListNode {
|
|
// TODO: Make unsigned.
|
|
int time;
|
|
int type;
|
|
Object* owner;
|
|
void* data;
|
|
struct QueueListNode* next;
|
|
} QueueListNode;
|
|
|
|
typedef struct EventTypeDescription {
|
|
QueueEventHandler* handlerProc;
|
|
QueueEventDataFreeProc* freeProc;
|
|
QueueEventDataReadProc* readProc;
|
|
QueueEventDataWriteProc* writeProc;
|
|
bool field_10;
|
|
QueueEventHandler* field_14;
|
|
} EventTypeDescription;
|
|
|
|
static int flareEventProcess(Object* obj, void* data);
|
|
static int explosionEventProcess(Object* obj, void* data);
|
|
static int _queue_explode_exit(Object* obj, void* data);
|
|
static int _queue_do_explosion_(Object* obj, bool a2);
|
|
static int explosionFailureEventProcess(Object* obj, void* data);
|
|
|
|
// Last queue list node found during [queueFindFirstEvent] and
|
|
// [queueFindNextEvent] calls.
|
|
//
|
|
// 0x51C690
|
|
static QueueListNode* gLastFoundQueueListNode = NULL;
|
|
|
|
// 0x6648C0
|
|
static QueueListNode* gQueueListHead;
|
|
|
|
// 0x51C540
|
|
static EventTypeDescription gEventTypeDescriptions[EVENT_TYPE_COUNT] = {
|
|
{ drugEffectEventProcess, internal_free, drugEffectEventRead, drugEffectEventWrite, true, _item_d_clear },
|
|
{ knockoutEventProcess, NULL, NULL, NULL, true, _critter_wake_clear },
|
|
{ withdrawalEventProcess, internal_free, withdrawalEventRead, withdrawalEventWrite, true, _item_wd_clear },
|
|
{ scriptEventProcess, internal_free, scriptEventRead, scriptEventWrite, true, NULL },
|
|
{ gameTimeEventProcess, NULL, NULL, NULL, true, NULL },
|
|
{ poisonEventProcess, NULL, NULL, NULL, false, NULL },
|
|
{ radiationEventProcess, internal_free, radiationEventRead, radiationEventWrite, false, NULL },
|
|
{ flareEventProcess, NULL, NULL, NULL, true, flareEventProcess },
|
|
{ explosionEventProcess, NULL, NULL, NULL, true, _queue_explode_exit },
|
|
{ miscItemTrickleEventProcess, NULL, NULL, NULL, true, _item_m_turn_off_from_queue },
|
|
{ sneakEventProcess, NULL, NULL, NULL, true, _critter_sneak_clear },
|
|
{ explosionFailureEventProcess, NULL, NULL, NULL, true, _queue_explode_exit },
|
|
{ mapUpdateEventProcess, NULL, NULL, NULL, true, NULL },
|
|
{ ambientSoundEffectEventProcess, internal_free, NULL, NULL, true, NULL },
|
|
};
|
|
|
|
// 0x4A2320
|
|
void queueInit()
|
|
{
|
|
gQueueListHead = NULL;
|
|
}
|
|
|
|
// 0x4A2330
|
|
int queueExit()
|
|
{
|
|
queueClear();
|
|
return 0;
|
|
}
|
|
|
|
// 0x4A2338
|
|
int queueLoad(File* stream)
|
|
{
|
|
int count;
|
|
if (fileReadInt32(stream, &count) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
QueueListNode* oldListHead = gQueueListHead;
|
|
gQueueListHead = NULL;
|
|
|
|
QueueListNode** nextPtr = &gQueueListHead;
|
|
|
|
int rc = 0;
|
|
for (int index = 0; index < count; index += 1) {
|
|
QueueListNode* queueListNode = (QueueListNode*)internal_malloc(sizeof(*queueListNode));
|
|
if (queueListNode == NULL) {
|
|
rc = -1;
|
|
break;
|
|
}
|
|
|
|
if (fileReadInt32(stream, &(queueListNode->time)) == -1) {
|
|
internal_free(queueListNode);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
|
|
if (fileReadInt32(stream, &(queueListNode->type)) == -1) {
|
|
internal_free(queueListNode);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
|
|
int objectId;
|
|
if (fileReadInt32(stream, &objectId) == -1) {
|
|
internal_free(queueListNode);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
|
|
Object* obj;
|
|
if (objectId == -2) {
|
|
obj = NULL;
|
|
} else {
|
|
obj = objectFindFirst();
|
|
while (obj != NULL) {
|
|
obj = _inven_find_id(obj, objectId);
|
|
if (obj != NULL) {
|
|
break;
|
|
}
|
|
obj = objectFindNext();
|
|
}
|
|
}
|
|
|
|
queueListNode->owner = obj;
|
|
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]);
|
|
if (eventTypeDescription->readProc != NULL) {
|
|
if (eventTypeDescription->readProc(stream, &(queueListNode->data)) == -1) {
|
|
internal_free(queueListNode);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
} else {
|
|
queueListNode->data = NULL;
|
|
}
|
|
|
|
queueListNode->next = NULL;
|
|
|
|
*nextPtr = queueListNode;
|
|
nextPtr = &(queueListNode->next);
|
|
}
|
|
|
|
if (rc == -1) {
|
|
while (gQueueListHead != NULL) {
|
|
QueueListNode* next = gQueueListHead->next;
|
|
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[gQueueListHead->type]);
|
|
if (eventTypeDescription->freeProc != NULL) {
|
|
eventTypeDescription->freeProc(gQueueListHead->data);
|
|
}
|
|
|
|
internal_free(gQueueListHead);
|
|
|
|
gQueueListHead = next;
|
|
}
|
|
}
|
|
|
|
if (oldListHead != NULL) {
|
|
QueueListNode** v13 = &gQueueListHead;
|
|
QueueListNode* v15;
|
|
do {
|
|
while (true) {
|
|
QueueListNode* v14 = *v13;
|
|
if (v14 == NULL) {
|
|
break;
|
|
}
|
|
|
|
if (v14->time > oldListHead->time) {
|
|
break;
|
|
}
|
|
|
|
v13 = &(v14->next);
|
|
}
|
|
v15 = oldListHead->next;
|
|
oldListHead->next = *v13;
|
|
*v13 = oldListHead;
|
|
oldListHead = v15;
|
|
} while (v15 != NULL);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
// 0x4A24E0
|
|
int queueSave(File* stream)
|
|
{
|
|
QueueListNode* queueListNode;
|
|
|
|
int count = 0;
|
|
|
|
queueListNode = gQueueListHead;
|
|
while (queueListNode != NULL) {
|
|
count += 1;
|
|
queueListNode = queueListNode->next;
|
|
}
|
|
|
|
if (fileWriteInt32(stream, count) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
queueListNode = gQueueListHead;
|
|
while (queueListNode != NULL) {
|
|
Object* object = queueListNode->owner;
|
|
int objectId = object != NULL ? object->id : -2;
|
|
|
|
if (fileWriteInt32(stream, queueListNode->time) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (fileWriteInt32(stream, queueListNode->type) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (fileWriteInt32(stream, objectId) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]);
|
|
if (eventTypeDescription->writeProc != NULL) {
|
|
if (eventTypeDescription->writeProc(stream, queueListNode->data) == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
queueListNode = queueListNode->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 0x4A258C
|
|
int queueAddEvent(int delay, Object* obj, void* data, int eventType)
|
|
{
|
|
QueueListNode* newQueueListNode = (QueueListNode*)internal_malloc(sizeof(QueueListNode));
|
|
if (newQueueListNode == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
int v1 = gameTimeGetTime();
|
|
int v2 = v1 + delay;
|
|
newQueueListNode->time = v2;
|
|
newQueueListNode->type = eventType;
|
|
newQueueListNode->owner = obj;
|
|
newQueueListNode->data = data;
|
|
|
|
if (obj != NULL) {
|
|
obj->flags |= OBJECT_QUEUED;
|
|
}
|
|
|
|
QueueListNode** v3 = &gQueueListHead;
|
|
|
|
if (gQueueListHead != NULL) {
|
|
QueueListNode* v4;
|
|
|
|
do {
|
|
v4 = *v3;
|
|
if (v2 < v4->time) {
|
|
break;
|
|
}
|
|
v3 = &(v4->next);
|
|
} while (v4->next != NULL);
|
|
}
|
|
|
|
newQueueListNode->next = *v3;
|
|
*v3 = newQueueListNode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 0x4A25F4
|
|
int queueRemoveEvents(Object* owner)
|
|
{
|
|
QueueListNode* queueListNode = gQueueListHead;
|
|
QueueListNode** queueListNodePtr = &gQueueListHead;
|
|
|
|
while (queueListNode) {
|
|
if (queueListNode->owner == owner) {
|
|
QueueListNode* temp = queueListNode;
|
|
|
|
queueListNode = queueListNode->next;
|
|
*queueListNodePtr = queueListNode;
|
|
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[temp->type]);
|
|
if (eventTypeDescription->freeProc != NULL) {
|
|
eventTypeDescription->freeProc(temp->data);
|
|
}
|
|
|
|
internal_free(temp);
|
|
} else {
|
|
queueListNodePtr = &(queueListNode->next);
|
|
queueListNode = queueListNode->next;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 0x4A264C
|
|
int queueRemoveEventsByType(Object* owner, int eventType)
|
|
{
|
|
QueueListNode* queueListNode = gQueueListHead;
|
|
QueueListNode** queueListNodePtr = &gQueueListHead;
|
|
|
|
while (queueListNode) {
|
|
if (queueListNode->owner == owner && queueListNode->type == eventType) {
|
|
QueueListNode* temp = queueListNode;
|
|
|
|
queueListNode = queueListNode->next;
|
|
*queueListNodePtr = queueListNode;
|
|
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[temp->type]);
|
|
if (eventTypeDescription->freeProc != NULL) {
|
|
eventTypeDescription->freeProc(temp->data);
|
|
}
|
|
|
|
internal_free(temp);
|
|
} else {
|
|
queueListNodePtr = &(queueListNode->next);
|
|
queueListNode = queueListNode->next;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Returns true if there is at least one event of given type scheduled.
|
|
//
|
|
// 0x4A26A8
|
|
bool queueHasEvent(Object* owner, int eventType)
|
|
{
|
|
QueueListNode* queueListEvent = gQueueListHead;
|
|
while (queueListEvent != NULL) {
|
|
if (owner == queueListEvent->owner && eventType == queueListEvent->type) {
|
|
return true;
|
|
}
|
|
|
|
queueListEvent = queueListEvent->next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 0x4A26D0
|
|
int queueProcessEvents()
|
|
{
|
|
int time = gameTimeGetTime();
|
|
int v1 = 0;
|
|
|
|
while (gQueueListHead != NULL) {
|
|
QueueListNode* queueListNode = gQueueListHead;
|
|
if (time < queueListNode->time || v1 != 0) {
|
|
break;
|
|
}
|
|
|
|
gQueueListHead = queueListNode->next;
|
|
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]);
|
|
v1 = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data);
|
|
|
|
if (eventTypeDescription->freeProc != NULL) {
|
|
eventTypeDescription->freeProc(queueListNode->data);
|
|
}
|
|
|
|
internal_free(queueListNode);
|
|
}
|
|
|
|
return v1;
|
|
}
|
|
|
|
// 0x4A2748
|
|
void queueClear()
|
|
{
|
|
QueueListNode* queueListNode = gQueueListHead;
|
|
while (queueListNode != NULL) {
|
|
QueueListNode* next = queueListNode->next;
|
|
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]);
|
|
if (eventTypeDescription->freeProc != NULL) {
|
|
eventTypeDescription->freeProc(queueListNode->data);
|
|
}
|
|
|
|
internal_free(queueListNode);
|
|
|
|
queueListNode = next;
|
|
}
|
|
|
|
gQueueListHead = NULL;
|
|
}
|
|
|
|
// 0x4A2790
|
|
void _queue_clear_type(int eventType, QueueEventHandler* fn)
|
|
{
|
|
QueueListNode** ptr = &gQueueListHead;
|
|
QueueListNode* curr = *ptr;
|
|
|
|
while (curr != NULL) {
|
|
if (eventType == curr->type) {
|
|
QueueListNode* tmp = curr;
|
|
|
|
*ptr = curr->next;
|
|
curr = *ptr;
|
|
|
|
if (fn != NULL && fn(tmp->owner, tmp->data) != 1) {
|
|
*ptr = tmp;
|
|
ptr = &(tmp->next);
|
|
} else {
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[tmp->type]);
|
|
if (eventTypeDescription->freeProc != NULL) {
|
|
eventTypeDescription->freeProc(tmp->data);
|
|
}
|
|
|
|
internal_free(tmp);
|
|
|
|
// SFALL: Re-read next event since `fn` handler can change it.
|
|
// This fixes crash when leaving the map while waiting for
|
|
// someone to die of a super stimpak overdose.
|
|
curr = *ptr;
|
|
}
|
|
} else {
|
|
ptr = &(curr->next);
|
|
curr = *ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Make unsigned.
|
|
//
|
|
// 0x4A2808
|
|
int queueGetNextEventTime()
|
|
{
|
|
if (gQueueListHead == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return gQueueListHead->time;
|
|
}
|
|
|
|
// 0x4A281C
|
|
static int flareEventProcess(Object* obj, void* data)
|
|
{
|
|
_obj_destroy(obj);
|
|
return 1;
|
|
}
|
|
|
|
// 0x4A2828
|
|
static int explosionEventProcess(Object* obj, void* data)
|
|
{
|
|
return _queue_do_explosion_(obj, true);
|
|
}
|
|
|
|
// 0x4A2830
|
|
static int _queue_explode_exit(Object* obj, void* data)
|
|
{
|
|
return _queue_do_explosion_(obj, false);
|
|
}
|
|
|
|
// 0x4A2834
|
|
static int _queue_do_explosion_(Object* explosive, bool a2)
|
|
{
|
|
int tile;
|
|
int elevation;
|
|
|
|
Object* owner = objectGetOwner(explosive);
|
|
if (owner) {
|
|
tile = owner->tile;
|
|
elevation = owner->elevation;
|
|
} else {
|
|
tile = explosive->tile;
|
|
elevation = explosive->elevation;
|
|
}
|
|
|
|
int maxDamage;
|
|
int minDamage;
|
|
|
|
// SFALL
|
|
explosiveGetDamage(explosive->pid, &minDamage, &maxDamage);
|
|
|
|
// FIXME: I guess this is a little bit wrong, dude can never be null, I
|
|
// guess it needs to check if owner is dude.
|
|
if (gDude != NULL) {
|
|
if (perkHasRank(gDude, PERK_DEMOLITION_EXPERT)) {
|
|
maxDamage += 10;
|
|
minDamage += 10;
|
|
}
|
|
}
|
|
|
|
if (actionExplode(tile, elevation, minDamage, maxDamage, gDude, a2) == -2) {
|
|
queueAddEvent(50, explosive, NULL, EVENT_TYPE_EXPLOSION);
|
|
} else {
|
|
_obj_destroy(explosive);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// 0x4A28E4
|
|
static int explosionFailureEventProcess(Object* obj, void* data)
|
|
{
|
|
MessageListItem msg;
|
|
|
|
// Due to your inept handling, the explosive detonates prematurely.
|
|
msg.num = 4000;
|
|
if (messageListGetItem(&gMiscMessageList, &msg)) {
|
|
displayMonitorAddMessage(msg.text);
|
|
}
|
|
|
|
return _queue_do_explosion_(obj, true);
|
|
}
|
|
|
|
// 0x4A2920
|
|
void _queue_leaving_map()
|
|
{
|
|
for (int eventType = 0; eventType < EVENT_TYPE_COUNT; eventType++) {
|
|
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[eventType]);
|
|
if (eventTypeDescription->field_10) {
|
|
_queue_clear_type(eventType, eventTypeDescription->field_14);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 0x4A294C
|
|
bool queueIsEmpty()
|
|
{
|
|
return gQueueListHead == NULL;
|
|
}
|
|
|
|
// 0x4A295C
|
|
void* queueFindFirstEvent(Object* owner, int eventType)
|
|
{
|
|
QueueListNode* queueListNode = gQueueListHead;
|
|
while (queueListNode != NULL) {
|
|
if (owner == queueListNode->owner && eventType == queueListNode->type) {
|
|
gLastFoundQueueListNode = queueListNode;
|
|
return queueListNode->data;
|
|
}
|
|
queueListNode = queueListNode->next;
|
|
}
|
|
|
|
gLastFoundQueueListNode = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
// 0x4A2994
|
|
void* queueFindNextEvent(Object* owner, int eventType)
|
|
{
|
|
if (gLastFoundQueueListNode != NULL) {
|
|
QueueListNode* queueListNode = gLastFoundQueueListNode->next;
|
|
while (queueListNode != NULL) {
|
|
if (owner == queueListNode->owner && eventType == queueListNode->type) {
|
|
gLastFoundQueueListNode = queueListNode;
|
|
return queueListNode->data;
|
|
}
|
|
queueListNode = queueListNode->next;
|
|
}
|
|
}
|
|
|
|
gLastFoundQueueListNode = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
} // namespace fallout
|