2022-05-19 01:51:26 -07:00
|
|
|
#include "actions.h"
|
|
|
|
|
2022-09-15 02:38:23 -07:00
|
|
|
#include <limits.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "animation.h"
|
2022-06-19 03:32:42 -07:00
|
|
|
#include "art.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "color.h"
|
|
|
|
#include "combat.h"
|
|
|
|
#include "combat_ai.h"
|
|
|
|
#include "config.h"
|
|
|
|
#include "critter.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "display_monitor.h"
|
|
|
|
#include "game.h"
|
|
|
|
#include "game_sound.h"
|
|
|
|
#include "geometry.h"
|
|
|
|
#include "interface.h"
|
|
|
|
#include "item.h"
|
|
|
|
#include "map.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "object.h"
|
2022-06-19 02:00:14 -07:00
|
|
|
#include "party_member.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "perk.h"
|
|
|
|
#include "proto.h"
|
|
|
|
#include "proto_instance.h"
|
2022-06-18 01:04:25 -07:00
|
|
|
#include "proto_types.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "random.h"
|
|
|
|
#include "scripts.h"
|
2022-10-06 06:32:46 -07:00
|
|
|
#include "settings.h"
|
2022-08-12 04:55:36 -07:00
|
|
|
#include "sfall_config.h"
|
2022-05-19 01:51:26 -07:00
|
|
|
#include "skill.h"
|
|
|
|
#include "stat.h"
|
|
|
|
#include "text_object.h"
|
|
|
|
#include "tile.h"
|
|
|
|
#include "trait.h"
|
|
|
|
|
2022-09-23 05:43:44 -07:00
|
|
|
namespace fallout {
|
|
|
|
|
2022-05-20 00:39:55 -07:00
|
|
|
#define MAX_KNOCKDOWN_DISTANCE 20
|
|
|
|
|
2022-08-12 04:55:36 -07:00
|
|
|
typedef enum ScienceRepairTargetType {
|
|
|
|
SCIENCE_REPAIR_TARGET_TYPE_DEFAULT,
|
|
|
|
SCIENCE_REPAIR_TARGET_TYPE_DUDE,
|
|
|
|
SCIENCE_REPAIR_TARGET_TYPE_ANYONE,
|
|
|
|
} ScienceRepairTargetType;
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
// 0x5106D0
|
2022-12-23 01:44:52 -08:00
|
|
|
static bool _action_in_explode = false;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// 0x5106E0
|
2022-06-18 01:04:25 -07:00
|
|
|
static const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = {
|
2022-05-19 01:51:26 -07:00
|
|
|
ANIM_DANCING_AUTOFIRE,
|
|
|
|
ANIM_SLICED_IN_HALF,
|
2022-06-04 15:01:49 -07:00
|
|
|
ANIM_CHARRED_BODY,
|
|
|
|
ANIM_CHARRED_BODY,
|
2022-05-19 01:51:26 -07:00
|
|
|
ANIM_ELECTRIFY,
|
|
|
|
ANIM_FALL_BACK,
|
|
|
|
ANIM_BIG_HOLE,
|
|
|
|
};
|
|
|
|
|
|
|
|
// 0x5106FC
|
2022-06-18 01:04:25 -07:00
|
|
|
static const int gMaximumBloodDeathAnimations[DAMAGE_TYPE_COUNT] = {
|
2022-05-19 01:51:26 -07:00
|
|
|
ANIM_CHUNKS_OF_FLESH,
|
|
|
|
ANIM_SLICED_IN_HALF,
|
|
|
|
ANIM_FIRE_DANCE,
|
|
|
|
ANIM_MELTED_TO_NOTHING,
|
|
|
|
ANIM_ELECTRIFIED_TO_NOTHING,
|
|
|
|
ANIM_FALL_BACK,
|
|
|
|
ANIM_EXPLODED_TO_NOTHING,
|
|
|
|
};
|
|
|
|
|
2022-06-18 01:04:25 -07:00
|
|
|
static int actionKnockdown(Object* obj, int* anim, int maxDistance, int rotation, int delay);
|
|
|
|
static int _action_blood(Object* obj, int anim, int delay);
|
2022-12-23 01:44:52 -08:00
|
|
|
static int _pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int attackerAnimation, bool hitFromFront);
|
|
|
|
static int _check_death(Object* obj, int anim, int minViolenceLevel, bool hitFromFront);
|
2022-06-18 01:04:25 -07:00
|
|
|
static int _internal_destroy(Object* a1, Object* a2);
|
2022-12-23 01:44:52 -08:00
|
|
|
static void _show_damage_to_object(Object* defender, int damage, int flags, Object* weapon, bool hitFromFront, int knockbackDistance, int knockbackRotation, int attackerAnimation, Object* attacker, int delay);
|
2022-06-18 01:04:25 -07:00
|
|
|
static int _show_death(Object* obj, int anim);
|
|
|
|
static int _show_damage_extras(Attack* attack);
|
2022-12-23 01:44:52 -08:00
|
|
|
static void _show_damage(Attack* attack, int attackerAnimation, int delay);
|
2022-06-18 01:04:25 -07:00
|
|
|
static int _action_melee(Attack* attack, int a2);
|
|
|
|
static int _action_ranged(Attack* attack, int a2);
|
|
|
|
static int _is_next_to(Object* a1, Object* a2);
|
|
|
|
static int _action_climb_ladder(Object* a1, Object* a2);
|
2022-09-01 08:41:37 -07:00
|
|
|
static int _action_use_skill_in_combat_error(Object* critter);
|
2022-06-18 01:04:25 -07:00
|
|
|
static int _pick_fall(Object* obj, int anim);
|
|
|
|
static int _report_explosion(Attack* attack, Object* a2);
|
|
|
|
static int _finished_explosion(Object* a1, Object* a2);
|
2022-12-23 01:44:52 -08:00
|
|
|
static int _compute_explosion_damage(int min, int max, Object* defender, int* knockbackDistancePtr);
|
2022-06-18 01:04:25 -07:00
|
|
|
static int _can_talk_to(Object* a1, Object* a2);
|
|
|
|
static int _talk_to(Object* a1, Object* a2);
|
|
|
|
static int _report_dmg(Attack* attack, Object* a2);
|
2022-12-23 01:44:52 -08:00
|
|
|
static int _compute_dmg_damage(int min, int max, Object* obj, int* knockbackDistancePtr, int damageType);
|
2022-06-18 01:04:25 -07:00
|
|
|
|
2022-12-22 08:54:04 -08:00
|
|
|
static int hideProjectile(void* a1, void* a2);
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
// 0x410468
|
|
|
|
int actionKnockdown(Object* obj, int* anim, int maxDistance, int rotation, int delay)
|
|
|
|
{
|
2022-10-05 03:06:49 -07:00
|
|
|
if (_critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*anim == ANIM_FALL_FRONT) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, *anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (!artExists(fid)) {
|
|
|
|
*anim = ANIM_FALL_BACK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-20 00:39:55 -07:00
|
|
|
// SFALL: Fix to limit the maximum distance for the knockback animation.
|
|
|
|
if (maxDistance > MAX_KNOCKDOWN_DISTANCE) {
|
|
|
|
maxDistance = MAX_KNOCKDOWN_DISTANCE;
|
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
int distance;
|
|
|
|
int tile;
|
|
|
|
for (distance = 1; distance <= maxDistance; distance++) {
|
|
|
|
tile = tileGetTileInDirection(obj->tile, rotation, distance);
|
|
|
|
if (_obj_blocking_at(obj, tile, obj->elevation) != NULL) {
|
|
|
|
distance--;
|
|
|
|
break;
|
|
|
|
}
|
2022-12-13 05:33:19 -08:00
|
|
|
|
|
|
|
// CE: Fix to prevent critters (including player) cross an exit grid as
|
|
|
|
// a result of knockback. Sfall has similar fix done differently and it
|
|
|
|
// affects the player only. This approach is better since it also
|
|
|
|
// prevents unreachable (=unlootable) corpses on exit grids.
|
|
|
|
if (isExitGridAt(tile, obj->elevation)) {
|
|
|
|
distance--;
|
|
|
|
break;
|
|
|
|
}
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const char* soundEffectName = sfxBuildCharName(obj, *anim, CHARACTER_SOUND_EFFECT_KNOCKDOWN);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(obj, soundEffectName, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
// TODO: Check, probably step back because we've started with 1?
|
|
|
|
distance--;
|
|
|
|
|
|
|
|
if (distance <= 0) {
|
|
|
|
tile = obj->tile;
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(obj, *anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
|
|
|
tile = tileGetTileInDirection(obj->tile, rotation, distance);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterMoveToTileStraightAndWaitForComplete(obj, tile, obj->elevation, *anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return tile;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x410568
|
|
|
|
int _action_blood(Object* obj, int anim, int delay)
|
|
|
|
{
|
2022-10-06 06:32:46 -07:00
|
|
|
if (settings.preferences.violence_level == VIOLENCE_LEVEL_NONE) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return anim;
|
|
|
|
}
|
|
|
|
|
|
|
|
int bloodyAnim;
|
|
|
|
if (anim == ANIM_FALL_BACK) {
|
|
|
|
bloodyAnim = ANIM_FALL_BACK_BLOOD;
|
|
|
|
} else if (anim == ANIM_FALL_FRONT) {
|
|
|
|
bloodyAnim = ANIM_FALL_FRONT_BLOOD;
|
|
|
|
} else {
|
|
|
|
return anim;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, bloodyAnim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (artExists(fid)) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(obj, bloodyAnim, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
|
|
|
bloodyAnim = anim;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bloodyAnim;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x41060C
|
2022-12-23 01:44:52 -08:00
|
|
|
int _pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int attackerAnimation, bool hitFromFront)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
|
|
|
int normalViolenceLevelDamageThreshold = 15;
|
|
|
|
int maximumBloodViolenceLevelDamageThreshold = 45;
|
|
|
|
|
|
|
|
int damageType = weaponGetDamageType(attacker, weapon);
|
|
|
|
|
|
|
|
if (weapon != NULL && weapon->pid == PROTO_ID_MOLOTOV_COCKTAIL) {
|
|
|
|
normalViolenceLevelDamageThreshold = 5;
|
|
|
|
maximumBloodViolenceLevelDamageThreshold = 15;
|
|
|
|
damageType = DAMAGE_TYPE_FIRE;
|
2022-12-23 01:44:52 -08:00
|
|
|
attackerAnimation = ANIM_FIRE_SINGLE;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (attacker == gDude && perkHasRank(attacker, PERK_PYROMANIAC) && damageType == DAMAGE_TYPE_FIRE) {
|
|
|
|
normalViolenceLevelDamageThreshold = 1;
|
|
|
|
maximumBloodViolenceLevelDamageThreshold = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (weapon != NULL && weaponGetPerk(weapon) == PERK_WEAPON_FLAMEBOY) {
|
|
|
|
normalViolenceLevelDamageThreshold /= 3;
|
|
|
|
maximumBloodViolenceLevelDamageThreshold /= 3;
|
|
|
|
}
|
|
|
|
|
2022-10-06 06:32:46 -07:00
|
|
|
int violenceLevel = settings.preferences.violence_level;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-10-05 03:06:49 -07:00
|
|
|
if (_critter_flag_check(defender->pid, CRITTER_SPECIAL_DEATH)) {
|
2022-12-23 01:44:52 -08:00
|
|
|
return _check_death(defender, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_NORMAL, hitFromFront);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
bool hasBloodyMess = false;
|
|
|
|
if (attacker == gDude && traitIsSelected(TRAIT_BLOODY_MESS)) {
|
|
|
|
hasBloodyMess = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: Original code is slightly different. There are lots of jumps and
|
|
|
|
// conditions. It's easier to set the default in advance, rather than catch
|
|
|
|
// it with bunch of "else" statements.
|
|
|
|
int deathAnim = ANIM_FALL_BACK;
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if ((attackerAnimation == ANIM_THROW_PUNCH && damageType == DAMAGE_TYPE_NORMAL)
|
|
|
|
|| attackerAnimation == ANIM_KICK_LEG
|
|
|
|
|| attackerAnimation == ANIM_THRUST_ANIM
|
|
|
|
|| attackerAnimation == ANIM_SWING_ANIM
|
|
|
|
|| (attackerAnimation == ANIM_THROW_ANIM && damageType != DAMAGE_TYPE_EXPLOSION)) {
|
2022-05-19 01:51:26 -07:00
|
|
|
if (violenceLevel == VIOLENCE_LEVEL_MAXIMUM_BLOOD && hasBloodyMess) {
|
|
|
|
deathAnim = ANIM_BIG_HOLE;
|
|
|
|
}
|
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
if (attackerAnimation == ANIM_FIRE_SINGLE && damageType == DAMAGE_TYPE_NORMAL) {
|
2022-05-19 01:51:26 -07:00
|
|
|
if (violenceLevel == VIOLENCE_LEVEL_MAXIMUM_BLOOD) {
|
|
|
|
if (hasBloodyMess || maximumBloodViolenceLevelDamageThreshold <= damage) {
|
|
|
|
deathAnim = ANIM_BIG_HOLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (violenceLevel > VIOLENCE_LEVEL_MINIMAL && (hasBloodyMess || normalViolenceLevelDamageThreshold <= damage)) {
|
|
|
|
if (violenceLevel > VIOLENCE_LEVEL_NORMAL && (hasBloodyMess || maximumBloodViolenceLevelDamageThreshold <= damage)) {
|
|
|
|
deathAnim = gMaximumBloodDeathAnimations[damageType];
|
2022-12-23 01:44:52 -08:00
|
|
|
if (_check_death(defender, deathAnim, VIOLENCE_LEVEL_MAXIMUM_BLOOD, hitFromFront) != deathAnim) {
|
2022-05-19 01:51:26 -07:00
|
|
|
deathAnim = gNormalDeathAnimations[damageType];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
deathAnim = gNormalDeathAnimations[damageType];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (!hitFromFront && deathAnim == ANIM_FALL_BACK) {
|
2022-05-19 01:51:26 -07:00
|
|
|
deathAnim = ANIM_FALL_FRONT;
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
return _check_death(defender, deathAnim, VIOLENCE_LEVEL_NONE, hitFromFront);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 0x410814
|
2022-12-23 01:44:52 -08:00
|
|
|
int _check_death(Object* obj, int anim, int minViolenceLevel, bool hitFromFront)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
|
|
|
int fid;
|
|
|
|
|
2022-10-06 06:32:46 -07:00
|
|
|
if (settings.preferences.violence_level >= minViolenceLevel) {
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (artExists(fid)) {
|
|
|
|
return anim;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (hitFromFront) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return ANIM_FALL_BACK;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (artExists(fid)) {
|
|
|
|
return ANIM_FALL_BACK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ANIM_FALL_FRONT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x4108C8
|
|
|
|
int _internal_destroy(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
return _obj_destroy(a2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Check very carefully, lots of conditions and jumps.
|
|
|
|
//
|
|
|
|
// 0x4108D0
|
2022-12-23 01:44:52 -08:00
|
|
|
void _show_damage_to_object(Object* defender, int damage, int flags, Object* weapon, bool hitFromFront, int knockbackDistance, int knockbackRotation, int attackerAnimation, Object* attacker, int delay)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
|
|
|
int anim;
|
|
|
|
int fid;
|
|
|
|
const char* sfx_name;
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (_critter_flag_check(defender->pid, CRITTER_NO_KNOCKBACK)) {
|
2022-05-19 01:51:26 -07:00
|
|
|
knockbackDistance = 0;
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
anim = FID_ANIM_TYPE(defender->fid);
|
|
|
|
if (!_critter_is_prone(defender)) {
|
2022-05-19 01:51:26 -07:00
|
|
|
if ((flags & DAM_DEAD) != 0) {
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_MISC, 10, 0, 0, 0);
|
2022-12-23 01:44:52 -08:00
|
|
|
if (fid == attacker->fid) {
|
|
|
|
anim = _check_death(defender, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_MAXIMUM_BLOOD, hitFromFront);
|
|
|
|
} else if (attacker->pid == PROTO_ID_0x20001EB) {
|
|
|
|
anim = _check_death(defender, ANIM_ELECTRIFIED_TO_NOTHING, VIOLENCE_LEVEL_MAXIMUM_BLOOD, hitFromFront);
|
|
|
|
} else if (attacker->fid == FID_0x20001F5) {
|
|
|
|
anim = _check_death(defender, attackerAnimation, VIOLENCE_LEVEL_MAXIMUM_BLOOD, hitFromFront);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
anim = _pick_death(attacker, defender, weapon, damage, attackerAnimation, hitFromFront);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (anim != ANIM_FIRE_DANCE) {
|
|
|
|
if (knockbackDistance != 0 && (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK)) {
|
2022-12-23 01:44:52 -08:00
|
|
|
actionKnockdown(defender, &anim, knockbackDistance, knockbackRotation, delay);
|
|
|
|
anim = _action_blood(defender, anim, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
sfx_name = sfxBuildCharName(defender, anim, CHARACTER_SOUND_EFFECT_DIE);
|
|
|
|
animationRegisterPlaySoundEffect(defender, sfx_name, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
anim = _pick_fall(defender, anim);
|
|
|
|
animationRegisterAnimate(defender, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK) {
|
2022-12-23 01:44:52 -08:00
|
|
|
anim = _action_blood(defender, anim, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, defender->fid & 0xFFF, ANIM_FIRE_DANCE, (defender->fid & 0xF000) >> 12, defender->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (artExists(fid)) {
|
2022-12-23 01:44:52 -08:00
|
|
|
sfx_name = sfxBuildCharName(defender, anim, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
animationRegisterPlaySoundEffect(defender, sfx_name, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
if (explosionEmitsLight()) {
|
|
|
|
// 0xFFFF0002:
|
|
|
|
// - distance: 2
|
|
|
|
// - intensity: 65535
|
|
|
|
//
|
|
|
|
// NOTE: Change intensity to 65536 (which is on par with
|
|
|
|
// `anim_set_check_light_fix` Sfall's hack).
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterSetLightIntensity(defender, 2, 65536, 0);
|
2022-08-05 07:43:00 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterAnimate(defender, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
if (explosionEmitsLight()) {
|
|
|
|
// 0x00010000:
|
|
|
|
// - distance: 0
|
|
|
|
// - intensity: 1
|
|
|
|
//
|
|
|
|
// NOTE: Change intensity to 0. I guess using 1 was a
|
|
|
|
// workaround for `anim_set_check_light_fix` hack which
|
|
|
|
// requires two upper bytes to be non-zero to override
|
|
|
|
// default behaviour.
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterSetLightIntensity(defender, 0, 0, -1);
|
2022-08-05 07:43:00 -07:00
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
int randomDistance = randomBetween(2, 5);
|
|
|
|
int randomRotation = randomBetween(0, 5);
|
|
|
|
|
2022-12-13 05:33:19 -08:00
|
|
|
// CE: Fix to prevent critters (including player) to cross
|
|
|
|
// an exit grid as a result of fire dance animation. See
|
|
|
|
// `actionKnockdown` for notes.
|
|
|
|
int rotation = randomRotation;
|
|
|
|
int distance = randomDistance;
|
|
|
|
while (true) {
|
2022-12-23 01:44:52 -08:00
|
|
|
int tile = tileGetTileInDirection(defender->tile, (rotation + randomRotation) % ROTATION_COUNT, distance);
|
|
|
|
if (!isExitGridAt(tile, defender->elevation)) {
|
2022-12-13 05:33:19 -08:00
|
|
|
Object* obstacle = NULL;
|
2022-12-23 01:44:52 -08:00
|
|
|
_make_straight_path(defender, defender->tile, tile, NULL, &obstacle, 4);
|
2022-12-13 05:33:19 -08:00
|
|
|
if (obstacle == NULL) {
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterRotateToTile(defender, tile);
|
|
|
|
animationRegisterMoveToTileStraight(defender, tile, defender->elevation, anim, 0);
|
2022-12-13 05:33:19 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (distance > 0) {
|
|
|
|
distance--;
|
|
|
|
} else if (rotation < ROTATION_COUNT) {
|
|
|
|
rotation++;
|
|
|
|
distance = randomDistance;
|
|
|
|
} else {
|
2022-05-19 01:51:26 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
anim = ANIM_BURNED_TO_NOTHING;
|
2022-12-23 01:44:52 -08:00
|
|
|
sfx_name = sfxBuildCharName(defender, anim, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
animationRegisterPlaySoundEffect(defender, sfx_name, -1);
|
|
|
|
animationRegisterAnimate(defender, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ((flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
anim = hitFromFront ? ANIM_FALL_BACK : ANIM_FALL_FRONT;
|
|
|
|
sfx_name = sfxBuildCharName(defender, anim, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
animationRegisterPlaySoundEffect(defender, sfx_name, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (knockbackDistance != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
actionKnockdown(defender, &anim, knockbackDistance, knockbackRotation, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
anim = _pick_fall(defender, anim);
|
|
|
|
animationRegisterAnimate(defender, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
2022-12-23 01:44:52 -08:00
|
|
|
} else if ((flags & DAM_ON_FIRE) != 0 && artExists(buildFid(OBJ_TYPE_CRITTER, defender->fid & 0xFFF, ANIM_FIRE_DANCE, (defender->fid & 0xF000) >> 12, defender->rotation + 1))) {
|
|
|
|
animationRegisterAnimate(defender, ANIM_FIRE_DANCE, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, defender->fid & 0xFFF, ANIM_STAND, (defender->fid & 0xF000) >> 12, defender->rotation + 1);
|
|
|
|
animationRegisterSetFid(defender, fid, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
|
|
|
if (knockbackDistance != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
anim = hitFromFront ? ANIM_FALL_BACK : ANIM_FALL_FRONT;
|
|
|
|
actionKnockdown(defender, &anim, knockbackDistance, knockbackRotation, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim == ANIM_FALL_BACK) {
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterAnimate(defender, ANIM_BACK_TO_STANDING, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterAnimate(defender, ANIM_PRONE_TO_STANDING, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
if (hitFromFront || !artExists(buildFid(OBJ_TYPE_CRITTER, defender->fid & 0xFFF, ANIM_HIT_FROM_BACK, (defender->fid & 0xF000) >> 12, defender->rotation + 1))) {
|
2022-05-19 01:51:26 -07:00
|
|
|
anim = ANIM_HIT_FROM_FRONT;
|
|
|
|
} else {
|
|
|
|
anim = ANIM_HIT_FROM_BACK;
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
sfx_name = sfxBuildCharName(defender, anim, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
animationRegisterPlaySoundEffect(defender, sfx_name, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterAnimate(defender, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
if ((flags & DAM_DEAD) != 0 && (defender->data.critter.combat.results & DAM_DEAD) == 0) {
|
|
|
|
anim = _action_blood(defender, anim, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (weapon != NULL) {
|
|
|
|
if ((flags & DAM_EXPLODE) != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)_obj_drop, -1);
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_MISC, 10, 0, 0, 0);
|
|
|
|
animationRegisterSetFid(weapon, fid, 0);
|
|
|
|
animationRegisterAnimateAndHide(weapon, ANIM_STAND, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
sfx_name = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(weapon, sfx_name, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterHideObjectForced(weapon);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else if ((flags & DAM_DESTROY) != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)_internal_destroy, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else if ((flags & DAM_DROP) != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)_obj_drop, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & DAM_DEAD) != 0) {
|
|
|
|
// TODO: Get rid of casts.
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterCallbackForced(defender, (void*)anim, (AnimationCallback*)_show_death, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x410E24
|
|
|
|
int _show_death(Object* obj, int anim)
|
|
|
|
{
|
2022-12-23 01:44:52 -08:00
|
|
|
Rect tempRect;
|
|
|
|
Rect dirtyRect;
|
2022-05-19 01:51:26 -07:00
|
|
|
int fid;
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
objectGetRect(obj, &dirtyRect);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim < 48 && anim > 63) {
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
2022-12-23 01:44:52 -08:00
|
|
|
if (objectSetFid(obj, fid, &tempRect) == 0) {
|
|
|
|
rectUnion(&dirtyRect, &tempRect, &dirtyRect);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (objectSetFrame(obj, 0, &tempRect) == 0) {
|
|
|
|
rectUnion(&dirtyRect, &tempRect, &dirtyRect);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-05 03:06:49 -07:00
|
|
|
if (!_critter_flag_check(obj->pid, CRITTER_FLAT)) {
|
2022-05-19 01:51:26 -07:00
|
|
|
obj->flags |= OBJECT_NO_BLOCK;
|
2022-12-23 01:44:52 -08:00
|
|
|
if (_obj_toggle_flat(obj, &tempRect) == 0) {
|
|
|
|
rectUnion(&dirtyRect, &tempRect, &dirtyRect);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (objectDisableOutline(obj, &tempRect) == 0) {
|
|
|
|
rectUnion(&dirtyRect, &tempRect, &dirtyRect);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-10-05 03:06:49 -07:00
|
|
|
if (anim >= 30 && anim <= 31 && !_critter_flag_check(obj->pid, CRITTER_SPECIAL_DEATH) && !_critter_flag_check(obj->pid, CRITTER_NO_DROP)) {
|
2022-08-17 22:41:15 -07:00
|
|
|
itemDropAll(obj, obj->tile);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
tileWindowRefreshRect(&dirtyRect, obj->elevation);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x410FEC
|
|
|
|
int _show_damage_extras(Attack* attack)
|
|
|
|
{
|
|
|
|
for (int index = 0; index < attack->extrasLength; index++) {
|
|
|
|
Object* obj = attack->extras[index];
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) {
|
2022-12-23 01:44:52 -08:00
|
|
|
// NOTE: Uninline.
|
|
|
|
bool hitFromFront = _is_hit_from_front(attack->attacker, obj);
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
_register_priority(1);
|
2022-12-23 01:44:52 -08:00
|
|
|
int attackerAnimation = critterGetAnimationForHitMode(attack->attacker, attack->hitMode);
|
|
|
|
int knockbackRotation = tileGetRotationTo(attack->attacker->tile, obj->tile);
|
|
|
|
_show_damage_to_object(obj, attack->extrasDamage[index], attack->extrasFlags[index], attack->weapon, hitFromFront, attack->extrasKnockback[index], knockbackRotation, attackerAnimation, attack->attacker, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
reg_anim_end();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x4110AC
|
2022-12-23 01:44:52 -08:00
|
|
|
void _show_damage(Attack* attack, int attackerAnimation, int delay)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
|
|
|
for (int index = 0; index < attack->extrasLength; index++) {
|
|
|
|
Object* object = attack->extras[index];
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(object->fid) == OBJ_TYPE_CRITTER) {
|
2022-12-23 04:22:53 -08:00
|
|
|
animationRegisterPing(ANIMATION_REQUEST_RESERVED, delay);
|
2022-12-23 01:44:52 -08:00
|
|
|
delay = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((attack->attackerFlags & DAM_HIT) == 0) {
|
|
|
|
if ((attack->attackerFlags & DAM_CRITICAL) != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
_show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, attackerAnimation, attack->attacker, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else if ((attack->attackerFlags & DAM_BACKWASH) != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
_show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, attackerAnimation, attack->attacker, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (attack->defender != NULL) {
|
2022-12-23 01:44:52 -08:00
|
|
|
// NOTE: Uninline.
|
|
|
|
bool hitFromFront = _is_hit_from_front(attack->defender, attack->attacker);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) {
|
2022-12-23 01:44:52 -08:00
|
|
|
if (attack->attacker->fid == FID_0x20001F5) {
|
|
|
|
int knockbackRotation = tileGetRotationTo(attack->attacker->tile, attack->defender->tile);
|
|
|
|
_show_damage_to_object(attack->defender, attack->defenderDamage, attack->defenderFlags, attack->weapon, hitFromFront, attack->defenderKnockback, knockbackRotation, attackerAnimation, attack->attacker, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
int weaponAnimation = critterGetAnimationForHitMode(attack->attacker, attack->hitMode);
|
|
|
|
int knockbackRotation = tileGetRotationTo(attack->attacker->tile, attack->defender->tile);
|
|
|
|
_show_damage_to_object(attack->defender, attack->defenderDamage, attack->defenderFlags, attack->weapon, hitFromFront, attack->defenderKnockback, knockbackRotation, weaponAnimation, attack->attacker, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tileGetRotationTo(attack->attacker->tile, attack->defender->tile);
|
|
|
|
critterGetAnimationForHitMode(attack->attacker, attack->hitMode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((attack->attackerFlags & DAM_DUD) != 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
_show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, attackerAnimation, attack->attacker, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x411224
|
|
|
|
int _action_attack(Attack* attack)
|
|
|
|
{
|
|
|
|
if (reg_anim_clear(attack->attacker) == -2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reg_anim_clear(attack->defender) == -2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int index = 0; index < attack->extrasLength; index++) {
|
|
|
|
if (reg_anim_clear(attack->extras[index]) == -2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int anim = critterGetAnimationForHitMode(attack->attacker, attack->hitMode);
|
|
|
|
if (anim < ANIM_FIRE_SINGLE && anim != ANIM_THROW_ANIM) {
|
|
|
|
return _action_melee(attack, anim);
|
|
|
|
} else {
|
|
|
|
return _action_ranged(attack, anim);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x4112B4
|
|
|
|
int _action_melee(Attack* attack, int anim)
|
|
|
|
{
|
|
|
|
int fid;
|
|
|
|
Art* art;
|
|
|
|
CacheEntry* cache_entry;
|
2022-12-23 01:44:52 -08:00
|
|
|
int delay;
|
2022-05-19 01:51:26 -07:00
|
|
|
const char* sfx_name;
|
|
|
|
char sfx_name_temp[16];
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
_register_priority(1);
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
art = artLock(fid, &cache_entry);
|
|
|
|
if (art != NULL) {
|
2022-12-23 01:44:52 -08:00
|
|
|
delay = artGetActionFrame(art);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-12-23 01:44:52 -08:00
|
|
|
delay = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
artUnlock(cache_entry);
|
|
|
|
|
|
|
|
tileGetTileInDirection(attack->attacker->tile, attack->attacker->rotation, 1);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRotateToTile(attack->attacker, attack->defender->tile);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (anim != ANIM_THROW_PUNCH && anim != ANIM_KICK_LEG) {
|
|
|
|
sfx_name = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_ATTACK, attack->weapon, attack->hitMode, attack->defender);
|
|
|
|
} else {
|
|
|
|
sfx_name = sfxBuildCharName(attack->attacker, anim, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(sfx_name_temp, sfx_name);
|
|
|
|
|
|
|
|
_combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_ATTACK, 0);
|
|
|
|
|
|
|
|
if (attack->attackerFlags & 0x0300) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(attack->attacker, sfx_name_temp, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim != ANIM_THROW_PUNCH && anim != ANIM_KICK_LEG) {
|
|
|
|
sfx_name = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, attack->weapon, attack->hitMode, attack->defender);
|
|
|
|
} else {
|
|
|
|
sfx_name = sfxBuildCharName(attack->attacker, anim, CHARACTER_SOUND_EFFECT_CONTACT);
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(sfx_name_temp, sfx_name);
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(attack->attacker, anim, 0);
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterPlaySoundEffect(attack->attacker, sfx_name_temp, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
_show_damage(attack, anim, 0);
|
|
|
|
} else {
|
|
|
|
if (attack->defender->data.critter.combat.results & 0x03) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(attack->attacker, sfx_name_temp, -1);
|
|
|
|
animationRegisterAnimate(attack->attacker, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, attack->defender->fid & 0xFFF, ANIM_DODGE_ANIM, (attack->defender->fid & 0xF000) >> 12, attack->defender->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
art = artLock(fid, &cache_entry);
|
|
|
|
if (art != NULL) {
|
2022-12-23 01:44:52 -08:00
|
|
|
int dodgeDelay = artGetActionFrame(art);
|
2022-05-19 01:51:26 -07:00
|
|
|
artUnlock(cache_entry);
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (dodgeDelay <= delay) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(attack->attacker, sfx_name_temp, -1);
|
|
|
|
animationRegisterAnimate(attack->attacker, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
sfx_name = sfxBuildCharName(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED);
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterPlaySoundEffect(attack->defender, sfx_name, delay - dodgeDelay);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(attack->defender, ANIM_DODGE_ANIM, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
|
|
|
sfx_name = sfxBuildCharName(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(attack->defender, sfx_name, -1);
|
|
|
|
animationRegisterAnimate(attack->defender, ANIM_DODGE_ANIM, 0);
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterPlaySoundEffect(attack->attacker, sfx_name_temp, dodgeDelay - delay);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(attack->attacker, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((attack->attackerFlags & DAM_HIT) != 0) {
|
|
|
|
if ((attack->defenderFlags & DAM_DEAD) == 0) {
|
|
|
|
_combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_HIT, -1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_MISS, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reg_anim_end() == -1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_show_damage_extras(attack);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x411600
|
|
|
|
int _action_ranged(Attack* attack, int anim)
|
|
|
|
{
|
2022-12-23 01:44:52 -08:00
|
|
|
Object* adjacentObjects[ROTATION_COUNT];
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
|
|
|
adjacentObjects[rotation] = NULL;
|
|
|
|
}
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
_register_priority(1);
|
|
|
|
|
|
|
|
Object* projectile = NULL;
|
2022-12-23 01:44:52 -08:00
|
|
|
Object* replacedWeapon = NULL;
|
2022-05-19 01:51:26 -07:00
|
|
|
int weaponFid = -1;
|
|
|
|
|
|
|
|
Proto* weaponProto;
|
|
|
|
Object* weapon = attack->weapon;
|
|
|
|
protoGetProto(weapon->pid, &weaponProto);
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
CacheEntry* artHandle;
|
|
|
|
Art* art = artLock(fid, &artHandle);
|
2022-12-23 01:44:52 -08:00
|
|
|
int delay = (art != NULL) ? artGetActionFrame(art) : 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
artUnlock(artHandle);
|
|
|
|
|
2022-08-17 22:41:15 -07:00
|
|
|
weaponGetRange(attack->attacker, attack->hitMode);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
int damageType = weaponGetDamageType(attack->attacker, attack->weapon);
|
|
|
|
|
|
|
|
tileGetTileInDirection(attack->attacker->tile, attack->attacker->rotation, 1);
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRotateToTile(attack->attacker, attack->defender->tile);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
bool isGrenade = false;
|
|
|
|
if (anim == ANIM_THROW_ANIM) {
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
if (damageType == explosionGetDamageType() || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) {
|
2022-05-19 01:51:26 -07:00
|
|
|
isGrenade = true;
|
|
|
|
}
|
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(attack->attacker, ANIM_POINT, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
_combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_ATTACK, 0);
|
|
|
|
|
|
|
|
const char* sfx;
|
|
|
|
if (((attack->attacker->fid & 0xF000) >> 12) != 0) {
|
|
|
|
sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_ATTACK, weapon, attack->hitMode, attack->defender);
|
|
|
|
} else {
|
|
|
|
sfx = sfxBuildCharName(attack->attacker, anim, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
}
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(attack->attacker, sfx, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(attack->attacker, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (anim != ANIM_FIRE_CONTINUOUS) {
|
|
|
|
if ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0) {
|
|
|
|
bool l56 = false;
|
|
|
|
|
|
|
|
int projectilePid = weaponGetProjectilePid(weapon);
|
|
|
|
Proto* projectileProto;
|
|
|
|
if (protoGetProto(projectilePid, &projectileProto) != -1 && projectileProto->fid != -1) {
|
|
|
|
if (anim == ANIM_THROW_ANIM) {
|
|
|
|
projectile = weapon;
|
|
|
|
weaponFid = weapon->fid;
|
|
|
|
int weaponFlags = weapon->flags;
|
|
|
|
|
|
|
|
int leftItemAction;
|
|
|
|
int rightItemAction;
|
|
|
|
interfaceGetItemActions(&leftItemAction, &rightItemAction);
|
|
|
|
|
|
|
|
itemRemove(attack->attacker, weapon, 1);
|
2022-12-23 01:44:52 -08:00
|
|
|
replacedWeapon = itemReplace(attack->attacker, weapon, weaponFlags & OBJECT_IN_ANY_HAND);
|
2022-05-19 01:51:26 -07:00
|
|
|
objectSetFid(projectile, projectileProto->fid, NULL);
|
|
|
|
_cAIPrepWeaponItem(attack->attacker, weapon);
|
|
|
|
|
|
|
|
if (attack->attacker == gDude) {
|
2022-12-23 01:44:52 -08:00
|
|
|
if (replacedWeapon == NULL) {
|
2022-05-19 01:51:26 -07:00
|
|
|
if ((weaponFlags & OBJECT_IN_LEFT_HAND) != 0) {
|
|
|
|
leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
|
|
|
|
} else if ((weaponFlags & OBJECT_IN_RIGHT_HAND) != 0) {
|
|
|
|
rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
interfaceUpdateItems(false, leftItemAction, rightItemAction);
|
|
|
|
}
|
|
|
|
|
|
|
|
_obj_connect(weapon, attack->attacker->tile, attack->attacker->elevation, NULL);
|
|
|
|
} else {
|
|
|
|
objectCreateWithFidPid(&projectile, projectileProto->fid, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
objectHide(projectile, NULL);
|
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
if (explosionEmitsLight() && projectile->lightIntensity == 0) {
|
|
|
|
objectSetLight(projectile, projectileProto->item.lightDistance, projectileProto->item.lightIntensity, NULL);
|
|
|
|
} else {
|
|
|
|
objectSetLight(projectile, 9, projectile->lightIntensity, NULL);
|
|
|
|
}
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
int projectileOrigin = _combat_bullet_start(attack->attacker, attack->defender);
|
|
|
|
objectSetLocation(projectile, projectileOrigin, attack->attacker->elevation, NULL);
|
|
|
|
|
|
|
|
int projectileRotation = tileGetRotationTo(attack->attacker->tile, attack->defender->tile);
|
|
|
|
objectSetRotation(projectile, projectileRotation, NULL);
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterUnsetFlag(projectile, OBJECT_HIDDEN, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_AMMO_FLYING, weapon, attack->hitMode, attack->defender);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(projectile, sfx, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
int explosionCenterTile;
|
2022-05-19 01:51:26 -07:00
|
|
|
if ((attack->attackerFlags & DAM_HIT) != 0) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterMoveToTileStraight(projectile, attack->defender->tile, attack->defender->elevation, ANIM_WALK, 0);
|
2022-12-23 01:44:52 -08:00
|
|
|
delay = _make_straight_path(projectile, projectileOrigin, attack->defender->tile, NULL, NULL, 32) - 1;
|
|
|
|
explosionCenterTile = attack->defender->tile;
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterMoveToTileStraight(projectile, attack->tile, attack->defender->elevation, ANIM_WALK, 0);
|
2022-12-23 01:44:52 -08:00
|
|
|
delay = 0;
|
|
|
|
explosionCenterTile = attack->tile;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
if (isGrenade || damageType == explosionGetDamageType()) {
|
2022-05-19 01:51:26 -07:00
|
|
|
if ((attack->attackerFlags & DAM_DROP) == 0) {
|
|
|
|
int explosionFrmId;
|
|
|
|
if (isGrenade) {
|
|
|
|
switch (damageType) {
|
|
|
|
case DAMAGE_TYPE_EMP:
|
|
|
|
explosionFrmId = 2;
|
|
|
|
break;
|
|
|
|
case DAMAGE_TYPE_PLASMA:
|
|
|
|
explosionFrmId = 31;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
explosionFrmId = 29;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
explosionFrmId = 10;
|
|
|
|
}
|
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
int explosionFrmIdOverride = explosionGetFrm();
|
|
|
|
if (explosionFrmIdOverride != -1) {
|
|
|
|
explosionFrmId = explosionFrmIdOverride;
|
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
if (isGrenade) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterSetFid(projectile, weaponFid, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int explosionFid = buildFid(OBJ_TYPE_MISC, explosionFrmId, 0, 0, 0);
|
|
|
|
animationRegisterSetFid(projectile, explosionFid, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(projectile, sfx, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
if (explosionEmitsLight()) {
|
|
|
|
animationRegisterAnimate(projectile, ANIM_STAND, 0);
|
|
|
|
// 0xFFFF0008
|
|
|
|
// - distance: 8
|
|
|
|
// - intensity: 65535
|
|
|
|
animationRegisterSetLightIntensity(projectile, 8, 65536, 0);
|
|
|
|
} else {
|
|
|
|
animationRegisterAnimateAndHide(projectile, ANIM_STAND, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// SFALL
|
|
|
|
int startRotation;
|
|
|
|
int endRotation;
|
|
|
|
explosionGetPattern(&startRotation, &endRotation);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
for (int rotation = startRotation; rotation < endRotation; rotation++) {
|
2022-12-23 01:44:52 -08:00
|
|
|
if (objectCreateWithFidPid(&(adjacentObjects[rotation]), explosionFid, -1) != -1) {
|
|
|
|
objectHide(adjacentObjects[rotation], NULL);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
int adjacentTile = tileGetTileInDirection(explosionCenterTile, rotation, 1);
|
|
|
|
objectSetLocation(adjacentObjects[rotation], adjacentTile, projectile->elevation, NULL);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
int delay;
|
|
|
|
if (rotation != ROTATION_NE) {
|
|
|
|
delay = 0;
|
|
|
|
} else {
|
|
|
|
if (damageType == DAMAGE_TYPE_PLASMA) {
|
|
|
|
delay = 4;
|
|
|
|
} else {
|
|
|
|
delay = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterUnsetFlag(adjacentObjects[rotation], OBJECT_HIDDEN, delay);
|
|
|
|
animationRegisterAnimateAndHide(adjacentObjects[rotation], ANIM_STAND, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
l56 = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (anim != ANIM_THROW_ANIM) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterHideObjectForced(projectile);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!l56) {
|
|
|
|
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender);
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterPlaySoundEffect(weapon, sfx, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
delay = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
|
|
|
if ((attack->attackerFlags & DAM_HIT) == 0) {
|
|
|
|
Object* defender = attack->defender;
|
|
|
|
if ((defender->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) == 0) {
|
2022-12-23 01:44:52 -08:00
|
|
|
animationRegisterAnimate(defender, ANIM_DODGE_ANIM, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
l56 = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
_show_damage(attack, anim, delay);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if ((attack->attackerFlags & DAM_HIT) == 0) {
|
|
|
|
_combatai_msg(attack->defender, attack, AI_MESSAGE_TYPE_MISS, -1);
|
|
|
|
} else {
|
|
|
|
if ((attack->defenderFlags & DAM_DEAD) == 0) {
|
|
|
|
_combatai_msg(attack->defender, attack, AI_MESSAGE_TYPE_HIT, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 07:43:00 -07:00
|
|
|
// SFALL
|
|
|
|
if (projectile != NULL && (isGrenade || damageType == explosionGetDamageType())) {
|
2022-12-22 08:54:04 -08:00
|
|
|
// CE: Use custom callback to hide projectile instead of relying on
|
|
|
|
// `animationRegisterHideObjectForced`. The problem is that completing
|
|
|
|
// `ANIM_KIND_HIDE` removes (frees) object entirely. When this happens
|
|
|
|
// `attack->weapon` becomes a dangling pointer, but this object is
|
|
|
|
// needed to process `damage_p_proc` by scripting engine which can
|
|
|
|
// interrogate weapon's properties (leading to crash on some platforms).
|
|
|
|
// So instead of removing projectile follow a pattern established in
|
|
|
|
// `opDestroyObject` for self-deleting objects (mark it hidden +
|
|
|
|
// no-save).
|
|
|
|
animationRegisterCallbackForced(attack, projectile, hideProjectile, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else if (anim == ANIM_THROW_ANIM && projectile != NULL) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterSetFid(projectile, weaponFid, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
2022-12-23 01:44:52 -08:00
|
|
|
if (adjacentObjects[rotation] != NULL) {
|
|
|
|
animationRegisterHideObjectForced(adjacentObjects[rotation]);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((attack->attackerFlags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_DEAD)) == 0) {
|
|
|
|
if (anim == ANIM_THROW_ANIM) {
|
2022-12-23 01:44:52 -08:00
|
|
|
bool takeOutAnimationRegistered = false;
|
|
|
|
if (replacedWeapon != NULL) {
|
|
|
|
int weaponAnimationCode = weaponGetAnimationCode(replacedWeapon);
|
|
|
|
if (weaponAnimationCode != 0) {
|
|
|
|
animationRegisterTakeOutWeapon(attack->attacker, weaponAnimationCode, -1);
|
|
|
|
takeOutAnimationRegistered = true;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (!takeOutAnimationRegistered) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, ANIM_STAND, 0, attack->attacker->rotation + 1);
|
|
|
|
animationRegisterSetFid(attack->attacker, fid, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(attack->attacker, ANIM_UNPOINT, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reg_anim_end() == -1) {
|
|
|
|
debugPrint("Something went wrong with a ranged attack sequence!\n");
|
|
|
|
if (projectile != NULL && (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION || anim != ANIM_THROW_ANIM)) {
|
|
|
|
objectDestroy(projectile, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
2022-12-23 01:44:52 -08:00
|
|
|
if (adjacentObjects[rotation] != NULL) {
|
|
|
|
objectDestroy(adjacentObjects[rotation], NULL);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_show_damage_extras(attack);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x411D68
|
|
|
|
int _is_next_to(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
if (objectGetDistanceBetween(a1, a2) > 1) {
|
|
|
|
if (a2 == gDude) {
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
// You cannot get there.
|
|
|
|
messageListItem.num = 2000;
|
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x411DB4
|
|
|
|
int _action_climb_ladder(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
if (a1 == gDude) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = FID_ANIM_TYPE(gDude->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim == ANIM_WALK || anim == ANIM_RUNNING) {
|
|
|
|
reg_anim_clear(gDude);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int animationRequestOptions;
|
|
|
|
int actionPoints;
|
2022-05-19 01:51:26 -07:00
|
|
|
if (isInCombat()) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRequestOptions = ANIMATION_REQUEST_RESERVED;
|
|
|
|
actionPoints = a1->data.critter.combat.ap;
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRequestOptions = ANIMATION_REQUEST_UNRESERVED;
|
|
|
|
actionPoints = -1;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (a1 == gDude) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRequestOptions = ANIMATION_REQUEST_RESERVED;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRequestOptions |= ANIMATION_REQUEST_NO_STAND;
|
|
|
|
reg_anim_begin(animationRequestOptions);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int tile = tileGetTileInDirection(a2->tile, ROTATION_SE, 1);
|
|
|
|
if (actionPoints != -1 || objectGetDistanceBetween(a1, a2) < 5) {
|
|
|
|
animationRegisterMoveToTile(a1, tile, a2->elevation, actionPoints, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRunToTile(a1, tile, a2->elevation, actionPoints, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(a1, a2, (AnimationCallback*)_is_next_to, -1);
|
|
|
|
animationRegisterRotateToTile(a1, a2->tile);
|
|
|
|
animationRegisterCallbackForced(a1, a2, (AnimationCallback*)_check_scenery_ap_cost, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int weaponAnimationCode = (a1->fid & 0xF000) >> 12;
|
|
|
|
if (weaponAnimationCode != 0) {
|
|
|
|
const char* puttingAwaySfx = sfxBuildCharName(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
animationRegisterPlaySoundEffect(a1, puttingAwaySfx, -1);
|
|
|
|
animationRegisterAnimate(a1, ANIM_PUT_AWAY, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
const char* climbingSfx = sfxBuildCharName(a1, ANIM_CLIMB_LADDER, CHARACTER_SOUND_EFFECT_UNUSED);
|
|
|
|
animationRegisterPlaySoundEffect(a1, climbingSfx, -1);
|
|
|
|
animationRegisterAnimate(a1, ANIM_CLIMB_LADDER, 0);
|
|
|
|
animationRegisterCallback(a1, a2, (AnimationCallback*)_obj_use, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (weaponAnimationCode != 0) {
|
|
|
|
animationRegisterTakeOutWeapon(a1, weaponAnimationCode, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return reg_anim_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x411F2C
|
|
|
|
int _action_use_an_item_on_object(Object* a1, Object* a2, Object* a3)
|
|
|
|
{
|
|
|
|
Proto* proto = NULL;
|
2022-07-29 06:04:05 -07:00
|
|
|
int type = FID_TYPE(a2->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
int sceneryType = -1;
|
|
|
|
if (type == OBJ_TYPE_SCENERY) {
|
|
|
|
if (protoGetProto(a2->pid, &proto) == -1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sceneryType = proto->scenery.type;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sceneryType != SCENERY_TYPE_LADDER_UP || a3 != NULL) {
|
|
|
|
if (a1 == gDude) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = FID_ANIM_TYPE(gDude->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim == ANIM_WALK || anim == ANIM_RUNNING) {
|
|
|
|
reg_anim_clear(gDude);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int animationRequestOptions;
|
2022-05-19 01:51:26 -07:00
|
|
|
int actionPoints;
|
|
|
|
if (isInCombat()) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRequestOptions = ANIMATION_REQUEST_RESERVED;
|
2022-05-19 01:51:26 -07:00
|
|
|
actionPoints = a1->data.critter.combat.ap;
|
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRequestOptions = ANIMATION_REQUEST_UNRESERVED;
|
2022-05-19 01:51:26 -07:00
|
|
|
actionPoints = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a1 == gDude) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRequestOptions = ANIMATION_REQUEST_RESERVED;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(animationRequestOptions);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (actionPoints != -1 || objectGetDistanceBetween(a1, a2) < 5) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterMoveToObject(a1, a2, actionPoints, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRunToObject(a1, a2, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(a1, a2, (AnimationCallback*)_is_next_to, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (a3 == NULL) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallback(a1, a2, (AnimationCallback*)_check_scenery_ap_cost, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
int a2a = (a1->fid & 0xF000) >> 12;
|
|
|
|
if (a2a != 0) {
|
|
|
|
const char* sfx = sfxBuildCharName(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(a1, sfx, -1);
|
|
|
|
animationRegisterAnimate(a1, ANIM_PUT_AWAY, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim;
|
|
|
|
int objectType = FID_TYPE(a2->fid);
|
|
|
|
if (objectType == OBJ_TYPE_CRITTER && _critter_is_prone(a2)) {
|
|
|
|
anim = ANIM_MAGIC_HANDS_GROUND;
|
|
|
|
} else if (objectType == OBJ_TYPE_SCENERY && (proto->scenery.extendedFlags & 0x01) != 0) {
|
|
|
|
anim = ANIM_MAGIC_HANDS_GROUND;
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
anim = ANIM_MAGIC_HANDS_MIDDLE;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (sceneryType != SCENERY_TYPE_STAIRS && a3 == NULL) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(a1, anim, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (a3 != NULL) {
|
|
|
|
// TODO: Get rid of cast.
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallback3(a1, a2, a3, (AnimationCallback3*)_obj_use_item_on, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallback(a1, a2, (AnimationCallback*)_obj_use, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (a2a != 0) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterTakeOutWeapon(a1, a2a, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return reg_anim_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
return _action_climb_ladder(a1, a2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x412114
|
|
|
|
int _action_use_an_object(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
return _action_use_an_item_on_object(a1, a2, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x412134
|
|
|
|
int actionPickUp(Object* critter, Object* item)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(item->fid) != OBJ_TYPE_ITEM) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critter == gDude) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int animationCode = FID_ANIM_TYPE(gDude->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (animationCode == ANIM_WALK || animationCode == ANIM_RUNNING) {
|
|
|
|
reg_anim_clear(gDude);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInCombat()) {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
|
|
|
animationRegisterMoveToObject(critter, item, critter->data.critter.combat.ap, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(critter == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (objectGetDistanceBetween(critter, item) >= 5) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRunToObject(critter, item, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterMoveToObject(critter, item, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(critter, item, (AnimationCallback*)_is_next_to, -1);
|
|
|
|
animationRegisterCallback(critter, item, (AnimationCallback*)_check_scenery_ap_cost, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
Proto* itemProto;
|
|
|
|
protoGetProto(item->pid, &itemProto);
|
|
|
|
|
|
|
|
if (itemProto->item.type != ITEM_TYPE_CONTAINER || _proto_action_can_pickup(item->pid)) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(critter, ANIM_MAGIC_HANDS_GROUND, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_MAGIC_HANDS_GROUND, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
int actionFrame;
|
|
|
|
CacheEntry* cacheEntry;
|
|
|
|
Art* art = artLock(fid, &cacheEntry);
|
|
|
|
if (art != NULL) {
|
|
|
|
actionFrame = artGetActionFrame(art);
|
|
|
|
} else {
|
|
|
|
actionFrame = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char sfx[16];
|
2022-07-29 06:04:05 -07:00
|
|
|
if (artCopyFileName(FID_TYPE(item->fid), item->fid & 0xFFF, sfx) == 0) {
|
2022-05-19 01:51:26 -07:00
|
|
|
// NOTE: looks like they copy sfx one more time, what for?
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(item, sfx, actionFrame);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallback(critter, item, (AnimationCallback*)_obj_pickup, actionFrame);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
int weaponAnimationCode = (critter->fid & 0xF000) >> 12;
|
|
|
|
if (weaponAnimationCode != 0) {
|
2022-05-19 01:51:26 -07:00
|
|
|
const char* sfx = sfxBuildCharName(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(critter, sfx, -1);
|
|
|
|
animationRegisterAnimate(critter, ANIM_PUT_AWAY, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// ground vs middle animation
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = (itemProto->item.data.container.openFlags & 0x01) == 0
|
|
|
|
? ANIM_MAGIC_HANDS_MIDDLE
|
|
|
|
: ANIM_MAGIC_HANDS_GROUND;
|
|
|
|
animationRegisterAnimate(critter, anim, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, 0, critter->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
int actionFrame;
|
|
|
|
CacheEntry* cacheEntry;
|
|
|
|
Art* art = artLock(fid, &cacheEntry);
|
|
|
|
if (art == NULL) {
|
|
|
|
actionFrame = artGetActionFrame(art);
|
|
|
|
artUnlock(cacheEntry);
|
|
|
|
} else {
|
|
|
|
actionFrame = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item->frame != 1) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallback(critter, item, (AnimationCallback*)_obj_use_container, actionFrame);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (weaponAnimationCode != 0) {
|
|
|
|
animationRegisterTakeOutWeapon(critter, weaponAnimationCode, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (item->frame == 0 || item->frame == 1) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallback(critter, item, (AnimationCallback*)scriptsRequestLooting, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reg_anim_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Looks like the name is a little misleading, container can only be a
|
|
|
|
// critter, which is enforced by this function as well as at the call sites.
|
|
|
|
// Used to loot corpses, so probably should be something like actionLootCorpse.
|
|
|
|
// Check if it can be called with a living critter.
|
|
|
|
//
|
|
|
|
// 0x4123E8
|
|
|
|
int _action_loot_container(Object* critter, Object* container)
|
|
|
|
{
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(container->fid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-10-05 03:26:13 -07:00
|
|
|
// SFALL: Fix for trying to loot corpses with the "NoSteal" flag.
|
|
|
|
if (_critter_flag_check(container->pid, CRITTER_NO_STEAL)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
if (critter == gDude) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = FID_ANIM_TYPE(gDude->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim == ANIM_WALK || anim == ANIM_RUNNING) {
|
|
|
|
reg_anim_clear(gDude);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInCombat()) {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
|
|
|
animationRegisterMoveToObject(critter, container, critter->data.critter.combat.ap, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(critter == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (objectGetDistanceBetween(critter, container) < 5) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterMoveToObject(critter, container, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRunToObject(critter, container, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(critter, container, (AnimationCallback*)_is_next_to, -1);
|
|
|
|
animationRegisterCallback(critter, container, (AnimationCallback*)_check_scenery_ap_cost, -1);
|
|
|
|
animationRegisterCallback(critter, container, (AnimationCallback*)scriptsRequestLooting, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
return reg_anim_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x4124E0
|
|
|
|
int _action_skill_use(int skill)
|
|
|
|
{
|
|
|
|
if (skill == SKILL_SNEAK) {
|
|
|
|
reg_anim_clear(gDude);
|
|
|
|
dudeToggleState(DUDE_STATE_SNEAKING);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Inlined.
|
|
|
|
//
|
|
|
|
// 0x412500
|
|
|
|
static int _action_use_skill_in_combat_error(Object* critter)
|
|
|
|
{
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
|
|
|
|
if (critter == gDude) {
|
|
|
|
messageListItem.num = 902;
|
|
|
|
if (messageListGetItem(&gProtoMessageList, &messageListItem) == 1) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
// skill_use
|
|
|
|
// 0x41255C
|
|
|
|
int actionUseSkill(Object* a1, Object* a2, int skill)
|
|
|
|
{
|
|
|
|
switch (skill) {
|
|
|
|
case SKILL_FIRST_AID:
|
|
|
|
case SKILL_DOCTOR:
|
|
|
|
if (isInCombat()) {
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Uninline.
|
|
|
|
return _action_use_skill_in_combat_error(a1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SKILL_LOCKPICK:
|
|
|
|
if (isInCombat()) {
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Uninline.
|
|
|
|
return _action_use_skill_in_combat_error(a1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(a2->pid) != OBJ_TYPE_ITEM && PID_TYPE(a2->pid) != OBJ_TYPE_SCENERY) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
case SKILL_STEAL:
|
|
|
|
if (isInCombat()) {
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Uninline.
|
|
|
|
return _action_use_skill_in_combat_error(a1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(a2->pid) != OBJ_TYPE_ITEM && PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a2 == a1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
case SKILL_TRAPS:
|
|
|
|
if (isInCombat()) {
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Uninline.
|
|
|
|
return _action_use_skill_in_combat_error(a1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
case SKILL_SCIENCE:
|
|
|
|
case SKILL_REPAIR:
|
|
|
|
if (isInCombat()) {
|
2022-09-01 08:41:37 -07:00
|
|
|
// NOTE: Uninline.
|
|
|
|
return _action_use_skill_in_combat_error(a1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critterGetKillType(a2) == KILL_TYPE_ROBOT) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (critterGetKillType(a2) == KILL_TYPE_BRAHMIN
|
|
|
|
&& skill == SKILL_SCIENCE) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-08-12 04:55:36 -07:00
|
|
|
// SFALL: Science on critters patch.
|
|
|
|
if (1) {
|
|
|
|
int targetType = SCIENCE_REPAIR_TARGET_TYPE_DEFAULT;
|
|
|
|
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_SCIENCE_REPAIR_TARGET_TYPE_KEY, &targetType);
|
|
|
|
if (targetType == SCIENCE_REPAIR_TARGET_TYPE_DUDE) {
|
|
|
|
if (a2 == gDude) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (targetType == SCIENCE_REPAIR_TARGET_TYPE_ANYONE) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
case SKILL_SNEAK:
|
2022-10-24 23:04:42 -07:00
|
|
|
dudeToggleState(DUDE_STATE_SNEAKING);
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
debugPrint("\nskill_use: invalid skill used.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Performer is either dude, or party member who's best at the specified
|
|
|
|
// skill in entire party, and this skill is his/her own best.
|
|
|
|
Object* performer = gDude;
|
|
|
|
|
|
|
|
if (a1 == gDude) {
|
|
|
|
Object* partyMember = partyMemberGetBestInSkill(skill);
|
|
|
|
|
|
|
|
if (partyMember == gDude) {
|
|
|
|
partyMember = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only dude can perform stealing.
|
|
|
|
if (skill == SKILL_STEAL) {
|
|
|
|
partyMember = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (partyMember != NULL) {
|
|
|
|
if (partyMemberGetBestSkill(partyMember) != skill) {
|
|
|
|
partyMember = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (partyMember != NULL) {
|
|
|
|
performer = partyMember;
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = FID_ANIM_TYPE(partyMember->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim != ANIM_WALK && anim != ANIM_RUNNING) {
|
|
|
|
if (anim != ANIM_STAND) {
|
|
|
|
performer = gDude;
|
|
|
|
partyMember = NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
reg_anim_clear(partyMember);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (partyMember != NULL) {
|
2022-12-23 01:44:52 -08:00
|
|
|
bool isDude = false;
|
2022-05-19 01:51:26 -07:00
|
|
|
if (objectGetDistanceBetween(gDude, a2) <= 1) {
|
2022-12-23 01:44:52 -08:00
|
|
|
isDude = true;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
char* msg = skillsGetGenericResponse(partyMember, isDude);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
Rect rect;
|
|
|
|
if (textObjectAdd(partyMember, msg, 101, _colorTable[32747], _colorTable[0], &rect) == 0) {
|
|
|
|
tileWindowRefreshRect(&rect, gElevation);
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (isDude) {
|
2022-05-19 01:51:26 -07:00
|
|
|
performer = gDude;
|
|
|
|
partyMember = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (partyMember == NULL) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = FID_ANIM_TYPE(performer->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim == ANIM_WALK || anim == ANIM_RUNNING) {
|
|
|
|
reg_anim_clear(performer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInCombat()) {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
|
|
|
animationRegisterMoveToObject(performer, a2, performer->data.critter.combat.ap, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(a1 == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (a2 != gDude) {
|
|
|
|
if (objectGetDistanceBetween(performer, a2) >= 5) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRunToObject(performer, a2, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterMoveToObject(performer, a2, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(performer, a2, (AnimationCallback*)_is_next_to, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER && _critter_is_prone(a2)) ? ANIM_MAGIC_HANDS_GROUND : ANIM_MAGIC_HANDS_MIDDLE;
|
|
|
|
int fid = buildFid(OBJ_TYPE_CRITTER, performer->fid & 0xFFF, anim, 0, performer->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
CacheEntry* artHandle;
|
|
|
|
Art* art = artLock(fid, &artHandle);
|
|
|
|
if (art != NULL) {
|
|
|
|
artGetActionFrame(art);
|
|
|
|
artUnlock(artHandle);
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterAnimate(performer, anim, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
// TODO: Get rid of casts.
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallback3(performer, a2, (void*)skill, (AnimationCallback3*)_obj_use_skill_on, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
return reg_anim_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x412BC4
|
|
|
|
bool _is_hit_from_front(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
int diff = a1->rotation - a2->rotation;
|
|
|
|
if (diff < 0) {
|
|
|
|
diff = -diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
return diff != 0 && diff != 1 && diff != 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x412BEC
|
|
|
|
bool _can_see(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
int diff;
|
|
|
|
|
|
|
|
diff = a1->rotation - tileGetRotationTo(a1->tile, a2->tile);
|
|
|
|
if (diff < 0) {
|
|
|
|
diff = -diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
return diff == 0 || diff == 1 || diff == 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
// looks like it tries to change fall animation depending on object's current rotation
|
|
|
|
// 0x412C1C
|
|
|
|
int _pick_fall(Object* obj, int anim)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int rotation;
|
|
|
|
int tile_num;
|
|
|
|
int fid;
|
|
|
|
|
|
|
|
if (anim == ANIM_FALL_FRONT) {
|
|
|
|
rotation = obj->rotation;
|
|
|
|
for (i = 1; i < 3; i++) {
|
|
|
|
tile_num = tileGetTileInDirection(obj->tile, rotation, i);
|
|
|
|
if (_obj_blocking_at(obj, tile_num, obj->elevation) != NULL) {
|
|
|
|
anim = ANIM_FALL_BACK;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (anim == ANIM_FALL_BACK) {
|
2022-06-09 23:23:43 -07:00
|
|
|
rotation = (obj->rotation + 3) % ROTATION_COUNT;
|
2022-05-19 01:51:26 -07:00
|
|
|
for (i = 1; i < 3; i++) {
|
|
|
|
tile_num = tileGetTileInDirection(obj->tile, rotation, i);
|
|
|
|
if (_obj_blocking_at(obj, tile_num, obj->elevation) != NULL) {
|
|
|
|
anim = ANIM_FALL_FRONT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (anim == ANIM_FALL_FRONT) {
|
2022-07-29 06:04:05 -07:00
|
|
|
fid = buildFid(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (!artExists(fid)) {
|
|
|
|
anim = ANIM_FALL_BACK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return anim;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x412CE4
|
|
|
|
bool _action_explode_running()
|
|
|
|
{
|
2022-12-23 01:44:52 -08:00
|
|
|
return _action_in_explode;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// action_explode
|
|
|
|
// 0x412CF4
|
|
|
|
int actionExplode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6)
|
|
|
|
{
|
|
|
|
if (a6 && _action_in_explode) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
2022-05-21 08:22:03 -07:00
|
|
|
Attack* attack = (Attack*)internal_malloc(sizeof(*attack));
|
2022-05-19 01:51:26 -07:00
|
|
|
if (attack == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object* explosion;
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_MISC, 10, 0, 0, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (objectCreateWithFidPid(&explosion, fid, -1) == -1) {
|
|
|
|
internal_free(attack);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
objectHide(explosion, NULL);
|
2022-12-22 05:49:17 -08:00
|
|
|
explosion->flags |= OBJECT_NO_SAVE;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
objectSetLocation(explosion, tile, elevation, NULL);
|
|
|
|
|
|
|
|
Object* adjacentExplosions[ROTATION_COUNT];
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
2022-07-29 06:04:05 -07:00
|
|
|
int fid = buildFid(OBJ_TYPE_MISC, 10, 0, 0, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (objectCreateWithFidPid(&(adjacentExplosions[rotation]), fid, -1) == -1) {
|
|
|
|
while (--rotation >= 0) {
|
|
|
|
objectDestroy(adjacentExplosions[rotation], NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
objectDestroy(explosion, NULL);
|
|
|
|
internal_free(attack);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
objectHide(adjacentExplosions[rotation], NULL);
|
2022-12-22 05:49:17 -08:00
|
|
|
adjacentExplosions[rotation]->flags |= OBJECT_NO_SAVE;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
int adjacentTile = tileGetTileInDirection(tile, rotation, 1);
|
|
|
|
objectSetLocation(adjacentExplosions[rotation], adjacentTile, elevation, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
Object* critter = _obj_blocking_at(NULL, tile, elevation);
|
|
|
|
if (critter != NULL) {
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER || (critter->data.critter.combat.results & DAM_DEAD) != 0) {
|
2022-05-19 01:51:26 -07:00
|
|
|
critter = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
attackInit(attack, explosion, critter, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);
|
|
|
|
|
|
|
|
attack->tile = tile;
|
|
|
|
attack->attackerFlags = DAM_HIT;
|
|
|
|
|
|
|
|
gameUiDisable(1);
|
|
|
|
|
|
|
|
if (critter != NULL) {
|
|
|
|
if (reg_anim_clear(critter) == -2) {
|
|
|
|
debugPrint("Cannot clear target's animation for action_explode!\n");
|
|
|
|
}
|
|
|
|
attack->defenderDamage = _compute_explosion_damage(minDamage, maxDamage, critter, &(attack->defenderKnockback));
|
|
|
|
}
|
|
|
|
|
|
|
|
_compute_explosion_on_extras(attack, 0, 0, 1);
|
|
|
|
|
|
|
|
for (int index = 0; index < attack->extrasLength; index++) {
|
|
|
|
Object* critter = attack->extras[index];
|
|
|
|
if (reg_anim_clear(critter) == -2) {
|
|
|
|
debugPrint("Cannot clear extra's animation for action_explode!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
attack->extrasDamage[index] = _compute_explosion_damage(minDamage, maxDamage, critter, &(attack->extrasKnockback[index]));
|
|
|
|
}
|
|
|
|
|
|
|
|
attackComputeDeathFlags(attack);
|
|
|
|
|
|
|
|
if (a6) {
|
2022-12-23 01:44:52 -08:00
|
|
|
_action_in_explode = true;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
_register_priority(1);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterPlaySoundEffect(explosion, "whn1xxx1", 0);
|
|
|
|
animationRegisterUnsetFlag(explosion, OBJECT_HIDDEN, 0);
|
|
|
|
animationRegisterAnimateAndHide(explosion, ANIM_STAND, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
_show_damage(attack, 0, 1);
|
|
|
|
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterUnsetFlag(adjacentExplosions[rotation], OBJECT_HIDDEN, 0);
|
|
|
|
animationRegisterAnimateAndHide(adjacentExplosions[rotation], ANIM_STAND, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(explosion, 0, (AnimationCallback*)_combat_explode_scenery, -1);
|
|
|
|
animationRegisterHideObjectForced(explosion);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterHideObjectForced(adjacentExplosions[rotation]);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(attack, a5, (AnimationCallback*)_report_explosion, -1);
|
|
|
|
animationRegisterCallbackForced(NULL, NULL, (AnimationCallback*)_finished_explosion, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (reg_anim_end() == -1) {
|
2022-12-23 01:44:52 -08:00
|
|
|
_action_in_explode = false;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
objectDestroy(explosion, NULL);
|
|
|
|
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
|
|
|
objectDestroy(adjacentExplosions[rotation], NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
internal_free(attack);
|
|
|
|
|
|
|
|
gameUiEnable();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_show_damage_extras(attack);
|
|
|
|
} else {
|
|
|
|
if (critter != NULL) {
|
|
|
|
if ((attack->defenderFlags & DAM_DEAD) != 0) {
|
|
|
|
critterKill(critter, -1, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int index = 0; index < attack->extrasLength; index++) {
|
|
|
|
if ((attack->extrasFlags[index] & DAM_DEAD) != 0) {
|
|
|
|
critterKill(attack->extras[index], -1, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_report_explosion(attack, a5);
|
|
|
|
|
|
|
|
_combat_explode_scenery(explosion, NULL);
|
|
|
|
|
|
|
|
objectDestroy(explosion, NULL);
|
|
|
|
|
|
|
|
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
|
|
|
objectDestroy(adjacentExplosions[rotation], NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x413144
|
|
|
|
int _report_explosion(Attack* attack, Object* a2)
|
|
|
|
{
|
|
|
|
bool mainTargetWasDead;
|
|
|
|
if (attack->defender != NULL) {
|
|
|
|
mainTargetWasDead = (attack->defender->data.critter.combat.results & DAM_DEAD) != 0;
|
|
|
|
} else {
|
|
|
|
mainTargetWasDead = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool extrasWasDead[6];
|
|
|
|
for (int index = 0; index < attack->extrasLength; index++) {
|
|
|
|
extrasWasDead[index] = (attack->extras[index]->data.critter.combat.results & DAM_DEAD) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
attackComputeDeathFlags(attack);
|
|
|
|
_combat_display(attack);
|
|
|
|
_apply_damage(attack, false);
|
|
|
|
|
|
|
|
Object* anyDefender = NULL;
|
|
|
|
int xp = 0;
|
|
|
|
if (a2 != NULL) {
|
|
|
|
if (attack->defender != NULL && attack->defender != a2) {
|
|
|
|
if ((attack->defender->data.critter.combat.results & DAM_DEAD) != 0) {
|
|
|
|
if (a2 == gDude && !mainTargetWasDead) {
|
|
|
|
xp += critterGetExp(attack->defender);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_critter_set_who_hit_me(attack->defender, a2);
|
|
|
|
anyDefender = attack->defender;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int index = 0; index < attack->extrasLength; index++) {
|
|
|
|
Object* critter = attack->extras[index];
|
|
|
|
if (critter != a2) {
|
|
|
|
if ((critter->data.critter.combat.results & DAM_DEAD) != 0) {
|
|
|
|
if (a2 == gDude && !extrasWasDead[index]) {
|
|
|
|
xp += critterGetExp(critter);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_critter_set_who_hit_me(critter, a2);
|
|
|
|
|
|
|
|
if (anyDefender == NULL) {
|
|
|
|
anyDefender = critter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (anyDefender != NULL) {
|
|
|
|
if (!isInCombat()) {
|
|
|
|
STRUCT_664980 combat;
|
|
|
|
combat.attacker = anyDefender;
|
|
|
|
combat.defender = a2;
|
|
|
|
combat.actionPointsBonus = 0;
|
|
|
|
combat.accuracyBonus = 0;
|
|
|
|
combat.damageBonus = 0;
|
|
|
|
combat.minDamage = 0;
|
|
|
|
combat.maxDamage = INT_MAX;
|
|
|
|
combat.field_1C = 0;
|
|
|
|
scriptsRequestCombat(&combat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal_free(attack);
|
|
|
|
gameUiEnable();
|
|
|
|
|
|
|
|
if (a2 == gDude) {
|
|
|
|
_combat_give_exps(xp);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x4132C0
|
|
|
|
int _finished_explosion(Object* a1, Object* a2)
|
|
|
|
{
|
2022-12-23 01:44:52 -08:00
|
|
|
_action_in_explode = false;
|
2022-05-19 01:51:26 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate explosion damage applying threshold and resistances
|
|
|
|
// 0x4132CC
|
2022-12-23 01:44:52 -08:00
|
|
|
int _compute_explosion_damage(int min, int max, Object* defender, int* knockbackDistancePtr)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
2022-12-23 01:44:52 -08:00
|
|
|
int damage = randomBetween(min, max) - critterGetStat(defender, STAT_DAMAGE_THRESHOLD_EXPLOSION);
|
|
|
|
if (damage > 0) {
|
|
|
|
damage -= critterGetStat(defender, STAT_DAMAGE_RESISTANCE_EXPLOSION) * damage / 100;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (damage < 0) {
|
|
|
|
damage = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (knockbackDistancePtr != NULL) {
|
|
|
|
if ((defender->flags & OBJECT_MULTIHEX) == 0) {
|
|
|
|
*knockbackDistancePtr = damage / 10;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
return damage;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 0x413330
|
|
|
|
int actionTalk(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
if (a1 != gDude) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
int anim = FID_ANIM_TYPE(gDude->fid);
|
2022-05-19 01:51:26 -07:00
|
|
|
if (anim == ANIM_WALK || anim == ANIM_RUNNING) {
|
|
|
|
reg_anim_clear(gDude);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInCombat()) {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
|
|
|
animationRegisterMoveToObject(a1, a2, a1->data.critter.combat.ap, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(a1 == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (objectGetDistanceBetween(a1, a2) >= 9 || _combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL)) {
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterRunToObject(a1, a2, -1, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(a1, a2, (AnimationCallback*)_can_talk_to, -1);
|
|
|
|
animationRegisterCallback(a1, a2, (AnimationCallback*)_talk_to, -1);
|
2022-05-19 01:51:26 -07:00
|
|
|
return reg_anim_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x413420
|
|
|
|
int _can_talk_to(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
if (_combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL) || objectGetDistanceBetween(a1, a2) >= 9) {
|
|
|
|
if (a1 == gDude) {
|
|
|
|
// You cannot get there. (used in actions.c)
|
|
|
|
MessageListItem messageListItem;
|
|
|
|
messageListItem.num = 2000;
|
|
|
|
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
|
|
|
displayMonitorAddMessage(messageListItem.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x413488
|
|
|
|
int _talk_to(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
scriptsRequestDialog(a2);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x413494
|
2022-07-29 06:04:05 -07:00
|
|
|
void actionDamage(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
2022-05-21 08:22:03 -07:00
|
|
|
Attack* attack = (Attack*)internal_malloc(sizeof(*attack));
|
2022-05-19 01:51:26 -07:00
|
|
|
if (attack == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
Object* attacker;
|
|
|
|
if (objectCreateWithFidPid(&attacker, FID_0x20001F5, -1) == -1) {
|
2022-05-19 01:51:26 -07:00
|
|
|
internal_free(attack);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
objectHide(attacker, NULL);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-12-22 05:49:17 -08:00
|
|
|
attacker->flags |= OBJECT_NO_SAVE;
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
objectSetLocation(attacker, tile, elevation, NULL);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
Object* defender = _obj_blocking_at(NULL, tile, elevation);
|
|
|
|
attackInit(attack, attacker, defender, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);
|
2022-05-19 01:51:26 -07:00
|
|
|
attack->tile = tile;
|
|
|
|
attack->attackerFlags = DAM_HIT;
|
|
|
|
gameUiDisable(1);
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
if (defender != NULL) {
|
|
|
|
reg_anim_clear(defender);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
int damage;
|
|
|
|
if (bypassArmor) {
|
|
|
|
damage = maxDamage;
|
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
damage = _compute_dmg_damage(minDamage, maxDamage, defender, &(attack->defenderKnockback), damageType);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
attack->defenderDamage = damage;
|
|
|
|
}
|
|
|
|
|
|
|
|
attackComputeDeathFlags(attack);
|
|
|
|
|
|
|
|
if (animated) {
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
|
|
|
animationRegisterPlaySoundEffect(attacker, "whc1xxx1", 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
_show_damage(attack, gMaximumBloodDeathAnimations[damageType], 0);
|
2022-07-29 06:04:05 -07:00
|
|
|
animationRegisterCallbackForced(attack, NULL, (AnimationCallback*)_report_dmg, 0);
|
|
|
|
animationRegisterHideObjectForced(attacker);
|
2022-05-19 01:51:26 -07:00
|
|
|
|
|
|
|
if (reg_anim_end() == -1) {
|
2022-07-29 06:04:05 -07:00
|
|
|
objectDestroy(attacker, NULL);
|
2022-05-19 01:51:26 -07:00
|
|
|
internal_free(attack);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
2022-07-29 06:04:05 -07:00
|
|
|
if (defender != NULL) {
|
2022-05-19 01:51:26 -07:00
|
|
|
if ((attack->defenderFlags & DAM_DEAD) != 0) {
|
2022-07-29 06:04:05 -07:00
|
|
|
critterKill(defender, -1, 1);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
// NOTE: Uninline.
|
|
|
|
_report_dmg(attack, NULL);
|
|
|
|
|
|
|
|
objectDestroy(attacker, NULL);
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
gameUiEnable();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x41363C
|
|
|
|
int _report_dmg(Attack* attack, Object* a2)
|
|
|
|
{
|
|
|
|
_combat_display(attack);
|
|
|
|
_apply_damage(attack, false);
|
|
|
|
internal_free(attack);
|
|
|
|
gameUiEnable();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate damage by applying threshold and resistances.
|
|
|
|
//
|
|
|
|
// 0x413660
|
2022-12-23 01:44:52 -08:00
|
|
|
int _compute_dmg_damage(int min, int max, Object* obj, int* knockbackDistancePtr, int damageType)
|
2022-05-19 01:51:26 -07:00
|
|
|
{
|
2022-10-05 03:06:49 -07:00
|
|
|
if (!_critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) {
|
2022-12-23 01:44:52 -08:00
|
|
|
knockbackDistancePtr = NULL;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
int damage = randomBetween(min, max) - critterGetStat(obj, STAT_DAMAGE_THRESHOLD + damageType);
|
|
|
|
if (damage > 0) {
|
|
|
|
damage -= critterGetStat(obj, STAT_DAMAGE_RESISTANCE + damageType) * damage / 100;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (damage < 0) {
|
|
|
|
damage = 0;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
if (knockbackDistancePtr != NULL) {
|
2022-06-08 10:38:46 -07:00
|
|
|
if ((obj->flags & OBJECT_MULTIHEX) == 0 && damageType != DAMAGE_TYPE_ELECTRICAL) {
|
2022-12-23 01:44:52 -08:00
|
|
|
*knockbackDistancePtr = damage / 10;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 01:44:52 -08:00
|
|
|
return damage;
|
2022-05-19 01:51:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 0x4136EC
|
|
|
|
bool actionCheckPush(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
// Cannot push anything but critters.
|
2022-07-29 06:04:05 -07:00
|
|
|
if (FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) {
|
2022-05-19 01:51:26 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cannot push itself.
|
|
|
|
if (a1 == a2) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cannot push inactive critters.
|
|
|
|
if (!critterIsActive(a2)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_action_can_talk_to(a1, a2) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can only push critters that have push handler.
|
|
|
|
if (!scriptHasProc(a2->sid, SCRIPT_PROC_PUSH)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInCombat()) {
|
|
|
|
if (a2->data.critter.combat.team == a1->data.critter.combat.team
|
|
|
|
&& a2 == a1->data.critter.combat.whoHitMe) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Check.
|
|
|
|
Object* whoHitMe = a2->data.critter.combat.whoHitMe;
|
|
|
|
if (whoHitMe != NULL
|
|
|
|
&& whoHitMe->data.critter.combat.team == a1->data.critter.combat.team) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x413790
|
|
|
|
int actionPush(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
if (!actionCheckPush(a1, a2)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sid;
|
|
|
|
if (_obj_sid(a2, &sid) == 0) {
|
|
|
|
scriptSetObjects(sid, a1, a2);
|
|
|
|
scriptExecProc(sid, SCRIPT_PROC_PUSH);
|
|
|
|
|
|
|
|
bool scriptOverrides = false;
|
|
|
|
|
|
|
|
Script* scr;
|
|
|
|
if (scriptGetScript(sid, &scr) != -1) {
|
|
|
|
scriptOverrides = scr->scriptOverrides;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scriptOverrides) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int rotation = tileGetRotationTo(a1->tile, a2->tile);
|
|
|
|
int tile;
|
|
|
|
do {
|
|
|
|
tile = tileGetTileInDirection(a2->tile, rotation, 1);
|
|
|
|
if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tile = tileGetTileInDirection(a2->tile, (rotation + 1) % ROTATION_COUNT, 1);
|
|
|
|
if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tile = tileGetTileInDirection(a2->tile, (rotation + 5) % ROTATION_COUNT, 1);
|
|
|
|
if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tile = tileGetTileInDirection(a2->tile, (rotation + 2) % ROTATION_COUNT, 1);
|
|
|
|
if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tile = tileGetTileInDirection(a2->tile, (rotation + 4) % ROTATION_COUNT, 1);
|
|
|
|
if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tile = tileGetTileInDirection(a2->tile, (rotation + 3) % ROTATION_COUNT, 1);
|
|
|
|
if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
int actionPoints;
|
|
|
|
if (isInCombat()) {
|
|
|
|
actionPoints = a2->data.critter.combat.ap;
|
|
|
|
} else {
|
|
|
|
actionPoints = -1;
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:04:05 -07:00
|
|
|
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
|
|
|
|
animationRegisterRotateToTile(a2, tile);
|
|
|
|
animationRegisterMoveToTile(a2, tile, a2->elevation, actionPoints, 0);
|
2022-05-19 01:51:26 -07:00
|
|
|
return reg_anim_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns -1 if can't see there (can't find a path there)
|
|
|
|
// Returns -2 if it's too far (> 12 tiles).
|
|
|
|
//
|
|
|
|
// 0x413970
|
|
|
|
int _action_can_talk_to(Object* a1, Object* a2)
|
|
|
|
{
|
|
|
|
if (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_sight_blocking_at) == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tileDistanceBetween(a1->tile, a2->tile) > 12) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2022-09-23 05:43:44 -07:00
|
|
|
|
2022-12-22 08:54:04 -08:00
|
|
|
static int hideProjectile(void* a1, void* a2)
|
|
|
|
{
|
|
|
|
Object* projectile = reinterpret_cast<Object*>(a2);
|
|
|
|
|
|
|
|
Rect rect;
|
|
|
|
if (objectHide(projectile, &rect) == 0) {
|
|
|
|
tileWindowRefreshRect(&rect, projectile->elevation);
|
|
|
|
}
|
|
|
|
|
|
|
|
projectile->flags |= OBJECT_NO_SAVE;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-09-23 05:43:44 -07:00
|
|
|
} // namespace fallout
|