Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: [GS] Scriptable league tables #10001

Merged
merged 1 commit into from
Nov 26, 2022
Merged

Conversation

ldpl
Copy link
Contributor

@ldpl ldpl commented Sep 4, 2022

Default company league table came from the original game and has long lost its purpose since. As the base game has no goal it's kind of pointless to rank companies by some arbitrary rating that doesn't even facilitate good gameplay. So while adding goals to the game is the job of gamescripts, right now they have no way to change the league table mechanics having to misuse global goals for that purpose leading to player confusion.
So I designed a GS API for league tables based on the "league tables" of CityMania servers. As they're the most complex ones I've seen and cover all other use cases I know of, nicely showcasing:

  • There may be multiple league tables, not just one.
  • Score can be of some custom type like time.
  • Ordering can also be quite complex (companies that reached the goal are ordered by time, who didn't by population).
  • It can include companies that are not in the game anymore
  • May need to include custom information (e.g. list of players).

Screenshot from 2022-08-28 11-32-32

So I kind of got it all working but before I go finish all the boring technical details I'd like to see what opinions there are on this PR in general and some details in particular:

  • Naming of things. Currently, I called the table itself LeagueTable and its elements (rows) LeagueTableElement like in story pages. But it seems kinda long and generic, should I just call them rows? Also, I added Link, LinkType and LinkTargetID types that currently are kind of out of the scope of LeagueTables but are only used by them. The idea is to eventually use them in other scriptable UI like goals or story book but I don't want to do that in this PR. So should I name them like LeagueTableLink for now? I'm also looking for better names for all the other stuff.

  • Default league table and scoring. As I said, I don't see much point in keeping it in the game anymore. But in case someone feels strongly about it I kinda wanted to avoid removing it in this PR. So far I made it so that it shows default league menu options (league table, detailed rating, highscores) only if there are no custom league tables in the game. If there are it only shows the list of tables as GS author can implement highscores as a custom league table if they wish so and making custom rating details is just something I don't see possible without a full framework for custom UI.

  • Attributes. LeagueTable is quite simple and only has a title, header and footer.
    LeagueTableElement though has a lot:

    • table - id of the table it belongs to
    • rating - hidden value used only for ordering elements in the table.
    • score - string with a visible score to show.
    • text - main text of the element (i.e. company name).
    • link - Link(target_type, target_id) for the object to open when this element is clicked.

    So I separated the logical "score" used for sorting and the visual one because, as I already said, the score can be quite complex and I don't think the game should limit it to certain types. And yet it needs some value to use for sorting the elements.

  • GS API. Currently, I've added the GSLeagueTable class with the following methods:

    • New(title, header, footer) -> LeagueTableID - Add a new league table.
    • NewElement(table, rating, company, text, score, link_type, link_target) -> LeagueTableElementID - Add an element (row) to the table.
    • UpdateElementData(element, company, text, link_type, link_target).
    • UpdateElementScore(element, rating, score).
    • RemoveElement(element).
      I decided to split updating into two methods as IMO score is updated much more often and in different places than data is.

TODO:

  • Saveload
  • RTL
  • Comments

GS to test this (implements default league table in GS):
testleaguetable.zip
Screenshot from 2022-09-05 01-14-45

src/os/macosx/macos.mm Outdated Show resolved Hide resolved
@2TallTyler
Copy link
Member

I haven't reviewed the code or tested this, but I think this is an excellent path forward from the current scoring system. As broken as it is, I agree with your decision to keep it as a fallback when no custom scoring is present.

@npabisz
Copy link

npabisz commented Sep 9, 2022

As owner of GS server I agree that whole system of goals should be reworked, I mean its 2022. This game should put more attention into multiplayer.

@ldpl ldpl marked this pull request as ready for review September 20, 2022 21:41
@ldpl
Copy link
Contributor Author

ldpl commented Sep 20, 2022

Since no one commented on the api or naming I went on with what I had and finished the PR.

@James103
Copy link
Contributor

Reimplementing the CityMania goal tables in the new league table system as it currently stands (with no header or footer) would mean losing the ability to have a detailed description of what the goal is or scores mean.

For example, without a header or footer, the league table would only have a title of "Goal: 10,000 people", the elements, and nothing else. With a header, it can be used to show the full goal and what the scores mean. With a footer, it can be used to show something along the lines of "... and X more" if there are too many companies/entries to show at once.

Therefore, league tables should (in this implementation) allow for a header and/or footer.

@ldpl
Copy link
Contributor Author

ldpl commented Oct 27, 2022

I added header a footer. Doesn't look particularly nice but so is the rest of scriptable UI.
I've updated the test GS to showcase them.

P.S. Stupid github doesn't want to attach new screenshot for some reason :(

@LordAro LordAro added this to the 13.0 milestone Oct 27, 2022
@ldpl ldpl force-pushed the gs-league-table branch 4 times, most recently from 9809cb8 to ce2a814 Compare November 11, 2022 05:49
@2TallTyler 2TallTyler added size: large This Pull Request is large in size; review will take a while needs review: Script API Review requested from GS/AI expert labels Nov 11, 2022
Copy link
Member

@LordAro LordAro left a comment

Choose a reason for hiding this comment

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

No issues in principle. Mostly nitpicks

src/league_cmd.cpp Outdated Show resolved Hide resolved
src/league_cmd.cpp Outdated Show resolved Hide resolved
src/league_gui.cpp Outdated Show resolved Hide resolved

class PerformanceLeagueWindow : public Window {
private:
GUIList<const Company*> companies;
Copy link
Member

Choose a reason for hiding this comment

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

I know this was in the original code, but why does there need to be a copy of the companies list stored within the window?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I understand it, because it's inconvenient to keep list constantly updated, so to avoid glitches due to resorting it stores a copy and updates it episodically with full redraw of the window.

}


class ScriptLeagueWindow : public Window {
Copy link
Member

Choose a reason for hiding this comment

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

Could do with some doxy comments throughout

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some

};

typedef uint8 LeagueTableID; ///< ID of a league table
struct LeagueTable;
Copy link
Member

Choose a reason for hiding this comment

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

Feels odd to have these forward declared in a header. Is that normal compared to other types?

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 was copying story_type.hpp, seems pretty common.


/* static */ ScriptLeagueTable::LeagueTableID ScriptLeagueTable::New(Text *title, Text *header, Text *footer)
{
CCountedPtr<Text> title_counter(title);
Copy link
Member

Choose a reason for hiding this comment

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

this feels very std::shared_ptr... but that's something for the future.

src/toolbar_gui.cpp Outdated Show resolved Hide resolved
*
* @param index Graph to show.
* @return #CBF_NONE
* Enum for the League Toolbar's and Graph Toolbar's related buttons.
Copy link
Member

Choose a reason for hiding this comment

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

Not actually an enum. Could it be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Possibly, but CTMN_* in the same file isn't technically a enum either but is called so in the comment.

src/league_gui.cpp Outdated Show resolved Hide resolved
src/league_gui.cpp Outdated Show resolved Hide resolved
@ldpl ldpl force-pushed the gs-league-table branch 2 times, most recently from 3aee5f5 to add83a4 Compare November 14, 2022 05:16
@@ -2020,7 +2057,7 @@ struct MainToolbarWindow : Window {
case MTHK_STORY: ShowStoryBook(_local_company); break;
case MTHK_GOAL: ShowGoalsList(_local_company); break;
case MTHK_GRAPHS: ShowOperatingProfitGraph(); break;
case MTHK_LEAGUE: ShowCompanyLeagueTable(); break;
case MTHK_LEAGUE: ShowPerformanceLeagueTable(); break; // FIXME
Copy link
Member

Choose a reason for hiding this comment

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

?

@ldpl ldpl force-pushed the gs-league-table branch 3 times, most recently from 69ac2aa to 9097688 Compare November 21, 2022 13:50
LordAro
LordAro previously approved these changes Nov 22, 2022
Copy link
Member

@LordAro LordAro left a comment

Choose a reason for hiding this comment

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

LGTM. Would appreciate another set of eyes on it

src/league_cmd.h Outdated Show resolved Hide resolved
src/league_cmd.cpp Show resolved Hide resolved
src/league_cmd.cpp Outdated Show resolved Hide resolved
src/league_gui.cpp Outdated Show resolved Hide resolved
widest_width = std::max(widest_width, GetStringBoundingBox(STR_COMPANY_LEAGUE_COMPANY_NAME).width);
}

this->text_width = widest_width + WidgetDimensions::scaled.hsep_indent * 3; // Keep some extra spacing
Copy link
Member

Choose a reason for hiding this comment

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

Multiplying hsep_indent by a magic number to create some new non-standard amount of space isn't particularly pretty. Is there another constant that could be used, or created?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does happen in other places: https://github.com/OpenTTD/OpenTTD/blob/master/src/cheat_gui.cpp#L244
Idk any better way to do it.

Copy link
Member

Choose a reason for hiding this comment

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

Copy & paste of my changes too, it's basically "make some arbitrary space", which may or may not be equal in different places.

src/league_gui.cpp Outdated Show resolved Hide resolved
src/league_gui.cpp Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs review: Script API Review requested from GS/AI expert size: large This Pull Request is large in size; review will take a while
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants