import 'dart:math' as math; import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/features/entities/collectible.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'; import 'package:wolf_dart/features/weapon/weapon.dart'; import 'package:wolf_dart/features/weapon/weapons/chain_gun.dart'; import 'package:wolf_dart/features/weapon/weapons/knife.dart'; import 'package:wolf_dart/features/weapon/weapons/machine_gun.dart'; import 'package:wolf_dart/features/weapon/weapons/pistol.dart'; enum WeaponSwitchState { idle, lowering, raising } class Player { // Spatial double x; double y; double angle; // Stats int health = 100; int ammo = 8; int score = 0; // Inventory bool hasGoldKey = false; bool hasSilverKey = false; bool hasMachineGun = false; bool hasChainGun = false; // Weapon System late Weapon currentWeapon; final Map weapons = { WeaponType.knife: Knife(), WeaponType.pistol: Pistol(), WeaponType.machineGun: null, WeaponType.chainGun: null, }; WeaponSwitchState switchState = WeaponSwitchState.idle; WeaponType? pendingWeaponType; // 0.0 is resting, 500.0 is fully off-screen double weaponAnimOffset = 0.0; // How fast the weapon drops/raises per tick final double switchSpeed = 30.0; Player({ required this.x, required this.y, required this.angle, }) { currentWeapon = weapons[WeaponType.pistol]!; } // Helper getter to interface with the RaycasterPainter Coordinate2D get position => Coordinate2D(x, y); // --- Weapon Switching & Animation Logic --- void updateWeaponSwitch() { if (switchState == WeaponSwitchState.lowering) { // If the map doesn't contain the pending weapon, stop immediately if (weapons[pendingWeaponType] == null) { switchState = WeaponSwitchState.idle; return; } weaponAnimOffset += switchSpeed; if (weaponAnimOffset >= 500.0) { weaponAnimOffset = 500.0; // We already know it's not null now, but we can keep the // fallback to pistol just to be extra safe. currentWeapon = weapons[pendingWeaponType]!; switchState = WeaponSwitchState.raising; } } else if (switchState == WeaponSwitchState.raising) { weaponAnimOffset -= switchSpeed; if (weaponAnimOffset <= 0) { weaponAnimOffset = 0.0; switchState = WeaponSwitchState.idle; } } } void requestWeaponSwitch(WeaponType weaponType) { if (switchState != WeaponSwitchState.idle) return; if (currentWeapon.state != WeaponState.idle) return; if (weaponType == currentWeapon.type) return; if (!weapons.containsKey(weaponType)) return; if (weaponType != WeaponType.knife && ammo <= 0) return; pendingWeaponType = weaponType; switchState = WeaponSwitchState.lowering; } // --- Health & Damage --- void takeDamage(int damage) { health = math.max(0, health - damage); if (health <= 0) { print("YOU DIED!"); } else { print("Ouch! ($health)"); } } void heal(int amount) { final int newHealth = math.min(100, health + amount); if (health < 100) { print("Feelin' better. ($newHealth)"); } health = newHealth; } void addAmmo(int amount) { final int newAmmo = math.min(99, ammo + amount); if (ammo < 99) { print("Hell yeah. ($newAmmo)"); } ammo = newAmmo; } bool tryPickup(Collectible item) { bool pickedUp = false; switch (item.type) { case CollectibleType.health: if (health >= 100) return false; heal(item.mapId == MapObjectId.dogFood ? 4 : 25); pickedUp = true; break; case CollectibleType.ammo: if (ammo >= 99) return false; int previousAmmo = ammo; addAmmo(8); if (currentWeapon is Knife && previousAmmo <= 0) { requestWeaponSwitch(WeaponType.pistol); } pickedUp = true; break; case CollectibleType.treasure: if (item.mapId == MapObjectId.cross) score += 100; if (item.mapId == MapObjectId.chalice) score += 500; if (item.mapId == MapObjectId.chest) score += 1000; if (item.mapId == MapObjectId.crown) score += 5000; if (item.mapId == MapObjectId.extraLife) { heal(100); addAmmo(25); } pickedUp = true; break; case CollectibleType.weapon: if (item.mapId == MapObjectId.machineGun) { if (weapons[WeaponType.machineGun] == null) { weapons[WeaponType.machineGun] = MachineGun(); hasMachineGun = true; } addAmmo(8); requestWeaponSwitch(WeaponType.machineGun); pickedUp = true; } if (item.mapId == MapObjectId.chainGun) { if (weapons[WeaponType.chainGun] == null) { weapons[WeaponType.chainGun] = ChainGun(); hasChainGun = true; } addAmmo(8); requestWeaponSwitch(WeaponType.chainGun); pickedUp = true; } break; case CollectibleType.key: if (item.mapId == MapObjectId.goldKey) hasGoldKey = true; if (item.mapId == MapObjectId.silverKey) hasSilverKey = true; pickedUp = true; break; } return pickedUp; } void fire(int currentTime) { if (switchState != WeaponSwitchState.idle) return; // We pass the isFiring state to handle automatic vs semi-auto behavior bool shotFired = currentWeapon.fire( currentTime, currentAmmo: ammo, ); if (shotFired && currentWeapon.type != WeaponType.knife) { ammo--; } } void releaseTrigger() { currentWeapon.releaseTrigger(); } /// Returns true only on the specific frame where the hit should be calculated void updateWeapon({ required int currentTime, required List entities, required bool Function(int x, int y) isWalkable, }) { int oldFrame = currentWeapon.frameIndex; currentWeapon.update(currentTime); // If we just crossed into the firing frame... if (currentWeapon.state == WeaponState.firing && oldFrame == 0 && currentWeapon.frameIndex == 1) { currentWeapon.performHitscan( playerX: x, playerY: y, playerAngle: angle, entities: entities, isWalkable: isWalkable, currentTime: currentTime, onEnemyKilled: (Enemy killedEnemy) { // Dynamic scoring based on the enemy type! int pointsToAdd = 0; switch (killedEnemy.runtimeType.toString()) { case 'BrownGuard': pointsToAdd = 100; break; case 'Dog': pointsToAdd = 200; break; // You can easily plug in future enemies here! // case 'SSOfficer': pointsToAdd = 500; break; default: pointsToAdd = 100; // Fallback } score += pointsToAdd; // Optional: Print to console so you can see it working print( "Killed ${killedEnemy.runtimeType}! +$pointsToAdd (Score: $score)", ); }, ); } if (currentWeapon.state == WeaponState.idle && ammo <= 0 && currentWeapon.type != WeaponType.knife) { requestWeaponSwitch(WeaponType.knife); } } }