Skip to content

Commit

Permalink
Add an in-game encyclopedia to Dune 2000.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mailaender authored and PunkPun committed Sep 11, 2022
1 parent d2a3659 commit 3be0e9e
Show file tree
Hide file tree
Showing 13 changed files with 528 additions and 0 deletions.
35 changes: 35 additions & 0 deletions OpenRA.Mods.Common/Traits/Encyclopedia.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion

using OpenRA.Traits;

namespace OpenRA.Mods.Common.Traits
{
public class EncyclopediaInfo : TraitInfo
{
[Desc("Explains the purpose in the in-game encyclopedia.")]
public readonly string Description = null;

[Desc("Number for ordering the list.")]
public readonly int Order;

[Desc("Group under this heading.")]
public readonly string Category;

public override object Create(ActorInitializer init) { return Encyclopedia.Instance; }
}

public class Encyclopedia
{
public static readonly Encyclopedia Instance = new Encyclopedia();
Encyclopedia() { }
}
}
182 changes: 182 additions & 0 deletions OpenRA.Mods.Common/Widgets/Logic/EncyclopediaLogic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Mods.Common.Traits.Render;
using OpenRA.Primitives;
using OpenRA.Widgets;

namespace OpenRA.Mods.Common.Widgets.Logic
{
public class EncyclopediaLogic : ChromeLogic
{
readonly World world;
readonly ModData modData;

readonly ScrollPanelWidget descriptionPanel;
readonly LabelWidget descriptionLabel;
readonly SpriteFont descriptionFont;

readonly ScrollPanelWidget actorList;
readonly ScrollItemWidget headerTemplate;
readonly ScrollItemWidget template;
readonly ActorPreviewWidget previewWidget;

ActorInfo selectedActor;
ScrollItemWidget firstItem;

[ObjectCreator.UseCtor]
public EncyclopediaLogic(Widget widget, World world, ModData modData, Action onExit)
{
this.world = world;
this.modData = modData;

actorList = widget.Get<ScrollPanelWidget>("ACTOR_LIST");

headerTemplate = widget.Get<ScrollItemWidget>("HEADER");
template = widget.Get<ScrollItemWidget>("TEMPLATE");

widget.Get("ACTOR_INFO").IsVisible = () => selectedActor != null;

previewWidget = widget.Get<ActorPreviewWidget>("ACTOR_PREVIEW");
previewWidget.IsVisible = () => selectedActor != null;

descriptionPanel = widget.Get<ScrollPanelWidget>("ACTOR_DESCRIPTION_PANEL");

descriptionLabel = descriptionPanel.Get<LabelWidget>("ACTOR_DESCRIPTION");
descriptionFont = Game.Renderer.Fonts[descriptionLabel.Font];

actorList.RemoveChildren();

var actorEncyclopediaPair = GetFilteredActorEncyclopediaPairs();
var categories = actorEncyclopediaPair.Select(a => a.Value.Category).Distinct().
OrderBy(string.IsNullOrWhiteSpace).ThenBy(s => s);
foreach (var category in categories)
{
CreateActorGroup(category, actorEncyclopediaPair
.Where(a => a.Value.Category == category)
.OrderBy(a => a.Value.Order)
.Select(a => a.Key));
}

widget.Get<ButtonWidget>("BACK_BUTTON").OnClick = () =>
{
Game.Disconnect();
Ui.CloseWindow();
onExit();
};
}

IEnumerable<KeyValuePair<ActorInfo, EncyclopediaInfo>> GetFilteredActorEncyclopediaPairs()
{
var actors = new List<KeyValuePair<ActorInfo, EncyclopediaInfo>>();
foreach (var actor in modData.DefaultRules.Actors.Values)
{
if (!actor.TraitInfos<IRenderActorPreviewSpritesInfo>().Any())
continue;

var statistics = actor.TraitInfoOrDefault<UpdatesPlayerStatisticsInfo>();
if (statistics != null && !string.IsNullOrEmpty(statistics.OverrideActor))
continue;

var encyclopedia = actor.TraitInfoOrDefault<EncyclopediaInfo>();
if (encyclopedia == null)
continue;

actors.Add(new KeyValuePair<ActorInfo, EncyclopediaInfo>(actor, encyclopedia));
}

return actors;
}

void CreateActorGroup(string title, IEnumerable<ActorInfo> actors)
{
var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => { });
header.Get<LabelWidget>("LABEL").GetText = () => title;
actorList.AddChild(header);

foreach (var actor in actors)
{
var item = ScrollItemWidget.Setup(template,
() => selectedActor != null && selectedActor.Name == actor.Name,
() => SelectActor(actor));

var label = item.Get<LabelWithTooltipWidget>("TITLE");
var name = actor.TraitInfoOrDefault<TooltipInfo>()?.Name;
if (!string.IsNullOrEmpty(name))
WidgetUtils.TruncateLabelToTooltip(label, name);

if (firstItem == null)
{
firstItem = item;
SelectActor(actor);
}

actorList.AddChild(item);
}
}

void SelectActor(ActorInfo actor)
{
selectedActor = actor;

var typeDictionary = new TypeDictionary()
{
new OwnerInit(world.WorldActor.Owner),
new FactionInit(world.WorldActor.Owner.PlayerReference.Faction)
};

foreach (var actorPreviewInit in actor.TraitInfos<IActorPreviewInitInfo>())
foreach (var inits in actorPreviewInit.ActorPreviewInits(actor, ActorPreviewType.ColorPicker))
typeDictionary.Add(inits);

previewWidget.SetPreview(actor, typeDictionary);

var text = "";

var buildable = actor.TraitInfoOrDefault<BuildableInfo>();
if (buildable != null)
{
var prerequisites = buildable.Prerequisites.Select(a => ActorName(modData.DefaultRules, a))
.Where(s => !s.StartsWith("~", StringComparison.Ordinal) && !s.StartsWith("!", StringComparison.Ordinal));
if (prerequisites.Any())
text += $"Requires {prerequisites.JoinWith(", ")}\n\n";
}

var info = actor.TraitInfoOrDefault<EncyclopediaInfo>();
if (info != null && !string.IsNullOrEmpty(info.Description))
text += WidgetUtils.WrapText(info.Description.Replace("\\n", "\n") + "\n\n", descriptionLabel.Bounds.Width, descriptionFont);

var height = descriptionFont.Measure(text).Y;
descriptionLabel.Text = text;
descriptionLabel.Bounds.Height = height;
descriptionPanel.Layout.AdjustChildren();

descriptionPanel.ScrollToTop();
}

static string ActorName(Ruleset rules, string name)
{
if (rules.Actors.TryGetValue(name.ToLowerInvariant(), out var actor))
{
var actorTooltip = actor.TraitInfos<TooltipInfo>().FirstOrDefault(info => info.EnabledByDefault);
if (actorTooltip != null)
return actorTooltip.Name;
}

return name;
}
}
}
13 changes: 13 additions & 0 deletions OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ public MainMenuLogic(Widget widget, World world, ModData modData)
loadButton.IsDisabled = () => !GameSaveBrowserLogic.IsLoadPanelEnabled(modData.Manifest);
loadButton.OnClick = OpenGameSaveBrowserPanel;

