Can now open secret walls and pick up machine gun

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-14 15:34:27 +01:00
parent 9f5f29100b
commit 001c7c3131
13 changed files with 545 additions and 120 deletions

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:wolf_dart/classes/coordinate_2d.dart';
import 'package:wolf_dart/classes/matrix.dart';
import 'package:wolf_dart/features/entities/entity.dart';
import 'package:wolf_dart/features/entities/pushwall_manager.dart'; // NEW IMPORT
import 'package:wolf_dart/features/player/player.dart';
import 'package:wolf_dart/features/renderer/color_palette.dart';
@@ -13,6 +14,7 @@ class RaycasterPainter extends CustomPainter {
final Player player;
final double fov;
final Map<String, double> doorOffsets;
final Pushwall? activePushwall; // NEW
final List<Matrix<int>> sprites;
final List<Entity> entities;
@@ -22,6 +24,7 @@ class RaycasterPainter extends CustomPainter {
required this.player,
required this.fov,
required this.doorOffsets,
this.activePushwall, // NEW
required this.sprites,
required this.entities,
});
@@ -49,13 +52,11 @@ class RaycasterPainter extends CustomPainter {
List<double> zBuffer = List.filled(renderWidth, 0.0);
// --- Coordinate2D Camera Vectors ---
Coordinate2D dir = Coordinate2D(
math.cos(player.angle),
math.sin(player.angle),
);
// The camera plane is perpendicular to the direction vector, scaled by FOV
Coordinate2D plane = Coordinate2D(-dir.y, dir.x) * math.tan(fov / 2);
// --- 1. CAST WALLS ---
@@ -70,7 +71,7 @@ class RaycasterPainter extends CustomPainter {
double sideDistY;
double deltaDistX = (rayDir.x == 0) ? 1e30 : (1.0 / rayDir.x).abs();
double deltaDistY = (rayDir.y == 0) ? 1e30 : (1.0 / rayDir.y).abs();
double perpWallDist;
double perpWallDist = 0.0;
int stepX;
int stepY;
@@ -78,7 +79,9 @@ class RaycasterPainter extends CustomPainter {
bool hitOutOfBounds = false;
int side = 0;
int hitWallId = 0;
double doorOffset = 0.0;
double textureOffset = 0.0; // Replaces doorOffset to handle both
bool customDistCalculated = false; // Flag to skip standard distance
Set<String> ignoredDoors = {};
if (rayDir.x < 0) {
@@ -115,9 +118,11 @@ class RaycasterPainter extends CustomPainter {
hit = true;
hitOutOfBounds = true;
} else if (map[mapY][mapX] > 0) {
String doorKey = '$mapX,$mapY';
if (map[mapY][mapX] >= 90 && !ignoredDoors.contains(doorKey)) {
double currentOffset = doorOffsets[doorKey] ?? 0.0;
String mapKey = '$mapX,$mapY';
// --- DOOR LOGIC ---
if (map[mapY][mapX] >= 90 && !ignoredDoors.contains(mapKey)) {
double currentOffset = doorOffsets[mapKey] ?? 0.0;
if (currentOffset > 0.0) {
double perpWallDistTemp = (side == 0)
? (sideDistX - deltaDistX)
@@ -127,23 +132,98 @@ class RaycasterPainter extends CustomPainter {
: player.x + perpWallDistTemp * rayDir.x;
wallXTemp -= wallXTemp.floor();
if (wallXTemp < currentOffset) {
ignoredDoors.add(doorKey);
continue;
ignoredDoors.add(mapKey);
continue; // Ray passed through the open door gap
}
}
doorOffset = currentOffset;
hit = true;
hitWallId = map[mapY][mapX];
textureOffset = currentOffset;
}
// --- PUSHWALL LOGIC ---
else if (activePushwall != null &&
mapX == activePushwall!.x &&
mapY == activePushwall!.y) {
hit = true;
hitWallId = map[mapY][mapX];
double pOffset = activePushwall!.offset;
int pDirX = activePushwall!.dirX;
int pDirY = activePushwall!.dirY;
perpWallDist = (side == 0)
? (sideDistX - deltaDistX)
: (sideDistY - deltaDistY);
// Did we hit the face that is being pushed deeper?
if (side == 0 && pDirX != 0) {
if (pDirX == stepX) {
double intersect = perpWallDist + pOffset * deltaDistX;
if (intersect < sideDistY) {
perpWallDist = intersect; // Hit the recessed front face
} else {
side =
1; // Missed the front face, hit the newly exposed side!
perpWallDist = sideDistY - deltaDistY;
}
} else {
perpWallDist -= (1.0 - pOffset) * deltaDistX;
}
} else if (side == 1 && pDirY != 0) {
if (pDirY == stepY) {
double intersect = perpWallDist + pOffset * deltaDistY;
if (intersect < sideDistX) {
perpWallDist = intersect;
} else {
side = 0;
perpWallDist = sideDistX - deltaDistX;
}
} else {
perpWallDist -= (1.0 - pOffset) * deltaDistY;
}
} else {
// We hit the side of the sliding block. Did the ray slip behind it?
double wallFraction = (side == 0)
? player.y + perpWallDist * rayDir.y
: player.x + perpWallDist * rayDir.x;
wallFraction -= wallFraction.floor();
if (side == 0) {
if (pDirY == 1 && wallFraction < pOffset) hit = false;
if (pDirY == -1 && wallFraction > (1.0 - pOffset)) hit = false;
if (hit) {
textureOffset =
pOffset * pDirY; // Stick the texture to the block
}
} else {
if (pDirX == 1 && wallFraction < pOffset) hit = false;
if (pDirX == -1 && wallFraction > (1.0 - pOffset)) hit = false;
if (hit) {
textureOffset =
pOffset * pDirX; // Stick the texture to the block
}
}
}
if (!hit) continue; // The ray slipped past! Keep looping.
customDistCalculated = true; // Lock in our custom distance math
}
// --- STANDARD WALL ---
else {
hit = true;
hitWallId = map[mapY][mapX];
}
hit = true;
hitWallId = map[mapY][mapX];
}
}
if (hitOutOfBounds) continue;
if (side == 0) {
perpWallDist = (sideDistX - deltaDistX);
} else {
perpWallDist = (sideDistY - deltaDistY);
// Apply standard math ONLY if we didn't calculate a sub-tile pushwall distance
if (!customDistCalculated) {
if (side == 0) {
perpWallDist = (sideDistX - deltaDistX);
} else {
perpWallDist = (sideDistY - deltaDistY);
}
}
zBuffer[x] = perpWallDist;
@@ -164,15 +244,15 @@ class RaycasterPainter extends CustomPainter {
size,
hitWallId,
textures,
doorOffset,
textureOffset,
columnPaint,
);
}
// --- 2. DRAW SPRITES ---
// (Keep your existing sprite rendering logic exactly the same)
List<Entity> activeSprites = List.from(entities);
// Sort sprites from furthest to closest using Coordinate2D
activeSprites.sort((a, b) {
double distA = player.position.distanceTo(a.position);
double distB = player.position.distanceTo(b.position);
@@ -180,10 +260,8 @@ class RaycasterPainter extends CustomPainter {
});
for (Entity entity in activeSprites) {
// Relative position to player
Coordinate2D spritePos = entity.position - player.position;
// Transform sprite with the inverse camera matrix
double invDet = 1.0 / (plane.x * dir.y - dir.x * plane.y);
double transformX = invDet * (dir.y * spritePos.x - dir.x * spritePos.y);
double transformY =
@@ -245,7 +323,7 @@ class RaycasterPainter extends CustomPainter {
Size size,
int hitWallId,
List<Matrix<int>> textures,
double doorOffset,
double textureOffset,
Paint paint,
) {
if (distance <= 0.01) distance = 0.01;
@@ -253,15 +331,19 @@ class RaycasterPainter extends CustomPainter {
double wallHeight = size.height / distance;
int drawStart = ((size.height / 2) - (wallHeight / 2)).toInt();
int texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
int texX = (wallX * 64).toInt().clamp(0, 63);
int texNum;
int texX;
if (hitWallId >= 90) {
// DOORS
texNum = 98.clamp(0, textures.length - 1);
texX = ((wallX - doorOffset) * 64).toInt().clamp(0, 63);
texX = ((wallX - textureOffset) * 64).toInt().clamp(0, 63);
} else {
// WALLS & PUSHWALLS
texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2);
if (side == 1) texNum += 1;
// We apply the modulo % 1.0 to handle negative texture offsets smoothly!
texX = (((wallX - textureOffset) % 1.0) * 64).toInt().clamp(0, 63);
}
if (side == 0 && math.cos(player.angle) > 0) texX = 63 - texX;

View File

@@ -10,6 +10,7 @@ import 'package:wolf_dart/features/entities/door_manager.dart';
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
import 'package:wolf_dart/features/entities/entity.dart';
import 'package:wolf_dart/features/entities/entity_registry.dart';
import 'package:wolf_dart/features/entities/pushwall_manager.dart';
import 'package:wolf_dart/features/input/input_manager.dart';
import 'package:wolf_dart/features/map/wolf_map.dart';
import 'package:wolf_dart/features/player/player.dart';
@@ -38,6 +39,7 @@ class _WolfRendererState extends State<WolfRenderer>
with SingleTickerProviderStateMixin {
final InputManager inputManager = InputManager();
final DoorManager doorManager = DoorManager();
final PushwallManager pushwallManager = PushwallManager();
late Ticker _gameLoop;
final FocusNode _focusNode = FocusNode();
@@ -68,6 +70,8 @@ class _WolfRendererState extends State<WolfRenderer>
final Matrix<int> objectLevel = gameMap.levels[0].objectGrid;
pushwallManager.initPushwalls(currentLevel, objectLevel);
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) {
int objId = objectLevel[y][x];
@@ -180,6 +184,7 @@ class _WolfRendererState extends State<WolfRenderer>
final inputResult = _processInputs(elapsed);
doorManager.update(elapsed);
pushwallManager.update(elapsed, currentLevel);
// 2. Explicit State Updates
player.updateWeaponSwitch();
@@ -214,11 +219,6 @@ class _WolfRendererState extends State<WolfRenderer>
setState(() {});
}
void _takeDamage(int damage) {
player.takeDamage(damage);
damageFlashOpacity = 0.5;
}
// Returns a Record containing both movement delta and rotation delta
({Coordinate2D movement, double dAngle}) _processInputs(Duration elapsed) {
inputManager.update();
@@ -235,6 +235,8 @@ class _WolfRendererState extends State<WolfRenderer>
if (inputManager.isFiring) {
player.fire(elapsed.inMilliseconds);
} else {
player.releaseTrigger();
}
// Calculate intended rotation
@@ -260,6 +262,12 @@ class _WolfRendererState extends State<WolfRenderer>
player.y,
player.angle,
);
pushwallManager.handleInteraction(
player.x,
player.y,
player.angle,
currentLevel,
);
}
return (movement: movement, dAngle: dAngle);
@@ -301,37 +309,50 @@ class _WolfRendererState extends State<WolfRenderer>
return Coordinate2D(newX, newY);
}
// renderer.dart
void _updateEntities(Duration elapsed) {
List<Entity> itemsToRemove = [];
List<Entity> itemsToAdd = []; // NEW: Buffer for dropped items
for (Entity entity in entities) {
if (entity is Enemy) {
// 1. Get Intent
// 1. Get Intent (Now passing tryOpenDoor!)
final intent = entity.update(
elapsedMs: elapsed.inMilliseconds,
playerPosition: player.position,
isWalkable: _isWalkable,
onDamagePlayer: _takeDamage,
tryOpenDoor: doorManager.tryOpenDoor,
onDamagePlayer: (int damage) {
player.takeDamage(damage);
damageFlashOpacity = 0.5;
},
);
// 2. Update Angle
entity.angle = intent.newAngle;
// 3. Resolve Movement & Collision
// We reuse the same logic we used for the player!
Coordinate2D validatedPos = _calculateValidatedPosition(
entity.position,
intent.movement,
);
// 3. Resolve Movement
// We NO LONGER use _calculateValidatedPosition here!
// The enemy's internal getValidMovement already did the math perfectly.
entity.x += intent.movement.x;
entity.y += intent.movement.y;
entity.position = validatedPos;
// 4. Handle Item Drops & Score (Matches KillActor in C code)
// Check if they just died this exact frame
if (entity.state == EntityState.dead &&
entity.isDying &&
!entity.hasDroppedItem) {
entity.hasDroppedItem = true; // Make sure we only drop once!
// 4. Handle Attacking (if the enemy logic decides to)
// You can move 'onDamagePlayer' calls into the enemy's
// internal state check here if preferred.
// You will need to add a `bool hasDroppedItem = false;` to your base Enemy class.
if (entity.runtimeType.toString() == 'BrownGuard') {
// Example: Spawn an ammo clip where the guard died
// itemsToAdd.add(Collectible(x: entity.x, y: entity.y, type: CollectibleType.ammoClip));
} else if (entity.runtimeType.toString() == 'Dog') {
// Dogs don't drop items, but maybe they give different points!
}
}
} else if (entity is Collectible) {
// Collectible pickup logic remains the same
if (player.position.distanceTo(entity.position) < 0.5) {
if (player.tryPickup(entity)) {
itemsToRemove.add(entity);
@@ -340,9 +361,13 @@ class _WolfRendererState extends State<WolfRenderer>
}
}
// Clean up dead items and add new drops
if (itemsToRemove.isNotEmpty) {
entities.removeWhere((e) => itemsToRemove.contains(e));
}
if (itemsToAdd.isNotEmpty) {
entities.addAll(itemsToAdd);
}
}
// Takes an input and returns a value instead of implicitly changing state
@@ -392,6 +417,7 @@ class _WolfRendererState extends State<WolfRenderer>
doorOffsets: doorManager.getOffsetsForRenderer(),
entities: entities,
sprites: gameMap.sprites,
activePushwall: pushwallManager.activePushwall,
),
),
Positioned(