Fixed shareware sprites

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 20:12:59 +01:00
parent d4183beb3f
commit 173339af82
3 changed files with 34 additions and 21 deletions

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/wolf_3d_entities.dart'; import 'package:wolf_3d_entities/wolf_3d_entities.dart';
import 'package:wolf_3d_flutter/wolf_3d.dart';
import 'package:wolf_3d_renderer/color_palette.dart'; import 'package:wolf_3d_renderer/color_palette.dart';
class SpriteGallery extends StatelessWidget { class SpriteGallery extends StatelessWidget {
@@ -8,6 +9,8 @@ class SpriteGallery extends StatelessWidget {
const SpriteGallery({super.key, required this.sprites}); const SpriteGallery({super.key, required this.sprites});
bool get isShareware => Wolf3d.I.activeGame.version == GameVersion.shareware;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -25,10 +28,12 @@ class SpriteGallery extends StatelessWidget {
// --- Check which enemy owns this sprite --- // --- Check which enemy owns this sprite ---
String label = "Sprite Index: $index"; String label = "Sprite Index: $index";
for (final enemy in EnemyType.values) { for (final enemy in EnemyType.values) {
if (enemy.claimsSpriteIndex(index)) { if (enemy.claimsSpriteIndex(index, isShareware: isShareware)) {
final EnemyAnimation? animation = enemy.getAnimationFromSprite( final EnemyAnimation? animation = enemy.getAnimationFromSprite(
index, index,
isShareware: isShareware,
); );
// Appends the enum name (e.g., "guard", "dog") // Appends the enum name (e.g., "guard", "dog")
label += "\n${enemy.name}"; label += "\n${enemy.name}";
@@ -37,11 +42,6 @@ class SpriteGallery extends StatelessWidget {
label += "\n${animation.name}"; label += "\n${animation.name}";
} }
// Append the Map IDs for level editing reference
int staticBase = enemy.mapData.baseId;
label +=
"\nStat: $staticBase (E), ${staticBase + 1} (M), ${staticBase + 2} (H)";
break; break;
} }
} }

View File

@@ -210,6 +210,9 @@ abstract class Enemy extends Entity {
final type = EnemyType.fromMapId(objId); final type = EnemyType.fromMapId(objId);
if (type == null) return null; if (type == null) return null;
// Reject enemies that don't exist in the shareware data!
if (isSharewareMode && !type.existsInShareware) return null;
final mapData = type.mapData; final mapData = type.mapData;
// ALL enemies have explicit directional angles! // ALL enemies have explicit directional angles!

View File

@@ -3,7 +3,6 @@ import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart'; import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart';
/// Maps all possible animation states to their specific frame ranges.
class EnemyAnimationMap { class EnemyAnimationMap {
final SpriteFrameRange idle; final SpriteFrameRange idle;
final SpriteFrameRange walking; final SpriteFrameRange walking;
@@ -87,6 +86,7 @@ enum EnemyType {
dying: SpriteFrameRange(227, 230), dying: SpriteFrameRange(227, 230),
dead: SpriteFrameRange(232, 232), dead: SpriteFrameRange(232, 232),
), ),
existsInShareware: false,
), ),
officer( officer(
mapData: EnemyMapData(MapObject.officerStart), mapData: EnemyMapData(MapObject.officerStart),
@@ -98,12 +98,18 @@ enum EnemyType {
dying: SpriteFrameRange(278, 281), dying: SpriteFrameRange(278, 281),
dead: SpriteFrameRange(283, 283), dead: SpriteFrameRange(283, 283),
), ),
existsInShareware: false,
); );
final EnemyMapData mapData; final EnemyMapData mapData;
final EnemyAnimationMap animations; final EnemyAnimationMap animations;
final bool existsInShareware;
const EnemyType({required this.mapData, required this.animations}); const EnemyType({
required this.mapData,
required this.animations,
this.existsInShareware = true,
});
static EnemyType? fromMapId(int id) { static EnemyType? fromMapId(int id) {
for (final type in EnemyType.values) { for (final type in EnemyType.values) {
@@ -112,10 +118,21 @@ enum EnemyType {
return null; return null;
} }
bool claimsSpriteIndex(int index) => animations.getAnimation(index) != null; /// Returns the animations only if the enemy actually exists in the current version.
EnemyAnimationMap? getAnimations(bool isShareware) {
if (isShareware && !existsInShareware) return null;
return animations;
}
EnemyAnimation? getAnimationFromSprite(int spriteIndex) { bool claimsSpriteIndex(int index, {bool isShareware = false}) {
return animations.getAnimation(spriteIndex); return getAnimations(isShareware)?.getAnimation(index) != null;
}
EnemyAnimation? getAnimationFromSprite(
int spriteIndex, {
bool isShareware = false,
}) {
return getAnimations(isShareware)?.getAnimation(spriteIndex);
} }
int getSpriteFromAnimation({ int getSpriteFromAnimation({
@@ -125,40 +142,33 @@ enum EnemyType {
double angleDiff = 0, double angleDiff = 0,
int? walkFrameOverride, int? walkFrameOverride,
}) { }) {
// We don't need to check isShareware here, because if the entity exists
// in the game world, it implicitly passed the version check during spawn.
final range = animations.getRange(animation); final range = animations.getRange(animation);
// Calculates which of the 8 directions the enemy is facing relative to player
int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8; int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
if (octant < 0) octant += 8; if (octant < 0) octant += 8;
return switch (animation) { return switch (animation) {
EnemyAnimation.idle => range.start + octant, EnemyAnimation.idle => range.start + octant,
EnemyAnimation.walking => () { EnemyAnimation.walking => () {
// Automatically calculates frames per angle (e.g. 32 frames / 8 angles = 4 frames)
int framesPerAngle = range.length ~/ 8; int framesPerAngle = range.length ~/ 8;
if (framesPerAngle < 1) framesPerAngle = 1; // Failsafe if (framesPerAngle < 1) framesPerAngle = 1;
int frame = walkFrameOverride ?? (elapsedMs ~/ 150) % framesPerAngle; int frame = walkFrameOverride ?? (elapsedMs ~/ 150) % framesPerAngle;
return range.start + (frame * 8) + octant; return range.start + (frame * 8) + octant;
}(), }(),
EnemyAnimation.attacking => () { EnemyAnimation.attacking => () {
int time = elapsedMs - lastActionTime; int time = elapsedMs - lastActionTime;
// Progresses through attack frames based on time, clamping at the last frame
int mappedFrame = (time ~/ 150).clamp(0, range.length - 1); int mappedFrame = (time ~/ 150).clamp(0, range.length - 1);
return range.start + mappedFrame; return range.start + mappedFrame;
}(), }(),
EnemyAnimation.pain => range.start, EnemyAnimation.pain => range.start,
EnemyAnimation.dying => () { EnemyAnimation.dying => () {
int time = elapsedMs - lastActionTime; int time = elapsedMs - lastActionTime;
// Progresses through death frames, staying on the final frame
int mappedFrame = (time ~/ 150).clamp(0, range.length - 1); int mappedFrame = (time ~/ 150).clamp(0, range.length - 1);
return range.start + mappedFrame; return range.start + mappedFrame;
}(), }(),
EnemyAnimation.dead => range.start, EnemyAnimation.dead => range.start,
}; };
} }