Add shareware support and spawn correctly for difficulty levels

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-14 17:01:01 +01:00
parent 690ac1e7e6
commit 278c73a256
19 changed files with 383 additions and 238 deletions

View File

@@ -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 {