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,
|
int objId,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
Difficulty _,
|
Difficulty difficulty, {
|
||||||
) {
|
bool isSharewareMode = false,
|
||||||
|
}) {
|
||||||
if (isCollectible(objId)) {
|
if (isCollectible(objId)) {
|
||||||
return Collectible(
|
return Collectible(
|
||||||
x: x,
|
x: x,
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ class Decorative extends Entity {
|
|||||||
int objId,
|
int objId,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
Difficulty _,
|
Difficulty difficulty, {
|
||||||
) {
|
bool isSharewareMode = false,
|
||||||
|
}) {
|
||||||
if (isDecoration(objId)) {
|
if (isDecoration(objId)) {
|
||||||
return Decorative(
|
return Decorative(
|
||||||
x: x,
|
x: x,
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ class HansGrosse extends Enemy {
|
|||||||
int objId,
|
int objId,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty, {
|
||||||
) {
|
bool isSharewareMode = false,
|
||||||
|
}) {
|
||||||
if (objId == MapObject.bossHansGrosse) {
|
if (objId == MapObject.bossHansGrosse) {
|
||||||
return HansGrosse(
|
return HansGrosse(
|
||||||
x: x,
|
x: x,
|
||||||
|
|||||||
@@ -1,37 +1,26 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
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/enemies/enemy.dart';
|
||||||
import 'package:wolf_dart/features/entities/entity.dart';
|
import 'package:wolf_dart/features/entities/entity.dart';
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart'; // NEW
|
|
||||||
|
|
||||||
class Dog extends Enemy {
|
class Dog extends Enemy {
|
||||||
static const double speed = 0.05;
|
static const double speed = 0.05;
|
||||||
bool _hasBittenThisCycle = false;
|
bool _hasBittenThisCycle = false;
|
||||||
|
|
||||||
|
static EnemyType get type => EnemyType.dog;
|
||||||
|
|
||||||
Dog({
|
Dog({
|
||||||
required super.x,
|
required super.x,
|
||||||
required super.y,
|
required super.y,
|
||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: EnemyType.dog.spriteBaseIdx,
|
spriteIndex: type.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
state: EntityState.idle,
|
||||||
);
|
) {
|
||||||
|
health = 1;
|
||||||
static Dog? trySpawn(int objId, double x, double y, Difficulty _) {
|
damage = 5;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -39,8 +28,8 @@ class Dog extends Enemy {
|
|||||||
required int elapsedMs,
|
required int elapsedMs,
|
||||||
required Coordinate2D playerPosition,
|
required Coordinate2D playerPosition,
|
||||||
required bool Function(int x, int y) isWalkable,
|
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 damage) onDamagePlayer,
|
||||||
|
required void Function(int x, int y) tryOpenDoor,
|
||||||
}) {
|
}) {
|
||||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||||
double newAngle = angle;
|
double newAngle = angle;
|
||||||
@@ -49,8 +38,6 @@ class Dog extends Enemy {
|
|||||||
elapsedMs: elapsedMs,
|
elapsedMs: elapsedMs,
|
||||||
playerPosition: playerPosition,
|
playerPosition: playerPosition,
|
||||||
isWalkable: isWalkable,
|
isWalkable: isWalkable,
|
||||||
baseReactionMs: 100,
|
|
||||||
reactionVarianceMs: 200,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
double distance = position.distanceTo(playerPosition);
|
double distance = position.distanceTo(playerPosition);
|
||||||
@@ -61,7 +48,6 @@ class Dog extends Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double diff = angleToPlayer - newAngle;
|
double diff = angleToPlayer - newAngle;
|
||||||
|
|
||||||
while (diff <= -math.pi) {
|
while (diff <= -math.pi) {
|
||||||
diff += 2 * math.pi;
|
diff += 2 * math.pi;
|
||||||
}
|
}
|
||||||
@@ -69,72 +55,35 @@ class Dog extends Enemy {
|
|||||||
diff -= 2 * math.pi;
|
diff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
EnemyAnimation currentAnim = switch (state) {
|
||||||
if (octant < 0) octant += 8;
|
EntityState.patrolling => EnemyAnimation.walking,
|
||||||
|
EntityState.attacking => EnemyAnimation.attacking,
|
||||||
|
EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
|
||||||
|
_ => EnemyAnimation.idle,
|
||||||
|
};
|
||||||
|
|
||||||
// 3. Clean State Machine
|
spriteIndex = type.getSpriteFromAnimation(
|
||||||
switch (state) {
|
animation: currentAnim,
|
||||||
case EntityState.idle:
|
elapsedMs: elapsedMs,
|
||||||
spriteIndex = 99 + octant;
|
lastActionTime: lastActionTime,
|
||||||
break;
|
angleDiff: diff,
|
||||||
|
);
|
||||||
|
|
||||||
case EntityState.patrolling:
|
if (state == EntityState.patrolling && distance < 1.0) {
|
||||||
if (distance > 0.8) {
|
state = EntityState.attacking;
|
||||||
// UPGRADED: Smooth vector movement instead of grid-snapping
|
lastActionTime = elapsedMs;
|
||||||
double moveX = math.cos(angleToPlayer) * speed;
|
_hasBittenThisCycle = false;
|
||||||
double moveY = math.sin(angleToPlayer) * speed;
|
}
|
||||||
|
|
||||||
Coordinate2D intendedMovement = Coordinate2D(moveX, moveY);
|
if (state == EntityState.attacking) {
|
||||||
|
int time = elapsedMs - lastActionTime;
|
||||||
movement = getValidMovement(
|
if (time >= 200 && !_hasBittenThisCycle) {
|
||||||
intendedMovement,
|
onDamagePlayer(damage);
|
||||||
isWalkable,
|
_hasBittenThisCycle = true;
|
||||||
tryOpenDoor,
|
} else if (time >= 400) {
|
||||||
);
|
state = EntityState.patrolling;
|
||||||
}
|
lastActionTime = elapsedMs;
|
||||||
|
}
|
||||||
int walkFrame = (elapsedMs ~/ 100) % 4;
|
|
||||||
spriteIndex = 107 + (walkFrame * 8) + octant;
|
|
||||||
|
|
||||||
if (distance < 1.0 && elapsedMs - lastActionTime > 1000) {
|
|
||||||
state = EntityState.attacking;
|
|
||||||
lastActionTime = elapsedMs;
|
|
||||||
_hasBittenThisCycle = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EntityState.attacking:
|
|
||||||
int timeAttacking = elapsedMs - lastActionTime;
|
|
||||||
if (timeAttacking < 200) {
|
|
||||||
spriteIndex = 139;
|
|
||||||
if (!_hasBittenThisCycle) {
|
|
||||||
onDamagePlayer(5);
|
|
||||||
_hasBittenThisCycle = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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);
|
return (movement: movement, newAngle: newAngle);
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
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/entity.dart';
|
||||||
|
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||||
|
|
||||||
|
enum EnemyAnimation {
|
||||||
|
idle,
|
||||||
|
walking,
|
||||||
|
attacking,
|
||||||
|
pain,
|
||||||
|
dying,
|
||||||
|
dead,
|
||||||
|
}
|
||||||
|
|
||||||
enum EnemyType {
|
enum EnemyType {
|
||||||
guard(mapBaseId: 108, spriteBaseIdx: 50),
|
guard(mapBaseId: 108, spriteBaseIdx: 50),
|
||||||
@@ -44,6 +60,93 @@ enum EnemyType {
|
|||||||
EnemyType.officer => index >= 238 && index <= 287,
|
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 {
|
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 x, int y) tryOpenDoor,
|
||||||
required void Function(int damage) onDamagePlayer,
|
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 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
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/enemies/enemy.dart';
|
||||||
import 'package:wolf_dart/features/entities/entity.dart';
|
import 'package:wolf_dart/features/entities/entity.dart';
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
|
||||||
|
|
||||||
class Guard extends Enemy {
|
class Guard extends Enemy {
|
||||||
static const double speed = 0.03;
|
static const double speed = 0.03;
|
||||||
bool _hasFiredThisCycle = false;
|
bool _hasFiredThisCycle = false;
|
||||||
|
|
||||||
|
static EnemyType get type => EnemyType.guard;
|
||||||
|
|
||||||
Guard({
|
Guard({
|
||||||
required super.x,
|
required super.x,
|
||||||
required super.y,
|
required super.y,
|
||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: EnemyType.guard.spriteBaseIdx,
|
spriteIndex: type.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
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
|
@override
|
||||||
({Coordinate2D movement, double newAngle}) update({
|
({Coordinate2D movement, double newAngle}) update({
|
||||||
required int elapsedMs,
|
required int elapsedMs,
|
||||||
@@ -58,9 +44,8 @@ class Guard extends Enemy {
|
|||||||
newAngle = angleToPlayer;
|
newAngle = angleToPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Octant logic (Directional sprites)
|
// Calculate angle diff for the octant logic
|
||||||
double diff = angleToPlayer - newAngle;
|
double diff = angleToPlayer - newAngle;
|
||||||
|
|
||||||
while (diff <= -math.pi) {
|
while (diff <= -math.pi) {
|
||||||
diff += 2 * math.pi;
|
diff += 2 * math.pi;
|
||||||
}
|
}
|
||||||
@@ -68,82 +53,32 @@ class Guard extends Enemy {
|
|||||||
diff -= 2 * math.pi;
|
diff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
// Helper to get sprite based on current state
|
||||||
if (octant < 0) octant += 8;
|
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
|
spriteIndex = type.getSpriteFromAnimation(
|
||||||
switch (state) {
|
animation: currentAnim,
|
||||||
case EntityState.idle:
|
elapsedMs: elapsedMs,
|
||||||
spriteIndex = 50 + octant;
|
lastActionTime: lastActionTime,
|
||||||
break;
|
angleDiff: diff,
|
||||||
|
);
|
||||||
|
|
||||||
case EntityState.patrolling:
|
// Logic triggers (Damage, State transitions)
|
||||||
if (distance > 0.8) {
|
if (state == EntityState.attacking) {
|
||||||
double moveX = math.cos(angleToPlayer) * speed;
|
int time = elapsedMs - lastActionTime;
|
||||||
double moveY = math.sin(angleToPlayer) * speed;
|
if (time >= 150 && time < 300 && !_hasFiredThisCycle) {
|
||||||
Coordinate2D intendedMovement = Coordinate2D(moveX, moveY);
|
onDamagePlayer(10);
|
||||||
|
_hasFiredThisCycle = true;
|
||||||
movement = getValidMovement(
|
} else if (time >= 450) {
|
||||||
intendedMovement,
|
state = EntityState.patrolling;
|
||||||
isWalkable,
|
lastActionTime = elapsedMs;
|
||||||
tryOpenDoor,
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
onDamagePlayer(10);
|
|
||||||
_hasFiredThisCycle = true;
|
|
||||||
}
|
|
||||||
} else if (timeShooting < 450) {
|
|
||||||
spriteIndex = 90; // Recoil (back to aim pose)
|
|
||||||
} else {
|
|
||||||
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);
|
return (movement: movement, newAngle: newAngle);
|
||||||
|
|||||||
@@ -1,42 +1,28 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
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/enemies/enemy.dart';
|
||||||
import 'package:wolf_dart/features/entities/entity.dart';
|
import 'package:wolf_dart/features/entities/entity.dart';
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
|
||||||
|
|
||||||
class Mutant extends Enemy {
|
class Mutant extends Enemy {
|
||||||
static const double speed = 0.045;
|
static const double speed = 0.04;
|
||||||
bool _hasFiredThisCycle = false;
|
bool _hasFiredThisCycle = false;
|
||||||
|
|
||||||
|
static EnemyType get type => EnemyType.mutant;
|
||||||
|
|
||||||
Mutant({
|
Mutant({
|
||||||
required super.x,
|
required super.x,
|
||||||
required super.y,
|
required super.y,
|
||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: EnemyType.mutant.spriteBaseIdx,
|
spriteIndex: type.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
state: EntityState.idle,
|
||||||
) {
|
) {
|
||||||
health = 45;
|
health = 45;
|
||||||
damage = 10;
|
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
|
@override
|
||||||
({Coordinate2D movement, double newAngle}) update({
|
({Coordinate2D movement, double newAngle}) update({
|
||||||
required int elapsedMs,
|
required int elapsedMs,
|
||||||
@@ -48,7 +34,6 @@ class Mutant extends Enemy {
|
|||||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||||
double newAngle = angle;
|
double newAngle = angle;
|
||||||
|
|
||||||
// Mutants don't make wake-up noises in the original game!
|
|
||||||
checkWakeUp(
|
checkWakeUp(
|
||||||
elapsedMs: elapsedMs,
|
elapsedMs: elapsedMs,
|
||||||
playerPosition: playerPosition,
|
playerPosition: playerPosition,
|
||||||
@@ -62,6 +47,7 @@ class Mutant extends Enemy {
|
|||||||
newAngle = angleToPlayer;
|
newAngle = angleToPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate angle diff for the octant logic
|
||||||
double diff = angleToPlayer - newAngle;
|
double diff = angleToPlayer - newAngle;
|
||||||
while (diff <= -math.pi) {
|
while (diff <= -math.pi) {
|
||||||
diff += 2 * math.pi;
|
diff += 2 * math.pi;
|
||||||
@@ -69,80 +55,31 @@ class Mutant extends Enemy {
|
|||||||
while (diff > math.pi) {
|
while (diff > math.pi) {
|
||||||
diff -= 2 * math.pi;
|
diff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
|
||||||
if (octant < 0) octant += 8;
|
|
||||||
|
|
||||||
switch (state) {
|
EnemyAnimation currentAnim = switch (state) {
|
||||||
case EntityState.idle:
|
EntityState.patrolling => EnemyAnimation.walking,
|
||||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + octant;
|
EntityState.attacking => EnemyAnimation.attacking,
|
||||||
break;
|
EntityState.pain => EnemyAnimation.pain,
|
||||||
|
EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
|
||||||
|
_ => EnemyAnimation.idle,
|
||||||
|
};
|
||||||
|
|
||||||
case EntityState.patrolling:
|
spriteIndex = type.getSpriteFromAnimation(
|
||||||
if (distance > 0.8) {
|
animation: currentAnim,
|
||||||
double moveX = math.cos(angleToPlayer) * speed;
|
elapsedMs: elapsedMs,
|
||||||
double moveY = math.sin(angleToPlayer) * speed;
|
lastActionTime: lastActionTime,
|
||||||
movement = getValidMovement(
|
angleDiff: diff,
|
||||||
Coordinate2D(moveX, moveY),
|
);
|
||||||
isWalkable,
|
|
||||||
tryOpenDoor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
if (state == EntityState.attacking) {
|
||||||
spriteIndex =
|
int time = elapsedMs - lastActionTime;
|
||||||
(EnemyType.mutant.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
|
if (time >= 150 && !_hasFiredThisCycle) {
|
||||||
|
onDamagePlayer(damage);
|
||||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
_hasFiredThisCycle = true;
|
||||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
} else if (time >= 300) {
|
||||||
state = EntityState.attacking;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
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) {
|
|
||||||
onDamagePlayer(damage);
|
|
||||||
_hasFiredThisCycle = true;
|
|
||||||
}
|
|
||||||
} else if (timeShooting < 450) {
|
|
||||||
spriteIndex = EnemyType.mutant.spriteBaseIdx + 48; // Recoil
|
|
||||||
} else {
|
|
||||||
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);
|
return (movement: movement, newAngle: newAngle);
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
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/enemies/enemy.dart';
|
||||||
import 'package:wolf_dart/features/entities/entity.dart';
|
import 'package:wolf_dart/features/entities/entity.dart';
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
|
||||||
|
|
||||||
class Officer extends Enemy {
|
class Officer extends Enemy {
|
||||||
static const double speed = 0.055;
|
static const double speed = 0.055;
|
||||||
@@ -23,20 +21,6 @@ class Officer extends Enemy {
|
|||||||
damage = 15;
|
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
|
@override
|
||||||
({Coordinate2D movement, double newAngle}) update({
|
({Coordinate2D movement, double newAngle}) update({
|
||||||
required int elapsedMs,
|
required int elapsedMs,
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
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/enemies/enemy.dart';
|
||||||
import 'package:wolf_dart/features/entities/entity.dart';
|
import 'package:wolf_dart/features/entities/entity.dart';
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
|
||||||
|
|
||||||
class SS extends Enemy {
|
class SS extends Enemy {
|
||||||
static const double speed = 0.04;
|
static const double speed = 0.04;
|
||||||
@@ -23,20 +21,6 @@ class SS extends Enemy {
|
|||||||
damage = 20;
|
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
|
@override
|
||||||
({Coordinate2D movement, double newAngle}) update({
|
({Coordinate2D movement, double newAngle}) update({
|
||||||
required int elapsedMs,
|
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/collectible.dart';
|
||||||
import 'package:wolf_dart/features/entities/decorative.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/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/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/entity.dart';
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||||
|
|
||||||
@@ -16,17 +11,14 @@ typedef EntitySpawner =
|
|||||||
int objId,
|
int objId,
|
||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty, {
|
||||||
);
|
bool isSharewareMode,
|
||||||
|
});
|
||||||
|
|
||||||
abstract class EntityRegistry {
|
abstract class EntityRegistry {
|
||||||
static final List<EntitySpawner> _spawners = [
|
static final List<EntitySpawner> _spawners = [
|
||||||
// Enemies need to try to spawn first
|
// Enemies need to try to spawn first
|
||||||
Guard.trySpawn,
|
Enemy.spawn,
|
||||||
Officer.trySpawn,
|
|
||||||
SS.trySpawn,
|
|
||||||
Mutant.trySpawn,
|
|
||||||
Dog.trySpawn,
|
|
||||||
|
|
||||||
// Bosses
|
// Bosses
|
||||||
HansGrosse.trySpawn,
|
HansGrosse.trySpawn,
|
||||||
@@ -41,11 +33,15 @@ abstract class EntityRegistry {
|
|||||||
double x,
|
double x,
|
||||||
double y,
|
double y,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty,
|
||||||
int maxSprites,
|
int maxSprites, {
|
||||||
) {
|
bool isSharewareMode = false,
|
||||||
|
}) {
|
||||||
// 1. Difficulty check before even looking for a spawner
|
// 1. Difficulty check before even looking for a spawner
|
||||||
if (!MapObject.shouldSpawn(objId, difficulty)) return null;
|
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;
|
if (objId == 0) return null;
|
||||||
|
|
||||||
for (final spawner in _spawners) {
|
for (final spawner in _spawners) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ abstract class WolfMapParser {
|
|||||||
static List<WolfLevel> parseMaps(
|
static List<WolfLevel> parseMaps(
|
||||||
ByteData mapHead,
|
ByteData mapHead,
|
||||||
ByteData gameMaps, {
|
ByteData gameMaps, {
|
||||||
bool isShareware = false,
|
bool isShareware = true,
|
||||||
}) {
|
}) {
|
||||||
List<WolfLevel> levels = [];
|
List<WolfLevel> levels = [];
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
y + 0.5,
|
y + 0.5,
|
||||||
widget.difficulty,
|
widget.difficulty,
|
||||||
gameMap.sprites.length,
|
gameMap.sprites.length,
|
||||||
|
isSharewareMode: isShareware,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newEntity != null) {
|
if (newEntity != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user