这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@brisvag
Copy link
Contributor

@brisvag brisvag commented Nov 21, 2019

Judging by #920 and #1125, the current idea for a game launcher seems to be a python/Qt, cross-platform implementation. I felt like playing around with PyQt5, so I tried to implement @heinezen 's tiled launcher mockup.

Functionality is basically nonexistent at the moment, except for the Quit button, which works marvellously :)

@TheJJ
Copy link
Member

TheJJ commented Nov 21, 2019

Hah, somebody actually started working on the launcher 😄, thank you very much!

My first thought was: is pyqt5 the right library? I've read that the new upstream library is pyside2, so wouldn't that be the better choice?

@TheJJ TheJJ added area: launcher Related to the game launcher nice new thing ☺ A new feature that was not there before lang: python Done in Python code labels Nov 21, 2019
@TheJJ
Copy link
Member

TheJJ commented Nov 21, 2019

Ah, another idea: We do the main openage gui with qtquick and QML, so I think it would be beneficial if the launcher's UI design would also be done in QML.

@brisvag
Copy link
Contributor Author

brisvag commented Nov 21, 2019

I didn't even know about PySide2 :D As far as I can tell, its API should be mostly the same as PyQt's. I have no experience with QML though.

@TheJJ
Copy link
Member

TheJJ commented Nov 21, 2019

@brisvag
Copy link
Contributor Author

brisvag commented Nov 22, 2019

Thanks for the links! I also found some other decent documentation, and I've been playing around with it quite a lot in the past few days.

To be honest, I'm not very pleased :/ It seems to me that no matter what you do, you still need to integrate python for a lot of the heavy lifting and functionality, with QML providing just a shell and basic slots/connections (unless you use some js, which is even worse for me :P).

The integration is far from seamless and I feel like I have to jump through a lot of hoops to do stuff that was otherwise quite straight forward in pure python.

I get the appeal of the declarative style, but I really felt that I have less understanding and ultimately less control over what's actually happening.

Probably I'm just very inexperienced with it and see only the downsides :P Anyways, if you guys really want to use QML instead of pure python, I think it's best if someone else takes care of it - and maybe I can chip in when I feel more comfortable.

@TheJJ
Copy link
Member

TheJJ commented Nov 26, 2019

Okay :)

you still need to integrate python for a lot of the heavy lifting and functionality

Yes indeed, and that's why i thought that my proposal would still fit you.

If you write the GUI declaration nice and encapsulated, then I'm totally ok with a non-qml implementation, because if necessary and useful, we could then replace the py-declarations by qml some time later.

For the design and layout, we gathered many ideas here: https://pad.stusta.de/p/openage-launcher
Of course you don't have to implement all of that, but maybe keep in mind that your work will be the foundation for all the things to come 😄

@brisvag
Copy link
Contributor Author

brisvag commented Nov 27, 2019

Ok, I'm going to try to get something done then :)

Current goal is to just work on the structure and layout, trying to make it as easy as possible to add/change the functionality later on.

I have some questions based on heinezen's mockup and the stuff in the linked pad:

  • In the tiled mockup, there is a distinction between "content packs" and "mods". Is that some sort of distinction between games and mods, or was it meant to be a distinction between modpacks and individual mods?
  • do you think adding a local mod, browsing a mod repo and possibly converting games into openage content should all fall under the same add button - with later discrimination - or should they be separated? If yes, how?
  • in the pad, multiplayer profiles are mentioned. Does that mean login form and so on? That should be optional, I imagine. Are there clearer plans about this?
  • what does an X mean at the beginning of a line in the pad? And an arrow? and Colors?

and more, but for now that's enough to think about :P

@heinezen
Copy link
Member

You should take my launcher design with a grain of salt because it's really old :D The design is from before nyan was finished, or just when it was finished, and a lot of our demands and goals have changed. So it might be better to wait until we have thought about the new design. If you want to use the mockup as a template, that is fine too, although we maybe have to scrap a lot of the design later on depending on the new design :D

In the tiled mockup, there is a distinction between "content packs" and "mods". Is that some sort of distinction between games and mods, or was it meant to be a distinction between modpacks and individual mods?

