Skip to content

Commit aadc5e2

Browse files
authored
Custom logic additions (#532)
* Custom logics - add several new moddable properties stopOnDamage thinkerDelay approachOffset approachVariation * Add collisionWidth and collisionHeight * Karjala - Projectile Offset Code Tidy up If the property is not set, keep the default value for the y offset Convert values to fixed point when the logic is set up (to be consistent with how the other custom logic properties are done) Transform the coordinates based on the actor's yaw Also flip the sign of the Y axis because this will be easier for users * Move floatToAngle conversions to the logic setup function for consistency
1 parent f3c82ef commit aadc5e2

File tree

6 files changed

+137
-51
lines changed

6 files changed

+137
-51
lines changed

TheForceEngine/TFE_DarkForces/Actor/actor.cpp

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ namespace TFE_DarkForces
303303
attackMod->meleeRate = FIXED(230);
304304
attackMod->attackFlags = ATTFLAG_RANGED | ATTFLAG_LIT_RNG;
305305

306+
// Why is this being returned? This function maybe should be a void?
306307
return attackMod->fireOffset.y;
307308
}
308309

@@ -1029,9 +1030,14 @@ namespace TFE_DarkForces
10291030
}
10301031

10311032
attackMod->anim.state = STATE_ANIMATE1;
1032-
ProjectileLogic* proj = (ProjectileLogic*)createProjectile(attackMod->projType, obj->sector, obj->posWS.x, attackMod->fireOffset.y + obj->posWS.y, obj->posWS.z, obj);
1033-
sound_playCued(attackMod->attackPrimSndSrc, obj->posWS);
1033+
vec3_fixed fireOffset = {};
1034+
1035+
// Calculate the X,Z fire offsets based on where the enemy is facing. It doesn't matter for Y.
1036+
transformFireOffsets(obj->yaw, &attackMod->fireOffset, &fireOffset);
10341037

1038+
ProjectileLogic* proj = (ProjectileLogic*)createProjectile(attackMod->projType, obj->sector, fireOffset.x + obj->posWS.x, fireOffset.y + obj->posWS.y, fireOffset.z + obj->posWS.z, obj);
1039+
sound_playCued(attackMod->attackPrimSndSrc, obj->posWS);
1040+
10351041
proj->prevColObj = obj;
10361042
proj->prevObj = obj;
10371043
proj->excludeObj = obj;
@@ -1048,22 +1054,25 @@ namespace TFE_DarkForces
10481054
vec3_fixed target = { s_playerObject->posWS.x, s_eyePos.y + ONE_16, s_playerObject->posWS.z };
10491055
proj_aimArcing(proj, target, proj->speed);
10501056

1051-
if (attackMod->fireOffset.x | attackMod->fireOffset.z)
1052-
{
1053-
proj->delta.x = attackMod->fireOffset.x;
1054-
proj->delta.z = attackMod->fireOffset.z;
1055-
proj_handleMovement(proj);
1056-
}
1057+
// This code was never hit in original DF because x and z fire offsets were always 0
1058+
// It causes strange collision effects to happen, so commenting out.
1059+
//if (fireOffset.x | fireOffset.z)
1060+
//{
1061+
// proj->delta.x = fireOffset.x;
1062+
// proj->delta.z = fireOffset.z;
1063+
// proj_handleMovement(proj);
1064+
//}
10571065
}
10581066
else
10591067
{
10601068
// Handle x and z fire offset.
1061-
if (attackMod->fireOffset.x | attackMod->fireOffset.z)
1062-
{
1063-
proj->delta.x = attackMod->fireOffset.x;
1064-
proj->delta.z = attackMod->fireOffset.z;
1065-
proj_handleMovement(proj);
1066-
}
1069+
// Commenting out - see note above
1070+
//if (fireOffset.x | fireOffset.z)
1071+
//{
1072+
// proj->delta.x = fireOffset.x;
1073+
// proj->delta.z = fireOffset.z;
1074+
// proj_handleMovement(proj);
1075+
//}
10671076

10681077
// Aim at the target.
10691078
vec3_fixed target = { s_eyePos.x, s_eyePos.y + ONE_16, s_eyePos.z };
@@ -1096,7 +1105,13 @@ namespace TFE_DarkForces
10961105
}
10971106

