Compare commits
24 Commits
devtest-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a2c6a383 | ||
|
|
fa6b7d4e73 | ||
|
|
4b2fea9944 | ||
|
|
5e3f25484e | ||
|
|
9baaf1184a | ||
|
|
426494d4eb | ||
|
|
216e64a870 | ||
|
|
e8f8f7c730 | ||
|
|
93d10f658e | ||
|
|
b5783428be | ||
|
|
8ae5561e73 | ||
|
|
64606d4ede | ||
|
|
7867eb99c6 | ||
|
|
7bc2768dc4 | ||
|
|
1f63294503 | ||
|
|
d99991d317 | ||
|
|
d7b2727d16 | ||
|
|
44f35729e1 | ||
|
|
7c9dcebbd8 | ||
|
|
fc94d93fc8 | ||
|
|
810ba2c9ae | ||
|
|
77190ed1ac | ||
|
|
b543f738db | ||
|
|
40a9b47b44 |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -12,10 +12,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
@@ -53,10 +53,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
|
||||
14
.github/workflows/documentation.yml
vendored
14
.github/workflows/documentation.yml
vendored
@@ -15,12 +15,12 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
@@ -66,12 +66,12 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Clone docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
|
||||
- name: Clone docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
|
||||
14
.github/workflows/packaging.yml
vendored
14
.github/workflows/packaging.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
@@ -37,10 +37,10 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -66,10 +66,10 @@ jobs:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
@@ -101,10 +101,10 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#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 OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class AsyncAction : IEffect
|
||||
{
|
||||
readonly Action a;
|
||||
readonly IAsyncResult ar;
|
||||
|
||||
public AsyncAction(IAsyncResult ar, Action a)
|
||||
{
|
||||
this.a = a;
|
||||
this.ar = ar;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (ar.IsCompleted)
|
||||
{
|
||||
world.AddFrameEndTask(w => { w.Remove(this); a(); });
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer r) { yield break; }
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,9 @@ namespace OpenRA.Network
|
||||
bool generateSyncReport = false;
|
||||
int sentOrdersFrame = 0;
|
||||
float tickScale = 1f;
|
||||
bool outOfSync = false;
|
||||
|
||||
/// <Remarks> Should only be set in <see cref="OutOfSync"/></Remarks>
|
||||
public bool IsOutOfSync { get; private set; } = false;
|
||||
|
||||
public struct ClientOrder
|
||||
{
|
||||
@@ -72,12 +74,12 @@ namespace OpenRA.Network
|
||||
|
||||
void OutOfSync(int frame)
|
||||
{
|
||||
if (outOfSync)
|
||||
if (IsOutOfSync)
|
||||
return;
|
||||
|
||||
syncReport.DumpSyncReport(frame);
|
||||
World.OutOfSync();
|
||||
outOfSync = true;
|
||||
IsOutOfSync = true;
|
||||
|
||||
TextNotificationsManager.AddSystemLine($"Out of sync in frame {frame}.\nCompare syncreport.log with other players.");
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ namespace OpenRA.Network
|
||||
var pause = order.TargetString == "Pause";
|
||||
|
||||
// Prevent injected unpause orders from restarting a finished game
|
||||
if (orderManager.World.PauseStateLocked && !pause)
|
||||
if (orderManager.World.IsGameOver && !pause)
|
||||
break;
|
||||
|
||||
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
||||
|
||||
@@ -223,12 +223,7 @@ namespace OpenRA.Scripting
|
||||
|
||||
FatalErrorOccurred = true;
|
||||
|
||||
World.AddFrameEndTask(w =>
|
||||
{
|
||||
World.EndGame();
|
||||
World.SetPauseState(true);
|
||||
World.PauseStateLocked = true;
|
||||
});
|
||||
World.AddFrameEndTask(w => World.EndGame());
|
||||
}
|
||||
|
||||
public void RegisterMapActor(string name, Actor a)
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace OpenRA.Server
|
||||
frame = BitConverter.ToInt32(bytes, 4);
|
||||
state = ReceiveState.Data;
|
||||
|
||||
if (expectLength < 0 || expectLength > MaxOrderLength)
|
||||
if (expectLength < 0 || (server.Type != ServerType.Local && expectLength > MaxOrderLength))
|
||||
{
|
||||
Log.Write("server", $"Closing socket connection to {EndPoint} because of excessive order length: {expectLength}");
|
||||
return;
|
||||
|
||||
@@ -68,15 +68,19 @@ namespace OpenRA
|
||||
public Player LocalPlayer { get; private set; }
|
||||
|
||||
public event Action GameOver = () => { };
|
||||
|
||||
/// <Remarks> Should only be set in <see cref="EndGame"/></Remarks>
|
||||
public bool IsGameOver { get; private set; }
|
||||
public void EndGame()
|
||||
{
|
||||
if (!IsGameOver)
|
||||
{
|
||||
SetPauseState(true);
|
||||
IsGameOver = true;
|
||||
|
||||
foreach (var t in WorldActor.TraitsImplementing<IGameOver>())
|
||||
t.GameOver(this);
|
||||
|
||||
gameInfo.FinalGameTick = WorldTick;
|
||||
GameOver();
|
||||
}
|
||||
@@ -381,7 +385,6 @@ namespace OpenRA
|
||||
|
||||
public bool Paused { get; internal set; }
|
||||
public bool PredictedPaused { get; internal set; }
|
||||
public bool PauseStateLocked { get; set; }
|
||||
|
||||
public int WorldTick { get; private set; }
|
||||
|
||||
@@ -393,7 +396,7 @@ namespace OpenRA
|
||||
|
||||
public void SetPauseState(bool paused)
|
||||
{
|
||||
if (PauseStateLocked)
|
||||
if (IsGameOver)
|
||||
return;
|
||||
|
||||
IssueOrder(Order.FromTargetString("PauseGame", paused ? "Pause" : "UnPause", false));
|
||||
@@ -615,8 +618,9 @@ namespace OpenRA
|
||||
public void OutOfSync()
|
||||
{
|
||||
EndGame();
|
||||
SetPauseState(true);
|
||||
PauseStateLocked = true;
|
||||
|
||||
// In the event the replay goes out of sync, it becomes no longer usable. For polish we permanently pause the world.
|
||||
ReplayTimestep = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1163,9 +1163,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
}
|
||||
|
||||
if (maybeAbstractCell == null)
|
||||
throw new Exception(
|
||||
"The abstract path should never be searched for an unreachable point. " +
|
||||
$"Cell {cell} failed lookup for an abstract cell.");
|
||||
return PathGraph.PathCostForInvalidPath;
|
||||
}
|
||||
|
||||
var abstractCell = maybeAbstractCell.Value;
|
||||
@@ -1176,9 +1174,8 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
{
|
||||
abstractSearch.TargetPredicate = c => c == abstractCell;
|
||||
if (!abstractSearch.ExpandToTarget())
|
||||
throw new Exception(
|
||||
"The abstract path should never be searched for an unreachable point. " +
|
||||
$"Abstract cell {abstractCell} failed to route to abstract cell.");
|
||||
return PathGraph.PathCostForInvalidPath;
|
||||
|
||||
info = graph[abstractCell];
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,12 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Eluant;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Video;
|
||||
|
||||
namespace OpenRA.Mods.Common.Scripting
|
||||
{
|
||||
@@ -38,13 +35,13 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
[Desc("Play an announcer voice listed in notifications.yaml")]
|
||||
public void PlaySpeechNotification(Player player, string notification)
|
||||
{
|
||||
Game.Sound.PlayNotification(world.Map.Rules, player, "Speech", notification, player != null ? player.Faction.InternalName : null);
|
||||
Game.Sound.PlayNotification(world.Map.Rules, player, "Speech", notification, player?.Faction.InternalName);
|
||||
}
|
||||
|
||||
[Desc("Play a sound listed in notifications.yaml")]
|
||||
public void PlaySoundNotification(Player player, string notification)
|
||||
{
|
||||
Game.Sound.PlayNotification(world.Map.Rules, player, "Sounds", notification, player != null ? player.Faction.InternalName : null);
|
||||
Game.Sound.PlayNotification(world.Map.Rules, player, "Sounds", notification, player?.Faction.InternalName);
|
||||
}
|
||||
|
||||
[Desc("Play a sound file")]
|
||||
@@ -55,34 +52,17 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
}
|
||||
|
||||
[Desc("Play track defined in music.yaml or map.yaml, or keep track empty for playing a random song.")]
|
||||
public void PlayMusic(string track = null, LuaFunction func = null)
|
||||
public void PlayMusic(string track = null, LuaFunction onPlayComplete = null)
|
||||
{
|
||||
if (!playlist.IsMusicAvailable)
|
||||
return;
|
||||
|
||||
var musicInfo = !string.IsNullOrEmpty(track) ? GetMusicTrack(track)
|
||||
var musicInfo = !string.IsNullOrEmpty(track)
|
||||
? GetMusicTrack(track)
|
||||
: playlist.GetNextSong();
|
||||
|
||||
if (func != null)
|
||||
{
|
||||
var f = (LuaFunction)func.CopyReference();
|
||||
Action onComplete = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (f)
|
||||
f.Call().Dispose();
|
||||
}
|
||||
catch (LuaException e)
|
||||
{
|
||||
Context.FatalError(e.Message);
|
||||
}
|
||||
};
|
||||
|
||||
playlist.Play(musicInfo, onComplete);
|
||||
}
|
||||
else
|
||||
playlist.Play(musicInfo);
|
||||
var onComplete = WrapOnPlayComplete(onPlayComplete);
|
||||
playlist.Play(musicInfo, onComplete);
|
||||
}
|
||||
|
||||
[Desc("Play track defined in music.yaml or map.yaml as background music." +
|
||||
@@ -113,78 +93,18 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
playlist.Stop();
|
||||
}
|
||||
|
||||
[Desc("Play a VQA video fullscreen. File name has to include the file extension.")]
|
||||
public void PlayMovieFullscreen(string movie, LuaFunction func = null)
|
||||
[Desc("Play a video fullscreen. File name has to include the file extension.")]
|
||||
public void PlayMovieFullscreen(string videoFileName, LuaFunction onPlayComplete = null)
|
||||
{
|
||||
Action onCompleteFullscreen;
|
||||
if (func != null)
|
||||
{
|
||||
var f = (LuaFunction)func.CopyReference();
|
||||
onCompleteFullscreen = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (f)
|
||||
f.Call().Dispose();
|
||||
}
|
||||
catch (LuaException e)
|
||||
{
|
||||
Context.FatalError(e.Message);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
onCompleteFullscreen = () => { };
|
||||
|
||||
Media.PlayFMVFullscreen(world, movie, onCompleteFullscreen);
|
||||
var onComplete = WrapOnPlayComplete(onPlayComplete);
|
||||
Media.PlayFMVFullscreen(world, videoFileName, onComplete);
|
||||
}
|
||||
|
||||
[Desc("Play a VQA video in the radar window. File name has to include the file extension. " +
|
||||
"Returns true on success, if the movie wasn't found the function returns false and the callback is executed.")]
|
||||
public bool PlayMovieInRadar(string movie, LuaFunction playComplete = null)
|
||||
[Desc("Play a video in the radar window. File name has to include the file extension.")]
|
||||
public void PlayMovieInRadar(string videoFileName, LuaFunction onPlayComplete = null)
|
||||
{
|
||||
Action onCompleteRadar;
|
||||
if (playComplete != null)
|
||||
{
|
||||
var f = (LuaFunction)playComplete.CopyReference();
|
||||
onCompleteRadar = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (f)
|
||||
f.Call().Dispose();
|
||||
}
|
||||
catch (LuaException e)
|
||||
{
|
||||
Context.FatalError(e.Message);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
onCompleteRadar = () => { };
|
||||
|
||||
Stream s;
|
||||
try
|
||||
{
|
||||
s = world.Map.Open(movie);
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
Log.Write("lua", $"Couldn't play movie {e.FileName}! File doesn't exist.");
|
||||
onCompleteRadar();
|
||||
return false;
|
||||
}
|
||||
|
||||
var l = new AsyncLoader(Media.LoadVideo);
|
||||
var ar = l.BeginInvoke(s, null, null);
|
||||
Action onLoadComplete = () =>
|
||||
{
|
||||
Media.StopFMVInRadar();
|
||||
world.AddFrameEndTask(_ => Media.PlayFMVInRadar(l.EndInvoke(ar), onCompleteRadar));
|
||||
};
|
||||
|
||||
world.AddFrameEndTask(w => w.Add(new AsyncAction(ar, onLoadComplete)));
|
||||
return true;
|
||||
var onComplete = WrapOnPlayComplete(onPlayComplete);
|
||||
Media.PlayFMVInRadar(videoFileName, onComplete);
|
||||
}
|
||||
|
||||
[Desc("Display a text message to the player.")]
|
||||
@@ -193,7 +113,7 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
var c = color.HasValue ? color.Value : Color.White;
|
||||
var c = color ?? Color.White;
|
||||
TextNotificationsManager.AddMissionLine(prefix, text, c);
|
||||
}
|
||||
|
||||
@@ -224,10 +144,33 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
if (string.IsNullOrEmpty(text) || !world.Map.Contains(world.Map.CellContaining(position)))
|
||||
return;
|
||||
|
||||
var c = color.HasValue ? color.Value : Color.White;
|
||||
var c = color ?? Color.White;
|
||||
world.AddFrameEndTask(w => w.Add(new FloatingText(position, c, text, duration)));
|
||||
}
|
||||
|
||||
public delegate IVideo AsyncLoader(Stream s);
|
||||
Action WrapOnPlayComplete(LuaFunction onPlayComplete)
|
||||
{
|
||||
Action onComplete;
|
||||
if (onPlayComplete != null)
|
||||
{
|
||||
var f = (LuaFunction)onPlayComplete.CopyReference();
|
||||
onComplete = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (f)
|
||||
f.Call().Dispose();
|
||||
}
|
||||
catch (LuaException e)
|
||||
{
|
||||
Context.FatalError(e.Message);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
onComplete = () => { };
|
||||
|
||||
return onComplete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,21 +12,20 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using OpenRA.Mods.Common.Widgets;
|
||||
using OpenRA.Video;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Scripting
|
||||
{
|
||||
public static class Media
|
||||
{
|
||||
public static void PlayFMVFullscreen(World w, string movie, Action onComplete)
|
||||
public static void PlayFMVFullscreen(World w, string videoFileName, Action onComplete)
|
||||
{
|
||||
var playerRoot = Game.OpenWindow(w, "FMVPLAYER");
|
||||
var player = playerRoot.Get<VideoPlayerWidget>("PLAYER");
|
||||
|
||||
try
|
||||
{
|
||||
player.Load(movie);
|
||||
player.LoadAndPlay(videoFileName);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
@@ -60,27 +59,10 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
});
|
||||
}
|
||||
|
||||
public static void PlayFMVInRadar(IVideo movie, Action onComplete)
|
||||
public static void PlayFMVInRadar(string videoFileName, Action onComplete)
|
||||
{
|
||||
var player = Ui.Root.Get<VideoPlayerWidget>("PLAYER");
|
||||
player.Open(movie);
|
||||
|
||||
player.PlayThen(() =>
|
||||
{
|
||||
onComplete();
|
||||
player.CloseVideo();
|
||||
});
|
||||
}
|
||||
|
||||
public static void StopFMVInRadar()
|
||||
{
|
||||
var player = Ui.Root.Get<VideoPlayerWidget>("PLAYER");
|
||||
player.Stop();
|
||||
}
|
||||
|
||||
public static IVideo LoadVideo(Stream s)
|
||||
{
|
||||
return VideoLoader.GetVideo(s, true, Game.ModData.VideoLoaders);
|
||||
player.LoadAndPlayAsync(videoFileName, onComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,12 +165,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
Game.RunAfterDelay(Info.GameOverDelay, () =>
|
||||
{
|
||||
if (!Game.IsCurrentWorld(player.World))
|
||||
return;
|
||||
|
||||
player.World.EndGame();
|
||||
player.World.SetPauseState(true);
|
||||
player.World.PauseStateLocked = true;
|
||||
if (Game.IsCurrentWorld(player.World))
|
||||
player.World.EndGame();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var spriteWidget = panel.GetOrNull<SpriteWidget>("SPRITE");
|
||||
if (spriteWidget != null)
|
||||
{
|
||||
spriteWidget.GetSprite = () => currentSprites?[currentFrame];
|
||||
spriteWidget.GetSprite = () => currentSprites?.Length > 0 ? currentSprites[currentFrame] : null;
|
||||
currentPalette = spriteWidget.Palette;
|
||||
spriteScale = spriteWidget.Scale;
|
||||
spriteWidget.GetPalette = () => currentPalette;
|
||||
@@ -226,15 +226,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var frameText = panel.GetOrNull<LabelWidget>("FRAME_COUNT");
|
||||
if (frameText != null)
|
||||
{
|
||||
var soundLength = new CachedTransform<int, string>(p =>
|
||||
modData.Translation.GetString(LengthInSeconds, Translation.Arguments("length", p)));
|
||||
var soundLength = new CachedTransform<double, string>(p =>
|
||||
modData.Translation.GetString(LengthInSeconds, Translation.Arguments("length", Math.Round(p, 3))));
|
||||
|
||||
frameText.GetText = () =>
|
||||
{
|
||||
if (isVideoLoaded)
|
||||
return $"{player.Video.CurrentFrameIndex + 1} / {player.Video.FrameCount}";
|
||||
|
||||
if (currentSoundFormat != null)
|
||||
return soundLength.Update((int)currentSoundFormat.LengthInSeconds);
|
||||
return soundLength.Update(currentSoundFormat.LengthInSeconds);
|
||||
|
||||
return $"{currentFrame} / {currentSprites.Length - 1}";
|
||||
};
|
||||
@@ -378,10 +379,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
}
|
||||
|
||||
var assetBrowserModData = modData.Manifest.Get<AssetBrowser>();
|
||||
allowedSpriteExtensions = assetBrowserModData.SpriteExtensions;
|
||||
allowedModelExtensions = assetBrowserModData.ModelExtensions;
|
||||
allowedAudioExtensions = assetBrowserModData.AudioExtensions;
|
||||
allowedVideoExtensions = assetBrowserModData.VideoExtensions;
|
||||
allowedSpriteExtensions = assetBrowserModData.SpriteExtensions.Select(x => x.ToLowerInvariant()).ToArray();
|
||||
allowedModelExtensions = assetBrowserModData.ModelExtensions.Select(x => x.ToLowerInvariant()).ToArray();
|
||||
allowedAudioExtensions = assetBrowserModData.AudioExtensions.Select(x => x.ToLowerInvariant()).ToArray();
|
||||
allowedVideoExtensions = assetBrowserModData.VideoExtensions.Select(x => x.ToLowerInvariant()).ToArray();
|
||||
allowedExtensions = allowedSpriteExtensions
|
||||
.Union(allowedModelExtensions)
|
||||
.Union(allowedAudioExtensions)
|
||||
@@ -507,7 +508,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
currentSprites = world.Map.Rules.Sequences.SpriteCache[prefix + filename];
|
||||
currentFrame = 0;
|
||||
|
||||
if (frameSlider != null)
|
||||
if (frameSlider != null && currentSprites?.Length > 0)
|
||||
{
|
||||
frameSlider.MaximumValue = (float)currentSprites.Length - 1;
|
||||
frameSlider.Ticks = currentSprites.Length;
|
||||
@@ -550,7 +551,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
if (video != null)
|
||||
{
|
||||
player = panel.Get<VideoPlayerWidget>("PLAYER");
|
||||
player.Load(prefix + filename);
|
||||
player.LoadAndPlay(prefix + filename);
|
||||
player.DrawOverlay = false;
|
||||
isVideoLoaded = true;
|
||||
|
||||
|
||||
@@ -45,18 +45,21 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var speed = PlaybackSpeed.Regular;
|
||||
var originalTimestep = world.Timestep;
|
||||
|
||||
// In the event the replay goes out of sync, it becomes no longer usable. For polish we permanently pause the world.
|
||||
Func<bool> isWidgetDisabled = () => orderManager.IsOutOfSync || orderManager.NetFrameNumber >= replayNetTicks;
|
||||
|
||||
var pauseButton = widget.Get<ButtonWidget>("BUTTON_PAUSE");
|
||||
pauseButton.IsVisible = () => world.ReplayTimestep != 0 && orderManager.NetFrameNumber < replayNetTicks;
|
||||
pauseButton.IsVisible = () => world.ReplayTimestep != 0 && !isWidgetDisabled();
|
||||
pauseButton.OnClick = () => world.ReplayTimestep = 0;
|
||||
|
||||
var playButton = widget.Get<ButtonWidget>("BUTTON_PLAY");
|
||||
playButton.IsVisible = () => world.ReplayTimestep == 0 || orderManager.NetFrameNumber >= replayNetTicks;
|
||||
playButton.IsVisible = () => world.ReplayTimestep == 0 || isWidgetDisabled();
|
||||
playButton.OnClick = () => world.ReplayTimestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]);
|
||||
playButton.IsDisabled = () => orderManager.NetFrameNumber >= replayNetTicks;
|
||||
playButton.IsDisabled = isWidgetDisabled;
|
||||
|
||||
var slowButton = widget.Get<ButtonWidget>("BUTTON_SLOW");
|
||||
slowButton.IsHighlighted = () => speed == PlaybackSpeed.Slow;
|
||||
slowButton.IsDisabled = () => orderManager.NetFrameNumber >= replayNetTicks;
|
||||
slowButton.IsDisabled = isWidgetDisabled;
|
||||
slowButton.OnClick = () =>
|
||||
{
|
||||
speed = PlaybackSpeed.Slow;
|
||||
@@ -66,7 +69,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
var normalSpeedButton = widget.Get<ButtonWidget>("BUTTON_REGULAR");
|
||||
normalSpeedButton.IsHighlighted = () => speed == PlaybackSpeed.Regular;
|
||||
normalSpeedButton.IsDisabled = () => orderManager.NetFrameNumber >= replayNetTicks;
|
||||
normalSpeedButton.IsDisabled = isWidgetDisabled;
|
||||
normalSpeedButton.OnClick = () =>
|
||||
{
|
||||
speed = PlaybackSpeed.Regular;
|
||||
@@ -76,7 +79,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
var fastButton = widget.Get<ButtonWidget>("BUTTON_FAST");
|
||||
fastButton.IsHighlighted = () => speed == PlaybackSpeed.Fast;
|
||||
fastButton.IsDisabled = () => orderManager.NetFrameNumber >= replayNetTicks;
|
||||
fastButton.IsDisabled = isWidgetDisabled;
|
||||
fastButton.OnClick = () =>
|
||||
{
|
||||
speed = PlaybackSpeed.Fast;
|
||||
@@ -86,7 +89,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
var maximumButton = widget.Get<ButtonWidget>("BUTTON_MAXIMUM");
|
||||
maximumButton.IsHighlighted = () => speed == PlaybackSpeed.Maximum;
|
||||
maximumButton.IsDisabled = () => orderManager.NetFrameNumber >= replayNetTicks;
|
||||
maximumButton.IsDisabled = isWidgetDisabled;
|
||||
maximumButton.OnClick = () =>
|
||||
{
|
||||
speed = PlaybackSpeed.Maximum;
|
||||
|
||||
@@ -370,7 +370,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
StopVideo(player);
|
||||
|
||||
playingVideo = pv;
|
||||
player.Load(video);
|
||||
player.LoadAndPlay(video);
|
||||
|
||||
if (player.Video == null)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Video;
|
||||
@@ -41,19 +43,72 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
Action onComplete;
|
||||
|
||||
public void Load(string filename)
|
||||
/// <summary>
|
||||
/// Tries to load a video from the specified file and play it. Does nothing if the file name matches the already loaded video.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file, including the extension.</param>
|
||||
public void LoadAndPlay(string filename)
|
||||
{
|
||||
if (filename == cachedVideoFileName)
|
||||
return;
|
||||
|
||||
cachedVideoFileName = filename;
|
||||
var stream = Game.ModData.DefaultFileSystem.Open(filename);
|
||||
var video = VideoLoader.GetVideo(stream, true, Game.ModData.VideoLoaders);
|
||||
Open(video);
|
||||
|
||||
cachedVideoFileName = filename;
|
||||
Play(video);
|
||||
}
|
||||
|
||||
public void Open(IVideo video)
|
||||
/// <summary>
|
||||
/// Tries to load a video from the specified file and play it. Does nothing if the file name matches the already loaded video.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file, including the extension.</param>
|
||||
/// <param name="after">Action to perform after the video ends.</param>
|
||||
public void LoadAndPlayAsync(string filename, Action after)
|
||||
{
|
||||
if (filename == cachedVideoFileName)
|
||||
return;
|
||||
|
||||
cachedVideoFileName = filename;
|
||||
|
||||
if (!stopped)
|
||||
CloseVideo();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = Game.ModData.DefaultFileSystem.Open(filename);
|
||||
var video = VideoLoader.GetVideo(stream, true, Game.ModData.VideoLoaders);
|
||||
|
||||
// Safeguard against race conditions with two videos being loaded at the same time - prefer to play only the last one.
|
||||
if (filename != cachedVideoFileName)
|
||||
{
|
||||
after();
|
||||
return;
|
||||
}
|
||||
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
Play(video);
|
||||
PlayThen(() =>
|
||||
{
|
||||
after();
|
||||
CloseVideo();
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
after();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays the given <see cref="IVideo"/>.
|
||||
/// </summary>
|
||||
/// <param name="video">An <see cref="IVideo"/> instance.</param>
|
||||
public void Play(IVideo video)
|
||||
{
|
||||
this.video = video;
|
||||
|
||||
@@ -144,6 +199,13 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
var videoScale = Math.Min((float)RenderBounds.Width / video.Width, RenderBounds.Height / (video.Height * AspectRatio));
|
||||
var halfRowHeight = (int)(videoScale * scale / 2 + 0.5f);
|
||||
|
||||
// If the video is "too tightly packed" into the player and there is no room for drawing an overlay disable it.
|
||||
if (halfRowHeight == 0)
|
||||
{
|
||||
DrawOverlay = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// The overlay can be minimally stored in a 1px column which is stretched to cover the full screen
|
||||
var overlayHeight = (int)(RenderBounds.Height * scale / halfRowHeight);
|
||||
var overlaySheetSize = new Size(1, Exts.NextPowerOf2(overlayHeight));
|
||||
@@ -224,7 +286,14 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
Game.Sound.StopVideo();
|
||||
video.Reset();
|
||||
videoSprite.Sheet.GetTexture().SetData(video.CurrentFrameData, textureSize, textureSize);
|
||||
Game.RunAfterTick(onComplete);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
if (onComplete != null)
|
||||
{
|
||||
onComplete();
|
||||
onComplete = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void CloseVideo()
|
||||
|
||||
@@ -66,9 +66,9 @@ Sounds:
|
||||
ChatLine: scold1
|
||||
InterruptType: Interrupt
|
||||
ClickDisabledSound: scold2
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
ClickSound: button
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
Cloak: trans1
|
||||
Clock: clock1
|
||||
Construction: constru2
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -76,10 +76,10 @@ Sounds:
|
||||
BuildPaletteOpen: BUTTON1
|
||||
BuildPaletteClose: BUTTON1
|
||||
TabClick: SIDEBAR1
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
ClickSound: BUTTON1
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
ClickDisabledSound: ENDLIST1
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
Beacon: CHAT1
|
||||
InterruptType: Interrupt
|
||||
|
||||
@@ -245,6 +245,12 @@ dialog4:
|
||||
Inherits: ^Dialog
|
||||
PanelRegion: 512, 387, 6, 6, 52, 52, 6, 6
|
||||
|
||||
# Dummy definition, partial copy of dialog because the widget definition in `common` expects it with this name.
|
||||
dialog5:
|
||||
Inherits: ^Dialog
|
||||
Regions:
|
||||
background: 1, 1, 478, 478
|
||||
|
||||
lobby-bits:
|
||||
Inherits: ^Glyphs
|
||||
Regions:
|
||||
|
||||
@@ -54,7 +54,7 @@ Background@ENCYCLOPEDIA_PANEL:
|
||||
Children:
|
||||
Background@ACTOR_BG:
|
||||
Width: 150
|
||||
Height: 150
|
||||
Height: 170
|
||||
Background: dialog3
|
||||
Children:
|
||||
ActorPreview@ACTOR_PREVIEW:
|
||||
@@ -65,13 +65,13 @@ Background@ENCYCLOPEDIA_PANEL:
|
||||
ScrollPanel@ACTOR_DESCRIPTION_PANEL:
|
||||
X: 150 + 10
|
||||
Width: PARENT_RIGHT - 150 - 10
|
||||
Height: 150
|
||||
Height: 170
|
||||
TopBottomSpacing: 8
|
||||
Children:
|
||||
Label@ACTOR_DESCRIPTION:
|
||||
X: 8
|
||||
Y: 8
|
||||
Width: PARENT_RIGHT - 32
|
||||
Width: PARENT_RIGHT - 40
|
||||
VAlign: Top
|
||||
Font: Regular
|
||||
Button@BACK_BUTTON:
|
||||
|
||||
@@ -20,7 +20,7 @@ light_inf:
|
||||
Armament:
|
||||
Weapon: LMG
|
||||
Encyclopedia:
|
||||
Description: Light Infantry are lightly armored foot soldiers, equipped with 9mm RP assault rifles. Light Infantry are effective against other infantry and lightly armored vehicles.\n\nLight Infantry are resistant to missiles and large caliber guns, but very vulnerable to high explosives, fire and bullet weapons.
|
||||
Description: Light Infantry are lightly armored foot soldiers, equipped with 9mm RP assault rifles. Light Infantry are effective against other infantry and lightly armored vehicles.\n\nLight Infantry are resistant to missiles and large-caliber guns, but very vulnerable to high-explosives, fire and bullet weapons.
|
||||
Order: 0
|
||||
Category: Units
|
||||
WithInfantryBody:
|
||||
@@ -54,7 +54,7 @@ engineer:
|
||||
CaptureTypes: building
|
||||
PlayerExperience: 10
|
||||
Encyclopedia:
|
||||
Description: Engineers can be used to capture enemy buildings.\n\nEngineers are resistant to anti-tank weaponry but very vulnerable to high explosives, fire and bullet weapons.
|
||||
Description: Engineers can be used to capture enemy buildings.\n\nEngineers are resistant to anti-tank weaponry but very vulnerable to high-explosives, fire and bullet weapons.
|
||||
Order: 30
|
||||
Category: Units
|
||||
-RevealOnFire:
|
||||
@@ -88,7 +88,7 @@ trooper:
|
||||
Weapon: Bazooka
|
||||
LocalOffset: 128,0,256
|
||||
Encyclopedia:
|
||||
Description: Armed with missile launchers, Troopers fire wire guided armor-piercing warheads. These units are particularly effective against vehicles (especially armored ones) and buildings. However, this unit is largely useless against infantry.\n\nTroopers are resistant to anti-tank weaponry but very vulnerable to high explosives, fire and bullet weapons.
|
||||
Description: Armed with missile launchers, Troopers fire wire guided armor-piercing warheads. These units are particularly effective against vehicles (especially armored ones) and buildings. However, this unit is largely useless against infantry.\n\nTroopers are resistant to anti-tank weaponry but very vulnerable to high-explosives, fire and bullet weapons.
|
||||
Order: 10
|
||||
Category: Units
|
||||
TakeCover:
|
||||
@@ -178,7 +178,7 @@ fremen:
|
||||
Armament@SECONDARY:
|
||||
Weapon: Fremen_L
|
||||
Encyclopedia:
|
||||
Description: Fremen are the native desert warriors of Dune. Fremen ground units carry 10mm Assault Rifles and Rockets. Their firepower is equally effective against infantry and vehicles.\n\nFremen units are very vulnerable to high explosive and bullet weapons.
|
||||
Description: Fremen are the native desert warriors of Dune. Fremen ground units carry 10mm Assault Rifles and Rockets. Their firepower is equally effective against infantry and vehicles.\n\nFremen units are very vulnerable to high-explosive and bullet weapons.
|
||||
Order: 70
|
||||
Category: Units
|
||||
WithInfantryBody:
|
||||
@@ -307,7 +307,7 @@ saboteur:
|
||||
Health:
|
||||
HP: 4000
|
||||
Encyclopedia:
|
||||
Description: The Saboteur is a special military unit acquired by House Ordos. A single Saboteur can destroy any enemy building once he moves into it, though also destroys himself! A Saboteur can be stealthed by deploying itself.\n\nThe Saboteur is resistant to anti-tank weaponry, but very vulnerable to high explosives, fire, and bullet weapons.
|
||||
Description: The Saboteur is a special military unit acquired by House Ordos. A single Saboteur can destroy any enemy building once he moves into it, though also destroys himself! A Saboteur can be stealthed by deploying itself.\n\nThe Saboteur is resistant to anti-tank weaponry, but very vulnerable to high-explosives, fire, and bullet weapons.
|
||||
Order: 80
|
||||
Category: Units
|
||||
Mobile:
|
||||
|
||||
@@ -339,7 +339,7 @@ deathhand:
|
||||
Tooltip:
|
||||
Name: Death Hand
|
||||
Encyclopedia:
|
||||
Description: The Death Hand Missiles' warhead carries atomic cluster munitions. It detonates above the target, inflicting great damage over a wide area.
|
||||
Description: The Death Hand warhead carries atomic cluster munitions. It detonates above the target, inflicting great damage over a wide area.
|
||||
Order: 250
|
||||
Category: Super Weapons
|
||||
Buildable:
|
||||
|
||||
@@ -832,7 +832,7 @@ large_gun_turret:
|
||||
Tooltip:
|
||||
Name: Rocket Turret
|
||||
Encyclopedia:
|
||||
Description: The substantially improved Rocket Turret has a longer range and a higher rate of fire than the Gun Turret. The Rocket Turret's advanced targeting equipment requires power to operate.\n\nThe Rocket Turret is resistant to bullet and explosive weapons, but vulnerable to missiles and high caliber guns.
|
||||
Description: The substantially improved Rocket Turret has a longer range and a higher rate of fire than the Gun Turret. The Rocket Turret's advanced targeting equipment requires power to operate.\n\nThe Rocket Turret is resistant to bullet and explosive weapons, but vulnerable to missiles and high-caliber guns.
|
||||
Category: Buildings
|
||||
Order: 110
|
||||
RequiresBuildableArea:
|
||||
|
||||
@@ -20,7 +20,7 @@ mcv:
|
||||
Armor:
|
||||
Type: light
|
||||
Encyclopedia:
|
||||
Description: The Mobile Construction Vehicle must be driven to a suitable deployment area. After locating an appropriate area of rock, the MCV can be transformed into a Construction Yard.\n\nMCVs are resistant to bullets and some high explosives. They are vulnerable to missiles and high caliber guns.
|
||||
Description: The Mobile Construction Vehicle must be driven to a suitable deployment area. After locating an appropriate area of rock, the MCV can be transformed into a Construction Yard.\n\nMCVs are resistant to bullets and light-explosives. They are vulnerable to missiles and high-caliber guns.
|
||||
Order: 180
|
||||
Category: Units
|
||||
Mobile:
|
||||
@@ -82,7 +82,7 @@ harvester:
|
||||
Armor:
|
||||
Type: harvester
|
||||
Encyclopedia:
|
||||
Description: Harvesters are resistant to bullets, and to some degree, high explosives. These units are vulnerable to missiles and high caliber guns.\n\nA Harvester is included with a Refinery.
|
||||
Description: Harvesters are resistant to bullets, and to some degree, high-explosives. These units are vulnerable to missiles and high-caliber guns.\n\nA Harvester is included with a Refinery.
|
||||
Order: 130
|
||||
Category: Units
|
||||
Mobile:
|
||||
@@ -138,7 +138,7 @@ trike:
|
||||
Armor:
|
||||
Type: wood
|
||||
Encyclopedia:
|
||||
Description: Trikes are lightly armored, three-wheeled vehicles equipped with heavy machine guns, effective against infantry and lightly armored vehicles.\n\nTrikes are vulnerable to most weapons, though high caliber guns are slightly less effective against them.
|
||||
Description: Trikes are lightly armored, three-wheeled vehicles equipped with heavy machine guns, effective against infantry and lightly armored vehicles.\n\nTrikes are vulnerable to most weapons, though high-caliber guns are slightly less effective against them.
|
||||
Order: 90
|
||||
Category: Units
|
||||
Mobile:
|
||||
@@ -192,7 +192,7 @@ quad:
|
||||
Weapon: Rocket
|
||||
LocalOffset: 128,64,64, 128,-64,64
|
||||
Encyclopedia:
|
||||
Description: Stronger than the Trike in both armor and firepower, the Quad is a four-wheeled vehicle firing armor-piercing rockets. The Quad is effective against most vehicles.\n\nQuads are resistant to bullets and high explosives, to a lesser degree. However, Quads are vulnerable to missiles and high caliber guns.
|
||||
Description: Stronger than the Trike in both armor and firepower, the Quad is a four-wheeled vehicle firing armor-piercing rockets. The Quad is effective against most vehicles.\n\nQuads are resistant to bullets and explosives, to a lesser degree. However, Quads are vulnerable to missiles and high-caliber guns.
|
||||
Order: 110
|
||||
Category: Units
|
||||
AttackFrontal:
|
||||
@@ -227,7 +227,7 @@ siege_tank:
|
||||
Armor:
|
||||
Type: light
|
||||
Encyclopedia:
|
||||
Description: Siege Tanks are very effective against infantry and lightly armored vehicles - but very weak against heavily armored targets. They fire over a long range.\n\nSiege Tanks are resistant to bullets, and to some degree, high explosives. These units are vulnerable to missiles and high caliber guns.
|
||||
Description: Siege Tanks are very effective against infantry and lightly armored vehicles - but very weak against heavily armored targets. They fire over a long range.\n\nSiege Tanks are resistant to bullets, and to some degree, explosives. These units are vulnerable to missiles and high-caliber guns.
|
||||
Order: 170
|
||||
Category: Units
|
||||
Mobile:
|
||||
@@ -287,7 +287,7 @@ missile_tank:
|
||||
Armor:
|
||||
Type: wood
|
||||
Encyclopedia:
|
||||
Description: The Missile Tank is anti-aircraft capable and effective against most targets, except infantry units.\n\nMissile Tanks are vulnerable to most weapons, though high caliber guns are slightly less effective.
|
||||
Description: The Missile Tank is anti-aircraft capable and effective against most targets, except infantry units.\n\nMissile Tanks are vulnerable to most weapons, though high-caliber guns are slightly less effective.
|
||||
Order: 190
|
||||
Category: Units
|
||||
RevealsShroud:
|
||||
@@ -341,7 +341,7 @@ sonic_tank:
|
||||
Weapon: Sound
|
||||
LocalOffset: 600,0,427
|
||||
Encyclopedia:
|
||||
Description: The Sonic Tank is most effective against infantry and lightly armored vehicles - but weaker against armored targets.\n\nThe Sonic Tank will damage all units in its firing path.\n\nThey are very resistant to bullets and high explosives, but vulnerable to missiles and high caliber guns.
|
||||
Description: The Sonic Tank is most effective against infantry and lightly armored vehicles - but weaker against armored targets.\n\nThe Sonic Tank will damage all units in its firing path.\n\nThey are very resistant to bullets and small-explosives, but vulnerable to missiles and high-caliber guns.
|
||||
Order: 200
|
||||
Category: Units
|
||||
AttackFrontal:
|
||||
@@ -392,7 +392,7 @@ devastator:
|
||||
LocalOffset: 640,0,32
|
||||
MuzzleSequence: muzzle
|
||||
Encyclopedia:
|
||||
Description: The Devastator is the most powerful tank on Dune - powerfully effective against most units, but slow - and slow to fire. Nuclear powered, the Devastator fires dual plasma charges and may be ordered to self-destruct, damaging surrounding units and structures.\n\nThe Devastator is very resistant to bullet and high explosives, but vulnerable to missiles and high caliber guns.
|
||||
Description: The Devastator is the most powerful tank on Dune - powerfully effective against most units, but slow - and slow to fire. Nuclear powered, the Devastator fires dual plasma charges and may be ordered to self-destruct, damaging surrounding units and structures.\n\nThe Devastator is very resistant to bullet and high-explosives, but vulnerable to missiles and high-caliber guns.
|
||||
Order: 220
|
||||
Category: Units
|
||||
AttackFrontal:
|
||||
@@ -449,7 +449,7 @@ raider:
|
||||
Tooltip:
|
||||
Name: Raider Trike
|
||||
Encyclopedia:
|
||||
Description: Raiders are similar to Trikes, but the Ordos have refined their fire power, speed and armor to create a powerful and maneuverable scout. With dual 20mm cannons, Raiders are most effective against infantry and lightly armored vehicles.\n\nRaiders are vulnerable to most types of weaponry, though high caliber guns are slightly less effective.
|
||||
Description: Raiders are similar to Trikes, but the Ordos have refined their fire power, speed and armor to create a powerful and maneuverable scout. With dual 20mm cannons, Raiders are most effective against infantry and lightly armored vehicles.\n\nRaiders are vulnerable to most types of weaponry, though high-caliber guns are slightly less effective.
|
||||
Order: 100
|
||||
Category: Units
|
||||
UpdatesPlayerStatistics:
|
||||
@@ -536,7 +536,7 @@ deviator:
|
||||
Armor:
|
||||
Type: wood
|
||||
Encyclopedia:
|
||||
Description: The Deviator's missiles discharge a silicon cloud that interferes with vehicle controls - temporarily changing the allegiance of the targeted unit. Personnel are not seriously effected by the cloud.\n\nThe Deviator is vulnerable to most types of weapon, though high caliber guns are slightly less effective.
|
||||
Description: The Deviator's missiles discharge a silicon cloud that interferes with vehicle controls - temporarily changing the allegiance of the targeted unit. Personnel are not seriously effected by the cloud.\n\nThe Deviator is vulnerable to most types of weapon, though high-caliber guns are slightly less effective.
|
||||
Order: 210
|
||||
Category: Units
|
||||
RevealsShroud:
|
||||
@@ -611,7 +611,7 @@ combat_tank_a:
|
||||
Tooltip:
|
||||
Name: Atreides Combat Tank
|
||||
Encyclopedia:
|
||||
Description: The Combat Tank is effective against most vehicles, less so against lightly armored vehicles.\n\nAtreides Combat Tanks are very resistant to bullet and heavy explosives, but vulnerable to missiles and high caliber guns.
|
||||
Description: The Combat Tank is effective against most vehicles, less so against lightly armored vehicles.\n\nAtreides Combat Tanks are very resistant to bullet and heavy-explosives, but vulnerable to missiles and high-caliber guns.
|
||||
Order: 140
|
||||
Category: Units
|
||||
Buildable:
|
||||
@@ -626,7 +626,7 @@ combat_tank_h:
|
||||
Tooltip:
|
||||
Name: Harkonnen Combat Tank
|
||||
Encyclopedia:
|
||||
Description: The Combat Tank is effective against most vehicles, less so against lightly armored vehicles.\n\nThe Harkonnen Combat Tank is stronger but slower.
|
||||
Description: The Combat Tank is effective against most vehicles, less so against lightly armored vehicles.\n\nThe Harkonnen Combat Tank is stronger than its counterparts, but also slower.
|
||||
Order: 160
|
||||
Category: Units
|
||||
Buildable:
|
||||
@@ -649,7 +649,7 @@ combat_tank_o:
|
||||
Turreted:
|
||||
TurnSpeed: 20
|
||||
Encyclopedia:
|
||||
Description: The Combat Tank is effective against most vehicles, less so against lightly armored vehicles.\n\nThe Ordos Combat Tank is faster but weaker.
|
||||
Description: The Combat Tank is effective against most vehicles, less so against lightly armored vehicles.\n\nThe Ordos Combat Tank is the fastest variant of the Combat Tank, but it is also the weakest.
|
||||
Order: 150
|
||||
Category: Units
|
||||
Armament:
|
||||
|
||||
@@ -130,7 +130,7 @@ Sounds:
|
||||
ChatLine: rabeep1
|
||||
InterruptType: Interrupt
|
||||
ClickSound: ramenu1
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
ClickDisabledSound:
|
||||
Beacon: beepslct
|
||||
InterruptType: Interrupt
|
||||
|
||||
@@ -18,9 +18,9 @@ Sounds:
|
||||
ChatLine: message1
|
||||
InterruptType: Interrupt
|
||||
ClickDisabledSound: wrong1
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
ClickSound: clicky1
|
||||
InterruptType: Interrupt
|
||||
InterruptType: Overlap
|
||||
GameForming: gamefrm1
|
||||
Gdiclose: gdiclose
|
||||
Gdiopen: gdiopen
|
||||
|
||||
@@ -80,7 +80,7 @@ install_assemblies() (
|
||||
done
|
||||
fi
|
||||
else
|
||||
dotnet publish -c Release -p:TargetPlatform="${TARGETPLATFORM}" -p:CopyGenericLauncher="${COPY_GENERIC_LAUNCHER}" -p:CopyCncDll="${COPY_CNC_DLL}" -p:CopyD2kDll="${COPY_D2K_DLL}" -r "${TARGETPLATFORM}" -o "${DEST_PATH}" --self-contained true
|
||||
dotnet publish -c Release -p:TargetPlatform="${TARGETPLATFORM}" -p:CopyGenericLauncher="${COPY_GENERIC_LAUNCHER}" -p:CopyCncDll="${COPY_CNC_DLL}" -p:CopyD2kDll="${COPY_D2K_DLL}" -r "${TARGETPLATFORM}" -p:PublishDir="${DEST_PATH}" --self-contained true
|
||||
fi
|
||||
cd "${ORIG_PWD}"
|
||||
)
|
||||
@@ -156,7 +156,7 @@ install_windows_launcher() (
|
||||
FAQ_URL="${7}"
|
||||
|
||||
rm -rf "${SRC_PATH}/OpenRA.WindowsLauncher/obj" || :
|
||||
dotnet publish "${SRC_PATH}/OpenRA.WindowsLauncher/OpenRA.WindowsLauncher.csproj" -c Release -r "${TARGETPLATFORM}" -p:LauncherName="${LAUNCHER_NAME}" -p:TargetPlatform="${TARGETPLATFORM}" -p:ModID="${MOD_ID}" -p:DisplayName="${MOD_NAME}" -p:FaqUrl="${FAQ_URL}" -o "${DEST_PATH}" --self-contained true
|
||||
dotnet publish "${SRC_PATH}/OpenRA.WindowsLauncher/OpenRA.WindowsLauncher.csproj" -c Release -r "${TARGETPLATFORM}" -p:LauncherName="${LAUNCHER_NAME}" -p:TargetPlatform="${TARGETPLATFORM}" -p:ModID="${MOD_ID}" -p:DisplayName="${MOD_NAME}" -p:FaqUrl="${FAQ_URL}" -p:PublishDir="${DEST_PATH}" --self-contained true
|
||||
|
||||
# NET 6 is unable to customize the application host for windows when compiling from Linux,
|
||||
# so we must patch the properties we need in the PE header.
|
||||
|
||||
@@ -124,16 +124,24 @@ modify_plist "{DEV_VERSION}" "${TAG}" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
modify_plist "{FAQ_URL}" "http://wiki.openra.net/FAQ" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
modify_plist "{MINIMUM_SYSTEM_VERSION}" "10.11" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
|
||||
# Compile universal (x86_64 + arm64) Launcher and arch-specific apphosts
|
||||
# Compile universal (x86_64 + arm64) arch-specific apphosts
|
||||
clang apphost.c -o "${TEMPLATE_DIR}/Contents/MacOS/apphost-x86_64" -framework AppKit -target x86_64-apple-macos10.15
|
||||
clang apphost.c -o "${TEMPLATE_DIR}/Contents/MacOS/apphost-arm64" -framework AppKit -target arm64-apple-macos10.15
|
||||
clang apphost-mono.c -o "${TEMPLATE_DIR}/Contents/MacOS/apphost-mono" -framework AppKit -target x86_64-apple-macos10.11
|
||||
clang checkmono.c -o "${TEMPLATE_DIR}/Contents/MacOS/checkmono" -framework AppKit -target x86_64-apple-macos10.11
|
||||
|
||||
# Compile universal (x86_64 + arm64) Launcher
|
||||
clang launcher.m -o "${TEMPLATE_DIR}/Contents/MacOS/Launcher-x86_64" -framework AppKit -target x86_64-apple-macos10.11
|
||||
clang launcher.m -o "${TEMPLATE_DIR}/Contents/MacOS/Launcher-arm64" -framework AppKit -target arm64-apple-macos10.15
|
||||
lipo -create -output "${TEMPLATE_DIR}/Contents/MacOS/Launcher" "${TEMPLATE_DIR}/Contents/MacOS/Launcher-x86_64" "${TEMPLATE_DIR}/Contents/MacOS/Launcher-arm64"
|
||||
rm "${TEMPLATE_DIR}/Contents/MacOS/Launcher-x86_64" "${TEMPLATE_DIR}/Contents/MacOS/Launcher-arm64"
|
||||
|
||||
# Compile universal (x86_64 + arm64) Utility
|
||||
clang utility.m -o "${TEMPLATE_DIR}/Contents/MacOS/Utility-x86_64" -framework AppKit -target x86_64-apple-macos10.11
|
||||
clang utility.m -o "${TEMPLATE_DIR}/Contents/MacOS/Utility-arm64" -framework AppKit -target arm64-apple-macos10.15
|
||||
lipo -create -output "${TEMPLATE_DIR}/Contents/MacOS/Utility" "${TEMPLATE_DIR}/Contents/MacOS/Utility-x86_64" "${TEMPLATE_DIR}/Contents/MacOS/Utility-arm64"
|
||||
rm "${TEMPLATE_DIR}/Contents/MacOS/Utility-x86_64" "${TEMPLATE_DIR}/Contents/MacOS/Utility-arm64"
|
||||
|
||||
build_app "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Red Alert.app" "ra" "Red Alert" "699222659766026240"
|
||||
build_app "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Tiberian Dawn.app" "cnc" "Tiberian Dawn" "699223250181292033"
|
||||
build_app "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Dune 2000.app" "d2k" "Dune 2000" "712711732770111550"
|
||||
|
||||
227
packaging/macos/utility.m
Normal file
227
packaging/macos/utility.m
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <dlfcn.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <mach/machine.h>
|
||||
|
||||
#define SYSTEM_MONO_PATH @"/Library/Frameworks/Mono.framework/Versions/Current/"
|
||||
#define SYSTEM_MONO_MIN_VERSION @"6.4"
|
||||
#define DOTNET_MIN_MACOS_VERSION 10.15
|
||||
|
||||
typedef void* hostfxr_handle;
|
||||
struct hostfxr_initialize_parameters
|
||||
{
|
||||
size_t size;
|
||||
char *host_path;
|
||||
char *dotnet_root;
|
||||
};
|
||||
|
||||
typedef int32_t(*hostfxr_initialize_for_dotnet_command_line_fn)(
|
||||
int argc,
|
||||
char **argv,
|
||||
struct hostfxr_initialize_parameters *parameters,
|
||||
hostfxr_handle *host_context_handle);
|
||||
|
||||
typedef int32_t(*hostfxr_run_app_fn)(const hostfxr_handle host_context_handle);
|
||||
typedef int32_t(*hostfxr_close_fn)(const hostfxr_handle host_context_handle);
|
||||
typedef int (*mono_main)(int argc, char **argv);
|
||||
|
||||
int launch_mono(int argc, char **argv, char *modId)
|
||||
{
|
||||
NSString *exePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/MacOS/"];
|
||||
NSString *hostPath = [SYSTEM_MONO_PATH stringByAppendingPathComponent: @"lib/libmonosgen-2.0.dylib"];
|
||||
NSString *dllPath = [exePath stringByAppendingPathComponent: @"mono/OpenRA.Utility.dll"];
|
||||
|
||||
// TODO: This snippet increasing the open file limit was copied from
|
||||
// the monodevelop launcher stub. It may not be needed for OpenRA.
|
||||
struct rlimit limit;
|
||||
if (getrlimit(RLIMIT_NOFILE, &limit) == 0 && limit.rlim_cur < 1024)
|
||||
{
|
||||
limit.rlim_cur = limit.rlim_max < 1024 ? limit.rlim_max : 1024;
|
||||
setrlimit(RLIMIT_NOFILE, &limit);
|
||||
}
|
||||
|
||||
void *libmono = dlopen([hostPath UTF8String], RTLD_LAZY);
|
||||
if (!libmono)
|
||||
{
|
||||
NSLog(@"Failed to load libmonosgen-2.0.dylib: %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
mono_main _mono_main = (mono_main)dlsym(libmono, "mono_main");
|
||||
if (!_mono_main)
|
||||
{
|
||||
NSLog(@"Could not load mono_main(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Insert hostpath, dll and modId as arguments. Overwrite the first argument which was used to launch this application.
|
||||
char **newv = malloc((argc + 2) * sizeof(char*));
|
||||
if (!newv)
|
||||
{
|
||||
NSLog(@"Failed to allocate memory for args array.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
newv[0] = (char*)[hostPath UTF8String];
|
||||
newv[1] = (char*)[dllPath UTF8String];
|
||||
newv[2] = modId;
|
||||
for (int i = 1; i < argc; i++)
|
||||
newv[i + 2] = argv[i];
|
||||
|
||||
int ret = _mono_main(argc + 2, newv);
|
||||
free(newv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int launch_dotnet(int argc, char **argv, char *modId, bool isArmArchitecture)
|
||||
{
|
||||
NSString *exePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/MacOS/"];
|
||||
NSString *dllPath;
|
||||
NSString *hostPath;
|
||||
|
||||
if (isArmArchitecture)
|
||||
{
|
||||
hostPath = [exePath stringByAppendingPathComponent: @"arm64/libhostfxr.dylib"];;
|
||||
dllPath = [exePath stringByAppendingPathComponent: @"arm64/OpenRA.Utility.dll"];
|
||||
}
|
||||
else
|
||||
{
|
||||
hostPath = [exePath stringByAppendingPathComponent: @"x86_64/libhostfxr.dylib"];;
|
||||
dllPath = [exePath stringByAppendingPathComponent: @"x86_64/OpenRA.Utility.dll"];
|
||||
}
|
||||
|
||||
void *lib = dlopen([hostPath UTF8String], RTLD_LAZY);
|
||||
if (!lib)
|
||||
{
|
||||
NSLog(@"Failed to load %@: %s\n", hostPath, dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)dlsym(lib, "hostfxr_initialize_for_dotnet_command_line");
|
||||
if (!hostfxr_initialize_for_dotnet_command_line)
|
||||
{
|
||||
NSLog(@"Could not load hostfxr_initialize_for_dotnet_command_line(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
hostfxr_run_app_fn hostfxr_run_app = (hostfxr_run_app_fn)dlsym(lib, "hostfxr_run_app");
|
||||
if (!hostfxr_run_app)
|
||||
{
|
||||
NSLog(@"Could not load hostfxr_run_app(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
hostfxr_close_fn hostfxr_close = (hostfxr_close_fn)dlsym(lib, "hostfxr_close");
|
||||
if (!hostfxr_close)
|
||||
{
|
||||
NSLog(@"Could not load hostfxr_close(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct hostfxr_initialize_parameters params;
|
||||
params.size = sizeof(params);
|
||||
params.host_path = (char*)[[exePath stringByAppendingPathComponent: @"Utility"] UTF8String];
|
||||
params.dotnet_root = dirname((char*)[hostPath UTF8String]);
|
||||
|
||||
// Insert dll and modId as arguments. Overwrite the first argument which was used to launch this application.
|
||||
char **newv = malloc((argc + 1) * sizeof(char*));
|
||||
if (!newv)
|
||||
{
|
||||
NSLog(@"Failed to allocate memory for args array.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
newv[0] = (char*)[dllPath UTF8String];
|
||||
newv[1] = modId;
|
||||
for (int i = 1; i < argc; i++)
|
||||
newv[i + 1] = argv[i];
|
||||
|
||||
hostfxr_handle host_context_handle;
|
||||
hostfxr_initialize_for_dotnet_command_line(
|
||||
argc + 1,
|
||||
newv,
|
||||
¶ms,
|
||||
&host_context_handle);
|
||||
|
||||
hostfxr_run_app(host_context_handle);
|
||||
|
||||
int ret = hostfxr_close(host_context_handle);
|
||||
free(newv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
size_t size;
|
||||
cpu_type_t type;
|
||||
size = sizeof(type);
|
||||
bool isArmArchitecture = sysctlbyname("hw.cputype", &type, &size, NULL, 0) == 0 && (type & 0xFF) == CPU_TYPE_ARM;
|
||||
|
||||
BOOL useMono = NO;
|
||||
|
||||
// Before 10.15 macOS didn't support arm.
|
||||
// Mono is compiled for intel only.
|
||||
if (!isArmArchitecture)
|
||||
{
|
||||
if (@available(macOS 10.15, *))
|
||||
useMono = [[[NSProcessInfo processInfo] environment]objectForKey:@"OPENRA_PREFER_MONO"] != nil;
|
||||
else
|
||||
useMono = YES;
|
||||
}
|
||||
|
||||
if (useMono)
|
||||
{
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
[task setLaunchPath: [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/MacOS/checkmono"]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
if ([task terminationStatus] != 0)
|
||||
{
|
||||
NSLog(@"Utility requires Mono %@ or later. Please install Mono and try again.\n", SYSTEM_MONO_MIN_VERSION);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
|
||||
char *modId;
|
||||
if (plist)
|
||||
{
|
||||
NSString *modIdValue = [plist objectForKey:@"ModId"];
|
||||
if (modIdValue && [modIdValue length] > 0)
|
||||
modId = (char*)[modIdValue UTF8String];
|
||||
}
|
||||
|
||||
if (!modId)
|
||||
{
|
||||
NSLog(@"Could not detect ModId\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
setenv("ENGINE_DIR", (char*)[[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/Resources/"] UTF8String], 1);
|
||||
|
||||
int ret;
|
||||
if (useMono)
|
||||
ret = launch_mono(argc, argv, modId);
|
||||
else
|
||||
ret = launch_dotnet(argc, argv, modId, isArmArchitecture);
|
||||
|
||||
[pool release];
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user