1
1
package com .interrupt .dungeoneer .entities ;
2
2
3
+ import com .badlogic .gdx .Gdx ;
3
4
import com .badlogic .gdx .graphics .Color ;
4
5
import com .badlogic .gdx .math .Vector3 ;
5
6
import com .badlogic .gdx .utils .Array ;
@@ -70,6 +71,10 @@ public class Monster extends Actor implements Directional {
70
71
@ EditorProperty
71
72
private float attackTime = 60 ;
72
73
74
+ /** Time to wait before starting to move again after an attack. */
75
+ @ EditorProperty
76
+ private float postAttackMoveWaitTime = 0.01f ;
77
+
73
78
/** Time interval between monster projectile attacks. */
74
79
@ EditorProperty
75
80
private float projectileAttackTime = 100 ;
@@ -87,9 +92,11 @@ public class Monster extends Actor implements Directional {
87
92
public float projectileOffset = 0f ;
88
93
89
94
private float attacktimer = 0 ;
95
+ private float rangedAttackTimer = 0 ;
90
96
private float regenManaTimer = 0 ;
91
97
92
98
private float stuntime = 0 ;
99
+ private float postAttackMoveWaitTimer = 0 ;
93
100
94
101
/** Is monster alerted to player's presence? */
95
102
public boolean alerted = false ;
@@ -200,6 +207,9 @@ public class Monster extends Actor implements Directional {
200
207
/** Monster attack animation. */
201
208
private SpriteAnimation attackAnimation = null ;
202
209
210
+ /** Monster ranged attack animation. */
211
+ private SpriteAnimation rangedAttackAnimation = null ;
212
+
203
213
/** Monster cast animation. */
204
214
private SpriteAnimation castAnimation = null ;
205
215
@@ -279,6 +289,7 @@ public Monster() {
279
289
stepHeight = 0.4f ;
280
290
281
291
attacktimer = 30 ;
292
+ rangedAttackTimer = 30 ;
282
293
283
294
mass = 2f ;
284
295
@@ -379,6 +390,7 @@ public int takeDamage(int damage, DamageType damageType, Entity instigator) {
379
390
int tookDamage = super .takeDamage (damage , damageType , instigator );
380
391
if (doPainRoll (tookDamage )) {
381
392
if (attackAnimation != null ) attackAnimation .playing = false ;
393
+ if (rangedAttackAnimation != null ) rangedAttackAnimation .playing = false ;
382
394
if (hurtAnimation != null ) hurtAnimation .play ();
383
395
}
384
396
return tookDamage ;
@@ -437,6 +449,7 @@ public void tick(Level level, float delta)
437
449
438
450
// tick some timers (TODO: MAKE A TIMER HELPER)
439
451
if (attacktimer > 0 ) attacktimer -= delta ;
452
+ if (rangedAttackTimer > 0 ) rangedAttackTimer -= delta ;
440
453
regenManaTimer += delta ;
441
454
442
455
if (regenManaTimer > 60 ) {
@@ -473,7 +486,9 @@ public void tick(Level level, float delta)
473
486
if (hostile ) {
474
487
canSeePlayer = level .canSeeIncludingDoors (x , y , player .x , player .y , 17 );
475
488
489
+ // Reset attack timers if we lose track of the player
476
490
if (!canSeePlayer && attacktimer < 30 ) attacktimer = 30 ;
491
+ if (!canSeePlayer && rangedAttackTimer < 30 ) rangedAttackTimer = 30 ;
477
492
478
493
pxdir = player .x - x ;
479
494
pydir = player .y - y ;
@@ -506,6 +521,7 @@ else if(playerdist > 0.75f) {
506
521
Audio .playPositionedSound (alertSound , new Vector3 (x , y , z ), soundVolume , 1f , 12f );
507
522
508
523
attacktimer = 40 + Game .rand .nextInt (20 );
524
+ rangedAttackTimer = 40 + Game .rand .nextInt (20 );
509
525
}
510
526
}
511
527
@@ -663,15 +679,34 @@ else if(tryAt == 4)
663
679
txa = 0 ;
664
680
tza = 0 ;
665
681
}
682
+
683
+ // stop moving after an attack
684
+ if (postAttackMoveWaitTimer > 0 ) {
685
+ boolean isPlayingAttackAnimation = false ;
686
+
687
+ if (attackAnimation != null )
688
+ isPlayingAttackAnimation |= attackAnimation .playing ;
689
+ if (rangedAttackAnimation != null )
690
+ isPlayingAttackAnimation |= rangedAttackAnimation .playing ;
691
+ if (castAnimation != null )
692
+ isPlayingAttackAnimation |= castAnimation .playing ;
693
+
694
+ if (!isPlayingAttackAnimation )
695
+ postAttackMoveWaitTimer -= delta ;
696
+
697
+ txa = 0 ;
698
+ tza = 0 ;
699
+ }
666
700
667
- if (hostile && (playerdist < collision .x + reach || playerdist < collision .x + attackStartDistance ))
668
- {
669
- float zDiff = Math .abs ((player .z + 0.3f ) - (z + 0.3f ));
670
- if (zDiff < reach || zDiff < attackStartDistance ) {
671
- attack (player );
672
- txa = 0 ;
673
- tza = 0 ;
674
- }
701
+ if (hostile ) {
702
+ if ((playerdist < collision .x + reach || playerdist < collision .x + attackStartDistance )) {
703
+ float zDiff = Math .abs ((player .z + 0.3f ) - (z + 0.3f ));
704
+ if (zDiff < reach || zDiff < attackStartDistance ) {
705
+ attack (player );
706
+ }
707
+ } else if (playerdist > projectileAttackMinDistance && playerdist < projectileAttackMaxDistance && (projectile != null || rangedAttackAnimation != null )) {
708
+ rangedAttack (player );
709
+ }
675
710
}
676
711
677
712
if (walkSound != null ) {
@@ -762,62 +797,8 @@ else if(tryAt == 4)
762
797
}
763
798
}
764
799
}
765
- }
766
-
767
- if (projectile != null && attacktimer <= 0 && stuntime <= 0 && canSeePlayer && hostile && !isParalyzed ()
768
- && playerdist < projectileAttackMaxDistance && playerdist > projectileAttackMinDistance )
769
- {
770
- // face fire direction!
771
- setDirectionTowards (player );
772
-
773
- attacktimer = projectileAttackTime ;
774
- if (attackAnimation != null ) {
775
- attackAnimation .play ();
776
- }
777
800
778
- try {
779
- Entity pCopy = null ;
780
- if (projectile instanceof Prefab ) {
781
- Prefab p = (Prefab )projectile ;
782
- pCopy = EntityManager .instance .getEntity (p .category , p .name );
783
- }
784
- else {
785
- pCopy = (Entity ) KryoSerializer .copyObject (projectile );
786
- }
787
-
788
- if (pCopy != null ) {
789
- pCopy .owner = this ;
790
- pCopy .ignorePlayerCollision = false ;
791
-
792
- // spawns from this entity
793
- pCopy .x = x ;
794
- pCopy .y = y ;
795
- pCopy .z = projectileOffset + z + (collision .z * 0.6f );
796
-
797
- Vector3 dirToPlayer = new Vector3 (player .x , player .y , player .z + (player .collision .z * 0.5f ));
798
- if (!pCopy .floating ) {
799
- // Go ballistics
800
- dirToPlayer .z += (playerdist * playerdist ) * projectileBallisticsMod ;
801
- }
802
-
803
- dirToPlayer .sub (pCopy .x , pCopy .y , pCopy .z ).nor ();
804
-
805
- // offset out of collision
806
- pCopy .x += dirToPlayer .x * collision .x * 0.5f ;
807
- pCopy .y += dirToPlayer .y * collision .x * 0.5f ;
808
- pCopy .z += dirToPlayer .z * collision .x * 0.5f ;
809
-
810
- // initial speed
811
- dirToPlayer .scl (projectileSpeed );
812
-
813
- pCopy .xa = dirToPlayer .x ;
814
- pCopy .ya = dirToPlayer .y ;
815
- pCopy .za = dirToPlayer .z ;
816
-
817
- level .SpawnEntity (pCopy );
818
- }
819
- }
820
- catch (Exception ex ) { }
801
+ postAttackMoveWaitTimer = postAttackMoveWaitTime ;
821
802
}
822
803
823
804
// idle sounds!
@@ -836,10 +817,11 @@ else if(tryAt == 4)
836
817
if (hurtAnimation != null && hurtAnimation .playing ) hurtAnimation .animate (delta , this );
837
818
else if (castAnimation != null && castAnimation .playing ) castAnimation .animate (delta , this );
838
819
else if (attackAnimation != null && attackAnimation .playing ) attackAnimation .animate (delta , this );
820
+ else if (rangedAttackAnimation != null && rangedAttackAnimation .playing ) rangedAttackAnimation .animate (delta , this );
839
821
else if (dodgeAnimation != null && dodgeAnimation .playing ) dodgeAnimation .animate (delta , this );
840
822
else if (walkAnimation != null ) walkAnimation .animate (delta , this );
841
823
}
842
-
824
+
843
825
private float getSpeed () {
844
826
float baseSpeed = speed ;
845
827
@@ -1142,36 +1124,128 @@ public void attack(Entity target)
1142
1124
if (!hostile ) return ;
1143
1125
if (stuntime > 0 ) return ;
1144
1126
if (isParalyzed ()) return ;
1145
- if (! alerted ) alerted = true ;
1127
+ alerted = true ;
1146
1128
1147
- if (attacktimer <= 0 )
1148
- {
1149
- if (dodgeAnimation != null ) {
1150
- dodgeAnimation .play ();
1151
- attacktimer = attackTime * 2 ;
1152
- return ;
1129
+ if (attacktimer > 0 )
1130
+ return ;
1131
+
1132
+ if (dodgeAnimation != null ) {
1133
+ dodgeAnimation .play ();
1134
+ attacktimer = attackTime * 2 ;
1135
+ return ;
1136
+ }
1137
+
1138
+ attackTarget = target ;
1139
+ attacktimer = attackTime + Game .rand .nextInt (10 );
1140
+ Audio .playPositionedSound (attackSwingSound , new Vector3 (x , y , z ), 0.75f , 1f , 12f );
1141
+
1142
+ setDirectionTowards (attackTarget );
1143
+
1144
+ if (Game .rand .nextFloat () > 0.7f ) Audio .playPositionedSound (attackSound , new Vector3 (x , y , z ), soundVolume , 1f , 12f );
1145
+
1146
+ if (attackAnimation != null ) {
1147
+ attackAnimation .play ();
1148
+
1149
+ // if no actions are set, just do the damage hit now
1150
+ // otherwise assume that there's a set damage frame
1151
+ if (attackAnimation .actions == null ) {
1152
+ tryDamageHit (attackTarget , 0f , 0.05f );
1153
1153
}
1154
+ }
1155
+ else {
1156
+ tryDamageHit (attackTarget , 0f , 0.05f );
1157
+ }
1154
1158
1155
- attackTarget = target ;
1156
- attacktimer = attackTime + Game .rand .nextInt (10 );
1157
- Audio .playPositionedSound (attackSwingSound , new Vector3 (x , y , z ), 0.75f , 1f , 12f );
1159
+ postAttackMoveWaitTimer = postAttackMoveWaitTime ;
1160
+ }
1161
+
1162
+ private void rangedAttack (Entity target ) {
1163
+ if (target == null || !target .isActive ) return ;
1164
+ if (!hostile ) return ;
1165
+ if (stuntime > 0 ) return ;
1166
+ if (isParalyzed ()) return ;
1167
+ alerted = true ;
1158
1168
1159
- setDirectionTowards (attackTarget );
1169
+ if (rangedAttackTimer > 0 )
1170
+ return ;
1160
1171
1161
- if (Game .rand .nextFloat () > 0.7f ) Audio .playPositionedSound (attackSound , new Vector3 (x , y , z ), soundVolume , 1f , 12f );
1172
+ attackTarget = target ;
1173
+ rangedAttackTimer = projectileAttackTime + Game .rand .nextInt (15 );
1174
+ setDirectionTowards (attackTarget );
1162
1175
1163
- if (attackAnimation != null ) {
1164
- attackAnimation .play ();
1176
+ if (rangedAttackAnimation != null ) {
1177
+ rangedAttackAnimation .play ();
1165
1178
1166
- // if no actions are set, just do the damage hit now
1167
- // otherwise assume that there's a set damage frame
1168
- if (attackAnimation .actions == null ) {
1169
- tryDamageHit (attackTarget , 0f , 0.05f );
1170
- }
1179
+ // If no actions are set, try to spawn the basic projectile.
1180
+ // Otherwise, assume there is a ProjectileAttackAction in there.
1181
+ if (rangedAttackAnimation .actions == null ) {
1182
+ spawnBasicProjectile (target );
1183
+ }
1184
+ }
1185
+ else {
1186
+ spawnBasicProjectile (target );
1187
+ }
1188
+
1189
+ postAttackMoveWaitTimer = postAttackMoveWaitTime ;
1190
+ }
1191
+
1192
+ private void spawnBasicProjectile (Entity target ) {
1193
+ // face fire direction!
1194
+ setDirectionTowards (target );
1195
+
1196
+ rangedAttackTimer = projectileAttackTime ;
1197
+ if (attackAnimation != null ) {
1198
+ attackAnimation .play ();
1199
+ }
1200
+
1201
+ try {
1202
+ Entity pCopy = null ;
1203
+ if (projectile instanceof Prefab ) {
1204
+ Prefab p = (Prefab )projectile ;
1205
+ pCopy = EntityManager .instance .getEntity (p .category , p .name );
1171
1206
}
1172
1207
else {
1173
- tryDamageHit ( attackTarget , 0f , 0.05f );
1208
+ pCopy = ( Entity ) KryoSerializer . copyObject ( projectile );
1174
1209
}
1210
+
1211
+ if (pCopy != null ) {
1212
+ pCopy .owner = this ;
1213
+ pCopy .ignorePlayerCollision = false ;
1214
+
1215
+ // spawns from this entity
1216
+ pCopy .x = x ;
1217
+ pCopy .y = y ;
1218
+ pCopy .z = projectileOffset + z + (collision .z * 0.6f );
1219
+
1220
+ Vector3 dirToTarget = new Vector3 (target .x , target .y , target .z + (target .collision .z * 0.5f ));
1221
+ if (!pCopy .floating ) {
1222
+ // Go ballistics
1223
+ dirToTarget .z += (playerdist * playerdist ) * projectileBallisticsMod ;
1224
+ }
1225
+
1226
+ dirToTarget .sub (pCopy .x , pCopy .y , pCopy .z ).nor ();
1227
+
1228
+ // offset out of collision
1229
+ pCopy .x += dirToTarget .x * collision .x * 0.5f ;
1230
+ pCopy .y += dirToTarget .y * collision .x * 0.5f ;
1231
+ pCopy .z += dirToTarget .z * collision .x * 0.5f ;
1232
+
1233
+ // initial speed
1234
+ dirToTarget .scl (projectileSpeed );
1235
+
1236
+ pCopy .xa = dirToTarget .x ;
1237
+ pCopy .ya = dirToTarget .y ;
1238
+ pCopy .za = dirToTarget .z ;
1239
+
1240
+ // Some items trigger effects when thrown
1241
+ if (pCopy instanceof Item )
1242
+ ((Item )pCopy ).tossItem (Game .instance .level , 1.0f );
1243
+
1244
+ Game .instance .level .SpawnEntity (pCopy );
1245
+ }
1246
+ }
1247
+ catch (Exception ex ) {
1248
+ Gdx .app .log ("DelverGame" , "Error spawning projectile: " + ex .getMessage ());
1175
1249
}
1176
1250
}
1177
1251
0 commit comments