10981107
attackMod->anim.state = STATE_ANIMATE2;
1099-
ProjectileLogic* proj = (ProjectileLogic*)createProjectile(attackMod->projType, obj->sector, obj->posWS.x, attackMod->fireOffset.y + obj->posWS.y, obj->posWS.z, obj);
1108+
1109+
vec3_fixed fireOffset = {};
1110+
1111+
// Calculate the fire offsets based on where the enemy is facing. It doesn't matter for Y.
1112+
transformFireOffsets(obj->yaw, &attackMod->fireOffset, &fireOffset);
1113+
1114+
ProjectileLogic* proj = (ProjectileLogic*)createProjectile(attackMod->projType, obj->sector, fireOffset.x + obj->posWS.x, fireOffset.y + obj->posWS.y, fireOffset.z + obj->posWS.z, obj);
11001115
sound_playCued(attackMod->attackPrimSndSrc, obj->posWS);
11011116
proj->prevColObj = obj;
11021117
proj->excludeObj = obj;
@@ -1110,21 +1125,23 @@ namespace TFE_DarkForces
11101125
vec3_fixed target = { s_playerObject->posWS.x, s_eyePos.y + ONE_16, s_playerObject->posWS.z };
11111126
proj_aimArcing(proj, target, proj->speed);
11121127