I think this was an old idea where content packs would be mods with no dependencies (like a converted AoE2) and normal mods would have a dependency on a content pack. We didn't follow up with this and the current idea is to have mod profiles which can store an arbitrary configuration of different modpacks and their load order. This configuration would then be selected by a player.

do you think adding a local mod, browsing a mod repo and possibly converting games into openage content should all fall under the same add button - with later discrimination - or should they be separated? If yes, how?

Hmm.. separation is probably better, at least for conversion and adding mods. Conversion has a different workflow than installing a mod and I'm not sure if this can be unified with the general mod repo installer. We would have to see how the scenario design turns out to know if they can be integrated into a unified part of the UI.

in the pad, multiplayer profiles are mentioned. Does that mean login form and so on? That should be optional, I imagine. Are there clearer plans about this?

The idea is to sync the config files (hotkeys, mod profiles) to a cloud storage. It is optional, yes.

what does an X mean at the beginning of a line in the pad? And an arrow? and Colors?

X means the user story is unique to the role. The arrow is something we thought about but are not sure if we want to have it available. The colors just show which person wrote what in the pad.

@brisvag
Copy link
Contributor Author

brisvag commented Jan 25, 2020

I tried picking this up again. Playing around it with, I feel like it's just gonna get messier and more complicated the more functionality I add to it. That is unless I properly separate functionality from UI from the get-go.

I'm not sure what the best practices about this are; I was thinking of just writing the whole thing in pure python and essentially exposing an API to then use from PyQt. Bonus points to this because it would make it easier to switch to QML in the future, if needed.

What do you think? Am I overthinking this?

@TheJJ
Copy link
Member

TheJJ commented Jan 26, 2020

That makes sense, separating logical components is generally a useful constraint to keep in mind, I would say. But the drawback is possible unneeded overengineering, so the golden path is to do both: plan ahead so things can be modularized and factored out, but design things direcly first, so only the parts that turn out to be worth separating can be separated easily afterwards once its clear it's better having them separated. Our well-known "development" fits there perfectly, you not just spit out the perfect solution, but rather work towards it - develop it.

One of the biggest challenges in programming, in my experience, is GUI integration. Because UIs are so interconnected with all the logic, the "right" way to integrate it is incredibly hard to figure out. And to make things worse, the "right" way differs vastly from application to application. So I fear the launcher requires a different approach than e.g. the openage-ingame-UI.

All that said, I guess you design the launcher in such a way it's fully functional from the commandline, too. That means there's commands for installing mods, (de)activating them, etc. Then you could design a Qt GUI on top of that: The GUI code will be very separate, and the whole launcher logic is already in place (for the CLI).

@brisvag
Copy link
Contributor Author

brisvag commented Jan 28, 2020

Good, I'll try out that approach, for now.

I suppose the launcher should be able to load modpack information in order to check conflicts, allow loading, downloading, and so on.

If I'm getting this right, for this to work there should be a parser/container for the modpack .nfo files discussed at #632. Does that (or something close to it) already exist somewhere? I looked at ModpackInfo in #1151, but that's only for generating, not parsing.

Otherwise, I could make up a mockup (or fully functional?) object that will contain that information and work with that.

@brisvag
Copy link
Contributor Author

brisvag commented Jan 31, 2020

I dropped the gui for now. I just pushed a mockup of what I thought the launcher logic could look like. There's some pseudocode here and there. What are your thoughts?

@simonsan simonsan requested a review from heinezen January 31, 2020 09:34
Copy link
Member

@heinezen heinezen left a comment

Choose a reason for hiding this comment

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

I only looked at the latest commit, so there might be more things to discuss, but this should do for now :)

self.enabled = []

@staticmethod
def get_version(self):
Copy link
Member

Choose a reason for hiding this comment

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

get_version() should return the version of the launcher. Accessing the engine version should be implemented in its own function get_engine_version.

"""
returns current openage version
"""
return "0.0.0"
Copy link
Member

Choose a reason for hiding this comment

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