var encyclopediaButton = singleplayerMenu.GetOrNull<ButtonWidget>("ENCYCLOPEDIA_BUTTON");
if (encyclopediaButton != null)
encyclopediaButton.OnClick = OpenEncyclopediaPanel;

singleplayerMenu.Get<ButtonWidget>("BACK_BUTTON").OnClick = () => SwitchMenu(MenuType.Main);

// Extras menu
Expand Down Expand Up @@ -466,6 +470,15 @@ void OpenMissionBrowserPanel(string map)
});
}

void OpenEncyclopediaPanel()
{
SwitchMenu(MenuType.None);
Game.OpenWindow("ENCYCLOPEDIA_PANEL", new WidgetArgs
{
{ "onExit", () => SwitchMenu(MenuType.Singleplayer) }
});
}

void OpenSkirmishLobbyPanel()
{
SwitchMenu(MenuType.None);
Expand Down
85 changes: 85 additions & 0 deletions mods/d2k/chrome/encyclopedia.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
Background@ENCYCLOPEDIA_PANEL:
Logic: EncyclopediaLogic
X: (WINDOW_RIGHT - WIDTH) / 2
Y: (WINDOW_BOTTOM - HEIGHT) / 2
Width: 900
Height: 600
Children:
Container@ENCYCLOPEDIA_CONTENT:
Width: PARENT_RIGHT - 40
Height: PARENT_BOTTOM - 80
X: 20
Y: 20
Children:
Label@ENCYCLOPEDIA_TITLE:
Width: PARENT_RIGHT
Height: 25
Text: Mentat
Align: Center
Font: Bold
ScrollPanel@ACTOR_LIST:
Y: 30
Width: 190
Height: PARENT_BOTTOM - 25
Children:
ScrollItem@HEADER:
BaseName: scrollheader
Width: PARENT_RIGHT - 27
Height: 13
X: 2
Visible: false
Children:
Label@LABEL:
Font: TinyBold
Width: PARENT_RIGHT
Height: 13
Align: Center
ScrollItem@TEMPLATE:
Width: PARENT_RIGHT - 27
Height: 25
X: 2
EnableChildMouseOver: True
Children:
LabelWithTooltip@TITLE:
X: 10
Width: PARENT_RIGHT - 20
Height: 25
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Container@ACTOR_INFO:
X: PARENT_RIGHT - WIDTH
Y: 30
Width: PARENT_RIGHT - 190 - 10
Height: PARENT_BOTTOM - 25
Children:
Background@ACTOR_BG:
Width: 150
Height: 150
Background: dialog3
Children:
ActorPreview@ACTOR_PREVIEW:
X: 1
Y: 1
Width: PARENT_RIGHT - 2
Height: PARENT_BOTTOM - 2
ScrollPanel@ACTOR_DESCRIPTION_PANEL:
X: 150 + 10
Width: PARENT_RIGHT - 150 - 10
Height: 150
TopBottomSpacing: 8
Children:
Label@ACTOR_DESCRIPTION:
X: 8
Y: 8
Width: PARENT_RIGHT - 32
VAlign: Top
Font: Regular
Button@BACK_BUTTON:
X: PARENT_RIGHT - 180
Y: PARENT_BOTTOM - 45
Width: 160
Height: 25
Text: Back
Font: Bold
Key: escape
TooltipContainer@TOOLTIP_CONTAINER:
7 changes: 7 additions & 0 deletions mods/d2k/chrome/mainmenu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ Container@MAINMENU:
Height: 30
Text: Load
Font: Bold
Button@ENCYCLOPEDIA_BUTTON:
X: PARENT_RIGHT / 2 - WIDTH / 2
Y: 180
Width: 140
Height: 30
Text: Mentat
Font: Bold
Button@BACK_BUTTON:
X: PARENT_RIGHT / 2 - WIDTH / 2
Key: escape
Expand Down
1 change: 1 addition & 0 deletions mods/d2k/mod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ ChromeLayout:
common|chrome/gamesave-browser.yaml
common|chrome/gamesave-loading.yaml
common|chrome/text-notifications.yaml
d2k|chrome/encyclopedia.yaml

Translations:
common|languages/en.ftl
Expand Down
10 changes: 10 additions & 0 deletions mods/d2k/rules/aircraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ carryall:
BeforeLoadDelay: 10
BeforeUnloadDelay: 15
LocalOffset: 0, 0, -128
Encyclopedia:
Description: Carryalls will automatically transport Harvesters back and forth from the Spice Fields to the Refineries. They will also pick up units and deliver them to the Repair Pad, when ordered to.\n\nThe Carryall is a lightly armored transport aircraft. They are vulnerable to missiles and can only be hit by anti-aircraft weapons.
Order: 230
Category: Units
Aircraft:
MinAirborneAltitude: 400
RevealsShroud@lifting_low:
Expand Down Expand Up @@ -95,6 +99,8 @@ frigate:

ornithopter:
Inherits: ^Plane
Buildable:
Prerequisites: upgrade.hightech
AttackBomber:
FacingTolerance: 8
Armament:
Expand All @@ -103,6 +109,10 @@ ornithopter:
HP: 9000
Armor:
Type: light
Encyclopedia:
Description: The fastest aircraft on Dune, the Ornithopther is lightly armored and capable of dropping 500lb bombs. This unit is most effective against infantry and lightly armored targets, but also damages armored targets.
Order: 240
Category: Units
Aircraft:
Speed: 224
TurnSpeed: 8
Expand Down

0 comments on commit 3be0e9e

Please sign in to comment.