Handle exit elevators and secret levels

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 15:25:22 +01:00
parent 45ab8e4aed
commit 5f3e3bb823
8 changed files with 187 additions and 105 deletions

View File

@@ -54,8 +54,9 @@ class _WolfRendererState extends State<WolfRenderer>
double damageFlashOpacity = 0.0;
late int _currentMapIndex;
late WolfLevel _currentLevel;
late int _currentEpisodeIndex;
late int _currentLevelIndex;
int? _returnLevelIndex;
List<Entity> entities = [];
@@ -65,52 +66,47 @@ class _WolfRendererState extends State<WolfRenderer>
_initGame();
}
void _loadLevel(int mapIndex) {
// Grab the specific level from the singleton
_currentLevel = Wolf3d.I.levels[mapIndex];
Future<void> _initGame() async {
// 1. Setup our starting indices
_currentEpisodeIndex = widget.startingEpisode;
_currentLevelIndex = 0;
// Play the exact track id Software intended for this level!
Wolf3d.I.audio.playLevelMusic(_currentLevel);
// 2. Load the first floor!
_loadLevel();
// TODO: Initialize player position, spawn enemies based on difficulty, etc.
debugPrint("Loaded Level: ${_currentLevel.name}");
}
_gameLoop = createTicker(_tick)..start();
_focusNode.requestFocus();
void _onLevelCompleted() {
Wolf3d.I.audio.stopMusic();
// When the player hits the elevator switch, advance the map
setState(() {
_currentMapIndex++;
// Check if they beat the episode (each episode is 10 levels)
int maxLevelForEpisode = (widget.startingEpisode * 10) + 9;
if (_currentMapIndex > maxLevelForEpisode) {
// TODO: Handle episode completion (show victory screen, return to menu)
debugPrint("Episode Completed!");
} else {
_loadLevel(_currentMapIndex);
}
_isLoading = false;
});
}
Future<void> _initGame() async {
// 1. Calculate the starting index
_currentMapIndex = widget.startingEpisode * 10;
void _loadLevel() {
// 1. Clean up the previous level's state
entities.clear();
damageFlashOpacity = 0.0;
// 2. Load the initial level data
_loadLevel(_currentMapIndex);
// 2. Grab the exact level from our new Episode hierarchy
final episode = widget.data.episodes[_currentEpisodeIndex];
activeLevel = episode.levels[_currentLevelIndex];
// Get the first level out of the data class
activeLevel = widget.data.levels.first;
// Set up your grids directly from the active level
currentLevel = activeLevel.wallGrid;
// 3. DEEP COPY the wall grid! If we don't do this, destroying walls/doors
// will permanently corrupt the map data in the Wolf3d singleton.
currentLevel = List.generate(
64,
(y) => List.from(activeLevel.wallGrid[y]),
);
final Level objectLevel = activeLevel.objectGrid;
// 4. Initialize Managers
doorManager.initDoors(currentLevel);
pushwallManager.initPushwalls(currentLevel, objectLevel);
// 5. Play Music
Wolf3d.I.audio.playLevelMusic(activeLevel);
// 6. Spawn Player and Entities
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) {
int objId = objectLevel[y][x];
@@ -119,25 +115,17 @@ class _WolfRendererState extends State<WolfRenderer>
if (objId >= MapObject.playerNorth && objId <= MapObject.playerWest) {
double spawnAngle = 0.0;
switch (objId) {
case MapObject.playerNorth:
spawnAngle = 3 * math.pi / 2;
break;
case MapObject.playerEast:
spawnAngle = 0.0;
break;
case MapObject.playerSouth:
spawnAngle = math.pi / 2;
break;
case MapObject.playerWest:
spawnAngle = math.pi;
break;
if (objId == MapObject.playerNorth) {
spawnAngle = 3 * math.pi / 2;
} else if (objId == MapObject.playerEast) {
spawnAngle = 0.0;
} else if (objId == MapObject.playerSouth) {
spawnAngle = math.pi / 2;
} else if (objId == MapObject.playerWest) {
spawnAngle = math.pi;
}
player = Player(
x: x + 0.5,
y: y + 0.5,
angle: spawnAngle,
);
player = Player(x: x + 0.5, y: y + 0.5, angle: spawnAngle);
} else {
Entity? newEntity = EntityRegistry.spawn(
objId,
@@ -147,31 +135,54 @@ class _WolfRendererState extends State<WolfRenderer>
widget.data.sprites.length,
isSharewareMode: widget.data.version == GameVersion.shareware,
);
if (newEntity != null) {
entities.add(newEntity);
}
if (newEntity != null) entities.add(newEntity);
}
}
}
// 7. Clear non-solid blocks from the collision grid
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) {
int id = currentLevel[y][x];
if ((id >= 1 && id <= 63) || (id >= 90 && id <= 101)) {
// Leave walls and doors solid
} else {
if (!((id >= 1 && id <= 63) || (id >= 90 && id <= 101))) {
currentLevel[y][x] = 0;
}
}
}
_bumpPlayerIfStuck();
_gameLoop = createTicker(_tick)..start();
_focusNode.requestFocus();
debugPrint("Loaded Floor: ${_currentLevelIndex + 1} - ${activeLevel.name}");
}
void _onLevelCompleted({bool isSecretExit = false}) {
Wolf3d.I.audio.stopMusic();
setState(() {
_isLoading = false;
final currentEpisode = widget.data.episodes[_currentEpisodeIndex];
if (isSecretExit) {
// Save the next normal map index so we can return to it later
_returnLevelIndex = _currentLevelIndex + 1;
_currentLevelIndex = 9; // Jump to the secret map
debugPrint("Found the Secret Exit!");
} else {
// Are we currently ON the secret map, and need to return?
if (_currentLevelIndex == 9 && _returnLevelIndex != null) {
_currentLevelIndex = _returnLevelIndex!;
_returnLevelIndex = null;
} else {
_currentLevelIndex++; // Normal progression
}
}
// Did we just beat the last map in the episode (Map 9) or the secret map (Map 10)?
if (_currentLevelIndex >= currentEpisode.levels.length ||
_currentLevelIndex > 9) {
debugPrint("Episode Completed! You win!");
Navigator.of(context).pop();
} else {
_loadLevel();
}
});
}
@@ -299,11 +310,37 @@ class _WolfRendererState extends State<WolfRenderer>
}
if (inputManager.isInteracting) {
doorManager.handleInteraction(
player.x,
player.y,
player.angle,
);
// 1. Calculate the tile exactly 1 block in front of the player
int targetX = (player.x + math.cos(player.angle)).toInt();
int targetY = (player.y + math.sin(player.angle)).toInt();
// Ensure we don't check outside the map bounds
if (targetX >= 0 && targetX < 64 && targetY >= 0 && targetY < 64) {
// 2. Check the WALL grid for the physical switch texture
int wallId = currentLevel[targetY][targetX];
if (wallId == MapObject.normalElevatorSwitch) {
// Player hit the switch!
_onLevelCompleted(isSecretExit: false);
return (movement: const Coordinate2D(0, 0), dAngle: 0.0);
} else if (wallId == MapObject.secretElevatorSwitch) {
_onLevelCompleted(isSecretExit: true);
return (movement: const Coordinate2D(0, 0), dAngle: 0.0);
}
// 3. Check the OBJECT grid for invisible floor triggers
// (Some custom maps use these instead of wall switches)
int objId = activeLevel.objectGrid[targetY][targetX];
if (objId == MapObject.normalExitTrigger) {
_onLevelCompleted(isSecretExit: false);
return (movement: movement, dAngle: dAngle);
} else if (objId == MapObject.secretExitTrigger) {
_onLevelCompleted(isSecretExit: true);
return (movement: movement, dAngle: dAngle);
}
}
// 4. If it wasn't an elevator, try opening a door or pushing a wall
doorManager.handleInteraction(player.x, player.y, player.angle);
pushwallManager.handleInteraction(
player.x,
player.y,