From 7835a6051e4d18f550236aba3f6150f0db8f396f Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Fri, 13 Mar 2026 19:54:22 +0100 Subject: [PATCH] Create a Decorative object class Signed-off-by: Hans Kokx --- lib/classes/decorative.dart | 22 +++++ lib/classes/enemy.dart | 60 ++++++++++++- lib/features/enemies/brown_guard.dart | 46 ++++++---- lib/features/renderer/renderer.dart | 119 ++++---------------------- 4 files changed, 122 insertions(+), 125 deletions(-) create mode 100644 lib/classes/decorative.dart diff --git a/lib/classes/decorative.dart b/lib/classes/decorative.dart new file mode 100644 index 0000000..90e0c8b --- /dev/null +++ b/lib/classes/decorative.dart @@ -0,0 +1,22 @@ +import 'package:wolf_dart/classes/entity.dart'; + +class Decorative extends Entity { + Decorative({ + required super.x, + required super.y, + required super.spriteIndex, + required super.mapId, + super.state = EntityState.staticObj, // Defaults to static + }); + + // Checks if the Map ID belongs to a standard decoration + static bool isDecoration(int objId) { + return (objId >= 23 && objId <= 70) || objId == 124; + } + + // Safely calculates the VSWAP sprite index for standard decorations + static int getSpriteIndex(int objId) { + if (objId == 124) return 95; // Dead guard + return objId - 21; + } +} diff --git a/lib/classes/enemy.dart b/lib/classes/enemy.dart index 3dc393e..64b2b36 100644 --- a/lib/classes/enemy.dart +++ b/lib/classes/enemy.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:wolf_dart/classes/entity.dart'; import 'package:wolf_dart/classes/linear_coordinates.dart'; @@ -12,12 +14,66 @@ abstract class Enemy extends Entity { super.lastActionTime, }); - // Every enemy must implement its own brain! + // Decodes the Map ID to figure out which way the enemy is facing + static double getInitialAngle(int objId) { + int normalizedId = (objId - 108) % 36; + int direction = normalizedId % 4; // 0=East, 1=North, 2=West, 3=South + + switch (direction) { + case 0: + return 0.0; + case 1: + return 3 * math.pi / 2; + case 2: + return math.pi; + case 3: + return math.pi / 2; + default: + return 0.0; + } + } + + // The enemy can now check its own line of sight! + bool hasLineOfSight( + LinearCoordinates player, + bool Function(int x, int y) isWalkable, + ) { + double dx = player.x - x; + double dy = player.y - y; + double distance = math.sqrt(dx * dx + dy * dy); + + // 1. FOV Check + double angleToPlayer = math.atan2(dy, dx); + double diff = angle - angleToPlayer; + + while (diff <= -math.pi) { + diff += 2 * math.pi; + } + while (diff > math.pi) { + diff -= 2 * math.pi; + } + + if (diff.abs() > math.pi / 2) return false; + + // 2. Map Check + double dirX = dx / distance; + double dirY = dy / distance; + double stepSize = 0.2; + for (double i = 0; i < distance; i += stepSize) { + int checkX = (x + dirX * i).toInt(); + int checkY = (y + dirY * i).toInt(); + + if (!isWalkable(checkX, checkY)) return false; + } + + return true; + } + + // Update signature is cleaner now void update({ required int elapsedMs, required LinearCoordinates player, required bool Function(int x, int y) isWalkable, - required bool Function(Entity entity) hasLineOfSight, required void Function(int damage) onDamagePlayer, }); } diff --git a/lib/features/enemies/brown_guard.dart b/lib/features/enemies/brown_guard.dart index 8beb710..435768b 100644 --- a/lib/features/enemies/brown_guard.dart +++ b/lib/features/enemies/brown_guard.dart @@ -18,17 +18,33 @@ class BrownGuard extends Enemy { state: EntityState.idle, ); + // Checks if a Map ID is a valid Brown Guard for the selected difficulty + static bool isSpawnableForDifficulty(int objId, int difficultyLevel) { + switch (difficultyLevel) { + case 0: + return objId >= 108 && objId <= 115; + case 1: + return objId >= 144 && objId <= 151; + case 2: + return objId >= 180 && objId <= 187; + case 3: + return objId >= 216 && objId <= 223; + default: + return false; + } + } + @override void update({ required int elapsedMs, required LinearCoordinates player, required bool Function(int x, int y) isWalkable, - required bool Function(Entity entity) hasLineOfSight, required void Function(int damage) onDamagePlayer, }) { // 1. Wake up if the player is spotted! if (state == EntityState.idle) { - if (hasLineOfSight(this)) { + // Look how clean this is now: + if (hasLineOfSight(player, isWalkable)) { state = EntityState.patrolling; lastActionTime = elapsedMs; } @@ -48,12 +64,8 @@ class BrownGuard extends Enemy { } double diff = angle - angleToPlayer; - while (diff <= -math.pi) { - diff += 2 * math.pi; - } - while (diff > math.pi) { - diff -= 2 * math.pi; - } + 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; @@ -61,7 +73,6 @@ class BrownGuard extends Enemy { if (state == EntityState.idle) { spriteIndex = 50 + octant; } else if (state == EntityState.patrolling) { - // A. Move towards the player if (distance > 0.8) { double moveX = x + math.cos(angle) * speed; double moveY = y + math.sin(angle) * speed; @@ -70,33 +81,30 @@ class BrownGuard extends Enemy { if (isWalkable(x.toInt(), moveY.toInt())) y = moveY; } - // B. Animate the walk cycle int walkFrame = (elapsedMs ~/ 150) % 4; spriteIndex = 58 + (walkFrame * 8) + octant; - // C. Decide to shoot! if (distance < 5.0 && elapsedMs - lastActionTime > 2000) { - if (hasLineOfSight(this)) { + // Clean call here too: + if (hasLineOfSight(player, isWalkable)) { state = EntityState.shooting; lastActionTime = elapsedMs; - _hasFiredThisCycle = false; // Arm the weapon + _hasFiredThisCycle = false; } } } else if (state == EntityState.shooting) { int timeShooting = elapsedMs - lastActionTime; if (timeShooting < 150) { - spriteIndex = 96; // Aiming + spriteIndex = 96; } else if (timeShooting < 300) { - spriteIndex = 97; // BANG! - - // DEAL DAMAGE ONCE! + spriteIndex = 97; if (!_hasFiredThisCycle) { - onDamagePlayer(10); // 10 damage per shot + onDamagePlayer(10); _hasFiredThisCycle = true; } } else if (timeShooting < 450) { - spriteIndex = 98; // Recoil + spriteIndex = 98; } else { state = EntityState.patrolling; lastActionTime = elapsedMs; diff --git a/lib/features/renderer/renderer.dart b/lib/features/renderer/renderer.dart index 16c3696..d77bc1e 100644 --- a/lib/features/renderer/renderer.dart +++ b/lib/features/renderer/renderer.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; +import 'package:wolf_dart/classes/decorative.dart'; import 'package:wolf_dart/classes/difficulty.dart'; import 'package:wolf_dart/classes/enemy.dart'; import 'package:wolf_dart/classes/entity.dart'; @@ -87,43 +88,31 @@ class _WolfRendererState extends State case 22: playerAngle = math.pi; } - } - // NEW: Populate the Entities! - else if (objId >= 23 && objId <= 70) { - int calculatedSpriteIndex = objId - 21; - if (calculatedSpriteIndex >= 0 && - calculatedSpriteIndex < gameMap.sprites.length) { + } // 1. POPULATE DECORATIONS & DEAD BODIES + else if (Decorative.isDecoration(objId)) { + int spriteIdx = Decorative.getSpriteIndex(objId); + if (spriteIdx >= 0 && spriteIdx < gameMap.sprites.length) { entities.add( - Entity( + Decorative( x: x + 0.5, y: y + 0.5, - spriteIndex: calculatedSpriteIndex, - state: EntityState.staticObj, + spriteIndex: spriteIdx, mapId: objId, + // NEW: Dynamically assign the state! + state: objId == 124 ? EntityState.dead : EntityState.staticObj, ), ); } - } - // NEW: The Dead Guard (FIXED INDEX) - else if (objId == 124) { - if (95 < gameMap.sprites.length) { - entities.add( - Entity( - x: x + 0.5, - y: y + 0.5, - spriteIndex: 95, - state: EntityState.dead, - mapId: objId, - ), - ); - } - } else if (_isGuardForDifficulty(objId)) { + } else if (BrownGuard.isSpawnableForDifficulty( + objId, + widget.difficulty.level, + )) { if (50 < gameMap.sprites.length) { entities.add( BrownGuard( x: x + 0.5, y: y + 0.5, - angle: _getGuardAngle(objId), + angle: Enemy.getInitialAngle(objId), mapId: objId, ), ); @@ -291,8 +280,7 @@ class _WolfRendererState extends State elapsedMs: elapsed.inMilliseconds, player: player, isWalkable: _isWalkable, - hasLineOfSight: _hasLineOfSight, - onDamagePlayer: _takeDamage, // Pass the callback! + onDamagePlayer: _takeDamage, ); } } @@ -305,83 +293,6 @@ class _WolfRendererState extends State setState(() {}); } - bool _isGuardForDifficulty(int objId) { - switch (widget.difficulty.level) { - case 0: // Baby - return objId >= 108 && objId <= 115; - case 1: // Easy - return objId >= 144 && objId <= 151; - case 2: // Normal - return objId >= 180 && objId <= 187; - case 3: // Hard - return objId >= 216 && objId <= 223; - default: - return false; - } - } - - // Decodes the Map ID to figure out which way the guard is facing - double _getGuardAngle(int objId) { - // Normalizes the ID across the 4 difficulty tiers - int normalizedId = (objId - 108) % 36; - int direction = normalizedId % 4; // 0=East, 1=North, 2=West, 3=South - - // Matches the player spawn angles you already set up - switch (direction) { - case 0: - return 0.0; // East - case 1: - return 3 * math.pi / 2; // North - case 2: - return math.pi; // West - case 3: - return math.pi / 2; // South - default: - return 0.0; - } - } - - bool _hasLineOfSight(Entity guard) { - double dx = player.x - guard.x; - double dy = player.y - guard.y; - double distance = math.sqrt(dx * dx + dy * dy); - - // 1. FOV Check (Are you in front of them?) - double angleToPlayer = math.atan2(dy, dx); - double diff = guard.angle - angleToPlayer; - - while (diff <= -math.pi) { - diff += 2 * math.pi; - } - while (diff > math.pi) { - diff -= 2 * math.pi; - } - - // A standard guard FOV is about 180 degrees (90 degrees left/right) - if (diff.abs() > math.pi / 2) { - return false; // You are behind them! - } - - // 2. Line of Sight Check (Are there walls in the way?) - double dirX = dx / distance; - double dirY = dy / distance; - - // Step along the ray in small increments to check for solid blocks - double stepSize = 0.2; - for (double i = 0; i < distance; i += stepSize) { - int checkX = (guard.x + dirX * i).toInt(); - int checkY = (guard.y + dirY * i).toInt(); - - // If we hit a solid wall or closed door, vision is blocked - if (!_isWalkable(checkX, checkY)) { - return false; - } - } - - // If we made it all the way to the player without hitting a wall... - return true; - } - // A helper method to handle getting shot void _takeDamage(int damage) { playerHealth -= damage;