#include "interpreter_extra.h" #include #include #include #include "actions.h" #include "animation.h" #include "art.h" #include "color.h" #include "combat.h" #include "combat_ai.h" #include "critter.h" #include "debug.h" #include "dialog.h" #include "display_monitor.h" #include "endgame.h" #include "game.h" #include "game_dialog.h" #include "game_movie.h" #include "game_sound.h" #include "geometry.h" #include "interface.h" #include "item.h" #include "light.h" #include "loadsave.h" #include "map.h" #include "object.h" #include "palette.h" #include "party_member.h" #include "perk.h" #include "proto.h" #include "proto_instance.h" #include "queue.h" #include "random.h" #include "reaction.h" #include "scripts.h" #include "settings.h" #include "sfall_opcodes.h" #include "skill.h" #include "stat.h" #include "svga.h" #include "text_object.h" #include "tile.h" #include "trait.h" #include "vcr.h" #include "worldmap.h" namespace fallout { typedef enum ScriptError { SCRIPT_ERROR_NOT_IMPLEMENTED, SCRIPT_ERROR_OBJECT_IS_NULL, SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID, SCRIPT_ERROR_FOLLOWS, SCRIPT_ERROR_COUNT, } ScriptError; typedef enum Metarule { METARULE_SIGNAL_END_GAME = 13, METARULE_FIRST_RUN = 14, METARULE_ELEVATOR = 15, METARULE_PARTY_COUNT = 16, METARULE_AREA_KNOWN = 17, METARULE_WHO_ON_DRUGS = 18, METARULE_MAP_KNOWN = 19, METARULE_IS_LOADGAME = 22, METARULE_CAR_CURRENT_TOWN = 30, METARULE_GIVE_CAR_TO_PARTY = 31, METARULE_GIVE_CAR_GAS = 32, METARULE_SKILL_CHECK_TAG = 40, METARULE_DROP_ALL_INVEN = 42, METARULE_INVEN_UNWIELD_WHO = 43, METARULE_GET_WORLDMAP_XPOS = 44, METARULE_GET_WORLDMAP_YPOS = 45, METARULE_CURRENT_TOWN = 46, METARULE_LANGUAGE_FILTER = 47, METARULE_VIOLENCE_FILTER = 48, METARULE_WEAPON_DAMAGE_TYPE = 49, METARULE_CRITTER_BARTERS = 50, METARULE_CRITTER_KILL_TYPE = 51, METARULE_SET_CAR_CARRY_AMOUNT = 52, METARULE_GET_CAR_CARRY_AMOUNT = 53, } Metarule; typedef enum Metarule3 { METARULE3_CLR_FIXED_TIMED_EVENTS = 100, METARULE3_MARK_SUBTILE = 101, METARULE3_SET_WM_MUSIC = 102, METARULE3_GET_KILL_COUNT = 103, METARULE3_MARK_MAP_ENTRANCE = 104, METARULE3_WM_SUBTILE_STATE = 105, METARULE3_TILE_GET_NEXT_CRITTER = 106, METARULE3_ART_SET_BASE_FID_NUM = 107, METARULE3_TILE_SET_CENTER = 108, // chem use preference METARULE3_109 = 109, // probably true if car is out of fuel METARULE3_110 = 110, // probably returns city index METARULE3_111 = 111, } Metarule3; typedef enum CritterTrait { CRITTER_TRAIT_PERK = 0, CRITTER_TRAIT_OBJECT = 1, CRITTER_TRAIT_TRAIT = 2, } CritterTrait; typedef enum CritterTraitObject { CRITTER_TRAIT_OBJECT_AI_PACKET = 5, CRITTER_TRAIT_OBJECT_TEAM = 6, CRITTER_TRAIT_OBJECT_ROTATION = 10, CRITTER_TRAIT_OBJECT_IS_INVISIBLE = 666, CRITTER_TRAIT_OBJECT_GET_INVENTORY_WEIGHT = 669, } CritterTraitObject; // See [opGetCritterState]. typedef enum CritterState { CRITTER_STATE_NORMAL = 0x00, CRITTER_STATE_DEAD = 0x01, CRITTER_STATE_PRONE = 0x02, } CritterState; enum { INVEN_TYPE_WORN = 0, INVEN_TYPE_RIGHT_HAND = 1, INVEN_TYPE_LEFT_HAND = 2, INVEN_TYPE_INV_COUNT = -2, }; typedef enum FloatingMessageType { FLOATING_MESSAGE_TYPE_WARNING = -2, FLOATING_MESSAGE_TYPE_COLOR_SEQUENCE = -1, FLOATING_MESSAGE_TYPE_NORMAL = 0, FLOATING_MESSAGE_TYPE_BLACK, FLOATING_MESSAGE_TYPE_RED, FLOATING_MESSAGE_TYPE_GREEN, FLOATING_MESSAGE_TYPE_BLUE, FLOATING_MESSAGE_TYPE_PURPLE, FLOATING_MESSAGE_TYPE_NEAR_WHITE, FLOATING_MESSAGE_TYPE_LIGHT_RED, FLOATING_MESSAGE_TYPE_YELLOW, FLOATING_MESSAGE_TYPE_WHITE, FLOATING_MESSAGE_TYPE_GREY, FLOATING_MESSAGE_TYPE_DARK_GREY, FLOATING_MESSAGE_TYPE_LIGHT_GREY, FLOATING_MESSAGE_TYPE_COUNT, } FloatingMessageType; typedef enum OpRegAnimFunc { OP_REG_ANIM_FUNC_BEGIN = 1, OP_REG_ANIM_FUNC_CLEAR = 2, OP_REG_ANIM_FUNC_END = 3, } OpRegAnimFunc; static void scriptPredefinedError(Program* program, const char* name, int error); static void scriptError(const char* format, ...); static int tileIsVisible(int tile); static int _correctFidForRemovedItem(Object* a1, Object* a2, int a3); static void opGiveExpPoints(Program* program); static void opScrReturn(Program* program); static void opPlaySfx(Program* program); static void opSetMapStart(Program* program); static void opOverrideMapStart(Program* program); static void opHasSkill(Program* program); static void opUsingSkill(Program* program); static void opRollVsSkill(Program* program); static void opSkillContest(Program* program); static void opDoCheck(Program* program); static void opSuccess(Program* program); static void opCritical(Program* program); static void opHowMuch(Program* program); static void opMarkAreaKnown(Program* program); static void opReactionInfluence(Program* program); static void opRandom(Program* program); static void opRollDice(Program* program); static void opMoveTo(Program* program); static void opCreateObject(Program* program); static void opDestroyObject(Program* program); static void opDisplayMsg(Program* program); static void opScriptOverrides(Program* program); static void opObjectIsCarryingObjectWithPid(Program* program); static void opTileContainsObjectWithPid(Program* program); static void opGetSelf(Program* program); static void opGetSource(Program* program); static void opGetTarget(Program* program); static void opGetDude(Program* program); static void opGetObjectBeingUsed(Program* program); static void opGetLocalVar(Program* program); static void opSetLocalVar(Program* program); static void opGetMapVar(Program* program); static void opSetMapVar(Program* program); static void opGetGlobalVar(Program* program); static void opSetGlobalVar(Program* program); static void opGetScriptAction(Program* program); static void opGetObjectType(Program* program); static void opGetItemType(Program* program); static void opGetCritterStat(Program* program); static void opSetCritterStat(Program* program); static void opAnimateStand(Program* program); static void opAnimateStandReverse(Program* program); static void opAnimateMoveObjectToTile(Program* program); static void opTileInTileRect(Program* program); static void opMakeDayTime(Program* program); static void opTileDistanceBetween(Program* program); static void opTileDistanceBetweenObjects(Program* program); static void opGetObjectTile(Program* program); static void opGetTileInDirection(Program* program); static void opPickup(Program* program); static void opDrop(Program* program); static void opAddObjectToInventory(Program* program); static void opRemoveObjectFromInventory(Program* program); static void opWieldItem(Program* program); static void opUseObject(Program* program); static void opObjectCanSeeObject(Program* program); static void opAttackComplex(Program* program); static void opStartGameDialog(Program* program); static void opEndGameDialog(Program* program); static void opGameDialogReaction(Program* program); static void opMetarule3(Program* program); static void opSetMapMusic(Program* program); static void opSetObjectVisibility(Program* program); static void opLoadMap(Program* program); static void opWorldmapCitySetPos(Program* program); static void opSetExitGrids(Program* program); static void opAnimBusy(Program* program); static void opCritterHeal(Program* program); static void opSetLightLevel(Program* program); static void opGetGameTime(Program* program); static void opGetGameTimeInSeconds(Program* program); static void opGetObjectElevation(Program* program); static void opKillCritter(Program* program); static int _correctDeath(Object* critter, int anim, bool a3); static void opKillCritterType(Program* program); static void opCritterDamage(Program* program); static void opAddTimerEvent(Program* program); static void opRemoveTimerEvent(Program* program); static void opGameTicks(Program* program); static void opHasTrait(Program* program); static void opObjectCanHearObject(Program* program); static void opGameTimeHour(Program* program); static void opGetFixedParam(Program* program); static void opTileIsVisible(Program* program); static void opGameDialogSystemEnter(Program* program); static void opGetActionBeingUsed(Program* program); static void opGetCritterState(Program* program); static void opGameTimeAdvance(Program* program); static void opRadiationIncrease(Program* program); static void opRadiationDecrease(Program* program); static void opCritterAttemptPlacement(Program* program); static void opGetObjectPid(Program* program); static void opGetCurrentMap(Program* program); static void opCritterAddTrait(Program* program); static void opCritterRemoveTrait(Program* program); static void opGetProtoData(Program* program); static void opGetMessageString(Program* program); static void opCritterGetInventoryObject(Program* program); static void opSetObjectLightLevel(Program* program); static void opWorldmap(Program* program); static void _op_inven_cmds(Program* program); static void opFloatMessage(Program* program); static void opMetarule(Program* program); static void opAnim(Program* program); static void opObjectCarryingObjectByPid(Program* program); static void opRegAnimFunc(Program* program); static void opRegAnimAnimate(Program* program); static void opRegAnimAnimateReverse(Program* program); static void opRegAnimObjectMoveToObject(Program* program); static void opRegAnimObjectRunToObject(Program* program); static void opRegAnimObjectMoveToTile(Program* program); static void opRegAnimObjectRunToTile(Program* program); static void opPlayGameMovie(Program* program); static void opAddMultipleObjectsToInventory(Program* program); static void opRemoveMultipleObjectsFromInventory(Program* program); static void opGetMonth(Program* program); static void opGetDay(Program* program); static void opExplosion(Program* program); static void opGetDaysSinceLastVisit(Program* program); static void _op_gsay_start(Program* program); static void _op_gsay_end(Program* program); static void _op_gsay_reply(Program* program); static void _op_gsay_option(Program* program); static void _op_gsay_message(Program* program); static void _op_giq_option(Program* program); static void opPoison(Program* program); static void opGetPoison(Program* program); static void opPartyAdd(Program* program); static void opPartyRemove(Program* program); static void opRegAnimAnimateForever(Program* program); static void opCritterInjure(Program* program); static void opCombatIsInitialized(Program* program); static void _op_gdialog_barter(Program* program); static void opGetGameDifficulty(Program* program); static void opGetRunningBurningGuy(Program* program); static void _op_inven_unwield(Program* program); static void opObjectIsLocked(Program* program); static void opObjectLock(Program* program); static void opObjectUnlock(Program* program); static void opObjectIsOpen(Program* program); static void opObjectOpen(Program* program); static void opObjectClose(Program* program); static void opGameUiDisable(Program* program); static void opGameUiEnable(Program* program); static void opGameUiIsDisabled(Program* program); static void opGameFadeOut(Program* program); static void opGameFadeIn(Program* program); static void opItemCapsTotal(Program* program); static void opItemCapsAdjust(Program* program); static void _op_anim_action_frame(Program* program); static void opRegAnimPlaySfx(Program* program); static void opCritterModifySkill(Program* program); static void opSfxBuildCharName(Program* program); static void opSfxBuildAmbientName(Program* program); static void opSfxBuildInterfaceName(Program* program); static void opSfxBuildItemName(Program* program); static void opSfxBuildWeaponName(Program* program); static void opSfxBuildSceneryName(Program* program); static void opSfxBuildOpenName(Program* program); static void opAttackSetup(Program* program); static void opDestroyMultipleObjects(Program* program); static void opUseObjectOnObject(Program* program); static void opEndgameSlideshow(Program* program); static void opMoveObjectInventoryToObject(Program* program); static void opEndgameMovie(Program* program); static void opGetObjectFid(Program* program); static void opGetFidAnim(Program* program); static void opGetPartyMember(Program* program); static void opGetRotationToTile(Program* program); static void opJamLock(Program* program); static void opGameDialogSetBarterMod(Program* program); static void opGetCombatDifficulty(Program* program); static void opObjectOnScreen(Program* program); static void opCritterIsFleeing(Program* program); static void opCritterSetFleeState(Program* program); static void opTerminateCombat(Program* program); static void opDebugMessage(Program* program); static void opCritterStopAttacking(Program* program); static void opTileGetObjectWithPid(Program* program); static void opGetObjectName(Program* program); static void opGetPcStat(Program* program); // 0x504B0C static char _aCritter[] = ""; // 0x453FC0 static Rect stru_453FC0 = { 0, 0, 640, 480 }; // 0x518EC0 static const char* _dbg_error_strs[SCRIPT_ERROR_COUNT] = { "unimped", "obj is NULL", "can't match program to sid", "follows", }; // Last message type during op_float_msg sequential. // // 0x518F00 static int _last_color = 1; // 0x518F04 static char* _strName = _aCritter; // NOTE: This value is a little bit odd. It's used to handle 2 operations: // [opStartGameDialog] and [opGameDialogReaction]. It's not used outside those // functions. // // When used inside [opStartGameDialog] this value stores [Fidget] constant // (1 - Good, 4 - Neutral, 7 - Bad). // // When used inside [opGameDialogReaction] this value contains specified // reaction (-1 - Good, 0 - Neutral, 1 - Bad). // // 0x5970D0 static int gGameDialogReactionOrFidget; // 0x453FD0 static void scriptPredefinedError(Program* program, const char* name, int error) { char string[260]; snprintf(string, sizeof(string), "Script Error: %s: op_%s: %s", program->name, name, _dbg_error_strs[error]); debugPrint(string); } // 0x45400C static void scriptError(const char* format, ...) { char string[260]; va_list argptr; va_start(argptr, format); vsnprintf(string, sizeof(string), format, argptr); va_end(argptr); debugPrint(string); } // 0x45404C static int tileIsVisible(int tile) { if (abs(gCenterTile - tile) % 200 < 5) { return 1; } if (abs(gCenterTile - tile) / 200 < 5) { return 1; } return 0; } // 0x45409C static int _correctFidForRemovedItem(Object* a1, Object* a2, int flags) { if (a1 == gDude) { bool animated = !gameUiIsDisabled(); interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } int fid = a1->fid; int v8 = (fid & 0xF000) >> 12; int newFid = -1; if ((flags & 0x03000000) != 0) { if (a1 == gDude) { if (interfaceGetCurrentHand()) { if ((flags & 0x02000000) != 0) { v8 = 0; } } else { if ((flags & 0x01000000) != 0) { v8 = 0; } } } else { if ((flags & 0x02000000) != 0) { v8 = 0; } } if (v8 == 0) { newFid = buildFid(FID_TYPE(fid), fid & 0xFFF, FID_ANIM_TYPE(fid), 0, (fid & 0x70000000) >> 28); } } else { if (a1 == gDude) { newFid = buildFid(FID_TYPE(fid), _art_vault_guy_num, FID_ANIM_TYPE(fid), v8, (fid & 0x70000000) >> 28); } _adjust_ac(a1, a2, NULL); } if (newFid != -1) { Rect rect; objectSetFid(a1, newFid, &rect); tileWindowRefreshRect(&rect, gElevation); } return 0; } // give_exp_points // 0x4541C8 static void opGiveExpPoints(Program* program) { int xp = programStackPopInteger(program); if (pcAddExperience(xp) != 0) { scriptError("\nScript Error: %s: op_give_exp_points: stat_pc_set failed"); } } // scr_return // 0x454238 static void opScrReturn(Program* program) { int data = programStackPopInteger(program); int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { script->field_28 = data; } } // play_sfx // 0x4542AC static void opPlaySfx(Program* program) { char* name = programStackPopString(program); soundPlayFile(name); } // set_map_start // 0x454314 static void opSetMapStart(Program* program) { int rotation = programStackPopInteger(program); int elevation = programStackPopInteger(program); int y = programStackPopInteger(program); int x = programStackPopInteger(program); if (mapSetElevation(elevation) != 0) { scriptError("\nScript Error: %s: op_set_map_start: map_set_elevation failed", program->name); return; } int tile = 200 * y + x; if (tileSetCenter(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) { scriptError("\nScript Error: %s: op_set_map_start: tile_set_center failed", program->name); return; } mapSetStart(tile, elevation, rotation); } // override_map_start // 0x4543F4 static void opOverrideMapStart(Program* program) { program->flags |= PROGRAM_FLAG_0x20; int rotation = programStackPopInteger(program); int elevation = programStackPopInteger(program); int y = programStackPopInteger(program); int x = programStackPopInteger(program); char text[60]; snprintf(text, sizeof(text), "OVERRIDE_MAP_START: x: %d, y: %d", x, y); debugPrint(text); int tile = 200 * y + x; int previousTile = gCenterTile; if (tile != -1) { if (objectSetRotation(gDude, rotation, NULL) != 0) { scriptError("\nError: %s: obj_set_rotation failed in override_map_start!", program->name); } if (objectSetLocation(gDude, tile, elevation, NULL) != 0) { scriptError("\nError: %s: obj_move_to_tile failed in override_map_start!", program->name); if (objectSetLocation(gDude, previousTile, elevation, NULL) != 0) { scriptError("\nError: %s: obj_move_to_tile RECOVERY Also failed!"); exit(1); } } tileSetCenter(tile, TILE_SET_CENTER_REFRESH_WINDOW); tileWindowRefresh(); } program->flags &= ~PROGRAM_FLAG_0x20; } // has_skill // 0x454568 static void opHasSkill(Program* program) { int skill = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int result = 0; if (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { result = skillGetValue(object, skill); } } else { scriptPredefinedError(program, "has_skill", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, result); } // using_skill // 0x454634 static void opUsingSkill(Program* program) { int skill = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); // NOTE: In the original source code this value is left uninitialized, that // explains why garbage is returned when using something else than dude and // SKILL_SNEAK as arguments. int result = 0; if (skill == SKILL_SNEAK && object == gDude) { result = dudeHasState(DUDE_STATE_SNEAKING); } programStackPushInteger(program, result); } // roll_vs_skill // 0x4546E8 static void opRollVsSkill(Program* program) { int modifier = programStackPopInteger(program); int skill = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int roll = ROLL_CRITICAL_FAILURE; if (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { roll = skillRoll(object, skill, modifier, &(script->howMuch)); } } } else { scriptPredefinedError(program, "roll_vs_skill", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, roll); } // skill_contest // 0x4547D4 static void opSkillContest(Program* program) { int data[3]; for (int arg = 0; arg < 3; arg++) { data[arg] = programStackPopInteger(program); } scriptPredefinedError(program, "skill_contest", SCRIPT_ERROR_NOT_IMPLEMENTED); programStackPushInteger(program, 0); } // do_check // 0x454890 static void opDoCheck(Program* program) { int mod = programStackPopInteger(program); int stat = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int roll = 0; if (object != NULL) { int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { switch (stat) { case STAT_STRENGTH: case STAT_PERCEPTION: case STAT_ENDURANCE: case STAT_CHARISMA: case STAT_INTELLIGENCE: case STAT_AGILITY: case STAT_LUCK: roll = statRoll(object, stat, mod, &(script->howMuch)); break; default: scriptError("\nScript Error: %s: op_do_check: Stat out of range", program->name); break; } } } else { scriptPredefinedError(program, "do_check", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, roll); } // success // 0x4549A8 static void opSuccess(Program* program) { int data = programStackPopInteger(program); int result = -1; switch (data) { case ROLL_CRITICAL_FAILURE: case ROLL_FAILURE: result = 0; break; case ROLL_SUCCESS: case ROLL_CRITICAL_SUCCESS: result = 1; break; } programStackPushInteger(program, result); } // critical // 0x454A44 static void opCritical(Program* program) { int data = programStackPopInteger(program); int result = -1; switch (data) { case ROLL_CRITICAL_FAILURE: case ROLL_CRITICAL_SUCCESS: result = 1; break; case ROLL_FAILURE: case ROLL_SUCCESS: result = 0; break; } programStackPushInteger(program, result); } // how_much // 0x454AD0 static void opHowMuch(Program* program) { int data = programStackPopInteger(program); int result = 0; int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { result = script->howMuch; } else { scriptPredefinedError(program, "how_much", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } programStackPushInteger(program, result); } // mark_area_known // 0x454B6C static void opMarkAreaKnown(Program* program) { int data[3]; for (int arg = 0; arg < 3; arg++) { data[arg] = programStackPopInteger(program); } // TODO: Provide meaningful names. if (data[2] == 0) { if (data[0] == CITY_STATE_INVISIBLE) { wmAreaSetVisibleState(data[1], 0, 1); } else { wmAreaSetVisibleState(data[1], 1, 1); wmAreaMarkVisitedState(data[1], data[0]); } } else if (data[2] == 1) { wmMapMarkVisited(data[1]); } } // reaction_influence // 0x454C34 static void opReactionInfluence(Program* program) { int data[3]; for (int arg = 0; arg < 3; arg++) { data[arg] = programStackPopInteger(program); } int result = _reaction_influence_(); programStackPushInteger(program, result); } // random // 0x454CD4 static void opRandom(Program* program) { int data[2]; for (int arg = 0; arg < 2; arg++) { data[arg] = programStackPopInteger(program); } int result; if (vcrGetState() == VCR_STATE_TURNED_OFF) { result = randomBetween(data[1], data[0]); } else { result = (data[0] - data[1]) / 2; } programStackPushInteger(program, result); } // roll_dice // 0x454D88 static void opRollDice(Program* program) { int data[2]; for (int arg = 0; arg < 2; arg++) { data[arg] = programStackPopInteger(program); } scriptPredefinedError(program, "roll_dice", SCRIPT_ERROR_NOT_IMPLEMENTED); programStackPushInteger(program, 0); } // move_to // 0x454E28 static void opMoveTo(Program* program) { int elevation = programStackPopInteger(program); int tile = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int newTile; if (object != NULL) { if (object == gDude) { bool tileLimitingEnabled = tileScrollLimitingIsEnabled(); bool tileBlockingEnabled = tileScrollBlockingIsEnabled(); if (tileLimitingEnabled) { tileScrollLimitingDisable(); } if (tileBlockingEnabled) { tileScrollBlockingDisable(); } Rect rect; newTile = objectSetLocation(object, tile, elevation, &rect); if (newTile != -1) { tileSetCenter(object->tile, TILE_SET_CENTER_REFRESH_WINDOW); } if (tileLimitingEnabled) { tileScrollLimitingEnable(); } if (tileBlockingEnabled) { tileScrollBlockingEnable(); } } else { Rect before; objectGetRect(object, &before); if (object->elevation != elevation && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { _combat_delete_critter(object); } Rect after; newTile = objectSetLocation(object, tile, elevation, &after); if (newTile != -1) { rectUnion(&before, &after, &before); tileWindowRefreshRect(&before, gElevation); } } } else { scriptPredefinedError(program, "move_to", SCRIPT_ERROR_OBJECT_IS_NULL); newTile = -1; } programStackPushInteger(program, newTile); } // create_object_sid // 0x454FA8 static void opCreateObject(Program* program) { int data[4]; for (int arg = 0; arg < 4; arg++) { data[arg] = programStackPopInteger(program); } int pid = data[3]; int tile = data[2]; int elevation = data[1]; int sid = data[0]; Object* object = NULL; if (_isLoadingGame() != 0) { debugPrint("\nError: attempt to Create critter in load/save-game: %s!", program->name); goto out; } if (pid == 0) { debugPrint("\nError: attempt to Create critter With PID of 0: %s!", program->name); goto out; } Proto* proto; if (protoGetProto(pid, &proto) != -1) { if (objectCreateWithFidPid(&object, proto->fid, pid) != -1) { if (tile == -1) { tile = 0; } Rect rect; if (objectSetLocation(object, tile, elevation, &rect) != -1) { tileWindowRefreshRect(&rect, object->elevation); } } } else { // SFALL: Prevent a crash when the proto is missing. goto out; } if (sid != -1) { int scriptType = 0; switch (PID_TYPE(object->pid)) { case OBJ_TYPE_CRITTER: scriptType = SCRIPT_TYPE_CRITTER; break; case OBJ_TYPE_ITEM: case OBJ_TYPE_SCENERY: scriptType = SCRIPT_TYPE_ITEM; break; } if (object->sid != -1) { scriptRemove(object->sid); object->sid = -1; } if (scriptAdd(&(object->sid), scriptType) == -1) { goto out; } Script* script; if (scriptGetScript(object->sid, &script) == -1) { goto out; } script->field_14 = sid - 1; if (scriptType == SCRIPT_TYPE_SPATIAL) { script->sp.built_tile = builtTileCreate(object->tile, object->elevation); script->sp.radius = 3; } object->id = scriptsNewObjectId(); script->field_1C = object->id; script->owner = object; _scr_find_str_run_info(sid - 1, &(script->field_50), object->sid); }; out: programStackPushPointer(program, object); } // destroy_object // 0x4551E4 static void opDestroyObject(Program* program) { program->flags |= PROGRAM_FLAG_0x20; Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "destroy_object", SCRIPT_ERROR_OBJECT_IS_NULL); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { if (_isLoadingGame()) { debugPrint("\nError: attempt to destroy critter in load/save-game: %s!", program->name); program->flags &= ~PROGRAM_FLAG_0x20; return; } } bool isSelf = object == scriptGetSelf(program); if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { _combat_delete_critter(object); } Object* owner = objectGetOwner(object); if (owner != NULL) { int quantity = itemGetQuantity(owner, object); itemRemove(owner, object, quantity); if (owner == gDude) { bool animated = !gameUiIsDisabled(); interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } _obj_connect(object, 1, 0, NULL); if (isSelf) { object->sid = -1; object->flags |= (OBJECT_HIDDEN | OBJECT_NO_SAVE); } else { reg_anim_clear(object); objectDestroy(object, NULL); } } else { reg_anim_clear(object); Rect rect; objectDestroy(object, &rect); tileWindowRefreshRect(&rect, gElevation); } program->flags &= ~PROGRAM_FLAG_0x20; if (isSelf) { program->flags |= PROGRAM_FLAG_0x0100; } } // display_msg // 0x455388 static void opDisplayMsg(Program* program) { char* string = programStackPopString(program); displayMonitorAddMessage(string); if (settings.debug.show_script_messages) { debugPrint("\n"); debugPrint(string); } } // script_overrides // 0x455430 static void opScriptOverrides(Program* program) { int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { script->scriptOverrides = 1; } else { scriptPredefinedError(program, "script_overrides", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } } // obj_is_carrying_obj_pid // 0x455470 static void opObjectIsCarryingObjectWithPid(Program* program) { int pid = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); int result = 0; if (obj != NULL) { result = objectGetCarriedQuantityByPid(obj, pid); } else { scriptPredefinedError(program, "obj_is_carrying_obj_pid", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, result); } // tile_contains_obj_pid // 0x455534 static void opTileContainsObjectWithPid(Program* program) { int pid = programStackPopInteger(program); int elevation = programStackPopInteger(program); int tile = programStackPopInteger(program); int result = 0; Object* object = objectFindFirstAtLocation(elevation, tile); while (object) { if (object->pid == pid) { result = 1; break; } object = objectFindNextAtLocation(); } programStackPushInteger(program, result); } // self_obj // 0x455600 static void opGetSelf(Program* program) { Object* object = scriptGetSelf(program); programStackPushPointer(program, object); } // source_obj // 0x455624 static void opGetSource(Program* program) { Object* object = NULL; int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { object = script->source; } else { scriptPredefinedError(program, "source_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } programStackPushPointer(program, object); } // target_obj // 0x455678 static void opGetTarget(Program* program) { Object* object = NULL; int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { object = script->target; } else { scriptPredefinedError(program, "target_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } programStackPushPointer(program, object); } // dude_obj // 0x4556CC static void opGetDude(Program* program) { programStackPushPointer(program, gDude); } // NOTE: The implementation is the same as in [opGetTarget]. // // obj_being_used_with // 0x4556EC static void opGetObjectBeingUsed(Program* program) { Object* object = NULL; int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { object = script->target; } else { scriptPredefinedError(program, "obj_being_used_with", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } programStackPushPointer(program, object); } // local_var // 0x455740 static void opGetLocalVar(Program* program) { int data = programStackPopInteger(program); ProgramValue value; value.opcode = VALUE_TYPE_INT; value.integerValue = -1; int sid = scriptGetSid(program); scriptGetLocalVar(sid, data, value); programStackPushValue(program, value); } // set_local_var // 0x4557C8 static void opSetLocalVar(Program* program) { ProgramValue value = programStackPopValue(program); int variable = programStackPopInteger(program); int sid = scriptGetSid(program); scriptSetLocalVar(sid, variable, value); } // map_var // 0x455858 static void opGetMapVar(Program* program) { int data = programStackPopInteger(program); ProgramValue value; if (mapGetGlobalVar(data, value) == -1) { value.opcode = VALUE_TYPE_INT; value.integerValue = -1; } programStackPushValue(program, value); } // set_map_var // 0x4558C8 static void opSetMapVar(Program* program) { ProgramValue value = programStackPopValue(program); int variable = programStackPopInteger(program); mapSetGlobalVar(variable, value); } // global_var // 0x455950 static void opGetGlobalVar(Program* program) { int data = programStackPopInteger(program); int value = -1; if (gGameGlobalVarsLength != 0) { value = gameGetGlobalVar(data); } else { scriptError("\nScript Error: %s: op_global_var: no global vars found!", program->name); } programStackPushInteger(program, value); } // set_global_var // 0x4559EC // 0x80C6 static void opSetGlobalVar(Program* program) { int value = programStackPopInteger(program); int variable = programStackPopInteger(program); if (gGameGlobalVarsLength != 0) { gameSetGlobalVar(variable, value); } else { scriptError("\nScript Error: %s: op_set_global_var: no global vars found!", program->name); } } // script_action // 0x455A90 static void opGetScriptAction(Program* program) { int action = 0; int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { action = script->action; } else { scriptPredefinedError(program, "script_action", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } programStackPushInteger(program, action); } // obj_type // 0x455AE4 static void opGetObjectType(Program* program) { Object* object = static_cast(programStackPopPointer(program)); int objectType = -1; if (object != NULL) { objectType = FID_TYPE(object->fid); } programStackPushInteger(program, objectType); } // obj_item_subtype // 0x455B6C static void opGetItemType(Program* program) { Object* obj = static_cast(programStackPopPointer(program)); int itemType = -1; if (obj != NULL) { if (PID_TYPE(obj->pid) == OBJ_TYPE_ITEM) { Proto* proto; if (protoGetProto(obj->pid, &proto) != -1) { itemType = itemGetType(obj); } } } programStackPushInteger(program, itemType); } // get_critter_stat // 0x455C10 static void opGetCritterStat(Program* program) { int stat = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int value = -1; if (object != NULL) { value = critterGetStat(object, stat); } else { scriptPredefinedError(program, "get_critter_stat", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, value); } // NOTE: Despite it's name it does not actually "set" stat, but "adjust". So // it's last argument is amount of adjustment, not it's final value. // // set_critter_stat // 0x455CCC static void opSetCritterStat(Program* program) { int value = programStackPopInteger(program); int stat = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int result = 0; if (object != NULL) { if (object == gDude) { int currentValue = critterGetBaseStatWithTraitModifier(object, stat); critterSetBaseStat(object, stat, currentValue + value); } else { scriptPredefinedError(program, "set_critter_stat", SCRIPT_ERROR_FOLLOWS); debugPrint(" Can't modify anyone except obj_dude!"); result = -1; } } else { scriptPredefinedError(program, "set_critter_stat", SCRIPT_ERROR_OBJECT_IS_NULL); result = -1; } programStackPushInteger(program, result); } // animate_stand_obj // 0x455DC8 static void opAnimateStand(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == -1) { scriptPredefinedError(program, "animate_stand_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } object = scriptGetSelf(program); } if (!isInCombat()) { reg_anim_begin(ANIMATION_REQUEST_UNRESERVED); animationRegisterAnimate(object, ANIM_STAND, 0); reg_anim_end(); } } // animate_stand_reverse_obj // 0x455E7C static void opAnimateStandReverse(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == -1) { scriptPredefinedError(program, "animate_stand_reverse_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } object = scriptGetSelf(program); } if (!isInCombat()) { reg_anim_begin(ANIMATION_REQUEST_UNRESERVED); animationRegisterAnimateReversed(object, ANIM_STAND, 0); reg_anim_end(); } } // animate_move_obj_to_tile // 0x455F30 static void opAnimateMoveObjectToTile(Program* program) { int flags = programStackPopInteger(program); int tile = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "animate_move_obj_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (tile <= -1) { return; } int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == -1) { scriptPredefinedError(program, "animate_move_obj_to_tile", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } if (!critterIsActive(object)) { return; } if (isInCombat()) { return; } if ((flags & 0x10) != 0) { reg_anim_clear(object); flags &= ~0x10; } reg_anim_begin(ANIMATION_REQUEST_UNRESERVED); if (flags == 0) { animationRegisterMoveToTile(object, tile, object->elevation, -1, 0); } else { animationRegisterRunToTile(object, tile, object->elevation, -1, 0); } reg_anim_end(); } // tile_in_tile_rect // 0x45607C static void opTileInTileRect(Program* program) { Point points[5]; for (int arg = 0; arg < 5; arg++) { int value = programStackPopInteger(program); points[arg].x = value % 200; points[arg].y = value / 200; } int x = points[0].x; int y = points[0].y; int minX = points[1].x; int maxX = points[4].x; int minY = points[4].y; int maxY = points[1].y; int result = 0; if (x >= minX && x <= maxX && y >= minY && y <= maxY) { result = 1; } programStackPushInteger(program, result); } // make_daytime // 0x456170 static void opMakeDayTime(Program* program) { } // tile_distance // 0x456174 static void opTileDistanceBetween(Program* program) { int tile2 = programStackPopInteger(program); int tile1 = programStackPopInteger(program); int distance; if (tile1 != -1 && tile2 != -1) { distance = tileDistanceBetween(tile1, tile2); } else { distance = 9999; } programStackPushInteger(program, distance); } // tile_distance_objs // 0x456228 static void opTileDistanceBetweenObjects(Program* program) { Object* object2 = static_cast(programStackPopPointer(program)); Object* object1 = static_cast(programStackPopPointer(program)); int distance = 9999; if (object1 != NULL && object2 != NULL) { if ((uintptr_t)object2 >= HEX_GRID_SIZE && (uintptr_t)object1 >= HEX_GRID_SIZE) { if (object1->elevation == object2->elevation) { if (object1->tile != -1 && object2->tile != -1) { distance = tileDistanceBetween(object1->tile, object2->tile); } } } else { scriptPredefinedError(program, "tile_distance_objs", SCRIPT_ERROR_FOLLOWS); debugPrint(" Passed a tile # instead of an object!!!BADBADBAD!"); } } programStackPushInteger(program, distance); } // tile_num // 0x456324 static void opGetObjectTile(Program* program) { Object* obj = static_cast(programStackPopPointer(program)); int tile = -1; if (obj != NULL) { tile = obj->tile; } else { scriptPredefinedError(program, "tile_num", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, tile); } // tile_num_in_direction // 0x4563B4 static void opGetTileInDirection(Program* program) { int distance = programStackPopInteger(program); int rotation = programStackPopInteger(program); int origin = programStackPopInteger(program); int tile = -1; if (origin != -1) { if (rotation < ROTATION_COUNT) { if (distance != 0) { tile = tileGetTileInDirection(origin, rotation, distance); if (tile < -1) { debugPrint("\nError: %s: op_tile_num_in_direction got #: %d", program->name, tile); tile = -1; } } } else { scriptPredefinedError(program, "tile_num_in_direction", SCRIPT_ERROR_FOLLOWS); debugPrint(" rotation out of Range!"); } } else { scriptPredefinedError(program, "tile_num_in_direction", SCRIPT_ERROR_FOLLOWS); debugPrint(" tileNum is -1!"); } programStackPushInteger(program, tile); } // pickup_obj // 0x4564D4 static void opPickup(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { return; } int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == 1) { scriptPredefinedError(program, "pickup_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } if (script->target == NULL) { scriptPredefinedError(program, "pickup_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } actionPickUp(script->target, object); } // drop_obj // 0x456580 static void opDrop(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { return; } int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == -1) { // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. scriptPredefinedError(program, "drop_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (script->target == NULL) { // FIXME: Should be SCRIPT_ERROR_OBJECT_IS_NULL. scriptPredefinedError(program, "drop_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } _obj_drop(script->target, object); } // add_obj_to_inven // 0x45662C static void opAddObjectToInventory(Program* program) { Object* item = static_cast(programStackPopPointer(program)); Object* owner = static_cast(programStackPopPointer(program)); if (owner == NULL || item == NULL) { return; } if (item->owner == NULL) { if (itemAdd(owner, item, 1) == 0) { Rect rect; _obj_disconnect(item, &rect); tileWindowRefreshRect(&rect, item->elevation); } } else { scriptPredefinedError(program, "add_obj_to_inven", SCRIPT_ERROR_FOLLOWS); debugPrint(" Item was already attached to something else!"); } } // rm_obj_from_inven // 0x456708 static void opRemoveObjectFromInventory(Program* program) { Object* item = static_cast(programStackPopPointer(program)); Object* owner = static_cast(programStackPopPointer(program)); if (owner == NULL || item == NULL) { return; } bool updateFlags = false; int flags = 0; if ((item->flags & OBJECT_EQUIPPED) != 0) { if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) { flags |= OBJECT_IN_LEFT_HAND; } if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { flags |= OBJECT_IN_RIGHT_HAND; } if ((item->flags & OBJECT_WORN) != 0) { flags |= OBJECT_WORN; } updateFlags = true; } if (itemRemove(owner, item, 1) == 0) { Rect rect; _obj_connect(item, 1, 0, &rect); tileWindowRefreshRect(&rect, item->elevation); if (updateFlags) { _correctFidForRemovedItem(owner, item, flags); } } } // wield_obj_critter // 0x45681C static void opWieldItem(Program* program) { Object* item = static_cast(programStackPopPointer(program)); Object* critter = static_cast(programStackPopPointer(program)); if (critter == NULL) { scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (item == NULL) { scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_FOLLOWS); debugPrint(" Only works for critters! ERROR ERROR ERROR!"); return; } int hand = HAND_RIGHT; bool shouldAdjustArmorClass = false; Object* oldArmor = NULL; Object* newArmor = NULL; if (critter == gDude) { if (interfaceGetCurrentHand() == HAND_LEFT) { hand = HAND_LEFT; } if (itemGetType(item) == ITEM_TYPE_ARMOR) { oldArmor = critterGetArmor(gDude); // SFALL shouldAdjustArmorClass = true; newArmor = item; } } if (_inven_wield(critter, item, hand) == -1) { scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_FOLLOWS); debugPrint(" inven_wield failed! ERROR ERROR ERROR!"); return; } if (critter == gDude) { if (shouldAdjustArmorClass) { _adjust_ac(critter, oldArmor, newArmor); // SFALL interfaceRenderArmorClass(false); } bool animated = !gameUiIsDisabled(); interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } // use_obj // 0x4569D0 static void opUseObject(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == -1) { // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. scriptPredefinedError(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (script->target == NULL) { scriptPredefinedError(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Object* self = scriptGetSelf(program); if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { _action_use_an_object(script->target, object); } else { _obj_use(self, object); } } // obj_can_see_obj // 0x456AC4 static void opObjectCanSeeObject(Program* program) { Object* object2 = static_cast(programStackPopPointer(program)); Object* object1 = static_cast(programStackPopPointer(program)); bool canSee = false; if (object2 != nullptr && object1 != nullptr) { // SFALL: Check objects are on the same elevation. // CE: These checks are on par with |opObjectCanHearObject|. if (object2->elevation == object1->elevation) { if (object2->tile != -1 && object1->tile != -1) { if (isWithinPerception(object1, object2)) { Object* obstacle; _make_straight_path(object1, object1->tile, object2->tile, nullptr, &obstacle, 16); if (obstacle == object2) { canSee = true; } } } } } else { scriptPredefinedError(program, "obj_can_see_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, canSee); } // attack_complex // 0x456C00 static void opAttackComplex(Program* program) { int data[8]; for (int arg = 0; arg < 7; arg++) { data[arg] = programStackPopInteger(program); } Object* target = static_cast(programStackPopPointer(program)); if (target == NULL) { scriptPredefinedError(program, "attack", SCRIPT_ERROR_OBJECT_IS_NULL); return; } program->flags |= PROGRAM_FLAG_0x20; Object* self = scriptGetSelf(program); if (self == NULL) { program->flags &= ~PROGRAM_FLAG_0x20; return; } if (!critterIsActive(self) || (self->flags & OBJECT_HIDDEN) != 0) { debugPrint("\n But is already Inactive (Dead/Stunned/Invisible)"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (!critterIsActive(target) || (target->flags & OBJECT_HIDDEN) != 0) { debugPrint("\n But target is already dead or invisible"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if ((target->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) { debugPrint("\n But target is AFRAID"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (_gdialogActive()) { // TODO: Might be an error, program flag is not removed. return; } if (isInCombat()) { CritterCombatData* combatData = &(self->data.critter.combat); if ((combatData->maneuver & CRITTER_MANEUVER_ENGAGING) == 0) { combatData->maneuver |= CRITTER_MANEUVER_ENGAGING; combatData->whoHitMe = target; } } else { STRUCT_664980 attack; attack.attacker = self; attack.defender = target; attack.actionPointsBonus = 0; attack.accuracyBonus = data[4]; attack.damageBonus = 0; attack.minDamage = data[3]; attack.maxDamage = data[2]; // TODO: Something is probably broken here, why it wants // flags to be the same? Maybe because both of them // are applied to defender because of the bug in 0x422F3C? if (data[1] == data[0]) { attack.field_1C = 1; attack.field_24 = data[0]; attack.field_20 = data[1]; } else { attack.field_1C = 0; } scriptsRequestCombat(&attack); } program->flags &= ~PROGRAM_FLAG_0x20; } // start_gdialog // 0x456DF0 static void opStartGameDialog(Program* program) { int backgroundId = programStackPopInteger(program); int headId = programStackPopInteger(program); int reactionLevel = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); programStackPopInteger(program); if (isInCombat()) { return; } if (obj == NULL) { scriptPredefinedError(program, "start_gdialog", SCRIPT_ERROR_OBJECT_IS_NULL); return; } gGameDialogHeadFid = -1; if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { Proto* proto; if (protoGetProto(obj->pid, &proto) == -1) { return; } } if (headId != -1) { gGameDialogHeadFid = buildFid(OBJ_TYPE_HEAD, headId, 0, 0, 0); } gameDialogSetBackground(backgroundId); gGameDialogReactionOrFidget = reactionLevel; if (gGameDialogHeadFid != -1) { int npcReactionValue = reactionGetValue(gGameDialogSpeaker); int npcReactionType = reactionTranslateValue(npcReactionValue); switch (npcReactionType) { case NPC_REACTION_BAD: gGameDialogReactionOrFidget = FIDGET_BAD; break; case NPC_REACTION_NEUTRAL: gGameDialogReactionOrFidget = FIDGET_NEUTRAL; break; case NPC_REACTION_GOOD: gGameDialogReactionOrFidget = FIDGET_GOOD; break; } } gGameDialogSid = scriptGetSid(program); gGameDialogSpeaker = scriptGetSelf(program); _gdialogInitFromScript(gGameDialogHeadFid, gGameDialogReactionOrFidget); } // end_dialogue // 0x456F80 static void opEndGameDialog(Program* program) { if (_gdialogExitFromScript() != -1) { gGameDialogSpeaker = NULL; gGameDialogSid = -1; } } // dialogue_reaction // 0x456FA4 static void opGameDialogReaction(Program* program) { int value = programStackPopInteger(program); gGameDialogReactionOrFidget = value; _talk_to_critter_reacts(value); } // metarule3 // 0x457110 static void opMetarule3(Program* program) { ProgramValue param3 = programStackPopValue(program); ProgramValue param2 = programStackPopValue(program); ProgramValue param1 = programStackPopValue(program); int rule = programStackPopInteger(program); ProgramValue result; result.opcode = VALUE_TYPE_INT; result.integerValue = 0; switch (rule) { case METARULE3_CLR_FIXED_TIMED_EVENTS: if (1) { _scrSetQueueTestVals(static_cast(param1.pointerValue), param2.integerValue); _queue_clear_type(EVENT_TYPE_SCRIPT, _scrQueueRemoveFixed); } break; case METARULE3_MARK_SUBTILE: result.integerValue = wmSubTileMarkRadiusVisited(param1.integerValue, param2.integerValue, param3.integerValue); break; case METARULE3_GET_KILL_COUNT: result.integerValue = killsGetByType(param1.integerValue); break; case METARULE3_MARK_MAP_ENTRANCE: result.integerValue = wmMapMarkMapEntranceState(param1.integerValue, param2.integerValue, param3.integerValue); break; case METARULE3_WM_SUBTILE_STATE: if (1) { int state; if (wmSubTileGetVisitedState(param1.integerValue, param2.integerValue, &state) == 0) { result.integerValue = state; } } break; case METARULE3_TILE_GET_NEXT_CRITTER: if (1) { int tile = param1.integerValue; int elevation = param2.integerValue; Object* previousCritter = static_cast(param3.pointerValue); bool critterFound = previousCritter == NULL; Object* object = objectFindFirstAtLocation(elevation, tile); while (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { if (critterFound) { result.opcode = VALUE_TYPE_PTR; result.pointerValue = object; break; } } if (object == previousCritter) { critterFound = true; } object = objectFindNextAtLocation(); } } break; case METARULE3_ART_SET_BASE_FID_NUM: if (1) { Object* obj = static_cast(param1.pointerValue); int frmId = param2.integerValue; int fid = buildFid(FID_TYPE(obj->fid), frmId, FID_ANIM_TYPE(obj->fid), (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 28); Rect updatedRect; objectSetFid(obj, fid, &updatedRect); tileWindowRefreshRect(&updatedRect, gElevation); } break; case METARULE3_TILE_SET_CENTER: result.integerValue = tileSetCenter(param1.integerValue, TILE_SET_CENTER_REFRESH_WINDOW); break; case METARULE3_109: result.integerValue = aiGetChemUse(static_cast(param1.pointerValue)); break; case METARULE3_110: result.integerValue = wmCarIsOutOfGas() ? 1 : 0; break; case METARULE3_111: result.integerValue = _map_target_load_area(); break; } programStackPushValue(program, result); } // set_map_music // 0x45734C static void opSetMapMusic(Program* program) { char* string = programStackPopString(program); int mapIndex = programStackPopInteger(program); debugPrint("\nset_map_music: %d, %s", mapIndex, string); wmSetMapMusic(mapIndex, string); } // NOTE: Function name is a bit misleading. Last parameter is a boolean value // where 1 or true makes object invisible, and value 0 (false) makes it visible // again. So a better name for this function is opSetObjectInvisible. // // // set_obj_visibility // 0x45741C static void opSetObjectVisibility(Program* program) { int invisible = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); if (obj == NULL) { scriptPredefinedError(program, "set_obj_visibility", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (_isLoadingGame()) { debugPrint("Error: attempt to set_obj_visibility in load/save-game: %s!", program->name); return; } if (invisible != 0) { if ((obj->flags & OBJECT_HIDDEN) == 0) { if (isInCombat()) { objectDisableOutline(obj, NULL); objectClearOutline(obj, NULL); } Rect rect; if (objectHide(obj, &rect) != -1) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { obj->flags |= OBJECT_NO_BLOCK; } tileWindowRefreshRect(&rect, obj->elevation); } } } else { if ((obj->flags & OBJECT_HIDDEN) != 0) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { obj->flags &= ~OBJECT_NO_BLOCK; } Rect rect; if (objectShow(obj, &rect) != -1) { tileWindowRefreshRect(&rect, obj->elevation); } } } } // load_map // 0x45755C static void opLoadMap(Program* program) { int param = programStackPopInteger(program); ProgramValue mapIndexOrName = programStackPopValue(program); char* mapName = NULL; if ((mapIndexOrName.opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { if ((mapIndexOrName.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { mapName = programGetString(program, mapIndexOrName.opcode, mapIndexOrName.integerValue); } else { programFatalError("script error: %s: invalid arg 1 to load_map", program->name); } } int mapIndex = -1; if (mapName != NULL) { gGameGlobalVars[GVAR_LOAD_MAP_INDEX] = param; mapIndex = wmMapMatchNameToIdx(mapName); } else { if (mapIndexOrName.integerValue >= 0) { gGameGlobalVars[GVAR_LOAD_MAP_INDEX] = param; mapIndex = mapIndexOrName.integerValue; } } if (mapIndex != -1) { MapTransition transition; transition.map = mapIndex; transition.elevation = -1; transition.tile = -1; transition.rotation = -1; mapSetTransition(&transition); } } // wm_area_set_pos // 0x457680 static void opWorldmapCitySetPos(Program* program) { int y = programStackPopInteger(program); int x = programStackPopInteger(program); int city = programStackPopInteger(program); if (wmAreaSetWorldPos(city, x, y) == -1) { scriptPredefinedError(program, "wm_area_set_pos", SCRIPT_ERROR_FOLLOWS); debugPrint("Invalid Parameter!"); } } // set_exit_grids // 0x457730 static void opSetExitGrids(Program* program) { int destinationRotation = programStackPopInteger(program); int destinationTile = programStackPopInteger(program); int destinationElevation = programStackPopInteger(program); int destinationMap = programStackPopInteger(program); int elevation = programStackPopInteger(program); Object* object = objectFindFirstAtElevation(elevation); while (object != NULL) { if (object->pid >= FIRST_EXIT_GRID_PID && object->pid <= LAST_EXIT_GRID_PID) { object->data.misc.map = destinationMap; object->data.misc.tile = destinationTile; object->data.misc.elevation = destinationElevation; } object = objectFindNextAtElevation(); } } // anim_busy // 0x4577EC static void opAnimBusy(Program* program) { Object* object = static_cast(programStackPopPointer(program)); int rc = 0; if (object != NULL) { rc = animationIsBusy(object); } else { scriptPredefinedError(program, "anim_busy", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, rc); } // critter_heal // 0x457880 static void opCritterHeal(Program* program) { int amount = programStackPopInteger(program); Object* critter = static_cast(programStackPopPointer(program)); int rc = critterAdjustHitPoints(critter, amount); if (critter == gDude) { interfaceRenderHitPoints(true); } programStackPushInteger(program, rc); } // set_light_level // 0x457934 static void opSetLightLevel(Program* program) { // Maps light level to light intensity. // // Middle value is mapped one-to-one which corresponds to 50% light level // (cavern lighting). Light levels above (51-100%) and below (0-49%) is // calculated as percentage from two adjacent light values. // // 0x453F90 static const int intensities[3] = { LIGHT_INTENSITY_MIN, (LIGHT_INTENSITY_MIN + LIGHT_INTENSITY_MAX) / 2, LIGHT_INTENSITY_MAX, }; int data = programStackPopInteger(program); int lightLevel = data; if (data == 50) { lightSetAmbientIntensity(intensities[1], true); return; } int lightIntensity; if (data > 50) { lightIntensity = intensities[1] + data * (intensities[2] - intensities[1]) / 100; } else { lightIntensity = intensities[0] + data * (intensities[1] - intensities[0]) / 100; } lightSetAmbientIntensity(lightIntensity, true); } // game_time // 0x4579F4 static void opGetGameTime(Program* program) { int time = gameTimeGetTime(); programStackPushInteger(program, time); } // game_time_in_seconds // 0x457A18 static void opGetGameTimeInSeconds(Program* program) { int time = gameTimeGetTime(); programStackPushInteger(program, time / 10); } // elevation // 0x457A44 static void opGetObjectElevation(Program* program) { Object* object = static_cast(programStackPopPointer(program)); int elevation = 0; if (object != NULL) { elevation = object->elevation; } else { scriptPredefinedError(program, "elevation", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, elevation); } // kill_critter // 0x457AD4 static void opKillCritter(Program* program) { int deathFrame = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "kill_critter", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (_isLoadingGame()) { debugPrint("\nError: attempt to destroy critter in load/save-game: %s!", program->name); } program->flags |= PROGRAM_FLAG_0x20; Object* self = scriptGetSelf(program); bool isSelf = self == object; reg_anim_clear(object); _combat_delete_critter(object); critterKill(object, deathFrame, 1); program->flags &= ~PROGRAM_FLAG_0x20; if (isSelf) { program->flags |= PROGRAM_FLAG_0x0100; } } // [forceBack] is to force fall back animation, otherwise it's fall front if it's present static int _correctDeath(Object* critter, int anim, bool forceBack) { if (anim >= ANIM_BIG_HOLE_SF && anim <= ANIM_FALL_FRONT_BLOOD_SF) { bool useStandardDeath = false; if (settings.preferences.violence_level < VIOLENCE_LEVEL_MAXIMUM_BLOOD) { useStandardDeath = true; } else { int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1); if (!artExists(fid)) { useStandardDeath = true; } } if (useStandardDeath) { if (forceBack) { anim = ANIM_FALL_BACK; } else { int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_FRONT, (critter->fid & 0xF000) >> 12, critter->rotation + 1); if (artExists(fid)) { anim = ANIM_FALL_FRONT; } else { anim = ANIM_FALL_BACK; } } } } return anim; } // kill_critter_type // 0x457CB4 static void opKillCritterType(Program* program) { // 0x518ED0 static const int ftList[] = { ANIM_FALL_BACK_BLOOD_SF, ANIM_BIG_HOLE_SF, ANIM_CHARRED_BODY_SF, ANIM_CHUNKS_OF_FLESH_SF, ANIM_FALL_FRONT_BLOOD_SF, ANIM_FALL_BACK_BLOOD_SF, ANIM_DANCING_AUTOFIRE_SF, ANIM_SLICED_IN_HALF_SF, ANIM_EXPLODED_TO_NOTHING_SF, ANIM_FALL_BACK_BLOOD_SF, ANIM_FALL_FRONT_BLOOD_SF, }; int deathFrame = programStackPopInteger(program); int pid = programStackPopInteger(program); if (_isLoadingGame()) { debugPrint("\nError: attempt to destroy critter in load/save-game: %s!", program->name); return; } program->flags |= PROGRAM_FLAG_0x20; Object* previousObj = NULL; int count = 0; int ftIndex = 0; Object* obj = objectFindFirst(); while (obj != NULL) { if (FID_ANIM_TYPE(obj->fid) < ANIM_FALL_BACK_SF) { if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->pid == pid && !critterIsDead(obj)) { if (obj == previousObj || count > 200) { scriptPredefinedError(program, "kill_critter_type", SCRIPT_ERROR_FOLLOWS); debugPrint(" Infinite loop destroying critters!"); program->flags &= ~PROGRAM_FLAG_0x20; return; } reg_anim_clear(obj); if (deathFrame != 0) { _combat_delete_critter(obj); if (deathFrame == 1) { // Pick next animation from the |ftList|. int anim = _correctDeath(obj, ftList[ftIndex], true); // SFALL: Fix for incorrect death animation. // CE: The fix is slightly different. Sfall passes // |false| to |correctDeath| to disambiguate usage // between this function and |opAnim|. On |false| it // simply returns |ANIM_FALL_BACK_SF|. Instead of doing // the same, check for standard death animations // returned from |correctDeath| and convert them to // appropariate single frame cousins. switch (anim) { case ANIM_FALL_BACK: anim = ANIM_FALL_BACK_SF; break; case ANIM_FALL_FRONT: anim = ANIM_FALL_FRONT_SF; break; } critterKill(obj, anim, true); ftIndex += 1; if (ftIndex >= (sizeof(ftList) / sizeof(ftList[0]))) { ftIndex = 0; } } else if (deathFrame >= FIRST_SF_DEATH_ANIM && deathFrame <= LAST_SF_DEATH_ANIM) { // CE: In some cases user-space scripts randomize death // frame between front/back animations but technically // speaking a scripter can provide any single frame // death animation which original code simply ignores. critterKill(obj, deathFrame, true); } else { critterKill(obj, ANIM_FALL_BACK_SF, true); } } else { reg_anim_clear(obj); Rect rect; objectDestroy(obj, &rect); tileWindowRefreshRect(&rect, gElevation); } previousObj = obj; count += 1; objectFindFirst(); gMapHeader.lastVisitTime = gameTimeGetTime(); } } obj = objectFindNext(); } program->flags &= ~PROGRAM_FLAG_0x20; } // critter_dmg // 0x457EB4 static void opCritterDamage(Program* program) { program->flags |= PROGRAM_FLAG_0x20; int damageTypeWithFlags = programStackPopInteger(program); int amount = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "critter_damage", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { scriptPredefinedError(program, "critter_damage", SCRIPT_ERROR_FOLLOWS); debugPrint(" Can't call on non-critters!"); return; } Object* self = scriptGetSelf(program); if (object->data.critter.combat.whoHitMeCid == -1) { object->data.critter.combat.whoHitMe = NULL; } bool animate = (damageTypeWithFlags & 0x200) == 0; bool bypassArmor = (damageTypeWithFlags & 0x100) != 0; int damageType = damageTypeWithFlags & ~(0x100 | 0x200); actionDamage(object->tile, object->elevation, amount, amount, damageType, animate, bypassArmor); program->flags &= ~PROGRAM_FLAG_0x20; if (self == object) { program->flags |= PROGRAM_FLAG_0x0100; } } // add_timer_event // 0x457FF0 static void opAddTimerEvent(Program* program) { int param = programStackPopInteger(program); int delay = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptError("\nScript Error: %s: op_add_timer_event: pobj is NULL!", program->name); return; } scriptAddTimerEvent(object->sid, delay, param); } // rm_timer_event // 0x458094 static void opRemoveTimerEvent(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { // FIXME: Should be op_rm_timer_event. scriptError("\nScript Error: %s: op_add_timer_event: pobj is NULL!"); return; } queueRemoveEvents(object); } // Converts seconds into game ticks. // // game_ticks // 0x458108 static void opGameTicks(Program* program) { int ticks = programStackPopInteger(program); if (ticks < 0) { ticks = 0; } programStackPushInteger(program, ticks * 10); } // NOTE: The name of this function is misleading. It has (almost) nothing to do // with player's "Traits" as a feature. Instead it's used to query many // information of the critters using passed parameters. It's like "metarule" but // for critters. // // 0x458180 // has_trait static void opHasTrait(Program* program) { int param = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int type = programStackPopInteger(program); int result = 0; if (object != NULL) { switch (type) { case CRITTER_TRAIT_PERK: if (param < PERK_COUNT) { result = perkGetRank(object, param); } else { scriptError("\nScript Error: %s: op_has_trait: Perk out of range", program->name); } break; case CRITTER_TRAIT_OBJECT: switch (param) { case CRITTER_TRAIT_OBJECT_AI_PACKET: if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { result = object->data.critter.combat.aiPacket; } break; case CRITTER_TRAIT_OBJECT_TEAM: if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { result = object->data.critter.combat.team; } break; case CRITTER_TRAIT_OBJECT_ROTATION: result = object->rotation; break; case CRITTER_TRAIT_OBJECT_IS_INVISIBLE: result = (object->flags & OBJECT_HIDDEN) == 0; break; case CRITTER_TRAIT_OBJECT_GET_INVENTORY_WEIGHT: result = objectGetInventoryWeight(object); break; } break; case CRITTER_TRAIT_TRAIT: if (param < TRAIT_COUNT) { result = traitIsSelected(param); } else { scriptError("\nScript Error: %s: op_has_trait: Trait out of range", program->name); } break; default: scriptError("\nScript Error: %s: op_has_trait: Trait out of range", program->name); break; } } else { scriptPredefinedError(program, "has_trait", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, result); } // obj_can_hear_obj // 0x45835C static void opObjectCanHearObject(Program* program) { Object* object2 = static_cast(programStackPopPointer(program)); Object* object1 = static_cast(programStackPopPointer(program)); bool canHear = false; // SFALL: Fix broken implementation. // CE: In Sfall this fix is available under "ObjCanHearObjFix" switch and // it's not enabled by default. Probably needs testing. if (object2 != nullptr && object1 != nullptr) { if (object2->elevation == object1->elevation) { if (object2->tile != -1 && object1->tile != -1) { if (isWithinPerception(object1, object2)) { canHear = true; } } } } programStackPushInteger(program, canHear); } // game_time_hour // 0x458438 static void opGameTimeHour(Program* program) { int value = gameTimeGetHour(); programStackPushInteger(program, value); } // fixed_param // 0x45845C static void opGetFixedParam(Program* program) { int fixedParam = 0; int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { fixedParam = script->fixedParam; } else { scriptPredefinedError(program, "fixed_param", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } programStackPushInteger(program, fixedParam); } // tile_is_visible // 0x4584B0 static void opTileIsVisible(Program* program) { int data = programStackPopInteger(program); int isVisible = 0; if (tileIsVisible(data)) { isVisible = 1; } programStackPushInteger(program, isVisible); } // dialogue_system_enter // 0x458534 static void opGameDialogSystemEnter(Program* program) { int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == -1) { return; } Object* self = scriptGetSelf(program); if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { if (!critterIsActive(self)) { return; } } if (isInCombat()) { return; } if (gameRequestState(GAME_STATE_4) == -1) { return; } gGameDialogSpeaker = scriptGetSelf(program); } // action_being_used // 0x458594 static void opGetActionBeingUsed(Program* program) { int action = -1; int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) != -1) { action = script->actionBeingUsed; } else { scriptPredefinedError(program, "action_being_used", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } programStackPushInteger(program, action); } // critter_state // 0x4585E8 static void opGetCritterState(Program* program) { Object* critter = static_cast(programStackPopPointer(program)); int state = CRITTER_STATE_DEAD; if (critter != NULL && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { if (critterIsActive(critter)) { state = CRITTER_STATE_NORMAL; int anim = FID_ANIM_TYPE(critter->fid); if (anim >= ANIM_FALL_BACK_SF && anim <= ANIM_FALL_FRONT_SF) { state = CRITTER_STATE_PRONE; } state |= (critter->data.critter.combat.results & DAM_CRIP); } else { if (!critterIsDead(critter)) { state = CRITTER_STATE_PRONE; } } } else { scriptPredefinedError(program, "critter_state", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, state); } // game_time_advance // 0x4586C8 static void opGameTimeAdvance(Program* program) { int data = programStackPopInteger(program); int days = data / GAME_TIME_TICKS_PER_DAY; int remainder = data % GAME_TIME_TICKS_PER_DAY; for (int day = 0; day < days; day++) { gameTimeAddTicks(GAME_TIME_TICKS_PER_DAY); queueProcessEvents(); } gameTimeAddTicks(remainder); queueProcessEvents(); } // radiation_inc // 0x458760 static void opRadiationIncrease(Program* program) { int amount = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "radiation_inc", SCRIPT_ERROR_OBJECT_IS_NULL); return; } critterAdjustRadiation(object, amount); } // radiation_dec // 0x458800 static void opRadiationDecrease(Program* program) { int amount = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "radiation_dec", SCRIPT_ERROR_OBJECT_IS_NULL); return; } int radiation = critterGetRadiation(object); int adjustment = radiation >= 0 ? -amount : 0; critterAdjustRadiation(object, adjustment); } // critter_attempt_placement // 0x4588B4 static void opCritterAttemptPlacement(Program* program) { int elevation = programStackPopInteger(program); int tile = programStackPopInteger(program); Object* critter = static_cast(programStackPopPointer(program)); if (critter == NULL) { scriptPredefinedError(program, "critter_attempt_placement", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (elevation != critter->elevation && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { _combat_delete_critter(critter); } objectSetLocation(critter, 0, elevation, NULL); int rc = _obj_attempt_placement(critter, tile, elevation, 1); programStackPushInteger(program, rc); } // obj_pid // 0x4589A0 static void opGetObjectPid(Program* program) { Object* obj = static_cast(programStackPopPointer(program)); int pid = -1; if (obj) { pid = obj->pid; } else { scriptPredefinedError(program, "obj_pid", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, pid); } // cur_map_index // 0x458A30 static void opGetCurrentMap(Program* program) { int mapIndex = mapGetCurrentMap(); programStackPushInteger(program, mapIndex); } // critter_add_trait // 0x458A54 static void opCritterAddTrait(Program* program) { int value = programStackPopInteger(program); int param = programStackPopInteger(program); int kind = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { switch (kind) { case CRITTER_TRAIT_PERK: if (1) { char* critterName = critterGetName(object); char* perkName = perkGetName(param); debugPrint("\nintextra::critter_add_trait: Adding Perk %s to %s", perkName, critterName); if (value > 0) { if (perkAddForce(object, param) != 0) { scriptError("\nScript Error: %s: op_critter_add_trait: perk_add_force failed", program->name); debugPrint("Perk: %d", param); } } else { if (perkRemove(object, param) != 0) { // FIXME: typo in debug message, should be perk_sub scriptError("\nScript Error: %s: op_critter_add_trait: per_sub failed", program->name); debugPrint("Perk: %d", param); } } if (object == gDude) { interfaceRenderHitPoints(true); } } break; case CRITTER_TRAIT_OBJECT: switch (param) { case CRITTER_TRAIT_OBJECT_AI_PACKET: critterSetAiPacket(object, value); break; case CRITTER_TRAIT_OBJECT_TEAM: if (objectIsPartyMember(object)) { break; } if (object->data.critter.combat.team == value) { break; } if (_isLoadingGame()) { break; } critterSetTeam(object, value); break; } break; default: scriptError("\nScript Error: %s: op_critter_add_trait: Trait out of range", program->name); break; } } } else { scriptPredefinedError(program, "critter_add_trait", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, -1); } // critter_rm_trait // 0x458C2C static void opCritterRemoveTrait(Program* program) { int value = programStackPopInteger(program); int param = programStackPopInteger(program); int kind = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "critter_rm_trait", SCRIPT_ERROR_OBJECT_IS_NULL); // FIXME: Ruins stack. return; } if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { switch (kind) { case CRITTER_TRAIT_PERK: while (perkGetRank(object, param) > 0) { if (perkRemove(object, param) != 0) { scriptError("\nScript Error: op_critter_rm_trait: perk_sub failed"); } } break; default: scriptError("\nScript Error: %s: op_critter_rm_trait: Trait out of range", program->name); break; } } programStackPushInteger(program, -1); } // proto_data // 0x458D38 static void opGetProtoData(Program* program) { int member = programStackPopInteger(program); int pid = programStackPopInteger(program); ProtoDataMemberValue value; value.integerValue = 0; int valueType = protoGetDataMember(pid, member, &value); switch (valueType) { case PROTO_DATA_MEMBER_TYPE_INT: programStackPushInteger(program, value.integerValue); break; case PROTO_DATA_MEMBER_TYPE_STRING: programStackPushString(program, value.stringValue); break; default: programStackPushInteger(program, 0); break; } } // message_str // 0x458E10 static void opGetMessageString(Program* program) { // 0x518EFC static char errStr[] = "Error"; int messageIndex = programStackPopInteger(program); int messageListIndex = programStackPopInteger(program); char* string; if (messageIndex >= 1) { string = _scr_get_msg_str_speech(messageListIndex, messageIndex, 1); if (string == NULL) { debugPrint("\nError: No message file EXISTS!: index %d, line %d", messageListIndex, messageIndex); string = errStr; } } else { string = errStr; } programStackPushString(program, string); } // critter_inven_obj // 0x458F00 static void opCritterGetInventoryObject(Program* program) { int type = programStackPopInteger(program); Object* critter = static_cast(programStackPopPointer(program)); if (PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { switch (type) { case INVEN_TYPE_WORN: programStackPushPointer(program, critterGetArmor(critter)); break; case INVEN_TYPE_RIGHT_HAND: if (critter == gDude) { if (interfaceGetCurrentHand() != HAND_LEFT) { programStackPushPointer(program, critterGetItem2(critter)); } else { programStackPushPointer(program, NULL); } } else { programStackPushPointer(program, critterGetItem2(critter)); } break; case INVEN_TYPE_LEFT_HAND: if (critter == gDude) { if (interfaceGetCurrentHand() == HAND_LEFT) { programStackPushPointer(program, critterGetItem1(critter)); } else { programStackPushPointer(program, NULL); } } else { programStackPushPointer(program, critterGetItem1(critter)); } break; case INVEN_TYPE_INV_COUNT: programStackPushInteger(program, critter->data.inventory.length); break; default: scriptError("script error: %s: Error in critter_inven_obj -- wrong type!", program->name); programStackPushInteger(program, 0); break; } } else { scriptPredefinedError(program, "critter_inven_obj", SCRIPT_ERROR_FOLLOWS); debugPrint(" Not a critter!"); programStackPushInteger(program, 0); } } // obj_set_light_level // 0x459088 static void opSetObjectLightLevel(Program* program) { int lightDistance = programStackPopInteger(program); int lightIntensity = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "obj_set_light_level", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Rect rect; if (lightIntensity != 0) { if (objectSetLight(object, lightDistance, (lightIntensity * 65636) / 100, &rect) == -1) { return; } } else { if (objectSetLight(object, lightDistance, 0, &rect) == -1) { return; } } tileWindowRefreshRect(&rect, object->elevation); } // 0x459170 static void opWorldmap(Program* program) { scriptsRequestWorldMap(); } // inven_cmds // 0x459178 static void _op_inven_cmds(Program* program) { int index = programStackPopInteger(program); int cmd = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); Object* item = NULL; if (obj != NULL) { switch (cmd) { case 13: item = _inven_index_ptr(obj, index); break; } } else { // FIXME: Should be inven_cmds. scriptPredefinedError(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushPointer(program, item); } // float_msg // 0x459280 static void opFloatMessage(Program* program) { int floatingMessageType = programStackPopInteger(program); ProgramValue stringValue = programStackPopValue(program); char* string = NULL; if ((stringValue.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = programGetString(program, stringValue.opcode, stringValue.integerValue); } Object* obj = static_cast(programStackPopPointer(program)); int color = _colorTable[32747]; int a5 = _colorTable[0]; int font = 101; if (obj == NULL) { scriptPredefinedError(program, "float_msg", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (string == NULL || *string == '\0') { textObjectsRemoveByOwner(obj); tileWindowRefresh(); return; } if (obj->elevation != gElevation) { return; } if (floatingMessageType == FLOATING_MESSAGE_TYPE_COLOR_SEQUENCE) { floatingMessageType = _last_color + 1; if (floatingMessageType >= FLOATING_MESSAGE_TYPE_COUNT) { floatingMessageType = FLOATING_MESSAGE_TYPE_BLACK; } _last_color = floatingMessageType; } switch (floatingMessageType) { case FLOATING_MESSAGE_TYPE_WARNING: color = _colorTable[31744]; a5 = _colorTable[0]; font = 103; tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW); break; case FLOATING_MESSAGE_TYPE_NORMAL: case FLOATING_MESSAGE_TYPE_YELLOW: color = _colorTable[32747]; break; case FLOATING_MESSAGE_TYPE_BLACK: case FLOATING_MESSAGE_TYPE_PURPLE: case FLOATING_MESSAGE_TYPE_GREY: color = _colorTable[10570]; break; case FLOATING_MESSAGE_TYPE_RED: color = _colorTable[31744]; break; case FLOATING_MESSAGE_TYPE_GREEN: color = _colorTable[992]; break; case FLOATING_MESSAGE_TYPE_BLUE: color = _colorTable[31]; break; case FLOATING_MESSAGE_TYPE_NEAR_WHITE: color = _colorTable[21140]; break; case FLOATING_MESSAGE_TYPE_LIGHT_RED: color = _colorTable[32074]; break; case FLOATING_MESSAGE_TYPE_WHITE: color = _colorTable[32767]; break; case FLOATING_MESSAGE_TYPE_DARK_GREY: color = _colorTable[8456]; break; case FLOATING_MESSAGE_TYPE_LIGHT_GREY: color = _colorTable[15855]; break; } Rect rect; if (textObjectAdd(obj, string, font, color, a5, &rect) != -1) { tileWindowRefreshRect(&rect, obj->elevation); } } // metarule // 0x4594A0 static void opMetarule(Program* program) { ProgramValue param = programStackPopValue(program); int rule = programStackPopInteger(program); int result = 0; switch (rule) { case METARULE_SIGNAL_END_GAME: result = 0; _game_user_wants_to_quit = 2; break; case METARULE_FIRST_RUN: result = (gMapHeader.flags & MAP_SAVED) == 0; break; case METARULE_ELEVATOR: scriptsRequestElevator(scriptGetSelf(program), param.integerValue); result = 0; break; case METARULE_PARTY_COUNT: result = _getPartyMemberCount(); break; case METARULE_AREA_KNOWN: result = wmAreaVisitedState(param.integerValue); break; case METARULE_WHO_ON_DRUGS: result = queueHasEvent(static_cast(param.pointerValue), EVENT_TYPE_DRUG); break; case METARULE_MAP_KNOWN: result = wmMapIsKnown(param.integerValue); break; case METARULE_IS_LOADGAME: result = _isLoadingGame(); break; case METARULE_CAR_CURRENT_TOWN: result = wmCarCurrentArea(); break; case METARULE_GIVE_CAR_TO_PARTY: result = wmCarGiveToParty(); break; case METARULE_GIVE_CAR_GAS: result = wmCarFillGas(param.integerValue); break; case METARULE_SKILL_CHECK_TAG: result = skillIsTagged(param.integerValue); break; case METARULE_DROP_ALL_INVEN: if (1) { Object* object = static_cast(param.pointerValue); result = itemDropAll(object, object->tile); if (gDude == object) { interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); interfaceRenderArmorClass(false); } } break; case METARULE_INVEN_UNWIELD_WHO: if (1) { Object* object = static_cast(param.pointerValue); int hand = HAND_RIGHT; if (object == gDude) { if (interfaceGetCurrentHand() == HAND_LEFT) { hand = HAND_LEFT; } } result = _invenUnwieldFunc(object, hand, 0); if (object == gDude) { bool animated = !gameUiIsDisabled(); interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } else { Object* item = critterGetItem1(object); if (itemGetType(item) == ITEM_TYPE_WEAPON) { item->flags &= ~OBJECT_IN_LEFT_HAND; } } } break; case METARULE_GET_WORLDMAP_XPOS: wmGetPartyWorldPos(&result, NULL); break; case METARULE_GET_WORLDMAP_YPOS: wmGetPartyWorldPos(NULL, &result); break; case METARULE_CURRENT_TOWN: if (wmGetPartyCurArea(&result) == -1) { debugPrint("\nIntextra: Error: metarule: current_town"); } break; case METARULE_LANGUAGE_FILTER: result = static_cast(settings.preferences.language_filter); break; case METARULE_VIOLENCE_FILTER: result = settings.preferences.violence_level; break; case METARULE_WEAPON_DAMAGE_TYPE: if (1) { Object* object = static_cast(param.pointerValue); if (PID_TYPE(object->pid) == OBJ_TYPE_ITEM) { if (itemGetType(object) == ITEM_TYPE_WEAPON) { result = weaponGetDamageType(NULL, object); break; } } else { if (buildFid(OBJ_TYPE_MISC, 10, 0, 0, 0) == object->fid) { result = DAMAGE_TYPE_EXPLOSION; break; } } scriptPredefinedError(program, "metarule:w_damage_type", SCRIPT_ERROR_FOLLOWS); debugPrint("Not a weapon!"); } break; case METARULE_CRITTER_BARTERS: if (1) { Object* object = static_cast(param.pointerValue); if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { Proto* proto; protoGetProto(object->pid, &proto); if ((proto->critter.data.flags & CRITTER_BARTER) != 0) { result = 1; } } } break; case METARULE_CRITTER_KILL_TYPE: result = critterGetKillType(static_cast(param.pointerValue)); break; case METARULE_SET_CAR_CARRY_AMOUNT: if (1) { Proto* proto; if (protoGetProto(PROTO_ID_CAR_TRUNK, &proto) != -1) { proto->item.data.container.maxSize = param.integerValue; result = 1; } } break; case METARULE_GET_CAR_CARRY_AMOUNT: if (1) { Proto* proto; if (protoGetProto(PROTO_ID_CAR_TRUNK, &proto) != -1) { result = proto->item.data.container.maxSize; } } break; } programStackPushInteger(program, result); } // anim // 0x4598BC static void opAnim(Program* program) { ProgramValue frameValue = programStackPopValue(program); int anim = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); // CE: There is a bug in the `animate_rotation` macro in the user-space // sсripts - instead of passing direction, it passes object. The direction // argument is thrown away by preprocessor. Original code ignores this bug // since there is no distiction between integers and pointers. In addition // there is a guard in the code path below which simply ignores any value // greater than 6 (so rotation does not change when pointer is passed). int frame; if (frameValue.opcode == VALUE_TYPE_INT) { frame = frameValue.integerValue; } else if (anim == 1000 && frameValue.opcode == VALUE_TYPE_PTR) { // Force code path below to skip setting rotation. frame = ROTATION_COUNT; } else { programFatalError("script error: %s: invalid arg 2 to anim", program->name); } if (obj == NULL) { scriptPredefinedError(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (anim < ANIM_COUNT) { CritterCombatData* combatData = NULL; if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { combatData = &(obj->data.critter.combat); } anim = _correctDeath(obj, anim, true); reg_anim_begin(ANIMATION_REQUEST_UNRESERVED); // TODO: Not sure about the purpose, why it handles knock down flag? if (frame == 0) { animationRegisterAnimate(obj, anim, 0); if (anim >= ANIM_FALL_BACK && anim <= ANIM_FALL_FRONT_BLOOD) { int fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 28); animationRegisterSetFid(obj, fid, -1); } if (combatData != NULL) { combatData->results &= DAM_KNOCKED_DOWN; } } else { int fid = buildFid(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); animationRegisterAnimateReversed(obj, anim, 0); if (anim == ANIM_PRONE_TO_STANDING) { fid = buildFid(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_FALL_FRONT_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); } else if (anim == ANIM_BACK_TO_STANDING) { fid = buildFid(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_FALL_BACK_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); } if (combatData != NULL) { combatData->results |= DAM_KNOCKED_DOWN; } animationRegisterSetFid(obj, fid, -1); } reg_anim_end(); } else if (anim == 1000) { if (frame < ROTATION_COUNT) { Rect rect; objectSetRotation(obj, frame, &rect); tileWindowRefreshRect(&rect, gElevation); } } else if (anim == 1010) { Rect rect; objectSetFrame(obj, frame, &rect); tileWindowRefreshRect(&rect, gElevation); } else { scriptError("\nScript Error: %s: op_anim: anim out of range", program->name); } } // obj_carrying_pid_obj // 0x459B5C static void opObjectCarryingObjectByPid(Program* program) { int pid = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); Object* result = NULL; if (object != NULL) { result = objectGetCarriedObjectByPid(object, pid); } else { scriptPredefinedError(program, "obj_carrying_pid_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushPointer(program, result); } // reg_anim_func // 0x459C20 static void opRegAnimFunc(Program* program) { ProgramValue param = programStackPopValue(program); int cmd = programStackPopInteger(program); if (!isInCombat()) { switch (cmd) { case OP_REG_ANIM_FUNC_BEGIN: reg_anim_begin(param.integerValue); break; case OP_REG_ANIM_FUNC_CLEAR: reg_anim_clear(static_cast(param.pointerValue)); break; case OP_REG_ANIM_FUNC_END: reg_anim_end(); break; } } } // reg_anim_animate // 0x459CD4 static void opRegAnimAnimate(Program* program) { int delay = programStackPopInteger(program); int anim = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (!isInCombat()) { if (anim != 20 || object == NULL || object->pid != 0x100002F || (settings.preferences.violence_level >= 2)) { if (object != NULL) { animationRegisterAnimate(object, anim, delay); } else { scriptPredefinedError(program, "reg_anim_animate", SCRIPT_ERROR_OBJECT_IS_NULL); } } } } // reg_anim_animate_reverse // 0x459DC4 static void opRegAnimAnimateReverse(Program* program) { int delay = programStackPopInteger(program); int anim = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (!isInCombat()) { if (object != NULL) { animationRegisterAnimateReversed(object, anim, delay); } else { scriptPredefinedError(program, "reg_anim_animate_reverse", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // reg_anim_obj_move_to_obj // 0x459E74 static void opRegAnimObjectMoveToObject(Program* program) { int delay = programStackPopInteger(program); Object* dest = static_cast(programStackPopPointer(program)); Object* object = static_cast(programStackPopPointer(program)); if (!isInCombat()) { if (object != NULL) { animationRegisterMoveToObject(object, dest, -1, delay); } else { scriptPredefinedError(program, "reg_anim_obj_move_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // reg_anim_obj_run_to_obj // 0x459F28 static void opRegAnimObjectRunToObject(Program* program) { int delay = programStackPopInteger(program); Object* dest = static_cast(programStackPopPointer(program)); Object* object = static_cast(programStackPopPointer(program)); if (!isInCombat()) { if (object != NULL) { animationRegisterRunToObject(object, dest, -1, delay); } else { scriptPredefinedError(program, "reg_anim_obj_run_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // reg_anim_obj_move_to_tile // 0x459FDC static void opRegAnimObjectMoveToTile(Program* program) { int delay = programStackPopInteger(program); int tile = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (!isInCombat()) { if (object != NULL) { animationRegisterMoveToTile(object, tile, object->elevation, -1, delay); } else { scriptPredefinedError(program, "reg_anim_obj_move_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // reg_anim_obj_run_to_tile // 0x45A094 static void opRegAnimObjectRunToTile(Program* program) { int delay = programStackPopInteger(program); int tile = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (!isInCombat()) { if (object != NULL) { animationRegisterRunToTile(object, tile, object->elevation, -1, delay); } else { scriptPredefinedError(program, "reg_anim_obj_run_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // play_gmovie // 0x45A14C static void opPlayGameMovie(Program* program) { // 0x453F9C static const unsigned short flags[MOVIE_COUNT] = { GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, }; program->flags |= PROGRAM_FLAG_0x20; int movie = programStackPopInteger(program); // CE: Disable map updates. Needed to stop animation of objects (dude in // particular) when playing movies (the problem can be seen as visual // artifacts when playing endgame oilrig explosion). bool isoWasDisabled = isoDisable(); gameDialogDisable(); if (gameMoviePlay(movie, flags[movie]) == -1) { debugPrint("\nError playing movie %d!", movie); } gameDialogEnable(); if (isoWasDisabled) { isoEnable(); } program->flags &= ~PROGRAM_FLAG_0x20; } // add_mult_objs_to_inven // 0x45A200 static void opAddMultipleObjectsToInventory(Program* program) { int quantity = programStackPopInteger(program); Object* item = static_cast(programStackPopPointer(program)); Object* object = static_cast(programStackPopPointer(program)); if (object == NULL || item == NULL) { return; } if (quantity < 0) { quantity = 1; } else if (quantity > 99999) { // SFALL quantity = 99999; } if (itemAdd(object, item, quantity) == 0) { Rect rect; _obj_disconnect(item, &rect); tileWindowRefreshRect(&rect, item->elevation); } } // rm_mult_objs_from_inven // 0x45A2D4 static void opRemoveMultipleObjectsFromInventory(Program* program) { int quantityToRemove = programStackPopInteger(program); Object* item = static_cast(programStackPopPointer(program)); Object* owner = static_cast(programStackPopPointer(program)); if (owner == NULL || item == NULL) { // FIXME: Ruined stack. return; } bool itemWasEquipped = (item->flags & OBJECT_EQUIPPED) != 0; int quantity = itemGetQuantity(owner, item); if (quantity > quantityToRemove) { quantity = quantityToRemove; } if (quantity != 0) { if (itemRemove(owner, item, quantity) == 0) { Rect updatedRect; _obj_connect(item, 1, 0, &updatedRect); if (itemWasEquipped) { if (owner == gDude) { bool animated = !gameUiIsDisabled(); interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } } } programStackPushInteger(program, quantity); } // get_month // 0x45A40C static void opGetMonth(Program* program) { int month; gameTimeGetDate(&month, NULL, NULL); programStackPushInteger(program, month); } // get_day // 0x45A43C static void opGetDay(Program* program) { int day; gameTimeGetDate(NULL, &day, NULL); programStackPushInteger(program, day); } // explosion // 0x45A46C static void opExplosion(Program* program) { int maxDamage = programStackPopInteger(program); int elevation = programStackPopInteger(program); int tile = programStackPopInteger(program); if (tile == -1) { debugPrint("\nError: explosion: bad tile_num!"); return; } int minDamage = 1; if (maxDamage == 0) { minDamage = 0; } scriptsRequestExplosion(tile, elevation, minDamage, maxDamage); } // days_since_visited // 0x45A528 static void opGetDaysSinceLastVisit(Program* program) { int days; if (gMapHeader.lastVisitTime != 0) { days = (gameTimeGetTime() - gMapHeader.lastVisitTime) / GAME_TIME_TICKS_PER_DAY; } else { days = -1; } programStackPushInteger(program, days); } // gsay_start // 0x45A56C static void _op_gsay_start(Program* program) { program->flags |= PROGRAM_FLAG_0x20; if (_gdialogStart() != 0) { program->flags &= ~PROGRAM_FLAG_0x20; programFatalError("Error starting dialog."); } program->flags &= ~PROGRAM_FLAG_0x20; } // gsay_end // 0x45A5B0 static void _op_gsay_end(Program* program) { program->flags |= PROGRAM_FLAG_0x20; _gdialogGo(); program->flags &= ~PROGRAM_FLAG_0x20; } // gsay_reply // 0x45A5D4 static void _op_gsay_reply(Program* program) { program->flags |= PROGRAM_FLAG_0x20; ProgramValue msg = programStackPopValue(program); int messageListId = programStackPopInteger(program); if ((msg.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* string = programGetString(program, msg.opcode, msg.integerValue); gameDialogSetTextReply(program, messageListId, string); } else if (msg.opcode == VALUE_TYPE_INT) { gameDialogSetMessageReply(program, messageListId, msg.integerValue); } else { programFatalError("script error: %s: invalid arg %d to gsay_reply", program->name, 0); } program->flags &= ~PROGRAM_FLAG_0x20; } // gsay_option // 0x45A6C4 static void _op_gsay_option(Program* program) { program->flags |= PROGRAM_FLAG_0x20; int reaction = programStackPopInteger(program); ProgramValue proc = programStackPopValue(program); ProgramValue msg = programStackPopValue(program); int messageListId = programStackPopInteger(program); if ((proc.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* procName = programGetString(program, proc.opcode, proc.integerValue); if ((msg.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { const char* string = programGetString(program, msg.opcode, msg.integerValue); gameDialogAddTextOptionWithProcIdentifier(messageListId, string, procName, reaction); } else if (msg.opcode == VALUE_TYPE_INT) { gameDialogAddMessageOptionWithProcIdentifier(messageListId, msg.integerValue, procName, reaction); } else { programFatalError("script error: %s: invalid arg %d to gsay_option", program->name, 1); } } else if ((proc.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if ((msg.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { const char* string = programGetString(program, msg.opcode, msg.integerValue); gameDialogAddTextOptionWithProc(messageListId, string, proc.integerValue, reaction); } else if (msg.opcode == VALUE_TYPE_INT) { gameDialogAddMessageOptionWithProc(messageListId, msg.integerValue, proc.integerValue, reaction); } else { programFatalError("script error: %s: invalid arg %d to gsay_option", program->name, 1); } } else { programFatalError("Invalid arg 3 to sayOption"); } program->flags &= ~PROGRAM_FLAG_0x20; } // gsay_message // 0x45A8AC static void _op_gsay_message(Program* program) { program->flags |= PROGRAM_FLAG_0x20; int reaction = programStackPopInteger(program); ProgramValue msg = programStackPopValue(program); int messageListId = programStackPopInteger(program); if ((msg.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* string = programGetString(program, msg.opcode, msg.integerValue); gameDialogSetTextReply(program, messageListId, string); } else if (msg.opcode == VALUE_TYPE_INT) { gameDialogSetMessageReply(program, messageListId, msg.integerValue); } else { programFatalError("script error: %s: invalid arg %d to gsay_message", program->name, 1); } gameDialogAddMessageOptionWithProcIdentifier(-2, -2, NULL, 50); _gdialogSayMessage(); program->flags &= ~PROGRAM_FLAG_0x20; } // giq_option // 0x45A9B4 static void _op_giq_option(Program* program) { program->flags |= PROGRAM_FLAG_0x20; int reaction = programStackPopInteger(program); ProgramValue proc = programStackPopValue(program); ProgramValue msg = programStackPopValue(program); int messageListId = programStackPopInteger(program); int iq = programStackPopInteger(program); int intelligence = critterGetStat(gDude, STAT_INTELLIGENCE); intelligence += perkGetRank(gDude, PERK_SMOOTH_TALKER); if (iq < 0) { if (-intelligence < iq) { program->flags &= ~PROGRAM_FLAG_0x20; return; } } else { if (intelligence < iq) { program->flags &= ~PROGRAM_FLAG_0x20; return; } } if ((proc.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* procName = programGetString(program, proc.opcode, proc.integerValue); if ((msg.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* string = programGetString(program, msg.opcode, msg.integerValue); gameDialogAddTextOptionWithProcIdentifier(messageListId, string, procName, reaction); } else if (msg.opcode == VALUE_TYPE_INT) { gameDialogAddMessageOptionWithProcIdentifier(messageListId, msg.integerValue, procName, reaction); } else { programFatalError("script error: %s: invalid arg %d to giq_option", program->name, 1); } } else if (proc.opcode == VALUE_TYPE_INT) { if ((msg.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* string = programGetString(program, msg.opcode, msg.integerValue); gameDialogAddTextOptionWithProc(messageListId, string, proc.integerValue, reaction); } else if (msg.opcode == VALUE_TYPE_INT) { gameDialogAddMessageOptionWithProc(messageListId, msg.integerValue, proc.integerValue, reaction); } else { programFatalError("script error: %s: invalid arg %d to giq_option", program->name, 1); } } else { programFatalError("script error: %s: invalid arg %d to giq_option", program->name, 3); } program->flags &= ~PROGRAM_FLAG_0x20; } // poison // 0x45AB90 static void opPoison(Program* program) { int amount = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); if (obj == NULL) { scriptPredefinedError(program, "poison", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (critterAdjustPoison(obj, amount) != 0) { debugPrint("\nScript Error: poison: adjust failed!"); } } // get_poison // 0x45AC44 static void opGetPoison(Program* program) { Object* obj = static_cast(programStackPopPointer(program)); int poison = 0; if (obj != NULL) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { poison = critterGetPoison(obj); } else { debugPrint("\nScript Error: get_poison: who is not a critter!"); } } else { scriptPredefinedError(program, "get_poison", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, poison); } // party_add // 0x45ACF4 static void opPartyAdd(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "party_add", SCRIPT_ERROR_OBJECT_IS_NULL); return; } partyMemberAdd(object); } // party_remove // 0x45AD68 static void opPartyRemove(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object == NULL) { scriptPredefinedError(program, "party_remove", SCRIPT_ERROR_OBJECT_IS_NULL); return; } partyMemberRemove(object); } // reg_anim_animate_forever // 0x45ADDC static void opRegAnimAnimateForever(Program* program) { int anim = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); if (!isInCombat()) { if (obj != NULL) { animationRegisterAnimateForever(obj, anim, -1); } else { scriptPredefinedError(program, "reg_anim_animate_forever", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // critter_injure // 0x45AE8C static void opCritterInjure(Program* program) { int flags = programStackPopInteger(program); Object* critter = static_cast(programStackPopPointer(program)); if (critter == NULL) { scriptPredefinedError(program, "critter_injure", SCRIPT_ERROR_OBJECT_IS_NULL); return; } bool reverse = (flags & DAM_PERFORM_REVERSE) != 0; flags &= DAM_CRIP; if (reverse) { critter->data.critter.combat.results &= ~flags; } else { critter->data.critter.combat.results |= flags; } if (critter == gDude) { if ((flags & DAM_CRIP_ARM_ANY) != 0) { int leftItemAction; int rightItemAction; interfaceGetItemActions(&leftItemAction, &rightItemAction); interfaceUpdateItems(true, leftItemAction, rightItemAction); } } } // combat_is_initialized // 0x45AF7C static void opCombatIsInitialized(Program* program) { programStackPushInteger(program, isInCombat() ? 1 : 0); } // gdialog_barter // 0x45AFA0 static void _op_gdialog_barter(Program* program) { int data = programStackPopInteger(program); if (gameDialogBarter(data) == -1) { debugPrint("\nScript Error: gdialog_barter: failed"); } } // difficulty_level // 0x45B010 static void opGetGameDifficulty(Program* program) { programStackPushInteger(program, settings.preferences.game_difficulty); } // running_burning_guy // 0x45B05C static void opGetRunningBurningGuy(Program* program) { programStackPushInteger(program, static_cast(settings.preferences.running_burning_guy)); } // inven_unwield static void _op_inven_unwield(Program* program) { Object* obj; int v1; obj = scriptGetSelf(program); v1 = 1; if (obj == gDude && !interfaceGetCurrentHand()) { v1 = 0; } _inven_unwield(obj, v1); } // obj_is_locked // 0x45B0D8 static void opObjectIsLocked(Program* program) { Object* object = static_cast(programStackPopPointer(program)); bool locked = false; if (object != NULL) { locked = objectIsLocked(object); } else { scriptPredefinedError(program, "obj_is_locked", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, locked ? 1 : 0); } // obj_lock // 0x45B16C static void opObjectLock(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object != NULL) { objectLock(object); } else { scriptPredefinedError(program, "obj_lock", SCRIPT_ERROR_OBJECT_IS_NULL); } } // obj_unlock // 0x45B1E0 static void opObjectUnlock(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object != NULL) { objectUnlock(object); } else { scriptPredefinedError(program, "obj_unlock", SCRIPT_ERROR_OBJECT_IS_NULL); } } // obj_is_open // 0x45B254 static void opObjectIsOpen(Program* program) { Object* object = static_cast(programStackPopPointer(program)); bool isOpen = false; if (object != NULL) { isOpen = objectIsOpen(object); } else { scriptPredefinedError(program, "obj_is_open", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, isOpen ? 1 : 0); } // obj_open // 0x45B2E8 static void opObjectOpen(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object != NULL) { objectOpen(object); } else { scriptPredefinedError(program, "obj_open", SCRIPT_ERROR_OBJECT_IS_NULL); } } // obj_close // 0x45B35C static void opObjectClose(Program* program) { Object* object = static_cast(programStackPopPointer(program)); if (object != NULL) { objectClose(object); } else { scriptPredefinedError(program, "obj_close", SCRIPT_ERROR_OBJECT_IS_NULL); } } // game_ui_disable // 0x45B3D0 static void opGameUiDisable(Program* program) { gameUiDisable(0); } // game_ui_enable // 0x45B3D8 static void opGameUiEnable(Program* program) { gameUiEnable(); } // game_ui_is_disabled // 0x45B3E0 static void opGameUiIsDisabled(Program* program) { programStackPushInteger(program, gameUiIsDisabled() ? 1 : 0); } // gfade_out // 0x45B404 static void opGameFadeOut(Program* program) { int data = programStackPopInteger(program); if (data != 0) { paletteFadeTo(gPaletteBlack); } else { scriptPredefinedError(program, "gfade_out", SCRIPT_ERROR_OBJECT_IS_NULL); } } // gfade_in // 0x45B47C static void opGameFadeIn(Program* program) { int data = programStackPopInteger(program); if (data != 0) { paletteFadeTo(_cmap); } else { scriptPredefinedError(program, "gfade_in", SCRIPT_ERROR_OBJECT_IS_NULL); } } // item_caps_total // 0x45B4F4 static void opItemCapsTotal(Program* program) { Object* object = static_cast(programStackPopPointer(program)); int amount = 0; if (object != NULL) { amount = itemGetTotalCaps(object); } else { scriptPredefinedError(program, "item_caps_total", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, amount); } // item_caps_adjust // 0x45B588 static void opItemCapsAdjust(Program* program) { int amount = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int rc = -1; if (object != NULL) { rc = itemCapsAdjust(object, amount); } else { scriptPredefinedError(program, "item_caps_adjust", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, rc); } // anim_action_frame static void _op_anim_action_frame(Program* program) { int anim = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); int actionFrame = 0; if (object != NULL) { int fid = buildFid(FID_TYPE(object->fid), object->fid & 0xFFF, anim, 0, object->rotation); CacheEntry* frmHandle; Art* frm = artLock(fid, &frmHandle); if (frm != NULL) { actionFrame = artGetActionFrame(frm); artUnlock(frmHandle); } } else { scriptPredefinedError(program, "anim_action_frame", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, actionFrame); } // reg_anim_play_sfx // 0x45B740 static void opRegAnimPlaySfx(Program* program) { int delay = programStackPopInteger(program); char* soundEffectName = programStackPopString(program); Object* obj = static_cast(programStackPopPointer(program)); if (soundEffectName == NULL) { scriptPredefinedError(program, "reg_anim_play_sfx", SCRIPT_ERROR_FOLLOWS); debugPrint(" Can't match string!"); } if (obj != NULL) { animationRegisterPlaySoundEffect(obj, soundEffectName, delay); } else { scriptPredefinedError(program, "reg_anim_play_sfx", SCRIPT_ERROR_OBJECT_IS_NULL); } } // critter_mod_skill // 0x45B840 static void opCritterModifySkill(Program* program) { int points = programStackPopInteger(program); int skill = programStackPopInteger(program); Object* critter = static_cast(programStackPopPointer(program)); if (critter != NULL && points != 0) { if (PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { if (critter == gDude) { int normalizedPoints = abs(points); if (skillIsTagged(skill)) { // Halve number of skill points. Increment/decrement skill // points routines handle that. normalizedPoints /= 2; } if (points > 0) { // Increment skill points one by one. for (int it = 0; it < normalizedPoints; it++) { skillAddForce(gDude, skill); } } else { // Decrement skill points one by one. for (int it = 0; it < normalizedPoints; it++) { skillSubForce(gDude, skill); } } // TODO: Checking for critter is dude twice probably means this // is inlined function. if (critter == gDude) { int leftItemAction; int rightItemAction; interfaceGetItemActions(&leftItemAction, &rightItemAction); interfaceUpdateItems(false, leftItemAction, rightItemAction); } } else { scriptPredefinedError(program, "critter_mod_skill", SCRIPT_ERROR_FOLLOWS); debugPrint(" Can't modify anyone except obj_dude!"); } } } else { scriptPredefinedError(program, "critter_mod_skill", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, 0); } // sfx_build_char_name // 0x45B9C4 static void opSfxBuildCharName(Program* program) { int extra = programStackPopInteger(program); int anim = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); if (obj != NULL) { char soundEffectName[16]; strcpy(soundEffectName, sfxBuildCharName(obj, anim, extra)); programStackPushString(program, soundEffectName); } else { scriptPredefinedError(program, "sfx_build_char_name", SCRIPT_ERROR_OBJECT_IS_NULL); programStackPushString(program, NULL); } } // sfx_build_ambient_name // 0x45BAA8 static void opSfxBuildAmbientName(Program* program) { char* baseName = programStackPopString(program); char soundEffectName[16]; strcpy(soundEffectName, gameSoundBuildAmbientSoundEffectName(baseName)); programStackPushString(program, soundEffectName); } // sfx_build_interface_name // 0x45BB54 static void opSfxBuildInterfaceName(Program* program) { char* baseName = programStackPopString(program); char soundEffectName[16]; strcpy(soundEffectName, gameSoundBuildInterfaceName(baseName)); programStackPushString(program, soundEffectName); } // sfx_build_item_name // 0x45BC00 static void opSfxBuildItemName(Program* program) { const char* baseName = programStackPopString(program); char soundEffectName[16]; strcpy(soundEffectName, gameSoundBuildInterfaceName(baseName)); programStackPushString(program, soundEffectName); } // sfx_build_weapon_name // 0x45BCAC static void opSfxBuildWeaponName(Program* program) { Object* target = static_cast(programStackPopPointer(program)); int hitMode = programStackPopInteger(program); Object* weapon = static_cast(programStackPopPointer(program)); int weaponSfxType = programStackPopInteger(program); char soundEffectName[16]; strcpy(soundEffectName, sfxBuildWeaponName(weaponSfxType, weapon, hitMode, target)); programStackPushString(program, soundEffectName); } // sfx_build_scenery_name // 0x45BD7C static void opSfxBuildSceneryName(Program* program) { int actionType = programStackPopInteger(program); int action = programStackPopInteger(program); char* baseName = programStackPopString(program); char soundEffectName[16]; strcpy(soundEffectName, sfxBuildSceneryName(actionType, action, baseName)); programStackPushString(program, soundEffectName); } // sfx_build_open_name // 0x45BE58 static void opSfxBuildOpenName(Program* program) { int action = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object != NULL) { char soundEffectName[16]; strcpy(soundEffectName, sfxBuildOpenName(object, action)); programStackPushString(program, soundEffectName); } else { scriptPredefinedError(program, "sfx_build_open_name", SCRIPT_ERROR_OBJECT_IS_NULL); programStackPushString(program, NULL); } } // attack_setup // 0x45BF38 static void opAttackSetup(Program* program) { Object* defender = static_cast(programStackPopPointer(program)); Object* attacker = static_cast(programStackPopPointer(program)); program->flags |= PROGRAM_FLAG_0x20; if (attacker != NULL) { if (!critterIsActive(attacker) || (attacker->flags & OBJECT_HIDDEN) != 0) { debugPrint("\n But is already dead or invisible"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (!critterIsActive(defender) || (defender->flags & OBJECT_HIDDEN) != 0) { debugPrint("\n But target is already dead or invisible"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if ((defender->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) { debugPrint("\n But target is AFRAID"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (isInCombat()) { if ((attacker->data.critter.combat.maneuver & CRITTER_MANEUVER_ENGAGING) == 0) { attacker->data.critter.combat.maneuver |= CRITTER_MANEUVER_ENGAGING; attacker->data.critter.combat.whoHitMe = defender; } } else { STRUCT_664980 attack; attack.attacker = attacker; attack.defender = defender; attack.actionPointsBonus = 0; attack.accuracyBonus = 0; attack.damageBonus = 0; attack.minDamage = 0; attack.maxDamage = INT_MAX; attack.field_1C = 0; scriptsRequestCombat(&attack); } } program->flags &= ~PROGRAM_FLAG_0x20; } // destroy_mult_objs // 0x45C0E8 static void opDestroyMultipleObjects(Program* program) { program->flags |= PROGRAM_FLAG_0x20; int quantity = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); Object* self = scriptGetSelf(program); bool isSelf = self == object; int result = 0; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { _combat_delete_critter(object); } Object* owner = objectGetOwner(object); if (owner != NULL) { int quantityToDestroy = itemGetQuantity(owner, object); if (quantityToDestroy > quantity) { quantityToDestroy = quantity; } itemRemove(owner, object, quantityToDestroy); if (owner == gDude) { bool animated = !gameUiIsDisabled(); interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } _obj_connect(object, 1, 0, NULL); if (isSelf) { object->sid = -1; object->flags |= (OBJECT_HIDDEN | OBJECT_NO_SAVE); } else { reg_anim_clear(object); objectDestroy(object, NULL); } result = quantityToDestroy; } else { reg_anim_clear(object); Rect rect; objectDestroy(object, &rect); tileWindowRefreshRect(&rect, gElevation); } programStackPushInteger(program, result); program->flags &= ~PROGRAM_FLAG_0x20; if (isSelf) { program->flags |= PROGRAM_FLAG_0x0100; } } // use_obj_on_obj // 0x45C290 static void opUseObjectOnObject(Program* program) { Object* target = static_cast(programStackPopPointer(program)); Object* item = static_cast(programStackPopPointer(program)); if (item == NULL) { scriptPredefinedError(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (target == NULL) { scriptPredefinedError(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Script* script; int sid = scriptGetSid(program); if (scriptGetScript(sid, &script) == -1) { // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. scriptPredefinedError(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Object* self = scriptGetSelf(program); if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { _action_use_an_item_on_object(self, target, item); } else { _obj_use_item_on(self, target, item); } } // endgame_slideshow // 0x45C3B0 static void opEndgameSlideshow(Program* program) { program->flags |= PROGRAM_FLAG_0x20; scriptsRequestEndgame(); program->flags &= ~PROGRAM_FLAG_0x20; } // move_obj_inven_to_obj // 0x45C3D0 static void opMoveObjectInventoryToObject(Program* program) { Object* object2 = static_cast(programStackPopPointer(program)); Object* object1 = static_cast(programStackPopPointer(program)); if (object1 == NULL) { scriptPredefinedError(program, "move_obj_inven_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (object2 == NULL) { scriptPredefinedError(program, "move_obj_inven_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Object* oldArmor = NULL; Object* item2 = NULL; if (object1 == gDude) { oldArmor = critterGetArmor(object1); } else { item2 = critterGetItem2(object1); } if (object1 != gDude && item2 != NULL) { int flags = 0; if ((item2->flags & 0x01000000) != 0) { flags |= 0x01000000; } if ((item2->flags & 0x02000000) != 0) { flags |= 0x02000000; } _correctFidForRemovedItem(object1, item2, flags); } itemMoveAll(object1, object2); if (object1 == gDude) { if (oldArmor != NULL) { _adjust_ac(gDude, oldArmor, NULL); } _proto_dude_update_gender(); bool animated = !gameUiIsDisabled(); interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } // endgame_movie // 0x45C54C static void opEndgameMovie(Program* program) { program->flags |= PROGRAM_FLAG_0x20; endgamePlayMovie(); program->flags &= ~PROGRAM_FLAG_0x20; } // obj_art_fid // 0x45C56C static void opGetObjectFid(Program* program) { Object* object = static_cast(programStackPopPointer(program)); int fid = 0; if (object != NULL) { fid = object->fid; } else { scriptPredefinedError(program, "obj_art_fid", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, fid); } // art_anim // 0x45C5F8 static void opGetFidAnim(Program* program) { int data = programStackPopInteger(program); programStackPushInteger(program, (data & 0xFF0000) >> 16); } // party_member_obj // 0x45C66C static void opGetPartyMember(Program* program) { int data = programStackPopInteger(program); Object* object = partyMemberFindByPid(data); programStackPushPointer(program, object); } // rotation_to_tile // 0x45C6DC static void opGetRotationToTile(Program* program) { int tile2 = programStackPopInteger(program); int tile1 = programStackPopInteger(program); int rotation = tileGetRotationTo(tile1, tile2); programStackPushInteger(program, rotation); } // jam_lock // 0x45C778 static void opJamLock(Program* program) { Object* object = static_cast(programStackPopPointer(program)); objectJamLock(object); } // gdialog_set_barter_mod // 0x45C7D4 static void opGameDialogSetBarterMod(Program* program) { int data = programStackPopInteger(program); gameDialogSetBarterModifier(data); } // combat_difficulty // 0x45C830 static void opGetCombatDifficulty(Program* program) { programStackPushInteger(program, settings.preferences.combat_difficulty); } // obj_on_screen // 0x45C878 static void opObjectOnScreen(Program* program) { Rect rect; rectCopy(&rect, &stru_453FC0); Object* object = static_cast(programStackPopPointer(program)); int result = 0; if (object != NULL) { if (gElevation == object->elevation) { Rect objectRect; objectGetRect(object, &objectRect); if (rectIntersection(&objectRect, &rect, &objectRect) == 0) { result = 1; } } } else { scriptPredefinedError(program, "obj_on_screen", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, result); } // critter_is_fleeing // 0x45C93C static void opCritterIsFleeing(Program* program) { Object* obj = static_cast(programStackPopPointer(program)); bool fleeing = false; if (obj != NULL) { fleeing = (obj->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0; } else { scriptPredefinedError(program, "critter_is_fleeing", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushInteger(program, fleeing ? 1 : 0); } // critter_set_flee_state // 0x45C9DC static void opCritterSetFleeState(Program* program) { int fleeing = programStackPopInteger(program); Object* object = static_cast(programStackPopPointer(program)); if (object != NULL) { if (fleeing != 0) { object->data.critter.combat.maneuver |= CRITTER_MANUEVER_FLEEING; } else { object->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; } } else { scriptPredefinedError(program, "critter_set_flee_state", SCRIPT_ERROR_OBJECT_IS_NULL); } } // terminate_combat // 0x45CA84 static void opTerminateCombat(Program* program) { if (isInCombat()) { _game_user_wants_to_quit = 1; Object* self = scriptGetSelf(program); if (self != NULL) { if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { self->data.critter.combat.maneuver |= CRITTER_MANEUVER_DISENGAGING; self->data.critter.combat.whoHitMe = NULL; aiInfoSetLastTarget(self, NULL); } } } } // debug_msg // 0x45CAC8 static void opDebugMessage(Program* program) { char* string = programStackPopString(program); if (string != NULL) { if (settings.debug.show_script_messages) { debugPrint("\n"); debugPrint(string); } } } // critter_stop_attacking // 0x45CB70 static void opCritterStopAttacking(Program* program) { Object* obj = static_cast(programStackPopPointer(program)); if (obj != NULL) { obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_DISENGAGING; obj->data.critter.combat.whoHitMe = NULL; aiInfoSetLastTarget(obj, NULL); } else { scriptPredefinedError(program, "critter_stop_attacking", SCRIPT_ERROR_OBJECT_IS_NULL); } } // tile_contains_pid_obj // 0x45CBF8 static void opTileGetObjectWithPid(Program* program) { int pid = programStackPopInteger(program); int elevation = programStackPopInteger(program); int tile = programStackPopInteger(program); Object* found = NULL; if (tile != -1) { Object* object = objectFindFirstAtLocation(elevation, tile); while (object != NULL) { if (object->pid == pid) { found = object; break; } object = objectFindNextAtLocation(); } } programStackPushPointer(program, found); } // obj_name // 0x45CCC8 static void opGetObjectName(Program* program) { Object* obj = static_cast(programStackPopPointer(program)); if (obj != NULL) { _strName = objectGetName(obj); } else { scriptPredefinedError(program, "obj_name", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushString(program, _strName); } // get_pc_stat // 0x45CD64 static void opGetPcStat(Program* program) { int data = programStackPopInteger(program); int value = pcGetStat(data); programStackPushInteger(program, value); } // 0x45CDD4 void _intExtraClose_() { sfallOpcodesExit(); } // 0x45CDD8 void _initIntExtra() { interpreterRegisterOpcode(0x80A1, opGiveExpPoints); // op_give_exp_points interpreterRegisterOpcode(0x80A2, opScrReturn); // op_scr_return interpreterRegisterOpcode(0x80A3, opPlaySfx); // op_play_sfx interpreterRegisterOpcode(0x80A4, opGetObjectName); // op_obj_name interpreterRegisterOpcode(0x80A5, opSfxBuildOpenName); // op_sfx_build_open_name interpreterRegisterOpcode(0x80A6, opGetPcStat); // op_get_pc_stat interpreterRegisterOpcode(0x80A7, opTileGetObjectWithPid); // op_tile_contains_pid_obj interpreterRegisterOpcode(0x80A8, opSetMapStart); // op_set_map_start interpreterRegisterOpcode(0x80A9, opOverrideMapStart); // op_override_map_start interpreterRegisterOpcode(0x80AA, opHasSkill); // op_has_skill interpreterRegisterOpcode(0x80AB, opUsingSkill); // op_using_skill interpreterRegisterOpcode(0x80AC, opRollVsSkill); // op_roll_vs_skill interpreterRegisterOpcode(0x80AD, opSkillContest); // op_skill_contest interpreterRegisterOpcode(0x80AE, opDoCheck); // op_do_check interpreterRegisterOpcode(0x80AF, opSuccess); // op_success interpreterRegisterOpcode(0x80B0, opCritical); // op_critical interpreterRegisterOpcode(0x80B1, opHowMuch); // op_how_much interpreterRegisterOpcode(0x80B2, opMarkAreaKnown); // op_mark_area_known interpreterRegisterOpcode(0x80B3, opReactionInfluence); // op_reaction_influence interpreterRegisterOpcode(0x80B4, opRandom); // op_random interpreterRegisterOpcode(0x80B5, opRollDice); // op_roll_dice interpreterRegisterOpcode(0x80B6, opMoveTo); // op_move_to interpreterRegisterOpcode(0x80B7, opCreateObject); // op_create_object interpreterRegisterOpcode(0x80B8, opDisplayMsg); // op_display_msg interpreterRegisterOpcode(0x80B9, opScriptOverrides); // op_script_overrides interpreterRegisterOpcode(0x80BA, opObjectIsCarryingObjectWithPid); // op_obj_is_carrying_obj interpreterRegisterOpcode(0x80BB, opTileContainsObjectWithPid); // op_tile_contains_obj_pid interpreterRegisterOpcode(0x80BC, opGetSelf); // op_self_obj interpreterRegisterOpcode(0x80BD, opGetSource); // op_source_obj interpreterRegisterOpcode(0x80BE, opGetTarget); // op_target_obj interpreterRegisterOpcode(0x80BF, opGetDude); interpreterRegisterOpcode(0x80C0, opGetObjectBeingUsed); // op_obj_being_used_with interpreterRegisterOpcode(0x80C1, opGetLocalVar); // op_get_local_var interpreterRegisterOpcode(0x80C2, opSetLocalVar); // op_set_local_var interpreterRegisterOpcode(0x80C3, opGetMapVar); // op_get_map_var interpreterRegisterOpcode(0x80C4, opSetMapVar); // op_set_map_var interpreterRegisterOpcode(0x80C5, opGetGlobalVar); // op_get_global_var interpreterRegisterOpcode(0x80C6, opSetGlobalVar); // op_set_global_var interpreterRegisterOpcode(0x80C7, opGetScriptAction); // op_script_action interpreterRegisterOpcode(0x80C8, opGetObjectType); // op_obj_type interpreterRegisterOpcode(0x80C9, opGetItemType); // op_item_subtype interpreterRegisterOpcode(0x80CA, opGetCritterStat); // op_get_critter_stat interpreterRegisterOpcode(0x80CB, opSetCritterStat); // op_set_critter_stat interpreterRegisterOpcode(0x80CC, opAnimateStand); // op_animate_stand_obj interpreterRegisterOpcode(0x80CD, opAnimateStandReverse); // animate_stand_reverse_obj interpreterRegisterOpcode(0x80CE, opAnimateMoveObjectToTile); // animate_move_obj_to_tile interpreterRegisterOpcode(0x80CF, opTileInTileRect); // tile_in_tile_rect interpreterRegisterOpcode(0x80D0, opAttackComplex); // op_attack interpreterRegisterOpcode(0x80D1, opMakeDayTime); // op_make_daytime interpreterRegisterOpcode(0x80D2, opTileDistanceBetween); // op_tile_distance interpreterRegisterOpcode(0x80D3, opTileDistanceBetweenObjects); // op_tile_distance_objs interpreterRegisterOpcode(0x80D4, opGetObjectTile); // op_tile_num interpreterRegisterOpcode(0x80D5, opGetTileInDirection); // op_tile_num_in_direction interpreterRegisterOpcode(0x80D6, opPickup); // op_pickup_obj interpreterRegisterOpcode(0x80D7, opDrop); // op_drop_obj interpreterRegisterOpcode(0x80D8, opAddObjectToInventory); // op_add_obj_to_inven interpreterRegisterOpcode(0x80D9, opRemoveObjectFromInventory); // op_rm_obj_from_inven interpreterRegisterOpcode(0x80DA, opWieldItem); // op_wield_obj_critter interpreterRegisterOpcode(0x80DB, opUseObject); // op_use_obj interpreterRegisterOpcode(0x80DC, opObjectCanSeeObject); // op_obj_can_see_obj interpreterRegisterOpcode(0x80DD, opAttackComplex); // op_attack interpreterRegisterOpcode(0x80DE, opStartGameDialog); // op_start_gdialog interpreterRegisterOpcode(0x80DF, opEndGameDialog); // op_end_gdialog interpreterRegisterOpcode(0x80E0, opGameDialogReaction); // op_dialogue_reaction interpreterRegisterOpcode(0x80E1, opMetarule3); // op_metarule3 interpreterRegisterOpcode(0x80E2, opSetMapMusic); // op_set_map_music interpreterRegisterOpcode(0x80E3, opSetObjectVisibility); // op_set_obj_visibility interpreterRegisterOpcode(0x80E4, opLoadMap); // op_load_map interpreterRegisterOpcode(0x80E5, opWorldmapCitySetPos); // op_wm_area_set_pos interpreterRegisterOpcode(0x80E6, opSetExitGrids); // op_set_exit_grids interpreterRegisterOpcode(0x80E7, opAnimBusy); // op_anim_busy interpreterRegisterOpcode(0x80E8, opCritterHeal); // op_critter_heal interpreterRegisterOpcode(0x80E9, opSetLightLevel); // op_set_light_level interpreterRegisterOpcode(0x80EA, opGetGameTime); // op_game_time interpreterRegisterOpcode(0x80EB, opGetGameTimeInSeconds); // op_game_time / 10 interpreterRegisterOpcode(0x80EC, opGetObjectElevation); // op_elevation interpreterRegisterOpcode(0x80ED, opKillCritter); // op_kill_critter interpreterRegisterOpcode(0x80EE, opKillCritterType); // op_kill_critter_type interpreterRegisterOpcode(0x80EF, opCritterDamage); // op_critter_damage interpreterRegisterOpcode(0x80F0, opAddTimerEvent); // op_add_timer_event interpreterRegisterOpcode(0x80F1, opRemoveTimerEvent); // op_rm_timer_event interpreterRegisterOpcode(0x80F2, opGameTicks); // op_game_ticks interpreterRegisterOpcode(0x80F3, opHasTrait); // op_has_trait interpreterRegisterOpcode(0x80F4, opDestroyObject); // op_destroy_object interpreterRegisterOpcode(0x80F5, opObjectCanHearObject); // op_obj_can_hear_obj interpreterRegisterOpcode(0x80F6, opGameTimeHour); // op_game_time_hour interpreterRegisterOpcode(0x80F7, opGetFixedParam); // op_fixed_param interpreterRegisterOpcode(0x80F8, opTileIsVisible); // op_tile_is_visible interpreterRegisterOpcode(0x80F9, opGameDialogSystemEnter); // op_dialogue_system_enter interpreterRegisterOpcode(0x80FA, opGetActionBeingUsed); // op_action_being_used interpreterRegisterOpcode(0x80FB, opGetCritterState); // critter_state interpreterRegisterOpcode(0x80FC, opGameTimeAdvance); // op_game_time_advance interpreterRegisterOpcode(0x80FD, opRadiationIncrease); // op_radiation_inc interpreterRegisterOpcode(0x80FE, opRadiationDecrease); // op_radiation_dec interpreterRegisterOpcode(0x80FF, opCritterAttemptPlacement); // critter_attempt_placement interpreterRegisterOpcode(0x8100, opGetObjectPid); // op_obj_pid interpreterRegisterOpcode(0x8101, opGetCurrentMap); // op_cur_map_index interpreterRegisterOpcode(0x8102, opCritterAddTrait); // op_critter_add_trait interpreterRegisterOpcode(0x8103, opCritterRemoveTrait); // critter_rm_trait interpreterRegisterOpcode(0x8104, opGetProtoData); // op_proto_data interpreterRegisterOpcode(0x8105, opGetMessageString); // op_message_str interpreterRegisterOpcode(0x8106, opCritterGetInventoryObject); // op_critter_inven_obj interpreterRegisterOpcode(0x8107, opSetObjectLightLevel); // op_obj_set_light_level interpreterRegisterOpcode(0x8108, opWorldmap); // op_scripts_request_world_map interpreterRegisterOpcode(0x8109, _op_inven_cmds); // op_inven_cmds interpreterRegisterOpcode(0x810A, opFloatMessage); // op_float_msg interpreterRegisterOpcode(0x810B, opMetarule); // op_metarule interpreterRegisterOpcode(0x810C, opAnim); // op_anim interpreterRegisterOpcode(0x810D, opObjectCarryingObjectByPid); // op_obj_carrying_pid_obj interpreterRegisterOpcode(0x810E, opRegAnimFunc); // op_reg_anim_func interpreterRegisterOpcode(0x810F, opRegAnimAnimate); // op_reg_anim_animate interpreterRegisterOpcode(0x8110, opRegAnimAnimateReverse); // op_reg_anim_animate_reverse interpreterRegisterOpcode(0x8111, opRegAnimObjectMoveToObject); // op_reg_anim_obj_move_to_obj interpreterRegisterOpcode(0x8112, opRegAnimObjectRunToObject); // op_reg_anim_obj_run_to_obj interpreterRegisterOpcode(0x8113, opRegAnimObjectMoveToTile); // op_reg_anim_obj_move_to_tile interpreterRegisterOpcode(0x8114, opRegAnimObjectRunToTile); // op_reg_anim_obj_run_to_tile interpreterRegisterOpcode(0x8115, opPlayGameMovie); // op_play_gmovie interpreterRegisterOpcode(0x8116, opAddMultipleObjectsToInventory); // op_add_mult_objs_to_inven interpreterRegisterOpcode(0x8117, opRemoveMultipleObjectsFromInventory); // rm_mult_objs_from_inven interpreterRegisterOpcode(0x8118, opGetMonth); // op_month interpreterRegisterOpcode(0x8119, opGetDay); // op_day interpreterRegisterOpcode(0x811A, opExplosion); // op_explosion interpreterRegisterOpcode(0x811B, opGetDaysSinceLastVisit); // op_days_since_visited interpreterRegisterOpcode(0x811C, _op_gsay_start); interpreterRegisterOpcode(0x811D, _op_gsay_end); interpreterRegisterOpcode(0x811E, _op_gsay_reply); // op_gsay_reply interpreterRegisterOpcode(0x811F, _op_gsay_option); // op_gsay_option interpreterRegisterOpcode(0x8120, _op_gsay_message); // op_gsay_message interpreterRegisterOpcode(0x8121, _op_giq_option); // op_giq_option interpreterRegisterOpcode(0x8122, opPoison); // op_poison interpreterRegisterOpcode(0x8123, opGetPoison); // op_get_poison interpreterRegisterOpcode(0x8124, opPartyAdd); // op_party_add interpreterRegisterOpcode(0x8125, opPartyRemove); // op_party_remove interpreterRegisterOpcode(0x8126, opRegAnimAnimateForever); // op_reg_anim_animate_forever interpreterRegisterOpcode(0x8127, opCritterInjure); // op_critter_injure interpreterRegisterOpcode(0x8128, opCombatIsInitialized); // op_is_in_combat interpreterRegisterOpcode(0x8129, _op_gdialog_barter); // op_gdialog_barter interpreterRegisterOpcode(0x812A, opGetGameDifficulty); // op_game_difficulty interpreterRegisterOpcode(0x812B, opGetRunningBurningGuy); // op_running_burning_guy interpreterRegisterOpcode(0x812C, _op_inven_unwield); // op_inven_unwield interpreterRegisterOpcode(0x812D, opObjectIsLocked); // op_obj_is_locked interpreterRegisterOpcode(0x812E, opObjectLock); // op_obj_lock interpreterRegisterOpcode(0x812F, opObjectUnlock); // op_obj_unlock interpreterRegisterOpcode(0x8131, opObjectOpen); // op_obj_open interpreterRegisterOpcode(0x8130, opObjectIsOpen); // op_obj_is_open interpreterRegisterOpcode(0x8132, opObjectClose); // op_obj_close interpreterRegisterOpcode(0x8133, opGameUiDisable); // op_game_ui_disable interpreterRegisterOpcode(0x8134, opGameUiEnable); // op_game_ui_enable interpreterRegisterOpcode(0x8135, opGameUiIsDisabled); // op_game_ui_is_disabled interpreterRegisterOpcode(0x8136, opGameFadeOut); // op_gfade_out interpreterRegisterOpcode(0x8137, opGameFadeIn); // op_gfade_in interpreterRegisterOpcode(0x8138, opItemCapsTotal); // op_item_caps_total interpreterRegisterOpcode(0x8139, opItemCapsAdjust); // op_item_caps_adjust interpreterRegisterOpcode(0x813A, _op_anim_action_frame); // op_anim_action_frame interpreterRegisterOpcode(0x813B, opRegAnimPlaySfx); // op_reg_anim_play_sfx interpreterRegisterOpcode(0x813C, opCritterModifySkill); // op_critter_mod_skill interpreterRegisterOpcode(0x813D, opSfxBuildCharName); // op_sfx_build_char_name interpreterRegisterOpcode(0x813E, opSfxBuildAmbientName); // op_sfx_build_ambient_name interpreterRegisterOpcode(0x813F, opSfxBuildInterfaceName); // op_sfx_build_interface_name interpreterRegisterOpcode(0x8140, opSfxBuildItemName); // op_sfx_build_item_name interpreterRegisterOpcode(0x8141, opSfxBuildWeaponName); // op_sfx_build_weapon_name interpreterRegisterOpcode(0x8142, opSfxBuildSceneryName); // op_sfx_build_scenery_name interpreterRegisterOpcode(0x8143, opAttackSetup); // op_attack_setup interpreterRegisterOpcode(0x8144, opDestroyMultipleObjects); // op_destroy_mult_objs interpreterRegisterOpcode(0x8145, opUseObjectOnObject); // op_use_obj_on_obj interpreterRegisterOpcode(0x8146, opEndgameSlideshow); // op_endgame_slideshow interpreterRegisterOpcode(0x8147, opMoveObjectInventoryToObject); // op_move_obj_inven_to_obj interpreterRegisterOpcode(0x8148, opEndgameMovie); // op_endgame_movie interpreterRegisterOpcode(0x8149, opGetObjectFid); // op_obj_art_fid interpreterRegisterOpcode(0x814A, opGetFidAnim); // op_art_anim interpreterRegisterOpcode(0x814B, opGetPartyMember); // op_party_member_obj interpreterRegisterOpcode(0x814C, opGetRotationToTile); // op_rotation_to_tile interpreterRegisterOpcode(0x814D, opJamLock); // op_jam_lock interpreterRegisterOpcode(0x814E, opGameDialogSetBarterMod); // op_gdialog_set_barter_mod interpreterRegisterOpcode(0x814F, opGetCombatDifficulty); // op_combat_difficulty interpreterRegisterOpcode(0x8150, opObjectOnScreen); // op_obj_on_screen interpreterRegisterOpcode(0x8151, opCritterIsFleeing); // op_critter_is_fleeing interpreterRegisterOpcode(0x8152, opCritterSetFleeState); // op_critter_set_flee_state interpreterRegisterOpcode(0x8153, opTerminateCombat); // op_terminate_combat interpreterRegisterOpcode(0x8154, opDebugMessage); // op_debug_msg interpreterRegisterOpcode(0x8155, opCritterStopAttacking); // op_critter_stop_attacking sfallOpcodesInit(); } // NOTE: Uncollapsed 0x45D878. // // 0x45D878 void intExtraUpdate() { } // NOTE: Uncollapsed 0x45D878. // // 0x45D878 void intExtraRemoveProgramReferences(Program* program) { } } // namespace fallout