Add a per-player handicap option to the lobby.

Handicaps reduce unit health, firepower, and build speed.
This commit is contained in:
Paul Chote
2020-12-30 15:17:44 +00:00
committed by reaperrr
parent c7c78eda80
commit 02a2624bcc
22 changed files with 346 additions and 0 deletions

View File

@@ -52,6 +52,16 @@ namespace OpenRA.Mods.Common.Scripting
}
}
[Desc("The player's handicap level.")]
public int Handicap
{
get
{
var c = Player.World.LobbyInfo.Clients.FirstOrDefault(i => i.Index == Player.ClientIndex);
return c?.Handicap ?? 0;
}
}
[Desc("Returns true if the player is a bot.")]
public bool IsBot { get { return Player.IsBot; } }

View File

@@ -43,6 +43,7 @@ namespace OpenRA.Mods.Common.Server
{ "name", Name },
{ "faction", Faction },
{ "team", Team },
{ "handicap", Handicap },
{ "spawn", Spawn },
{ "clear_spawn", ClearPlayerSpawn },
{ "color", PlayerColor },
@@ -245,6 +246,7 @@ namespace OpenRA.Mods.Common.Server
client.Slot = null;
client.SpawnPoint = 0;
client.Team = 0;
client.Handicap = 0;
client.Color = Color.White;
server.SyncLobbyClients();
CheckAutoStart(server);
@@ -377,6 +379,7 @@ namespace OpenRA.Mods.Common.Server
Faction = "Random",
SpawnPoint = 0,
Team = 0,
Handicap = 0,
State = Session.ClientState.NotReady,
BotControllerClientIndex = controllerClientIndex
};
@@ -736,6 +739,7 @@ namespace OpenRA.Mods.Common.Server
targetClient.Slot = null;
targetClient.SpawnPoint = 0;
targetClient.Team = 0;
targetClient.Handicap = 0;
targetClient.Color = Color.White;
targetClient.State = Session.ClientState.NotReady;
server.SendMessage("{0} moved {1} to spectators.".F(client.Name, targetClient.Name));
@@ -824,6 +828,42 @@ namespace OpenRA.Mods.Common.Server
}
}
static bool Handicap(S server, Connection conn, Session.Client client, string s)
{
lock (server.LobbyInfo)
{
var parts = s.Split(' ');
var targetClient = server.LobbyInfo.ClientWithIndex(Exts.ParseIntegerInvariant(parts[0]));
// Only the host can change other client's info
if (targetClient.Index != client.Index && !client.IsAdmin)
return true;
// Map has disabled handicap changes
if (server.LobbyInfo.Slots[targetClient.Slot].LockHandicap)
return true;
if (!Exts.TryParseIntegerInvariant(parts[1], out var handicap))
{
Log.Write("server", "Invalid handicap: {0}", s);
return false;
}
// Handicaps may be set between 0 - 95% in steps of 5%
var options = Enumerable.Range(0, 20).Select(i => 5 * i);
if (!options.Contains(handicap))
{
Log.Write("server", "Invalid handicap: {0}", s);
return false;
}
targetClient.Handicap = handicap;
server.SyncLobbyClients();
return true;
}
}
static bool ClearPlayerSpawn(S server, Connection conn, Session.Client client, string s)
{
var spawnPoint = Exts.ParseIntegerInvariant(s);
@@ -1003,6 +1043,7 @@ namespace OpenRA.Mods.Common.Server
LockFaction = pr.LockFaction,
LockColor = pr.LockColor,
LockTeam = pr.LockTeam,
LockHandicap = pr.LockHandicap,
LockSpawn = pr.LockSpawn,
Required = pr.Required,
};

View File

@@ -0,0 +1,40 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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
{
[Desc("Modifies the damage applied to this actor based on the owner's handicap.")]
public class HandicapDamageMultiplierInfo : TraitInfo
{
public override object Create(ActorInitializer init) { return new HandicapDamageMultiplier(init.Self); }
}
public class HandicapDamageMultiplier : IDamageModifier
{
readonly Actor self;
public HandicapDamageMultiplier(Actor self)
{
this.self = self;
}
int IDamageModifier.GetDamageModifier(Actor attacker, Damage damage)
{
// Equivalent to the health handicap from C&C3:
// 5% handicap = 95% health = 105% damage
// 50% handicap = 50% health = 200% damage
// 95% handicap = 5% health = 2000% damage
return 10000 / (100 - self.Owner.Handicap);
}
}
}

View File

