Eliminating side-effects
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -51,13 +51,8 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
|
|
||||||
List<Entity> entities = [];
|
List<Entity> entities = [];
|
||||||
|
|
||||||
// Track door animations
|
|
||||||
// Key is "X,Y" coordinate. Value is how far open it is (0.0 to 1.0)
|
|
||||||
Map<String, double> doorOffsets = {};
|
Map<String, double> doorOffsets = {};
|
||||||
Map<String, int> doorStates = {}; // 1 = opening, 2 = fully open
|
Map<String, int> doorStates = {};
|
||||||
|
|
||||||
double moveStepX = 0;
|
|
||||||
double moveStepY = 0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -66,20 +61,15 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initGame({bool demo = true}) async {
|
Future<void> _initGame({bool demo = true}) async {
|
||||||
// 1. Load the entire WL1 (demo)/WL6 (retail) data
|
|
||||||
gameMap = demo ? await WolfMap.loadDemo() : await WolfMap.load();
|
gameMap = demo ? await WolfMap.loadDemo() : await WolfMap.load();
|
||||||
|
|
||||||
// 2. Extract Level 1 (E1M1)
|
|
||||||
currentLevel = gameMap.levels[0].wallGrid;
|
currentLevel = gameMap.levels[0].wallGrid;
|
||||||
|
|
||||||
final Matrix<int> objectLevel = gameMap.levels[0].objectGrid;
|
final Matrix<int> objectLevel = gameMap.levels[0].objectGrid;
|
||||||
|
|
||||||
// 1. SCAN FOR PLAYER SPAWN & ENTITIES
|
|
||||||
for (int y = 0; y < 64; y++) {
|
for (int y = 0; y < 64; y++) {
|
||||||
for (int x = 0; x < 64; x++) {
|
for (int x = 0; x < 64; x++) {
|
||||||
int objId = objectLevel[y][x];
|
int objId = objectLevel[y][x];
|
||||||
|
|
||||||
// Player Spawn
|
|
||||||
if (objId >= 19 && objId <= 22) {
|
if (objId >= 19 && objId <= 22) {
|
||||||
double spawnAngle = 0.0;
|
double spawnAngle = 0.0;
|
||||||
switch (objId) {
|
switch (objId) {
|
||||||
@@ -117,7 +107,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. CLEAN UP WALLS / PRESERVE DOORS
|
|
||||||
for (int y = 0; y < 64; y++) {
|
for (int y = 0; y < 64; y++) {
|
||||||
for (int x = 0; x < 64; x++) {
|
for (int x = 0; x < 64; x++) {
|
||||||
int id = currentLevel[y][x];
|
int id = currentLevel[y][x];
|
||||||
@@ -129,7 +118,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Start the game!
|
|
||||||
_bumpPlayerIfStuck();
|
_bumpPlayerIfStuck();
|
||||||
_gameLoop = createTicker(_tick)..start();
|
_gameLoop = createTicker(_tick)..start();
|
||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
@@ -180,10 +168,9 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isWalkable(int x, int y) {
|
bool _isWalkable(int x, int y) {
|
||||||
if (currentLevel[y][x] == 0) return true; // Empty space
|
if (currentLevel[y][x] == 0) return true;
|
||||||
if (currentLevel[y][x] >= 90) {
|
if (currentLevel[y][x] >= 90) {
|
||||||
String key = '$x,$y';
|
String key = '$x,$y';
|
||||||
// Allow the player to walk through if the door is > 70% open
|
|
||||||
if (doorOffsets[key] != null && doorOffsets[key]! > 0.7) {
|
if (doorOffsets[key] != null && doorOffsets[key]! > 0.7) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -191,16 +178,19 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ORCHESTRATOR ---
|
||||||
void _tick(Duration elapsed) {
|
void _tick(Duration elapsed) {
|
||||||
// 1. Inputs & Intentions
|
// 1. Process intentions and receive movement vectors
|
||||||
_processInputs(elapsed);
|
final movement = _processInputs(elapsed);
|
||||||
|
|
||||||
// 2. State Updates
|
// 2. Explicit State Updates
|
||||||
player.updateWeaponSwitch();
|
player.updateWeaponSwitch();
|
||||||
_updateDoors();
|
_updateDoors();
|
||||||
_applyMovementAndCollision();
|
_applyMovementAndCollision(movement.dx, movement.dy);
|
||||||
_updateEntities(elapsed);
|
_updateEntities(elapsed);
|
||||||
_updateScreenEffects();
|
|
||||||
|
// Explicit reassignment from a pure(r) function
|
||||||
|
damageFlashOpacity = _calculateScreenEffects(damageFlashOpacity);
|
||||||
|
|
||||||
// 3. Combat
|
// 3. Combat
|
||||||
if (player.updateWeapon(elapsed.inMilliseconds)) {
|
if (player.updateWeapon(elapsed.inMilliseconds)) {
|
||||||
@@ -211,7 +201,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// A helper method to handle getting shot
|
|
||||||
void _takeDamage(int damage) {
|
void _takeDamage(int damage) {
|
||||||
player.takeDamage(damage);
|
player.takeDamage(damage);
|
||||||
damageFlashOpacity = 0.5;
|
damageFlashOpacity = 0.5;
|
||||||
@@ -219,16 +208,14 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
|
|
||||||
void _performRaycastAttack(Duration elapsed) {
|
void _performRaycastAttack(Duration elapsed) {
|
||||||
Enemy? closestEnemy;
|
Enemy? closestEnemy;
|
||||||
double minDistance = 15.0; // Maximum range of the gun
|
double minDistance = 15.0;
|
||||||
|
|
||||||
for (Entity entity in entities) {
|
for (Entity entity in entities) {
|
||||||
if (entity is Enemy && entity.state != EntityState.dead) {
|
if (entity is Enemy && entity.state != EntityState.dead) {
|
||||||
// 1. Calculate the angle from player to enemy
|
|
||||||
double dx = entity.x - player.x;
|
double dx = entity.x - player.x;
|
||||||
double dy = entity.y - player.y;
|
double dy = entity.y - player.y;
|
||||||
double angleToEnemy = math.atan2(dy, dx);
|
double angleToEnemy = math.atan2(dy, dx);
|
||||||
|
|
||||||
// 2. Check if that angle is close to our player's aiming angle
|
|
||||||
double angleDiff = player.angle - angleToEnemy;
|
double angleDiff = player.angle - angleToEnemy;
|
||||||
while (angleDiff <= -math.pi) {
|
while (angleDiff <= -math.pi) {
|
||||||
angleDiff += 2 * math.pi;
|
angleDiff += 2 * math.pi;
|
||||||
@@ -237,13 +224,10 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
angleDiff -= 2 * math.pi;
|
angleDiff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Simple bounding box check (approx 0.4 units wide)
|
|
||||||
double dist = math.sqrt(dx * dx + dy * dy);
|
double dist = math.sqrt(dx * dx + dy * dy);
|
||||||
double threshold =
|
double threshold = 0.2 / dist;
|
||||||
0.2 / dist; // Smaller threshold as they get further away
|
|
||||||
|
|
||||||
if (angleDiff.abs() < threshold) {
|
if (angleDiff.abs() < threshold) {
|
||||||
// 4. Ensure there is no wall between you and the enemy
|
|
||||||
if (_hasLineOfSightToEnemy(entity, dist)) {
|
if (_hasLineOfSightToEnemy(entity, dist)) {
|
||||||
if (dist < minDistance) {
|
if (dist < minDistance) {
|
||||||
minDistance = dist;
|
minDistance = dist;
|
||||||
@@ -260,7 +244,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
elapsed.inMilliseconds,
|
elapsed.inMilliseconds,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the shot was fatal, reward the player
|
|
||||||
if (closestEnemy.state == EntityState.dead) {
|
if (closestEnemy.state == EntityState.dead) {
|
||||||
player.score += 100;
|
player.score += 100;
|
||||||
}
|
}
|
||||||
@@ -279,45 +262,38 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _processInputs(Duration elapsed) {
|
// Returns movement deltas instead of modifying class variables
|
||||||
|
({double dx, double dy}) _processInputs(Duration elapsed) {
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
|
|
||||||
const double moveSpeed = 0.16;
|
const double moveSpeed = 0.16;
|
||||||
const double turnSpeed = 0.12;
|
const double turnSpeed = 0.12;
|
||||||
|
double dx = 0;
|
||||||
|
double dy = 0;
|
||||||
|
|
||||||
// Reset steps each tick
|
|
||||||
moveStepX = 0;
|
|
||||||
moveStepY = 0;
|
|
||||||
|
|
||||||
// 1. Weapon Switching
|
|
||||||
if (inputManager.requestedWeapon != null) {
|
if (inputManager.requestedWeapon != null) {
|
||||||
player.requestWeaponSwitch(inputManager.requestedWeapon!);
|
player.requestWeaponSwitch(inputManager.requestedWeapon!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Firing
|
|
||||||
if (inputManager.isFiring) {
|
if (inputManager.isFiring) {
|
||||||
player.fire(elapsed.inMilliseconds);
|
player.fire(elapsed.inMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Movement
|
|
||||||
if (inputManager.isMovingForward) {
|
if (inputManager.isMovingForward) {
|
||||||
moveStepX += math.cos(player.angle) * moveSpeed;
|
dx += math.cos(player.angle) * moveSpeed;
|
||||||
moveStepY += math.sin(player.angle) * moveSpeed;
|
dy += math.sin(player.angle) * moveSpeed;
|
||||||
}
|
}
|
||||||
if (inputManager.isMovingBackward) {
|
if (inputManager.isMovingBackward) {
|
||||||
moveStepX -= math.cos(player.angle) * moveSpeed;
|
dx -= math.cos(player.angle) * moveSpeed;
|
||||||
moveStepY -= math.sin(player.angle) * moveSpeed;
|
dy -= math.sin(player.angle) * moveSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Turning
|
|
||||||
if (inputManager.isTurningLeft) player.angle -= turnSpeed;
|
if (inputManager.isTurningLeft) player.angle -= turnSpeed;
|
||||||
if (inputManager.isTurningRight) player.angle += turnSpeed;
|
if (inputManager.isTurningRight) player.angle += turnSpeed;
|
||||||
|
|
||||||
// Keep angle wrapped cleanly
|
|
||||||
if (player.angle < 0) player.angle += 2 * math.pi;
|
if (player.angle < 0) player.angle += 2 * math.pi;
|
||||||
if (player.angle > 2 * math.pi) player.angle -= 2 * math.pi;
|
if (player.angle > 2 * math.pi) player.angle -= 2 * math.pi;
|
||||||
|
|
||||||
// 5. Interaction (Doors)
|
|
||||||
if (inputManager.isInteracting) {
|
if (inputManager.isInteracting) {
|
||||||
int targetX = (player.x + math.cos(player.angle)).toInt();
|
int targetX = (player.x + math.cos(player.angle)).toInt();
|
||||||
int targetY = (player.y + math.sin(player.angle)).toInt();
|
int targetY = (player.y + math.sin(player.angle)).toInt();
|
||||||
@@ -328,46 +304,41 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
targetX < currentLevel[0].length) {
|
targetX < currentLevel[0].length) {
|
||||||
if (currentLevel[targetY][targetX] >= 90) {
|
if (currentLevel[targetY][targetX] >= 90) {
|
||||||
String key = '$targetX,$targetY';
|
String key = '$targetX,$targetY';
|
||||||
// Start the animation if it isn't already opening!
|
|
||||||
if (!doorStates.containsKey(key) || doorStates[key] == 0) {
|
if (!doorStates.containsKey(key) || doorStates[key] == 0) {
|
||||||
doorStates[key] = 1;
|
doorStates[key] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (dx: dx, dy: dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateDoors() {
|
void _updateDoors() {
|
||||||
doorStates.forEach((key, state) {
|
doorStates.forEach((key, state) {
|
||||||
if (state == 1) {
|
if (state == 1) {
|
||||||
// 1 = opening
|
doorOffsets[key] = (doorOffsets[key] ?? 0.0) + 0.02;
|
||||||
doorOffsets[key] = (doorOffsets[key] ?? 0.0) + 0.02; // Slide speed
|
|
||||||
if (doorOffsets[key]! >= 1.0) {
|
if (doorOffsets[key]! >= 1.0) {
|
||||||
doorOffsets[key] = 1.0;
|
doorOffsets[key] = 1.0;
|
||||||
doorStates[key] = 2; // Mark as fully open
|
doorStates[key] = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _applyMovementAndCollision() {
|
// Now receives dx and dy explicitly
|
||||||
|
void _applyMovementAndCollision(double dx, double dy) {
|
||||||
const double margin = 0.3;
|
const double margin = 0.3;
|
||||||
|
|
||||||
// X-axis collision
|
double newX = player.x + dx;
|
||||||
double newX = player.x + moveStepX;
|
int checkX = (dx > 0) ? (newX + margin).toInt() : (newX - margin).toInt();
|
||||||
int checkX = (moveStepX > 0)
|
|
||||||
? (newX + margin).toInt()
|
|
||||||
: (newX - margin).toInt();
|
|
||||||
|
|
||||||
if (_isWalkable(checkX, player.y.toInt())) {
|
if (_isWalkable(checkX, player.y.toInt())) {
|
||||||
player.x = newX;
|
player.x = newX;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Y-axis collision
|
double newY = player.y + dy;
|
||||||
double newY = player.y + moveStepY;
|
int checkY = (dy > 0) ? (newY + margin).toInt() : (newY - margin).toInt();
|
||||||
int checkY = (moveStepY > 0)
|
|
||||||
? (newY + margin).toInt()
|
|
||||||
: (newY - margin).toInt();
|
|
||||||
|
|
||||||
if (_isWalkable(player.x.toInt(), checkY)) {
|
if (_isWalkable(player.x.toInt(), checkY)) {
|
||||||
player.y = newY;
|
player.y = newY;
|
||||||
@@ -385,14 +356,11 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
isWalkable: _isWalkable,
|
isWalkable: _isWalkable,
|
||||||
onDamagePlayer: _takeDamage,
|
onDamagePlayer: _takeDamage,
|
||||||
);
|
);
|
||||||
}
|
} else if (entity is Collectible) {
|
||||||
// Collectible Interaction Logic
|
|
||||||
else if (entity is Collectible) {
|
|
||||||
double dx = player.x - entity.x;
|
double dx = player.x - entity.x;
|
||||||
double dy = player.y - entity.y;
|
double dy = player.y - entity.y;
|
||||||
double dist = math.sqrt(dx * dx + dy * dy);
|
double dist = math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
// If player is close enough to the item
|
|
||||||
if (dist < 0.5) {
|
if (dist < 0.5) {
|
||||||
if (player.tryPickup(entity)) {
|
if (player.tryPickup(entity)) {
|
||||||
itemsToRemove.add(entity);
|
itemsToRemove.add(entity);
|
||||||
@@ -401,17 +369,17 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the items that were successfully picked up
|
|
||||||
if (itemsToRemove.isNotEmpty) {
|
if (itemsToRemove.isNotEmpty) {
|
||||||
entities.removeWhere((e) => itemsToRemove.contains(e));
|
entities.removeWhere((e) => itemsToRemove.contains(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateScreenEffects() {
|
// Takes an input and returns a value instead of implicitly changing state
|
||||||
// Fade out the damage flash smoothly
|
double _calculateScreenEffects(double currentOpacity) {
|
||||||
if (damageFlashOpacity > 0) {
|
if (currentOpacity > 0) {
|
||||||
damageFlashOpacity = math.max(0.0, damageFlashOpacity - 0.05);
|
return math.max(0.0, currentOpacity - 0.05);
|
||||||
}
|
}
|
||||||
|
return currentOpacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -432,7 +400,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
onKeyEvent: (_) {},
|
onKeyEvent: (_) {},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Game view
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
@@ -456,7 +423,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
sprites: gameMap.sprites,
|
sprites: gameMap.sprites,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Weapon Viewmodel
|
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: -20,
|
bottom: -20,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -464,8 +430,9 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: Offset(
|
offset: Offset(
|
||||||
// Bobbing math
|
// Replaced hidden step variables with a direct intention check!
|
||||||
(moveStepX.abs() + moveStepY.abs()) > 0
|
(inputManager.isMovingForward ||
|
||||||
|
inputManager.isMovingBackward)
|
||||||
? math.sin(
|
? math.sin(
|
||||||
DateTime.now()
|
DateTime.now()
|
||||||
.millisecondsSinceEpoch /
|
.millisecondsSinceEpoch /
|
||||||
@@ -473,7 +440,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
) *
|
) *
|
||||||
12
|
12
|
||||||
: 0,
|
: 0,
|
||||||
// Y-Offset for lowering and raising
|
|
||||||
player.weaponAnimOffset,
|
player.weaponAnimOffset,
|
||||||
),
|
),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@@ -506,8 +472,6 @@ class _WolfRendererState extends State<WolfRenderer>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// HUD
|
|
||||||
Hud(player: player),
|
Hud(player: player),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user