Run Re-Do
This commit is contained in:
@@ -68,8 +68,20 @@ namespace ProjectM.Client
|
||||
// END-2: terminal win/loss banner (observes the replicated RunOutcome; latched server-side).
|
||||
VisualElement _runBanner;
|
||||
Label _runBannerText, _runBannerSub;
|
||||
// DR-042 C6a: the Aether ability-upgrade button (was U-key only) + its live affordability tint.
|
||||
Button _upgradeBtn;
|
||||
// Step 14 (expedition redesign): the choice-of-3 boon modal + the route-choice panel. Both are
|
||||
// observe-only readers of replicated state (BoonOffer via GhostOwnerIsLocal; RunInfo RouteOpt*); clicks
|
||||
// enqueue through the client send-systems' statics. Built lazily on first show.
|
||||
VisualElement _boonModal, _boonCardRow;
|
||||
VisualElement _routePanel, _routeBtnRow;
|
||||
Label _routeTitle;
|
||||
byte _boonShownFor; // last (Option0^Option1^Option2 ^ room) signature the modal was built for
|
||||
bool _boonModalBuilt, _routePanelBuilt;
|
||||
// Step 14 (meta shop): Staging-only permanent-upgrade shop (replicated MetaTierState + ledger Aether;
|
||||
// row clicks enqueue MetaSpendSendSystem.RequestPurchase — the server re-validates everything).
|
||||
VisualElement _metaPanel, _metaRowsHost;
|
||||
Label _metaShopTitle;
|
||||
bool _metaShopBuilt;
|
||||
int _metaShownFor; // last (class, tiers, aether) signature the shop rows were built for
|
||||
|
||||
|
||||
readonly List<VisualElement> _pips = new();
|
||||
@@ -171,41 +183,97 @@ namespace ProjectM.Client
|
||||
_cycleText.text = "";
|
||||
}
|
||||
|
||||
// ---- Location + gate hint (banner sub-line) ----
|
||||
var cam = Camera.main;
|
||||
// ---- Location line (banner sub-line) — Step 14: driven by the replicated RunInfo lifecycle FSM ----
|
||||
// (the old camera-X + walk-in-gate copy died with the gate; siege/final overrides below still win).
|
||||
var cam = Camera.main; // camera-X region signal still feeds downstream panels (atmosphere/threat)
|
||||
bool onExpedition = cam != null && cam.transform.position.x > ExpeditionRegionXMin;
|
||||
_locationText.text = onExpedition
|
||||
? "ON EXPEDITION - carve the frontier, then return"
|
||||
: finalSiege
|
||||
? "FINAL SIEGE - hold the Engine, this is the last stand"
|
||||
: siege
|
||||
? "DEFEND THE BASE - hold the line"
|
||||
: "MINE THE CRYSTALS - any attack harvests Ore, then BUILD";
|
||||
_locationText.style.color = onExpedition ? new Color(1f, 0.8f, 0.4f)
|
||||
: finalSiege ? new Color(1f, 0.3f, 0.25f)
|
||||
: siege ? new Color(1f, 0.55f, 0.4f) : new Color(0.6f, 0.95f, 0.7f);
|
||||
// DR-042 C7 (gate prompt) + C7b (objective readout): the expedition is the win-driver, so signpost it.
|
||||
// Reads the REPLICATED ExpeditionObjective summary (cross-region safe). Lower priority than the siege /
|
||||
// cold-turret / overrun overrides below, which still win.
|
||||
if (SystemAPI.TryGetSingleton<ExpeditionObjective>(out var obj))
|
||||
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo);
|
||||
SystemAPI.TryGetSingleton<ExpeditionObjective>(out var obj);
|
||||
if (haveRun && !siege && !finalSiege)
|
||||
{
|
||||
if (onExpedition)
|
||||
switch (runInfo.Lifecycle)
|
||||
{
|
||||
if (obj.State == ExpeditionObjectiveState.Cleared)
|
||||
{ _locationText.text = "ZONE CLEARED - return to base to claim"; _locationText.style.color = new Color(0.5f, 1f, 0.6f); }
|
||||
else if (obj.State == ExpeditionObjectiveState.Active)
|
||||
{ _locationText.text = "CLEAR THE ZONE - " + obj.Remaining + " enemies remaining"; _locationText.style.color = new Color(1f, 0.8f, 0.4f); }
|
||||
}
|
||||
else if (!siege)
|
||||
{
|
||||
if (obj.State == ExpeditionObjectiveState.Cleared)
|
||||
{ _locationText.text = "EXPEDITION CLEARED - return to claim your reward"; _locationText.style.color = new Color(0.5f, 1f, 0.6f); }
|
||||
else if (obj.State == ExpeditionObjectiveState.Active)
|
||||
{ _locationText.text = "EXPEDITION IN PROGRESS - " + obj.Remaining + " enemies remaining"; _locationText.style.color = new Color(1f, 0.8f, 0.4f); }
|
||||
else
|
||||
{ _locationText.text = "GO TO THE EXPEDITION GATE - clear a sortie to advance the Engine"; _locationText.style.color = new Color(0.55f, 0.85f, 1f); }
|
||||
case RunLifecycle.Staging:
|
||||
{
|
||||
// Client counts the party's replicated ready flags (send-to-all) for the N/M readout.
|
||||
int total = 0, readyCount = 0;
|
||||
foreach (var pr in SystemAPI.Query<RefRO<PlayerReady>>().WithAll<PlayerTag>())
|
||||
{
|
||||
total++;
|
||||
if (pr.ValueRO.Value != 0) readyCount++;
|
||||
}
|
||||
_locationText.text = "READY UP [T] - " + readyCount + "/" + Mathf.Max(total, 1)
|
||||
+ " ready - launch a run to advance the Engine";
|
||||
_locationText.style.color = new Color(0.55f, 0.85f, 1f);
|
||||
break;
|
||||
}
|
||||
case RunLifecycle.Launching:
|
||||
{
|
||||
uint nowTick = SystemAPI.TryGetSingleton<NetworkTime>(out var ntime) && ntime.ServerTick.IsValid
|
||||
? ntime.ServerTick.TickIndexForValidTick : 0u;
|
||||
int secs = runInfo.LaunchTick != 0 && nowTick != 0
|
||||
? Mathf.Max(0, (int)((runInfo.LaunchTick - nowTick) / 60u) + 1) : 0;
|
||||
_locationText.text = "LAUNCHING IN " + secs + " - un-ready [T] to abort";
|
||||
_locationText.style.color = new Color(1f, 0.9f, 0.4f);
|
||||
break;
|
||||
}
|
||||
case RunLifecycle.InRoom:
|
||||
{
|
||||
string room = "ROOM " + (runInfo.CurrentRoom + 1) + "/" + runInfo.RoomCount
|
||||
+ " " + RoomTypeLabel(runInfo.CurrentRoomType);
|
||||
_locationText.text = obj.State == ExpeditionObjectiveState.Active
|
||||
? room + " - " + obj.Remaining + " enemies remaining"
|
||||
: room + " - clear it to advance";
|
||||
_locationText.style.color = new Color(1f, 0.8f, 0.4f);
|
||||
break;
|
||||
}
|
||||
case RunLifecycle.RoomReward:
|
||||
_locationText.text = "ROOM CLEARED - choose your boon";
|
||||
_locationText.style.color = new Color(0.5f, 1f, 0.6f);
|
||||
break;
|
||||
case RunLifecycle.RouteSelect:
|
||||
_locationText.text = "CHOOSE YOUR PATH";
|
||||
_locationText.style.color = new Color(0.55f, 0.85f, 1f);
|
||||
break;
|
||||
case RunLifecycle.Returning:
|
||||
_locationText.text = "RETURNING HOME...";
|
||||
_locationText.style.color = new Color(0.7f, 0.9f, 1f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!haveRun)
|
||||
{
|
||||
_locationText.text = finalSiege
|
||||
? "FINAL SIEGE - hold the Engine, this is the last stand"
|
||||
: siege ? "DEFEND THE BASE - hold the line"
|
||||
: "MINE THE CRYSTALS - any attack harvests Ore, then BUILD";
|
||||
_locationText.style.color = finalSiege ? new Color(1f, 0.3f, 0.25f)
|
||||
: siege ? new Color(1f, 0.55f, 0.4f) : new Color(0.6f, 0.95f, 0.7f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_locationText.text = finalSiege
|
||||
? "FINAL SIEGE - hold the Engine, this is the last stand"
|
||||
: "DEFEND THE BASE - hold the line";
|
||||
_locationText.style.color = finalSiege ? new Color(1f, 0.3f, 0.25f) : new Color(1f, 0.55f, 0.4f);
|
||||
}
|
||||
|
||||
|
||||
// ---- Step 14: the choice-of-3 boon modal + the route-choice panel (observe replicated state; the
|
||||
// card/button clicks enqueue through the client send-systems' statics) ----
|
||||
BoonOffer localOffer = default;
|
||||
bool hasOffer = false;
|
||||
foreach (var off in SystemAPI.Query<RefRO<BoonOffer>>().WithAll<PlayerTag, GhostOwnerIsLocal>())
|
||||
{
|
||||
localOffer = off.ValueRO;
|
||||
hasOffer = true;
|
||||
break;
|
||||
}
|
||||
BlobAssetReference<BoonCatalogBlob> boonPool = default;
|
||||
if (SystemAPI.TryGetSingleton<BoonCatalog>(out var bcat))
|
||||
boonPool = bcat.Value;
|
||||
UpdateBoonModal(localOffer, hasOffer && localOffer.Pending == 1, boonPool);
|
||||
UpdateRoutePanel(haveRun ? runInfo : default);
|
||||
|
||||
// ---- Goal (hex-pip meter, or a continuous bar for large targets) ----
|
||||
if (SystemAPI.TryGetSingleton<GoalProgress>(out var goal))
|
||||
@@ -255,9 +323,31 @@ namespace ProjectM.Client
|
||||
_oreNum.text = ore.ToString();
|
||||
_bioNum.text = bio.ToString();
|
||||
_chargeNum.text = charge.ToString();
|
||||
|
||||
// ---- Step 14 (meta shop): Staging-only permanent-upgrade shop for the LOCAL class. Class derives from
|
||||
// the replicated AbilityRef (tracks the dev class-switch; PlayerClass is server-only); tiers from the
|
||||
// replicated MetaTierState record on the director ghost; Aether from the ledger read above. ----
|
||||
byte localClass = ClassTraits.WarriorClass;
|
||||
bool haveLocalPlayer = false;
|
||||
foreach (var ar in SystemAPI.Query<RefRO<AbilityRef>>().WithAll<PlayerTag, GhostOwnerIsLocal>())
|
||||
{
|
||||
localClass = ClassTraits.ClassForAbility(ar.ValueRO.Id);
|
||||
haveLocalPlayer = true;
|
||||
break;
|
||||
}
|
||||
bool metaShow = false;
|
||||
BlobAssetReference<MetaUpgradeCatalogBlob> metaPool = default;
|
||||
DynamicBuffer<MetaTierState> metaRecord = default;
|
||||
if (haveRun && runInfo.Lifecycle == RunLifecycle.Staging && haveLocalPlayer && !siege
|
||||
&& SystemAPI.TryGetSingleton<MetaUpgradeCatalog>(out var metaCat) && metaCat.Value.IsCreated
|
||||
&& SystemAPI.TryGetSingletonBuffer<MetaTierState>(out metaRecord, true))
|
||||
{
|
||||
metaPool = metaCat.Value;
|
||||
metaShow = true;
|
||||
}
|
||||
UpdateMetaShop(metaShow, localClass, aether, metaPool, metaRecord);
|
||||
// DR-042 C6a: dim the Aether upgrade button when it isn't affordable (cost is a compile-time const).
|
||||
if (_upgradeBtn != null)
|
||||
_upgradeBtn.style.opacity = aether >= Tuning.AbilityUpgradeCostAmount ? 1f : 0.5f;
|
||||
// (Step 11: upgrade-button affordability tint retired with the button.)
|
||||
// EB-2 quiet-turret cue (GLOBAL, not per-turret, so the deterministic Charge split never reads as one
|
||||
// broken turret): a dry base during a siege tells the player to build a Fabricator.
|
||||
if (siege && charge == 0 && !onExpedition)
|
||||
@@ -848,9 +938,8 @@ namespace ProjectM.Client
|
||||
strip.Add(ResourceChip(null, ChargeViolet, "0", out _chargeNum, 26, 20)); // EB-2 turret ammo (flat violet, no icon)
|
||||
// DR-042 C6a: the only Aether sink (ability-damage upgrade) gets a visible, clickable button (was U-key
|
||||
// only). The Button element handles its own picking even though the HUD root Ignores clicks.
|
||||
_upgradeBtn = MenuUi.Button("UPGRADE DMG (" + Tuning.AbilityUpgradeCostAmount + " AETHER)", BuildSendSystem.UpgradeAbility);
|
||||
_upgradeBtn.style.marginLeft = 18;
|
||||
strip.Add(_upgradeBtn);
|
||||
// (Step 11: the Aether UPGRADE-DMG button was RETIRED with AbilityUpgradeRequest — the choice-of-3
|
||||
// boon modal + the base meta-shop (Step 14) replace it.)
|
||||
|
||||
root.Add(strip);
|
||||
}
|
||||
@@ -1138,5 +1227,243 @@ namespace ProjectM.Client
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Step 14: boon modal + route panel (lazy-built overlays; clicks -> client send statics) ====
|
||||
|
||||
byte _routeShownFor; // last (room ^ options) signature the route buttons were built for
|
||||
|
||||
static string RoomTypeLabel(byte roomType) => roomType == RoomTypeId.Boss ? "[BOSS]"
|
||||
: roomType == RoomTypeId.Elite ? "[ELITE]"
|
||||
: roomType == RoomTypeId.Reward ? "[REWARD]" : "[COMBAT]";
|
||||
|
||||
void UpdateBoonModal(BoonOffer offer, bool show, BlobAssetReference<BoonCatalogBlob> pool)
|
||||
{
|
||||
if (!show || !pool.IsCreated)
|
||||
{
|
||||
if (_boonModal != null) _boonModal.style.display = DisplayStyle.None;
|
||||
_boonShownFor = 0;
|
||||
return;
|
||||
}
|
||||
var root = _doc != null ? _doc.rootVisualElement : null;
|
||||
if (root == null) return;
|
||||
if (!_boonModalBuilt)
|
||||
{
|
||||
BuildBoonModal(root);
|
||||
_boonModalBuilt = true;
|
||||
}
|
||||
|
||||
// Rebuild the three cards only when the offer actually changes (a new room's deal).
|
||||
byte sig = (byte)(offer.Option0 ^ (offer.Option1 * 3) ^ (offer.Option2 * 7));
|
||||
if (sig == 0) sig = 1;
|
||||
if (_boonShownFor != sig)
|
||||
{
|
||||
_boonCardRow.Clear();
|
||||
ref var defs = ref pool.Value;
|
||||
for (byte k = 0; k < 3; k++)
|
||||
{
|
||||
byte id = k == 2 ? offer.Option2 : k == 1 ? offer.Option1 : offer.Option0;
|
||||
int idx = BoonMath.FindDef(ref defs, id);
|
||||
string title = idx >= 0 ? defs.Defs[idx].Name.ToString() : ("BOON " + id);
|
||||
string desc = idx >= 0 ? defs.Defs[idx].Desc.ToString() : "";
|
||||
byte pick = k; // capture a COPY into the closure, never the loop variable
|
||||
var card = MenuUi.Button(title + "\n" + desc, () => BoonSendSystem.PickBoon(pick));
|
||||
card.style.width = 200;
|
||||
card.style.height = 96;
|
||||
card.style.marginLeft = 8;
|
||||
card.style.marginRight = 8;
|
||||
card.style.whiteSpace = WhiteSpace.Normal;
|
||||
_boonCardRow.Add(card);
|
||||
}
|
||||
_boonShownFor = sig;
|
||||
}
|
||||
_boonModal.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
void BuildBoonModal(VisualElement root)
|
||||
{
|
||||
_boonModal = new VisualElement { pickingMode = PickingMode.Ignore };
|
||||
_boonModal.style.position = Position.Absolute;
|
||||
_boonModal.style.left = 0; _boonModal.style.right = 0;
|
||||
_boonModal.style.top = 0; _boonModal.style.bottom = 0;
|
||||
_boonModal.style.alignItems = Align.Center;
|
||||
_boonModal.style.justifyContent = Justify.Center;
|
||||
_boonModal.style.display = DisplayStyle.None;
|
||||
|
||||
var box = new VisualElement();
|
||||
box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.96f);
|
||||
box.style.borderTopLeftRadius = 10; box.style.borderTopRightRadius = 10;
|
||||
box.style.borderBottomLeftRadius = 10; box.style.borderBottomRightRadius = 10;
|
||||
box.style.paddingLeft = 18; box.style.paddingRight = 18;
|
||||
box.style.paddingTop = 14; box.style.paddingBottom = 16;
|
||||
box.style.alignItems = Align.Center;
|
||||
|
||||
var title = new Label("ROOM CLEARED — CHOOSE A BOON");
|
||||
title.style.color = new Color(0.6f, 1f, 0.7f);
|
||||
title.style.fontSize = 18;
|
||||
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
title.style.marginBottom = 12;
|
||||
box.Add(title);
|
||||
|
||||
_boonCardRow = new VisualElement();
|
||||
_boonCardRow.style.flexDirection = FlexDirection.Row;
|
||||
box.Add(_boonCardRow);
|
||||
|
||||
_boonModal.Add(box);
|
||||
root.Add(_boonModal);
|
||||
}
|
||||
|
||||
void UpdateRoutePanel(RunInfo runInfo)
|
||||
{
|
||||
// Keyed on the LIFECYCLE (never RouteOptionCount alone — the review's D-F6 criterion).
|
||||
bool show = runInfo.Lifecycle == RunLifecycle.RouteSelect && runInfo.RouteOptionCount > 0;
|
||||
if (!show)
|
||||
{
|
||||
if (_routePanel != null) _routePanel.style.display = DisplayStyle.None;
|
||||
_routeShownFor = 0;
|
||||
return;
|
||||
}
|
||||
var root = _doc != null ? _doc.rootVisualElement : null;
|
||||
if (root == null) return;
|
||||
if (!_routePanelBuilt)
|
||||
{
|
||||
BuildRoutePanel(root);
|
||||
_routePanelBuilt = true;
|
||||
}
|
||||
|
||||
byte sig = (byte)((runInfo.CurrentRoom + 1) ^ (runInfo.RouteOpt0Type * 3)
|
||||
^ (runInfo.RouteOpt1Type * 5) ^ (runInfo.RouteOpt2Type * 7) ^ runInfo.RouteOptionCount);
|
||||
if (sig == 0) sig = 1;
|
||||
if (_routeShownFor != sig)
|
||||
{
|
||||
_routeBtnRow.Clear();
|
||||
for (byte k = 0; k < runInfo.RouteOptionCount && k < 3; k++)
|
||||
{
|
||||
byte type = k == 2 ? runInfo.RouteOpt2Type : k == 1 ? runInfo.RouteOpt1Type : runInfo.RouteOpt0Type;
|
||||
byte pick = k; // closure copy
|
||||
var b = MenuUi.Button("→ " + RoomTypeLabel(type), () => RouteSendSystem.PickRoute(pick));
|
||||
b.style.marginLeft = 6;
|
||||
b.style.marginRight = 6;
|
||||
_routeBtnRow.Add(b);
|
||||
}
|
||||
_routeTitle.text = "CHOOSE YOUR PATH — room " + (runInfo.CurrentRoom + 2) + "/" + runInfo.RoomCount;
|
||||
_routeShownFor = sig;
|
||||
}
|
||||
_routePanel.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
void BuildRoutePanel(VisualElement root)
|
||||
{
|
||||
_routePanel = new VisualElement { pickingMode = PickingMode.Ignore };
|
||||
_routePanel.style.position = Position.Absolute;
|
||||
_routePanel.style.left = 0; _routePanel.style.right = 0;
|
||||
_routePanel.style.bottom = 120;
|
||||
_routePanel.style.alignItems = Align.Center;
|
||||
_routePanel.style.display = DisplayStyle.None;
|
||||
|
||||
var box = new VisualElement();
|
||||
box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.94f);
|
||||
box.style.borderTopLeftRadius = 10; box.style.borderTopRightRadius = 10;
|
||||
box.style.borderBottomLeftRadius = 10; box.style.borderBottomRightRadius = 10;
|
||||
box.style.paddingLeft = 16; box.style.paddingRight = 16;
|
||||
box.style.paddingTop = 10; box.style.paddingBottom = 12;
|
||||
box.style.alignItems = Align.Center;
|
||||
|
||||
_routeTitle = new Label("CHOOSE YOUR PATH");
|
||||
_routeTitle.style.color = new Color(0.55f, 0.85f, 1f);
|
||||
_routeTitle.style.fontSize = 15;
|
||||
_routeTitle.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
_routeTitle.style.marginBottom = 8;
|
||||
box.Add(_routeTitle);
|
||||
|
||||
_routeBtnRow = new VisualElement();
|
||||
_routeBtnRow.style.flexDirection = FlexDirection.Row;
|
||||
box.Add(_routeBtnRow);
|
||||
|
||||
_routePanel.Add(box);
|
||||
root.Add(_routePanel);
|
||||
}
|
||||
|
||||
void UpdateMetaShop(bool show, byte classId, int aether,
|
||||
BlobAssetReference<MetaUpgradeCatalogBlob> pool, DynamicBuffer<MetaTierState> record)
|
||||
{
|
||||
if (!show)
|
||||
{
|
||||
if (_metaPanel != null) _metaPanel.style.display = DisplayStyle.None;
|
||||
_metaShownFor = 0;
|
||||
return;
|
||||
}
|
||||
var root = _doc != null ? _doc.rootVisualElement : null;
|
||||
if (root == null) return;
|
||||
if (!_metaShopBuilt)
|
||||
{
|
||||
BuildMetaShop(root);
|
||||
_metaShopBuilt = true;
|
||||
}
|
||||
|
||||
// Rebuild the rows only when class / owned tiers / affordability actually change (Staging-only, <=8 rows).
|
||||
int sig = classId * 131 ^ aether * 31;
|
||||
for (int i = 0; i < record.Length; i++)
|
||||
sig ^= (record[i].ClassId * 7 + record[i].UpgradeId * 13 + record[i].Tier) * (i + 3);
|
||||
if (sig == 0) sig = 1;
|
||||
if (_metaShownFor != sig)
|
||||
{
|
||||
_metaRowsHost.Clear();
|
||||
_metaShopTitle.text = (classId == ClassTraits.RangerClass ? "RANGER" : "WARRIOR")
|
||||
+ " PERMANENT UPGRADES - AETHER " + aether;
|
||||
ref var defs = ref pool.Value;
|
||||
byte classBit = BoonMath.MaskFor(classId);
|
||||
for (int d = 0; d < defs.Defs.Length; d++)
|
||||
{
|
||||
if ((defs.Defs[d].ClassMask & classBit) == 0) continue;
|
||||
byte id = defs.Defs[d].Id;
|
||||
byte owned = MetaMath.TierOf(record, classId, id);
|
||||
bool maxed = owned >= defs.Defs[d].MaxTier;
|
||||
int cost = MetaMath.CostForTier(in defs.Defs[d], owned);
|
||||
string label = defs.Defs[d].Name.ToString() + " [" + owned + "/" + defs.Defs[d].MaxTier + "]"
|
||||
+ (maxed ? " MAXED" : " - " + cost + " Aether")
|
||||
+ "\n" + defs.Defs[d].Desc.ToString();
|
||||
byte buyId = id; // closure copy, never the loop variable
|
||||
var row = MenuUi.Button(label, () => MetaSpendSendSystem.RequestPurchase(buyId));
|
||||
row.style.width = 290;
|
||||
row.style.marginBottom = 4;
|
||||
row.style.whiteSpace = WhiteSpace.Normal;
|
||||
row.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
row.SetEnabled(!maxed && aether >= cost); // honest UI; the server re-validates everything anyway
|
||||
_metaRowsHost.Add(row);
|
||||
}
|
||||
_metaShownFor = sig;
|
||||
}
|
||||
_metaPanel.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
void BuildMetaShop(VisualElement root)
|
||||
{
|
||||
_metaPanel = new VisualElement { pickingMode = PickingMode.Ignore };
|
||||
_metaPanel.style.position = Position.Absolute;
|
||||
_metaPanel.style.right = 12;
|
||||
_metaPanel.style.top = Length.Percent(22);
|
||||
_metaPanel.style.alignItems = Align.FlexEnd;
|
||||
_metaPanel.style.display = DisplayStyle.None;
|
||||
|
||||
var box = new VisualElement();
|
||||
box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.92f);
|
||||
box.style.borderTopLeftRadius = 10; box.style.borderTopRightRadius = 10;
|
||||
box.style.borderBottomLeftRadius = 10; box.style.borderBottomRightRadius = 10;
|
||||
box.style.paddingLeft = 12; box.style.paddingRight = 12;
|
||||
box.style.paddingTop = 10; box.style.paddingBottom = 10;
|
||||
|
||||
_metaShopTitle = new Label("PERMANENT UPGRADES");
|
||||
_metaShopTitle.style.color = AetherCyan;
|
||||
_metaShopTitle.style.fontSize = 14;
|
||||
_metaShopTitle.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
_metaShopTitle.style.marginBottom = 8;
|
||||
box.Add(_metaShopTitle);
|
||||
|
||||
_metaRowsHost = new VisualElement();
|
||||
box.Add(_metaRowsHost);
|
||||
|
||||
_metaPanel.Add(box);
|
||||
root.Add(_metaPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,30 @@ namespace ProjectM.Client
|
||||
Color expFog = cfg != null ? cfg.ExpeditionFogColor : new Color(0.851f, 0.549f, 0.227f, 1f);
|
||||
float expDen = cfg != null ? cfg.ExpeditionFogDensity : 0.010f;
|
||||
Color expAmb = cfg != null ? cfg.ExpeditionAmbientSky : new Color(0.910f, 0.769f, 0.604f, 1f);
|
||||
// Step 14 (expedition redesign): the EXPEDITION-side palette keys on the replicated per-room biome
|
||||
// (RunInfo.CurrentBiome) so every room re-themes — the camera-X blend still owns the base↔run fade.
|
||||
// Palettes are code consts (cosmetic; per-biome config knobs can layer on later without churn).
|
||||
if (SystemAPI.TryGetSingleton<ProjectM.Simulation.RunInfo>(out var runInfo)
|
||||
&& runInfo.Lifecycle != ProjectM.Simulation.RunLifecycle.Staging)
|
||||
{
|
||||
switch (runInfo.CurrentBiome)
|
||||
{
|
||||
case ProjectM.Simulation.RoomBiomeId.Meadow:
|
||||
expFog = new Color(0.62f, 0.82f, 0.62f, 1f); expDen = 0.008f;
|
||||
expAmb = new Color(0.68f, 0.88f, 0.70f, 1f);
|
||||
break;
|
||||
case ProjectM.Simulation.RoomBiomeId.Cavern:
|
||||
expFog = new Color(0.42f, 0.48f, 0.60f, 1f); expDen = 0.014f;
|
||||
expAmb = new Color(0.50f, 0.56f, 0.72f, 1f);
|
||||
break;
|
||||
case ProjectM.Simulation.RoomBiomeId.Blight:
|
||||
expFog = new Color(0.55f, 0.42f, 0.62f, 1f); expDen = 0.016f;
|
||||
expAmb = new Color(0.62f, 0.50f, 0.70f, 1f);
|
||||
break;
|
||||
// Arid keeps the config/default orange above.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float x = _cam.transform.position.x;
|
||||
float t = Mathf.Clamp01((x - (boundary - half)) / (2f * half));
|
||||
|
||||
Reference in New Issue
Block a user