Core: Added basic support for Spectators

TODO: Someone modify the files for cnc (chrome / rules)
This commit is contained in:
geckosoft
2010-10-31 03:57:03 +01:00
parent b9c40ad3ce
commit 030bd4b28d
18 changed files with 418 additions and 186 deletions

View File

@@ -56,7 +56,7 @@ namespace OpenRA.Network
public string Bot; // trait name of the bot to initialize in this slot, or null otherwise.
public bool Closed; // host has explicitly closed this slot.
public string MapPlayer; // playerReference to bind against.
public bool Spectator = false; // Spectating or not
// todo: more stuff?
}

View File

@@ -78,7 +78,7 @@ namespace OpenRA
PlayerName = client.Name;
InternalName = pr.Name;
Country = world.GetCountries()
.FirstOrDefault(c => client != null && client.Country == c.Race)
.FirstOrDefault(c => client != null && client.Country == c.Race )
?? world.GetCountries().Random(world.SharedRandom);
ClientIndex = client.Index;

View File

@@ -40,6 +40,9 @@ namespace OpenRA.Server
const int MasterPingInterval = 60 * 3; // 3 minutes. server has a 5 minute TTL for games, so give ourselves a bit
// of leeway.
public static int MaxSpectators = 4; // How many spectators to allow // @todo Expose this as an option
static int lastPing = 0;
static bool isInternetServer;
static string masterServerUrl;
@@ -135,6 +138,12 @@ namespace OpenRA.Server
.Where(s => s != null)
.Select((s, i) => { s.Index = i; return s; })
.ToList();
// Generate slots for spectators
for (int i = 0; i < MaxSpectators; i++)
{
lobbyInfo.Slots.Add(new Session.Slot { Spectator = true, Index = lobbyInfo.Slots.Count(), MapPlayer = null, Bot = null});
}
}
/* lobby rework todo:
@@ -283,6 +292,8 @@ namespace OpenRA.Server
static void SyncClientToPlayerReference(Session.Client c, PlayerReference pr)
{
if (pr == null)
return;
if (pr.LockColor)
{
c.Color1 = pr.Color;
@@ -358,6 +369,13 @@ namespace OpenRA.Server
SyncLobbyInfo();
return true;
}},
{ "spectator",
s =>
{
// GetClient(conn).Slot = -1; /* observer */
SyncLobbyInfo();
return true;
}},
{ "team",
s =>
{
@@ -411,7 +429,7 @@ namespace OpenRA.Server
var cl = GetClient(conn);
cl.Slot = slot;
SyncClientToPlayerReference(cl, Map.Players[slotData.MapPlayer]);
SyncClientToPlayerReference(cl, slotData.MapPlayer != null ? Map.Players[slotData.MapPlayer] : null);
SyncLobbyInfo();
return true;

View File

@@ -158,7 +158,7 @@ namespace OpenRA.Traits
void DrawUnitPath(Actor self)
{
if (!self.World.LocalPlayer.PlayerActor.Trait<DeveloperMode>().PathDebug) return;
if (self.World.LocalPlayer == null ||!self.World.LocalPlayer.PlayerActor.Trait<DeveloperMode>().PathDebug) return;
var activity = self.GetCurrentActivity();
var mobile = self.TraitOrDefault<IMove>();

View File

@@ -19,7 +19,7 @@ namespace OpenRA.Widgets.Delegates
{
public class LobbyDelegate : IWidgetDelegate
{
Widget Players, LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, EmptySlotTemplateHost;
Widget Players, LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, EmptySlotTemplateHost, EmptySpectatorSlotTemplateHost;
Dictionary<string, string> CountryNames;
string MapUid;
@@ -44,6 +44,7 @@ namespace OpenRA.Widgets.Delegates
RemotePlayerTemplate = Players.GetWidget("TEMPLATE_REMOTE");
EmptySlotTemplate = Players.GetWidget("TEMPLATE_EMPTY");
EmptySlotTemplateHost = Players.GetWidget("TEMPLATE_EMPTY_HOST");
EmptySpectatorSlotTemplateHost = Players.GetWidget("TEMPLATE_EMPTY_SPECTATOR");
var mapPreview = lobby.GetWidget<MapPreviewWidget>("LOBBY_MAP_PREVIEW");
mapPreview.Map = () => Map;
@@ -71,6 +72,7 @@ namespace OpenRA.Widgets.Delegates
};
CountryNames = Rules.Info["world"].Traits.WithInterface<OpenRA.Traits.CountryInfo>().ToDictionary(a => a.Race, a => a.Name);
CountryNames.Add("random", "Random");
var mapButton = lobby.GetWidget("CHANGEMAP_BUTTON");
@@ -232,25 +234,48 @@ namespace OpenRA.Widgets.Delegates
{
if (Game.IsHost)
{
template = EmptySlotTemplateHost.Clone();
var name = template.GetWidget<ButtonWidget>("NAME");
name.GetText = () => s.Closed ? "Closed" : (s.Bot == null)? "Open" : "Bot: " + s.Bot;
name.OnMouseUp = _ =>
if (slot.Spectator)
{
if (s.Closed)
template = EmptySlotTemplateHost.Clone();
var btn = template.GetWidget<ButtonWidget>("JOIN");
var name = template.GetWidget<ButtonWidget>("NAME");
btn.GetText = () => "Spectate in this slot";
name.GetText = () => s.Closed ? "Closed" : "Open";
name.OnMouseUp = _ =>
{
s.Bot = null;
orderManager.IssueOrder(Order.Command("slot_open " + s.Index));
}
else
{
if (s.Bot == null && Map.Players[s.MapPlayer].AllowBots)
orderManager.IssueOrder(Order.Command("slot_bot {0} HackyAI".F(s.Index)));
if (s.Closed)
{
orderManager.IssueOrder(Order.Command("slot_open " + s.Index));
}
else
{
orderManager.IssueOrder(Order.Command("slot_close " + s.Index));
}
return true;
};
}
return true;
};
}
else
{
template = EmptySlotTemplateHost.Clone();
var name = template.GetWidget<ButtonWidget>("NAME");
name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : "Bot: " + s.Bot;
name.OnMouseUp = _ =>
{
if (s.Closed)
{
s.Bot = null;
orderManager.IssueOrder(Order.Command("slot_open " + s.Index));
}
else
{
if (s.Bot == null && Map.Players[s.MapPlayer].AllowBots)
orderManager.IssueOrder(Order.Command("slot_bot {0} HackyAI".F(s.Index)));
else
orderManager.IssueOrder(Order.Command("slot_close " + s.Index));
}
return true;
};
}
}
else
{
@@ -330,6 +355,14 @@ namespace OpenRA.Widgets.Delegates
var status = template.GetWidget<CheckboxWidget>("STATUS");
status.Checked = () => c.State == Session.ClientState.Ready;
status.OnMouseDown = CycleReady;
Session.Slot slot1 = slot;
color.IsVisible = () => !slot1.Spectator;
colorBlock.IsVisible = () => !slot1.Spectator;
faction.IsVisible = () => !slot1.Spectator;
factionname.IsVisible = () => !slot1.Spectator;
factionflag.IsVisible = () => !slot1.Spectator;
team.IsVisible = () => !slot1.Spectator;
}
else
{

View File

@@ -39,7 +39,7 @@ namespace OpenRA
public void AddPlayer(Player p) { players[p.Index] = p; }
int localPlayerIndex;
int localPlayerIndex = -999;
public Player LocalPlayer
{
get { return players.ContainsKey(localPlayerIndex) ? players[localPlayerIndex] : null; }

View File

@@ -103,6 +103,7 @@ namespace OpenRA
public static bool IsVisible(this Actor a, Player byPlayer) /* must never be relied on in synced code! */
{
if (byPlayer == null) return true; // Observer
if (a.World.LocalPlayer != null && a.World.LocalPlayer.Shroud.Disabled)
return true;

View File

@@ -36,7 +36,7 @@ namespace OpenRA.Mods.RA
// create the players which are bound through slots.
foreach (var slot in w.LobbyInfo.Slots)
{
var client = w.LobbyInfo.Clients.FirstOrDefault(c => c.Slot == slot.Index);
var client = w.LobbyInfo.Clients.FirstOrDefault(c => c.Slot == slot.Index && slot.MapPlayer != null);
if (client != null)
{
/* spawn a real player in this slot. */
@@ -45,7 +45,7 @@ namespace OpenRA.Mods.RA
if (client.Index == Game.LocalClientId)
w.SetLocalPlayer(player.Index); // bind this one to the local player.
}
else if (slot.Bot != null)
else if (slot.Bot != null && slot.MapPlayer != null)
{
/* spawn a bot in this slot, "owned" by the host */
@@ -80,6 +80,14 @@ namespace OpenRA.Mods.RA
static Stance ChooseInitialStance(Player p, Player q)
{
if (p == q) return Stance.Ally;
var pc = GetClientForPlayer(p);
var qc = GetClientForPlayer(q);
if (p.World.LobbyInfo.Slots.Count > 0)
{
if (p.World.LobbyInfo.Slots[pc.Slot].Spectator) return Stance.Ally;
if (p.World.LobbyInfo.Slots[qc.Slot].Spectator) return Stance.Ally;
}
if (p.PlayerRef.Allies.Contains(q.InternalName))
return Stance.Ally;
@@ -92,8 +100,6 @@ namespace OpenRA.Mods.RA
if (p.IsBot ^ q.IsBot)
return Stance.Enemy; // bots and humans hate each other
var pc = GetClientForPlayer(p);
var qc = GetClientForPlayer(q);
return pc.Team != 0 && pc.Team == qc.Team
? Stance.Ally : Stance.Enemy;

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Network;
using OpenRA.Traits;
using XRandom = OpenRA.Thirdparty.Random;
using OpenRA.FileFormats;
@@ -232,6 +233,10 @@ namespace OpenRA.Mods.RA
return !hackyAI.enabled;
}
bool HasHumanPlayers()
{
return p.World.players.Any(a => !a.Value.IsBot && !a.Value.NonCombatant);
}
int2? ChooseEnemyTarget()
{
// Criteria for picking an enemy:
@@ -240,7 +245,7 @@ namespace OpenRA.Mods.RA
// 3. not dead.
var possibleTargets = world.WorldActor.Trait<MPStartLocations>().Start
.Where(kv => kv.Key != p && IsHumanPlayer(kv.Key)
.Where(kv => kv.Key != p && (!HasHumanPlayers()|| IsHumanPlayer(kv.Key))
&& p.WinState == WinState.Undefined)
.Select(kv => kv.Value);

View File

@@ -28,13 +28,15 @@ namespace OpenRA.Mods.RA
public void WorldLoaded(World world)
{
var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0)
var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0 && c.Slot != -1)
.Select(c => world.Map.SpawnPoints.ElementAt(c.SpawnPoint - 1)).ToList();
var available = world.Map.SpawnPoints.Except(taken).ToList();
// Set spawn
foreach (var slot in world.LobbyInfo.Slots)
{
if (slot.Spectator)
continue; // Skip spectator slots
var client = world.LobbyInfo.Clients.FirstOrDefault(c => c.Slot == slot.Index);
var player = FindPlayerInSlot(world, slot);
@@ -49,7 +51,7 @@ namespace OpenRA.Mods.RA
// Explore allied shroud
foreach (var p in Start)
if (p.Key == world.LocalPlayer || p.Key.Stances[world.LocalPlayer] == Stance.Ally)
if ((world.LocalPlayer != null ) &&(p.Key == world.LocalPlayer || p.Key.Stances[world.LocalPlayer] == Stance.Ally))
world.WorldActor.Trait<Shroud>().Explore(world, p.Value,
world.WorldActor.Info.Traits.Get<MPStartLocationsInfo>().InitialExploreRange);

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{4A8A43B5-A9EF-4ED0-99DD-4BAB10A0DB6E}</ProjectGuid>
<OutputType>Library</OutputType>
@@ -230,6 +230,7 @@
<Compile Include="WaterPaletteRotation.cs" />
<Compile Include="Weapon.cs" />
<Compile Include="Widgets\BuildPaletteWidget.cs" />
<Compile Include="Widgets\Delegates\IngameObserverChromeDelegate.cs" />
<Compile Include="Widgets\Delegates\IngameChromeDelegate.cs" />
<Compile Include="Widgets\MoneyBinWidget.cs" />
<Compile Include="Widgets\OrderButtonWidget.cs" />

View File

@@ -15,6 +15,7 @@ namespace OpenRA.Mods.RA
public class OpenWidgetAtGameStartInfo : ITraitInfo
{
public readonly string Widget = "INGAME_ROOT";
public readonly string ObserverWidget = "";
public object Create(ActorInitializer init) { return new OpenWidgetAtGameStart(this); }
}
@@ -29,9 +30,10 @@ namespace OpenRA.Mods.RA
public void WorldLoaded(World world)
{
// Todo: custom observer ui?
if (world.LocalPlayer != null)
world.OpenWindow(Info.Widget);
else if (Info.ObserverWidget != null)
world.OpenWindow(Info.ObserverWidget);
}
}
}

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA
void SpawnUnitsForPlayer(Player p, int2 sp)
{
if (!p.PlayerRef.DefaultStartingUnits)
return; /* they don't want an mcv, the map provides something else for them. */
return; /* they don't want an mcv, the map provides something else for them OR it is a spectator. */
p.World.CreateActor("mcv", new TypeDictionary
{

View File

@@ -71,6 +71,8 @@ Player:
World:
OpenWidgetAtGameStart:
Widget: INGAME_ROOT
# ObserverWidget:
ScreenShaker:
NukePaletteEffect:
WaterPaletteRotation:

View File

@@ -193,6 +193,7 @@
<image name="allies" x="30" y="84" width="30" height="15" />
<image name="soviet" x="0" y="84" width="30" height="15" />
<image name="random" x="60" y="84" width="30" height="15" />
<image name="spectator" x="60" y="84" width="30" height="15" />
</collection>
<collection name="music" src="musicplayer.png">
<image name="pause" x="0" y="0" width="25" height="25" />

View File

@@ -29,175 +29,204 @@ Background@SERVER_LOBBY:
Y:4
Width:244
Height:244
Container@PLAYERS:
Id:PLAYERS
ListBox@PLAYERSX:
Id:PLAYERSX
X:20
Y:75
Width:500
Height:200
Height:227
Children:
Container@TEMPLATE_LOCAL:
Id:TEMPLATE_LOCAL
X:0
Y:0
Container@PLAYERS:
Id:PLAYERS
X:2
Y:1
Width:500
Height:30
Visible:false
Height:2270
Children:
TextField@NAME:
Id:NAME
Text:Name
Width:139
Height:25
Container@TEMPLATE_LOCAL:
Id:TEMPLATE_LOCAL
X:0
Y:0
MaxLength:16
Button@COLOR:
Id:COLOR
Width:65
Height:25
X:159
Y:0
Width:400
Height:30
Visible:false
Children:
ColorBlock@COLORBLOCK:
Id:COLORBLOCK
X:5
TextField@NAME:
Id:NAME
Text:Name
Width:139
Height:25
X:0
Y:0
MaxLength:16
Button@COLOR:
Id:COLOR
Width:65
Height:25
X:159
Y:0
Children:
ColorBlock@COLORBLOCK:
Id:COLORBLOCK
X:5
Y:7
Width:PARENT_RIGHT-10
Height:PARENT_BOTTOM-12
Button@FACTION:
Id:FACTION
Width:110
Height:25
X:244
Y:0
Children:
Image@FACTIONFLAG:
Id:FACTIONFLAG
Width:30
Height:15
X:5
Y:5
Label@FACTIONNAME:
Id:FACTIONNAME
Text:Faction
Width:60
Height:25
X:40
Y:0
Button@TEAM:
Id:TEAM
Text:Team
Width:25
Height:25
X:374
Y:0
Checkbox@STATUS:
Id:STATUS
X:450
Y:2
Width:20
Height:20
Container@TEMPLATE_REMOTE:
Id:TEMPLATE_REMOTE
X:0
Y:0
Width:400
Height:30
Visible:false
Children:
Label@NAME:
Id:NAME
Text:Name
Width:139
Height:25
X:0
Y:0
ColorBlock@COLOR:
Id:COLOR
X:164
Y:7
Width:PARENT_RIGHT-10
Height:PARENT_BOTTOM-12
Button@FACTION:
Id:FACTION
Width:110
Height:25
X:244
Y:0
Children:
Image@FACTIONFLAG:
Id:FACTIONFLAG
Width:30
Height:15
X:5
Y:5
Label@FACTIONNAME:
Id:FACTIONNAME
Text:Faction
Width:60
Width:55
Height:13
Label@FACTION:
Id:FACTION
Width:110
Height:25
X:40
X:244
Y:0
Button@TEAM:
Id:TEAM
Text:Team
Width:25
Height:25
X:374
Y:0
Checkbox@STATUS:
Id:STATUS
X:455
Y:2
Width:20
Height:20
Container@TEMPLATE_REMOTE:
Id:TEMPLATE_REMOTE
X:0
Y:0
Width:500
Height:30
Visible:false
Children:
Label@NAME:
Id:NAME
Text:Name
Width:139
Height:25
X:0
Y:0
ColorBlock@COLOR:
Id:COLOR
X:164
Y:7
Width:55
Height:13
Label@FACTION:
Id:FACTION
Width:110
Height:25
X:244
Y:0
Children:
Image@FACTIONFLAG:
Id:FACTIONFLAG
Width:30
Height:15
X:5
Y:5
Label@FACTIONNAME:
Id:FACTIONNAME
Text:Faction
Width:60
Children:
Image@FACTIONFLAG:
Id:FACTIONFLAG
Width:30
Height:15
X:5
Y:5
Label@FACTIONNAME:
Id:FACTIONNAME
Text:Faction
Width:60
Height:25
X:40
Y:0
Label@TEAM:
Id:TEAM
Text:Team
Width:70
Height:25
X:40
X:351
Y:0
Label@TEAM:
Id:TEAM
Text:Team
Width:70
Height:25
X:351
Y:0
Align:Center
Bold: false
Checkbox@STATUS:
Id:STATUS
X:455
Y:2
Width:20
Height:20
Container@TEMPLATE_EMPTY:
Id:TEMPLATE_EMPTY
X:0
Y:0
Width:500
Height:30
Visible:false
Children:
Label@NAME:
Id:NAME
Text:Name
Width:139
Height:25
Align:Center
Bold: false
Checkbox@STATUS:
Id:STATUS
X:455
Y:2
Width:20
Height:20
Container@TEMPLATE_EMPTY_SPECTATOR:
Id:TEMPLATE_EMPTY_SPECTATOR
X:0
Y:0
Button@JOIN:
Id:JOIN
Text:Play in this slot
Width:PARENT_RIGHT - 160
Height:25
X:160
Y:0
Container@TEMPLATE_EMPTY_HOST:
Id:TEMPLATE_EMPTY_HOST
X:0
Y:0
Width:500
Height:30
Visible:false
Children:
Button@NAME: -- TODO: replace with dropdown
Id:NAME
Text:Name
Width:155
Height:25
Width:400
Height:30
Visible:false
Children:
Button@NAME:
Id:NAME
Text:Name
Width:139
Height:25
X:0
Y:0
Button@JOIN:
Id:JOIN
Text:Spectate this match
Width:PARENT_RIGHT - 90
Height:25
X:160
Y:0
Container@TEMPLATE_EMPTY:
Id:TEMPLATE_EMPTY
X:0
Y:0
Button@JOIN:
Id:JOIN
Text:Play in this slot
Width:PARENT_RIGHT - 160
Height:25
X:160
Width:400
Height:30
Visible:false
Children:
Label@NAME:
Id:NAME
Text:Name
Width:139
Height:25
X:0
Y:0
Button@JOIN:
Id:JOIN
Text:Play in this slot
Width:PARENT_RIGHT - 90
Height:25
X:160
Y:0
Container@TEMPLATE_EMPTY_HOST:
Id:TEMPLATE_EMPTY_HOST
X:0
Y:0
Width:400
Height:30
Visible:false
Children:
Button@NAME: -- TODO: replace with dropdown
Id:NAME
Text:Name
Width:155
Height:25
X:0
Y:0
Button@JOIN:
Id:JOIN
Text:Play in this slot
Width:PARENT_RIGHT - 90
Height:25
X:160
Y:0
Container@LABEL_CONTAINER:
X:30
Y:45
@@ -240,7 +269,7 @@ Background@SERVER_LOBBY:
Bold:True
Label@LABEL_LOBBY_STATUS:
Id:LABEL_LOBBY_STATUS
X:432
X:420
Y:0
Width:70
Height:25

View File

@@ -284,3 +284,133 @@ Container@INGAME_ROOT:
Width:200
Height:20
Text: Give Exploration
Container@OBSERVER_ROOT:
Id:OBSERVER_ROOT
Visible:true
Delegate:IngameObserverChromeDelegate
Children:
WorldInteractionController:
X:0
Y:0
Width:WINDOW_RIGHT
Height:WINDOW_BOTTOM
ViewportScrollController:
X:0
Y:0
Width:WINDOW_RIGHT
Height:WINDOW_BOTTOM
Timer@GAME_TIMER:
Id:GAME_TIMER
X: WINDOW_RIGHT/2
Y: 10
Background@POSTGAME_BG:
Id:POSTGAME_BG
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - HEIGHT)/2
Width:400
Height:100
Background:dialog4
Visible:false
Children:
Label@TEXT:
Id:TEXT
X:(PARENT_RIGHT - WIDTH)/2
Y:(PARENT_BOTTOM - HEIGHT)/2
Width:200
Height:40
Align:Center
Bold:True
SpecialPowerBin@INGAME_POWERS_BIN:
Id:INGAME_POWERS_BIN
X:0
Y:25
Button@INGAME_OPTIONS_BUTTON:
Id:INGAME_OPTIONS_BUTTON
X:0
Y:0
Width:160
Height:25
Text:Options
Bold:True
WorldTooltip:
Background@INGAME_OPTIONS_BG:
Id:INGAME_OPTIONS_BG
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - HEIGHT)/2
Width:300
Height:320
Visible:false
Children:
Label@LABEL_TITLE:
Id:LABEL_TITLE
X:(PARENT_RIGHT - WIDTH)/2
Y:20
Width:250
Height:25
Text:Options
Align:Center
Bold:True
Button@RESUME:
Id:RESUME
X:(PARENT_RIGHT - WIDTH)/2
Y:60
Width:160
Height:25
Text:Resume
Bold:True
Button@SETTINGS:
Id:SETTINGS
X:(PARENT_RIGHT - WIDTH)/2
Y:100
Width:160
Height:25
Text:Settings
Bold:True
Button@MUSIC:
Id:MUSIC
X:(PARENT_RIGHT - WIDTH)/2
Y:140
Width:160
Height:25
Text:Music
Bold:True
Button@SURRENDER:
Id:SURRENDER
X:(PARENT_RIGHT - WIDTH)/2
Y:180
Width:160
Height:25
Text:Surrender
Bold:True
Button@DISCONNECT:
Id:DISCONNECT
X:(PARENT_RIGHT - WIDTH)/2
Y:220
Width:160
Height:25
Text:Disconnect
Bold:True
Button@QUIT:
Id:QUIT
X:(PARENT_RIGHT - WIDTH)/2
Y:260
Width:160
Height:25
Text:Quit
Bold:True
ChatDisplay@CHAT_DISPLAY:
Id:CHAT_DISPLAY
X:250
Y:WINDOW_BOTTOM - HEIGHT - 30
Width: 760
Height: 200
ClickThrough: True
DrawBackground: False
RemoveTime:250
ChatEntry@CHAT_ENTRY:
Id:CHAT_ENTRY
X:250
Y:WINDOW_BOTTOM - HEIGHT
Width: 760
Height: 30
ClickThrough: True

View File

@@ -127,6 +127,8 @@ Player:
World:
OpenWidgetAtGameStart:
Widget: INGAME_ROOT
ObserverWidget: OBSERVER_ROOT
ScreenShaker:
WaterPaletteRotation:
ChronoshiftPaletteEffect: