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)