2022-11-07 09:03:04 -08:00
|
|
|
#include "sfall_opcodes.h"
|
|
|
|
|
2023-05-29 23:06:55 -07:00
|
|
|
#include <string.h>
|
|
|
|
|
2023-04-20 01:22:47 -07:00
|
|
|
#include "animation.h"
|
2022-11-07 09:03:04 -08:00
|
|
|
#include "art.h"
|
2022-11-09 02:09:56 -08:00
|
|
|
#include "combat.h"
|
|
|
|
#include "debug.h"
|
2022-12-06 06:46:52 -08:00
|
|
|
#include "game.h"
|
2023-04-19 09:10:09 -07:00
|
|
|
#include "input.h"
|
2022-11-07 09:03:04 -08:00
|
|
|
#include "interface.h"
|
|
|
|
#include "interpreter.h"
|
2022-11-07 09:41:33 -08:00
|
|
|
#include "item.h"
|
2022-11-10 07:07:23 -08:00
|
|
|
#include "message.h"
|
2022-11-07 09:03:04 -08:00
|
|
|
#include "mouse.h"
|
2022-11-08 07:59:14 -08:00
|
|
|
#include "object.h"
|
2023-05-29 23:06:55 -07:00
|
|
|
#include "party_member.h"
|
2023-04-18 23:21:38 -07:00
|
|
|
#include "proto.h"
|
2023-04-19 09:10:09 -07:00
|
|
|
#include "scripts.h"
|
2023-05-29 23:06:55 -07:00
|
|
|
#include "sfall_arrays.h"
|
2022-11-08 07:01:00 -08:00
|
|
|
#include "sfall_global_vars.h"
|
2022-11-08 11:55:25 -08:00
|
|
|
#include "sfall_lists.h"
|
2022-11-08 07:59:14 -08:00
|
|
|
#include "stat.h"
|
2022-11-07 09:03:04 -08:00
|
|
|
#include "svga.h"
|
2023-04-20 00:25:13 -07:00
|
|
|
#include "tile.h"
|
2023-04-18 23:09:17 -07:00
|
|
|
#include "worldmap.h"
|
2022-11-07 09:03:04 -08:00
|
|
|
|
|
|
|
namespace fallout {
|
|
|
|
|
2022-12-22 09:18:07 -08:00
|
|
|
static constexpr int kVersionMajor = 4;
|
|
|
|
static constexpr int kVersionMinor = 3;
|
|
|
|
static constexpr int kVersionPatch = 4;
|
|
|
|
|
2022-11-09 02:09:56 -08:00
|
|
|
// read_byte
|
|
|
|
static void opReadByte(Program* program)
|
|
|
|
{
|
|
|
|
int addr = programStackPopInteger(program);
|
|
|
|
|
|
|
|
int value = 0;
|
|
|
|
switch (addr) {
|
|
|
|
case 0x56D38C:
|
|
|
|
value = combatGetTargetHighlight();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
debugPrint("%s: attempt to 'read_byte' at 0x%x", program->name, addr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
programStackPushInteger(program, value);
|
|
|
|
}
|
|
|
|
|
2023-04-18 23:02:54 -07:00
|
|
|
// set_pc_base_stat
|
|
|
|
static void op_set_pc_base_stat(Program* program)
|
|
|
|
{
|
|
|
|
// CE: Implementation is different. Sfall changes value directly on the
|
|
|
|
// dude's proto, without calling |critterSetBaseStat|. This function has
|
|
|
|
// important call to update derived stats, which is not present in Sfall.
|
|
|
|
int value = programStackPopInteger(program);
|
|
|
|
int stat = programStackPopInteger(program);
|
|
|
|
critterSetBaseStat(gDude, stat, value);
|
|
|
|
}
|
|
|
|
|
2022-11-08 07:59:14 -08:00
|
|
|
// set_pc_extra_stat
|
|
|
|
static void opSetPcBonusStat(Program* program)
|
|
|
|
{
|
|
|
|
// CE: Implementation is different. Sfall changes value directly on the
|
|
|
|
// dude's proto, without calling |critterSetBonusStat|. This function has
|
|
|
|
// important call to update derived stats, which is not present in Sfall.
|
|
|
|
int value = programStackPopInteger(program);
|
|
|
|
int stat = programStackPopInteger(program);
|
|
|
|
critterSetBonusStat(gDude, stat, value);
|
|
|
|
}
|
|
|
|
|
2023-04-18 23:02:54 -07:00
|
|
|
// get_pc_base_stat
|
|
|
|
static void op_get_pc_base_stat(Program* program)
|
|
|
|
{
|
|
|
|
// CE: Implementation is different. Sfall obtains value directly from
|
|
|
|
// dude's proto. This can have unforeseen consequences when dealing with
|
|
|
|
// current stats.
|
|
|
|
int stat = programStackPopInteger(program);
|
|
|
|
programStackPushInteger(program, critterGetBaseStat(gDude, stat));
|
|
|
|
}
|
|
|
|
|
2022-11-08 07:59:14 -08:00
|
|
|
// get_pc_extra_stat
|
|
|
|
static void opGetPcBonusStat(Program* program)
|
|
|
|
{
|
|
|
|
int stat = programStackPopInteger(program);
|
|
|
|
int value = critterGetBonusStat(gDude, stat);
|
|
|
|
programStackPushInteger(program, value);
|
|
|
|
}
|
|
|
|
|
2023-04-19 09:10:09 -07:00
|
|
|
// get_year
|
|
|
|
static void op_get_year(Program* program)
|
|
|
|
{
|
|
|
|
int year;
|
|
|
|
gameTimeGetDate(nullptr, nullptr, &year);
|
|
|
|
programStackPushInteger(program, year);
|
|
|
|
}
|
|
|
|
|
2023-04-18 23:09:17 -07:00
|
|
|
// in_world_map
|
|
|
|
static void op_in_world_map(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, GameMode::isInGameMode(GameMode::kWorldmap) ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set_world_map_pos
|
|
|
|
static void op_set_world_map_pos(Program* program)
|
|
|
|
{
|
|
|
|
int y = programStackPopInteger(program);
|
|
|
|
int x = programStackPopInteger(program);
|
|
|
|
wmSetPartyWorldPos(x, y);
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
// active_hand
|
|
|
|
static void opGetCurrentHand(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, interfaceGetCurrentHand());
|
|
|
|
}
|
|
|
|
|
2022-11-08 07:01:00 -08:00
|
|
|
// set_sfall_global
|
|
|
|
static void opSetGlobalVar(Program* program)
|
|
|
|
{
|
|
|
|
ProgramValue value = programStackPopValue(program);
|
|
|
|
ProgramValue variable = programStackPopValue(program);
|
|
|
|
|
|
|
|
if ((variable.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {
|
|
|
|
const char* key = programGetString(program, variable.opcode, variable.integerValue);
|
|
|
|
sfallGlobalVarsStore(key, value.integerValue);
|
|
|
|
} else if (variable.opcode == VALUE_TYPE_INT) {
|
|
|
|
sfallGlobalVarsStore(variable.integerValue, value.integerValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get_sfall_global_int
|
|
|
|
static void opGetGlobalInt(Program* program)
|
|
|
|
{
|
|
|
|
ProgramValue variable = programStackPopValue(program);
|
|
|
|
|
|
|
|
int value = 0;
|
|
|
|
if ((variable.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {
|
|
|
|
const char* key = programGetString(program, variable.opcode, variable.integerValue);
|
|
|
|
sfallGlobalVarsFetch(key, value);
|
|
|
|
} else if (variable.opcode == VALUE_TYPE_INT) {
|
|
|
|
sfallGlobalVarsFetch(variable.integerValue, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
programStackPushInteger(program, value);
|
|
|
|
}
|
|
|
|
|
2022-12-06 06:46:52 -08:00
|
|
|
// get_game_mode
|
|
|
|
static void opGetGameMode(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, GameMode::getCurrentGameMode());
|
|
|
|
}
|
|
|
|
|
2023-04-19 09:10:09 -07:00
|
|
|
// get_uptime
|
|
|
|
static void op_get_uptime(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, getTicks());
|
|
|
|
}
|
|
|
|
|
2023-04-18 23:09:17 -07:00
|
|
|
// set_car_current_town
|
|
|
|
static void op_set_car_current_town(Program* program)
|
|
|
|
{
|
|
|
|
int area = programStackPopInteger(program);
|
|
|
|
wmCarSetCurrentArea(area);
|
|
|
|
}
|
|
|
|
|
2023-04-19 00:03:04 -07:00
|
|
|
// get_bodypart_hit_modifier
|
|
|
|
static void op_get_bodypart_hit_modifier(Program* program)
|
|
|
|
{
|
|
|
|
int hit_location = programStackPopInteger(program);
|
|
|
|
programStackPushInteger(program, combat_get_hit_location_penalty(hit_location));
|
|
|
|
}
|
|
|
|
|
|
|
|
// set_bodypart_hit_modifier
|
|
|
|
static void op_set_bodypart_hit_modifier(Program* program)
|
|
|
|
{
|
|
|
|
int penalty = programStackPopInteger(program);
|
|
|
|
int hit_location = programStackPopInteger(program);
|
|
|
|
combat_set_hit_location_penalty(hit_location, penalty);
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:53:50 -07:00
|
|
|
// sqrt
|
|
|
|
static void op_sqrt(Program* program)
|
|
|
|
{
|
|
|
|
ProgramValue programValue = programStackPopValue(program);
|
|
|
|
programStackPushFloat(program, sqrtf(programValue.asFloat()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// abs
|
|
|
|
static void op_abs(Program* program)
|
|
|
|
{
|
|
|
|
ProgramValue programValue = programStackPopValue(program);
|
|
|
|
|
|
|
|
if (programValue.isInt()) {
|
|
|
|
programStackPushInteger(program, abs(programValue.integerValue));
|
|
|
|
} else {
|
|
|
|
programStackPushFloat(program, abs(programValue.asFloat()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-18 23:21:38 -07:00
|
|
|
// get_proto_data
|
|
|
|
static void op_get_proto_data(Program* program)
|
|
|
|
{
|
|
|
|
size_t offset = static_cast<size_t>(programStackPopInteger(program));
|
|
|
|
int pid = programStackPopInteger(program);
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
if (protoGetProto(pid, &proto) != 0) {
|
|
|
|
debugPrint("op_get_proto_data: bad proto %d", pid);
|
|
|
|
programStackPushInteger(program, -1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// CE: Make sure the requested offset is within memory bounds and is
|
|
|
|
// properly aligned.
|
|
|
|
if (offset + sizeof(int) > proto_size(PID_TYPE(pid)) || offset % sizeof(int) != 0) {
|
|
|
|
debugPrint("op_get_proto_data: bad offset %d", offset);
|
|
|
|
programStackPushInteger(program, -1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int value = *reinterpret_cast<int*>(reinterpret_cast<unsigned char*>(proto) + offset);
|
|
|
|
programStackPushInteger(program, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set_proto_data
|
|
|
|
static void op_set_proto_data(Program* program)
|
|
|
|
{
|
|
|
|
int value = programStackPopInteger(program);
|
|
|
|
size_t offset = static_cast<size_t>(programStackPopInteger(program));
|
|
|
|
int pid = programStackPopInteger(program);
|
|
|
|
|
|
|
|
Proto* proto;
|
|
|
|
if (protoGetProto(pid, &proto) != 0) {
|
|
|
|
debugPrint("op_set_proto_data: bad proto %d", pid);
|
|
|
|
programStackPushInteger(program, -1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// CE: Make sure the requested offset is within memory bounds and is
|
|
|
|
// properly aligned.
|
|
|
|
if (offset + sizeof(int) > proto_size(PID_TYPE(pid)) || offset % sizeof(int) != 0) {
|
|
|
|
debugPrint("op_set_proto_data: bad offset %d", offset);
|
|
|
|
programStackPushInteger(program, -1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
*reinterpret_cast<int*>(reinterpret_cast<unsigned char*>(proto) + offset) = value;
|
|
|
|
}
|
|
|
|
|
2022-11-08 11:55:25 -08:00
|
|
|
// list_begin
|
|
|
|
static void opListBegin(Program* program)
|
|
|
|
{
|
|
|
|
int listType = programStackPopInteger(program);
|
|
|
|
int listId = sfallListsCreate(listType);
|
|
|
|
programStackPushInteger(program, listId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// list_next
|
|
|
|
static void opListNext(Program* program)
|
|
|
|
{
|
|
|
|
int listId = programStackPopInteger(program);
|
|
|
|
Object* obj = sfallListsGetNext(listId);
|
|
|
|
programStackPushPointer(program, obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
// list_end
|
|
|
|
static void opListEnd(Program* program)
|
|
|
|
{
|
|
|
|
int listId = programStackPopInteger(program);
|
|
|
|
sfallListsDestroy(listId);
|
|
|
|
}
|
|
|
|
|
2022-12-22 09:18:07 -08:00
|
|
|
// sfall_ver_major
|
|
|
|
static void opGetVersionMajor(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, kVersionMajor);
|
|
|
|
}
|
|
|
|
|
|
|
|
// sfall_ver_minor
|
|
|
|
static void opGetVersionMinor(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, kVersionMinor);
|
|
|
|
}
|
|
|
|
|
|
|
|
// sfall_ver_build
|
|
|
|
static void opGetVersionPatch(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, kVersionPatch);
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:41:33 -08:00
|
|
|
// get_weapon_ammo_pid
|
|
|
|
static void opGetWeaponAmmoPid(Program* program)
|
|
|
|
{
|
|
|
|
Object* obj = static_cast<Object*>(programStackPopPointer(program));
|
|
|
|
|
|
|
|
int pid = -1;
|
|
|
|
if (obj != nullptr) {
|
|
|
|
if (PID_TYPE(obj->pid) == OBJ_TYPE_ITEM) {
|
|
|
|
switch (itemGetType(obj)) {
|
|
|
|
case ITEM_TYPE_WEAPON:
|
|
|
|
pid = weaponGetAmmoTypePid(obj);
|
|
|
|
break;
|
|
|
|
case ITEM_TYPE_MISC:
|
|
|
|
pid = miscItemGetPowerTypePid(obj);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
programStackPushInteger(program, pid);
|
|
|
|
}
|
|
|
|
|
2022-12-22 09:36:31 -08:00
|
|
|
// There are two problems with this function.
|
|
|
|
//
|
|
|
|
// 1. Sfall's implementation changes ammo PID of misc items, which is impossible
|
|
|
|
// since it's stored in proto, not in the object.
|
|
|
|
// 2. Changing weapon's ammo PID is done without checking for ammo
|
|
|
|
// quantity/capacity which can probably lead to bad things.
|
|
|
|
//
|
|
|
|
// set_weapon_ammo_pid
|
|
|
|
static void opSetWeaponAmmoPid(Program* program)
|
|
|
|
{
|
|
|
|
int ammoTypePid = programStackPopInteger(program);
|
|
|
|
Object* obj = static_cast<Object*>(programStackPopPointer(program));
|
|
|
|
|
|
|
|
if (obj != nullptr) {
|
|
|
|
if (PID_TYPE(obj->pid) == OBJ_TYPE_ITEM) {
|
|
|
|
switch (itemGetType(obj)) {
|
|
|
|
case ITEM_TYPE_WEAPON:
|
|
|
|
obj->data.item.weapon.ammoTypePid = ammoTypePid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:41:33 -08:00
|
|
|
// get_weapon_ammo_count
|
|
|
|
static void opGetWeaponAmmoCount(Program* program)
|
|
|
|
{
|
|
|
|
Object* obj = static_cast<Object*>(programStackPopPointer(program));
|
|
|
|
|
|
|
|
// CE: Implementation is different.
|
|
|
|
int ammoQuantityOrCharges = 0;
|
|
|
|
if (obj != nullptr) {
|
|
|
|
if (PID_TYPE(obj->pid) == OBJ_TYPE_ITEM) {
|
|
|
|
switch (itemGetType(obj)) {
|
|
|
|
case ITEM_TYPE_AMMO:
|
|
|
|
case ITEM_TYPE_WEAPON:
|
|
|
|
ammoQuantityOrCharges = ammoGetQuantity(obj);
|
|
|
|
break;
|
|
|
|
case ITEM_TYPE_MISC:
|
|
|
|
ammoQuantityOrCharges = miscItemGetCharges(obj);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
programStackPushInteger(program, ammoQuantityOrCharges);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set_weapon_ammo_count
|
|
|
|
static void opSetWeaponAmmoCount(Program* program)
|
|
|
|
{
|
|
|
|
int ammoQuantityOrCharges = programStackPopInteger(program);
|
|
|
|
Object* obj = static_cast<Object*>(programStackPopPointer(program));
|
|
|
|
|
|
|
|
// CE: Implementation is different.
|
|
|
|
if (obj != nullptr) {
|
|
|
|
if (PID_TYPE(obj->pid) == OBJ_TYPE_ITEM) {
|
|
|
|
switch (itemGetType(obj)) {
|
|
|
|
case ITEM_TYPE_AMMO:
|
|
|
|
case ITEM_TYPE_WEAPON:
|
|
|
|
ammoSetQuantity(obj, ammoQuantityOrCharges);
|
|
|
|
break;
|
|
|
|
case ITEM_TYPE_MISC:
|
|
|
|
miscItemSetCharges(obj, ammoQuantityOrCharges);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
// get_mouse_x
|
|
|
|
static void opGetMouseX(Program* program)
|
|
|
|
{
|
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
mouseGetPosition(&x, &y);
|
|
|
|
programStackPushInteger(program, x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// get_mouse_y
|
|
|
|
static void opGetMouseY(Program* program)
|
|
|
|
{
|
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
mouseGetPosition(&x, &y);
|
|
|
|
programStackPushInteger(program, y);
|
|
|
|
}
|
|
|
|
|
2023-04-20 00:31:32 -07:00
|
|
|
// get_mouse_buttons
|
|
|
|
static void op_get_mouse_buttons(Program* program)
|
|
|
|
{
|
|
|
|
// CE: Implementation is slightly different - it does not handle middle
|
|
|
|
// mouse button.
|
|
|
|
programStackPushInteger(program, mouse_get_last_buttons());
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
// get_screen_width
|
|
|
|
static void opGetScreenWidth(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, screenGetWidth());
|
|
|
|
}
|
|
|
|
|
|
|
|
// get_screen_height
|
|
|
|
static void opGetScreenHeight(Program* program)
|
|
|
|
{
|
|
|
|
programStackPushInteger(program, screenGetHeight());
|
|
|
|
}
|
|
|
|
|
2023-04-20 00:51:20 -07:00
|
|
|
// get_attack_type
|
|
|
|
static void op_get_attack_type(Program* program)
|
|
|
|
{
|
|
|
|
int hit_mode;
|
|
|
|
if (interface_get_current_attack_mode(&hit_mode)) {
|
|
|
|
programStackPushInteger(program, hit_mode);
|
|
|
|
} else {
|
|
|
|
programStackPushInteger(program, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
// atoi
|
|
|
|
static void opParseInt(Program* program)
|
|
|
|
{
|
|
|
|
const char* string = programStackPopString(program);
|
|
|
|
programStackPushInteger(program, static_cast<int>(strtol(string, nullptr, 0)));
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:53:50 -07:00
|
|
|
// atof
|
|
|
|
static void op_atof(Program* program)
|
|
|
|
{
|
|
|
|
const char* string = programStackPopString(program);
|
|
|
|
programStackPushFloat(program, static_cast<float>(atof(string)));
|
|
|
|
}
|
|
|
|
|
2023-04-20 00:25:13 -07:00
|
|
|
// tile_under_cursor
|
|
|
|
static void op_tile_under_cursor(Program* program)
|
|
|
|
{
|
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
mouseGetPosition(&x, &y);
|
|
|
|
|
|
|
|
int tile = tileFromScreenXY(x, y, gElevation);
|
|
|
|
programStackPushInteger(program, tile);
|
|
|
|
}
|
|
|
|
|
2023-05-29 23:06:55 -07:00
|
|
|
// substr
|
|
|
|
static void opSubstr(Program* program)
|
|
|
|
{
|
|
|
|
auto length = programStackPopInteger(program);
|
|
|
|
auto startPos = programStackPopInteger(program);
|
|
|
|
const char* str = programStackPopString(program);
|
|
|
|
|
|
|
|
char buf[5120] = { 0 };
|
|
|
|
|
|
|
|
int len = strlen(str);
|
|
|
|
|
|
|
|
if (startPos < 0) {
|
|
|
|
startPos += len; // start from end
|
|
|
|
if (startPos < 0) {
|
|
|
|
startPos = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length < 0) {
|
|
|
|
length += len - startPos; // cutoff at end
|
|
|
|
if (length == 0) {
|
|
|
|
programStackPushString(program, buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
length = abs(length); // length can't be negative
|
|
|
|
}
|
|
|
|
|
|
|
|
// check position
|
|
|
|
if (startPos >= len) {
|
|
|
|
// start position is out of string length, return empty string
|
|
|
|
programStackPushString(program, buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length == 0 || length + startPos > len) {
|
|
|
|
length = len - startPos; // set the correct length, the length of characters goes beyond the end of the string
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length > sizeof(buf) - 1) {
|
|
|
|
length = sizeof(buf) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(buf, &str[startPos], length);
|
|
|
|
buf[length] = '\0';
|
|
|
|
programStackPushString(program, buf);
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
// strlen
|
|
|
|
static void opGetStringLength(Program* program)
|
|
|
|
{
|
|
|
|
const char* string = programStackPopString(program);
|
|
|
|
programStackPushInteger(program, static_cast<int>(strlen(string)));
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:53:50 -07:00
|
|
|
// pow (^)
|
|
|
|
static void op_power(Program* program)
|
|
|
|
{
|
|
|
|
ProgramValue expValue = programStackPopValue(program);
|
|
|
|
ProgramValue baseValue = programStackPopValue(program);
|
|
|
|
|
|
|
|
// CE: Implementation is slightly different, check.
|
|
|
|
float result = powf(baseValue.asFloat(), expValue.asFloat());
|
|
|
|
|
|
|
|
if (baseValue.isInt() && expValue.isInt()) {
|
|
|
|
programStackPushInteger(program, static_cast<int>(result));
|
|
|
|
} else {
|
|
|
|
programStackPushFloat(program, result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-10 07:07:23 -08:00
|
|
|
// message_str_game
|
|
|
|
static void opGetMessage(Program* program)
|
|
|
|
{
|
|
|
|
int messageId = programStackPopInteger(program);
|
|
|
|
int messageListId = programStackPopInteger(program);
|
|
|
|
char* text = messageListRepositoryGetMsg(messageListId, messageId);
|
|
|
|
programStackPushString(program, text);
|
|
|
|
}
|
|
|
|
|
2023-05-29 23:06:55 -07:00
|
|
|
// get_array_key
|
|
|
|
static void opGetArrayKey(Program* program)
|
|
|
|
{
|
|
|
|
auto index = programStackPopInteger(program);
|
|
|
|
auto arrayId = programStackPopInteger(program);
|
|
|
|
auto value = GetArrayKey(arrayId, index, program);
|
|
|
|
programStackPushValue(program, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// create_array
|
|
|
|
static void opCreateArray(Program* program)
|
|
|
|
{
|
|
|
|
auto flags = programStackPopInteger(program);
|
|
|
|
auto len = programStackPopInteger(program);
|
|
|
|
auto arrayId = CreateArray(len, flags);
|
|
|
|
programStackPushInteger(program, arrayId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// temp_array
|
|
|
|
static void opTempArray(Program* program)
|
|
|
|
{
|
|
|
|
auto flags = programStackPopInteger(program);
|
|
|
|
auto len = programStackPopInteger(program);
|
|
|
|
auto arrayId = CreateTempArray(len, flags);
|
|
|
|
programStackPushInteger(program, arrayId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// fix_array
|
|
|
|
static void opFixArray(Program* program)
|
|
|
|
{
|
|
|
|
auto arrayId = programStackPopInteger(program);
|
|
|
|
FixArray(arrayId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// string_split
|
|
|
|
static void opStringSplit(Program* program)
|
|
|
|
{
|
|
|
|
auto split = programStackPopString(program);
|
|
|
|
auto str = programStackPopString(program);
|
|
|
|
auto arrayId = StringSplit(str, split);
|
|
|
|
programStackPushInteger(program, arrayId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set_array
|
|
|
|
static void opSetArray(Program* program)
|
|
|
|
{
|
|
|
|
auto value = programStackPopValue(program);
|
|
|
|
auto key = programStackPopValue(program);
|
|
|
|
auto arrayId = programStackPopInteger(program);
|
|
|
|
SetArray(arrayId, key, value, true, program);
|
|
|
|
}
|
|
|
|
|
|
|
|
// arrayexpr
|
|
|
|
static void opStackArray(Program* program)
|
|
|
|
{
|
|
|
|
auto value = programStackPopValue(program);
|
|
|
|
auto key = programStackPopValue(program);
|
|
|
|
auto returnValue = StackArray(key, value, program);
|
|
|
|
programStackPushInteger(program, returnValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
// scan array
|
|
|
|
static void opScanArray(Program* program)
|
|
|
|
{
|
|
|
|
auto value = programStackPopValue(program);
|
|
|
|
auto arrayId = programStackPopInteger(program);
|
|
|
|
auto returnValue = ScanArray(arrayId, value, program);
|
|
|
|
programStackPushValue(program, returnValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
// get_array
|
|
|
|
static void opGetArray(Program* program)
|
|
|
|
{
|
|
|
|
auto key = programStackPopValue(program);
|
|
|
|
auto arrayId = programStackPopValue(program);
|
|
|
|
|
|
|
|
if (arrayId.isInt()) {
|
|
|
|
auto value = GetArray(arrayId.integerValue, key, program);
|
|
|
|
programStackPushValue(program, value);
|
|
|
|
} else if (arrayId.isString() && key.isInt()) {
|
|
|
|
auto pos = key.asInt();
|
|
|
|
auto str = programGetString(program, arrayId.opcode, arrayId.integerValue);
|
|
|
|
|
|
|
|
char buf[2] = { 0 };
|
|
|
|
if (pos < strlen(str)) {
|
|
|
|
buf[0] = str[pos];
|
|
|
|
programStackPushString(program, buf);
|
|
|
|
} else {
|
|
|
|
programStackPushString(program, buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// free_array
|
|
|
|
static void opFreeArray(Program* program)
|
|
|
|
{
|
|
|
|
auto arrayId = programStackPopInteger(program);
|
|
|
|
FreeArray(arrayId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// len_array
|
|
|
|
static void opLenArray(Program* program)
|
|
|
|
{
|
|
|
|
auto arrayId = programStackPopInteger(program);
|
|
|
|
programStackPushInteger(program, LenArray(arrayId));
|
|
|
|
}
|
|
|
|
|
|
|
|
// resize_array
|
|
|
|
static void opResizeArray(Program* program)
|
|
|
|
{
|
|
|
|
auto newLen = programStackPopInteger(program);
|
|
|
|
auto arrayId = programStackPopInteger(program);
|
|
|
|
ResizeArray(arrayId, newLen);
|
|
|
|
}
|
|
|
|
|
|
|
|
// party_member_list
|
|
|
|
static void opPartyMemberList(Program* program)
|
|
|
|
{
|
|
|
|
auto includeHidden = programStackPopInteger(program);
|
|
|
|
auto objects = get_all_party_members_objects(includeHidden);
|
|
|
|
auto arrayId = CreateTempArray(objects.size(), SFALL_ARRAYFLAG_RESERVED);
|
|
|
|
for (int i = 0; i < LenArray(arrayId); i++) {
|
|
|
|
SetArray(arrayId, ProgramValue { i }, ProgramValue { objects[i] }, false, program);
|
|
|
|
}
|
|
|
|
programStackPushInteger(program, arrayId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// type_of
|
|
|
|
static void opTypeOf(Program* program)
|
|
|
|
{
|
|
|
|
auto value = programStackPopValue(program);
|
|
|
|
if (value.isInt()) {
|
|
|
|
programStackPushInteger(program, 1);
|
|
|
|
} else if (value.isFloat()) {
|
|
|
|
programStackPushInteger(program, 2);
|
|
|
|
} else {
|
|
|
|
programStackPushInteger(program, 3);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
// round
|
|
|
|
static void opRound(Program* program)
|
|
|
|
{
|
|
|
|
float floatValue = programStackPopFloat(program);
|
|
|
|
int integerValue = static_cast<int>(floatValue);
|
|
|
|
float mod = floatValue - static_cast<float>(integerValue);
|
|
|
|
if (abs(mod) >= 0.5) {
|
|
|
|
integerValue += mod > 0.0 ? 1 : -1;
|
|
|
|
}
|
|
|
|
programStackPushInteger(program, integerValue);
|
|
|
|
}
|
|
|
|
|
2023-04-20 01:22:47 -07:00
|
|
|
enum BlockType {
|
|
|
|
BLOCKING_TYPE_BLOCK,
|
|
|
|
BLOCKING_TYPE_SHOOT,
|
|
|
|
BLOCKING_TYPE_AI,
|
|
|
|
BLOCKING_TYPE_SIGHT,
|
|
|
|
BLOCKING_TYPE_SCROLL,
|
|
|
|
};
|
|
|
|
|
|
|
|
PathBuilderCallback* get_blocking_func(int type)
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case BLOCKING_TYPE_SHOOT:
|
|
|
|
return _obj_shoot_blocking_at;
|
|
|
|
case BLOCKING_TYPE_AI:
|
|
|
|
return _obj_ai_blocking_at;
|
|
|
|
case BLOCKING_TYPE_SIGHT:
|
|
|
|
return _obj_sight_blocking_at;
|
|
|
|
default:
|
|
|
|
return _obj_blocking_at;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// obj_blocking_line
|
|
|
|
static void op_make_straight_path(Program* program)
|
|
|
|
{
|
|
|
|
int type = programStackPopInteger(program);
|
|
|
|
int dest = programStackPopInteger(program);
|
|
|
|
Object* object = static_cast<Object*>(programStackPopPointer(program));
|
|
|
|
|
|
|
|
int flags = type == BLOCKING_TYPE_SHOOT ? 32 : 0;
|
|
|
|
|
|
|
|
Object* obstacle = nullptr;
|
|
|
|
_make_straight_path_func(object, object->tile, dest, nullptr, &obstacle, flags, get_blocking_func(type));
|
|
|
|
programStackPushPointer(program, obstacle);
|
|
|
|
}
|
|
|
|
|
|
|
|
// obj_blocking_tile
|
|
|
|
static void op_obj_blocking_at(Program* program)
|
|
|
|
{
|
|
|
|
int type = programStackPopInteger(program);
|
|
|
|
int elevation = programStackPopInteger(program);
|
|
|
|
int tile = programStackPopInteger(program);
|
|
|
|
|
|
|
|
PathBuilderCallback* func = get_blocking_func(type);
|
|
|
|
Object* obstacle = func(NULL, tile, elevation);
|
|
|
|
if (obstacle != NULL) {
|
|
|
|
if (type == BLOCKING_TYPE_SHOOT) {
|
|
|
|
if ((obstacle->flags & OBJECT_SHOOT_THRU) != 0) {
|
|
|
|
obstacle = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
programStackPushPointer(program, obstacle);
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
// art_exists
|
|
|
|
static void opArtExists(Program* program)
|
|
|
|
{
|
|
|
|
int fid = programStackPopInteger(program);
|
|
|
|
programStackPushInteger(program, artExists(fid));
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:53:50 -07:00
|
|
|
// div (/)
|
|
|
|
static void op_div(Program* program)
|
|
|
|
{
|
|
|
|
ProgramValue divisorValue = programStackPopValue(program);
|
|
|
|
ProgramValue dividendValue = programStackPopValue(program);
|
|
|
|
|
|
|
|
if (divisorValue.integerValue == 0) {
|
|
|
|
debugPrint("Division by zero");
|
|
|
|
|
|
|
|
// TODO: Looks like execution is not halted in Sfall's div, check.
|
|
|
|
programStackPushInteger(program, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dividendValue.isFloat() || divisorValue.isFloat()) {
|
|
|
|
programStackPushFloat(program, dividendValue.asFloat() / divisorValue.asFloat());
|
|
|
|
} else {
|
|
|
|
// Unsigned divison.
|
|
|
|
programStackPushInteger(program, static_cast<unsigned int>(dividendValue.integerValue) / static_cast<unsigned int>(divisorValue.integerValue));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 09:03:04 -08:00
|
|
|
void sfallOpcodesInit()
|
|
|
|
{
|
2022-11-09 02:09:56 -08:00
|
|
|
interpreterRegisterOpcode(0x8156, opReadByte);
|
2023-04-18 23:02:54 -07:00
|
|
|
interpreterRegisterOpcode(0x815A, op_set_pc_base_stat);
|
2022-11-08 07:59:14 -08:00
|
|
|
interpreterRegisterOpcode(0x815B, opSetPcBonusStat);
|
2023-04-18 23:02:54 -07:00
|
|
|
interpreterRegisterOpcode(0x815C, op_get_pc_base_stat);
|
2022-11-08 07:59:14 -08:00
|
|
|
interpreterRegisterOpcode(0x815D, opGetPcBonusStat);
|
2023-04-19 09:10:09 -07:00
|
|
|
interpreterRegisterOpcode(0x8163, op_get_year);
|
2023-04-18 23:09:17 -07:00
|
|
|
interpreterRegisterOpcode(0x8170, op_in_world_map);
|
|
|
|
interpreterRegisterOpcode(0x8172, op_set_world_map_pos);
|
2022-11-07 09:03:04 -08:00
|
|
|
interpreterRegisterOpcode(0x8193, opGetCurrentHand);
|
2022-11-08 07:01:00 -08:00
|
|
|
interpreterRegisterOpcode(0x819D, opSetGlobalVar);
|
|
|
|
interpreterRegisterOpcode(0x819E, opGetGlobalInt);
|
2022-12-06 06:46:52 -08:00
|
|
|
interpreterRegisterOpcode(0x81AF, opGetGameMode);
|
2023-04-19 09:10:09 -07:00
|
|
|
interpreterRegisterOpcode(0x81B3, op_get_uptime);
|
2023-04-18 23:09:17 -07:00
|
|
|
interpreterRegisterOpcode(0x81B6, op_set_car_current_town);
|
2023-04-19 00:03:04 -07:00
|
|
|
interpreterRegisterOpcode(0x81DF, op_get_bodypart_hit_modifier);
|
|
|
|
interpreterRegisterOpcode(0x81E0, op_set_bodypart_hit_modifier);
|
2023-04-19 23:53:50 -07:00
|
|
|
interpreterRegisterOpcode(0x81EC, op_sqrt);
|
|
|
|
interpreterRegisterOpcode(0x81ED, op_abs);
|
2023-04-18 23:21:38 -07:00
|
|
|
interpreterRegisterOpcode(0x8204, op_get_proto_data);
|
|
|
|
interpreterRegisterOpcode(0x8205, op_set_proto_data);
|
2022-11-08 11:55:25 -08:00
|
|
|
interpreterRegisterOpcode(0x820D, opListBegin);
|
|
|
|
interpreterRegisterOpcode(0x820E, opListNext);
|
|
|
|
interpreterRegisterOpcode(0x820F, opListEnd);
|
2022-12-22 09:18:07 -08:00
|
|
|
interpreterRegisterOpcode(0x8210, opGetVersionMajor);
|
|
|
|
interpreterRegisterOpcode(0x8211, opGetVersionMinor);
|
|
|
|
interpreterRegisterOpcode(0x8212, opGetVersionPatch);
|
2022-11-07 09:41:33 -08:00
|
|
|
interpreterRegisterOpcode(0x8217, opGetWeaponAmmoPid);
|
2022-12-22 09:36:31 -08:00
|
|
|
interpreterRegisterOpcode(0x8218, opSetWeaponAmmoPid);
|
2022-11-07 09:41:33 -08:00
|
|
|
interpreterRegisterOpcode(0x8219, opGetWeaponAmmoCount);
|
|
|
|
interpreterRegisterOpcode(0x821A, opSetWeaponAmmoCount);
|
2022-11-07 09:03:04 -08:00
|
|
|
interpreterRegisterOpcode(0x821C, opGetMouseX);
|
|
|
|
interpreterRegisterOpcode(0x821D, opGetMouseY);
|
2023-04-20 00:31:32 -07:00
|
|
|
interpreterRegisterOpcode(0x821E, op_get_mouse_buttons);
|
2022-11-07 09:03:04 -08:00
|
|
|
interpreterRegisterOpcode(0x8220, opGetScreenWidth);
|
|
|
|
interpreterRegisterOpcode(0x8221, opGetScreenHeight);
|
2023-04-20 00:51:20 -07:00
|
|
|
interpreterRegisterOpcode(0x8228, op_get_attack_type);
|
2023-05-29 23:06:55 -07:00
|
|
|
interpreterRegisterOpcode(0x822D, opCreateArray);
|
|
|
|
interpreterRegisterOpcode(0x822E, opSetArray);
|
|
|
|
interpreterRegisterOpcode(0x822F, opGetArray);
|
|
|
|
interpreterRegisterOpcode(0x8230, opFreeArray);
|
|
|
|
interpreterRegisterOpcode(0x8231, opLenArray);
|
|
|
|
interpreterRegisterOpcode(0x8232, opResizeArray);
|
|
|
|
interpreterRegisterOpcode(0x8233, opTempArray);
|
|
|
|
interpreterRegisterOpcode(0x8234, opFixArray);
|
|
|
|
interpreterRegisterOpcode(0x8235, opStringSplit);
|
2022-11-07 09:03:04 -08:00
|
|
|
interpreterRegisterOpcode(0x8237, opParseInt);
|
2023-04-19 23:53:50 -07:00
|
|
|
interpreterRegisterOpcode(0x8238, op_atof);
|
2023-05-29 23:06:55 -07:00
|
|
|
interpreterRegisterOpcode(0x8239, opScanArray);
|
2023-04-20 00:25:13 -07:00
|
|
|
interpreterRegisterOpcode(0x824B, op_tile_under_cursor);
|
2023-05-29 23:06:55 -07:00
|
|
|
interpreterRegisterOpcode(0x824E, opSubstr);
|
2022-11-07 09:03:04 -08:00
|
|
|
interpreterRegisterOpcode(0x824F, opGetStringLength);
|
2023-05-29 23:06:55 -07:00
|
|
|
interpreterRegisterOpcode(0x8253, opTypeOf);
|
|
|
|
interpreterRegisterOpcode(0x8256, opGetArrayKey);
|
|
|
|
interpreterRegisterOpcode(0x8257, opStackArray);
|
2023-04-19 23:53:50 -07:00
|
|
|
interpreterRegisterOpcode(0x8263, op_power);
|
2022-11-07 09:03:04 -08:00
|
|
|
interpreterRegisterOpcode(0x8267, opRound);
|
2023-05-29 23:06:55 -07:00
|
|
|
interpreterRegisterOpcode(0x826B, opGetMessage);
|
2023-04-20 01:22:47 -07:00
|
|
|
interpreterRegisterOpcode(0x826E, op_make_straight_path);
|
|
|
|
interpreterRegisterOpcode(0x826F, op_obj_blocking_at);
|
2023-05-29 23:06:55 -07:00
|
|
|
interpreterRegisterOpcode(0x8271, opPartyMemberList);
|
2022-11-07 09:03:04 -08:00
|
|
|
interpreterRegisterOpcode(0x8274, opArtExists);
|
2023-04-19 23:53:50 -07:00
|
|
|
interpreterRegisterOpcode(0x827F, op_div);
|
2022-11-07 09:03:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void sfallOpcodesExit()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace fallout
|