Index: ps/trunk/binaries/data/mods/public/simulation/components/StatusBars.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/StatusBars.js (revision 22401)
+++ ps/trunk/binaries/data/mods/public/simulation/components/StatusBars.js (revision 22402)
@@ -1,328 +1,334 @@
const g_NaturalColor = "255 255 255 255"; // pure white
function StatusBars() {}
StatusBars.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
/**
* For every sprite, the code will call their "Add" method when regenerating
* the sprites. Every sprite adder should return the height it needs.
*
* Modders who need extra sprites can just modify this array, and
* provide the right methods.
*/
StatusBars.prototype.Sprites = [
"ExperienceBar",
"PackBar",
"ResourceSupplyBar",
"CaptureBar",
"HealthBar",
"AuraIcons",
"RankIcon"
];
StatusBars.prototype.Init = function()
{
this.enabled = false;
this.showRank = false;
this.showExperience = false;
// Whether the status bars used the player colors anywhere (e.g. in the capture bar)
this.usedPlayerColors = false;
this.auraSources = new Map();
};
/**
* Don't serialise this.enabled since it's modified by the GUI.
*/
StatusBars.prototype.Serialize = function()
{
return { "auraSources": this.auraSources };
};
StatusBars.prototype.Deserialize = function(data)
{
this.Init();
this.auraSources = data.auraSources;
};
StatusBars.prototype.SetEnabled = function(enabled, showRank, showExperience)
{
// Quick return if no change
if (enabled == this.enabled && showRank == this.showRank && showExperience == this.showExperience)
return;
this.enabled = enabled;
this.showRank = showRank;
this.showExperience = showExperience;
// Update the displayed sprites
this.RegenerateSprites();
};
StatusBars.prototype.AddAuraSource = function(source, auraName)
{
if (this.auraSources.has(source))
this.auraSources.get(source).push(auraName);
else
this.auraSources.set(source, [auraName]);
this.RegenerateSprites();
};
StatusBars.prototype.RemoveAuraSource = function(source, auraName)
{
let names = this.auraSources.get(source);
names.splice(names.indexOf(auraName), 1);
this.RegenerateSprites();
};
StatusBars.prototype.OnHealthChanged = function(msg)
{
if (this.enabled)
this.RegenerateSprites();
};
StatusBars.prototype.OnCapturePointsChanged = function(msg)
{
if (this.enabled)
this.RegenerateSprites();
};
StatusBars.prototype.OnResourceSupplyChanged = function(msg)
{
if (this.enabled)
this.RegenerateSprites();
};
StatusBars.prototype.OnPackProgressUpdate = function(msg)
{
if (this.enabled)
this.RegenerateSprites();
};
StatusBars.prototype.OnExperienceChanged = function()
{
if (this.enabled)
this.RegenerateSprites();
};
StatusBars.prototype.UpdateColor = function()
{
if (this.usedPlayerColors)
this.RegenerateSprites();
};
+StatusBars.prototype.OnPlayerColorChanged = function(msg)
+{
+ if (this.enabled)
+ this.RegenerateSprites();
+};
+
StatusBars.prototype.RegenerateSprites = function()
{
let cmpOverlayRenderer = Engine.QueryInterface(this.entity, IID_OverlayRenderer);
cmpOverlayRenderer.Reset();
let yoffset = 0;
for (let sprite of this.Sprites)
yoffset += this["Add" + sprite](cmpOverlayRenderer, yoffset);
};
// Internal helper functions
/**
* Generic piece of code to add a bar.
*/
StatusBars.prototype.AddBar = function(cmpOverlayRenderer, yoffset, type, amount, heightMultiplier = 1)
{
// Size of health bar (in world-space units)
let width = +this.template.BarWidth;
let height = +this.template.BarHeight * heightMultiplier;
// World-space offset from the unit's position
let offset = { "x": 0, "y": +this.template.HeightOffset, "z": 0 };
// background
cmpOverlayRenderer.AddSprite(
"art/textures/ui/session/icons/" + type + "_bg.png",
{ "x": -width / 2, "y": yoffset },
{ "x": width / 2, "y": height + yoffset },
offset,
g_NaturalColor
);
// foreground
cmpOverlayRenderer.AddSprite(
"art/textures/ui/session/icons/" + type + "_fg.png",
{ "x": -width / 2, "y": yoffset },
{ "x": width * (amount - 0.5), "y": height + yoffset },
offset,
g_NaturalColor
);
return height * 1.2;
};
StatusBars.prototype.AddExperienceBar = function(cmpOverlayRenderer, yoffset)
{
if (!this.enabled || !this.showExperience)
return 0;
let cmpPromotion = Engine.QueryInterface(this.entity, IID_Promotion);
if (!cmpPromotion || !cmpPromotion.GetCurrentXp() || !cmpPromotion.GetRequiredXp())
return 0;
return this.AddBar(cmpOverlayRenderer, yoffset, "pack", cmpPromotion.GetCurrentXp() / cmpPromotion.GetRequiredXp(), 2/3);
};
StatusBars.prototype.AddPackBar = function(cmpOverlayRenderer, yoffset)
{
if (!this.enabled)
return 0;
let cmpPack = Engine.QueryInterface(this.entity, IID_Pack);
if (!cmpPack || !cmpPack.IsPacking())
return 0;
return this.AddBar(cmpOverlayRenderer, yoffset, "pack", cmpPack.GetProgress());
};
StatusBars.prototype.AddHealthBar = function(cmpOverlayRenderer, yoffset)
{
if (!this.enabled)
return 0;
let cmpHealth = QueryMiragedInterface(this.entity, IID_Health);
if (!cmpHealth || cmpHealth.GetHitpoints() <= 0)
return 0;
return this.AddBar(cmpOverlayRenderer, yoffset, "health", cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints());
};
StatusBars.prototype.AddResourceSupplyBar = function(cmpOverlayRenderer, yoffset)
{
if (!this.enabled)
return 0;
let cmpResourceSupply = QueryMiragedInterface(this.entity, IID_ResourceSupply);
if (!cmpResourceSupply)
return 0;
let value = cmpResourceSupply.IsInfinite() ? 1 : cmpResourceSupply.GetCurrentAmount() / cmpResourceSupply.GetMaxAmount();
return this.AddBar(cmpOverlayRenderer, yoffset, "supply", value);
};
StatusBars.prototype.AddCaptureBar = function(cmpOverlayRenderer, yoffset)
{
if (!this.enabled)
return 0;
let cmpCapturable = QueryMiragedInterface(this.entity, IID_Capturable);
if (!cmpCapturable)
return 0;
let cmpOwnership = QueryMiragedInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
return 0;
let owner = cmpOwnership.GetOwner();
if (owner == INVALID_PLAYER)
return 0;
this.usedPlayerColors = true;
let cp = cmpCapturable.GetCapturePoints();
// Size of health bar (in world-space units)
let width = +this.template.BarWidth;
let height = +this.template.BarHeight;
// World-space offset from the unit's position
let offset = { "x": 0, "y": +this.template.HeightOffset, "z": 0 };
let setCaptureBarPart = function(playerID, startSize)
{
let c = QueryPlayerIDInterface(playerID).GetDisplayedColor();
let strColor = (c.r * 255) + " " + (c.g * 255) + " " + (c.b * 255) + " 255";
let size = width * cp[playerID] / cmpCapturable.GetMaxCapturePoints();
cmpOverlayRenderer.AddSprite(
"art/textures/ui/session/icons/capture_bar.png",
{ "x": startSize, "y": yoffset },
{ "x": startSize + size, "y": height + yoffset },
offset,
strColor
);
return size + startSize;
};
// First handle the owner's points, to keep those points on the left for clarity
let size = setCaptureBarPart(owner, -width / 2);
for (let i in cp)
if (i != owner && cp[i] > 0)
size = setCaptureBarPart(i, size);
return height * 1.2;
};
StatusBars.prototype.AddAuraIcons = function(cmpOverlayRenderer, yoffset)
{
let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
let sources = cmpGuiInterface.GetEntitiesWithStatusBars().filter(e => this.auraSources.has(e) && this.auraSources.get(e).length);
if (!sources.length)
return 0;
let iconSet = new Set();
for (let ent of sources)
{
let cmpAuras = Engine.QueryInterface(ent, IID_Auras);
if (!cmpAuras) // probably the ent just died
continue;
for (let name of this.auraSources.get(ent))
iconSet.add(cmpAuras.GetOverlayIcon(name));
}
// World-space offset from the unit's position
let offset = { "x": 0, "y": +this.template.HeightOffset + yoffset, "z": 0 };
let iconSize = +this.template.BarWidth / 2;
let xoffset = -iconSize * (iconSet.size - 1) * 0.6;
for (let icon of iconSet)
{
cmpOverlayRenderer.AddSprite(
icon,
{ "x": xoffset - iconSize / 2, "y": yoffset },
{ "x": xoffset + iconSize / 2, "y": iconSize + yoffset },
offset,
g_NaturalColor
);
xoffset += iconSize * 1.2;
}
return iconSize + this.template.BarHeight / 2;
};
StatusBars.prototype.AddRankIcon = function(cmpOverlayRenderer, yoffset)
{
if (!this.enabled || !this.showRank)
return 0;
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (!cmpIdentity || !cmpIdentity.GetRank())
return 0;
let iconSize = +this.template.BarWidth / 2;
cmpOverlayRenderer.AddSprite(
"art/textures/ui/session/icons/ranks/" + cmpIdentity.GetRank() + ".png",
{ "x": -iconSize / 2, "y": yoffset },
{ "x": iconSize / 2, "y": iconSize + yoffset },
{ "x": 0, "y": +this.template.HeightOffset + 0.1, "z": 0 },
g_NaturalColor);
return iconSize + this.template.BarHeight / 2;
};
Engine.RegisterComponentType(IID_StatusBars, "StatusBars", StatusBars);
Index: ps/trunk/source/simulation2/components/CCmpMinimap.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpMinimap.cpp (revision 22401)
+++ ps/trunk/source/simulation2/components/CCmpMinimap.cpp (revision 22402)
@@ -1,241 +1,243 @@
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpMinimap.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/MessageTypes.h"
#include "graphics/Color.h"
class CCmpMinimap : public ICmpMinimap
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Deserialized);
componentManager.SubscribeToMessageType(MT_PositionChanged);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+ componentManager.SubscribeToMessageType(MT_PlayerColorChanged);
componentManager.SubscribeToMessageType(MT_MinimapPing);
}
DEFAULT_COMPONENT_ALLOCATOR(Minimap)
// Template state:
bool m_UsePlayerColor;
u8 m_R, m_G, m_B; // static template state if m_UsePlayerColor false; dynamic state if true
// Dynamic state:
bool m_Active;
entity_pos_t m_X, m_Z; // cache the latest position for more efficient rendering; only valid when m_Active true
// Not serialized (based on renderer timing):
// TODO: eventually ping state should be serialized and tied into simulation time, but currently lag causes too many problems
double m_PingEndTime;
bool m_IsPinging;
static std::string GetSchema()
{
return
""
""
"food"
"wood"
"stone"
"metal"
"structure"
"unit"
"support"
"hero"
""
""
""
""
""
"0255"
""
""
"0255"
""
""
"0255"
""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
m_Active = true;
m_IsPinging = false;
m_PingEndTime = 0.0;
const CParamNode& color = paramNode.GetChild("Color");
if (color.IsOk())
{
m_UsePlayerColor = false;
m_R = (u8)color.GetChild("@r").ToInt();
m_G = (u8)color.GetChild("@g").ToInt();
m_B = (u8)color.GetChild("@b").ToInt();
}
else
{
m_UsePlayerColor = true;
// Choose a bogus color which will get replaced once we have an owner
m_R = 255;
m_G = 0;
m_B = 255;
}
}
virtual void Deinit()
{
}
template
void SerializeCommon(S& serialize)
{
serialize.Bool("active", m_Active);
if (m_Active)
{
serialize.NumberFixed_Unbounded("x", m_X);
serialize.NumberFixed_Unbounded("z", m_Z);
}
}
virtual void Serialize(ISerializer& serialize)
{
SerializeCommon(serialize);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
SerializeCommon(deserialize);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_PositionChanged:
{
const CMessagePositionChanged& data = static_cast (msg);
if (data.inWorld)
{
m_Active = true;
m_X = data.x;
m_Z = data.z;
}
else
{
m_Active = false;
}
break;
}
case MT_Deserialized:
case MT_OwnershipChanged:
+ case MT_PlayerColorChanged:
{
UpdateColor();
break;
}
case MT_MinimapPing:
{
CmpPtr cmpOwnership(GetSimContext(), GetEntityId());
if (!cmpOwnership || cmpOwnership->GetOwner() != (player_id_t)GetSimContext().GetCurrentDisplayedPlayer())
break;
// This depends on the viewing player, so don't alter the synchronized simulation state
m_IsPinging = true;
m_PingEndTime = 0.0;
break;
}
}
}
virtual bool GetRenderData(u8& r, u8& g, u8& b, entity_pos_t& x, entity_pos_t& z) const
{
if (!m_Active)
return false;
r = m_R;
g = m_G;
b = m_B;
x = m_X;
z = m_Z;
return true;
}
virtual bool CheckPing(double currentTime, double pingDuration)
{
if (!m_Active || !m_IsPinging)
return false;
// We're currently pinging
if (m_PingEndTime == 0.0)
m_PingEndTime = currentTime + pingDuration;
else if (currentTime > m_PingEndTime)
{
m_IsPinging = false;
m_PingEndTime = 0;
}
return m_IsPinging;
}
virtual void UpdateColor()
{
if (!m_UsePlayerColor)
return;
CmpPtr cmpOwnership(GetEntityHandle());
if (!cmpOwnership)
return;
player_id_t owner = cmpOwnership->GetOwner();
if (owner == INVALID_PLAYER)
return;
CmpPtr cmpPlayerManager(GetSystemEntity());
if (!cmpPlayerManager)
return;
CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(owner));
if (!cmpPlayer)
return;
CColor color = cmpPlayer->GetDisplayedColor();
m_R = (u8) (color.r * 255);
m_G = (u8) (color.g * 255);
m_B = (u8) (color.b * 255);
}
};
REGISTER_COMPONENT_TYPE(Minimap)
Index: ps/trunk/source/simulation2/components/CCmpRangeOverlayRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRangeOverlayRenderer.cpp (revision 22401)
+++ ps/trunk/source/simulation2/components/CCmpRangeOverlayRenderer.cpp (revision 22402)
@@ -1,228 +1,230 @@
/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ICmpRangeOverlayRenderer.h"
#include "graphics/Overlay.h"
#include "graphics/TextureManager.h"
#include "renderer/Renderer.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/system/Component.h"
class CCmpRangeOverlayRenderer : public ICmpRangeOverlayRenderer
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Deserialized);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+ componentManager.SubscribeToMessageType(MT_PlayerColorChanged);
}
DEFAULT_COMPONENT_ALLOCATOR(RangeOverlayRenderer)
CCmpRangeOverlayRenderer() : m_RangeOverlayData()
{
m_Color = CColor(0.f, 0.f, 0.f, 1.f);
}
~CCmpRangeOverlayRenderer()
{
for (const RangeOverlayData& rangeOverlay : m_RangeOverlayData)
delete rangeOverlay.second;
}
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_EnabledInterpolate = false;
m_EnabledRenderSubmit = false;
m_Enabled = false;
UpdateMessageSubscriptions();
}
virtual void Deinit() { }
virtual void Serialize(ISerializer& UNUSED(serialize))
{
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
void ResetRangeOverlays()
{
for (const RangeOverlayData& rangeOverlay : m_RangeOverlayData)
delete rangeOverlay.second;
m_RangeOverlayData.clear();
UpdateMessageSubscriptions();
m_Enabled = false;
}
virtual void AddRangeOverlay(float radius, const std::string& texture, const std::string& textureMask, float thickness)
{
if (!CRenderer::IsInitialised())
return;
SOverlayDescriptor rangeOverlayDescriptor;
SOverlayTexturedLine* rangeOverlay = nullptr;
rangeOverlayDescriptor.m_Radius = radius;
rangeOverlayDescriptor.m_LineTexture = CStrIntern(TEXTUREBASEPATH + texture);
rangeOverlayDescriptor.m_LineTextureMask = CStrIntern(TEXTUREBASEPATH + textureMask);
rangeOverlayDescriptor.m_LineThickness = thickness;
m_RangeOverlayData.push_back({ rangeOverlayDescriptor, rangeOverlay });
m_Enabled = true;
UpdateMessageSubscriptions();
}
void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast (msg);
for (RangeOverlayData& rangeOverlay : m_RangeOverlayData)
{
delete rangeOverlay.second;
rangeOverlay.second = new SOverlayTexturedLine;
UpdateRangeOverlay(&rangeOverlay.first, *rangeOverlay.second, msgData.offset);
}
UpdateMessageSubscriptions();
break;
}
case MT_Deserialized:
case MT_OwnershipChanged:
+ case MT_PlayerColorChanged:
{
UpdateColor();
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector);
break;
}
}
}
virtual void UpdateColor()
{
CmpPtr cmpOwnership(GetEntityHandle());
if (!cmpOwnership)
return;
player_id_t owner = cmpOwnership->GetOwner();
if (owner == INVALID_PLAYER)
return;
CmpPtr cmpPlayerManager(GetSystemEntity());
if (!cmpPlayerManager)
return;
CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(owner));
if (!cmpPlayer)
return;
CColor color = cmpPlayer->GetDisplayedColor();
m_Color = color;
}
void UpdateMessageSubscriptions()
{
bool needInterpolate = false;
bool needRenderSubmit = false;
if (m_Enabled)
{
needInterpolate = true;
needRenderSubmit = true;
}
if (needInterpolate != m_EnabledInterpolate)
{
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needInterpolate);
m_EnabledInterpolate = needInterpolate;
m_Enabled = needInterpolate;
}
if (needRenderSubmit != m_EnabledRenderSubmit)
{
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRenderSubmit);
m_EnabledRenderSubmit = needRenderSubmit;
m_Enabled = needRenderSubmit;
}
}
void RenderSubmit(SceneCollector& collector)
{
if (!m_RangeOverlayData.size())
return;
for (const RangeOverlayData& rangeOverlay : m_RangeOverlayData)
if (rangeOverlay.second)
collector.Submit(rangeOverlay.second);
}
private:
void UpdateRangeOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset)
{
if (!CRenderer::IsInitialised())
return;
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return;
float rotY;
CVector2D origin;
cmpPosition->GetInterpolatedPosition2D(frameOffset, origin.X, origin.Y, rotY);
overlay.m_SimContext = &GetSimContext();
overlay.m_Color = m_Color;
overlay.CreateOverlayTexture(overlayDescriptor);
SimRender::ConstructTexturedLineCircle(overlay, origin, overlayDescriptor->m_Radius);
}
bool m_EnabledInterpolate;
bool m_EnabledRenderSubmit;
bool m_Enabled;
const char* TEXTUREBASEPATH = "art/textures/selection/";
typedef std::pair RangeOverlayData;
std::vector m_RangeOverlayData;
CColor m_Color;
};
REGISTER_COMPONENT_TYPE(RangeOverlayRenderer)