X Tutup
Skip to content

ALG: Initial version of American Laser Games engine#6500

Merged
sev- merged 10 commits intoscummvm:masterfrom
dckone:master
Apr 10, 2025
Merged

ALG: Initial version of American Laser Games engine#6500
sev- merged 10 commits intoscummvm:masterfrom
dckone:master

Conversation

@dckone
Copy link

@dckone dckone commented Mar 22, 2025

Initial version of the American Laser Games engine.

Currently supported and can be completed:
Crime Patrol DOS
Crime Patrol Demo DOS
Drug Wars DOS
Drug Wars Demo DOS
Who Shot Johnny Rock? DOS
Mad Dog McCree DOS
Mad Dog II: The Lost Gold DOS
Space Pirates DOS
Space Pirates Demo DOS

Partially implemented, but unsupported and can probably not be completed
The Last Bounty Hunter DOS
The Last Bounty Hunter Demo DOS

So far only DOS versions are supported, in the future 3DO versions are quite possible.

Of the supported games, there are still some issues remaining:

  • Most original interpreters do not allow the cursor to go outside of the video area in most directions, this is unimplemented but minor.
  • Some minor timing differences in a few scenes.
  • Video decoder is not implemented similar to other video decoders and currently lives in engines/alg. Not sure if this is an issue as this video format is only used by ALG AFAIK.
  • Video fade-in is not implemented across the board, but it is not used that often and the effect is minor IMHO.
  • TODO: Weird pause at the beginning of MadDog2, probably a bug
  • Maybe some style issues in the code

Feedback would be much appreciated :)

P.S. Sorry for the squashed commit, the reason I did that is because so far I only commited to a local repo and only manually transferred to github infrequently, also my previous (local) commits did not follow the ScummVM commit rules yet.
Also there is quite a lot of refactoring going on in my local commits which makes them not very suitable for reviewing I guess.

@dckone dckone changed the title ALG: Initial version DRAFT: ALG: Initial version Mar 22, 2025
@dckone dckone changed the title DRAFT: ALG: Initial version ALG: Initial version of American Laser Games engine Mar 22, 2025
@dckone
Copy link
Author

dckone commented Mar 22, 2025

I am aware there are still issues with memory leaks, I am trying to fix those in the next days

Copy link
Member

@sev- sev- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid work!

Checked 19 / 29 files, put my comments and suggestions. Unfortunately some renames are required.

_paletteDirty = true;
_screen = new Graphics::Surface();
_rnd = new Common::RandomSource("alg");
_screen->create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is advised to not do anything heavy in the constructor. Please move these to Game::run()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dckone the code is still in the init() function. Perhaps you forgot to push your changes?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bluegr I do not understand. The code is in the init() method in Game, each inheriting game class then has a call to its specific init() method in ::run(), which in turn calls the parents init() method.

Is there a problem with that setup?

@dckone dckone requested a review from sev- March 23, 2025 10:41
@bluegr
Copy link
Member

bluegr commented Mar 23, 2025

Since each game has its own hardcoded logic, it would make sense to group all the game logic in a folder. For example, instead of having game_maddog.cpp and game_maddog2.cpp, we could place all of these files in a logic folder, and end up with e.g. logic/maddog.cpp and logic/maddog2.cpp

@dckone
Copy link
Author

dckone commented Mar 23, 2025

Since each game has its own hardcoded logic, it would make sense to group all the game logic in a folder. For example, instead of having game_maddog.cpp and game_maddog2.cpp, we could place all of these files in a logic folder, and end up with e.g. logic/maddog.cpp and logic/maddog2.cpp

I like the idea, but not 100% sure how to achieve that at the moment. Currently each game has minor differences in handling its main game loop, as well as calling some functions provided in the base Game class.

The state of the game is held be the extended class, for example GameMaddog. And then the majority of the code is just functions called by the game script/Scene file. These again need access to the state.

So not 100% sure how to decouple all of this in a way that makes sense.

// EDIT: or do you mean just literally moving the game-specific files as-is to the subfolder? In that case that would be easy of course...