@@ -0,0 +1,40 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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
{
[Desc("Modifies the damage applied by this actor based on the owner's handicap.")]
public class HandicapFirepowerMultiplierInfo : TraitInfo
{
public override object Create(ActorInitializer init) { return new HandicapFirepowerMultiplier(init.Self); }
}
public class HandicapFirepowerMultiplier : IFirepowerModifier
{
readonly Actor self;
public HandicapFirepowerMultiplier(Actor self)
{
this.self = self;
}
int IFirepowerModifier.GetFirepowerModifier()
{
// Equivalent to the firepower handicap from C&C3:
// 5% handicap = 95% firepower
// 50% handicap = 50% firepower
// 95% handicap = 5% firepower
return 100 - self.Owner.Handicap;
}
}
}

View File

@@ -0,0 +1,32 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 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.Collections.Generic;
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Modifies the production time of this actor based on the producer's handicap.")]
public class HandicapProductionTimeMultiplierInfo : TraitInfo<HandicapProductionTimeMultiplier>, IProductionTimeModifierInfo
{
int IProductionTimeModifierInfo.GetProductionTimeModifier(TechTree techTree, string queue)
{
// Equivalent to the build speed handicap from C&C3:
// 5% handicap = 105% build time
// 50% handicap = 150% build time
// 95% handicap = 195% build time
return 100 + techTree.Owner.Handicap;
}
}
public class HandicapProductionTimeMultiplier { }
}

View File

@@ -106,6 +106,8 @@ namespace OpenRA.Mods.Common.Traits
return ret;
}
public Player Owner { get { return player; } }
class Watcher
{
public readonly string Key;

View File

@@ -64,6 +64,7 @@ namespace OpenRA.Mods.Common.Traits
DisplayFactionId = clientFaction.InternalName,
Color = client.Color,
Team = client.Team,
Handicap = client.Handicap,
SpawnPoint = resolvedSpawnPoint,
IsRandomFaction = clientFaction.RandomFactionMembers.Any(),
IsRandomSpawnPoint = client.SpawnPoint == 0,

View File

@@ -624,6 +624,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
LobbyUtils.SetupEditableColorWidget(template, slot, client, orderManager, shellmapWorld, colorPreview);
LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, factions);
LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableHandicapWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager, map);
}
@@ -640,6 +641,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (isHost)
{
LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableHandicapWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, map);
LobbyUtils.SetupPlayerActionWidget(template, slot, client, orderManager, worldRenderer,
lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players);
@@ -648,6 +650,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
LobbyUtils.SetupNameWidget(template, slot, client, orderManager, worldRenderer);
LobbyUtils.SetupTeamWidget(template, slot, client);
LobbyUtils.SetupHandicapWidget(template, slot, client);
LobbyUtils.SetupSpawnWidget(template, slot, client);
}

View File

@@ -146,6 +146,25 @@ namespace OpenRA.Mods.Common.Widgets.Logic
dropdown.ShowDropDown("TEAM_DROPDOWN_TEMPLATE", 150, options, setupItem);
}
public static void ShowHandicapDropDown(DropDownButtonWidget dropdown, Session.Client client,
OrderManager orderManager)
{
Func<int, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
() => client.Handicap == ii,
() => orderManager.IssueOrder(Order.Command("handicap {0} {1}".F(client.Index, ii))));
var label = "{0}%".F(ii);
item.Get<LabelWidget>("LABEL").GetText = () => label;
return item;
};
// Handicaps may be set between 0 - 95% in steps of 5%
var options = Enumerable.Range(0, 20).Select(i => 5 * i);
dropdown.ShowDropDown("TEAM_DROPDOWN_TEMPLATE", 150, options, setupItem);
}
public static void ShowSpawnDropDown(DropDownButtonWidget dropdown, Session.Client client,
OrderManager orderManager, IEnumerable<int> spawnPoints)
{
@@ -562,6 +581,29 @@ namespace OpenRA.Mods.Common.Widgets.Logic
HideChildWidget(parent, "TEAM_DROPDOWN");
}
public static void SetupEditableHandicapWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map)
{
var dropdown = parent.Get<DropDownButtonWidget>("HANDICAP_DROPDOWN");
dropdown.IsVisible = () => true;
dropdown.IsDisabled = () => s.LockTeam || orderManager.LocalClient.IsReady;
dropdown.OnMouseDown = _ => ShowHandicapDropDown(dropdown, c, orderManager);
var handicapLabel = new CachedTransform<int, string>(h => "{0}%".F(h));
dropdown.GetText = () => handicapLabel.Update(c.Handicap);
HideChildWidget(parent, "HANDICAP");
}
public static void SetupHandicapWidget(Widget parent, Session.Slot s, Session.Client c)
{
var team = parent.Get<LabelWidget>("HANDICAP");
team.IsVisible = () => true;
var handicapLabel = new CachedTransform<int, string>(h => "{0}%".F(h));
team.GetText = () => handicapLabel.Update(c.Handicap);
HideChildWidget(parent, "HANDICAP_DROPDOWN");
}
public static void SetupEditableSpawnWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map)
{
var dropdown = parent.Get<DropDownButtonWidget>("SPAWN_DROPDOWN");