123 lines
3.2 KiB
Dart
123 lines
3.2 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
|
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
|
import 'package:wolf_dart/features/entities/entity.dart';
|
|
|
|
enum WeaponState { idle, firing }
|
|
|
|
enum WeaponType { knife, pistol, machineGun, chainGun }
|
|
|
|
abstract class Weapon {
|
|
final WeaponType type;
|
|
final int idleSprite;
|
|
final List<int> fireFrames;
|
|
final int damage;
|
|
final int msPerFrame;
|
|
final bool isAutomatic;
|
|
|
|
WeaponState state = WeaponState.idle;
|
|
int frameIndex = 0;
|
|
int lastFrameTime = 0;
|
|
bool _triggerReleased = true;
|
|
|
|
Weapon({
|
|
required this.type,
|
|
required this.idleSprite,
|
|
required this.fireFrames,
|
|
required this.damage,
|
|
this.msPerFrame = 100,
|
|
this.isAutomatic = true,
|
|
});
|
|
|
|
int get currentSprite =>
|
|
state == WeaponState.idle ? idleSprite : fireFrames[frameIndex];
|
|
|
|
void releaseTrigger() {
|
|
_triggerReleased = true;
|
|
}
|
|
|
|
bool fire(int currentTime, {required int currentAmmo}) {
|
|
if (state == WeaponState.idle && currentAmmo > 0) {
|
|
if (!isAutomatic && !_triggerReleased) return false;
|
|
|
|
state = WeaponState.firing;
|
|
frameIndex = 0;
|
|
lastFrameTime = currentTime;
|
|
_triggerReleased = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void update(int currentTime) {
|
|
if (state == WeaponState.firing) {
|
|
if (currentTime - lastFrameTime > msPerFrame) {
|
|
frameIndex++;
|
|
lastFrameTime = currentTime;
|
|
if (frameIndex >= fireFrames.length) {
|
|
state = WeaponState.idle;
|
|
frameIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NEW: The weapon calculates its own hits and applies damage!
|
|
void performHitscan({
|
|
required double playerX,
|
|
required double playerY,
|
|
required double playerAngle,
|
|
required List<Entity> entities,
|
|
required bool Function(int x, int y) isWalkable,
|
|
required int currentTime,
|
|
required void Function(Enemy killedEnemy) onEnemyKilled,
|
|
}) {
|
|
Enemy? closestEnemy;
|
|
double minDistance = 15.0;
|
|
|
|
for (Entity entity in entities) {
|
|
if (entity is Enemy && entity.state != EntityState.dead) {
|
|
double dx = entity.x - playerX;
|
|
double dy = entity.y - playerY;
|
|
double angleToEnemy = math.atan2(dy, dx);
|
|
|
|
double angleDiff = playerAngle - angleToEnemy;
|
|
while (angleDiff <= -math.pi) {
|
|
angleDiff += 2 * math.pi;
|
|
}
|
|
while (angleDiff > math.pi) {
|
|
angleDiff -= 2 * math.pi;
|
|
}
|
|
double dist = math.sqrt(dx * dx + dy * dy);
|
|
double threshold = 0.2 / dist;
|
|
|
|
if (angleDiff.abs() < threshold) {
|
|
Coordinate2D source = Coordinate2D(playerX, playerY);
|
|
|
|
if (entity.hasLineOfSightFrom(
|
|
source,
|
|
playerAngle,
|
|
dist,
|
|
isWalkable,
|
|
)) {
|
|
if (dist < minDistance) {
|
|
minDistance = dist;
|
|
closestEnemy = entity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (closestEnemy != null) {
|
|
closestEnemy.takeDamage(damage, currentTime);
|
|
// If the shot was fatal, pass the enemy back so the Player class
|
|
// can calculate the correct score based on enemy type!
|
|
if (closestEnemy.state == EntityState.dead) {
|
|
onEnemyKilled(closestEnemy);
|
|
}
|
|
}
|
|
}
|
|
}
|