Trying to figure out issue with sprites loading improperly. Broken, still.
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -32,8 +32,9 @@ class Collectible extends Entity {
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
Difficulty _,
|
||||
) {
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
if (isCollectible(objId)) {
|
||||
return Collectible(
|
||||
x: x,
|
||||
|
||||
@@ -39,8 +39,9 @@ class Decorative extends Entity {
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
Difficulty _,
|
||||
) {
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
if (isDecoration(objId)) {
|
||||
return Decorative(
|
||||
x: x,
|
||||
|
||||
@@ -35,8 +35,9 @@ class HansGrosse extends Enemy {
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
Difficulty difficulty,
|
||||
) {
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
if (objId == MapObject.bossHansGrosse) {
|
||||
return HansGrosse(
|
||||
x: x,
|
||||
|
||||
@@ -1,37 +1,26 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||
import 'package:wolf_dart/features/entities/entity.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart'; // NEW
|
||||
|
||||
class Dog extends Enemy {
|
||||
static const double speed = 0.05;
|
||||
bool _hasBittenThisCycle = false;
|
||||
|
||||
static EnemyType get type => EnemyType.dog;
|
||||
|
||||
Dog({
|
||||
required super.x,
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(
|
||||
spriteIndex: EnemyType.dog.spriteBaseIdx,
|
||||
spriteIndex: type.spriteBaseIdx,
|
||||
state: EntityState.idle,
|
||||
);
|
||||
|
||||
static Dog? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||
if (EnemyType.dog.claimsMapId(objId)) {
|
||||
bool isPatrolling = objId >= EnemyType.dog.mapBaseId + 18;
|
||||
|
||||
return Dog(
|
||||
x: x,
|
||||
y: y,
|
||||
angle: MapObject.getAngle(objId),
|
||||
mapId: objId,
|
||||
)..state = isPatrolling ? EntityState.patrolling : EntityState.idle;
|
||||
}
|
||||
return null;
|
||||
) {
|
||||
health = 1;
|
||||
damage = 5;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -39,8 +28,8 @@ class Dog extends Enemy {
|
||||
required int elapsedMs,
|
||||
required Coordinate2D playerPosition,
|
||||
required bool Function(int x, int y) isWalkable,
|
||||
required void Function(int x, int y) tryOpenDoor,
|
||||
required void Function(int damage) onDamagePlayer,
|
||||
required void Function(int x, int y) tryOpenDoor,
|
||||
}) {
|
||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||
double newAngle = angle;
|
||||
@@ -49,8 +38,6 @@ class Dog extends Enemy {
|
||||
elapsedMs: elapsedMs,
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
baseReactionMs: 100,
|
||||
reactionVarianceMs: 200,
|
||||
);
|
||||
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
@@ -61,7 +48,6 @@ class Dog extends Enemy {
|
||||
}
|
||||
|
||||
double diff = angleToPlayer - newAngle;
|
||||
|
||||
while (diff <= -math.pi) {
|
||||
diff += 2 * math.pi;
|
||||
}
|
||||
@@ -69,72 +55,35 @@ class Dog extends Enemy {
|
||||
diff -= 2 * math.pi;
|
||||
}
|
||||
|
||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
||||
if (octant < 0) octant += 8;
|
||||
EnemyAnimation currentAnim = switch (state) {
|
||||
EntityState.patrolling => EnemyAnimation.walking,
|
||||
EntityState.attacking => EnemyAnimation.attacking,
|
||||
EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
|
||||
_ => EnemyAnimation.idle,
|
||||
};
|
||||
|
||||
// 3. Clean State Machine
|
||||
switch (state) {
|
||||
case EntityState.idle:
|
||||
spriteIndex = 99 + octant;
|
||||
break;
|
||||
|
||||
case EntityState.patrolling:
|
||||
if (distance > 0.8) {
|
||||
// UPGRADED: Smooth vector movement instead of grid-snapping
|
||||
double moveX = math.cos(angleToPlayer) * speed;
|
||||
double moveY = math.sin(angleToPlayer) * speed;
|
||||
|
||||
Coordinate2D intendedMovement = Coordinate2D(moveX, moveY);
|
||||
|
||||
movement = getValidMovement(
|
||||
intendedMovement,
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
spriteIndex = type.getSpriteFromAnimation(
|
||||
animation: currentAnim,
|
||||
elapsedMs: elapsedMs,
|
||||
lastActionTime: lastActionTime,
|
||||
angleDiff: diff,
|
||||
);
|
||||
}
|
||||
|
||||
int walkFrame = (elapsedMs ~/ 100) % 4;
|
||||
spriteIndex = 107 + (walkFrame * 8) + octant;
|
||||
|
||||
if (distance < 1.0 && elapsedMs - lastActionTime > 1000) {
|
||||
if (state == EntityState.patrolling && distance < 1.0) {
|
||||
state = EntityState.attacking;
|
||||
lastActionTime = elapsedMs;
|
||||
_hasBittenThisCycle = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityState.attacking:
|
||||
int timeAttacking = elapsedMs - lastActionTime;
|
||||
if (timeAttacking < 200) {
|
||||
spriteIndex = 139;
|
||||
if (!_hasBittenThisCycle) {
|
||||
onDamagePlayer(5);
|
||||
if (state == EntityState.attacking) {
|
||||
int time = elapsedMs - lastActionTime;
|
||||
if (time >= 200 && !_hasBittenThisCycle) {
|
||||
onDamagePlayer(damage);
|
||||
_hasBittenThisCycle = true;
|
||||
}
|
||||
} else {
|
||||
} else if (time >= 400) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
}
|
||||
break;
|
||||
|
||||
// Make sure dogs have a death state so they don't stay standing!
|
||||
case EntityState.dead:
|
||||
if (isDying) {
|
||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 100;
|
||||
if (deathFrame < 4) {
|
||||
spriteIndex =
|
||||
140 + deathFrame; // Dog death frames usually start here
|
||||
} else {
|
||||
spriteIndex = 143; // Dead dog on floor
|
||||
isDying = false;
|
||||
}
|
||||
} else {
|
||||
spriteIndex = 143;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (movement: movement, newAngle: newAngle);
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/dog.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/guard.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/mutant.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/officer.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/ss.dart';
|
||||
import 'package:wolf_dart/features/entities/entity.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||
|
||||
enum EnemyAnimation {
|
||||
idle,
|
||||
walking,
|
||||
attacking,
|
||||
pain,
|
||||
dying,
|
||||
dead,
|
||||
}
|
||||
|
||||
enum EnemyType {
|
||||
guard(mapBaseId: 108, spriteBaseIdx: 50),
|
||||
@@ -44,6 +60,93 @@ enum EnemyType {
|
||||
EnemyType.officer => index >= 238 && index <= 287,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the current animation state for a given sprite index.
|
||||
/// Returns null if the sprite index does not belong to this enemy.
|
||||
EnemyAnimation? getAnimationFromSprite(int spriteIndex) {
|
||||
if (!claimsSpriteIndex(spriteIndex)) return null;
|
||||
|
||||
// By working with offsets, we don't have to hardcode the 100+ sprite indices!
|
||||
int offset = spriteIndex - spriteBaseIdx;
|
||||
|
||||
// All standard enemies use offsets 0-7 for their 8 directional Idle frames
|
||||
if (offset >= 0 && offset <= 7) return EnemyAnimation.idle;
|
||||
|
||||
// The action frames vary slightly depending on the enemy type
|
||||
return switch (this) {
|
||||
EnemyType.guard || EnemyType.ss => switch (offset) {
|
||||
>= 8 && <= 39 => EnemyAnimation.walking, // 4 frames * 8 directions
|
||||
>= 40 && <= 42 => EnemyAnimation.attacking, // Aim, Fire, Recoil
|
||||
43 => EnemyAnimation.pain,
|
||||
>= 44 && <= 46 => EnemyAnimation.dying,
|
||||
_ => EnemyAnimation.dead, // Catch-all for final frames
|
||||
},
|
||||
|
||||
EnemyType.officer || EnemyType.mutant => switch (offset) {
|
||||
>= 8 && <= 39 => EnemyAnimation.walking,
|
||||
>= 40 && <= 41 => EnemyAnimation.attacking, // Only 2 attack frames!
|
||||
42 => EnemyAnimation.pain,
|
||||
>= 43 && <= 45 => EnemyAnimation.dying,
|
||||
_ => EnemyAnimation.dead,
|
||||
},
|
||||
|
||||
EnemyType.dog => switch (offset) {
|
||||
// Dogs are special: 3 walk frames (24 total) and NO pain frame!
|
||||
>= 8 && <= 31 => EnemyAnimation.walking,
|
||||
>= 32 && <= 34 => EnemyAnimation.attacking, // Leap and bite
|
||||
>= 35 && <= 37 => EnemyAnimation.dying,
|
||||
_ => EnemyAnimation.dead,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
int getSpriteFromAnimation({
|
||||
required EnemyAnimation animation,
|
||||
required int elapsedMs,
|
||||
required int lastActionTime,
|
||||
double angleDiff = 0,
|
||||
int? walkFrameOverride, // Optional for custom timing
|
||||
}) {
|
||||
// 1. Calculate Octant for directional sprites (Idle/Walk)
|
||||
int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
||||
if (octant < 0) octant += 8;
|
||||
|
||||
return switch (animation) {
|
||||
EnemyAnimation.idle => spriteBaseIdx + octant,
|
||||
|
||||
EnemyAnimation.walking => () {
|
||||
int frameCount = this == EnemyType.dog ? 3 : 4;
|
||||
int frame = walkFrameOverride ?? (elapsedMs ~/ 150) % frameCount;
|
||||
return (spriteBaseIdx + 8) + (frame * 8) + octant;
|
||||
}(),
|
||||
|
||||
EnemyAnimation.attacking => () {
|
||||
int time = elapsedMs - lastActionTime;
|
||||
return switch (this) {
|
||||
EnemyType.guard || EnemyType.ss || EnemyType.dog =>
|
||||
spriteBaseIdx +
|
||||
(time < 150
|
||||
? 40
|
||||
: time < 300
|
||||
? 41
|
||||
: 40),
|
||||
EnemyType.officer ||
|
||||
EnemyType.mutant => spriteBaseIdx + (time < 200 ? 40 : 41),
|
||||
};
|
||||
}(),
|
||||
|
||||
EnemyAnimation.pain => spriteBaseIdx + (this == EnemyType.dog ? 32 : 42),
|
||||
|
||||
EnemyAnimation.dying => () {
|
||||
int frame = (elapsedMs - lastActionTime) ~/ 150;
|
||||
int maxFrames = this == EnemyType.dog ? 2 : 3;
|
||||
int offset = this == EnemyType.dog ? 35 : 43;
|
||||
return spriteBaseIdx + offset + (frame.clamp(0, maxFrames));
|
||||
}(),
|
||||
|
||||
EnemyAnimation.dead => spriteBaseIdx + (this == EnemyType.dog ? 37 : 45),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Enemy extends Entity {
|
||||
@@ -216,4 +319,34 @@ abstract class Enemy extends Entity {
|
||||
required void Function(int x, int y) tryOpenDoor,
|
||||
required void Function(int damage) onDamagePlayer,
|
||||
});
|
||||
|
||||
/// Centralized factory to handle all enemy spawning logic
|
||||
static Enemy? spawn(
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
// 1. Check Difficulty & Compatibility
|
||||
if (!MapObject.shouldSpawn(objId, difficulty)) return null;
|
||||
|
||||
// If the checkbox is checked, block non-Shareware enemies
|
||||
if (isSharewareMode && !MapObject.isSharewareCompatible(objId)) return null;
|
||||
|
||||
final type = EnemyType.fromMapId(objId);
|
||||
if (type == null) return null;
|
||||
|
||||
bool isPatrolling = objId >= type.mapBaseId + 18;
|
||||
double spawnAngle = MapObject.getAngle(objId);
|
||||
|
||||
// 2. Return the specific instance
|
||||
return switch (type) {
|
||||
EnemyType.guard => Guard(x: x, y: y, angle: spawnAngle, mapId: objId),
|
||||
EnemyType.dog => Dog(x: x, y: y, angle: spawnAngle, mapId: objId),
|
||||
EnemyType.ss => SS(x: x, y: y, angle: spawnAngle, mapId: objId),
|
||||
EnemyType.mutant => Mutant(x: x, y: y, angle: spawnAngle, mapId: objId),
|
||||
EnemyType.officer => Officer(x: x, y: y, angle: spawnAngle, mapId: objId),
|
||||
}..state = isPatrolling ? EntityState.patrolling : EntityState.idle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,25 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||
import 'package:wolf_dart/features/entities/entity.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||
|
||||
class Guard extends Enemy {
|
||||
static const double speed = 0.03;
|
||||
bool _hasFiredThisCycle = false;
|
||||
|
||||
static EnemyType get type => EnemyType.guard;
|
||||
|
||||
Guard({
|
||||
required super.x,
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(
|
||||
spriteIndex: EnemyType.guard.spriteBaseIdx,
|
||||
spriteIndex: type.spriteBaseIdx,
|
||||
state: EntityState.idle,
|
||||
);
|
||||
|
||||
static Guard? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||
if (EnemyType.guard.claimsMapId(objId) && objId != 124 && objId != 125) {
|
||||
bool isPatrolling = objId >= EnemyType.guard.mapBaseId + 18;
|
||||
|
||||
return Guard(
|
||||
x: x,
|
||||
y: y,
|
||||
angle: MapObject.getAngle(objId),
|
||||
mapId: objId,
|
||||
)..state = isPatrolling ? EntityState.patrolling : EntityState.idle;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
({Coordinate2D movement, double newAngle}) update({
|
||||
required int elapsedMs,
|
||||
@@ -58,9 +44,8 @@ class Guard extends Enemy {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
// Octant logic (Directional sprites)
|
||||
// Calculate angle diff for the octant logic
|
||||
double diff = angleToPlayer - newAngle;
|
||||
|
||||
while (diff <= -math.pi) {
|
||||
diff += 2 * math.pi;
|
||||
}
|
||||
@@ -68,82 +53,32 @@ class Guard extends Enemy {
|
||||
diff -= 2 * math.pi;
|
||||
}
|
||||
|
||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
||||
if (octant < 0) octant += 8;
|
||||
// Helper to get sprite based on current state
|
||||
EnemyAnimation currentAnim = switch (state) {
|
||||
EntityState.patrolling => EnemyAnimation.walking,
|
||||
EntityState.attacking => EnemyAnimation.attacking,
|
||||
EntityState.pain => EnemyAnimation.pain,
|
||||
EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
|
||||
_ => EnemyAnimation.idle,
|
||||
};
|
||||
|
||||
// 3. State Machine
|
||||
switch (state) {
|
||||
case EntityState.idle:
|
||||
spriteIndex = 50 + octant;
|
||||
break;
|
||||
|
||||
case EntityState.patrolling:
|
||||
if (distance > 0.8) {
|
||||
double moveX = math.cos(angleToPlayer) * speed;
|
||||
double moveY = math.sin(angleToPlayer) * speed;
|
||||
Coordinate2D intendedMovement = Coordinate2D(moveX, moveY);
|
||||
|
||||
movement = getValidMovement(
|
||||
intendedMovement,
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
spriteIndex = type.getSpriteFromAnimation(
|
||||
animation: currentAnim,
|
||||
elapsedMs: elapsedMs,
|
||||
lastActionTime: lastActionTime,
|
||||
angleDiff: diff,
|
||||
);
|
||||
}
|
||||
|
||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
||||
spriteIndex = 58 + (walkFrame * 8) + octant;
|
||||
|
||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||
state = EntityState.attacking;
|
||||
lastActionTime = elapsedMs;
|
||||
_hasFiredThisCycle = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityState.attacking:
|
||||
int timeShooting = elapsedMs - lastActionTime;
|
||||
if (timeShooting < 150) {
|
||||
spriteIndex = 90; // Aiming
|
||||
} else if (timeShooting < 300) {
|
||||
spriteIndex = 91; // Firing
|
||||
if (!_hasFiredThisCycle) {
|
||||
// Logic triggers (Damage, State transitions)
|
||||
if (state == EntityState.attacking) {
|
||||
int time = elapsedMs - lastActionTime;
|
||||
if (time >= 150 && time < 300 && !_hasFiredThisCycle) {
|
||||
onDamagePlayer(10);
|
||||
_hasFiredThisCycle = true;
|
||||
}
|
||||
} else if (timeShooting < 450) {
|
||||
spriteIndex = 90; // Recoil (back to aim pose)
|
||||
} else {
|
||||
} else if (time >= 450) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityState.pain:
|
||||
spriteIndex = 92; // Ouch frame
|
||||
if (elapsedMs - lastActionTime > 250) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityState.dead:
|
||||
if (isDying) {
|
||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
||||
if (deathFrame < 3) {
|
||||
spriteIndex = 93 + deathFrame; // Cycles 93, 94, 95
|
||||
} else {
|
||||
spriteIndex = 95; // Final dead frame
|
||||
isDying = false;
|
||||
}
|
||||
} else {
|
||||
spriteIndex = 95; // Final dead frame
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (movement: movement, newAngle: newAngle);
|
||||
|
||||
@@ -1,42 +1,28 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||
import 'package:wolf_dart/features/entities/entity.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||
|
||||
class Mutant extends Enemy {
|
||||
static const double speed = 0.045;
|
||||
static const double speed = 0.04;
|
||||
bool _hasFiredThisCycle = false;
|
||||
|
||||
static EnemyType get type => EnemyType.mutant;
|
||||
|
||||
Mutant({
|
||||
required super.x,
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(
|
||||
spriteIndex: EnemyType.mutant.spriteBaseIdx,
|
||||
spriteIndex: type.spriteBaseIdx,
|
||||
state: EntityState.idle,
|
||||
) {
|
||||
health = 45;
|
||||
damage = 10;
|
||||
}
|
||||
|
||||
static Mutant? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||
if (EnemyType.mutant.claimsMapId(objId)) {
|
||||
bool isPatrolling = objId >= EnemyType.mutant.mapBaseId + 18;
|
||||
|
||||
return Mutant(
|
||||
x: x,
|
||||
y: y,
|
||||
angle: MapObject.getAngle(objId),
|
||||
mapId: objId,
|
||||
)..state = isPatrolling ? EntityState.patrolling : EntityState.idle;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
({Coordinate2D movement, double newAngle}) update({
|
||||
required int elapsedMs,
|
||||
@@ -48,7 +34,6 @@ class Mutant extends Enemy {
|
||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||
double newAngle = angle;
|
||||
|
||||
// Mutants don't make wake-up noises in the original game!
|
||||
checkWakeUp(
|
||||
elapsedMs: elapsedMs,
|
||||
playerPosition: playerPosition,
|
||||
@@ -62,6 +47,7 @@ class Mutant extends Enemy {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
// Calculate angle diff for the octant logic
|
||||
double diff = angleToPlayer - newAngle;
|
||||
while (diff <= -math.pi) {
|
||||
diff += 2 * math.pi;
|
||||
@@ -69,80 +55,31 @@ class Mutant extends Enemy {
|
||||
while (diff > math.pi) {
|
||||
diff -= 2 * math.pi;
|
||||
}
|
||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
||||
if (octant < 0) octant += 8;
|
||||
|
||||
switch (state) {
|
||||
case EntityState.idle:
|
||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + octant;
|
||||
break;
|
||||
EnemyAnimation currentAnim = switch (state) {
|
||||
EntityState.patrolling => EnemyAnimation.walking,
|
||||
EntityState.attacking => EnemyAnimation.attacking,
|
||||
EntityState.pain => EnemyAnimation.pain,
|
||||
EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
|
||||
_ => EnemyAnimation.idle,
|
||||
};
|
||||
|
||||
case EntityState.patrolling:
|
||||
if (distance > 0.8) {
|
||||
double moveX = math.cos(angleToPlayer) * speed;
|
||||
double moveY = math.sin(angleToPlayer) * speed;
|
||||
movement = getValidMovement(
|
||||
Coordinate2D(moveX, moveY),
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
spriteIndex = type.getSpriteFromAnimation(
|
||||
animation: currentAnim,
|
||||
elapsedMs: elapsedMs,
|
||||
lastActionTime: lastActionTime,
|
||||
angleDiff: diff,
|
||||
);
|
||||
}
|
||||
|
||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
||||
spriteIndex =
|
||||
(EnemyType.mutant.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
|
||||
|
||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||
state = EntityState.attacking;
|
||||
lastActionTime = elapsedMs;
|
||||
_hasFiredThisCycle = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityState.attacking:
|
||||
int timeShooting = elapsedMs - lastActionTime;
|
||||
if (timeShooting < 150) {
|
||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + 46; // Aiming
|
||||
} else if (timeShooting < 300) {
|
||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + 47; // Firing
|
||||
if (!_hasFiredThisCycle) {
|
||||
if (state == EntityState.attacking) {
|
||||
int time = elapsedMs - lastActionTime;
|
||||
if (time >= 150 && !_hasFiredThisCycle) {
|
||||
onDamagePlayer(damage);
|
||||
_hasFiredThisCycle = true;
|
||||
}
|
||||
} else if (timeShooting < 450) {
|
||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + 48; // Recoil
|
||||
} else {
|
||||
} else if (time >= 300) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityState.pain:
|
||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + 44;
|
||||
if (elapsedMs - lastActionTime > 250) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityState.dead:
|
||||
if (isDying) {
|
||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
||||
if (deathFrame < 4) {
|
||||
spriteIndex = (EnemyType.mutant.spriteBaseIdx + 40) + deathFrame;
|
||||
} else {
|
||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + 45;
|
||||
isDying = false;
|
||||
}
|
||||
} else {
|
||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + 45;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (movement: movement, newAngle: newAngle);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||
import 'package:wolf_dart/features/entities/entity.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||
|
||||
class Officer extends Enemy {
|
||||
static const double speed = 0.055;
|
||||
@@ -23,20 +21,6 @@ class Officer extends Enemy {
|
||||
damage = 15;
|
||||
}
|
||||
|
||||
static Officer? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||
if (EnemyType.officer.claimsMapId(objId)) {
|
||||
bool isPatrolling = objId >= EnemyType.officer.mapBaseId + 18;
|
||||
|
||||
return Officer(
|
||||
x: x,
|
||||
y: y,
|
||||
angle: MapObject.getAngle(objId),
|
||||
mapId: objId,
|
||||
)..state = isPatrolling ? EntityState.patrolling : EntityState.idle;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
({Coordinate2D movement, double newAngle}) update({
|
||||
required int elapsedMs,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||
import 'package:wolf_dart/features/entities/entity.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||
|
||||
class SS extends Enemy {
|
||||
static const double speed = 0.04;
|
||||
@@ -23,20 +21,6 @@ class SS extends Enemy {
|
||||
damage = 20;
|
||||
}
|
||||
|
||||
static SS? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||
if (EnemyType.ss.claimsMapId(objId)) {
|
||||
bool isPatrolling = objId >= EnemyType.ss.mapBaseId + 18;
|
||||
|
||||
return SS(
|
||||
x: x,
|
||||
y: y,
|
||||
angle: MapObject.getAngle(objId),
|
||||
mapId: objId,
|
||||
)..state = isPatrolling ? EntityState.patrolling : EntityState.idle;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
({Coordinate2D movement, double newAngle}) update({
|
||||
required int elapsedMs,
|
||||
|
||||
@@ -2,12 +2,7 @@ import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||
import 'package:wolf_dart/features/entities/collectible.dart';
|
||||
import 'package:wolf_dart/features/entities/decorative.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/bosses/hans_grosse.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/dog.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/guard.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/mutant.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/officer.dart';
|
||||
import 'package:wolf_dart/features/entities/enemies/ss.dart';
|
||||
import 'package:wolf_dart/features/entities/entity.dart';
|
||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||
|
||||
@@ -16,17 +11,14 @@ typedef EntitySpawner =
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
Difficulty difficulty,
|
||||
);
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode,
|
||||
});
|
||||
|
||||
abstract class EntityRegistry {
|
||||
static final List<EntitySpawner> _spawners = [
|
||||
// Enemies need to try to spawn first
|
||||
Guard.trySpawn,
|
||||
Officer.trySpawn,
|
||||
SS.trySpawn,
|
||||
Mutant.trySpawn,
|
||||
Dog.trySpawn,
|
||||
Enemy.spawn,
|
||||
|
||||
// Bosses
|
||||
HansGrosse.trySpawn,
|
||||
@@ -41,11 +33,15 @@ abstract class EntityRegistry {
|
||||
double x,
|
||||
double y,
|
||||
Difficulty difficulty,
|
||||
int maxSprites,
|
||||
) {
|
||||
int maxSprites, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
// 1. Difficulty check before even looking for a spawner
|
||||
if (!MapObject.shouldSpawn(objId, difficulty)) return null;
|
||||
|
||||
// If the checkbox is checked, block non-Shareware enemies
|
||||
if (isSharewareMode && !MapObject.isSharewareCompatible(objId)) return null;
|
||||
|
||||
if (objId == 0) return null;
|
||||
|
||||
for (final spawner in _spawners) {
|
||||
|
||||
@@ -9,7 +9,7 @@ abstract class WolfMapParser {
|
||||
static List<WolfLevel> parseMaps(
|
||||
ByteData mapHead,
|
||||
ByteData gameMaps, {
|
||||
bool isShareware = false,
|
||||
bool isShareware = true,
|
||||
}) {
|
||||
List<WolfLevel> levels = [];
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
||||
y + 0.5,
|
||||
widget.difficulty,
|
||||
gameMap.sprites.length,
|
||||
isSharewareMode: isShareware,
|
||||
);
|
||||
|
||||
if (newEntity != null) {
|
||||
|
||||
Reference in New Issue
Block a user