@bluegr
Copy link
Member

bluegr commented Mar 23, 2025

// EDIT: or do you mean just literally moving the game-specific files as-is to the subfolder? In that case that would be easy of course...

Yes, I was asking for the game-specific files to be moved. Any further big refactorings in the code can be done at a later stage in-tree

@dckone
Copy link
Author

dckone commented Mar 23, 2025

Yes, I was asking for the game-specific files to be moved. Any further big refactorings in the code can be done at a later stage in-tree

ok, i moved it.

@dckone
Copy link
Author

dckone commented Mar 23, 2025

I have a question: Because my fork was already months behind the ScummVM master branch, I updated using the Github UI. Now the following can be seen in the PR commit history:
image
I was informed that might be a problem, as branch merges are not allowed.

How can I fix this? My git skills are not the best.

Also, is there a way to update my fork to parent master branch without creating such a problem?

@lotharsm
Copy link
Member

lotharsm commented Mar 23, 2025

Also, is there a way to update my fork to parent master branch without creating such a problem?

I can't reliably comment on how to fix the existing commit, because everytime I tried something like that, I horribly failed - so I'll leave this up to the others :-D

In order to prevent this to happen in the future, make sure that your git configuration is set to

git config pull.rebase true

This will avoid merge commits by always rebasing the current upstream code onto your fork.

Then, you can update your fork by running

git pull upstream master, assuming that you configured your repo to use github.com/scummvm/scummvm as the upstream remote. I don't think syncing by rebasing is possible via the GitHub UI...

@bluegr
Copy link
Member

bluegr commented Mar 23, 2025

I have a question: Because my fork was already months behind the ScummVM master branch, I updated using the Github UI. Now the following can be seen in the PR commit history: image I was informed that might be a problem, as branch merges are not allowed.

How can I fix this? My git skills are not the best.

Also, is there a way to update my fork to parent master branch without creating such a problem?

I could help with that. Essentially, the way to do this properly is to rebase your commits on top of the current ScummVM head. The problem is that this will require rebasing, which will rewrite your branch's history (via force pushing), since the commit hashes won't be the same anymore. If someone else does it for you, you'll have to reset your local branch with the changes from the remote branch. If you're OK with that, I can go ahead and fix the commit history for you.

@dckone
Copy link
Author

dckone commented Mar 24, 2025

I could help with that. Essentially, the way to do this properly is to rebase your commits on top of the current ScummVM head. The problem is that this will require rebasing, which will rewrite your branch's history (via force pushing), since the commit hashes won't be the same anymore. If someone else does it for you, you'll have to reset your local branch with the changes from the remote branch. If you're OK with that, I can go ahead and fix the commit history for you.

sounds great, I appreciate the help. I backed up my local repo just to be safe, and invited you as a collaborator on my github repo. I hope that is enough to give you write access.

Copy link
Member

@sev- sev- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for addressing the review feedback so quickly.

I have only few tiny notes, so I consider it ready for merge.

bool AlgEngine::hasFeature(EngineFeature f) const {
return (f == kSupportsReturnToLauncher) ||
(f == kSupportsLoadingDuringRuntime) ||
(f == kSupportsSavingDuringRuntime);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may also consider to add ExtendedSaves support here, so then screenshots, and other metadata is handled for you. But it could be added after the merge.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I would like to leave it as-is. There is only one save slot, as only saving via the original game interface is supported. Also screenshots might be misleading, as the save does not save exactly the location in the scene, but just the scene itself, so the scene is restarted on load, just like in the original interpreters.

@bluegr
Copy link
Member

bluegr commented Mar 25, 2025

@dckone Fixed commit history

The commits are created by you, and committed by me (which is how git works)

@dckone
Copy link
Author

dckone commented Mar 25, 2025

@dckone Fixed commit history

The commits are created by you, and committed by me (which is how git works)

thank you very much for the help :)

