Add explosions improvements (#29)

This commit is contained in:
Alexander Batalov 2022-08-05 17:43:00 +03:00
parent 3ccb087d20
commit 5170948588
12 changed files with 404 additions and 34 deletions

View File

@ -316,8 +316,32 @@ void _show_damage_to_object(Object* a1, int damage, int flags, Object* weapon, b
sfx_name = sfxBuildCharName(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED);
animationRegisterPlaySoundEffect(a1, sfx_name, a10);
// 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).
animationRegisterSetLightIntensity(a1, 2, 65536, 0);
}
animationRegisterAnimate(a1, anim, 0);
// 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.
animationRegisterSetLightIntensity(a1, 0, 0, -1);
}
int randomDistance = randomBetween(2, 5);
int randomRotation = randomBetween(0, 5);
@ -692,7 +716,8 @@ int _action_ranged(Attack* attack, int anim)
bool isGrenade = false;
if (anim == ANIM_THROW_ANIM) {
if (damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) {
// SFALL
if (damageType == explosionGetDamageType() || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) {
isGrenade = true;
}
} else {
@ -750,7 +775,12 @@ int _action_ranged(Attack* attack, int anim)
objectHide(projectile, NULL);
// SFALL
if (explosionEmitsLight() && projectile->lightIntensity == 0) {
objectSetLight(projectile, projectileProto->item.lightDistance, projectileProto->item.lightIntensity, NULL);
} else {
objectSetLight(projectile, 9, projectile->lightIntensity, NULL);
}
int projectileOrigin = _combat_bullet_start(attack->attacker, attack->defender);
objectSetLocation(projectile, projectileOrigin, attack->attacker->elevation, NULL);
@ -774,7 +804,8 @@ int _action_ranged(Attack* attack, int anim)
v24 = attack->tile;
}
if (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION) {
// SFALL
if (isGrenade || damageType == explosionGetDamageType()) {
if ((attack->attackerFlags & DAM_DROP) == 0) {
int explosionFrmId;
if (isGrenade) {
@ -793,6 +824,12 @@ int _action_ranged(Attack* attack, int anim)
explosionFrmId = 10;
}
// SFALL
int explosionFrmIdOverride = explosionGetFrm();
if (explosionFrmIdOverride != -1) {
explosionFrmId = explosionFrmIdOverride;
}
if (isGrenade) {
animationRegisterSetFid(projectile, weaponFid, -1);
}
@ -803,9 +840,23 @@ int _action_ranged(Attack* attack, int anim)
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender);
animationRegisterPlaySoundEffect(projectile, sfx, 0);
// SFALL
if (explosionEmitsLight()) {
animationRegisterAnimate(projectile, ANIM_STAND, 0);
// 0xFFFF0008
// - distance: 8
// - intensity: 65535
animationRegisterSetLightIntensity(projectile, 8, 65536, 0);
} else {
animationRegisterAnimateAndHide(projectile, ANIM_STAND, 0);
}
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
// SFALL
int startRotation;
int endRotation;
explosionGetPattern(&startRotation, &endRotation);
for (int rotation = startRotation; rotation < endRotation; rotation++) {
if (objectCreateWithFidPid(&(neighboors[rotation]), explosionFid, -1) != -1) {
objectHide(neighboors[rotation], NULL);
@ -864,7 +915,8 @@ int _action_ranged(Attack* attack, int anim)
}
}
if (projectile != NULL && (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION)) {
// SFALL
if (projectile != NULL && (isGrenade || damageType == explosionGetDamageType())) {
animationRegisterHideObjectForced(projectile);
} else if (anim == ANIM_THROW_ANIM && projectile != NULL) {
animationRegisterSetFid(projectile, weaponFid, -1);

View File

@ -64,6 +64,10 @@ typedef enum AnimationKind {
ANIM_KIND_26 = 26,
ANIM_KIND_27 = 27,
ANIM_KIND_NOOP = 28,
// New animation to update both light distance and intensity. Required to
// impement Sfall's explosion light effects without resorting to hackery.
ANIM_KIND_SET_LIGHT_INTENSITY,
} AnimationKind;
typedef enum AnimationSequenceFlags {
@ -197,6 +201,9 @@ typedef struct AnimationDescription {
// ANIM_KIND_CALLBACK3
void* param3;
// ANIM_KIND_SET_LIGHT_INTENSITY
int lightIntensity;
};
CacheEntry* artCacheKey;
} AnimationDescription;
@ -1527,6 +1534,11 @@ static int animationRunSequence(int animationSequenceIndex)
tileWindowRefreshRect(&rect, animationDescription->owner->elevation);
rc = _anim_set_continue(animationSequenceIndex, 0);
break;
case ANIM_KIND_SET_LIGHT_INTENSITY:
objectSetLight(animationDescription->owner, animationDescription->lightDistance, animationDescription->lightIntensity, &rect);
tileWindowRefreshRect(&rect, animationDescription->owner->elevation);
rc = _anim_set_continue(animationSequenceIndex, 0);
break;
case ANIM_KIND_20:
rc = _anim_move_on_stairs(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex);
break;
@ -3330,3 +3342,24 @@ static unsigned int animationComputeTicksPerFrame(Object* object, int fid)
return 1000 / fps;
}
int animationRegisterSetLightIntensity(Object* owner, int lightDistance, int lightIntensity, int delay)
{
if (_check_registry(owner) == -1) {
_anim_cleanup();
return -1;
}
AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]);
AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]);
animationDescription->kind = ANIM_KIND_SET_LIGHT_INTENSITY;
animationDescription->artCacheKey = NULL;
animationDescription->owner = owner;
animationDescription->lightDistance = lightDistance;
animationDescription->lightIntensity = lightIntensity;
animationDescription->delay = delay;
gAnimationDescriptionCurrentIndex++;
return 0;
}

