305 lines
9 KiB
Dart
305 lines
9 KiB
Dart
import 'package:flame/components.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shitman/game/shitman_game.dart';
|
|
import 'package:shitman/game/components/poop_bag.dart';
|
|
import 'package:shitman/game/components/house_components.dart';
|
|
import 'package:shitman/game/components/vision_cone.dart';
|
|
import 'package:shitman/game/components/base.dart';
|
|
import 'package:shitman/game/levels/operation_shitstorm.dart';
|
|
import 'package:shitman/settings/app_settings.dart';
|
|
import 'dart:math';
|
|
|
|
class Player extends InteractiveShit with Ambulatory, AppSettings {
|
|
static const double speed = 100.0;
|
|
static const double playerSize = 32.0;
|
|
|
|
Vector2 velocity = Vector2.zero();
|
|
bool hasPoopBag = true;
|
|
bool isHidden = false;
|
|
bool isCaught = false;
|
|
double stealthLevel = 0.0; // 0.0 = fully visible, 1.0 = completely hidden
|
|
PoopBag? placedPoopBag;
|
|
VisionCone? playerVisionCone;
|
|
double lastMovementDirection = 0.0;
|
|
|
|
// Mission state
|
|
bool isEscaping = false;
|
|
double escapeTimeLimit = 30.0; // 30 seconds to escape
|
|
double escapeTimeRemaining = 0.0;
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await super.onLoad();
|
|
await initSettings();
|
|
|
|
// Create a simple colored rectangle as player
|
|
size = Vector2.all(playerSize);
|
|
position = Vector2(200, 200); // Start at center intersection
|
|
|
|
// Create player vision cone
|
|
playerVisionCone = VisionCone(
|
|
origin: size / 2, // Relative to player center
|
|
direction: lastMovementDirection,
|
|
range: 80.0,
|
|
fov: pi / 2, // 90 degrees
|
|
color: Colors.blue,
|
|
opacity: 0.0, // Start hidden
|
|
);
|
|
add(playerVisionCone!);
|
|
|
|
appLog.fine('Player loaded at position $position');
|
|
}
|
|
|
|
void handleInput(Set<LogicalKeyboardKey> keysPressed) {
|
|
// Don't handle input if caught
|
|
if (isCaught) {
|
|
velocity = Vector2.zero();
|
|
return;
|
|
}
|
|
|
|
velocity = Vector2.zero();
|
|
|
|
// Movement controls
|
|
if (keysPressed.contains(LogicalKeyboardKey.arrowUp) ||
|
|
keysPressed.contains(LogicalKeyboardKey.keyW)) {
|
|
velocity.y -= speed;
|
|
}
|
|
if (keysPressed.contains(LogicalKeyboardKey.arrowDown) ||
|
|
keysPressed.contains(LogicalKeyboardKey.keyS)) {
|
|
velocity.y += speed;
|
|
}
|
|
if (keysPressed.contains(LogicalKeyboardKey.arrowLeft) ||
|
|
keysPressed.contains(LogicalKeyboardKey.keyA)) {
|
|
velocity.x -= speed;
|
|
}
|
|
if (keysPressed.contains(LogicalKeyboardKey.arrowRight) ||
|
|
keysPressed.contains(LogicalKeyboardKey.keyD)) {
|
|
velocity.x += speed;
|
|
}
|
|
}
|
|
|
|
void handleAction(LogicalKeyboardKey key) {
|
|
// Don't handle actions if caught
|
|
if (isCaught) return;
|
|
|
|
// Action controls
|
|
if (key == LogicalKeyboardKey.space) {
|
|
placePoop();
|
|
}
|
|
if (key == LogicalKeyboardKey.keyE) {
|
|
ringDoorbell();
|
|
}
|
|
}
|
|
|
|
void updateStealthLevel(double dt) {
|
|
// Simple stealth calculation - can be enhanced later
|
|
// For now, player is more hidden when moving slowly or not at all
|
|
if (velocity.length < 50) {
|
|
stealthLevel = (stealthLevel + dt * 0.5).clamp(0.0, 1.0);
|
|
} else {
|
|
stealthLevel = (stealthLevel - dt * 1.5).clamp(0.0, 1.0);
|
|
}
|
|
|
|
isHidden = stealthLevel > 0.7;
|
|
}
|
|
|
|
void placePoop() {
|
|
if (!hasPoopBag) return;
|
|
|
|
appLog.fine('Placing poop bag at $position');
|
|
|
|
// Create and place the poop bag
|
|
placedPoopBag = PoopBag();
|
|
placedPoopBag!.position =
|
|
position + Vector2(playerSize / 2, playerSize + 10);
|
|
game.world.add(placedPoopBag!);
|
|
|
|
hasPoopBag = false;
|
|
|
|
// Check if near target house
|
|
checkMissionProgress();
|
|
}
|
|
|
|
void ringDoorbell() {
|
|
appLog.fine('Attempting to ring doorbell');
|
|
|
|
// Check if near any target house door
|
|
final currentLevel = game.world.children.whereType<OperationShitstorm>().firstOrNull;
|
|
if (currentLevel != null && currentLevel.isPlayerNearTarget(position)) {
|
|
if (placedPoopBag != null) {
|
|
// Light the poop bag on fire
|
|
placedPoopBag!.lightOnFire();
|
|
appLog.info('Ding dong! Poop bag is lit! RUN!');
|
|
|
|
// Start escape timer - player has limited time to escape
|
|
startEscapeSequence();
|
|
} else {
|
|
appLog.fine('Need to place poop bag first!');
|
|
}
|
|
} else {
|
|
appLog.fine('Not near target house door');
|
|
}
|
|
}
|
|
|
|
void startEscapeSequence() {
|
|
appLog.info('Escape sequence started! Get to the edge of the map!');
|
|
isEscaping = true;
|
|
escapeTimeRemaining = escapeTimeLimit;
|
|
|
|
// TODO: Show escape timer UI
|
|
// TODO: Highlight escape routes on the map
|
|
}
|
|
|
|
/// Check if player is at an escape point (edge of the map)
|
|
bool isAtEscapePoint() {
|
|
const double escapeZoneThreshold = 50.0;
|
|
|
|
// Check if near any edge of the level
|
|
return position.x < escapeZoneThreshold ||
|
|
position.y < escapeZoneThreshold ||
|
|
position.x > (5 * 100) - escapeZoneThreshold || // 5x5 grid * 100 cell size
|
|
position.y > (5 * 100) - escapeZoneThreshold;
|
|
}
|
|
|
|
void checkMissionProgress() {
|
|
// Check if near target house and has placed poop bag
|
|
final currentLevel = game.world.children.whereType<OperationShitstorm>().firstOrNull;
|
|
final targetPos = currentLevel?.getTargetPosition();
|
|
if (targetPos != null && placedPoopBag != null) {
|
|
final distance = (position - targetPos).length;
|
|
if (distance < 80) {
|
|
appLog.fine('Near target house with poop bag placed!');
|
|
}
|
|
}
|
|
}
|
|
|
|
void getDetected() {
|
|
appLog.warning('Player detected! Mission failed!');
|
|
isCaught = true;
|
|
velocity = Vector2.zero(); // Stop movement immediately
|
|
game.failMission();
|
|
}
|
|
|
|
@override
|
|
void update(double dt) {
|
|
super.update(dt);
|
|
|
|
// Apply movement
|
|
if (velocity.length > 0) {
|
|
velocity = velocity.normalized() * speed;
|
|
position += velocity * dt;
|
|
|
|
// Update movement direction for vision cone
|
|
lastMovementDirection = atan2(velocity.y, velocity.x);
|
|
|
|
// Keep player on screen (basic bounds checking)
|
|
position.x = position.x.clamp(0, 800 - size.x);
|
|
position.y = position.y.clamp(0, 600 - size.y);
|
|
}
|
|
|
|
// Update stealth level based on environment
|
|
updateStealthLevel(dt);
|
|
|
|
// Handle escape sequence
|
|
if (isEscaping) {
|
|
escapeTimeRemaining -= dt;
|
|
|
|
// Check if time ran out
|
|
if (escapeTimeRemaining <= 0) {
|
|
appLog.warning('Time ran out! Mission failed!');
|
|
getDetected();
|
|
return;
|
|
}
|
|
|
|
// Check if player reached escape point (edge of map)
|
|
if (isAtEscapePoint()) {
|
|
appLog.info('Successfully escaped! Mission complete!');
|
|
isEscaping = false;
|
|
game.completeCurrentMission();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for detection by houses (only if not already caught)
|
|
if (!isCaught) {
|
|
checkForDetection();
|
|
}
|
|
|
|
// Update player vision cone
|
|
if (playerVisionCone != null) {
|
|
playerVisionCone!.updatePosition(size / 2, lastMovementDirection);
|
|
|
|
// Update vision cone visibility based on settings
|
|
try {
|
|
final showVisionCones = appSettings.getBool('game.show_vision_cones');
|
|
playerVisionCone!.updateOpacity(showVisionCones ? 0.2 : 0.0);
|
|
} catch (e) {
|
|
playerVisionCone!.updateOpacity(0.0); // Hide if settings not ready
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkForDetection() {
|
|
// Check for detection from houses in any active level
|
|
final houses = game.world.children
|
|
.expand((component) => component.children)
|
|
.whereType<HouseComponent>();
|
|
|
|
for (final house in houses) {
|
|
if (house.detectsPlayer(position, stealthLevel)) {
|
|
getDetected();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void render(Canvas canvas) {
|
|
// Draw player as rectangle with color based on stealth level
|
|
final playerPaint = Paint()
|
|
..color = isHidden
|
|
? const Color(0xFF00FF00).withValues(alpha: 0.7) // Green when hidden
|
|
: const Color(0xFF0000FF).withValues(alpha: 0.9); // Blue when visible
|
|
|
|
canvas.drawRect(size.toRect(), playerPaint);
|
|
|
|
// Draw stealth indicator in debug mode
|
|
try {
|
|
final debugMode = appSettings.getBool('game.debug_mode');
|
|
if (debugMode) {
|
|
final stealthPaint = Paint()
|
|
..color = Color.lerp(
|
|
const Color(0xFFFF0000),
|
|
const Color(0xFF00FF00),
|
|
stealthLevel,
|
|
)!;
|
|
canvas.drawRect(Rect.fromLTWH(-5, -10, size.x + 10, 5), stealthPaint);
|
|
}
|
|
} catch (e) {
|
|
// Continue without debug rendering if settings not available
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> reset() async {
|
|
hasPoopBag = true;
|
|
isHidden = false;
|
|
isCaught = false;
|
|
isEscaping = false;
|
|
escapeTimeRemaining = 0.0;
|
|
stealthLevel = 0.0;
|
|
velocity = Vector2.zero();
|
|
lastMovementDirection = 0.0;
|
|
placedPoopBag = null;
|
|
position = Vector2(200, 200); // Reset to starting position
|
|
|
|
// Reset vision cone
|
|
if (playerVisionCone != null) {
|
|
await playerVisionCone!.reset();
|
|
playerVisionCone!.updatePosition(size / 2, lastMovementDirection);
|
|
}
|
|
|
|
appLog.fine('Player reset to initial state');
|
|
}
|
|
}
|