The version number not be hardcoded and instead be fetched from a config file or somewhere else.

Comment on lines 57 to 59
def import_remote_mod(self, url):
mod = ModInfo(url=url)
self.remote_mods.add(mod)
Copy link
Member

Choose a reason for hiding this comment

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

Specifying a URL here will give us a lot of security issues. The Launcher should rather load a list of repo URL from a (signed) file, check the signature and then search the existing repos for the UID of a mod.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I never had to think about security before :P So, how would you handle this at the ModInfo level? Just feed it something like a file object on init, and handle the rest at a higher level?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think ModInfo should handle this. It'd be better to implement a dedicated mod manager that handles adding and updating mods, similar to what Linux distro package managers like apt do (example). The launcher should just provide a GUI for it.

If you have never build secure applications before, it's probably better to leave this out for now :D

Copy link
Contributor

@simonsan simonsan Feb 4, 2020

Choose a reason for hiding this comment

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

Here is an example from mod.io docs and its' python wrapper:
https://github.com/ClementJ18/mod.io/#example

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm leaving this out for now, assuming the ModInfo is initialized with the already extracted toml string, which should be easy to change down the line.

Comment on lines 89 to 93
try:
idx = self.enabled.index(mod_uid)
except ValueError:
# mod is not active
return
Copy link
Member

Choose a reason for hiding this comment

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

Better:

if mod_uid in self.enabled:
    idx = self.enabled.index(mod_uid)
else:
    # Exception

# mod is not active
return

def change_mod_priority(self, mod_uid, shift):
Copy link
Member

Choose a reason for hiding this comment

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

Why use relational shift instead of an absolute priority?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought this was more intuitive and less error prone. I wasn't sure how to handle priority clash wihtout unnecessary work. What if you set all mods priority to 3? Do they load randomly?

Copy link
Member

Choose a reason for hiding this comment

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

If you use the insert(index, element) method for lists, the other elements in the list would be automatically pushed back. This is the easiest way to do it.

I don't know what you mean with "What if you set all mods priority to 3?". Since you can only set the priority of one mod with a single function call, the insertion is sequential anyway. The element currently at position 3 would always get pushed back by one. The order would not be random.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I get what you mean now. I thought you meant something like this:

mods = {
    'mod1': 3,
    'mod2': 2
}

where the int is the priority, which indeed would be a mess. I didn't even think about list.insert().

Comment on lines 107 to 109
@staticmethod
def get_news_feed(self):
return 'Openage Feed'
Copy link
Member

Choose a reason for hiding this comment

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

Static methods do not need self and it looks like this function doesn't need it either.

Comment on lines 12 to 14
elif url is not None:
info_str = some_black_magic(url) # TODO: pseudocode
info = toml.loads(info_str)
Copy link
Member

Choose a reason for hiding this comment

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

Modpacks should never be loaded by URL alone. They should always come from a repo. If we do this, we open ourselves for all kinds of security attacks.

self.confl = self.info['conflicts']
self.req = self.info['requires']

def conflicts_with(self, other):
Copy link
Member

Choose a reason for hiding this comment

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

Use a function get_conflicts() for returning self.conflicts instead and solve the conflicts on a higher level in the launcher. Something like get_conflicts() is just easier to work with because you can also use it in other situations like displaying the conflicts or searching them for a specific mod UID.

return True
return False

def requires(self, other):
Copy link
Member

Choose a reason for hiding this comment

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

Similar situation to the above: get_requirements() should be implemented here and the requirements should resolved higher up.

@okias
Copy link
Contributor

okias commented Dec 30, 2020

Any news on it?

@brisvag
Copy link
Contributor Author

brisvag commented Jan 2, 2021

@okias, I haven't had the time to get on this. I doubt I will be able to pick it up any time soon, so feel free to take over if you want!

@TheJJ TheJJ added the zombie Closed pull requests or issues that might be useful later on. label Apr 3, 2023
@TheJJ TheJJ closed this Apr 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: launcher Related to the game launcher lang: python Done in Python code nice new thing ☺ A new feature that was not there before zombie Closed pull requests or issues that might be useful later on.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants