using System; using System.Collections.Generic; using System.Linq; using System.Text; using ProjectI.Builds; using ProjectI.Combat; using ProjectI.Menu; using ProjectI.Runs; using UnityEngine; using UnityEngine.UIElements; namespace ProjectI.UI { public sealed class CoreRewardView : IDisposable { public VisualElement Panel { get; } public Label Heading { get; } public Label Status { get; } public Toggle Details { get; } public ScrollView CardScroll { get; } public VisualElement Cards { get; } public Button Confirm { get; } private readonly Label team,buildText,footerHint,offerTitle,remaining,localStatus,buildContext; private readonly Button inspect; private readonly ScrollView inspection; private readonly VisualElement installed; private readonly Action confirmAction,inspectionAction; private string inventoryIdentity=""; public bool InspectionOpen { get; private set; } public CoreRewardView(VisualElement parent,Action confirm,Action inspectBuild) { confirmAction=confirm;inspectionAction=inspectBuild; Panel=new VisualElement {name="upgradeChoicePanel"};Panel.AddToClassList("run-panel");Panel.AddToClassList("upgrade-choice-panel");Panel.AddToClassList("core-reward-shell");Panel.AddToClassList("hidden");parent.Add(Panel); var header=new VisualElement();header.AddToClassList("core-reward-header");Panel.Add(header); Label(header,"INDIE FPS ROGUELIKE / STAGE TRANSITION","core-reward-eyebrow"); Heading=Label(header,"Choose a core","run-heading");Label(header,"Stage transition / run-only upgrades","core-reward-subtitle"); var body=new VisualElement();body.AddToClassList("core-reward-body");Panel.Add(body); var main=new VisualElement();main.AddToClassList("core-reward-main");body.Add(main); var aside=new VisualElement();aside.AddToClassList("core-reward-team");body.Add(aside); Label(aside,"TEAM PROGRESS","core-reward-eyebrow");team=Label(aside,"","core-team-progress");team.name="rewardTeamProgress"; Label(aside,"You","core-team-owner");localStatus=Label(aside,"Choosing a core","core-team-local");localStatus.name="rewardLocalStatus"; Label(aside,"Travel begins when everyone has completed their choices.","core-reward-subtitle"); var offerRow=new VisualElement();offerRow.AddToClassList("core-offer-heading");main.Add(offerRow); offerTitle=Label(offerRow,"SELECT ONE CORE","core-reward-eyebrow");offerTitle.name="rewardOfferTitle";remaining=Label(offerRow,"","core-choice-count");remaining.name="rewardChoiceCount"; var toolbar=new VisualElement();toolbar.AddToClassList("core-reward-toolbar"); Details=new Toggle("Details / stack difference (or hold Shift)"){name="coreDetailsToggle"};toolbar.Add(Details); inspect=new Button(ToggleInspection){text="+ Inspect current build and installed cores",name="inspectRewardBuild"};toolbar.Insert(0,inspect); CardScroll=new ScrollView(ScrollViewMode.Vertical){name="upgradeCardsScroll"};CardScroll.AddToClassList("upgrade-card-scroll");main.Add(CardScroll); Cards=new VisualElement {name="upgradeCards"};Cards.AddToClassList("upgrade-cards");CardScroll.Add(Cards); buildContext=Label(main,"Your run abilities stay equipped. Only compatible cores enter the offer.","core-reward-context");buildContext.name="rewardEquippedContext";main.Add(toolbar); inspection=new ScrollView(ScrollViewMode.Vertical){name="rewardBuildInspection"};inspection.AddToClassList("core-reward-inspection");inspection.AddToClassList("hidden");main.Add(inspection); buildText=Label(inspection,"","core-build-text");Label(inspection,"INSTALLED CORES · current ranks","core-inspection-heading"); installed=new VisualElement();installed.AddToClassList("inspection-grid");inspection.Add(installed); var footer=new VisualElement();footer.AddToClassList("core-reward-footer");main.Add(footer);var footerCopy=new VisualElement();footerCopy.AddToClassList("core-footer-copy");footer.Add(footerCopy);footerHint=Label(footerCopy,"Inspecting a card does not spend the core.","core-reward-subtitle");footerHint.name="coreRewardHint"; Status=Label(footerCopy,"Select a card to preview its effect.","upgrade-choice-status");Status.AddToClassList("run-wrap"); Confirm=new Button(confirmAction){text="Confirm core",name="upgradeNext"};Confirm.AddToClassList("upgrade-next-button");Confirm.SetEnabled(false);footer.Add(Confirm); Panel.RegisterCallback(OnKeyDown); Panel.RegisterCallback(OnGeometry); } private static Label Label(VisualElement parent,string text,string css) {var label=new Label(text){pickingMode=PickingMode.Ignore};label.AddToClassList(css);parent.Add(label);return label;} public void BindHeader(string heading,string localStatus,int complete,int participants) {Heading.text=heading=="CHOOSE A CORE"?"Choose a core":heading=="CORE INSTALLED"?"Core installed":heading=="CHOOSE A CATEGORY"?"Choose a category":heading=="CHOICES COMPLETE"?"Choices complete":heading;Status.text=localStatus;team.text=$"{Math.Max(0,Math.Min(complete,participants))} / {participants} complete";} public void BindFooter(CoreOfferKind kind,bool exhausted=false) {footerHint.text=kind==CoreOfferKind.Category?(exhausted?"Keep this core for the next stage when no compatible award is available.":"Choosing a category awards one random compatible core immediately."):kind==CoreOfferKind.AwardReceipt?"This core is installed. Continue to finish reviewing it.":kind==CoreOfferKind.Specific?"Inspecting a card does not spend the core.":"Inspect your current build while waiting for the team.";} public void BindOfferContext(CoreOfferKind kind,UpgradeTier? tier,int pending,string equipped,string ownerStatus) {offerTitle.text=kind==CoreOfferKind.AwardReceipt?"INSTALLED CORE":kind==CoreOfferKind.None?(pending>0?"PREPARING NEXT CORE":"CHOICES COMPLETE"):(tier.HasValue?tier.Value.ToString().ToUpperInvariant()+" CORE · ":"")+(kind==CoreOfferKind.Category?"CHOOSE A CATEGORY":"SELECT ONE");remaining.text=pending==1?"1 choice remaining":Math.Max(0,pending)+" choices remaining";localStatus.text=ownerStatus;buildContext.text=string.IsNullOrEmpty(equipped)?"Your run abilities stay equipped. Only compatible cores enter the offer.":"Equipped: "+equipped+". Only compatible cores enter the offer.";} private void OnGeometry(GeometryChangedEvent value)=>Panel.EnableInClassList("core-reward-short",Panel.worldBound.height<600); private void ToggleInspection() {SetInspection(!InspectionOpen);if(InspectionOpen){inspectionAction?.Invoke();inspection.Focus();}else inspect.Focus();} private void SetInspection(bool open) {InspectionOpen=open;Panel.EnableInClassList("core-inspecting",open);inspection.EnableInClassList("hidden",!open);CardScroll.EnableInClassList("hidden",open);inspect.text=open?"− Inspect current build and installed cores [Esc]":"+ Inspect current build and installed cores";} private void OnKeyDown(KeyDownEvent value) {if(value.keyCode==KeyCode.Escape&&InspectionOpen){CloseInspection();value.StopImmediatePropagation();}} public void CloseInspection(){SetInspection(false);inspect.Focus();} public void OpenInspection(){SetInspection(true);inspection.Focus();} public void ResetInspection(){SetInspection(false);inventoryIdentity="";installed.Clear();buildText.text="";} public void BindInspection(PrototypePlayerCombat player,IReadOnlyList inventory) { if(player==null)return; var definitions=player.AbilityDefinitions.Where(d=>d!=null).ToDictionary(d=>d.id,d=>d); var text=new StringBuilder($"CURRENT RUN BUILD · read only\nHP {player.Vitals.CurrentHealth:0.#} / {player.Vitals.MaximumHealth:0.#} · shield {player.Vitals.Shield:0.#} / {player.Vitals.MaximumShield:0.#} · overshield {player.Vitals.Overshield:0.#}\nWalk {player.MovementSpeed:0.##} m/s · gun {player.AttackDamageMultiplier:0.##}× · ability {player.AbilityDamageMultiplier:0.##}×\nAbilities stay equipped during this run.\n"); foreach(LibrarySlot slot in Enum.GetValues(typeof(LibrarySlot))) { int id=AbilityLibraryState.EquippedId(player.Loadout,slot);if(!definitions.TryGetValue(id,out var d))continue; text.AppendLine($"\n{slot} · {d.displayName}\n{AbilityLibraryPresentation.Summary(d)}"); } string identity=player.Vitals.MaximumHealth+"|"+player.Vitals.MaximumShield+"|"+string.Join(";",(inventory??Array.Empty()).Select(x=>$"{x.UpgradeId},{x.AbilityId},{x.Rank}")); buildText.text=text.ToString();if(identity==inventoryIdentity)return;inventoryIdentity=identity;installed.Clear(); foreach(var entry in inventory??Array.Empty()) { AddInstalledTile(entry,new CoreDisplayContext(player.Vitals.MaximumHealth,player.Vitals.MaximumShield,CoreRewardPresentation.TargetLabel(entry.AbilityId,player.Loadout,definitions))); } if(installed.childCount==0)Label(installed,"No cores installed yet.","core-build-text"); } public void ShowInspectionContent(string summary,IReadOnlyList inventory,CoreDisplayContext context) { buildText.text=summary;installed.Clear(); foreach(var entry in inventory??Array.Empty()) {AddInstalledTile(entry,context);} if(installed.childCount==0)Label(installed,"No cores installed yet.","core-build-text"); } private void AddInstalledTile(UpgradeInventoryEntry entry,CoreDisplayContext context) { var tile=CoreInspectionPanel.CreateInstalledTile(entry,context);installed.Add(tile); var formatted=UpgradeCardFormatter.Format((UpgradeId)entry.UpgradeId,entry.Rank,context,CorePresentationMode.Installed); Label(tile,entry.Tier+" · rank "+entry.Rank+(entry.AbilityId>0?"\n"+context.AbilityName:""),"core-card-target"); var detail=Label(tile,formatted.Secondary+"\n"+formatted.Details,"core-installed-detail");detail.AddToClassList("hidden"); Action expand=()=>{detail.RemoveFromClassList("hidden");tile.schedule.Execute(()=>inspection.ScrollTo(tile)).ExecuteLater(1);}; tile.clicked+=expand;tile.RegisterCallback(_=>expand()); } public CompactCoreCard CreateCard(int id,UpgradeTier tier,long rank,bool awarded,string target,Action activate) { var view=new CompactCoreCard(id,tier,awarded?"Installed":"Select core",activate);view.EnableRewardPresentation(); view.SetRewardMetadata(CoreRewardPresentation.RankTransition(rank,awarded),target=="Global core"?"Applies · your build":"Target · "+target); if(awarded){view.Action.SetEnabled(false);view.Action.style.display=DisplayStyle.None;} view.Action.RegisterCallback(_=>CardScroll.ScrollTo(view.Action));Cards.Add(view);return view; } public void ShowSpecific(UpgradeOffer offer,CoreDisplayContext context,Action select) { BindFooter(CoreOfferKind.Specific); Cards.Clear();foreach(var card in offer.Cards??Array.Empty()) {var view=CreateCard(card.UpgradeId,card.Tier,card.CurrentRank,false,context.AbilityName,()=>select(card));view.Bind(UpgradeCardFormatter.Format((UpgradeId)card.UpgradeId,card.CurrentRank,context,CorePresentationMode.Next),Details.value);} } public void ShowCategory(CoreCategoryOffer offer,Action select,Action defer) {BindFooter(CoreOfferKind.Category,offer.Exhausted);Cards.Clear();Cards.Add(new CoreCategoryView(offer,select,defer,true));Confirm.style.display=DisplayStyle.None;Details.style.display=DisplayStyle.None;} public void ShowReceipt(CoreAwardReceipt receipt,CoreDisplayContext context) {BindFooter(CoreOfferKind.AwardReceipt);Cards.Clear();var card=CreateCard(receipt.UpgradeId,receipt.Tier,receipt.NewRank,true,context.AbilityName,()=>{});card.Bind(UpgradeCardFormatter.Format((UpgradeId)receipt.UpgradeId,receipt.NewRank,context,CorePresentationMode.Awarded),Details.value);Confirm.text="Continue";Confirm.SetEnabled(true);} public void SetAwaiting(bool awaiting,string actionText){Confirm.text=actionText;Confirm.SetEnabled(!awaiting);} public void ShowWaiting(string reason){BindFooter(CoreOfferKind.None);Cards.Clear();Status.text=reason;Confirm.SetEnabled(false);Details.style.display=DisplayStyle.None;} public void Dispose(){Panel.UnregisterCallback(OnKeyDown);Panel.UnregisterCallback(OnGeometry);Confirm.clicked-=confirmAction;inspect.clicked-=ToggleInspection;Panel.RemoveFromHierarchy();} } }