View File

@ -154,4 +154,6 @@ void _dude_stand(Object* obj, int rotation, int fid);
void _dude_standup(Object* a1);
void animationStop();
int animationRegisterSetLightIntensity(Object* owner, int lightDistance, int lightIntensity, int delay);
#endif /* ANIMATION_H */

View File

@ -3430,6 +3430,9 @@ int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation)
_critter_set_who_hit_me(a1, a2);
}
// SFALL
explosionSettingsReset();
_combat_call_display = 1;
_combat_cleanup_enabled = 1;
aiInfoSetLastTarget(a1, a2);
@ -3707,7 +3710,8 @@ static int attackCompute(Attack* attack)
bool isGrenade = false;
int damageType = weaponGetDamageType(attack->attacker, attack->weapon);
if (anim == ANIM_THROW_ANIM && (damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP)) {
// SFALL
if (anim == ANIM_THROW_ANIM && (damageType == explosionGetDamageType() || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP)) {
isGrenade = true;
}
@ -3835,7 +3839,8 @@ static int attackCompute(Attack* attack)
}
}
if ((damageType == DAMAGE_TYPE_EXPLOSION || isGrenade) && ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0)) {
// SFALL
if ((damageType == explosionGetDamageType() || isGrenade) && ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0)) {
_compute_explosion_on_extras(attack, 0, isGrenade, 0);
} else {
if ((attack->attackerFlags & DAM_EXPLODE) != 0) {
@ -3882,7 +3887,10 @@ void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4
int rotation = 0;
int v5 = -1;
int v19 = tile;
while (attack->extrasLength < 6) {
// SFALL
int maxTargets = explosionGetMaxTargets();
while (attack->extrasLength < maxTargets) {
if (v22 != 0 && (v5 == -1 || (v5 = tileGetTileInDirection(v5, rotation, 1)) != v19)) {
v20++;
if (v20 % v22 == 0) {

View File

@ -1399,7 +1399,10 @@ char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* ta
v6 = 1;
}
if (effectTypeCode != 'H' || target == NULL || weaponIsGrenade(weapon)) {
int damageType = weaponGetDamageType(NULL, weapon);
// SFALL
if (effectTypeCode != 'H' || target == NULL || damageType == explosionGetDamageType() || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) {
materialCode = 'X';
} else {
const int type = FID_TYPE(target->fid);

View File

@ -3277,7 +3277,7 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType)
}
}
}
} else if (item->pid == PROTO_ID_DYNAMITE_II || item->pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) {
} else if (explosiveIsActiveExplosive(item->pid)) {
_dropped_explosive = 1;
_obj_drop(v41, item);
} else {

View File

@ -30,6 +30,7 @@
#include <string.h>
#include <algorithm>
#include <vector>
#define ADDICTION_COUNT (9)
@ -62,6 +63,10 @@ static void booksInitCustom();
static void booksAdd(int bookPid, int messageId, int skill);
static void booksExit();
static void explosionsInit();
static void explosionsReset();
static void explosionsExit();
typedef struct DrugDescription {
int drugPid;
int gvar;
@ -74,6 +79,13 @@ typedef struct BookDescription {
int skill;
} BookDescription;
typedef struct ExplosiveDescription {
int pid;
int activePid;
int minDamage;
int maxDamage;
} ExplosiveDescription;
// 0x509FFC
static char _aItem_1[] = "<item>";
@ -153,6 +165,20 @@ static Object* _wd_obj;
static int _wd_gvar;
static std::vector<BookDescription> gBooks;
static bool gExplosionEmitsLight;
static int gGrenadeExplosionRadius;
static int gRocketExplosionRadius;
static int gDynamiteMinDamage;
static int gDynamiteMaxDamage;
static int gPlasticExplosiveMinDamage;
static int gPlasticExplosiveMaxDamage;
static std::vector<ExplosiveDescription> gExplosives;
static int gExplosionStartRotation;
static int gExplosionEndRotation;
static int gExplosionFrm;
static int gExplosionRadius;
static int gExplosionDamageType;
static int gExplosionMaxTargets;
// 0x4770E0
int itemsInit()
@ -170,6 +196,7 @@ int itemsInit()
// SFALL
booksInit();
explosionsInit();
return 0;
}
@ -177,7 +204,8 @@ int itemsInit()
// 0x477144
void itemsReset()
{
return;
// SFALL
explosionsReset();
}
// 0x477148
@ -187,6 +215,7 @@ void itemsExit()
// SFALL
booksExit();
explosionsExit();
}
// NOTE: Collapsed.
@ -2002,13 +2031,23 @@ int _item_w_area_damage_radius(Object* weapon, int hitMode)
// 0x479180
int _item_w_grenade_dmg_radius(Object* weapon)
{
return 2;
// SFALL
if (gExplosionRadius != -1) {
return gExplosionRadius;
}
return gGrenadeExplosionRadius;
}
// 0x479188
int _item_w_rocket_dmg_radius(Object* weapon)
{
return 3;
// SFALL
if (gExplosionRadius != -1) {
return gExplosionRadius;
}
return gRocketExplosionRadius;
}
// 0x479190
@ -3353,3 +3392,221 @@ bool booksGetInfo(int bookPid, int* messageIdPtr, int* skillPtr)
}
return false;
}
static void explosionsInit()
{
gExplosionEmitsLight = false;
configGetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_EXPLOSION_EMITS_LIGHT_KEY, &gExplosionEmitsLight);
explosionsReset();
}
static void explosionsReset()
{
gGrenadeExplosionRadius = 2;
gRocketExplosionRadius = 3;
gDynamiteMinDamage = 30;
gDynamiteMaxDamage = 50;
gPlasticExplosiveMinDamage = 40;
gPlasticExplosiveMaxDamage = 80;
if (configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DYNAMITE_MAX_DAMAGE_KEY, &gDynamiteMaxDamage)) {
gDynamiteMaxDamage = std::clamp(gDynamiteMaxDamage, 0, 9999);
}
if (configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DYNAMITE_MIN_DAMAGE_KEY, &gDynamiteMinDamage)) {
gDynamiteMinDamage = std::clamp(gDynamiteMinDamage, 0, gDynamiteMaxDamage);
}
if (configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PLASTIC_EXPLOSIVE_MAX_DAMAGE_KEY, &gPlasticExplosiveMaxDamage)) {
gPlasticExplosiveMaxDamage = std::clamp(gPlasticExplosiveMaxDamage, 0, 9999);
}
if (configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PLASTIC_EXPLOSIVE_MIN_DAMAGE_KEY, &gPlasticExplosiveMinDamage)) {
gPlasticExplosiveMinDamage = std::clamp(gPlasticExplosiveMinDamage, 0, gPlasticExplosiveMaxDamage);
}
gExplosives.clear();
explosionSettingsReset();
}
static void explosionsExit()
{
gExplosives.clear();
}
bool explosionEmitsLight()
{
return gExplosionEmitsLight;
}
void weaponSetGrenadeExplosionRadius(int value)
{
gGrenadeExplosionRadius = value;
}
void weaponSetRocketExplosionRadius(int value)
{
gRocketExplosionRadius = value;
}
void explosiveAdd(int pid, int activePid, int minDamage, int maxDamage)
{
ExplosiveDescription explosiveDescription;
explosiveDescription.pid = pid;
explosiveDescription.activePid = activePid;
explosiveDescription.minDamage = minDamage;
explosiveDescription.maxDamage = maxDamage;
gExplosives.push_back(std::move(explosiveDescription));
}
bool explosiveIsExplosive(int pid)
{
if (pid == PROTO_ID_DYNAMITE_I) return true;
if (pid == PROTO_ID_PLASTIC_EXPLOSIVES_I) return true;
for (const auto& explosive : gExplosives) {
if (explosive.pid == pid) return true;
}
return false;
}
bool explosiveIsActiveExplosive(int pid)
{
if (pid == PROTO_ID_DYNAMITE_II) return true;
if (pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) return true;
for (const auto& explosive : gExplosives) {
if (explosive.activePid == pid) return true;
}
return false;
}
bool explosiveActivate(int* pidPtr)
{
if (*pidPtr == PROTO_ID_DYNAMITE_I) {
*pidPtr = PROTO_ID_DYNAMITE_II;
return true;
}
if (*pidPtr == PROTO_ID_PLASTIC_EXPLOSIVES_I) {
*pidPtr = PROTO_ID_PLASTIC_EXPLOSIVES_II;
return true;
}
for (const auto& explosive : gExplosives) {
if (explosive.pid == *pidPtr) {
*pidPtr = explosive.activePid;
return true;
}
}
return false;
}
bool explosiveSetDamage(int pid, int minDamage, int maxDamage)
{
if (pid == PROTO_ID_DYNAMITE_I) {
gDynamiteMinDamage = minDamage;
gDynamiteMaxDamage = maxDamage;
return true;
}
if (pid == PROTO_ID_PLASTIC_EXPLOSIVES_I) {
gPlasticExplosiveMinDamage = minDamage;
gPlasticExplosiveMaxDamage = maxDamage;
return true;
}
// NOTE: For unknown reason this function do not update custom explosives
// damage. Since we're after compatibility (at least at this time), the
// only way to follow this behaviour.
return false;
}
bool explosiveGetDamage(int pid, int* minDamagePtr, int* maxDamagePtr)
{
if (pid == PROTO_ID_DYNAMITE_I) {
*minDamagePtr = gDynamiteMinDamage;
*maxDamagePtr = gDynamiteMaxDamage;
return true;
}
if (pid == PROTO_ID_PLASTIC_EXPLOSIVES_I) {
*minDamagePtr = gPlasticExplosiveMinDamage;
*maxDamagePtr = gPlasticExplosiveMaxDamage;
return true;
}
for (const auto& explosive : gExplosives) {
if (explosive.pid == pid) {
*minDamagePtr = explosive.minDamage;
*maxDamagePtr = explosive.maxDamage;
return true;
}
}
return false;
}
void explosionSettingsReset()
{
gExplosionStartRotation = 0;
gExplosionEndRotation = ROTATION_COUNT;
gExplosionFrm = -1;
gExplosionRadius = -1;
gExplosionDamageType = DAMAGE_TYPE_EXPLOSION;
gExplosionMaxTargets = 6;
}
void explosionGetPattern(int* startRotationPtr, int* endRotationPtr)
{
*startRotationPtr = gExplosionStartRotation;
*endRotationPtr = gExplosionEndRotation;
}
void explosionSetPattern(int startRotation, int endRotation)
{
gExplosionStartRotation = startRotation;
gExplosionEndRotation = endRotation;
}
int explosionGetFrm()
{
return gExplosionFrm;
}
void explosionSetFrm(int frm)
{
gExplosionFrm = frm;
}
void explosionSetRadius(int radius)
{
gExplosionRadius = radius;
}
int explosionGetDamageType()
{
return gExplosionDamageType;
}
void explosionSetDamageType(int damageType)
{
gExplosionDamageType = damageType;
}
int explosionGetMaxTargets()
{
return gExplosionMaxTargets;
}
void explosionSetMaxTargets(int maxTargets)
{
gExplosionMaxTargets = maxTargets;
}

