Working on fixing enemy identification
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -16,14 +16,13 @@ class Dog extends Enemy {
|
|||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: 99,
|
spriteIndex: EnemyType.dog.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
state: EntityState.idle,
|
||||||
);
|
);
|
||||||
|
|
||||||
static Dog? trySpawn(int objId, double x, double y, Difficulty _) {
|
static Dog? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||||
// Dogs span 216 to 251.
|
if (EnemyType.dog.claimsMapId(objId)) {
|
||||||
if (objId >= MapObject.dogStart && objId <= MapObject.dogStart + 35) {
|
bool isPatrolling = objId >= EnemyType.dog.mapBaseId + 18;
|
||||||
bool isPatrolling = objId >= MapObject.dogStart + 18;
|
|
||||||
|
|
||||||
return Dog(
|
return Dog(
|
||||||
x: x,
|
x: x,
|
||||||
|
|||||||
@@ -3,6 +3,49 @@ import 'dart:math' as math;
|
|||||||
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
import 'package:wolf_dart/classes/coordinate_2d.dart';
|
||||||
import 'package:wolf_dart/features/entities/entity.dart';
|
import 'package:wolf_dart/features/entities/entity.dart';
|
||||||
|
|
||||||
|
enum EnemyType {
|
||||||
|
guard(mapBaseId: 108, spriteBaseIdx: 50),
|
||||||
|
dog(mapBaseId: 216, spriteBaseIdx: 99),
|
||||||
|
ss(mapBaseId: 180, spriteBaseIdx: 138),
|
||||||
|
mutant(mapBaseId: 252, spriteBaseIdx: 187),
|
||||||
|
officer(mapBaseId: 144, spriteBaseIdx: 238),
|
||||||
|
;
|
||||||
|
|
||||||
|
final int mapBaseId;
|
||||||
|
final int spriteBaseIdx;
|
||||||
|
|
||||||
|
const EnemyType({
|
||||||
|
required this.mapBaseId,
|
||||||
|
required this.spriteBaseIdx,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Helper to check if a specific TED5 Map ID belongs to this enemy
|
||||||
|
bool claimsMapId(int id) => id >= mapBaseId && id <= mapBaseId + 35;
|
||||||
|
|
||||||
|
/// Helper to find which EnemyType a given Map ID belongs to
|
||||||
|
static EnemyType? fromMapId(int id) {
|
||||||
|
for (final type in EnemyType.values) {
|
||||||
|
if (type.claimsMapId(id)) return type;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool claimsSpriteIndex(int index) {
|
||||||
|
return switch (this) {
|
||||||
|
// Walk, Action, & Death: 50-98
|
||||||
|
EnemyType.guard => index >= 50 && index <= 98,
|
||||||
|
// Walk, Action, & Death: 99-137
|
||||||
|
EnemyType.dog => index >= 99 && index <= 137,
|
||||||
|
// Walk, Action, & Death: 138-186
|
||||||
|
EnemyType.ss => index >= 138 && index <= 186,
|
||||||
|
// Walk, Action, & Death: 187-237
|
||||||
|
EnemyType.mutant => index >= 187 && index <= 237,
|
||||||
|
// Walk, Action, & Death: 238-287
|
||||||
|
EnemyType.officer => index >= 238 && index <= 287,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class Enemy extends Entity {
|
abstract class Enemy extends Entity {
|
||||||
Enemy({
|
Enemy({
|
||||||
required super.x,
|
required super.x,
|
||||||
|
|||||||
@@ -16,18 +16,13 @@ class Guard extends Enemy {
|
|||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: 50,
|
spriteIndex: EnemyType.guard.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
state: EntityState.idle,
|
||||||
);
|
);
|
||||||
|
|
||||||
static Guard? trySpawn(int objId, double x, double y, Difficulty difficulty) {
|
static Guard? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||||
// Guards span 108 to 143. (124 and 125 are decorative dead bodies).
|
if (EnemyType.guard.claimsMapId(objId) && objId != 124 && objId != 125) {
|
||||||
if (objId >= MapObject.guardStart &&
|
bool isPatrolling = objId >= EnemyType.guard.mapBaseId + 18;
|
||||||
objId <= MapObject.guardStart + 35 &&
|
|
||||||
objId != 124 &&
|
|
||||||
objId != 125) {
|
|
||||||
// If the ID is in the second half of the block, it's a Patrolling guard
|
|
||||||
bool isPatrolling = objId >= MapObject.guardStart + 18;
|
|
||||||
|
|
||||||
return Guard(
|
return Guard(
|
||||||
x: x,
|
x: x,
|
||||||
@@ -110,15 +105,15 @@ class Guard extends Enemy {
|
|||||||
case EntityState.attacking:
|
case EntityState.attacking:
|
||||||
int timeShooting = elapsedMs - lastActionTime;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (timeShooting < 150) {
|
if (timeShooting < 150) {
|
||||||
spriteIndex = 96; // Aiming
|
spriteIndex = 90; // Aiming
|
||||||
} else if (timeShooting < 300) {
|
} else if (timeShooting < 300) {
|
||||||
spriteIndex = 97; // Firing
|
spriteIndex = 91; // Firing
|
||||||
if (!_hasFiredThisCycle) {
|
if (!_hasFiredThisCycle) {
|
||||||
onDamagePlayer(10);
|
onDamagePlayer(10);
|
||||||
_hasFiredThisCycle = true;
|
_hasFiredThisCycle = true;
|
||||||
}
|
}
|
||||||
} else if (timeShooting < 450) {
|
} else if (timeShooting < 450) {
|
||||||
spriteIndex = 98; // Recoil
|
spriteIndex = 90; // Recoil (back to aim pose)
|
||||||
} else {
|
} else {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -126,8 +121,7 @@ class Guard extends Enemy {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EntityState.pain:
|
case EntityState.pain:
|
||||||
spriteIndex = 94; // Ouch frame
|
spriteIndex = 92; // Ouch frame
|
||||||
// Stay in pain for a brief moment, then resume attacking
|
|
||||||
if (elapsedMs - lastActionTime > 250) {
|
if (elapsedMs - lastActionTime > 250) {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -137,15 +131,14 @@ class Guard extends Enemy {
|
|||||||
case EntityState.dead:
|
case EntityState.dead:
|
||||||
if (isDying) {
|
if (isDying) {
|
||||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
||||||
if (deathFrame < 4) {
|
if (deathFrame < 3) {
|
||||||
// FIX: Removed the buggy "- 1"
|
spriteIndex = 93 + deathFrame; // Cycles 93, 94, 95
|
||||||
spriteIndex = 90 + deathFrame;
|
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = 95; // Final dead frame
|
spriteIndex = 95; // Final dead frame
|
||||||
isDying = false;
|
isDying = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = 95;
|
spriteIndex = 95; // Final dead frame
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:wolf_dart/features/entities/map_objects.dart';
|
|||||||
|
|
||||||
class Mutant extends Enemy {
|
class Mutant extends Enemy {
|
||||||
static const double speed = 0.045;
|
static const double speed = 0.045;
|
||||||
static const int _baseSprite = 187;
|
|
||||||
bool _hasFiredThisCycle = false;
|
bool _hasFiredThisCycle = false;
|
||||||
|
|
||||||
Mutant({
|
Mutant({
|
||||||
@@ -17,22 +16,16 @@ class Mutant extends Enemy {
|
|||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: _baseSprite,
|
spriteIndex: EnemyType.mutant.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
state: EntityState.idle,
|
||||||
) {
|
) {
|
||||||
health = 45;
|
health = 45;
|
||||||
damage = 10;
|
damage = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Mutant? trySpawn(
|
static Mutant? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||||
int objId,
|
if (EnemyType.mutant.claimsMapId(objId)) {
|
||||||
double x,
|
bool isPatrolling = objId >= EnemyType.mutant.mapBaseId + 18;
|
||||||
double y,
|
|
||||||
Difficulty difficulty,
|
|
||||||
) {
|
|
||||||
// Mutants span 252 to 287
|
|
||||||
if (objId >= MapObject.mutantStart && objId <= MapObject.mutantStart + 35) {
|
|
||||||
bool isPatrolling = objId >= MapObject.mutantStart + 18;
|
|
||||||
|
|
||||||
return Mutant(
|
return Mutant(
|
||||||
x: x,
|
x: x,
|
||||||
@@ -81,7 +74,7 @@ class Mutant extends Enemy {
|
|||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case EntityState.idle:
|
case EntityState.idle:
|
||||||
spriteIndex = _baseSprite + octant;
|
spriteIndex = EnemyType.mutant.spriteBaseIdx + octant;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EntityState.patrolling:
|
case EntityState.patrolling:
|
||||||
@@ -96,7 +89,8 @@ class Mutant extends Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
int walkFrame = (elapsedMs ~/ 150) % 4;
|
||||||
spriteIndex = (_baseSprite + 8) + (walkFrame * 8) + octant;
|
spriteIndex =
|
||||||
|
(EnemyType.mutant.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
|
||||||
|
|
||||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
||||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||||
@@ -110,15 +104,15 @@ class Mutant extends Enemy {
|
|||||||
case EntityState.attacking:
|
case EntityState.attacking:
|
||||||
int timeShooting = elapsedMs - lastActionTime;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (timeShooting < 150) {
|
if (timeShooting < 150) {
|
||||||
spriteIndex = _baseSprite + 46; // Aiming
|
spriteIndex = EnemyType.mutant.spriteBaseIdx + 46; // Aiming
|
||||||
} else if (timeShooting < 300) {
|
} else if (timeShooting < 300) {
|
||||||
spriteIndex = _baseSprite + 47; // Firing
|
spriteIndex = EnemyType.mutant.spriteBaseIdx + 47; // Firing
|
||||||
if (!_hasFiredThisCycle) {
|
if (!_hasFiredThisCycle) {
|
||||||
onDamagePlayer(damage);
|
onDamagePlayer(damage);
|
||||||
_hasFiredThisCycle = true;
|
_hasFiredThisCycle = true;
|
||||||
}
|
}
|
||||||
} else if (timeShooting < 450) {
|
} else if (timeShooting < 450) {
|
||||||
spriteIndex = _baseSprite + 48; // Recoil
|
spriteIndex = EnemyType.mutant.spriteBaseIdx + 48; // Recoil
|
||||||
} else {
|
} else {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -126,7 +120,7 @@ class Mutant extends Enemy {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EntityState.pain:
|
case EntityState.pain:
|
||||||
spriteIndex = _baseSprite + 44;
|
spriteIndex = EnemyType.mutant.spriteBaseIdx + 44;
|
||||||
if (elapsedMs - lastActionTime > 250) {
|
if (elapsedMs - lastActionTime > 250) {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -137,13 +131,13 @@ class Mutant extends Enemy {
|
|||||||
if (isDying) {
|
if (isDying) {
|
||||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
||||||
if (deathFrame < 4) {
|
if (deathFrame < 4) {
|
||||||
spriteIndex = (_baseSprite + 40) + deathFrame;
|
spriteIndex = (EnemyType.mutant.spriteBaseIdx + 40) + deathFrame;
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = _baseSprite + 45;
|
spriteIndex = EnemyType.mutant.spriteBaseIdx + 45;
|
||||||
isDying = false;
|
isDying = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = _baseSprite + 45;
|
spriteIndex = EnemyType.mutant.spriteBaseIdx + 45;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:wolf_dart/features/entities/map_objects.dart';
|
|||||||
|
|
||||||
class Officer extends Enemy {
|
class Officer extends Enemy {
|
||||||
static const double speed = 0.055;
|
static const double speed = 0.055;
|
||||||
static const int _baseSprite = 50;
|
|
||||||
bool _hasFiredThisCycle = false;
|
bool _hasFiredThisCycle = false;
|
||||||
|
|
||||||
Officer({
|
Officer({
|
||||||
@@ -17,22 +16,16 @@ class Officer extends Enemy {
|
|||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: _baseSprite,
|
spriteIndex: EnemyType.officer.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
state: EntityState.idle,
|
||||||
) {
|
) {
|
||||||
health = 50;
|
health = 50;
|
||||||
damage = 15;
|
damage = 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Officer? trySpawn(
|
static Officer? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||||
int objId,
|
if (EnemyType.officer.claimsMapId(objId)) {
|
||||||
double x,
|
bool isPatrolling = objId >= EnemyType.officer.mapBaseId + 18;
|
||||||
double y,
|
|
||||||
Difficulty difficulty,
|
|
||||||
) {
|
|
||||||
if (objId >= MapObject.officerStart &&
|
|
||||||
objId <= MapObject.officerStart + 35) {
|
|
||||||
bool isPatrolling = objId >= MapObject.officerStart + 18;
|
|
||||||
|
|
||||||
return Officer(
|
return Officer(
|
||||||
x: x,
|
x: x,
|
||||||
@@ -81,7 +74,7 @@ class Officer extends Enemy {
|
|||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case EntityState.idle:
|
case EntityState.idle:
|
||||||
spriteIndex = _baseSprite + octant;
|
spriteIndex = EnemyType.officer.spriteBaseIdx + octant;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EntityState.patrolling:
|
case EntityState.patrolling:
|
||||||
@@ -96,7 +89,8 @@ class Officer extends Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
int walkFrame = (elapsedMs ~/ 150) % 4;
|
||||||
spriteIndex = (_baseSprite + 8) + (walkFrame * 8) + octant;
|
spriteIndex =
|
||||||
|
(EnemyType.officer.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
|
||||||
|
|
||||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
||||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||||
@@ -110,15 +104,15 @@ class Officer extends Enemy {
|
|||||||
case EntityState.attacking:
|
case EntityState.attacking:
|
||||||
int timeShooting = elapsedMs - lastActionTime;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (timeShooting < 150) {
|
if (timeShooting < 150) {
|
||||||
spriteIndex = _baseSprite + 46; // Aiming
|
spriteIndex = EnemyType.officer.spriteBaseIdx + 40; // Aiming
|
||||||
} else if (timeShooting < 300) {
|
} else if (timeShooting < 300) {
|
||||||
spriteIndex = _baseSprite + 47; // Firing
|
spriteIndex = EnemyType.officer.spriteBaseIdx + 41; // Firing
|
||||||
if (!_hasFiredThisCycle) {
|
if (!_hasFiredThisCycle) {
|
||||||
onDamagePlayer(damage);
|
onDamagePlayer(damage);
|
||||||
_hasFiredThisCycle = true;
|
_hasFiredThisCycle = true;
|
||||||
}
|
}
|
||||||
} else if (timeShooting < 450) {
|
} else if (timeShooting < 450) {
|
||||||
spriteIndex = _baseSprite + 48; // Recoil
|
spriteIndex = EnemyType.officer.spriteBaseIdx + 40; // Recoil
|
||||||
} else {
|
} else {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -126,7 +120,7 @@ class Officer extends Enemy {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EntityState.pain:
|
case EntityState.pain:
|
||||||
spriteIndex = _baseSprite + 44;
|
spriteIndex = EnemyType.officer.spriteBaseIdx + 42;
|
||||||
if (elapsedMs - lastActionTime > 250) {
|
if (elapsedMs - lastActionTime > 250) {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -136,14 +130,14 @@ class Officer extends Enemy {
|
|||||||
case EntityState.dead:
|
case EntityState.dead:
|
||||||
if (isDying) {
|
if (isDying) {
|
||||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
||||||
if (deathFrame < 4) {
|
if (deathFrame < 3) {
|
||||||
spriteIndex = (_baseSprite + 40) + deathFrame;
|
spriteIndex = (EnemyType.officer.spriteBaseIdx + 43) + deathFrame;
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = _baseSprite + 45;
|
spriteIndex = EnemyType.officer.spriteBaseIdx + 45;
|
||||||
isDying = false;
|
isDying = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = _baseSprite + 45;
|
spriteIndex = EnemyType.officer.spriteBaseIdx + 45;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:wolf_dart/features/entities/map_objects.dart';
|
|||||||
|
|
||||||
class SS extends Enemy {
|
class SS extends Enemy {
|
||||||
static const double speed = 0.04;
|
static const double speed = 0.04;
|
||||||
static const int _baseSprite = 138;
|
|
||||||
bool _hasFiredThisCycle = false;
|
bool _hasFiredThisCycle = false;
|
||||||
|
|
||||||
SS({
|
SS({
|
||||||
@@ -17,16 +16,16 @@ class SS extends Enemy {
|
|||||||
required super.angle,
|
required super.angle,
|
||||||
required super.mapId,
|
required super.mapId,
|
||||||
}) : super(
|
}) : super(
|
||||||
spriteIndex: _baseSprite,
|
spriteIndex: EnemyType.ss.spriteBaseIdx,
|
||||||
state: EntityState.idle,
|
state: EntityState.idle,
|
||||||
) {
|
) {
|
||||||
health = 100;
|
health = 100;
|
||||||
damage = 20;
|
damage = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SS? trySpawn(int objId, double x, double y, Difficulty difficulty) {
|
static SS? trySpawn(int objId, double x, double y, Difficulty _) {
|
||||||
if (objId >= MapObject.ssStart && objId <= MapObject.ssStart + 35) {
|
if (EnemyType.ss.claimsMapId(objId)) {
|
||||||
bool isPatrolling = objId >= MapObject.ssStart + 18;
|
bool isPatrolling = objId >= EnemyType.ss.mapBaseId + 18;
|
||||||
|
|
||||||
return SS(
|
return SS(
|
||||||
x: x,
|
x: x,
|
||||||
@@ -75,7 +74,7 @@ class SS extends Enemy {
|
|||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case EntityState.idle:
|
case EntityState.idle:
|
||||||
spriteIndex = _baseSprite + octant;
|
spriteIndex = EnemyType.ss.spriteBaseIdx + octant;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EntityState.patrolling:
|
case EntityState.patrolling:
|
||||||
@@ -90,7 +89,8 @@ class SS extends Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
int walkFrame = (elapsedMs ~/ 150) % 4;
|
||||||
spriteIndex = (_baseSprite + 8) + (walkFrame * 8) + octant;
|
spriteIndex =
|
||||||
|
(EnemyType.ss.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
|
||||||
|
|
||||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||||
@@ -105,15 +105,15 @@ class SS extends Enemy {
|
|||||||
// SS machine gun fires much faster than a standard pistol!
|
// SS machine gun fires much faster than a standard pistol!
|
||||||
int timeShooting = elapsedMs - lastActionTime;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (timeShooting < 100) {
|
if (timeShooting < 100) {
|
||||||
spriteIndex = _baseSprite + 46; // Aiming
|
spriteIndex = EnemyType.ss.spriteBaseIdx + 46; // Aiming
|
||||||
} else if (timeShooting < 200) {
|
} else if (timeShooting < 200) {
|
||||||
spriteIndex = _baseSprite + 47; // Firing
|
spriteIndex = EnemyType.ss.spriteBaseIdx + 47; // Firing
|
||||||
if (!_hasFiredThisCycle) {
|
if (!_hasFiredThisCycle) {
|
||||||
onDamagePlayer(damage);
|
onDamagePlayer(damage);
|
||||||
_hasFiredThisCycle = true;
|
_hasFiredThisCycle = true;
|
||||||
}
|
}
|
||||||
} else if (timeShooting < 300) {
|
} else if (timeShooting < 300) {
|
||||||
spriteIndex = _baseSprite + 48; // Recoil
|
spriteIndex = EnemyType.ss.spriteBaseIdx + 48; // Recoil
|
||||||
} else {
|
} else {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -121,7 +121,7 @@ class SS extends Enemy {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EntityState.pain:
|
case EntityState.pain:
|
||||||
spriteIndex = _baseSprite + 44;
|
spriteIndex = EnemyType.ss.spriteBaseIdx + 44;
|
||||||
if (elapsedMs - lastActionTime > 250) {
|
if (elapsedMs - lastActionTime > 250) {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
@@ -132,13 +132,13 @@ class SS extends Enemy {
|
|||||||
if (isDying) {
|
if (isDying) {
|
||||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
||||||
if (deathFrame < 4) {
|
if (deathFrame < 4) {
|
||||||
spriteIndex = (_baseSprite + 40) + deathFrame;
|
spriteIndex = (EnemyType.ss.spriteBaseIdx + 40) + deathFrame;
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = _baseSprite + 45;
|
spriteIndex = EnemyType.ss.spriteBaseIdx + 45;
|
||||||
isDying = false;
|
isDying = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spriteIndex = _baseSprite + 45;
|
spriteIndex = EnemyType.ss.spriteBaseIdx + 45;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:wolf_dart/features/entities/collectible.dart';
|
|||||||
import 'package:wolf_dart/features/entities/decorative.dart';
|
import 'package:wolf_dart/features/entities/decorative.dart';
|
||||||
import 'package:wolf_dart/features/entities/enemies/bosses/hans_grosse.dart';
|
import 'package:wolf_dart/features/entities/enemies/bosses/hans_grosse.dart';
|
||||||
import 'package:wolf_dart/features/entities/enemies/dog.dart';
|
import 'package:wolf_dart/features/entities/enemies/dog.dart';
|
||||||
|
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||||
import 'package:wolf_dart/features/entities/enemies/guard.dart';
|
import 'package:wolf_dart/features/entities/enemies/guard.dart';
|
||||||
import 'package:wolf_dart/features/entities/enemies/mutant.dart';
|
import 'package:wolf_dart/features/entities/enemies/mutant.dart';
|
||||||
import 'package:wolf_dart/features/entities/enemies/officer.dart';
|
import 'package:wolf_dart/features/entities/enemies/officer.dart';
|
||||||
@@ -50,6 +51,11 @@ abstract class EntityRegistry {
|
|||||||
for (final spawner in _spawners) {
|
for (final spawner in _spawners) {
|
||||||
Entity? entity = spawner(objId, x, y, difficulty);
|
Entity? entity = spawner(objId, x, y, difficulty);
|
||||||
|
|
||||||
|
final EnemyType? type = EnemyType.fromMapId(objId);
|
||||||
|
if (type != null) {
|
||||||
|
print("Spawning ${type.name} enemy");
|
||||||
|
}
|
||||||
|
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
// Safety bounds check for the VSWAP array
|
// Safety bounds check for the VSWAP array
|
||||||
if (entity.spriteIndex >= 0 && entity.spriteIndex < maxSprites) {
|
if (entity.spriteIndex >= 0 && entity.spriteIndex < maxSprites) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:wolf_dart/classes/cardinal_direction.dart';
|
import 'package:wolf_dart/classes/cardinal_direction.dart';
|
||||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||||
|
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||||
|
|
||||||
abstract class MapObject {
|
abstract class MapObject {
|
||||||
// --- Player Spawns ---
|
// --- Player Spawns ---
|
||||||
@@ -81,10 +82,10 @@ abstract class MapObject {
|
|||||||
|
|
||||||
// --- Enemy Range Constants ---
|
// --- Enemy Range Constants ---
|
||||||
static const int guardStart = 108; // 108-143
|
static const int guardStart = 108; // 108-143
|
||||||
static const int officerStart = 144; // 144-179 (WL6)
|
static const int officerStart = 144; // 144-179
|
||||||
static const int ssStart = 180; // 180-215 (WL6)
|
static const int ssStart = 180; // 180-215
|
||||||
static const int dogStart = 216; // 216-251
|
static const int dogStart = 216; // 216-251
|
||||||
static const int mutantStart = 252; // 252-287 (WL6)
|
static const int mutantStart = 252; // 252-287
|
||||||
|
|
||||||
// --- Missing Decorative Bodies ---
|
// --- Missing Decorative Bodies ---
|
||||||
static const int deadGuard = 124; // Decorative only in WL1
|
static const int deadGuard = 124; // Decorative only in WL1
|
||||||
@@ -112,6 +113,7 @@ abstract class MapObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static double getAngle(int id) {
|
static double getAngle(int id) {
|
||||||
|
// Player spawn
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case playerNorth:
|
case playerNorth:
|
||||||
return CardinalDirection.north.radians;
|
return CardinalDirection.north.radians;
|
||||||
@@ -123,52 +125,34 @@ abstract class MapObject {
|
|||||||
return CardinalDirection.west.radians;
|
return CardinalDirection.west.radians;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIX: Expand the boundary to include ALL enemies (Dogs and Mutants)
|
// Boss check
|
||||||
if (id < guardStart || id > (mutantStart + 35)) return 0.0;
|
if (id == bossHansGrosse) return 0.0;
|
||||||
|
|
||||||
int baseId;
|
final EnemyType? type = EnemyType.fromMapId(id);
|
||||||
if (id >= mutantStart) {
|
if (type == null) return 0.0; // Not a standard directional enemy
|
||||||
baseId = mutantStart;
|
|
||||||
} else if (id >= dogStart) {
|
|
||||||
baseId = dogStart;
|
|
||||||
} else if (id >= ssStart) {
|
|
||||||
baseId = ssStart;
|
|
||||||
} else if (id >= officerStart) {
|
|
||||||
baseId = officerStart;
|
|
||||||
} else {
|
|
||||||
baseId = guardStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIX: Normalize patrolling enemies back to the standing block, THEN get the 4-way angle
|
// Normalize patrolling enemies back to the standing block, THEN get the
|
||||||
int directionIndex = ((id - baseId) % 18) % 4;
|
// 4-way angle
|
||||||
|
int directionIndex = ((id - type.mapBaseId) % 18) % 4;
|
||||||
return CardinalDirection.fromEnemyIndex(directionIndex).radians;
|
return CardinalDirection.fromEnemyIndex(directionIndex).radians;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool shouldSpawn(int id, Difficulty selectedDifficulty) {
|
static bool shouldSpawn(int id, Difficulty selectedDifficulty) {
|
||||||
// FIX: Expand the boundary so Dogs and Mutants aren't bypassing difficulty checks
|
EnemyType? type = EnemyType.fromMapId(id);
|
||||||
if (id < guardStart || id > (mutantStart + 35)) return true;
|
|
||||||
|
|
||||||
int baseId;
|
// If it's not a standard enemy (it's a decoration, boss, or player), spawn it
|
||||||
if (id >= mutantStart) {
|
if (type == null) return true;
|
||||||
baseId = mutantStart;
|
|
||||||
} else if (id >= dogStart) {
|
|
||||||
baseId = dogStart;
|
|
||||||
} else if (id >= ssStart) {
|
|
||||||
baseId = ssStart;
|
|
||||||
} else if (id >= officerStart) {
|
|
||||||
baseId = officerStart;
|
|
||||||
} else {
|
|
||||||
baseId = guardStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
int relativeId = (id - baseId) % 18;
|
int offset = id - type.mapBaseId;
|
||||||
|
int normalizedOffset = offset >= 18 ? offset - 18 : offset;
|
||||||
|
|
||||||
return switch (relativeId) {
|
return switch (normalizedOffset) {
|
||||||
< 4 => true,
|
< 4 => true, // Spawns on all difficulties
|
||||||
< 8 => selectedDifficulty.level >= Difficulty.dontHurtMe.level,
|
< 8 => selectedDifficulty.level >= Difficulty.bringEmOn.level, // Normal
|
||||||
< 12 => selectedDifficulty.level >= Difficulty.bringEmOn.level,
|
< 16 =>
|
||||||
< 16 => selectedDifficulty.level >= Difficulty.iAmDeathIncarnate.level,
|
selectedDifficulty.level >=
|
||||||
_ => true,
|
Difficulty.iAmDeathIncarnate.level, // Hard & Ambush
|
||||||
|
_ => true, // Dead bodies (decorations)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,11 @@ import 'package:wolf_3d_data/wolf_3d_data.dart';
|
|||||||
|
|
||||||
class WolfLevel {
|
class WolfLevel {
|
||||||
final String name;
|
final String name;
|
||||||
final int width; // Always 64 in standard Wolf3D
|
|
||||||
final int height; // Always 64
|
|
||||||
final Sprite wallGrid;
|
final Sprite wallGrid;
|
||||||
final Sprite objectGrid;
|
final Sprite objectGrid;
|
||||||
|
|
||||||
WolfLevel({
|
WolfLevel({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.width,
|
|
||||||
required this.height,
|
|
||||||
required this.wallGrid,
|
required this.wallGrid,
|
||||||
required this.objectGrid,
|
required this.objectGrid,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ class WolfMap {
|
|||||||
gameMaps,
|
gameMaps,
|
||||||
isShareware: true,
|
isShareware: true,
|
||||||
);
|
);
|
||||||
final parsedTextures = VswapParser.parseWalls(vswap);
|
final parsedTextures = WLParser.parseWalls(vswap);
|
||||||
final parsedSprites = VswapParser.parseSprites(vswap);
|
final parsedSprites = WLParser.parseSprites(vswap);
|
||||||
|
|
||||||
// 3. Return the populated instance!
|
// 3. Return the populated instance!
|
||||||
return WolfMap._(
|
return WolfMap._(
|
||||||
@@ -49,8 +49,8 @@ class WolfMap {
|
|||||||
|
|
||||||
// 2. Parse the data using the parser we just built
|
// 2. Parse the data using the parser we just built
|
||||||
final parsedLevels = WolfMapParser.parseMaps(mapHead, gameMaps);
|
final parsedLevels = WolfMapParser.parseMaps(mapHead, gameMaps);
|
||||||
final parsedTextures = VswapParser.parseWalls(vswap);
|
final parsedTextures = WLParser.parseWalls(vswap);
|
||||||
final parsedSprites = VswapParser.parseSprites(vswap);
|
final parsedSprites = WLParser.parseSprites(vswap);
|
||||||
|
|
||||||
// 3. Return the populated instance!
|
// 3. Return the populated instance!
|
||||||
return WolfMap._(
|
return WolfMap._(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
|
||||||
import 'package:wolf_dart/features/entities/map_objects.dart';
|
import 'package:wolf_dart/features/entities/map_objects.dart';
|
||||||
import 'package:wolf_dart/features/map/wolf_level.dart';
|
import 'package:wolf_dart/features/map/wolf_level.dart';
|
||||||
|
|
||||||
@@ -40,10 +39,6 @@ abstract class WolfMapParser {
|
|||||||
int plane0Length = gameMaps.getUint16(mapOffset + 12, Endian.little);
|
int plane0Length = gameMaps.getUint16(mapOffset + 12, Endian.little);
|
||||||
int plane1Length = gameMaps.getUint16(mapOffset + 14, Endian.little);
|
int plane1Length = gameMaps.getUint16(mapOffset + 14, Endian.little);
|
||||||
|
|
||||||
// Dimensions (Always 64x64, but we read it anyway for accuracy)
|
|
||||||
int width = gameMaps.getUint16(mapOffset + 18, Endian.little);
|
|
||||||
int height = gameMaps.getUint16(mapOffset + 20, Endian.little);
|
|
||||||
|
|
||||||
// Map Name (16 bytes of ASCII text)
|
// Map Name (16 bytes of ASCII text)
|
||||||
List<int> nameBytes = [];
|
List<int> nameBytes = [];
|
||||||
for (int n = 0; n < 16; n++) {
|
for (int n = 0; n < 16; n++) {
|
||||||
@@ -86,25 +81,23 @@ abstract class WolfMapParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Sprite wallGrid = [];
|
List<List<int>> wallGrid = [];
|
||||||
Sprite objectGrid = []; // NEW
|
List<List<int>> objectGrid = [];
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < 64; y++) {
|
||||||
List<int> wallRow = [];
|
List<int> wallRow = [];
|
||||||
List<int> objectRow = []; // NEW
|
List<int> objectRow = [];
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < 64; x++) {
|
||||||
wallRow.add(flatWallGrid[y * width + x]);
|
wallRow.add(flatWallGrid[y * 64 + x]);
|
||||||
objectRow.add(flatObjectGrid[y * width + x]); // NEW
|
objectRow.add(flatObjectGrid[y * 64 + x]);
|
||||||
}
|
}
|
||||||
wallGrid.add(wallRow);
|
wallGrid.add(wallRow);
|
||||||
objectGrid.add(objectRow); // NEW
|
objectGrid.add(objectRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
levels.add(
|
levels.add(
|
||||||
WolfLevel(
|
WolfLevel(
|
||||||
name: name,
|
name: name,
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
wallGrid: wallGrid,
|
wallGrid: wallGrid,
|
||||||
objectGrid: objectGrid,
|
objectGrid: objectGrid,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||||
|
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
|
||||||
import 'package:wolf_dart/features/renderer/color_palette.dart';
|
import 'package:wolf_dart/features/renderer/color_palette.dart';
|
||||||
|
|
||||||
class SpriteGallery extends StatelessWidget {
|
class SpriteGallery extends StatelessWidget {
|
||||||
@@ -11,7 +12,7 @@ class SpriteGallery extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("VSWAP Sprite Gallery"),
|
title: const Text("Sprite Gallery"),
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
@@ -21,11 +22,22 @@ class SpriteGallery extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
itemCount: sprites.length,
|
itemCount: sprites.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
// --- Check which enemy owns this sprite ---
|
||||||
|
String label = "Idx: $index";
|
||||||
|
for (final enemy in EnemyType.values) {
|
||||||
|
if (enemy.claimsSpriteIndex(index)) {
|
||||||
|
// Appends the enum name (e.g., "guard", "dog")
|
||||||
|
label += "\n${enemy.name}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Idx: $index",
|
label,
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 10),
|
style: const TextStyle(color: Colors.white, fontSize: 10),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import 'matrix.dart';
|
|
||||||
|
|
||||||
typedef Level = Matrix<int>;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
typedef Matrix<T> = List<List<T>>;
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
import 'matrix.dart';
|
typedef Matrix<T> = List<List<T>>;
|
||||||
|
|
||||||
typedef Sprite = Matrix<int>;
|
typedef Sprite = Matrix<int>;
|
||||||
|
|
||||||
|
typedef Wall = Sprite;
|
||||||
|
|
||||||
|
typedef Level = Matrix<int>;
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'classes/sprite.dart';
|
|
||||||
|
|
||||||
class VswapParser {
|
|
||||||
/// Extracts the 64x64 wall textures from VSWAP.WL1
|
|
||||||
static List<Sprite> parseWalls(ByteData vswap) {
|
|
||||||
// 1. Read Header
|
|
||||||
int chunks = vswap.getUint16(0, Endian.little);
|
|
||||||
int spriteStart = vswap.getUint16(2, Endian.little);
|
|
||||||
// int soundStart = vswap.getUint16(4, Endian.little); // We don't need this yet
|
|
||||||
|
|
||||||
// 2. Read Offsets (Where does each chunk start in the file?)
|
|
||||||
List<int> offsets = [];
|
|
||||||
for (int i = 0; i < spriteStart; i++) {
|
|
||||||
offsets.add(vswap.getUint32(6 + (i * 4), Endian.little));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Extract the Wall Textures
|
|
||||||
List<Sprite> textures = [];
|
|
||||||
|
|
||||||
// Walls are chunks 0 through (spriteStart - 1)
|
|
||||||
for (int i = 0; i < spriteStart; i++) {
|
|
||||||
int offset = offsets[i];
|
|
||||||
if (offset == 0) continue; // Empty chunk
|
|
||||||
|
|
||||||
// Walls are always exactly 64x64 pixels (4096 bytes)
|
|
||||||
// Note: Wolf3D stores pixels in COLUMN-MAJOR order (Top to bottom, then left to right)
|
|
||||||
Sprite texture = List.generate(64, (_) => List.filled(64, 0));
|
|
||||||
|
|
||||||
for (int x = 0; x < 64; x++) {
|
|
||||||
for (int y = 0; y < 64; y++) {
|
|
||||||
int byteIndex = offset + (x * 64) + y;
|
|
||||||
texture[x][y] = vswap.getUint8(byteIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
textures.add(texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
return textures;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the compiled scaled sprites from VSWAP.WL1
|
|
||||||
static List<Sprite> parseSprites(ByteData vswap) {
|
|
||||||
int chunks = vswap.getUint16(0, Endian.little);
|
|
||||||
int spriteStart = vswap.getUint16(2, Endian.little);
|
|
||||||
int soundStart = vswap.getUint16(4, Endian.little);
|
|
||||||
|
|
||||||
List<int> offsets = [];
|
|
||||||
for (int i = 0; i < chunks; i++) {
|
|
||||||
offsets.add(vswap.getUint32(6 + (i * 4), Endian.little));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Sprite> sprites = [];
|
|
||||||
|
|
||||||
// Sprites are located between the walls and the sounds
|
|
||||||
for (int i = spriteStart; i < soundStart; i++) {
|
|
||||||
int offset = offsets[i];
|
|
||||||
if (offset == 0) continue; // Some chunks are empty placeholders
|
|
||||||
|
|
||||||
// Initialize the 64x64 grid with 255 (The Magenta Transparency Color!)
|
|
||||||
Sprite sprite = List.generate(64, (_) => List.filled(64, 255));
|
|
||||||
|
|
||||||
int leftPix = vswap.getUint16(offset, Endian.little);
|
|
||||||
int rightPix = vswap.getUint16(offset + 2, Endian.little);
|
|
||||||
|
|
||||||
// Read the offsets for each vertical column of the sprite
|
|
||||||
List<int> colOffsets = [];
|
|
||||||
for (int x = leftPix; x <= rightPix; x++) {
|
|
||||||
colOffsets.add(
|
|
||||||
vswap.getUint16(offset + 4 + ((x - leftPix) * 2), Endian.little),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int x = leftPix; x <= rightPix; x++) {
|
|
||||||
int colOffset = colOffsets[x - leftPix];
|
|
||||||
if (colOffset == 0) continue;
|
|
||||||
|
|
||||||
int cmdOffset = offset + colOffset;
|
|
||||||
|
|
||||||
// Execute the column drawing commands
|
|
||||||
while (true) {
|
|
||||||
int endY = vswap.getUint16(cmdOffset, Endian.little);
|
|
||||||
if (endY == 0) break; // 0 marks the end of the column
|
|
||||||
endY ~/= 2; // Wolf3D stores Y coordinates multiplied by 2
|
|
||||||
|
|
||||||
int pixelOfs = vswap.getUint16(cmdOffset + 2, Endian.little);
|
|
||||||
|
|
||||||
int startY = vswap.getUint16(cmdOffset + 4, Endian.little);
|
|
||||||
startY ~/= 2;
|
|
||||||
|
|
||||||
for (int y = startY; y < endY; y++) {
|
|
||||||
// The Carmack 286 Hack: pixelOfs + y gives the exact byte address!
|
|
||||||
sprite[x][y] = vswap.getUint8(offset + pixelOfs + y);
|
|
||||||
}
|
|
||||||
cmdOffset += 6; // Move to the next 6-byte instruction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sprites.add(sprite);
|
|
||||||
}
|
|
||||||
return sprites;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts digitized sound effects (PCM Audio) from VSWAP.WL1
|
|
||||||
static List<Uint8List> parseSounds(ByteData vswap) {
|
|
||||||
int chunks = vswap.getUint16(0, Endian.little);
|
|
||||||
int soundStart = vswap.getUint16(4, Endian.little);
|
|
||||||
|
|
||||||
List<int> offsets = [];
|
|
||||||
List<int> lengths = [];
|
|
||||||
|
|
||||||
// Offsets are 32-bit integers starting at byte 6
|
|
||||||
for (int i = 0; i < chunks; i++) {
|
|
||||||
offsets.add(vswap.getUint32(6 + (i * 4), Endian.little));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lengths are 16-bit integers immediately following the offset array
|
|
||||||
int lengthStart = 6 + (chunks * 4);
|
|
||||||
for (int i = 0; i < chunks; i++) {
|
|
||||||
lengths.add(vswap.getUint16(lengthStart + (i * 2), Endian.little));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Uint8List> sounds = [];
|
|
||||||
|
|
||||||
// Sounds start after the sprites and go to the end of the chunks
|
|
||||||
for (int i = soundStart; i < chunks; i++) {
|
|
||||||
int offset = offsets[i];
|
|
||||||
int length = lengths[i];
|
|
||||||
|
|
||||||
if (offset == 0 || length == 0) {
|
|
||||||
sounds.add(Uint8List(0)); // Empty placeholder
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the raw 8-bit PCM audio bytes
|
|
||||||
final soundData = vswap.buffer.asUint8List(offset, length);
|
|
||||||
sounds.add(soundData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sounds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
130
packages/wolf_3d_data/lib/src/wl_parser.dart
Normal file
130
packages/wolf_3d_data/lib/src/wl_parser.dart
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'classes/sprite.dart';
|
||||||
|
|
||||||
|
class WLParser {
|
||||||
|
/// Extracts the 64x64 wall textures from VSWAP.WL1
|
||||||
|
static List<Sprite> parseWalls(ByteData vswap) {
|
||||||
|
final header = _VswapHeader(vswap);
|
||||||
|
|
||||||
|
return header.offsets
|
||||||
|
.take(header.spriteStart)
|
||||||
|
.where((offset) => offset != 0) // Skip empty chunks
|
||||||
|
.map((offset) => _parseWallChunk(vswap, offset))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the compiled scaled sprites from VSWAP.WL1
|
||||||
|
static List<Sprite> parseSprites(ByteData vswap) {
|
||||||
|
final header = _VswapHeader(vswap);
|
||||||
|
final sprites = <Sprite>[];
|
||||||
|
|
||||||
|
// Sprites are located between the walls and the sounds
|
||||||
|
for (int i = header.spriteStart; i < header.soundStart; i++) {
|
||||||
|
int offset = header.offsets[i];
|
||||||
|
if (offset != 0) {
|
||||||
|
sprites.add(_parseSingleSprite(vswap, offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprites;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts digitized sound effects (PCM Audio) from VSWAP.WL1
|
||||||
|
static List<Uint8List> parseSounds(ByteData vswap) {
|
||||||
|
final header = _VswapHeader(vswap);
|
||||||
|
final lengthStart = 6 + (header.chunks * 4);
|
||||||
|
final sounds = <Uint8List>[];
|
||||||
|
|
||||||
|
// Sounds start after the sprites and go to the end of the chunks
|
||||||
|
for (int i = header.soundStart; i < header.chunks; i++) {
|
||||||
|
int offset = header.offsets[i];
|
||||||
|
int length = vswap.getUint16(lengthStart + (i * 2), Endian.little);
|
||||||
|
|
||||||
|
if (offset == 0 || length == 0) {
|
||||||
|
sounds.add(Uint8List(0)); // Empty placeholder
|
||||||
|
} else {
|
||||||
|
// Extract the raw 8-bit PCM audio bytes
|
||||||
|
sounds.add(vswap.buffer.asUint8List(offset, length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Private Helpers ---
|
||||||
|
|
||||||
|
static Sprite _parseWallChunk(ByteData vswap, int offset) {
|
||||||
|
// Generate the 64x64 pixel grid in column-major order functionally
|
||||||
|
return List.generate(
|
||||||
|
64,
|
||||||
|
(x) => List.generate(64, (y) => vswap.getUint8(offset + (x * 64) + y)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Sprite _parseSingleSprite(ByteData vswap, int offset) {
|
||||||
|
// Initialize the 64x64 grid with 255 (The Magenta Transparency Color!)
|
||||||
|
Sprite sprite = List.generate(64, (_) => List.filled(64, 255));
|
||||||
|
|
||||||
|
int leftPix = vswap.getUint16(offset, Endian.little);
|
||||||
|
int rightPix = vswap.getUint16(offset + 2, Endian.little);
|
||||||
|
|
||||||
|
// Parse vertical columns within the sprite bounds
|
||||||
|
for (int x = leftPix; x <= rightPix; x++) {
|
||||||
|
int colOffset = vswap.getUint16(
|
||||||
|
offset + 4 + ((x - leftPix) * 2),
|
||||||
|
Endian.little,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (colOffset != 0) {
|
||||||
|
_parseSpriteColumn(vswap, sprite, x, offset, offset + colOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _parseSpriteColumn(
|
||||||
|
ByteData vswap,
|
||||||
|
Sprite sprite,
|
||||||
|
int x,
|
||||||
|
int baseOffset,
|
||||||
|
int cmdOffset,
|
||||||
|
) {
|
||||||
|
// Execute the column drawing commands
|
||||||
|
while (true) {
|
||||||
|
int endY = vswap.getUint16(cmdOffset, Endian.little);
|
||||||
|
if (endY == 0) break; // 0 marks the end of the column
|
||||||
|
endY ~/= 2; // Wolf3D stores Y coordinates multiplied by 2
|
||||||
|
|
||||||
|
int pixelOfs = vswap.getUint16(cmdOffset + 2, Endian.little);
|
||||||
|
|
||||||
|
int startY = vswap.getUint16(cmdOffset + 4, Endian.little);
|
||||||
|
startY ~/= 2;
|
||||||
|
|
||||||
|
for (int y = startY; y < endY; y++) {
|
||||||
|
// The Carmack 286 Hack: pixelOfs + y gives the exact byte address
|
||||||
|
sprite[x][y] = vswap.getUint8(baseOffset + pixelOfs + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdOffset += 6; // Move to the next 6-byte instruction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper class to parse and store redundant VSWAP header data
|
||||||
|
class _VswapHeader {
|
||||||
|
final int chunks;
|
||||||
|
final int spriteStart;
|
||||||
|
final int soundStart;
|
||||||
|
final List<int> offsets;
|
||||||
|
|
||||||
|
_VswapHeader(ByteData vswap)
|
||||||
|
: chunks = vswap.getUint16(0, Endian.little),
|
||||||
|
spriteStart = vswap.getUint16(2, Endian.little),
|
||||||
|
soundStart = vswap.getUint16(4, Endian.little),
|
||||||
|
offsets = List.generate(
|
||||||
|
vswap.getUint16(0, Endian.little), // total chunks
|
||||||
|
(i) => vswap.getUint32(6 + (i * 4), Endian.little),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,5 @@
|
|||||||
/// More dartdocs go here.
|
/// More dartdocs go here.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
export 'src/classes/level.dart' show Level;
|
export 'src/classes/sprite.dart' hide Matrix;
|
||||||
export 'src/classes/sprite.dart' show Sprite;
|
export 'src/wl_parser.dart' show WLParser;
|
||||||
export 'src/vswap_parser.dart' show VswapParser;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user