Add shareware support and spawn correctly for difficulty levels
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
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 BrownGuard extends Enemy {
|
||||
static const double speed = 0.03;
|
||||
@@ -23,33 +25,18 @@ class BrownGuard extends Enemy {
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
int difficultyLevel,
|
||||
Difficulty difficulty,
|
||||
) {
|
||||
bool canSpawn = false;
|
||||
switch (difficultyLevel) {
|
||||
case 0:
|
||||
canSpawn = objId >= 108 && objId <= 115;
|
||||
break;
|
||||
case 1:
|
||||
canSpawn = objId >= 144 && objId <= 151;
|
||||
break;
|
||||
case 2:
|
||||
canSpawn = objId >= 180 && objId <= 187;
|
||||
break;
|
||||
case 3:
|
||||
canSpawn = objId >= 216 && objId <= 223;
|
||||
break;
|
||||
}
|
||||
|
||||
if (canSpawn) {
|
||||
// Use the range constants we defined in MapObject!
|
||||
if (objId >= MapObject.guardStart && objId <= MapObject.guardStart + 17) {
|
||||
return BrownGuard(
|
||||
x: x,
|
||||
y: y,
|
||||
angle: Enemy.getInitialAngle(objId),
|
||||
angle: MapObject.getAngle(objId),
|
||||
mapId: objId,
|
||||
);
|
||||
}
|
||||
return null; // Not a Brown Guard!
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -63,20 +50,11 @@ class BrownGuard extends Enemy {
|
||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||
double newAngle = angle;
|
||||
|
||||
// 1. Wake up logic (Matches SightPlayer & FirstSighting)
|
||||
if (state == EntityState.idle &&
|
||||
hasLineOfSight(playerPosition, isWalkable)) {
|
||||
if (reactionTimeMs == 0) {
|
||||
// Init reaction delay: ~1 to 4 tics in C (1 tic = ~14ms, but plays out longer in engine ticks).
|
||||
// Let's approximate human-feeling reaction time: 200ms - 800ms
|
||||
reactionTimeMs = elapsedMs + 200 + math.Random().nextInt(600);
|
||||
} else if (elapsedMs >= reactionTimeMs) {
|
||||
state =
|
||||
EntityState.patrolling; // Equivalent to FirstSighting chase frame
|
||||
lastActionTime = elapsedMs;
|
||||
reactionTimeMs = 0; // Reset
|
||||
}
|
||||
}
|
||||
checkWakeUp(
|
||||
elapsedMs: elapsedMs,
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
);
|
||||
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
@@ -85,14 +63,11 @@ class BrownGuard extends Enemy {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
// Octant logic remains the same
|
||||
double diff = newAngle - angleToPlayer;
|
||||
while (diff <= -math.pi) {
|
||||
diff += 2 * math.pi;
|
||||
}
|
||||
while (diff > math.pi) {
|
||||
diff -= 2 * math.pi;
|
||||
}
|
||||
// Octant logic (Directional sprites)
|
||||
double diff = angleToPlayer - newAngle;
|
||||
|
||||
while (diff <= -math.pi) diff += 2 * math.pi;
|
||||
while (diff > math.pi) diff -= 2 * math.pi;
|
||||
|
||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
||||
if (octant < 0) octant += 8;
|
||||
@@ -105,13 +80,10 @@ class BrownGuard extends Enemy {
|
||||
|
||||
case EntityState.patrolling:
|
||||
if (distance > 0.8) {
|
||||
// Jitter fix: Use continuous vector movement instead of single-axis snapping
|
||||
double moveX = math.cos(angleToPlayer) * speed;
|
||||
double moveY = math.sin(angleToPlayer) * speed;
|
||||
|
||||
Coordinate2D intendedMovement = Coordinate2D(moveX, moveY);
|
||||
|
||||
// Pass tryOpenDoor down!
|
||||
movement = getValidMovement(
|
||||
intendedMovement,
|
||||
isWalkable,
|
||||
@@ -119,12 +91,9 @@ class BrownGuard extends Enemy {
|
||||
);
|
||||
}
|
||||
|
||||
// Animation fix: Update the sprite so he actually turns and walks!
|
||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
||||
spriteIndex = 58 + (walkFrame * 8) + octant;
|
||||
|
||||
// Shooting fix: Give him permission to stop and shoot you
|
||||
// (1500ms delay between shots)
|
||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||
state = EntityState.shooting;
|
||||
@@ -132,20 +101,20 @@ class BrownGuard extends Enemy {
|
||||
_hasFiredThisCycle = false;
|
||||
}
|
||||
}
|
||||
break; // Fallthrough fix: Don't forget the break!
|
||||
break;
|
||||
|
||||
case EntityState.shooting:
|
||||
int timeShooting = elapsedMs - lastActionTime;
|
||||
if (timeShooting < 150) {
|
||||
spriteIndex = 96;
|
||||
spriteIndex = 96; // Aiming
|
||||
} else if (timeShooting < 300) {
|
||||
spriteIndex = 97;
|
||||
spriteIndex = 97; // Firing
|
||||
if (!_hasFiredThisCycle) {
|
||||
onDamagePlayer(10); // DAMAGING PLAYER
|
||||
onDamagePlayer(10);
|
||||
_hasFiredThisCycle = true;
|
||||
}
|
||||
} else if (timeShooting < 450) {
|
||||
spriteIndex = 98;
|
||||
spriteIndex = 98; // Recoil
|
||||
} else {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
@@ -153,7 +122,8 @@ class BrownGuard extends Enemy {
|
||||
break;
|
||||
|
||||
case EntityState.pain:
|
||||
spriteIndex = 94;
|
||||
spriteIndex = 94; // Ouch frame
|
||||
// Stay in pain for a brief moment, then resume attacking
|
||||
if (elapsedMs - lastActionTime > 250) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
@@ -164,9 +134,10 @@ class BrownGuard extends Enemy {
|
||||
if (isDying) {
|
||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
||||
if (deathFrame < 4) {
|
||||
spriteIndex = 90 + deathFrame - 1;
|
||||
// FIX: Removed the buggy "- 1"
|
||||
spriteIndex = 90 + deathFrame;
|
||||
} else {
|
||||
spriteIndex = 95;
|
||||
spriteIndex = 95; // Final dead frame
|
||||
isDying = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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; // Dogs are much faster than guards!
|
||||
static const double speed = 0.05;
|
||||
bool _hasBittenThisCycle = false;
|
||||
|
||||
Dog({
|
||||
@@ -14,32 +16,17 @@ class Dog extends Enemy {
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(
|
||||
spriteIndex: 99, // Dogs start at index 99 in VSWAP
|
||||
spriteIndex: 99,
|
||||
state: EntityState.idle,
|
||||
);
|
||||
|
||||
static Dog? trySpawn(int objId, double x, double y, int difficultyLevel) {
|
||||
bool canSpawn = false;
|
||||
switch (difficultyLevel) {
|
||||
case 0:
|
||||
canSpawn = objId >= 116 && objId <= 119;
|
||||
break;
|
||||
case 1:
|
||||
canSpawn = objId >= 152 && objId <= 155;
|
||||
break;
|
||||
case 2:
|
||||
canSpawn = objId >= 188 && objId <= 191;
|
||||
break;
|
||||
case 3:
|
||||
canSpawn = objId >= 224 && objId <= 227;
|
||||
break;
|
||||
}
|
||||
|
||||
if (canSpawn) {
|
||||
static Dog? trySpawn(int objId, double x, double y, Difficulty difficulty) {
|
||||
// The renderer already checked difficulty, so we just check the ID block!
|
||||
if (objId >= MapObject.dogStart && objId <= MapObject.dogStart + 17) {
|
||||
return Dog(
|
||||
x: x,
|
||||
y: y,
|
||||
angle: Enemy.getInitialAngle(objId),
|
||||
angle: MapObject.getAngle(objId),
|
||||
mapId: objId,
|
||||
);
|
||||
}
|
||||
@@ -51,23 +38,19 @@ 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, // NEW
|
||||
required void Function(int x, int y) tryOpenDoor,
|
||||
required void Function(int damage) onDamagePlayer,
|
||||
}) {
|
||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||
double newAngle = angle;
|
||||
|
||||
// 1. Wake up logic
|
||||
if (state == EntityState.idle &&
|
||||
hasLineOfSight(playerPosition, isWalkable)) {
|
||||
if (reactionTimeMs == 0) {
|
||||
reactionTimeMs = elapsedMs + 100 + math.Random().nextInt(200);
|
||||
} else if (elapsedMs >= reactionTimeMs) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
reactionTimeMs = 0;
|
||||
}
|
||||
}
|
||||
checkWakeUp(
|
||||
elapsedMs: elapsedMs,
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
baseReactionMs: 100,
|
||||
reactionVarianceMs: 200,
|
||||
);
|
||||
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
@@ -76,13 +59,10 @@ class Dog extends Enemy {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
double diff = newAngle - angleToPlayer;
|
||||
while (diff <= -math.pi) {
|
||||
diff += 2 * math.pi;
|
||||
}
|
||||
while (diff > math.pi) {
|
||||
diff -= 2 * math.pi;
|
||||
}
|
||||
double diff = angleToPlayer - newAngle;
|
||||
|
||||
while (diff <= -math.pi) diff += 2 * math.pi;
|
||||
while (diff > math.pi) diff -= 2 * math.pi;
|
||||
|
||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
||||
if (octant < 0) octant += 8;
|
||||
@@ -95,15 +75,12 @@ class Dog extends Enemy {
|
||||
|
||||
case EntityState.patrolling:
|
||||
if (distance > 0.8) {
|
||||
double deltaX = playerPosition.x - position.x;
|
||||
double deltaY = playerPosition.y - position.y;
|
||||
|
||||
double moveX = deltaX > 0 ? speed : (deltaX < 0 ? -speed : 0);
|
||||
double moveY = deltaY > 0 ? speed : (deltaY < 0 ? -speed : 0);
|
||||
// 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);
|
||||
|
||||
// Pass tryOpenDoor down!
|
||||
movement = getValidMovement(
|
||||
intendedMovement,
|
||||
isWalkable,
|
||||
@@ -135,6 +112,22 @@ class Dog extends Enemy {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,28 @@ abstract class Enemy extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
void checkWakeUp({
|
||||
required int elapsedMs,
|
||||
required Coordinate2D playerPosition,
|
||||
required bool Function(int x, int y) isWalkable,
|
||||
int baseReactionMs = 200,
|
||||
int reactionVarianceMs = 600,
|
||||
}) {
|
||||
if (state == EntityState.idle &&
|
||||
hasLineOfSight(playerPosition, isWalkable)) {
|
||||
if (reactionTimeMs == 0) {
|
||||
reactionTimeMs =
|
||||
elapsedMs +
|
||||
baseReactionMs +
|
||||
math.Random().nextInt(reactionVarianceMs);
|
||||
} else if (elapsedMs >= reactionTimeMs) {
|
||||
state = EntityState.patrolling;
|
||||
lastActionTime = elapsedMs;
|
||||
reactionTimeMs = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matches WL_STATE.C's 'CheckLine' using canonical Integer DDA traversal
|
||||
bool hasLineOfSight(
|
||||
Coordinate2D playerPosition,
|
||||
@@ -63,7 +85,7 @@ abstract class Enemy extends Entity {
|
||||
// 1. Proximity Check (Matches WL_STATE.C 'MINSIGHT')
|
||||
// If the player is very close, sight is automatic regardless of facing angle.
|
||||
// This compensates for our lack of a noise/gunshot alert system!
|
||||
if (position.distanceTo(playerPosition) < 2.0) {
|
||||
if (position.distanceTo(playerPosition) < 1.2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user