View File

@ -124,5 +124,24 @@ int itemGetMoney(Object* obj);
int itemSetMoney(Object* obj, int a2);
bool booksGetInfo(int bookPid, int* messageIdPtr, int* skillPtr);
bool explosionEmitsLight();
void weaponSetGrenadeExplosionRadius(int value);
void weaponSetRocketExplosionRadius(int value);
void explosiveAdd(int pid, int activePid, int minDamage, int maxDamage);
bool explosiveIsExplosive(int pid);
bool explosiveIsActiveExplosive(int pid);
bool explosiveActivate(int* pidPtr);
bool explosiveSetDamage(int pid, int minDamage, int maxDamage);
bool explosiveGetDamage(int pid, int* minDamagePtr, int* maxDamagePtr);
void explosionSettingsReset();
void explosionGetPattern(int* startRotationPtr, int* endRotationPtr);
void explosionSetPattern(int startRotation, int endRotation);
int explosionGetFrm();
void explosionSetFrm(int frm);
void explosionSetRadius(int radius);
int explosionGetDamageType();
void explosionSetDamageType(int damageType);
int explosionGetMaxTargets();
void explosionSetMaxTargets(int maxTargets);
#endif /* ITEM_H */

View File

@ -2090,7 +2090,8 @@ int _obj_inven_free(Inventory* inventory)
bool _obj_action_can_use(Object* obj)
{
int pid = obj->pid;
if (pid != PROTO_ID_LIT_FLARE && pid != PROTO_ID_DYNAMITE_II && pid != PROTO_ID_PLASTIC_EXPLOSIVES_II) {
// SFALL
if (pid != PROTO_ID_LIT_FLARE && !explosiveIsActiveExplosive(pid)) {
return _proto_action_can_use(pid);
} else {
return false;

View File

@ -843,10 +843,8 @@ static int _obj_use_explosive(Object* explosive)
MessageListItem messageListItem;
int pid = explosive->pid;
if (pid != PROTO_ID_DYNAMITE_I
&& pid != PROTO_ID_PLASTIC_EXPLOSIVES_I
&& pid != PROTO_ID_DYNAMITE_II
&& pid != PROTO_ID_PLASTIC_EXPLOSIVES_II) {
// SFALL
if (!explosiveIsExplosive(pid)) {
return -1;
}
@ -865,11 +863,8 @@ static int _obj_use_explosive(Object* explosive)
displayMonitorAddMessage(messageListItem.text);
}
if (pid == PROTO_ID_DYNAMITE_I) {
explosive->pid = PROTO_ID_DYNAMITE_II;
} else if (pid == PROTO_ID_PLASTIC_EXPLOSIVES_I) {
explosive->pid = PROTO_ID_PLASTIC_EXPLOSIVES_II;
}
// SFALL
explosiveActivate(&(explosive->pid));
int delay = 10 * seconds;
@ -1048,7 +1043,8 @@ int _protinst_use_item(Object* critter, Object* item)
// 0x49BFE8
static int _protinstTestDroppedExplosive(Object* a1)
{
if (a1->pid == PROTO_ID_DYNAMITE_II || a1->pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) {
// SFALL
if (explosiveIsActiveExplosive(a1->pid)) {
Attack attack;
attackInit(&attack, gDude, 0, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);
attack.attackerFlags = DAM_HIT;

View File

@ -478,15 +478,9 @@ static int _queue_do_explosion_(Object* explosive, bool a2)
int maxDamage;
int minDamage;
if (explosive->pid == PROTO_ID_DYNAMITE_I || explosive->pid == PROTO_ID_DYNAMITE_II) {
// Dynamite
minDamage = 30;
maxDamage = 50;
} else {
// Plastic explosive
minDamage = 40;
maxDamage = 80;
}
// SFALL
explosiveGetDamage(explosive->pid, &minDamage, &maxDamage);
// FIXME: I guess this is a little bit wrong, dude can never be null, I
// guess it needs to check if owner is dude.

View File

@ -35,6 +35,11 @@
#define SFALL_CONFIG_BURST_MOD_CENTER_DIVISOR_KEY "ComputeSpray_CenterDiv"
#define SFALL_CONFIG_BURST_MOD_TARGET_MULTIPLIER_KEY "ComputeSpray_TargetMult"
#define SFALL_CONFIG_BURST_MOD_TARGET_DIVISOR_KEY "ComputeSpray_TargetDiv"
#define SFALL_CONFIG_DYNAMITE_MIN_DAMAGE_KEY "Dynamite_DmgMin"
#define SFALL_CONFIG_DYNAMITE_MAX_DAMAGE_KEY "Dynamite_DmgMax"
#define SFALL_CONFIG_PLASTIC_EXPLOSIVE_MIN_DAMAGE_KEY "PlasticExplosive_DmgMin"
#define SFALL_CONFIG_PLASTIC_EXPLOSIVE_MAX_DAMAGE_KEY "PlasticExplosive_DmgMax"
#define SFALL_CONFIG_EXPLOSION_EMITS_LIGHT_KEY "ExplosionsEmitLight"
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3