int32 dstX = x;
int32 dstY = y;
Common::Rect subRect = Common::Rect(0, 0, src->w, src->h);
if (dstX < 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a clip function in Graphics::Surface that may help simplify this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will look into that hopefully soon

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after trying for 2 hours i gave up, somehow I do not understand how this clip function could work in my case. I might look into it again if I have more time in the future.

Comment on lines +94 to +96
for (uint16 x = 0; x < width; x++) {
aniImage->setPixel(x, y, aniFile.readByte());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (uint16 x = 0; x < width; x++) {
aniImage->setPixel(x, y, aniFile.readByte());
}
aniFile.read(aniImage->getBasePtr(0, y), width);

Something similar can also be done in other places that read in rows of pixels at a time.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, will look into it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
uint8 *pixels = new uint8[width * height]();
vgaFile.read(pixels, width * height);
surface->setPixels(pixels);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaks the memory allocated when calling surface->create. It might be simpler to just read directly to the surface memory rather than creating a separate buffer for it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do I interpret that correctly that the "new Graphics::Surface()" already allocates a pixel buffer and then the create() allocates another one and the original one is not freed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close - create() creates the first buffer, then setPixels replaces it with the manually allocated one without freeing the first one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be fixed now

}
while (runLength > 0) {
if (color > 0) {
_frame->setPixel(x, y, color);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may find memset useful here for performance with longer values of runLength.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will look into it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

error("GameBountyHunter::run(): Cannot find scene %s in libfile", scene->_name.c_str());
}
_sceneSkipped = false;
Audio::PacketizedAudioStream *audioStream = _videoDecoder->getAudioStream();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend keeping the implementation details for the video decoder self-contained in case you need to support alternative video formats later.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you mean by that. Is it that I should let the videoDecoder handle the playback of the audio?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i moved the audio stream handling into the video decoder as a quick fix.

In the future I might rework the video decoding, currently it is a bit hacky in places. Also the frame timing should be handled by the video decoder, currently it is handled by game logic.

@ccawley2011
Copy link
Member

Would it be possible to provide the demos listed in the detection tables on our demos page?

@dckone
Copy link
Author

dckone commented Mar 28, 2025

Would it be possible to provide the demos listed in the detection tables on our demos page?

Sure, I need to request access, then I can supply these. I will followup on that soon hopefully.
Same problem with some of the previously mentioned issues, starting tomorrow I will be on vacation for 2 weeks. Hopefully I can sneak in some coding on these issues, if not I will definitely work on it once back.

Comment on lines +41 to +52
case Alg::GType_CRIME_PATROL: {
GameCrimePatrol *game = new GameCrimePatrol(this, gd);
_debugger = new DebuggerCrimePatrol(game);
_game = game;
break;
}
case Alg::GType_DRUG_WARS: {
GameDrugWars *game = new GameDrugWars(this, gd);
_debugger = new DebuggerDrugWars(game);
_game = game;
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case Alg::GType_CRIME_PATROL: {
GameCrimePatrol *game = new GameCrimePatrol(this, gd);
_debugger = new DebuggerCrimePatrol(game);
_game = game;
break;
}
case Alg::GType_DRUG_WARS: {
GameDrugWars *game = new GameDrugWars(this, gd);
_debugger = new DebuggerDrugWars(game);
_game = game;
break;
}
case Alg::GType_CRIME_PATROL: {
_game = new GameCrimePatrol(this, gd);
_debugger = new DebuggerCrimePatrol(_game);
break;
}
case Alg::GType_DRUG_WARS: {
_game = new GameDrugWars(this, gd);
_debugger = new DebuggerDrugWars(_game);
break;
}

Same for the cases below

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i implement that change it does not compile anymore:

engines/alg/alg.cpp: In constructor 'Alg::AlgEngine::AlgEngine(OSystem*, const Alg::AlgGameDescription*)':
engines/alg/alg.cpp:43:53: error: invalid conversion from 'Alg::Game*' to 'Alg::GameCrimePatrol*' [-fpermissive]
43 | _debugger = new DebuggerCrimePatrol(_game);

@sev-
Copy link
Member

sev- commented Apr 10, 2025

Thanks, merging

@sev- sev- merged commit 3e03257 into scummvm:master Apr 10, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

X Tutup