stefanhendriks / Dune-II---The-Maker

A remake of the classic Dune 2 - The Building of a Dynasty (by Westwood Studios) with several enhancements. Like: higher screenresolutions, zooming, multiselect, skirmish play, etc.
https://www.dune2themaker.com
302 stars 27 forks source link

Feature/check required files #490

Closed Mira-Chronos closed 2 years ago

Mira-Chronos commented 2 years ago

Need instructions for "data/gfxaudio.dat"

stefanhendriks commented 2 years ago

tbh I need to think this a bit more through. This is a step into the right direction, but this would have to be duplicated several times for other data files. And; on top of that I don't want the wiring between data files and the resource required become tightly coupled (like now).

Me thinking out loud I think what is required is a class (like cFileNameSettings) that gets a resource (ie, a dat file, or directory, etc). But at the same time it will need to be feeded with the 'kinds of data' it has. Ie , the map you have now in cFileNameSettings should be provided and not created in the constructor.

This way we can construct several cFileNameSettings objects (I would probably rename it to cDataContainer or something), and perhaps even have one cDataContainer having multiple other cDataContainers.

So, we end up with having one big data container class where we can ask resources, which basically would do:

The data container gets filled by some kind of construction mechanism (can now be hard-coded, but later it will be read from a resource definition file). (ie for #78).

This way we decouple loading from a .dat file and we decouple how we handle resources.

Now, resources should be 'found' by some unique key, and that key can be an ID (int) I think. I am thinking about this from a mod perspective. Currently the resources are all hard-coded (somewhat) in the codebase. But I'd like to loosen the coupling so we can provide the resources and their references via ini (or any other format) file. That means at the very least there needs to be a definition of all resources in an ini file. Something along the lines of:

[RESOURCE]
ID=1
Filename=Quad.bmp
Type=BITMAP
<maybe other properties>

We don't need to build this whole "read resource definitions from a file" thing yet, but knowing I want to go there (for #78 ) it is good to have this abstraction somewhere. (even though we might still construct this 'resource definition' in code).

So, what does this come down to?

A single cDataContainer class we set up (on game init, when loading game.ini or something); that sets up all resources.

So somewhere in cGame.init we would have a function setupDataContainers(). Within this it will construct a cDataContainer. Something like this (pseudocode):

baseDir = "data";
cDataContainer data = cDataContainer(
   baseDir
);

font = {
 id=1,
 file="bene.ttf",
 type=FONT,
...
}

// Add font resource
data.addResource(font);

// this is for the gfxdata.dat file for Allegro based files, so it knows it reads from a .dat file
quad = {
 id=BMP_QUAD, // header ID (from gfxdata.h)
 type=BITMAP
 source=ALLEGRO_DATAFILE
 File="gfxdata.dat"
}

data.addResource(quad);

obviously, adding a resource must check that the ID is unique.

Within addResource we have something like:

void addResource(eResource resource) {

// check if ID already exists or not (if so, error!)

switch (resource.source) {
  case ALLEGRO_DATAFILE:
    // get data file from map string/datafile (based on file)
   getOrLoadDataFile(resource.file).... // get from dat file
break;
   case FILESYSTEM:
    ... open file, etc...

 }
}

And when we want to use this, we would have to have a layer that can deal with these Resource's, so it can draw, play sound, etc:

id = 1;
font = data.getResource(id);

gfxLayer->drawText(resource, "foo");

// or for fetching data from .dat file, for drawing:

quad = data.getResource(100); // this is the ID of the resource, not the gfxdata.h file

gfxLayer->draw(quad, x, y)

When we do this, we have only a few places left where we are tightly coupled to Allegro, and that is on resource loading and drawing (which are relatively easy to replace then). We could even add other file sources (ie .PAK files or .MIX files, or whatever).

Hope this makes sense?

Wonder what you guys think (@pbomta , @Akatsuki57 )

Mira-Chronos commented 2 years ago

If I can thinking out loud ...

Yes and no.

  1. data - progam

Yes, total separation of data and program is a very good thing and I will modify my work on this PR to comply.

The current code is very dependent as you point out. It's work to decouple it and it's a great thing to take on that challenge.

  1. Platform abstraction

No, platform abstraction is not a good thing. If we weren't developing a game, I would say quite the opposite: but we need performance.

One of the reasons I put forward is that the philosophy of the platform changes, depending on the platform. SDL2 brings the writing of textures to the GPU. If it's not to use this feature, you might as well do nothing because its whole point is to gain performance. Writing in the GPU means a change in philosophy and also performance gains. Let's imagine playing d2tm on a 4k screen...

For exemple !!! Not to do. If we have to rewrite d2tm with openGL, it will even be easy because SDL2 is very close to OpenGL but if we had to redo it in Vulkan for example, everything would have to be reviewed because Vulkan does not think like SDL2 at all and OpenGL.

I'm also not saying that you absolutely have to use all the features of the target library, but at least everything that gives it its flavor, which compromises the abstraction.

Mira-Chronos commented 2 years ago

Still aloud: I propose to work on this idea in several stages. Firstly: 1) create a class that just reads ini files with d2tm specifics 2) take out of the code all the game data that is hardcoded. At the same time, reorganize the source code to best apply the principles of SOLID. https://en.wikipedia.org/wiki/SOLID

stefanhendriks commented 2 years ago

I am not experienced with SDL2 or OpenGL, but I can't imagine having one layer of indirection to be that much of a problem?

Just to be clear, I would still use direct access to all kinds of SDL objects for instance. So a Resource would still hold a pointer to a SDL_Surface for instance. I am merely talking about calls like blit or drawSprite or playSound. We can wrap those around SDL or Allegro without losing (much) speed?

stefanhendriks commented 2 years ago

ah yes SOLID practices; we are already working with them; unfortunately 15-18 year old me didn't when I started D2TM :D

stefanhendriks commented 2 years ago

Made a few changes; looking good.

I have made it so that we can see which validations fail, example:

image

This way , when something is wrong we can see what is wrong and where. Which is kinda neat.

I love that we have a settings.ini file now!