1113-
if (attackMod->fireOffset.x | attackMod->fireOffset.z)
1114-
{
1115-
proj->delta.x = attackMod->fireOffset.x;
1116-
proj->delta.z = attackMod->fireOffset.z;
1117-
proj_handleMovement(proj);
1118-
}
1128+
// Commenting out - see note above
1129+
//if (fireOffset.x | fireOffset.z)
1130+
//{
1131+
// proj->delta.x = fireOffset.x;
1132+
// proj->delta.z = fireOffset.z;
1133+
// proj_handleMovement(proj);
1134+
//}
11191135
}
11201136
else
11211137
{
1122-
if (attackMod->fireOffset.x | attackMod->fireOffset.z)
1123-
{
1124-
proj->delta.x = attackMod->fireOffset.x;
1125-
proj->delta.z = attackMod->fireOffset.z;
1126-
proj_handleMovement(proj);
1127-
}
1138+
// Commenting out - see note above
1139+
//if (fireOffset.x | fireOffset.z)
1140+
//{
1141+
// proj->delta.x = fireOffset.x;
1142+
// proj->delta.z = fireOffset.z;
1143+
// proj_handleMovement(proj);
1144+
//}
11281145
vec3_fixed target = { s_eyePos.x, s_eyePos.y + ONE_16, s_eyePos.z };
11291146
proj_aimAtTarget(proj, target);
11301147
if (attackMod->fireSpread)

TheForceEngine/TFE_DarkForces/Actor/enemies.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
namespace TFE_DarkForces
2121
{
2222
static const s32 s_reeyeeMinDist = FIXED(30);
23-
23+
2424
Logic* reeyees_setup(SecObject* obj, LogicSetupFunc* setupFunc)
2525
{
2626
ActorDispatch* dispatch = actor_createDispatch(obj, setupFunc);
@@ -190,7 +190,7 @@ namespace TFE_DarkForces
190190
{
191191
ActorDispatch* dispatch = actor_createDispatch(obj, setupFunc);
192192
dispatch->alertSndSrc = sound_load(cust->alertSound, SOUND_PRIORITY_MED5);
193-
dispatch->fov = cust->fov;
193+
dispatch->fov = floatToAngle((f32)cust->fov);
194194
dispatch->awareRange = FIXED(cust->awareRange);
195195

196196
// Damage Module
@@ -200,6 +200,7 @@ namespace TFE_DarkForces
200200
damageMod->dieEffect = (HitEffectID)cust->dieEffect;
201201
damageMod->hurtSndSrc = sound_load(cust->painSound, SOUND_PRIORITY_MED5);
202202
damageMod->dieSndSrc = sound_load(cust->dieSound, SOUND_PRIORITY_MED5);
203+
damageMod->stopOnHit = cust->stopOnDamage ? JTRUE : JFALSE;
203204
actor_addModule(dispatch, (ActorModule*)damageMod);
204205

205206
// Attack Module
@@ -221,22 +222,27 @@ namespace TFE_DarkForces
221222
attackMod->meleeRate = FIXED(cust->meleeRate);
222223
attackMod->minDist = FIXED(cust->minAttackDist);
223224
attackMod->fireSpread = FIXED(cust->fireSpread);
225+
attackMod->fireOffset.x = floatToFixed16(cust->fireOffset.x);
226+
attackMod->fireOffset.y = cust->fireOffset.y < -999 ? attackMod->fireOffset.y : floatToFixed16(cust->fireOffset.y); // if -1000 use the default value
227+
attackMod->fireOffset.z = floatToFixed16(cust->fireOffset.z);
224228
s_actorState.attackMod = attackMod;
225229
actor_addModule(dispatch, (ActorModule*)attackMod);
226230

227231
// Thinker Module
228232
ThinkerModule* thinkerMod = actor_createThinkerModule(dispatch);
229-
thinkerMod->target.speedRotation = cust->rotationSpeed;
233+
thinkerMod->target.speedRotation = floatToAngle((f32)cust->rotationSpeed);
230234
thinkerMod->target.speed = FIXED(cust->speed);
235+
thinkerMod->approachVariation = floatToAngle((f32)cust->approachVariation);
236+
thinkerMod->targetOffset = FIXED(cust->approachOffset);
237+
thinkerMod->startDelay = TICKS(cust->thinkerDelay);
231238
thinkerMod->anim.flags &= ~AFLAG_PLAYONCE; // Ensures that walking animations will loop
232-
thinkerMod->startDelay = TICKS(2);
233239
actor_addModule(dispatch, (ActorModule*)thinkerMod);
234240

235241
// Flying Thinker Module (if flying enemy)
236242
if (cust->isFlying)
237243
{
238244
ThinkerModule* flyingMod = actor_createFlyingModule((Logic*)dispatch);
239-
flyingMod->target.speedRotation = cust->rotationSpeed;
245+
flyingMod->target.speedRotation = floatToAngle((f32)cust->rotationSpeed);
240246
flyingMod->target.speed = FIXED(cust->speed);
241247
flyingMod->target.speedVert = FIXED(cust->verticalSpeed);
242248
actor_addModule(dispatch, (ActorModule*)flyingMod);
@@ -245,7 +251,9 @@ namespace TFE_DarkForces
245251
// Movement Module
246252
MovementModule* moveMod = actor_createMovementModule(dispatch);
247253
dispatch->moveMod = moveMod;
248-
moveMod->physics.width = obj->worldWidth;
254+
moveMod->physics.width = cust->collisionWidth < 0 ? obj->worldWidth : floatToFixed16(cust->collisionWidth);
255+
moveMod->physics.height = cust->collisionHeight < 0 ? moveMod->physics.height : floatToFixed16(cust->collisionHeight);
256+
249257
if (cust->isFlying)
250258
{
251259
moveMod->collisionFlags = (moveMod->collisionFlags & ~ACTORCOL_ALL) | ACTORCOL_BIT2; // Remove bits 0, 1 and set bit 2

TheForceEngine/TFE_DarkForces/projectile.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,4 +1535,16 @@ namespace TFE_DarkForces
15351535
return JFALSE;
15361536
}
15371537

1538+
// TFE - Transform the spawn offset of a projectile based on orientation (yaw)
1539+
void transformFireOffsets(angle14_16 yaw, vec3_fixed* sourceOffset, vec3_fixed* offset)
1540+
{
1541+
fixed16_16 sinYaw;
1542+
fixed16_16 cosYaw;
1543+
1544+
sinCosFixed(yaw, &sinYaw, &cosYaw);
1545+
offset->x = mul16(sourceOffset->x,cosYaw) + mul16(sourceOffset->z, sinYaw);
1546+
offset->z = -mul16(sourceOffset->x,sinYaw) + mul16(sourceOffset->z, cosYaw);
1547+
offset->y = sourceOffset->y;
1548+
}
1549+
15381550
} // TFE_DarkForces

TheForceEngine/TFE_DarkForces/projectile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,5 @@ namespace TFE_DarkForces
137137

138138
extern WallHitFlag s_hitWallFlag;
139139
extern angle14_32 s_projReflectOverrideYaw;
140+
void transformFireOffsets(angle14_16 yaw, vec3_fixed* sourceOffset, vec3_fixed* offset); // New in TFE
140141
} // namespace TFE_DarkForces

TheForceEngine/TFE_ExternalData/dfLogics.cpp

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace TFE_ExternalData
1818
void parseLogicData(char* data, const char* filename, std::vector<CustomActorLogic>& actorLogics);
1919
bool tryAssignProperty(cJSON* data, CustomActorLogic& customLogic);
2020

21-
21+
2222
ExternalLogics* getExternalLogics()
2323
{
2424
return &s_externalLogics;
@@ -111,7 +111,7 @@ namespace TFE_ExternalData
111111
{
112112
return false;
113113
}
114-
114+
115115
if (cJSON_IsBool(data) && strcasecmp(data->string, "hasGravity") == 0)
116116
{
117117
customLogic.hasGravity = cJSON_IsTrue(data);
@@ -126,7 +126,7 @@ namespace TFE_ExternalData
126126

127127
if (cJSON_IsNumber(data) && strcasecmp(data->string, "fieldOfView") == 0)
128128
{
129-
customLogic.fov = floatToAngle((f32)data->valueint);
129+
customLogic.fov = data->valueint;
130130
return true;
131131
}
132132

@@ -141,7 +141,7 @@ namespace TFE_ExternalData
141141
customLogic.alertSound = data->valuestring;
142142
return true;
143143
}
144-
144+
145145
if (cJSON_IsString(data) && strcasecmp(data->string, "painSound") == 0)
146146
{
147147
customLogic.painSound = data->valuestring;
@@ -233,13 +233,13 @@ namespace TFE_ExternalData
233233
customLogic.litWithMeleeAttack = cJSON_IsTrue(data);
234234
return true;
235235
}
236-
236+
237237
if (cJSON_IsBool(data) && strcasecmp(data->string, "litWithRangedAttack") == 0)
238238
{
239239
customLogic.litWithRangedAttack = cJSON_IsTrue(data);
240240
return true;
241241
}
242-
242+
243243
// Projectile as number
244244
if (cJSON_IsNumber(data) && strcasecmp(data->string, "projectile") == 0)
245245
{
@@ -330,24 +330,69 @@ namespace TFE_ExternalData
330330

331331
if (cJSON_IsNumber(data) && strcasecmp(data->string, "rotationSpeed") == 0)
332332
{
333-
customLogic.rotationSpeed = floatToAngle((f32)data->valueint);
333+
customLogic.rotationSpeed = data->valueint;
334+
return true;
335+
}
336+
337+
if (cJSON_IsBool(data) && strcasecmp(data->string, "stopOnDamage") == 0)
338+
{
339+
customLogic.stopOnDamage = cJSON_IsTrue(data);
340+
return true;
341+
}
342+
343+
if (cJSON_IsNumber(data) && strcasecmp(data->string, "approachVariation") == 0)
344+
{
345+
customLogic.approachVariation = data->valueint;
334346
return true;
335347
}
336348

337-
/* JK: Leaving these out for now until we have a better understanding of what they mean
338-
if (cJSON_IsNumber(data) && strcasecmp(data->string, "delay") == 0)
349+
if (cJSON_IsNumber(data) && strcasecmp(data->string, "approachOffset") == 0)
339350
{
340-
customLogic.delay = data->valueint;
351+
customLogic.approachOffset = data->valueint;
341352
return true;
342353
}
343354

344-
if (cJSON_IsNumber(data) && strcasecmp(data->string, "startDelay") == 0)
355+
if (cJSON_IsNumber(data) && strcasecmp(data->string, "thinkerDelay") == 0)
345356
{
346-
customLogic.startDelay = data->valueint;
357+
customLogic.thinkerDelay = data->valueint;
347358
return true;
348359
}
349-
*/
360+
361+
if (cJSON_IsNumber(data) && strcasecmp(data->string, "collisionWidth") == 0)
362+
{
363+
customLogic.collisionWidth = data->valuedouble;
364+
return true;
365+
}
366+
367+
if (cJSON_IsNumber(data) && strcasecmp(data->string, "collisionHeight") == 0)
368+
{
369+
customLogic.collisionHeight = data->valuedouble;
370+
return true;
371+
}
372+
373+
// When it comes to offsets these are considered from the perspective of the actor.
374+
//
375+
// Projectile spawn details guide.
376+
//
377+
// X value positive = projectile spawns to the LEFT of actor
378+
// X value negative = projectile spawns to the RIGHT of actor
379+
// Z value postivie = projectile spawns in FRONT of actor
380+
// Z value negative = projectile spawns BEHIND the actor
381+
// Y value positive = projectile spawns ABOVE the actor
382+
// Y value negative = projectile spawns BELOW the actor
383+
//
384+
if (cJSON_IsArray(data) && strcasecmp(data->string, "fireOffset") == 0)
385+
{
386+
if (cJSON_GetArraySize(data) == 3)
387+
{
388+
customLogic.fireOffset.x = -cJSON_GetArrayItem(data, 0)->valuedouble;
389+
customLogic.fireOffset.y = -cJSON_GetArrayItem(data, 1)->valuedouble;
390+
customLogic.fireOffset.z = cJSON_GetArrayItem(data, 2)->valuedouble;
391+
return true;
392+
}
393+
return false;
394+
}
350395

351396
return false;
352397
}
353-
}
398+
}

TheForceEngine/TFE_ExternalData/dfLogics.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace TFE_ExternalData
2525
u32 hitPoints = 4;
2626
s32 dropItem = -1;
2727
s32 dieEffect = -1;
28+
bool stopOnDamage = true;
2829

2930
bool hasMeleeAttack = false;
3031
bool hasRangedAttack = true;
@@ -40,15 +41,17 @@ namespace TFE_ExternalData
4041
u32 minAttackDist = 0;
4142
u32 maxAttackDist = 160;
4243
u32 fireSpread = 30;
44+
vec3_float fireOffset = { 0, -1000, 0 }; // (y = -1000) will be treated as default
4345

4446
u32 speed = 4;
4547
u32 verticalSpeed = 10;
4648
u32 rotationSpeed = 0x7fff;
49+
u32 approachVariation = 4096;
50+
u32 approachOffset = 3;
51+
u32 thinkerDelay = 2;
4752

48-
/* JK: Leaving these out for now until we have a better understanding of what they mean
49-
u32 delay = 72;
50-
u32 startDelay = 2;
51-
*/
53+
f32 collisionWidth = -1;
54+
f32 collisionHeight = -1;
5255
};
5356

5457
struct ExternalLogics

0 commit comments

Comments
 (0)